From 2b5cf47ef897faff8404f222f0f7a857e4945fc7 Mon Sep 17 00:00:00 2001 From: aleien Date: Wed, 20 Jul 2016 08:42:48 +0300 Subject: [PATCH 01/15] About, mail and headset reaction --- .idea/.name | 1 - .idea/encodings.xml | 6 - .idea/gradle.xml | 11 +- .idea/runConfigurations.xml | 12 -- .idea/vcs.xml | 2 +- app/build.gradle | 10 +- .../yapplication/ListArtistsActivity.java | 108 +++++++++++++++++- .../yapplication/utils/IntentBuilder.java | 34 ++++++ .../ru/aleien/yapplication/utils/Utils.java | 22 ++++ .../ic_stat_hardware_headset.png | Bin 0 -> 789 bytes .../res/drawable-hdpi-v11/ic_stat_yamusic.png | Bin 0 -> 1119 bytes .../ic_stat_hardware_headset.png | Bin 0 -> 981 bytes .../res/drawable-hdpi/ic_stat_yamusic.png | Bin 0 -> 1207 bytes app/src/main/res/drawable-hdpi/ic_yamusic.png | Bin 0 -> 3727 bytes .../ic_stat_hardware_headset.png | Bin 0 -> 521 bytes .../res/drawable-mdpi-v11/ic_stat_yamusic.png | Bin 0 -> 675 bytes .../ic_stat_hardware_headset.png | Bin 0 -> 617 bytes .../res/drawable-mdpi/ic_stat_yamusic.png | Bin 0 -> 761 bytes app/src/main/res/drawable-mdpi/ic_yamusic.png | Bin 0 -> 2268 bytes app/src/main/res/drawable-mdpi/ic_yaradio.png | Bin 0 -> 2401 bytes .../ic_stat_hardware_headset.png | Bin 0 -> 1026 bytes .../drawable-xhdpi-v11/ic_stat_yamusic.png | Bin 0 -> 1502 bytes .../ic_stat_hardware_headset.png | Bin 0 -> 1312 bytes .../res/drawable-xhdpi/ic_stat_yamusic.png | Bin 0 -> 1696 bytes .../main/res/drawable-xhdpi/ic_yamusic.png | Bin 0 -> 5240 bytes .../main/res/drawable-xhdpi/ic_yaradio.png | Bin 0 -> 6072 bytes .../ic_stat_hardware_headset.png | Bin 0 -> 1716 bytes .../drawable-xxhdpi-v11/ic_stat_yamusic.png | Bin 0 -> 2463 bytes .../ic_stat_hardware_headset.png | Bin 0 -> 1820 bytes .../res/drawable-xxhdpi/ic_stat_yamusic.png | Bin 0 -> 2633 bytes .../main/res/drawable-xxhdpi/ic_yamusic.png | Bin 0 -> 9065 bytes .../main/res/drawable-xxhdpi/ic_yaradio.png | Bin 0 -> 10750 bytes .../ic_stat_hardware_headset.png | Bin 0 -> 2682 bytes .../drawable-xxxhdpi-v11/ic_stat_yamusic.png | Bin 0 -> 3451 bytes .../ic_stat_hardware_headset.png | Bin 0 -> 2565 bytes .../res/drawable-xxxhdpi/ic_stat_yamusic.png | Bin 0 -> 3521 bytes .../main/res/drawable-xxxhdpi/ic_yamusic.png | Bin 0 -> 13315 bytes .../main/res/drawable-xxxhdpi/ic_yaradio.png | Bin 0 -> 15153 bytes app/src/main/res/drawable/shadow.xml | 8 ++ app/src/main/res/layout/activity_main.xml | 35 ++++-- app/src/main/res/menu/main_menu.xml | 9 ++ app/src/main/res/values-ru/strings.xml | 5 + app/src/main/res/values/colors.xml | 6 +- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 7 ++ build.gradle | 2 +- 46 files changed, 228 insertions(+), 51 deletions(-) delete mode 100644 .idea/.name delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/runConfigurations.xml create mode 100644 app/src/main/java/ru/aleien/yapplication/utils/IntentBuilder.java create mode 100755 app/src/main/res/drawable-hdpi-v11/ic_stat_hardware_headset.png create mode 100755 app/src/main/res/drawable-hdpi-v11/ic_stat_yamusic.png create mode 100755 app/src/main/res/drawable-hdpi/ic_stat_hardware_headset.png create mode 100755 app/src/main/res/drawable-hdpi/ic_stat_yamusic.png create mode 100755 app/src/main/res/drawable-hdpi/ic_yamusic.png create mode 100755 app/src/main/res/drawable-mdpi-v11/ic_stat_hardware_headset.png create mode 100755 app/src/main/res/drawable-mdpi-v11/ic_stat_yamusic.png create mode 100755 app/src/main/res/drawable-mdpi/ic_stat_hardware_headset.png create mode 100755 app/src/main/res/drawable-mdpi/ic_stat_yamusic.png create mode 100755 app/src/main/res/drawable-mdpi/ic_yamusic.png create mode 100755 app/src/main/res/drawable-mdpi/ic_yaradio.png create mode 100755 app/src/main/res/drawable-xhdpi-v11/ic_stat_hardware_headset.png create mode 100755 app/src/main/res/drawable-xhdpi-v11/ic_stat_yamusic.png create mode 100755 app/src/main/res/drawable-xhdpi/ic_stat_hardware_headset.png create mode 100755 app/src/main/res/drawable-xhdpi/ic_stat_yamusic.png create mode 100755 app/src/main/res/drawable-xhdpi/ic_yamusic.png create mode 100755 app/src/main/res/drawable-xhdpi/ic_yaradio.png create mode 100755 app/src/main/res/drawable-xxhdpi-v11/ic_stat_hardware_headset.png create mode 100755 app/src/main/res/drawable-xxhdpi-v11/ic_stat_yamusic.png create mode 100755 app/src/main/res/drawable-xxhdpi/ic_stat_hardware_headset.png create mode 100755 app/src/main/res/drawable-xxhdpi/ic_stat_yamusic.png create mode 100755 app/src/main/res/drawable-xxhdpi/ic_yamusic.png create mode 100755 app/src/main/res/drawable-xxhdpi/ic_yaradio.png create mode 100755 app/src/main/res/drawable-xxxhdpi-v11/ic_stat_hardware_headset.png create mode 100755 app/src/main/res/drawable-xxxhdpi-v11/ic_stat_yamusic.png create mode 100755 app/src/main/res/drawable-xxxhdpi/ic_stat_hardware_headset.png create mode 100755 app/src/main/res/drawable-xxxhdpi/ic_stat_yamusic.png create mode 100755 app/src/main/res/drawable-xxxhdpi/ic_yamusic.png create mode 100755 app/src/main/res/drawable-xxxhdpi/ic_yaradio.png create mode 100644 app/src/main/res/drawable/shadow.xml create mode 100644 app/src/main/res/menu/main_menu.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 8598a75..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -Yapplication \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 97626ba..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 2ab1e27..7ac24c7 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -3,22 +3,15 @@ diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e4ec7db..2cf6cd2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,13 +3,13 @@ apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'me.tatarka.retrolambda' android { - compileSdkVersion 23 - buildToolsVersion "23.0.2" + compileSdkVersion 24 + buildToolsVersion "23.0.3" defaultConfig { applicationId "ru.aleien.yapplication" minSdkVersion 15 - targetSdkVersion 23 + targetSdkVersion 24 versionCode 1 versionName "1.0" } @@ -37,7 +37,7 @@ android { } dependencies { - ext.supportVersion = '23.3.0' + ext.supportVersion = '24.0.0' compile fileTree(dir: 'libs', include: ['*.jar']) compile "com.android.support:support-v4:$supportVersion" compile "com.android.support:appcompat-v7:$supportVersion" @@ -54,7 +54,7 @@ dependencies { // Testing testCompile 'junit:junit:4.12' androidTestCompile "com.android.support:support-annotations:$supportVersion" - androidTestCompile 'com.android.support.test:runner:0.4.1' + androidTestCompile 'com.android.support.test:runner:0.5' testCompile "org.robolectric:robolectric:3.0" testCompile "org.mockito:mockito-core:1.10.19" diff --git a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java b/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java index 4c72636..6937dd8 100644 --- a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java +++ b/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java @@ -1,11 +1,30 @@ package ru.aleien.yapplication; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.net.Uri; import android.os.Bundle; import android.os.PersistableBundle; import android.support.v4.app.Fragment; +import android.support.v4.app.NotificationCompat; +import android.support.v4.content.ContextCompat; +import android.support.v4.content.res.ResourcesCompat; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; +import android.widget.Toast; + +import ru.aleien.yapplication.utils.IntentBuilder; +import ru.aleien.yapplication.utils.Utils; public class ListArtistsActivity extends AppCompatActivity implements MainView { private ArtistsPresenter artistsPresenter; @@ -32,6 +51,19 @@ protected void onStart() { super.onStart(); artistsPresenter.attachView(this); artistsPresenter.onStart(); + + registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + showHeadphonesNotification(audioManager.isWiredHeadsetOn() + || audioManager.isBluetoothA2dpOn() + || audioManager.isBluetoothScoOn()); + + } + // TODO: Реагировать на Bluetooth-гарнитуру + }, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); + } @Override @@ -46,13 +78,29 @@ public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistent outState.putSerializable("presenterState", artistsPresenter); } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main_menu, menu); + return true; + } + @SuppressWarnings("ConstantConditions") @Override public boolean onOptionsItemSelected(MenuItem menuItem) { - if (menuItem.getItemId() == android.R.id.home) { - getSupportActionBar().setDisplayHomeAsUpEnabled(false); - getSupportFragmentManager().popBackStack(); + switch (menuItem.getItemId()) { + case android.R.id.home: + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + getSupportFragmentManager().popBackStack(); + break; + case R.id.menu_about: + showAbout(); + break; + case R.id.menu_contact: + composeEmail(); + break; } + return super.onOptionsItemSelected(menuItem); } @@ -80,4 +128,58 @@ public void onBackPressed() { //noinspection ConstantConditions getSupportActionBar().setDisplayHomeAsUpEnabled(false); } + + // TODO: Вынести в отдельный класс + // TODO: При открытой странице инфо об артисте, открывать страницу артиста + private void showHeadphonesNotification(boolean wiredHeadsetOn) { + Intent musicIntent = IntentBuilder.buildOpenAppOrMarketPageIntent("ru.yandex.music", this); + Intent radioIntent = IntentBuilder.buildOpenAppOrMarketPageIntent("ru.yandex.radio",this); + + PendingIntent musicPendingIntent = PendingIntent.getActivity(this, 1010, musicIntent, 0); + PendingIntent radioPendingIntent = PendingIntent.getActivity(this, 1020, radioIntent, 0); + + int musicNotificationId = 001; + + if (wiredHeadsetOn) { + NotificationCompat.Builder musicNotificationBuilder = + new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.ic_stat_hardware_headset) + .setColor(ContextCompat.getColor(this, R.color.colorPrimary)) + .setContentTitle("Headphones plugged in") + .addAction(R.drawable.ic_stat_yamusic, "Ya.Music", musicPendingIntent) + .addAction(R.drawable.ic_stat_hardware_headset, "Ya.Radio", radioPendingIntent) + .setContentText("Open in:"); + + NotificationManager mNotifyMgr = + (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + mNotifyMgr.notify(musicNotificationId, musicNotificationBuilder.build()); + } else { + NotificationManager mNotifyMgr = + (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + mNotifyMgr.cancel(musicNotificationId); + } + + } + + private void composeEmail() { + Intent emailIntent = new Intent(Intent.ACTION_SENDTO); + emailIntent.setType("text/plain"); + emailIntent.setData(Uri.parse("mailto:")); + emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"technogenom@gmail.com"}); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Re: Yapplication"); + if (emailIntent.resolveActivity(getPackageManager()) != null) { + startActivity(emailIntent); + } + + } + + private void showAbout() { + new AlertDialog.Builder(this) + .setTitle(getResources().getString(R.string.about_title)) + .setMessage(getResources().getString(R.string.about_message) + Utils.getAppVersion(this)) + .setNegativeButton(getResources().getString(R.string.title_dismiss), null) + .show(); + } } diff --git a/app/src/main/java/ru/aleien/yapplication/utils/IntentBuilder.java b/app/src/main/java/ru/aleien/yapplication/utils/IntentBuilder.java new file mode 100644 index 0000000..a231202 --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/utils/IntentBuilder.java @@ -0,0 +1,34 @@ +package ru.aleien.yapplication.utils; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; + +/** + * Created by user on 19.07.16. + */ +public class IntentBuilder { + private static final String playStore = "market://details?id="; + + public static Intent buildOpenAppOrMarketPageIntent(String pack, Context context) { + + try { + if (Utils.checkPackageExists(context, pack)) { + PackageManager pm = context.getPackageManager(); + return pm.getLaunchIntentForPackage(pack); + } else { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(playStore + pack)); + return intent; + } + } catch (ActivityNotFoundException e) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(playStore + pack)); + return intent; + } + + } + +} diff --git a/app/src/main/java/ru/aleien/yapplication/utils/Utils.java b/app/src/main/java/ru/aleien/yapplication/utils/Utils.java index 6b5c718..eccb8de 100644 --- a/app/src/main/java/ru/aleien/yapplication/utils/Utils.java +++ b/app/src/main/java/ru/aleien/yapplication/utils/Utils.java @@ -1,6 +1,8 @@ package ru.aleien.yapplication.utils; +import android.app.Activity; import android.content.Context; +import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.support.annotation.NonNull; @@ -78,5 +80,25 @@ public static List decodeResponse(Response response) { return resultList; } + public static String getAppVersion(Context context) { + String versionCode = "1.0"; + try { + versionCode = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + + return versionCode; + } + + public static boolean checkPackageExists(Context context, String targetPackage) { + try { + context.getPackageManager().getPackageInfo(targetPackage, PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + return true; + } + } diff --git a/app/src/main/res/drawable-hdpi-v11/ic_stat_hardware_headset.png b/app/src/main/res/drawable-hdpi-v11/ic_stat_hardware_headset.png new file mode 100755 index 0000000000000000000000000000000000000000..c9507cfbd81b23a1b4e4aa038896c23763225c08 GIT binary patch literal 789 zcmV+w1M2*VP)YOqoRC!Pko^5)UHbNs1xTkZCd~q~s0d z!PkQak&nrQ$kLkj)xKwD?m1_=rlGr@?m2hwwSIej?7i0!-AvTYc*q50X8nLEzzkpx zFbWt73qLBMLD z7VzJlkq^Kw;JlgPDQMY#i_)0prid zfciAI7Yv1PF9E87=@EcKO_TII3Dw3!W;Px;1h^uXpFk6^L6TQbZZWgbzzJY3&@(8g z2ewIi7Aq_QGBeMCBfz>a=ofGi*eb~@rqg0(BY?xe3ZO?&bQ-9U^rfIU3FM{iNmq!u zhiSc}C!HpVCYsqK;8YYo4_#HfCHw(0vths)U{TQV64)fEp&T$#UM&EQ10F^f51lXi ztv|#-YlC+R<-H%+C&|NBHZd~~*B)SZQ0m)(d+BTv$a7~)WOn^K(8>bLDIq`knag&!hpw;Cg(AdcdVH`1tx@fFC>jk z0(t5AY3_1e(&D^E@{BjLYf%BUNa~*ivPfZ_q*Zwgh z=v`g*&5{Nt4_EIhy-i7zzp*hkN%HQv=(P8%fH#8P3U{T4{FvG-sV)iRm-~&t&J?I} zddOi8coQkfFZ;#chs?~OyklJx!dLk26ML*2K>l&UyWw-8HE)G!1C*9LUGe{Kw24+U TDwBVF00000NkvXXu0mjfdHG-3 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi-v11/ic_stat_yamusic.png b/app/src/main/res/drawable-hdpi-v11/ic_stat_yamusic.png new file mode 100755 index 0000000000000000000000000000000000000000..95c6e2f3e8d31c19e38a9a73d25d2cbdc8b438e3 GIT binary patch literal 1119 zcmV-l1fctgP);qsk5JbUf6-nF*a%uJgZ4}%UA z5r+Xc0gnL-Bz@D%p*IE4;lT2Q_zrj;SO_eV^i>nwrUokaUk$tnJO|8?^lc}&e-o(O zf4ro*{{z*;%YWvfcYzOplY!j|z`+NKh%JEq@^3o_tE6=iQD)hrlFo^Uy@6AKlYkR} zO9w56B4Q*kJ~!~@+W@P8gC(tsh$Dby>5V?1^6DaDS3n-SQ9&IM2LM+A699Xpv=V3o z4>f5|`Mp)zN5m-L5qqJSybgF1SOI(n+$QPQZXSw=A(DP?f@=hdh%vxJMNnGcZU^q} zR2FK0W&&?XvTAp=TA+v+4Lkwtkt;j{Oqb;B>1dsT+5;ovW?&+4rleQ8&F>17!M7N2 zklG#xCQJIU?t~3M(=y;s={cw)Vg_(+ZvF-^rqd!3;Q-&)P5{0D4(*g>5pg4MS*|xz z(siw~EFyN#fZZSXEn!@>2P`5CF&#K9waDK-1$>>S=fHuqj|l6}(p z1J6G|c@V3$y=1t#k}R<6SvL=T3>*Y}2@IF?a{**woRf?83bZ5e9mEO4oxHErv$!)DW%vL}dD;6#5jW_li zYOJKC4cFB1bM{mAhPwuXo-N7ZyMfhJdxKc&&&^pMxqXoIb}vv7ag<94a7fO3*jHWk zVE>v;{m@?NCF_RG@AMmh*^+*!1B-~A0SEErz@9mFNqXn&ZgbmojCQjN?HR=)?*l%m zpT!>nYx3JJ*?#!hW6usR=FD^Ec6&m%@=*Vvh_L2d0XU6I{c#827NWn67z+5WJgD`^ z8F{@V|6gm39x7)?#4)K)<8tG=#SLlcu-?4h1k#g-s*7cF<}0);xGQj(vH1b85^!42 llk|C8kc|TBb(3Dk{|(-#sWi}uNmKv;002ovPDHLkV1fc#1Q-AS literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_stat_hardware_headset.png b/app/src/main/res/drawable-hdpi/ic_stat_hardware_headset.png new file mode 100755 index 0000000000000000000000000000000000000000..6b70e37ad855c939f4bda4e77dfda59eb4c7b281 GIT binary patch literal 981 zcmV;`11kK9P)0iA13bUX>6C5CuK~f=$>U7DymL3QAQGD`dt_Jjn!v5oD5?iS0a; zhBg(YMS@KwR%C$$UjRW7AP^D-bx|G?%PvaDx`=y626TS|E|W2_*hY%#`uC8F;D;3FZ#d$w)= zl1KVm55h1kjEszoan4_g$-)gm5XC23B^@IKb zSYKbiRnxQuA;dFr9Gj{j5nW`AeJZ8Ah=@BxbR#0(PDGC&;%y3O7)FP4{(hRK^OKX4 zKlMVc0KzalXqx89G)+g7BvEhr8xbAnoS!Kci$5YFbrXpQS5{W;5JHS0;sg=hr0Y6p znzk*aJlYG%0Lqp^p>Vw2ZXda9$9VuK@9gY+?EAjV^Vj!%W?9x_jIkvEIIL_lP4jfS z-L|`SWq{4i&10QTXEjYz69CRL#>O1S`D*BhTn=b78V?}i831@t)3lCZ7%xvuOq^89 zxVE--7y!0Aoz4LO_?n2G^gQpv)fVr@g<*I|2=M^`+-I8R&z$p7+qS>Jjg5`hn$4!K z0vThj>$>4yi+AILAaJCVfqJ$q%eQU&b=9m7lO!2c)1vG8qoq>mhie5yQFLdLB%i41 z(sg~iSS&t)K@fZ|gt&__cFuL(r+cSmuOKyDK@gmiQa;5wzu>y=A*|Qyzbi$E7|zVh zxO*+yAKz#+0wQt%py_$u4GKVNL+QHyrsFuT{#!s8hHoTE@`~Dkp64OfYPDt8W9q3%%>=m`T$#`)PJ|_D{f|BD$`ha$Wam9v}#Utt3gt^VT!K z;^N|!L##hg0j8&?a{`l+ zLjh@;<|+8RQmH(w0LO?(9nsdHDY_a31IDz8h)$(aIf1_K8@jF^0RRQMx7RMEpjPcYNQE_y0N$J>B;$8fwuW?*9x$11?m%00000NkvXXu0mjf DdM3wx literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_stat_yamusic.png b/app/src/main/res/drawable-hdpi/ic_stat_yamusic.png new file mode 100755 index 0000000000000000000000000000000000000000..19c7e819c1815f3ce2d5079077c7d3611f2e3470 GIT binary patch literal 1207 zcmV;o1W5adP)r1yCYz5oG;g^t%g&yA ze*6EQd+r&8Enc|A`C%Kdp~Kr2ZH+}!Q&V98*e8UT5kh=AFfj1jTBJ?YU}9oogma!I zgn-B6Spa~q0ASj2oKFIQ!0*klO#v#E%7|^-X+#7e1SpCE5=JTg%2xR*I&ez>QUL(W0xaGJ_sXP-qTD>7R;$t3+1W=4A)Sn|UxLBlCD$SVI6)~L z;G91vgyG4Ud$)UYgTr{g%XkbeTemA1CFV}pZ(wUq#=vbVRFt#(vfPEoa5jd9M8 znx;ts;3Ogz=jZ1y=(^6`1QO6_G}4NqEGnp0tFHjS1Af1MY$aqFz%Yy+&iN_JvSjku z!ootOIkCIBR4OeVQ$k2B8jbdq%VlZP2ZWG=!C>(8sumTCMW3Q5U)SsPE<#9F)3j7` zpkWvfQA+P;jQznFJLJ|t0xFftOQval?DP39&&|zs>$)zRZ*g5$FgZEdU$58S6GHqb zgm@+%kN+zJCMPF%5<6ZX;+qHzrz@Na~;5x zrfF|2#da~qE(5?qB9XYQ1(3~VF9N`mKA-QkP$=}F49I4)djViZUNfseWKs4flgULp z%jNRndcA%e04^ny$=xl0d_F(MIUiD0^<+34e$@@gWHJ#%JPH766T}ok45w15VpDIN zFio>hp162C9&G^>3WYAman4gpiK?p42ZO;c-Jo19*UdRUgoqtNh^vHf_;qV=Gn;QDc3Z)QE^WC*{{xAF VHAQdyJ`n%_002ovPDHLkV1npeE$cM4 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi-v11/ic_stat_hardware_headset.png b/app/src/main/res/drawable-mdpi-v11/ic_stat_hardware_headset.png new file mode 100755 index 0000000000000000000000000000000000000000..c4a65a841b40bc008f543c33c7cddf074f2a7f94 GIT binary patch literal 521 zcmV+k0`~ohP)6vpw-6)D8Opin|&$%?X*Y$h9JQbeIa_7<$gEGVQzvcN{NQ?g;F$Vf^4213cW zb-z8&^vvshhnX=}PCbj~*ZE%Oy3e`qSm4O8{H2to*p7YJgwc-IKJo8)^J z)DNh3T*ESaz@>T(vY@f>7>;8VYBh(F+!+XHY`Nbke}b_j)#n+b`EMLMno35xcJ(S~ z)ZD^IqkPYwByY(L6t3{B~e@IAVMjn zt$gTz{TBTPOgS*Dix$kHx`!_sw2*UL5lCYz*I@se%m73OMzVpIM#gtc1Su{Nl=u;%qH7j2OI+W09S#dK&zxT0hXa)v82mc zL_G*>&j&66pMZ_PSxH~Z7iSPeR5wVvQD~9vLBMTb9I#wcxUi!8j7%wTvatU4oAAzxv9NV#*CqOj6vZ9%_k~(eoP6}!yh3Z^_mheBF$_Hs@ zha-X+1Wl466w#;2Nokv;*+m4KfxSSZq=vLOcHfw;=K;WH;1sZ_6iM6r<2lkMX+^T9O7XeoO_Z{0u4JbWM-fRE>002ov JPDHLkV1lM?Bi{f3 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_stat_hardware_headset.png b/app/src/main/res/drawable-mdpi/ic_stat_hardware_headset.png new file mode 100755 index 0000000000000000000000000000000000000000..deb07a47c504bd7528aed372535f654772d3d78a GIT binary patch literal 617 zcmV-v0+#)WP)X1^@s6IQ*`u0006oNklD-o#?Dw|?Ii9@Ojg+nn=fp65N0QW{FB&qVZ1ixKe_W9%O1 z+%pX0rIa!okH^2x*7U1ZtKH2!K^nay*J>$*n(u%ZFP7&}U(QV%Wy zD3wYdl~Svi&*#Z7jNjpKxaR{rkH_N&eqg;`-vEH^d2KWrt$ChzZ3fUq#@Is77~4xE z5=}p#QmMq0QjK{{saZq-bGh6Z;{t&|ESXI1`vK*0d3QRUo`5a@M5N=YGe7(O3ow~X zntt6(pUq|k0C4}`g2CXq9caKQqooEM64AP!%sG$QwtZ%V@pycq0|5AdnM~#`BDw(Z z5D`t?mLB0Wz;)eSAw=`X1^@s6IQ*`u0008ONklL6cs&{q?;^BHb;>`y|gdU_Q6fGWfT@Ror$~XWZgisG-?0O=RSo#f6 zC=~WYB9R;b^Z`I80OVV()=ep8lQ9-gCX@F)$k^Bz+VJ3Cp^xDP(PofZ_8487Z2_X(b zsAySMS0|dc?uNN%KN&>)fUDuaQ(~MC{Z(xk~ODWgZ*Viw20M~V23n7-;+uLi2 zL}J(nn5OyGn;aS%>MNB>VaC`?&iMf;rQ+EaLMV)}UR6~aQ&Us3l+vTARO+w~C>Dz? zLda+)lbNhmt39n&>!J5#o15oA)3ipZRMMrC|mdZlzM`O8Hd?vFN() rosp4|xy_O7s2aEXGq)}0e;@D@D}h@nK5#h000000NkvXXu0mjfaj#J= literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_yamusic.png b/app/src/main/res/drawable-mdpi/ic_yamusic.png new file mode 100755 index 0000000000000000000000000000000000000000..b06a63a15948f120c25440ceee23c5ae9231d051 GIT binary patch literal 2268 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 TLY)ZL00000NkvXXu0mjfUP_zd literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi-v11/ic_stat_hardware_headset.png b/app/src/main/res/drawable-xhdpi-v11/ic_stat_hardware_headset.png new file mode 100755 index 0000000000000000000000000000000000000000..dae527f2fdbcc0d59cbf8f0022e5d2ef34cdb37b GIT binary patch literal 1026 zcmV+d1pWJoP)|rlUlqf6kQTPB&2Uo1Dkpz`RL0DNq=0DcgxpMCIoO>SL@5l#tjV|Y|z1I4#*WP<=F~La_+fRBKWflq+1B>j4>4r~p;cC5Vucm~+m(vY3N7T^y_ zZX=@=0NXQx-M|vyk~WHSYyJX02Ubfu)>Kww0Jfh5HULwbDy!f>0qm6YcoVs`0oa}n zya_C=Q1Ek>@4o@z-Jr?96yO>lj0?*!1e;*aD~7(A~KS`9>kZW-*dpK z3VBN;MX?X0N&vPOW}d&HKvH;k8}PEEZ>yB$|F?Z3um*^fxvXGqL|~<)LxbcF1YrA0 z;2>~QBII9SKk!JK!b^aJV_pRA0wyJT!YNB7MOqe8DFE9S0`CL2CM!$KncqZjpAK%;d?J^+QzVbk%V*$@5NiWm}V0&J+Yewhj z(PyyEJ#?}iDHJskt@?tTg5R@L`Z85o7y;WmfQJ+2&mx^9{XKNca{{x}{UAG% zlT#e^H&apo8KX1;cL47tmfkPvwa%>vY_9}fPjs)4^i~;wXkT%@ z&(mT_pPnZG^Rvs(#X0@kByBDO5Z;d{Qss$t*q+bJvmN*5xO1POyiI8lj{poi8lwnI z_!E2PbWbA+2Pt{<2EIC-xsEBJQ=o46nzsF;{-7qWH87sR`9fR7Y*$6|uC23q-vM6K-XxQI_mI%8vcl!ysH zOadBF#{dpUT3&Xgyc0Ok$oix!G+Em+h3=EIrwl-hFOC4Sn;6nz1me|ZuB1ryF)Esk z-Uhr=^6XQHt0Mq0PK?LDzQJ4Bc)byb4gkb-{BB9_rXC#hLK7qI)xf<#JoJ^m3y%|k wcsGtG*L}dNlD;nr|9PGHG^w*P-U$!jKRVE!Kvy<(zW@LL07*qoM6N<$f&_HpsQ>@~ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi-v11/ic_stat_yamusic.png b/app/src/main/res/drawable-xhdpi-v11/ic_stat_yamusic.png new file mode 100755 index 0000000000000000000000000000000000000000..701c0d1c8e2982d13621abe540032e129cdb88a2 GIT binary patch literal 1502 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_yaradio.png b/app/src/main/res/drawable-xhdpi/ic_yaradio.png new file mode 100755 index 0000000000000000000000000000000000000000..83956e2acf02725b6e9284e79660e080844cf6db GIT binary patch literal 6072 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 ic$)72gsr0lA(eE_vBmV~zDp42! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi-v11/ic_stat_hardware_headset.png b/app/src/main/res/drawable-xxhdpi-v11/ic_stat_hardware_headset.png new file mode 100755 index 0000000000000000000000000000000000000000..b919e7f2e555dc008664782d1464d7566945320c GIT binary patch literal 1716 zcmV;l221&gP)yad&|nD~Yfus}_C_=kM8S@z zvByR<8WoKOOEh*p@{_x|c{}rFUfJEZaSlATyqP=a{`d5I&xnfZBA^PN`Uq426-Y>* z0TdC-0L!M&4!};p_Q1BlYQPr2a*5yHf!V;fz=yz_>GL}9hd+}3>Q(Gnw5o-Y#1A_toZiCz(^j9+Mj{uiRvKqETEdvx08vzdi z_DuFtoj~?7WcFB7H4+E=|Wa2mAN8o4RRp1HW6W~)|jwF9iL>TBz zlVRUFsqD^x)p+Fw?3(SFFMyJ&kIGyGE{ z3`fL@X+e%k#@}Ai3$s+gIKKkt0e3cIml_5rB31{6rH5`!J`6MaR9rU#tvqXde z*$g-wI4DV%7pxx51Fi$^kn~L>3)2ul5wSdQ5^y1~alwP$1*X`e03WnFMu~`}fh_^Y zS*N6^ydFzm0OJx!ma0-z8lbdtj%#iOY!~v#cEoCQJutIb-7m%cB70?9xFc|W0;?-s z$r{2j(mB9WWf!awfSevU7_+g=(|hMo*8&qIdBysSB4S4(HyA2rA-{5pHm%JD z43adr9!PZoN?Ezp=FB=#vcWs8QTJK2a;<(J5iYZM;f|>@)uJ^CKUm6IWQV<{8LMgC5B77yW^1Q#!y5!@^*%w zH3x?z6C{mo7@&x-&A9>CC&$n|2#k}CL>ve_kdst@{*T%hWBN@>#Zz)d;ThgXs}>tU?( z06H?cNQdvWrx33UR z!K1yhlhs*`= zepE}dCAk1sN`gL0WzE{_4b0%81h20q+~{*6!du`@o%YSRcdn!!Rnj~avn$WiQ`eUd zmSkHr;3$=h?@#sYTK*SG>M`d$fbyc_rzPz-;E?IYa75(US0weoqXMWKcojg4iu{!X z)iVJVKm`V}0;mG0H6L0@P(MqMPh>Pxpr0nm+c0gTi13LGS3MggAFIhckid8CBMbZUYPyti{)S98JB&d?0N`n4Hpug)u-T z{lvQ7lZ;N0RCQb~S`OePiD zmx%DYeSG-Zn{-)c`^uteQ!Lvx%azz6DS18H>VbCzmvnKM12b1r$qhwo1%X8 zu4nf*Knnq%(0$8qhyh*(=9TGuE(K5-5m{2NsQ@Zi*$SWnfc^pS6Ut{Q(CE4V0000< KMNUMnLSTY}`4KVz literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi-v11/ic_stat_yamusic.png b/app/src/main/res/drawable-xxhdpi-v11/ic_stat_yamusic.png new file mode 100755 index 0000000000000000000000000000000000000000..f6a8d6c5dac3dc98236d482311664e4809c87f16 GIT binary patch literal 2463 zcmV;Q31Ie#P)!4d>oG~PPPxuIiV<-qNsp8C z2uTl+v{}+2&GWs=_A93Vl+C?ClC1S?pI4x(t~F#uJ?V&GeZbgV(vwn7r*F?$0U85H zK4(P$iiqncV4fjqH%WIWWOnTX6cOl^gC#vp((MX?Sqxj-07b-Bl3pk2p@qQwP10rQ z=W0psv3>GfI;fC`-eI#nv50t>q?07wqkyp=NcxPVFG~6j89+%_w`8-8ap<2esV4c_l-Z1a0GO#p~JKPYKm+gFTb zUt&l`nT}21{CX^76Q39o1YINP5ZiAa%f~2yB7*4oW0D9TnuOv#J~4IG zSf-luCQ1-X{an&cw!b=tpK5?2;zsC{6h7p8o|cs3Y-V-_fZ!25`5fCwcNL8RXgf(C zZV5E;ZFaFulR+^>#4RM@%C?y{yY4dF1E<|fD}_LK`>dUFJ+z!nK&%!;*yE`I+RyRy@ljylY|DdS503x7e++9kUDjYWi5R!u)+B#eBeA~Hh z>fEwMf^gY4%vL(tHhJg1=e47xQ)jSR`5wIhF*qXdpmY9yYWuE*mrx}^_&B)^InMSA z1_0VWIc}vqm5wU`2#1mVn{at9dxPfJWVr;1bwTek@C}$w(D!NjRd6myFed&(3 zJNHHA5kWPVi=j!PNvGQ0rIeQ%fau40akelO3L@ll2Sgo9hrS@qF_rM%53r4X>7ze1H|0HRT zA>Gwvum&K4RNnLhvlE_?80G_$K9B$hfQjnI($CTX0jX~)*>nf zh}9yZ&GGt!q#bPYeMB!s1Uig-Q0|crP37;@*f8zrv!Q*+Ql2g;cL|RpGdtn2?^iTHoz5AEkJzz$CG+w zw!?3dRp}M|#hlb_MnsJ@!Q|#1IztJ7G2=WHYVG)8gA@@wWu%POy(WQHwML_VnBLYM@V|Pq&!JRss`!l^w+w!ss<=Y5lQhA zQnjJEHWdhN>irdGn`LN3&JWD7cmDoY&2Pd7Tl&=rmReHF)t5w9SReS zhs?ofxFYXOzWC+|Xfl74yi14@IF(dBh?(@Pnw!lAPy;BQ!i%sTE$)8FL2Jhl=3q^a zvVC#wpCko@w;$_KVU1L@RpoTOp8l@Ir z`_P3yWlx^Y4T5W9l*^A4C*SeL7+fC0l==|;o+U(aQN>^ zU@8JgrBVw7LHJPD^|`95=Da%@2L<7Io`MV_gr06_XgF@yt}TFpfdSDljBSLF*Rt7c z-DC;3iliVOkB6q#yO%Ft4z{(mjg|u`3y?@8rhC2K0aaC>q3KfMzyNR#0L~(Wb`wHQ zVT^wcg+do-`&245i>4a@EGC4k1c12!FdG24V)i(Wqp5X3mgO~mzyH$JAg&6KOeX8S zUhikwZ1!$lr*&8KI6?^7iZMPV%kl+Dl8mbjC`l3{isG#-%iak9n*d-rK;hPw7JN-j z%`dvHKi=Hj{97rTB?02`_)Jk0_hvGgM`+3vDafw@AUZNKazK)#l5|&t*|TR4J2*JF zf?=2tA!Mm})}rGv3_~G~V~m%zw6y$F%vMo=fq?-YWBjqAC@T!Zu!`3SA>D!?ywTFq zqLj-~JKsx^B-GW_MF=4ggb;6b)>tGC>$?7EvGIxmq|@p3nx@4u#)3t_FaU%{Mn-ly z^?E5FLI~>X>w6gh;s7wy+>T)w&ExU3H#Idy3ku(QA(cukLI|Bylmh1}2%(jMK;R2I zUUk|g8jU`H5c(7VZZ_MX>jlsA4>dM69x-1ph`{#k+doniWtAo8RYHg_7!2-22u;r9 zUxJVpd2Ma&vxJa$5JG}khbW2%FJ8R3+#LN00Cb_k7$4JhT`(7vVHj_QLZNj|z3QZ2 zG#cFj0F9R05nU9;hs{x#0FX|n&&skq$1>n$j^pOFwzi&kBE)H*&d$zRJkS3M05_R! zc)i}!KA&&?__aL1jvYIeWHOmUmQ@xbgkA~;gS(vyanmmvjXnVY`v8E=3%;^iKj!oK zKBw360O@qPLzd-;Wx&&;qoa!?Ny@qr;-+tJZ|_lr(EVl`9*-y8+}zwe0U()79@R8$ zLPLgOl7T>=)r|nReWTGRU9Gzcmu(@tA-(J6g~-|v@mDU?d3o>vs*eaqDO$LQ$j?d9%m+=#Q&ckJHj3_|EOv%YE5 zroFI!{rcTGfMhbcQ&rVhEe88Tp-_{ZL9TV1-rnA~0AQ^pjo#k4apT$?Ks+9&d(YMT6tGReryJznuZDb(>f$ zwiIK0(5#PPn7@L-;EWtVPfriA-pUGsu&lMU^~-B5&W`=QzP?2mKB#0@%p_*KPhE5wI4>^;QFlqMWc!G~sagN)2>(cNfe;qA2Dz z)z(hd{{bN6^Z5$4dUojCPcuI1pvBFy*>cSg|XE4ZUM5{?7nhoTI&1Fn>Synfv&Euf*P=E zR1gH$5pe3fdv(xb`R8v#qY8IkJ?9_SrBBlUNiATh%Hs%EXxMM;jp8Xm=>{({lE_hp|1!bdv#s^Mv^4e?AWqDHLw$fsccgPm`d*# zE#j#bRO$HD6spu?lij`=fywr0rS_^3sMKSV-M$)u$@XZa_WlK*kl={7ajn?^0000< KMNUMnLSTYj(`g?7 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_yamusic.png b/app/src/main/res/drawable-xxhdpi/ic_stat_yamusic.png new file mode 100755 index 0000000000000000000000000000000000000000..108b35e64b60bfa1504f09fdff045534f6941d1a GIT binary patch literal 2633 zcmV-P3byr$P)5uPK-di|s9pb`dw=HwImRD2#^onn0RL|bb}i7gzzQ|cb)gk1aK+?Z3#?d$D`e2MzzP+( zV)Cv9R;$(}kFbJjeT}tU* z&iRv^^T#8R$m^YeIieOQm&z!GPBcwB2mpJPQct+9`&c*}-nSHxqXvLqNFSK{`}_N^J>~#-0#iyKv@C0D z7eG6~)Xp9ZV#jeZ7{Fd2FZHcy+R$-BA6ozpj0ZIVi?9IrPOCArKd_M z`vBnA{r&y-dzvj4iz&uf+KU0hFm^?w(OdkQ_rMTBF4uMa9m}!?{%2VzpU;0Z7z}<> zO1W7nb)l5%DG3!pC&LMlRt>mA3T4U)g8l=_ij7{9PA>z_-xKq~;1 zO66t%xIiftZsq&lIV}O~-o4v!T{q7dy9IsSSA70ODaHRQBO@aR+woll;JWU77BWrq zLqS|96mFJM?lw(x+Axga?c2Azt@ySCP^nY~7-Kokx#1IjAEh)Di^U#qb%1|8m&+xE z5c8E7vyf$3^Jx?TxUPGbuItP+&EZfeG~J5-k^su(@7NxXTDRo%a^;ZuZIPmP+wQD2(N{lhkV1ccU-z_#)YlS;#NlSlu(+*Tx9%oQ({ALPe+~;V)(D(?0f1|o_A+Da z1J`vg@nQ+ns2@N!oBap?{HosE&9&UYU~ncJ4qvw{0Hsptvz+s%T-RM+Clmm%ZO4us z4>V(x&*wj;>-w}1;v0(aeV9D%Ou?-_=1aX1`)rR^9f zm&=3IYPB@)X8G2B$ z0{29t(Hq+akjv$sab5QUkH9p|Yb?upxRF7jP{8hUFJtVC<() zPY(?ZUDP&!iHV8*LWoo90X!QB1TL^F>*YoUrBZ2ItycRP{@-dmY_=W<1U?kR9{)n2 zaQ5u%?6Uw6L^@5=_Q&J#)7l0wK0baJ0FJ94x}Q?IF&>Y<-N;~SYU*~*`DiD?qjInk z4-XH2XrRF}(&_XDP1F9Olsc`h#KXyCvTr#6u({We003Siglue^5sSq_je9UL5JC{Zr|WAxnM@kX0Z=7`1nU6a6hdrl*04=YO=Vrz-O-8vBaLAg zJBNpd6OHugbb5W=0;kv4YRP1>XE^{4D5XwB^ASQ0>AJot7K^>w$Y64E^82pq?r!&< zr*WTQ7~5m9*j(@9eWmIRydi|RXmoV+KqKE=F1Lj-_JA+8o=K%r7q)GIY&LthlyW}r z1_FVJp`oFsu98ltcL2b6yTegk2&I%tj*N`#Y$jALm)q$$PTX6gl>Q`!1{lnT={Z?G(DrkRl0Z1!X+CAJ<{wXg)fw0MM48crk< zZ#CmpC={-99Oqssm7(WoO6CY0ALQse12Fd^_s5h&o9=Bd$h%3aYwCI z8%G7!1K3eYT{SW?^4rDSbIFP9;K74?0N@(mP1-xAX%5RFDLPp`^TC=@mkLhh?ptDo{Q z#9DO4=;-MDy;9pT;NO_ZWNuMP6>+w}|IwX-!Qehk(*_3z2OnE0f^xb16(K~qR;zsy zHD7-?07O!$)E%w(x9s6<+cr(poK#8;c(W=_S8LX+d7n~Rs#dGTR4Vm;tAiTXGnvfs zJv}`UDP?$ecJ_Fj)V$dn0HzsZG26CTE50oOAOj3cAw*Fr^<57H9)|CDP1AfyO8H{| zcsLr3E~u)F0@P=St0|@5V~m~Sx~{(!R{(I2rfHE@0q^l`3xJp6Ow+s#ErGL$$H048 zt?T-mN~u3lN?(vt;*09HrIdK9um<}oO6eI=%JTr=V$S*LD0sugFsp0+*2PkFM={_h zvTa+(f~J(ZjSzCGe+?~y?|1Q2zlp%R9CQIwDK&RPffvH~@%g&PVm|=f${4%Pw(W)Y z^h@|r+ZOQiNT<_hDy4=AA@~j#bA6`|UeP=N08=9)BMZ9Sc9?bq02yPk853=;wKUl&pRNTu)!b^)buF-3n(UNM r*8-htZnfyT7FaD!cFO0Nx4?e^Nw9a9b{cqZ00000NkvXXu0mjf-`Vj% literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_yamusic.png b/app/src/main/res/drawable-xxhdpi/ic_yamusic.png new file mode 100755 index 0000000000000000000000000000000000000000..e67d93b153a9c09960c851c0f776b05053bb00d2 GIT binary patch literal 9065 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 ZO+K8YgP(meaD@h>t?@v;Ow~H%{{XiN-9rEX literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_yaradio.png b/app/src/main/res/drawable-xxhdpi/ic_yaradio.png new file mode 100755 index 0000000000000000000000000000000000000000..928db32dd5c24ec6f0be6b149b879d4269c14bb0 GIT binary patch literal 10750 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 CNufpn literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi-v11/ic_stat_hardware_headset.png b/app/src/main/res/drawable-xxxhdpi-v11/ic_stat_hardware_headset.png new file mode 100755 index 0000000000000000000000000000000000000000..84bf4ef85c3dd380c32b1fa9539be173ec1dc385 GIT binary patch literal 2682 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi-v11/ic_stat_yamusic.png b/app/src/main/res/drawable-xxxhdpi-v11/ic_stat_yamusic.png new file mode 100755 index 0000000000000000000000000000000000000000..6cdb8b84360bc8c5732d40b1e2acb87b405cc314 GIT binary patch literal 3451 zcmV->4TSQEP)O11tF!fE7d3>JINYC6j_o=A*6_S z%x9e6%+LG3%enWS`+MK%f1jCmrs;p~xzByR=REs4KWn08m4KJ^?bFLO0kF)AU;yTv-W$8me3jk-5pl32K)6iO8In#*KzzmaMcu_!{)yEEfO*LQ z#@8i%E&+0eZT{b~(CP@lOwg#J^CW#M0RbRCS+t5;odK9%@qqCgN#6woNoS_Nvu*!# z%&jp2P%T{m`CU>|>mh?Vh={98 zy1As=O1ibA8z+@Prt-74|5yb`+YVR?Km{1=3_t)3{_Y^@YLc$n0Hl?8D@mw>M1b`)ATvyTq5(v+dbd2r8b9Q0dm=%?bi-=n$qxeKg50`Y` zwDRk|V<7-60Rx+{hV2X5gq!p4%ML(9+#m^iO-WCdbngxx$-)815o(t)%LYJ1Twl_2 zByAw+DUxo~h4D0Y0-%S`6cMcQ*GO7N(oOr!F#*s+YRR}_RNo@$sXeAx`pC2ca)lTX z7}O0Vy+qPobDx_V_a8}rm4wUr2T9ate=h0Gw$B~Y3+lmZ_9LU)fQZ0velCKN^q_3c z{x=XIe>-&~u-Xyy`RVVnwl;76lH0Pr+Phf2D8=2y^@^9xB|l=M|e zU+U}f4Fuqgyx;bhGx_c{Y*YXu;vSL+SRS9j9V?B1>NrU!&`fLl%8ch~hYSSZXi2w| zw1w?+hVaq!>}UW)1Y!IBlGaP%dlMhD|9w=_ev*D#YTFk802lcfNjuv9?HC}80zgDO zMADwg&nJ&lN9cG|%||4iWc$x0Ke7M-8%g@Nq)$oO&GwH=^2c1o0Z3}+Jt??q;unMZ z4oOGazNGYrF<&77W~{MLwz7RzNnVQr5D_HFP&-?;s2zmAgQVk!>iH&N6b1n20A~Vq zL>9IPl?LGXk}zI3Y*9O}lXP$?U0?UE!T|6ZcIpPUPc9<;k}4=7){yjGNt-q>_b*9D zNZO^ezOQ=c2mtUJ_}|ht-!hnr?0}SFz}tD3q?^@nb*ZFxN_xBPiwAR_F~LXx{3!u| zcQlwv0dUXc$vm`%BmDUTCGEb*^H?JRz$bD}U?bbV9Ew0G0Cr9ZwmO&dloZf#45&Ay zokr1yIxV$ZDpeRq4%uoEgRshm2 z*fA{z>Kavakfc}lRYOAnpht(*@wghsew|i71(hsHc_h4#66MOacjf{>dkku}u8K#0 zDQc)!o5~OXUPvNM5^W__gleLLEbF9s@^lWN(JCW--6q_ef6oN~W6UbAb4dv>H?jT6 zoR=;gGXwzA&G^DqgrB^*Z5m*+C?fE77(a%=t>PgIfTV_~W#i2OMiuR08<)2;4FLd# z>heBBh_npiC1g=Vu=-z>HMq>6E&veWqi*S%uA;+iZ_$UXApnpOq))PnKA!-{%JWAA z&9)~=dTgJM==n?+00_pg1J9^=7Y)MC?!(6r0O(Snd$@|;ml9T4w^Ky~K(fM`(%5~z zuj!d=0GK2qno<*I+O7-u8u=aq09K#YjVkJ-6C+}$R1YpKiFGg$8xq0o#*GuX*<+wtKZ$4grANPMuejl+=x&p9Os%KQqc7^SxBTcnz4gmmHvo8Hi@{L4R7DdGS88h#@j17+^KTMksjI5c6MF2rMh;G?cCni|Ko880N+UGxvD;zya?5z+&mC9WdgBY zACW8dxp6F>HEo}r^K3-mY|y>8cFp+HZ1)NxhXBCp;e<;SkpU$YkyQguEiKIVf*gSq z7(;oKLQz)pZ$#WF1&S1js_0nT?BdQe1OW6W0aO){A>7zDoz7Vl5x9BO0qXMZS@UEJ zq6-^0nHHKXiioz%@BX&2W1VRT056q<`&j3G?Pq(JT#h1wNb(a6YAtuFwsEwStY@2h zL9-|#a1iPQOl_FVFCBrMX$SyxCy>9X)7uXs&e&NKu78gV};X|0;KqK0TkIX0K8h`5fV*Cy4*f0ra01W1qUW1IiZ zqKLrG(T-Rb^BkX=w0d0t-Wve8j|c)8KB`U)ap;1gHDF{>M37&i*iT?q=jLY3H8+S8 zE>yK>h5t+L!|4z?X%Y?=RuSrFC)@1WUNjT{1Vo;^u6uzt?sfWf6GsHKW-4w^Z%BEx zeKdzFs7>r;`_#68g@YGpNYD zbKT(z0QRAiyMb{Im~dX8_esh@J0PhcnwKcf*BMTJxnixpEqoC{nin^aqps`wUJBh=VrF) z2^vJD0AL={wQp_kh`3&WF0-Rc2x`z**F2oM{n*LaHE!stB0L|CSaHxKtLELrYY_m_ zE}&D6Bpmmx)e(0)Y;T*y+lr%z;LaipD+kQ$h_>{#ZF6P9U@8Vc0)bZby<51#C}X^S zFqr#-6GQ~Ps5ldKMtAnMU2SvQOA#s!0QUfqp1?>p5x(`-UD6aqzP4tPly0DgU6(B~dBLS~%`{ul;Y|yAQ(GiEmr^T% zT`?*SKmvh}VntDCX(C)jf~5mzZbfM<0B{r8&Uh1d zY~h$VfdbB6l1?t+Sv0*b9jhY6K$-)Gp{@SFseZgSiY~n)-a4K}0U+%HwlhgJl67?l z-RAkCq4%(KUCmjgfk=YCSBk1%mU(Lzy{#0TNaFPlBAXV(XaFP-sHGCraQRVA*8nRF zAV?)}+VO|{^*vRentr*Zi$9`hQW+eyXj}ELPuB-6_ymtxwe3E-X;c6b2z)HHUQ|ri z23ErS*)O2ZUs-m+(ug`muz*ZFg4*MbI<6b$c68DuU5MkZsHhXhP&@OCj1E8of%mZb zh!Zf-eGXv|f$|fyvpeIcF_40QNJ&sInO$$pVeUSiYN#0uEq?x~rPW~73RnmLvlplI zGKCg&aGU4RWyO{li${hHqGK+ro1?@_7Yaby4P-F!ySXnY$1ark&bB$w_rxxTE;=Sz zH~@1V5~mNJmjWP+WY>aAn^4>Soq`QUo`CQ~e#&+%PiNV@pf(94Fev2Umv5eKS8Tdt zE@#TNF~lAG5GO4)#2pK{I;W1QW!V8$%tXYU(~jWkdJHxOwIpG@ac{ZA<3!t>V_tSz zF#yaNYFtKqc2pF%qRV3kH}#Q`w?f$6795I%{$ z`CrhO{e;PUMU1i}$EIimo4Kka(1@y0J1de_Gyp47VuhKS04OZbuG38bbd^})@e=@r d1=@AG{{oQW!ku$ef;<2K002ovPDHLkV1ha2UFrY; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stat_hardware_headset.png b/app/src/main/res/drawable-xxxhdpi/ic_stat_hardware_headset.png new file mode 100755 index 0000000000000000000000000000000000000000..44e371e9989a22ebab7354d39aab447b1825b222 GIT binary patch literal 2565 zcmV+g3i|blP)NcBov%d8Y#3DX&X?0mb9Xckf0z%L@l67p+zbJni5|eJN8{|pU+OU z5c~Z5?sV}{8(I(*R0V{JR6-*`3IesH5g?VKKvFeT(@LaCjGZR2Z-2}T8sjs*^PbOV z@6~7BjblNX3S-3pbX`|AZrr$s5OS|=+uJ$k@3(DR%D*6I^TC;g!4*ZhfQYXEz%S3AKmU}j z>#3DMSP_6^GWiKY$aiep-oY5#z!;lxyx5IWDnU#rigFwP^!obxzbtpmWdj%;9j)h_ z4_KB}D`lQ<28(fCs>0{<8354X_xoeZflv^DbgWlbSBDs552aG6+5!fh27pNbFoX~W zA;jP^N&K%0NIQJ9M->391Av(dv6QB1+LWrQKjWNt`~7}dO)@j=BApZjU}R+EHcDx$ zWm)%ImZdJixGMl~8WGP3Ax##<7A(b3UQbIwOosg#_V#hgze;!#8#vn=cH)6>&(mRD4&*Y%3J zy1M%9Sfa zt*xzBo$Sa7V03i!9?P=6Zy3gnIUJY(fVie<{Y_0xvKGJREs;q048v$f#5Mr9#>oIp z)80hHrt$IdUpwWaoB&2fMxHkeW2Y?D4zXqmA$mFI!|mG=MAXqdR0wL&7(qy zPDIqQ!$DqJQBiU1;>C;i>AF6ZZL1@IWHQ-inr1g+%&FE}i1-wxw6nRn`JL5tK0UU~ zRi9N&gIwa4TpJu5 ztOkI?0PxKm3Gt+D+dJp44Ke^ElgV!whB3ext8?f;E^l|VwYB}NuCA_@ zD%j^R7MF{oR4VmQx_Zq4IDGie$bVwl2Q%dg-1OjqTzb4wiz`(B%@k`m&fDm#) z)3o-+#>SaMJ_lfAWTaziYDz9G7p(ayr45@lZ5k`qt?6PAjmP6(H%;@$4wGqXnl^ag zz=3cEfOtIqglU=&I~a3GRn;%Iw6y%87={+B&sZ#Wn`K$Q1AzBskD+PWGtJG-au4x; z1;pd=Bc^G7(Sg{X2qAld!Qkm)8C#5ghlYkW3n6|206QGQ;8-vi{Coz0;o;#^jIk}* z#t=fDp_B$%T3X~oiZ##J#6u%)FXx|W{Eg648SBocA83b?I+NF*|Wh#TArQ%423 z)w%2^91go)1vuxfRe`j9o&wTIMV7&`SAbg?R`%m|2*@1Z=5Ek21j-sfPfw4Dh^kv* z;I;yKdV1Wf0#-O2)-oQ9L?V&b0U*~~p#_V>Z2&HZz}av(ocU}p5{bM30G}-f1yD*~ z3WY*~Pw=)3r_4Vyyj3wL(1GfRl9N{VdvD~N z%g@j5TaotwP@({S%Q7hf1ZYG520PvNSD51mv$gk6$L&V3z;qb}% zlk1qMdU|@U6GA+QhOilU0tqL0ht5~r~)Su@igcBC;+^`7<((%H^#0SxzfC`8vpkI ztOoT`8q-riDb2=e*y1T*HK>=;n4SVkX*O2F7Eb}ILA{j5^b}A^v#}bscnVkz>ZLSh b*(>0`%LHHDffBbL00000NkvXXu0mjfpe4sw literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stat_yamusic.png b/app/src/main/res/drawable-xxxhdpi/ic_stat_yamusic.png new file mode 100755 index 0000000000000000000000000000000000000000..a44264bcec4c469ebaed5bf4605b9d6a9641b913 GIT binary patch literal 3521 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_yamusic.png b/app/src/main/res/drawable-xxxhdpi/ic_yamusic.png new file mode 100755 index 0000000000000000000000000000000000000000..87be3f0d76c6222717f8dfb3952f8ab474a01a7b GIT binary patch literal 13315 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 z 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 D{ZQ^s literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_yaradio.png b/app/src/main/res/drawable-xxxhdpi/ic_yaradio.png new file mode 100755 index 0000000000000000000000000000000000000000..958eb25fa908bb4255a406376b93e263092b8220 GIT binary patch literal 15153 zcmeIZ_g9la6EK>D0HKH8Av8fc(t835QIKk(OP4AF0@4$zQY175ECfM%uhJ0|P(Z3k z5wHN#d$0EizVG|pd;fs@(>>?m9L{EUc6MfVW_HR`13hgjayD`h2t;)qebopA0!I-3 zU?jkZT=U&w5C{Rfeie1=vDNBpvNVg^>HFvFC55Ha^xZcg*Ab#%I;IcqARCVR7D#R4 zzw{ie7Az0JYQD4vIQO67Uo@Iv9=xvjH<(zL9|<9RAS4XH6tR!fq-uY?nLwtMvx|+>SBm&MGKP|=S25!_{9Dy z`#%Uj1^FfjOMp+Eb?`sHF7dXhLMMp-4e$RC*pL4Kdq{(o`-dZ8;=lE6Ineh7x_|xy zHusAAKXfrKzf@n~iU25ok4w+f)N%c6N5wnxOK&BZL7; z4*5<@#v+J+aa;UF$KL8>(J`g&AOeZPBd&HBjrXAG23`&{H#Y~FKj~gE1OU-W-S(9( zgznHyR@iC#{7Jw311bb#hKH1V*T3X(| z%cA>>hk#W?7u$I9RoVeRhR0h%7RGvnp&s7xmmlFj^RhQ?$`WJ4;A%zo33Qss8OPBF z;_oRtq~;i)X0{5ViOpz!aP!HIl}45UdiJ10&WV7co80F!{y5CDyn*rY&s6j9US$FZ zh_pR(k{+_Lc0gVC1HgL&cvuaxRu6*QUBzc-hkG(WYDoyIt?BKz`6cy`+7`=eYx5-D z7!nkK5!5NfEd%N+Ffcr99G~6BKndIyVjh+ryCQx`FqkvSyXU!BkPfU{i9ivIZbDyD z+ehdEt*k=N#ZFafwYm^`_<&UUuGFol>75xBU>@|mmE&W7*K#l`f zPq%oe3qRgYiiU>fd)mu!T{r=T?ilSd0{jR1{(~FGVl|@?z`0>IGHD#r0J^|Cd2b6~ zd{`?}*C@IT3~p8dwmGeBn^h&C`5{omj)LES6#PK)JGkG*kT8_>SyGwoKnE#d7#!~Z z=+Veb6#W<*Kqefr>Mt%^3#r^nV4bCXpo%8}aMz{IrR!~!*p?Qx1@m6rX<7nCO;zHf zx)2hwg*s^ShhFwxlB~5mi~x+EuYD>U=_GYA;+0fH?+`0DkcRN48p%;TVD7{4aYQd! z;cs>T2XhL$RetL<%$a@8>ltUk$GVPW1i^L`i>XP(&_IulkFz|aq|+~`l2#)$ss&Ml z|CGArfiMPUO`9;e3V;*H7|0`+3ivL06J!?ZJ^C>N-F6?M_P6*6QIaTw~H4k|~#WDS7L@2fbT^I~yyKS`>NpJoAQ{V=D|NkYQN z76hiIrl>fTv7cEln3w)ZQl9K81~YB4wyEMu1*uuMJ=iy5&0t#s&1eCG$k|JbRW7`%MgaQHr+8nGVn+)MjmupPPy%Uj80u9D< z2uk3qXjJ`mge{aMync@?8-V9h$28B6eL{ziU75J_3^-MOpHW&YlvfwoWj)$uu=v+$ z>3R+@kGH9J+=njzH9(ZS`vlB%Jp_7D5Gfuc^LUVES`y|h;-h_mHw(X{Dws38zN*Um zD2}Y}gFKX6L*r&`hUz-W3{fa-n ziL+cAq^y2s%o=Ir|3sLmaAzr@Ww4O@$P{@6lhbuxodGIh{o)d~aIRMaDeyOlj|Nf2 zVpn+an;>*RRMoR9ehBE!BsN56z=PuYO#(4b+*+?dQjW1k%p(g`2KD!O_|B;=mqLE- z3t}l(R%nsSgSh8>h(2vdx<4|Yzc5WQG8Q>xX-~LXzswTF1GB#n8HBfDcFq}$4nmO* z@cbPjVf!j!r4f{3k0$ViZil3z7QPM>A(B_M3D{PlKY;TVP6 zsZ!3m9Oy{7btV&I;nTRMQMRT(<}DH4t4fiSZW(AgFQbJh5U&I{2DOXrE{eGoLRJ1} z2K%cEQ9l!(3B93_Bzmhy8N0Vn6!?bn^V-V@!)V*M>r>Xb09qOqpqY`UH>e9Ycn5;{ z>=&{g?hXF6DoD>Gq7cW}?{lmF`Z#+T%l`lnAtBfsapA7iZ{qlJu;qhvLIqT6bfdFEcYilr?C30G03 zg>u=?lVYQq>H6A>J}l?EOdMJoanUnGd999Eycm*?I6iYYiO264_06SudO?I6A!*0g ztVb{7Ms(j^xr$Wh-Jx;}=8#=`3JBfC_s&!**(SQmO?!N0$4o8#P=?DGv&j)3dONG^ z$m~XUn<|H+k;Mi3xx}+Ai|+906RCbjMV-+jPeLcRs8%AV9I_lk7)%~2<}CiSZlp^d zO)meS3*7g|trj)rPu=}A1H`vm#A?e$z}GcbgO%+^3y_8JkCqSy!l{Ls&)k$-_N@uF zQR#X2&b4m?9*Ea}L6Ndxh379@LO*vWM!Bewn>@Pv?E_bGAjH!z@g_k<-s3b*72Vg| zohBI%kj8|`yO-b5hNj4X11+i2_Z0)oe6Fs=_j=A;aMnUR{&Q@>iIR24W!!IyGLkf@ zX{gAfn{8oVxWI}(c@T{W%VtRyk}rC0tG?}&BHZrzl?kr!iM@W@bP?98tUkar*&jls zgP*@glzv*VX|1j+OFxhhx3>0RcKa}+=c)kTg)zCasxmUKgP2^uV$AKjiA9pTOf%w$ zUx4mp)2x2VBWtc`avshdbH5NEpE9G=dlZWb-YRln<5^h#9t}umEy{l3to`%s>R1KQ zk)R%dIr$xL-1D3EBxH&}evc1ELN_KdngB+*&@**|grPGR7F4$j$L z*)&MiO%%irZDRyzG#p>S4=WC2F&bHz($dl~g;s+4wkYOgFZ}C>mY2w=$1rd4GL5MD zY0SSNLuyQa|Jj(w&3!~trRL8NeU)HKs_a%{orZ~VoOQ|~T|a1f5eD0(Q7fkoF&b1= zxgL^!fPa9KJ~1V*Z=&QDW0um+^|#88Tk*awgS`bEeqWmzDmQvmo||=0Qd3gm%gV~4 zUj6z?FmfY~S-#Xua}pXPYh_~U-k-uS8QwhhnXX{xC!ZcSu|)tn6#HmrQX_?)pwgR9 zm*m4=sMD}M1{qK~E2@^W%;8A#O*5lK8)$Kl;z%4rggoD0$eeETmBP%Y;%o^{ohc>| zm+8Aa6lEZmME4u)Pa0y~0J~>HPV}9nt3HI5C zVoQ5%S%MTKPn0g5k)jhqK$3}=QEcx);)r4<&$G+0tA`i2O%m%YlFyldv{Fl_$b?Q` zMXsJxbjSH^-?Sm09Pl7Ja_4a&@bK|=75`_Fxi9VV5Jx@g_l~|Cp>$0Bqw-@ji5uP z%d@{A%GXZY7PxiCn3kU?g1?=8*hDIq+o~D)bqxE_?My*~wRwaI`?J*D=KSHf{Yk}& zEj5)rDWuT<-Y@t0%kvsDR-<)k*+~T6d8#hdbKcoMd~}@_n3EAgK9aPq>9`4Mw>N%! z%5iP(;=G{SCN!pjDjil)^E~V&&+>cex>2pr%Gm60o|yqf@@avAfqcJsUxwixp@e9V zs^@pm4%zxW;e8(yF)m6>l`Ho#-#EH{eUrU(`eaaw{?STAxgfblJ2A?=o^mMAcx*N0 zltLNM5MuAiuADcO@5|uMQhVl>sOHN(fGa~M3R60HS9~r48f-+;cNaQl8`BvKK{*ci zb9Tk}Wx-gXAHZejzYV_;+&1wCQ{iLL#BUvkaZ#l9!k;}Fu`mAm{+9iuoSgdv^z^<7 zJuy#K7`~w_ddGqop>#i|qv6R|*=sU;Jk#LJpuHUMBFmB}j@+%#M8TGOA0MO`*560C zPS4v=6e}%4M|g^OZuTTS`*rir_k@>k-n>f{j@~l&BCq95Fzm~$0g^nLq_=D6mm_-8rX?2lqXfr?R6r=eDn=_#~(ebn4T|(wk9TZUFx=+J!{HTQ% z+M(?^$#L#uI#&qO$nQX&Ds|Rn!v;?D~VJ1EjU)I~bn1x26H zwwv_QR}PDdMMXz*dhZs3hrYxcn-?9sW%kfI{1u!qdZW zP?V4x)Jlc2eA^<0|ED@ON}#P$9h5$PR>z5Ds`Hq6c6N4Vu#*yo1ECD2Dru8BrRC>WiAyo7*9vGg)Tuuz1toypoy1%e}%w-I%sD)NB}o#T@bqI^cDd{O+~-MD@laA**WQQ z8&r`+Lr13lkn_P`Z3<@XzbEi>N~5~;Hk1wFzlC6h(B0osxD)A+N%~1XtTS1D?LLb7 z}il@n3OYbYU@4{BMR(D3&gc_WQ0K)jH4Wh5ra=#ycyyMdjjarC)tQc7Ldip z$o@A|u>7#FDYBH&%lW&=YwA07;8Z=3PXIN}6jMWk%b+mv1}Vb2IS}Pckfe7eD-Ysa z&USH;FNU=85by{<%WC|9W?g}*G?FrBT(VAR)%HHwZ^Hos*gp{=HpH;qEu$!FK93QhNyN{i~9<9x}Tx21=jvY z;|&>yp4~(1hvWA4lUpKy-sI5=!++7l-PQpGN>)bQbM0DHi~OD3Hl|7Lt7{?q?ha@? zft>9b*j@`F_qN2;Alf}5C+TFhST6QEdoK_;Iac1f0!SG@%y%ZdTgcg6{lKFF#PRe&Y?#@Rkp28qFy@(6vJy#E%ZC*Q;Ejcbk2Dw~I@Gs@ ztip=SFQ=S=W?PF44(%RJZB)(eNFj$8!*+fht%Rt2LiH&zSI*v1uh41`6N8ztG~Hl` zcn=Fxd(d!Xjh|ihfLpQt&e>hku{$Nto-q6w^MY&!Dx-^~cG& z`N`FeE9_1Upzb&D(?*KDm2)arsbCL9DV5W)A^9|I?LKh^at#Zp&9_9{Z*M&}VGDne zwgvgFeTp;}!GT9y1CPzBU-ON}dqn#z7}wBbC>B8h8-2QnK6e%dt~~{8#M@ugO1Y^F1COyK6)*GYXSAp&B@0r5Iv)bpJJO}Qcmws#8QMPZ~n8~6T`h(AT${#j}zFecdlV=>nsNm_Jt7~BU{u8o!LB07XU4ywYQsLGM=kJ{~tnu}m_W3ipayk7h@ zr~E%L5lTgcJ{ozS@3*rHUu^bzqC}BIsL8s!xTz@jz?D7wF8IivHhCYqGw;=N6FD#T zi<$V_(O+CR*FM*cIP-xDIwd8gB{m+yVuNXL(*ZU9z{oP{r_Zk~5+HPIjH~=ER&izt ztA?!ZQy5Wn0L+(drt<1uSMUeIB;UJeFBVRaG(D6tpe|E3aF}FKBh6Q_%&Vz-F9KQg zBP#h$!T(W;@zx%^d+T;-XP1TtJYRm~(#5hB5-SC|{GCWI%X~V?fS0OkH7S=RA>-26 zdHu-!*~A+XQ5-E&l4_pL@27{g7$UkUG?>eeB-^-P?`|@m_h{)U+r;PIjW@XB^R2)U zzxomxV2a+SHwpx6yLrmWFioVkb;h%2c}O;1zgR0+9zbkO)0iHr-;-|>nI+A*@H- z53&!+EShY1ai?!vGU4rY&>~KSN_E|Q-bv=*A#|pKSF#W+kvSs1ZM#Qf|7LE}U}v`} zhRw_Fx#%B8aJqWPQg;SzZCwQA!&|4N%uTa#`rG7*! zJr5RNC2MFiuZmp0J;DCw>4fe#6iV`1QkVhhbw+;_Y(_Aq15f3?)#Z*b-UM;S5n{y6#zZHj72ls0PgfbI13$@$jb>?r4IdckSOTbjy zD6}Jf+h%-8Xv`~L_Rcfva;wazhZOK(YduVGB!cgnKKH~rJHFqtHG9p$CjsN}vNQ}b z;_;K(r(Tk|J`=Od(p%z3HWW?L`U3f#(J}XRsmRhS3vi@F@5V$Ox0Dd$zY*o{%b;v( zl+_kYCsvTxlj-TcN_9oacC*Ah{A*>a?s6pRBpB__%hl-2Sa~Ap5TWut$~`gcTG?`@ zUqfqYCJ!&LEg4SwNQ~WXpEV7B=a(`zmx4Gxl)e~&0edaI@gwOjVCQ)Y%z|YVc<|A@ zcIlwb>$pvHhi+ZiDB7jVkBOiCrvj@+)q?}dEK~V*78rz~{4Bd|Ae+SLebL*d8#V_N zvO1CVMX`Q zl4vK#6+S|61doikpM+0c2CO&&CbSFg7lVBy_B{i$M=B>(9Q;=Ml3N6gU#HQ44;e&z zgy-D<$XT(uxx2fIx~Cb5#fH0}l2%_0i|A>)UVQfAQnz zZk+Gau5{LQDD4zH>>#sQ-!aGf`dAjE#p>()AA45jSfqu?(x2S1oE7m|XVc-NuAbq< zAbQofM{VCodR?hHs(6=>{_NQ^rOR^eRu`6Rxeq7K**D#KYq3)55u1nLvG$KY&&@FD z>ZJUC%Kl?I-*<(_-rc>3z{;+Rzam)swx4!wI)dP%u>}2s9d>rU7Y8FOFE=BR$HB-Y zI5#mW1FQLlgpFp)A;{C|s(R&4-do!WryvD}h%{K3aB+CCKdAw$%me=9+i9)ewSG;u z$aX~qAqGwa5XZFVP?YQnsp5ejGN6_wJ64-MS3b!vSA*atAeHOQgCz~N)-~ET9OS;V z#&z-j5$YtfSA$kttz{zcbE|FGR!A}8)bZ;5z1{d(G4ooMdT+_W{L3&;T4?Z)(^MCt zgXYk7s_?Dk2ADWad2?Q;b-a57YA-IiO-r$HoqnWv8=-TSIBdiR4hG*;uGj|b_J92{ zwZ+tu6{E7LJjutr5;=#E$$l-Pw%$xrcxEXy|6PKHciBIm*jLkIyBk}l z{YB?^x(fv`tEXxjhu0#1NduZLxIuT+lOliG#A>#KSk{$k#D<-xV06qzMimkK9T_S- zBt_Gt8>IPUkUdO}p=K`_m)O-*o$fOAPF*POl@4lyqk4wQ1UVTTwsU2j7}a;8V1K!p zcl64q-KfsucT}$dVD1U>WnbBG%BAM5?EiH$(uB=n^b|+h$t;clW{ONYvK;Wrmh(s0{`$WU74VO!JKVM z1H2Z383^PvCtznYH+!FJt>0le*3*UBFbzBu#i>!ES4F-_A3RqhOJ_t7qXU9o6^bHR z4eZD#hKp1|V-nzZ=Y~S3%ov;ghsS1~+jpVk^v(3pZ+mLpSoKXGT|-yhR>p-kc_q-Z z%%@>+ZPWlw*;oG2cp(soZG}(^(1VzKu+{r~i$;iH?MW%ZgaWjT#BbW8j+~IiM_ibr zdEdp$)+;uVUr4UX=j4O>zLQt37bJAuiptw-A-b~e{bt;8L}WA}=OSk5KNAfQsR%K9 zFQTdZhjh5`$xDs1=L=+Ez0izSP)CW-5$kQnCh&72F(230=t}&%Udz4X;jk(|In? z{f2+w?N9B>I+3_jgDm`%&hGKxTdW+^=`;$lE$0D6^{Eh5?kJNevGoh@2=X>3u0#Ti zd;$o8D6FG3su+l8^>(3!N^f;rvVc)=L@;FFNVeH@CAq71>dqTgTe}0>wzJ`b+(-Gz zEd@adh~^&@C?||r1<6pk!U!yE<%dXFQ`iU8x@EtvoEEu895HwfjO;?Qn0x%_)ae(F zd&Rhx_i}|y<~4;((_|QQ#nN<+vGYtb>mf6~S4&k!xo+acU@Oc>r;j4cMU~6zr^JqU z^SD}9j@)V^$(=zsaS6Hjk*cwoV6p$5;k-`qd z+)X`qyDuk4^3z!Q_RFwQFZ?PSwx;bwvk&_5iw?Qw3t{~5Rrjc6G?8@ZWq9!HsI}2W z=!ED_@&~g&#;M$;C9YZ5P)`~Ww`S-bV0J8=Pl)z@=DCeYjMzG!&0TM%cWN}75s*Aa zN~=P?l}QFwXgH)UW-J%=E3u_{<}Efqml=5*i15}cEAy5TE8#{R87DrAVtVuz!Hu8^6*R}oUN7=;Y9^flo8H~`H4Y}?U@3=i_@zlk-u&-Y?f|PR*{2Fl3 zJI;+_B^INA&tqo7WtCDvxtkJ(Uyomqn0e6IH~EgQG~Jg?&7WR;e+N9FZG^jyVNHOH zT6l-yD@8Dx=P+7tACjltk0!0U(7qG*fTSc8#U8ap2(&rm$z5>{35;$Z5M;cSSt0!T zu01&Z9yCL5S3@4z+km6|a((&fMv zKi8AXWeujZ4!fi+UTYX|C9S18#2Duvo8EuTB|#c-Ja~Wbs&s*T(yr97q*{Ow%;pti zLMEy3KBax%WL&i^jSywa_fQn>5`K3wYzO7YWvyk+lAp7a(%yY|wmo8*x%s}$MY_Y7 zvyyG7x)+rA_%`ix_Rq3H+0imA!cP#o3*J}vqysVU+@2x5$o){qNEf9gmMj9I2TYxa5`avr{sMpud&TXxt>WSvMnZCnY{{d%5uK ze7jg3jP03h)uE|(w%UDBl|o$W*UY{-xn{-CtHCO_hbwRnN5+O9B_bY6_8-v>fBP1x zk@XX`>R1HaL`(RNxrU9Z{p#kfrAd(x<(oolzckr_r!Fryvo{)QwX&Y2)Dp>uNs!vcVtl7<-wUE~ns2)$FqhB!JXLz76`$-K zh7(@&y_DAsy%M8ANT_tK-nwTp86;(Y{JM0tcBnw|QA{AJ;N7p@XzXaul0W9#&<%wx z%6kv8SAMAhW^#g?!KCU(D`&p{J*?=-6-4?&B!#I&`|LymlMImWZD;p2Vug+dgIG?3 z#cHK(-u$qXi<$AU44Me*djTG&5!8fl%~Tk^4>aR1m6mxU;&gPC({|tt)eWY}YMFBS zK1CD!!M4I=N*M-dM`JVrqPSc(IXk9i9h)+B=yaE_?Pr19h+JN-iTPP=Bk^L#l=?>( zR{@Ut81=i|!Jl5ri*u^KJq1%F`N-c%H6<4fgbbA@G?LR96gD=pAMu){&;ePsjZ1+g zN^O&m$*mjTo8U1&Zpy_RF>BKp9k-Gj9beLf-)OzfQs2goa#CS;I!bAj7fZE*actP3 zw|{Z&S@LDp{NRN=rv_HCBG=``qmuL2K*B4n zUC%B5=D!Rd{1tW5mu5DVypX%s8>_#_LrIe*QKP+?;F5{U)?aK?%_!%sz~koUVQuo^ zpBEy^-9moPG>iX`KWK-Tj$${zY^b+-zs7l2q{A}_Z|{i z_8kk`|6&B$@#!mqZDJEwM$8VP01T!btA(0Q{JYzTFkX99LViW@zxS6L)@yanb>Ew& zg|z+Zw%JKZe5M-AH1H^@qu+f9B)hzgyCx4wq9#ql?ePM+XiIZ#MM4mxX1wfmc)(#S z4twm{bM7L3%ovGU4g2s3`|c^5%NZT+^hrl_MmATpp!Zp(8^fB^Ft_XFfirik0wpGU z-U2rM*9>XP^thdJ^t&sWE5NZ1LTQdmUHa1BY(lR3;f_eCb4~mYVhme;bHQgX$1^sk zc+;`o&z(v^>G4|<%l9vNV@4-2J7?4pw<-<1CFaCt**3NP`TYO#5DLv?Gfs-eQ>L9- zK$U-dwdc0Zfj66+_O(l2RY=GF7_JtMANoBcDmlJ$?c0mNr{rUX_mi0hg2ZsA6r{Ew zY=)n#{knuVQ9a4YgnlA*5dB5c`F*+FMS^Uwa-dp*DIjI8=Y%h|)#!_UXX@`{#gj1T`V|`Ov+~<(LNX`aV(W z#9QRJB0FCuk+7?!ys6ljR~7!(djQxko5&NYT{4ZM{LO(h+i!cm|I3{l%s>V5AE{+V z`x?@|=zZUQ@6$K@@KXq68wj#;r%~K5ftna;>ozvE z)Lk}(RL__N{(z-gk*}O1a^@C5q+BJryIPeki5vodeTHcXx-7RU(fQnR?z@JP8pZ6J z47swe23GENv$n|kon4Prn%?)VU-9CyyE19IO1WDe5#xP+>g z&vKz1CH$x^Ro`N;(tWbxEg@r{ zChZhRxmOz+^)1ln<}ZanW!5({Ey9Zurv8Aq^`GFO@W45_+&q_ZtKGMBRv>e#_i7-;S1E;mRSn9D z-@0+X8Rw5&_6ggGDfnb~oxc40ZJd85X|dz|cuBuh%-Pjyho0wXz>{(yC#X5Im5)AL zvODbPzlunkz7(sqFYNBKEzmLQ_5JD)&h92YuD1q3?OJ*>#NId~QX&9>(c%J@oUDul z2-6g?vtm1HwYNbToBs*zs}xSC3Nn7_`KDC`RI~a3-9N%Sz9P=fcNdcStwuhJ`-!`s z>A*~X_N(gtysUmgM7A|9}R zRt_iBy9Jp$xy(A_|0;D4ghJ8|9w(~eC6B0NL$dvsByRpW4e5*hyLji3UGrTAuNi}y zz({wJN^VHTS0?nZy)7;>Wi(p zTYVLk;5i#~dsv`(Cv%Z~B5Wk-X;(hrrq|yCIR;ZEWhko3%;!Hv{*X^P{OlwMQSmh! z&e#x59?DW|Eagn-zB8(p=-a%I9YcN?dWU5g<*Svu*ePn=8BBOrT+JGiYVnY?EQY0n zfmEK%Yf+@*e#JY?PH|z8ORqX}`xD1bcUf`Z9R=%;@j6f@_um3D*M9A?x-wz%Pxl~^p+@EM zH>bsx;rLzGuo1Oi43S^VBC~oJB-6vyS96^&4;(0>Ix-i>Xq4k`S0abW^G)w-I%blX zn1=opv?;l!3Ou+L2|mxOi8%E`5ykxWSmb^@E-TvC*?11fvC3`L>8~O5=ZW0q$&JJz zUjh}|wlW!_$_(20Uu^7Hp@$Wb^Ccc?H;NqJP>a3?B~5XcXEd!`S|%x!qOL~pFVdn= z=pi@lN9jc;Y}Q?^L>&nzuhzie%nw}9m;@1q&_aVj3HnBX^n%bHlFIceyrm^HeI#Gw zmo~ZGK4{|J;M(P2{(%i1R7WFp=xC{RN%FbaI<#7TNL4?|exJ$<_SB`(Lj3MJ$AWrt z3ZZrdJ>Dq12QhD@UL4+3=)p{vu8`9 z%6K)QzOdpkztAd|-3Bx6EE7q7PIZgK(5!~jjwT^%OB+c=jE|CWgzS_f5fKe&2?H$}RT+!~fL2z&)~KBqv(K;R^rBNqjk zanF<oLS9n-P06SBhPR-3Xw>N)j020>mwrfO>> z26j9Vr@q*nCFR5%VYwUnkVtM&t(vSdMK>2|PJ*r`wF3EQdp4r#S%eHXnf*L?QFYN% z#@;czF*_1Tp<%>L0TecV4T3Re|Fsl$j}~^ruuW3BS;gMyV!Ne8V(kaSZv51X4~cNO zHfSXpXCW|^9F7ScprVEqCNnTZN)gLG#}>+<<|Ygl$BE`j)6o(SB5NKq8O72-_S9fW zmQd#(-01Dm^1t4$crYh%NmZ4!C!iIx33YFjqVHgL4X09Su{Rt}{?@dbFHzk?l{Kq0 zG1X~(E@Cd+y;Re3`4yyieT|y}>+KX_1 zlHATYl*w-GQ|!DZ3T_ZuBsd)CBetIh^m>S|KwwNyjG#3~9MdwPwwf3Z(ndW2g1&qC zsSk_87(>dMw#Z3H{TgH!s>9Oe2<7Ltjzb*OWFuM#GN~k2zD+Ihf3{pwo$O;6rUjF$ zngNsoSP8a0akil5$Ep0c@Oio|k5y_04wM^TLqVSM;&NC%>j^$5ct0-Ez50;EOaDe< z{?CX2UINgfpLOZ!3B~&F<j@fTMw-bszH~5oaC1&$LxX`3 zL;KVqe2-A04$CsF)Zzd624u(IcwZaf>H9ja=lob4?Ld#|jN-uHK-#MdV*D$XN0aUB z?%G&eBXXK4LN8ibHq0*vq9L+y>$q7q#LeVL0A&75j0q801O{Yj{#~7naQxFdAKB0d zYXD0(M+gB+>E|EK&=Wha8;&a>G_~Dd*m>hQz9%vi=6JvB_?-6Y*pT+U@ zq6Fviy$mN4A9$T?VGalo)zB+nTE8gppEhSTM}S`t#Dnftk1c`2I>RSoNhMC2)yg|9v&>VXSE%zung_24k~0V>IRKF~)5M1C;S29D&<2wf)dSGyGsbdOU3$&2n_ zAXu!2CcJ@(CXl1Y1$%dMX6VwwF8WxYu2?tKLFmrbzicI1Cc2#R4Pl>Hu}W?-e!M<| zGc%(_)UUK4a)_7|$MbG0{%0T${%DUcgp0IcDiD|ggFJHQY;h($EZXm;jW`coC%7p$d zKm-`{2#sSJ3S!C4F`VES)InYf0jbv1%IL&P_!nuuH?#h5NJhTAIO+omhjreY$V}d) z)z!#7`!5V37mE){QdHwOz|F-wVzaB0b=xwA5snD}lqb0Q$|6Gq-UH$4#}Fj)q}^?G zLYsh5z?~kh=p+nvsG@?xlf*>YPFdI-<;|JPz;8AIA~w`?X&8)^(vdro0y{1F_3!p9 zgXhIYA8)2j?S})cbphcf=P5w-XvaMY>@x@a`Bw%S$W!Ks$GUvlC_;PVpdez98>0QM zo6kB7MEn7IZS>+7DIgC0 + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 48e82b4..0ed90a1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,24 +1,39 @@ - - + android:layout_height="wrap_content"> + + + + + + + + + + android:layout_height="match_parent" + android:layout_marginTop="@dimen/toolbar" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml new file mode 100644 index 0000000..0f2126c --- /dev/null +++ b/app/src/main/res/menu/main_menu.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index adca2aa..7ab8dc7 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -4,4 +4,9 @@ Биография %d альбомов · %d песен %d альбомов, %d песен + О программе + Контакты + Простое приложение с простыми потребностями. Никаких проблем.\nНу разве что несмного. + О программе + ЗАКРЫТЬ \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index fb3aa08..8a8146c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,8 +1,8 @@ - #6945b6 - #44218b - #1ff4e6 + #e74c3c + #c0392b + #3498db #000000 #ffffff diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 7e7ae64..612dd69 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -4,4 +4,5 @@ 8dp 3dp 100dp + 55dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 587c55f..5b01b21 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,5 +4,12 @@ Biography %d albums · %d songs %d albums, %d songs + About + Contact + + About + This is a simple application with simple needs. No worries, no trouble.\n Well, maybe just some. + DISMISS + diff --git a/build.gradle b/build.gradle index b206c45..51fa98f 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0' + classpath 'com.android.tools.build:gradle:2.2.0-alpha5' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'me.tatarka:gradle-retrolambda:3.2.5' From 8a66aa3c2472e6185709b3db3838a56052c35944 Mon Sep 17 00:00:00 2001 From: aleien Date: Sat, 23 Jul 2016 00:44:46 +0300 Subject: [PATCH 02/15] Injected dagger --- .idea/misc.xml | 16 +++++++ .idea/vcs.xml | 2 +- app/.gitignore | 1 + app/build.gradle | 29 ++++++------ app/proguard-rules.pro | 23 ++++++++++ app/src/main/AndroidManifest.xml | 1 + .../aleien/yapplication/ArtistsPresenter.java | 2 + .../yapplication/ListArtistsActivity.java | 18 ++++++-- .../aleien/yapplication/di/AppComponent.java | 16 +++++++ .../ru/aleien/yapplication/di/AppModule.java | 44 +++++++++++++++++++ .../aleien/yapplication/model/DBHelper.java | 25 +++++++++++ build.gradle | 5 ++- 12 files changed, 163 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/ru/aleien/yapplication/di/AppComponent.java create mode 100644 app/src/main/java/ru/aleien/yapplication/di/AppModule.java create mode 100644 app/src/main/java/ru/aleien/yapplication/model/DBHelper.java diff --git a/.idea/misc.xml b/.idea/misc.xml index fbb6828..cca2cda 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -43,4 +43,20 @@ + + + + + 1.8 + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore index 796b96d..09fb41b 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1,2 @@ /build +.idea/* \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 2cf6cd2..e81c07e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,9 +3,16 @@ apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'me.tatarka.retrolambda' android { + signingConfigs { + Yappl { + keyAlias 'YappAndroidKey' + keyPassword 'u5ghn6bj' + storeFile file('/Users/user/keystores/yapplication.jks') + storePassword 'Yapplication' + } + } compileSdkVersion 24 buildToolsVersion "23.0.3" - defaultConfig { applicationId "ru.aleien.yapplication" minSdkVersion 15 @@ -15,7 +22,7 @@ android { } buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } @@ -23,13 +30,11 @@ android { testCoverageEnabled = true } } - sourceSets { androidTest { setRoot('src/test') } } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -38,34 +43,32 @@ android { dependencies { ext.supportVersion = '24.0.0' - compile fileTree(dir: 'libs', include: ['*.jar']) + compile fileTree(include: ['*.jar'], dir: 'libs') compile "com.android.support:support-v4:$supportVersion" compile "com.android.support:appcompat-v7:$supportVersion" compile "com.android.support:design:$supportVersion" - // Annotation heaven compile 'com.jakewharton:butterknife:7.0.1' compile 'javax.annotation:jsr250-api:1.0' - // UI compile "com.android.support:cardview-v7:$supportVersion" compile "com.android.support:recyclerview-v7:$supportVersion" - // Testing testCompile 'junit:junit:4.12' androidTestCompile "com.android.support:support-annotations:$supportVersion" androidTestCompile 'com.android.support.test:runner:0.5' - testCompile "org.robolectric:robolectric:3.0" - testCompile "org.mockito:mockito-core:1.10.19" - + testCompile 'org.robolectric:robolectric:3.0' + testCompile 'org.mockito:mockito-core:1.10.19' // Network compile 'com.squareup.okhttp:okhttp:2.7.5' compile 'com.squareup.okhttp3:logging-interceptor:3.0.1' compile 'com.squareup.retrofit2:converter-gson:2.0.0' compile 'com.squareup.retrofit2:retrofit:2.0.0' - debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2' - // Image processor compile 'com.github.bumptech.glide:glide:3.7.0' + + compile 'com.google.dagger:dagger:+' + apt 'com.google.dagger:dagger-compiler:+' + provided 'javax.annotation:jsr250-api:1.0' } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 0a0c3b5..49e0649 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -15,3 +15,26 @@ #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} + +# Butterknife +-keep public class * implements butterknife.internal.ViewBinder { public (); } + +# Prevent obfuscation of types which use ButterKnife annotations since the simple name +# is used to reflectively look up the generated ViewBinder. +-keep class butterknife.* +-keepclasseswithmembernames class * { @butterknife.* ; } +-keepclasseswithmembernames class * { @butterknife.* ; } +-keepnames class * { @butterknife.Bind *;} + +# Dagger +-keepclassmembers,allowobfuscation class * { + @javax.inject.* *; + @dagger.* *; + (); +} + +-keep class javax.inject.** { *; } +-keep class **$$ModuleAdapter +-keep class **$$InjectAdapter +-keep class **$$StaticInjection +-keep class dagger.** { *; } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cd8d13b..df0273a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" + android:theme="@style/AppTheme"> diff --git a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java index 0496e90..cd245d1 100644 --- a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java +++ b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java @@ -8,6 +8,8 @@ import java.lang.ref.WeakReference; import java.util.List; +import javax.inject.Inject; + import ru.aleien.yapplication.base.BasePresenter; import ru.aleien.yapplication.dataprovider.ArtistsProvider; import ru.aleien.yapplication.dataprovider.WebArtistsProvider; diff --git a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java b/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java index 6937dd8..04ce42f 100644 --- a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java +++ b/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java @@ -6,6 +6,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.database.sqlite.SQLiteDatabase; import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; @@ -23,19 +24,29 @@ import android.view.MenuItem; import android.widget.Toast; +import javax.inject.Inject; + +import ru.aleien.yapplication.di.AppComponent; +import ru.aleien.yapplication.di.AppModule; +import ru.aleien.yapplication.di.DaggerAppComponent; import ru.aleien.yapplication.utils.IntentBuilder; import ru.aleien.yapplication.utils.Utils; public class ListArtistsActivity extends AppCompatActivity implements MainView { - private ArtistsPresenter artistsPresenter; + ArtistsPresenter artistsPresenter; + @Inject SQLiteDatabase database; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + AppComponent appComponent = DaggerAppComponent.builder() + .appModule(new AppModule(this)) + .build(); + appComponent.inject(this); + setContentView(R.layout.activity_main); setupToolbar(); instantiatePresenter(savedInstanceState); - } private void instantiatePresenter(Bundle savedInstanceState) { @@ -61,7 +72,6 @@ public void onReceive(Context context, Intent intent) { || audioManager.isBluetoothScoOn()); } - // TODO: Реагировать на Bluetooth-гарнитуру }, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); } @@ -133,7 +143,7 @@ public void onBackPressed() { // TODO: При открытой странице инфо об артисте, открывать страницу артиста private void showHeadphonesNotification(boolean wiredHeadsetOn) { Intent musicIntent = IntentBuilder.buildOpenAppOrMarketPageIntent("ru.yandex.music", this); - Intent radioIntent = IntentBuilder.buildOpenAppOrMarketPageIntent("ru.yandex.radio",this); + Intent radioIntent = IntentBuilder.buildOpenAppOrMarketPageIntent("ru.yandex.radio", this); PendingIntent musicPendingIntent = PendingIntent.getActivity(this, 1010, musicIntent, 0); PendingIntent radioPendingIntent = PendingIntent.getActivity(this, 1020, radioIntent, 0); diff --git a/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java b/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java new file mode 100644 index 0000000..b55763e --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java @@ -0,0 +1,16 @@ +package ru.aleien.yapplication.di; + +import javax.inject.Singleton; + +import dagger.Component; +import ru.aleien.yapplication.ListArtistsActivity; + +/** + * Created by aleien on 22.07.16. + */ +@Singleton +@Component(modules = AppModule.class) +public interface AppComponent { + + void inject(ListArtistsActivity listArtistsActivity); +} diff --git a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java new file mode 100644 index 0000000..6029343 --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java @@ -0,0 +1,44 @@ +package ru.aleien.yapplication.di; + +import android.app.Activity; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import ru.aleien.yapplication.ArtistsPresenter; + +import static android.content.Context.MODE_PRIVATE; + +/** + * Created by user on 23.07.16. + */ +@Module +public class AppModule { + + private Context context; + + public AppModule(Activity activity) { + this.context = activity.getApplicationContext(); + } + + @Provides + @Singleton + Context provideContext() { + return this.context; + } + + @Provides + @Singleton + SQLiteDatabase provideDatabase(Context context) { + return context.openOrCreateDatabase("ArtistsDB", MODE_PRIVATE, null); + } + +// @Provides +// @Singleton +// ArtistsPresenter provideArtistsPresenter(Activity activity) { +// return new ArtistsPresenter(activity); +// } +} diff --git a/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java b/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java new file mode 100644 index 0000000..dec61e1 --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java @@ -0,0 +1,25 @@ +package ru.aleien.yapplication.model; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +/** + * Created by user on 22.07.16. + */ + +public class DBHelper extends SQLiteOpenHelper { + public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { + super(context, name, null, 1); + } + + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + + } + + @Override + public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { + + } +} diff --git a/build.gradle b/build.gradle index 51fa98f..352cbca 100644 --- a/build.gradle +++ b/build.gradle @@ -5,13 +5,16 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0-alpha5' + classpath 'com.android.tools.build:gradle:2.1.2' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'me.tatarka:gradle-retrolambda:3.2.5' +// classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } + +// configurations.classpath.exclude group: 'com.android.tools.external.lombok' } allprojects { From 80c8a600fc85b6c06cbe88b60abb708f8541869c Mon Sep 17 00:00:00 2001 From: aleien Date: Sat, 23 Jul 2016 01:14:51 +0300 Subject: [PATCH 03/15] Trying to implement cache using SQLite. Just started --- app/build.gradle | 6 +-- .../aleien/yapplication/ArtistsPresenter.java | 13 ++++- .../yapplication/ListArtistsActivity.java | 13 +---- .../ru/aleien/yapplication/di/AppModule.java | 15 +++--- .../aleien/yapplication/model/DBHelper.java | 53 +++++++++++++++++-- 5 files changed, 75 insertions(+), 25 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e81c07e..7f7990d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,12 +11,12 @@ android { storePassword 'Yapplication' } } - compileSdkVersion 24 + compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { applicationId "ru.aleien.yapplication" minSdkVersion 15 - targetSdkVersion 24 + targetSdkVersion 23 versionCode 1 versionName "1.0" } @@ -42,7 +42,7 @@ android { } dependencies { - ext.supportVersion = '24.0.0' + ext.supportVersion = '23.3.0' compile fileTree(include: ['*.jar'], dir: 'libs') compile "com.android.support:support-v4:$supportVersion" compile "com.android.support:appcompat-v7:$supportVersion" diff --git a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java index cd245d1..7f4869d 100644 --- a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java +++ b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java @@ -14,6 +14,7 @@ import ru.aleien.yapplication.dataprovider.ArtistsProvider; import ru.aleien.yapplication.dataprovider.WebArtistsProvider; import ru.aleien.yapplication.model.Artist; +import ru.aleien.yapplication.model.DBHelper; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoFragment; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoView; import ru.aleien.yapplication.screens.list.ArtistsListView; @@ -27,19 +28,29 @@ */ public class ArtistsPresenter extends BasePresenter implements ArtistsRequester, ArtistClickHandler, Serializable { ArtistsProvider artistsProvider; + DBHelper dbHelper; private WeakReference> artistsListView; private WeakReference currentFragment; - public ArtistsPresenter(Context context) { + @Inject + public ArtistsPresenter(Context context, DBHelper dbHelper) { artistsProvider = new WebArtistsProvider(this, context); + this.dbHelper = dbHelper; } @Override public void takeListView(ArtistsListView list) { artistsListView = new WeakReference<>(list); + if (dbHelper.getAllArtists().size() != 0) { + showCachedData(); + } artistsProvider.requestData(); } + private void showCachedData() { + // stub + } + @Override public void takeDetailedView(ArtistInfoView info, Artist artist) { WeakReference artistInfoView = new WeakReference<>(info); diff --git a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java b/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java index 04ce42f..dee834c 100644 --- a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java +++ b/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java @@ -33,7 +33,7 @@ import ru.aleien.yapplication.utils.Utils; public class ListArtistsActivity extends AppCompatActivity implements MainView { - ArtistsPresenter artistsPresenter; + @Inject ArtistsPresenter artistsPresenter; @Inject SQLiteDatabase database; @Override @@ -46,15 +46,6 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); setupToolbar(); - instantiatePresenter(savedInstanceState); - } - - private void instantiatePresenter(Bundle savedInstanceState) { - if (savedInstanceState != null && savedInstanceState.containsKey("presenterState")) { - artistsPresenter = (ArtistsPresenter) savedInstanceState.get("presenterState"); - } else { - artistsPresenter = new ArtistsPresenter(this); - } } @Override @@ -128,7 +119,7 @@ public void changeFragmentTo(Fragment fragment, boolean hideBackButton) { getSupportActionBar().setDisplayHomeAsUpEnabled(hideBackButton); getSupportFragmentManager().beginTransaction() .replace(R.id.fragment_container, fragment) - .addToBackStack(null) + .addToBackStack("info") .commit(); } diff --git a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java index 6029343..d6490f4 100644 --- a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java +++ b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java @@ -9,6 +9,7 @@ import dagger.Module; import dagger.Provides; import ru.aleien.yapplication.ArtistsPresenter; +import ru.aleien.yapplication.model.DBHelper; import static android.content.Context.MODE_PRIVATE; @@ -19,6 +20,7 @@ public class AppModule { private Context context; + public static String dbName = "ArtistsDB"; public AppModule(Activity activity) { this.context = activity.getApplicationContext(); @@ -33,12 +35,13 @@ Context provideContext() { @Provides @Singleton SQLiteDatabase provideDatabase(Context context) { - return context.openOrCreateDatabase("ArtistsDB", MODE_PRIVATE, null); + return context.openOrCreateDatabase(dbName, MODE_PRIVATE, null); + } + + @Provides + @Singleton + DBHelper provideDBHelper(Context context) { + return new DBHelper(context, dbName); } -// @Provides -// @Singleton -// ArtistsPresenter provideArtistsPresenter(Activity activity) { -// return new ArtistsPresenter(activity); -// } } diff --git a/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java b/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java index dec61e1..2782d9b 100644 --- a/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java +++ b/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java @@ -1,25 +1,70 @@ package ru.aleien.yapplication.model; +import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import java.util.ArrayList; + /** * Created by user on 22.07.16. */ public class DBHelper extends SQLiteOpenHelper { - public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { + public static final String ARTISTS_TABLE_NAME = "artists"; + public static final String ARTISTS_COLUMN_ID = "id"; + public static final String ARTISTS_COLUMN_NAME = "name"; + public static final String ARTISTS_COLUMN_TRACKS = "tracks"; + public static final String ARTISTS_COLUMN_ALBUMS = "albums"; + public static final String ARTISTS_COLUMN_LINK = "link"; + public static final String ARTISTS_COLUMN_DESCRIPTION = "description"; + public static final String ARTISTS_COLUMN_SMALL_COVER = "small_cover"; + public static final String ARTISTS_COLUMN_BIG_COVER = "big_cover"; + + public DBHelper(Context context, String name) { super(context, name, null, 1); } @Override - public void onCreate(SQLiteDatabase sqLiteDatabase) { - + public void onCreate(SQLiteDatabase db) { + db.execSQL( + "create table artists " + + "(id integer primary key, name text,tracks integer,albums integer, link text,description text, small_cover text, big_cover text)" + ); } @Override - public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { + public void onUpgrade(SQLiteDatabase db, int i, int i1) { + db.execSQL("DROP TABLE IF EXISTS artists"); + onCreate(db); + } + + public boolean insertArtist(String name, int tracks, int albums, String link, String description, String small_cover, String big_cover) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put(ARTISTS_COLUMN_NAME, name); + contentValues.put(ARTISTS_COLUMN_TRACKS, tracks); + contentValues.put(ARTISTS_COLUMN_ALBUMS, albums); + contentValues.put(ARTISTS_COLUMN_LINK, link); + contentValues.put(ARTISTS_COLUMN_DESCRIPTION, description); + contentValues.put(ARTISTS_COLUMN_SMALL_COVER, small_cover); + contentValues.put(ARTISTS_COLUMN_BIG_COVER, big_cover); + db.insert(ARTISTS_TABLE_NAME, null, contentValues); + return true; + } + + public ArrayList getAllArtists() { + ArrayList artists = new ArrayList<>(); + SQLiteDatabase db = this.getReadableDatabase(); + Cursor res = db.rawQuery("select * from artists", null); + res.moveToFirst(); + while (res.isAfterLast() == false) { + artists.add(res.getString(res.getColumnIndex(ARTISTS_COLUMN_NAME))); + res.moveToNext(); + } + return artists; } } From 7c6008616a93571269b2626f2ce834eaf7d38dde Mon Sep 17 00:00:00 2001 From: aleien Date: Sat, 23 Jul 2016 01:18:19 +0300 Subject: [PATCH 04/15] Securing signing information --- app/build.gradle | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7f7990d..34a5ec2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,13 +2,17 @@ apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'me.tatarka.retrolambda' +def keystorePropertiesFile = rootProject.file("keystore.properties") +def keystoreProperties = new Properties() +keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + android { signingConfigs { Yappl { - keyAlias 'YappAndroidKey' - keyPassword 'u5ghn6bj' - storeFile file('/Users/user/keystores/yapplication.jks') - storePassword 'Yapplication' + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] } } compileSdkVersion 23 From ba929a141968aab72c97f96a34c3955f4a09ff6a Mon Sep 17 00:00:00 2001 From: aleien Date: Sun, 24 Jul 2016 02:31:48 +0300 Subject: [PATCH 05/15] RxJava injected, basic data caching --- app/build.gradle | 12 +++ .../aleien/yapplication/ArtistsPresenter.java | 23 ++++- .../dataprovider/WebArtistsProvider.java | 56 ++++++----- .../aleien/yapplication/di/AppComponent.java | 3 + .../yapplication/model/ArtistsDataSource.java | 96 +++++++++++++++++++ .../aleien/yapplication/model/DBHelper.java | 25 +---- 6 files changed, 163 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java diff --git a/app/build.gradle b/app/build.gradle index 34a5ec2..5cf1b48 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,28 +51,40 @@ dependencies { compile "com.android.support:support-v4:$supportVersion" compile "com.android.support:appcompat-v7:$supportVersion" compile "com.android.support:design:$supportVersion" + // Annotation heaven compile 'com.jakewharton:butterknife:7.0.1' compile 'javax.annotation:jsr250-api:1.0' + // UI compile "com.android.support:cardview-v7:$supportVersion" compile "com.android.support:recyclerview-v7:$supportVersion" + // Testing testCompile 'junit:junit:4.12' androidTestCompile "com.android.support:support-annotations:$supportVersion" androidTestCompile 'com.android.support.test:runner:0.5' testCompile 'org.robolectric:robolectric:3.0' testCompile 'org.mockito:mockito-core:1.10.19' + // Network compile 'com.squareup.okhttp:okhttp:2.7.5' compile 'com.squareup.okhttp3:logging-interceptor:3.0.1' + compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2' compile 'com.squareup.retrofit2:converter-gson:2.0.0' compile 'com.squareup.retrofit2:retrofit:2.0.0' debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2' + // Image processor compile 'com.github.bumptech.glide:glide:3.7.0' + // DI compile 'com.google.dagger:dagger:+' apt 'com.google.dagger:dagger-compiler:+' provided 'javax.annotation:jsr250-api:1.0' + + // Rx + compile 'io.reactivex:rxandroid:1.2.1' + compile 'io.reactivex:rxjava:1.1.6' + } diff --git a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java index 7f4869d..83e2bb6 100644 --- a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java +++ b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java @@ -3,6 +3,7 @@ import android.content.Context; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; +import android.util.Log; import java.io.Serializable; import java.lang.ref.WeakReference; @@ -14,6 +15,7 @@ import ru.aleien.yapplication.dataprovider.ArtistsProvider; import ru.aleien.yapplication.dataprovider.WebArtistsProvider; import ru.aleien.yapplication.model.Artist; +import ru.aleien.yapplication.model.ArtistsDataSource; import ru.aleien.yapplication.model.DBHelper; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoFragment; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoView; @@ -29,21 +31,25 @@ public class ArtistsPresenter extends BasePresenter implements ArtistsRequester, ArtistClickHandler, Serializable { ArtistsProvider artistsProvider; DBHelper dbHelper; + private final ArtistsDataSource dbSource; private WeakReference> artistsListView; private WeakReference currentFragment; @Inject - public ArtistsPresenter(Context context, DBHelper dbHelper) { + public ArtistsPresenter(Context context, + DBHelper dbHelper, + ArtistsDataSource dbSource) { artistsProvider = new WebArtistsProvider(this, context); this.dbHelper = dbHelper; + this.dbSource = dbSource; } @Override public void takeListView(ArtistsListView list) { artistsListView = new WeakReference<>(list); - if (dbHelper.getAllArtists().size() != 0) { - showCachedData(); - } +// if (dbSource.getAllArtists().size() != 0) { +// showCachedData(); +// } artistsProvider.requestData(); } @@ -60,6 +66,15 @@ public void takeDetailedView(ArtistInfoView info, Artist artist) { @Override public void provideData(List response) { artistsListView.get().setAdapter(new ArtistsRecyclerAdapter(response, this)); + dbHelper.onUpgrade(dbHelper.getWritableDatabase(), 0, 1); + for (Artist artist: response) { + dbSource.insertArtist(artist); + } + + List artistNames = dbSource.getAllArtists(); + for (String artistName: artistNames) { + Log.d("ARTIST",artistName); + } } @Override diff --git a/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java b/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java index 962b84f..2308e45 100644 --- a/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java +++ b/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java @@ -15,9 +15,17 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.http.GET; import ru.aleien.yapplication.ArtistsRequester; import ru.aleien.yapplication.model.Artist; import ru.aleien.yapplication.utils.Utils; +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Created by aleien on 09.04.16. @@ -25,40 +33,40 @@ */ public class WebArtistsProvider implements ArtistsProvider { - private static final String JSON_URL = "http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/artists.json"; + private static final String BASE_URL = "http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/"; private final ArtistsRequester artistsRequester; private OkHttpClient client; + private Api api; + + interface Api { + @GET("artists.json") + Observable> getArtists(); + } + public WebArtistsProvider(ArtistsRequester artistsRequester, Context context) { this.artistsRequester = artistsRequester; - Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = Utils.createInterceptor(context); - Cache cache = Utils.getCache(context); - client = new OkHttpClient.Builder() - .cache(cache) - .addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR) + OkHttpClient loggingClient = new OkHttpClient.Builder() + .addInterceptor(new HttpLoggingInterceptor()) .build(); - } - @Override - public void requestData() { - Handler mainHandler = new Handler(Looper.getMainLooper()); - Request request = new Request.Builder() - .url(JSON_URL) + Retrofit retrofit = new Retrofit.Builder() + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .baseUrl(BASE_URL) + .client(loggingClient) .build(); - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - Log.d("Main", "FAIL"); - } + api = retrofit.create(Api.class); + } - @Override - public void onResponse(Call call, Response response) throws IOException { - List responseList = Utils.decodeResponse(response); - mainHandler.post(() -> artistsRequester.provideData(responseList)); // Очень странно, что onResponse выполняется не в main-треде - // Вместо хэндлера можно, например, использовать rx-яву, которая очень хорошо дружит с ретрофитом - } - }); + @Override + public void requestData() { + api.getArtists() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(artistsRequester::provideData, + Throwable::printStackTrace); } } diff --git a/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java b/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java index b55763e..d2ee602 100644 --- a/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java +++ b/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java @@ -4,6 +4,7 @@ import dagger.Component; import ru.aleien.yapplication.ListArtistsActivity; +import ru.aleien.yapplication.model.ArtistsDataSource; /** * Created by aleien on 22.07.16. @@ -13,4 +14,6 @@ public interface AppComponent { void inject(ListArtistsActivity listArtistsActivity); + + void inject(ArtistsDataSource artistsDataSource); } diff --git a/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java b/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java new file mode 100644 index 0000000..539e9b1 --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java @@ -0,0 +1,96 @@ +package ru.aleien.yapplication.model; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import ru.aleien.yapplication.di.AppComponent; + +import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_ALBUMS; +import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_BIG_COVER; +import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_DESCRIPTION; +import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_ID; +import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_LINK; +import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_NAME; +import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_SMALL_COVER; +import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_TRACKS; +import static ru.aleien.yapplication.model.DBHelper.ARTISTS_TABLE_NAME; + +/** + * Created by aleien on 24.07.16. + */ + +public class ArtistsDataSource { + + private SQLiteDatabase mDatabase; + + DBHelper mHelper; + + private String[] allColumns = new String[]{ + ARTISTS_COLUMN_ID, + ARTISTS_COLUMN_NAME, + ARTISTS_COLUMN_TRACKS, + ARTISTS_COLUMN_ALBUMS, + ARTISTS_COLUMN_LINK, + ARTISTS_COLUMN_DESCRIPTION, + ARTISTS_COLUMN_SMALL_COVER, + ARTISTS_COLUMN_BIG_COVER + }; + + @Inject + public ArtistsDataSource(DBHelper helper) { + mHelper = helper; + } + + public void open() { + mHelper.getWritableDatabase(); + } + + public void close() { + mHelper.close(); + } + + public boolean insertArtist(Artist artist) { + return insertArtist(artist.name, artist.tracks, artist.albums, artist.link, artist.description, artist.cover.small, artist.cover.big); + } + + public boolean insertArtist(String name, int tracks, int albums, String link, String description, String small_cover, String big_cover) { + SQLiteDatabase db = mHelper.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put(ARTISTS_COLUMN_NAME, name); + contentValues.put(ARTISTS_COLUMN_TRACKS, tracks); + contentValues.put(ARTISTS_COLUMN_ALBUMS, albums); + contentValues.put(ARTISTS_COLUMN_LINK, link); + contentValues.put(ARTISTS_COLUMN_DESCRIPTION, description); + contentValues.put(ARTISTS_COLUMN_SMALL_COVER, small_cover); + contentValues.put(ARTISTS_COLUMN_BIG_COVER, big_cover); + db.insert(ARTISTS_TABLE_NAME, null, contentValues); + return true; + } + + public List getAllArtists() { + List artists = new ArrayList<>(); + SQLiteDatabase db = mHelper.getReadableDatabase(); + Cursor cursor = db.query(ARTISTS_TABLE_NAME, + allColumns, null, null, null, null, null); + cursor.moveToFirst(); + + while (!cursor.isAfterLast()) { + artists.add(cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_NAME))); + cursor.moveToNext(); + } + + cursor.close(); + return artists; + } + + public void clearArtists() { + mHelper.getWritableDatabase().execSQL("DROP TABLE IF EXISTS artists"); + } +} diff --git a/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java b/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java index 2782d9b..cffd854 100644 --- a/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java +++ b/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java @@ -41,30 +41,7 @@ public void onUpgrade(SQLiteDatabase db, int i, int i1) { onCreate(db); } - public boolean insertArtist(String name, int tracks, int albums, String link, String description, String small_cover, String big_cover) { - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues contentValues = new ContentValues(); - contentValues.put(ARTISTS_COLUMN_NAME, name); - contentValues.put(ARTISTS_COLUMN_TRACKS, tracks); - contentValues.put(ARTISTS_COLUMN_ALBUMS, albums); - contentValues.put(ARTISTS_COLUMN_LINK, link); - contentValues.put(ARTISTS_COLUMN_DESCRIPTION, description); - contentValues.put(ARTISTS_COLUMN_SMALL_COVER, small_cover); - contentValues.put(ARTISTS_COLUMN_BIG_COVER, big_cover); - db.insert(ARTISTS_TABLE_NAME, null, contentValues); - return true; - } - public ArrayList getAllArtists() { - ArrayList artists = new ArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - Cursor res = db.rawQuery("select * from artists", null); - res.moveToFirst(); - while (res.isAfterLast() == false) { - artists.add(res.getString(res.getColumnIndex(ARTISTS_COLUMN_NAME))); - res.moveToNext(); - } - return artists; - } + } From d8a9160b3e6456819d4fc117e7228920dc8a47ab Mon Sep 17 00:00:00 2001 From: aleien Date: Sun, 24 Jul 2016 19:54:49 +0300 Subject: [PATCH 06/15] =?UTF-8?q?=D0=97=D0=B0=D0=BF=D0=B8=D1=81=D1=8C=20?= =?UTF-8?q?=D0=B8=20=D0=B8=D0=B7=D0=B2=D0=BB=D0=B5=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BA=D1=8D=D1=88=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D0=B8=D0=B7=20=D0=B1=D0=B0=D0=B7=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + .../aleien/yapplication/ArtistsPresenter.java | 18 ++++++----------- .../yapplication/model/ArtistsDataSource.java | 20 +++++++++++++++---- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index df0273a..1b143a3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + list) { artistsListView = new WeakReference<>(list); -// if (dbSource.getAllArtists().size() != 0) { -// showCachedData(); -// } + List cachedArtists = dbSource.getAllArtists(); + if (cachedArtists.size() != 0) { + Log.d("Cache", "Got artists in cache! Size: " + cachedArtists.size()); + provideData(cachedArtists); + } artistsProvider.requestData(); } - private void showCachedData() { - // stub - } - @Override public void takeDetailedView(ArtistInfoView info, Artist artist) { WeakReference artistInfoView = new WeakReference<>(info); @@ -66,15 +64,11 @@ public void takeDetailedView(ArtistInfoView info, Artist artist) { @Override public void provideData(List response) { artistsListView.get().setAdapter(new ArtistsRecyclerAdapter(response, this)); - dbHelper.onUpgrade(dbHelper.getWritableDatabase(), 0, 1); + dbSource.clearArtists(); for (Artist artist: response) { dbSource.insertArtist(artist); } - List artistNames = dbSource.getAllArtists(); - for (String artistName: artistNames) { - Log.d("ARTIST",artistName); - } } @Override diff --git a/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java b/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java index 539e9b1..41e3253 100644 --- a/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java +++ b/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java @@ -74,15 +74,15 @@ public boolean insertArtist(String name, int tracks, int albums, String link, St return true; } - public List getAllArtists() { - List artists = new ArrayList<>(); + public List getAllArtists() { + List artists = new ArrayList<>(); SQLiteDatabase db = mHelper.getReadableDatabase(); Cursor cursor = db.query(ARTISTS_TABLE_NAME, allColumns, null, null, null, null, null); cursor.moveToFirst(); while (!cursor.isAfterLast()) { - artists.add(cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_NAME))); + artists.add(cursorToArtist(cursor)); cursor.moveToNext(); } @@ -91,6 +91,18 @@ public List getAllArtists() { } public void clearArtists() { - mHelper.getWritableDatabase().execSQL("DROP TABLE IF EXISTS artists"); + mHelper.getWritableDatabase().execSQL("DELETE FROM " + DBHelper.ARTISTS_TABLE_NAME); + } + + private Artist cursorToArtist(Cursor cursor) { + return new Artist(cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_ID)), + cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_NAME)), + new ArrayList<>(), + cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_TRACKS)), + cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_ALBUMS)), + cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_LINK)), + cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_DESCRIPTION)), + new Artist.Cover(cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_SMALL_COVER)), + cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_BIG_COVER)))); } } From b59329caec4997d1a3845bd688e2fd181b7dadee Mon Sep 17 00:00:00 2001 From: aleien Date: Sun, 24 Jul 2016 20:28:15 +0300 Subject: [PATCH 07/15] =?UTF-8?q?=D0=97=D0=B0=D0=BF=D0=B8=D1=81=D1=8C=20/?= =?UTF-8?q?=20=D0=B8=D0=B7=D0=B2=D0=BB=D0=B5=D1=87=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=B8=D0=B7=20=D0=B1=D0=B0=D0=B7=D1=8B=20=D0=B0=D1=81=D0=B8?= =?UTF-8?q?=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aleien/yapplication/ArtistsPresenter.java | 16 ++++++++----- .../yapplication/base/BasePresenter.java | 9 +++++++ .../dataprovider/WebArtistsProvider.java | 1 + .../yapplication/model/ArtistsDataSource.java | 12 +++++++--- .../aleien/yapplication/model/DBHelper.java | 24 ++++++++++++++++--- 5 files changed, 50 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java index e5910f1..f2f44a0 100644 --- a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java +++ b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java @@ -22,6 +22,8 @@ import ru.aleien.yapplication.screens.list.ArtistsListView; import ru.aleien.yapplication.screens.list.ArtistsRecyclerFragment; import ru.aleien.yapplication.utils.adapters.ArtistsRecyclerAdapter; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; /** * Created by aleien on 09.04.16. @@ -47,11 +49,13 @@ public ArtistsPresenter(Context context, @Override public void takeListView(ArtistsListView list) { artistsListView = new WeakReference<>(list); - List cachedArtists = dbSource.getAllArtists(); - if (cachedArtists.size() != 0) { - Log.d("Cache", "Got artists in cache! Size: " + cachedArtists.size()); - provideData(cachedArtists); - } + + subscribe(dbSource.getAllArtists() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::provideData, + throwable -> Log.e("DBError", "Error while reading cached artists"))); + artistsProvider.requestData(); } @@ -65,7 +69,7 @@ public void takeDetailedView(ArtistInfoView info, Artist artist) { public void provideData(List response) { artistsListView.get().setAdapter(new ArtistsRecyclerAdapter(response, this)); dbSource.clearArtists(); - for (Artist artist: response) { + for (Artist artist : response) { dbSource.insertArtist(artist); } diff --git a/app/src/main/java/ru/aleien/yapplication/base/BasePresenter.java b/app/src/main/java/ru/aleien/yapplication/base/BasePresenter.java index 39c9ab4..e7c685c 100644 --- a/app/src/main/java/ru/aleien/yapplication/base/BasePresenter.java +++ b/app/src/main/java/ru/aleien/yapplication/base/BasePresenter.java @@ -2,6 +2,9 @@ import java.lang.ref.WeakReference; +import rx.Subscription; +import rx.subscriptions.CompositeSubscription; + /** * Created by aleien on 21.04.16. * Базовый класс для презентера, отвечает за сохранение ссылки на представление (вьюху). @@ -9,14 +12,20 @@ */ public abstract class BasePresenter { private WeakReference view; + private CompositeSubscription subs = new CompositeSubscription(); public abstract void onStart(); + public void subscribe(Subscription sub) { + subs.add(sub); + } + public void attachView(V view) { this.view = new WeakReference<>(view); } public void detachView() { + subs.clear(); view.clear(); view = null; } diff --git a/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java b/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java index 2308e45..d099a89 100644 --- a/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java +++ b/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java @@ -60,6 +60,7 @@ public WebArtistsProvider(ArtistsRequester artistsRequester, Context context) { api = retrofit.create(Api.class); } + //TODO: переделать через rx, параллельным запросом? @Override public void requestData() { api.getArtists() diff --git a/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java b/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java index 41e3253..9c34965 100644 --- a/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java +++ b/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java @@ -1,7 +1,6 @@ package ru.aleien.yapplication.model; import android.content.ContentValues; -import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; @@ -10,7 +9,7 @@ import javax.inject.Inject; -import ru.aleien.yapplication.di.AppComponent; +import rx.Observable; import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_ALBUMS; import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_BIG_COVER; @@ -74,7 +73,14 @@ public boolean insertArtist(String name, int tracks, int albums, String link, St return true; } - public List getAllArtists() { + public Observable> getAllArtists() { + return Observable.create(subscriber -> { + subscriber.onNext(loadArtists()); + subscriber.onCompleted(); + }); + } + + private List loadArtists() { List artists = new ArrayList<>(); SQLiteDatabase db = mHelper.getReadableDatabase(); Cursor cursor = db.query(ARTISTS_TABLE_NAME, diff --git a/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java b/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java index cffd854..6f37cd9 100644 --- a/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java +++ b/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java @@ -23,6 +23,15 @@ public class DBHelper extends SQLiteOpenHelper { public static final String ARTISTS_COLUMN_SMALL_COVER = "small_cover"; public static final String ARTISTS_COLUMN_BIG_COVER = "big_cover"; + public static final String GENRES_TABLE_NAME = "genres"; + public static final String GENRES_COLUMN_ID = "id"; + public static final String GENRES_COLUMN_GENRE = "genre"; + + public static final String GENRE_TO_ARTIST_TABLE_NAME = "genre_to_artist"; + public static final String GENRE_TO_ARTIST_COLUMN_ID = "id"; + public static final String ARTIST_COLUMN_ID = "artist_id"; + public static final String GENRE_COLUMN_ID = "genre_id"; + public DBHelper(Context context, String name) { super(context, name, null, 1); } @@ -33,15 +42,24 @@ public void onCreate(SQLiteDatabase db) { "create table artists " + "(id integer primary key, name text,tracks integer,albums integer, link text,description text, small_cover text, big_cover text)" ); + + db.execSQL( + "CREATE TABLE " + GENRES_TABLE_NAME + + " (" + GENRES_COLUMN_ID + " integer primary key, " + GENRES_COLUMN_GENRE + " string)"); + + db.execSQL( + "CREATE TABLE " + GENRE_TO_ARTIST_TABLE_NAME + + " (" + GENRE_TO_ARTIST_COLUMN_ID + " integer primary key, " + + ARTIST_COLUMN_ID + " integer," + GENRE_COLUMN_ID + " integer )"); } @Override public void onUpgrade(SQLiteDatabase db, int i, int i1) { - db.execSQL("DROP TABLE IF EXISTS artists"); + db.execSQL("DROP TABLE IF EXISTS " + ARTISTS_TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + GENRES_TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + GENRE_TO_ARTIST_TABLE_NAME); onCreate(db); } - - } From 372001166018d0f7d6c18d3fabeb41a860fa0d4d Mon Sep 17 00:00:00 2001 From: aleien Date: Mon, 1 Aug 2016 08:58:27 +0300 Subject: [PATCH 08/15] Merge fixes --- .idea/gradle.xml | 10 ++---- app/build.gradle | 31 +++++++++++-------- .../yapplication/ListArtistsActivity.java | 3 -- .../dataprovider/WebArtistsProvider.java | 3 +- build.gradle | 5 ++- keystore.properties | 0 6 files changed, 24 insertions(+), 28 deletions(-) create mode 100644 keystore.properties diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 7ac24c7..f99298b 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -3,15 +3,9 @@ diff --git a/app/build.gradle b/app/build.gradle index 5cf1b48..29af3d9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,14 +7,15 @@ def keystoreProperties = new Properties() keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) android { - signingConfigs { - Yappl { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - storeFile file(keystoreProperties['storeFile']) - storePassword keystoreProperties['storePassword'] - } - } + compileSdkVersion 23 +// signingConfigs { +// Yappl { +// keyAlias keystoreProperties['keyAlias'] +// keyPassword keystoreProperties['keyPassword'] +// storeFile file(keystoreProperties['storeFile']) +// storePassword keystoreProperties['storePassword'] +// } +// } compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { @@ -47,7 +48,7 @@ android { dependencies { ext.supportVersion = '23.3.0' - compile fileTree(include: ['*.jar'], dir: 'libs') + compile fileTree(dir: 'libs', include: ['*.jar']) compile "com.android.support:support-v4:$supportVersion" compile "com.android.support:appcompat-v7:$supportVersion" compile "com.android.support:design:$supportVersion" @@ -70,17 +71,21 @@ dependencies { // Network compile 'com.squareup.okhttp:okhttp:2.7.5' compile 'com.squareup.okhttp3:logging-interceptor:3.0.1' + compile 'com.squareup.retrofit2:converter-gson:2.1.0' + compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2' - compile 'com.squareup.retrofit2:converter-gson:2.0.0' - compile 'com.squareup.retrofit2:retrofit:2.0.0' + + compile 'com.fasterxml.jackson:jackson-parent:2.8' + compile 'com.squareup.retrofit2:converter-jackson:2.1.0' + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2' // Image processor compile 'com.github.bumptech.glide:glide:3.7.0' // DI - compile 'com.google.dagger:dagger:+' - apt 'com.google.dagger:dagger-compiler:+' + compile 'com.google.dagger:dagger:2.6' + apt 'com.google.dagger:dagger-compiler:2.6' provided 'javax.annotation:jsr250-api:1.0' // Rx diff --git a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java b/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java index dee834c..3a059c6 100644 --- a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java +++ b/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java @@ -14,15 +14,12 @@ import android.support.v4.app.Fragment; import android.support.v4.app.NotificationCompat; import android.support.v4.content.ContextCompat; -import android.support.v4.content.res.ResourcesCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; -import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.widget.Toast; import javax.inject.Inject; diff --git a/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java b/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java index d099a89..fd7ef73 100644 --- a/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java +++ b/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java @@ -15,6 +15,8 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; @@ -60,7 +62,6 @@ public WebArtistsProvider(ArtistsRequester artistsRequester, Context context) { api = retrofit.create(Api.class); } - //TODO: переделать через rx, параллельным запросом? @Override public void requestData() { api.getArtists() diff --git a/build.gradle b/build.gradle index 352cbca..b714fcc 100644 --- a/build.gradle +++ b/build.gradle @@ -5,10 +5,9 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.2.0-alpha6' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' - classpath 'me.tatarka:gradle-retrolambda:3.2.5' -// classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2' + classpath 'me.tatarka:gradle-retrolambda:3.3.0-beta4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/keystore.properties b/keystore.properties new file mode 100644 index 0000000..e69de29 From f98a8117d4f1070133c7707ef7092e6f665858cf Mon Sep 17 00:00:00 2001 From: aleien Date: Tue, 9 Aug 2016 18:19:54 +0300 Subject: [PATCH 09/15] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B4=D0=BE=D0=BB?= =?UTF-8?q?=D0=B6=D0=B0=D0=B5=D0=BC=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=83?= =?UTF-8?q?=20=D1=81=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B9=20+=20=D0=BD=D0=B0?= =?UTF-8?q?=D1=87=D0=B0=D0=BB=D0=BE=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 16 -- .idea/runConfigurations.xml | 12 ++ app/build.gradle | 42 ++--- .../aleien/yapplication/ArtistsPresenter.java | 15 +- .../yapplication/ListArtistsActivity.java | 3 + .../database/DBArtistsProvider.java | 8 + .../yapplication/database/DBBackend.java | 150 ++++++++++++++++++ .../yapplication/database/DBContract.java | 39 +++++ .../yapplication/database/DBHelper.java | 69 ++++++++ .../dataprovider/WebArtistsProvider.java | 2 +- .../aleien/yapplication/di/AppComponent.java | 4 +- .../ru/aleien/yapplication/di/AppModule.java | 3 +- .../ru/aleien/yapplication/model/Artist.java | 2 + .../yapplication/model/ArtistsDataSource.java | 114 ------------- .../aleien/yapplication/model/DBHelper.java | 65 -------- .../detailedinfo/ArtistInfoFragment.java | 10 +- .../screens/list/ArtistsRecyclerFragment.java | 16 +- .../adapters/ArtistsRecyclerAdapter.java | 12 +- .../yapplication/ArtistsPresenterTest.java | 18 ++- .../ru/aleien/yapplication/UtilsTest.java | 2 +- .../yapplication/database/DBBackendTest.java | 82 ++++++++++ 21 files changed, 433 insertions(+), 251 deletions(-) create mode 100644 .idea/runConfigurations.xml create mode 100644 app/src/main/java/ru/aleien/yapplication/database/DBArtistsProvider.java create mode 100644 app/src/main/java/ru/aleien/yapplication/database/DBBackend.java create mode 100644 app/src/main/java/ru/aleien/yapplication/database/DBContract.java create mode 100644 app/src/main/java/ru/aleien/yapplication/database/DBHelper.java delete mode 100644 app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java delete mode 100644 app/src/main/java/ru/aleien/yapplication/model/DBHelper.java create mode 100644 app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java diff --git a/.idea/misc.xml b/.idea/misc.xml index cca2cda..fbb6828 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -43,20 +43,4 @@ - - - - - 1.8 - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 5cf1b48..af20ad3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,11 +34,7 @@ android { testCoverageEnabled = true } } - sourceSets { - androidTest { - setRoot('src/test') - } - } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -48,43 +44,47 @@ android { dependencies { ext.supportVersion = '23.3.0' compile fileTree(include: ['*.jar'], dir: 'libs') - compile "com.android.support:support-v4:$supportVersion" - compile "com.android.support:appcompat-v7:$supportVersion" - compile "com.android.support:design:$supportVersion" + compile 'com.android.support:support-v4:24.1.1' + compile 'com.android.support:appcompat-v7:24.1.1' + compile 'com.android.support:design:24.1.1' // Annotation heaven - compile 'com.jakewharton:butterknife:7.0.1' + compile 'com.jakewharton:butterknife:8.2.1' + apt 'com.jakewharton:butterknife-compiler:8.2.1' compile 'javax.annotation:jsr250-api:1.0' // UI - compile "com.android.support:cardview-v7:$supportVersion" - compile "com.android.support:recyclerview-v7:$supportVersion" + compile 'com.android.support:cardview-v7:24.1.1' + compile 'com.android.support:recyclerview-v7:24.1.1' // Testing testCompile 'junit:junit:4.12' - androidTestCompile "com.android.support:support-annotations:$supportVersion" + androidTestCompile "com.android.support:support-annotations:24.1.1" androidTestCompile 'com.android.support.test:runner:0.5' - testCompile 'org.robolectric:robolectric:3.0' - testCompile 'org.mockito:mockito-core:1.10.19' + testCompile 'org.mockito:mockito-core:2.0.99-beta' + testCompile "org.robolectric:robolectric:3.1.2" + testCompile 'org.robolectric:shadows-support-v4:3.1.2' // Network compile 'com.squareup.okhttp:okhttp:2.7.5' - compile 'com.squareup.okhttp3:logging-interceptor:3.0.1' - compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2' - compile 'com.squareup.retrofit2:converter-gson:2.0.0' - compile 'com.squareup.retrofit2:retrofit:2.0.0' + compile 'com.squareup.okhttp3:logging-interceptor:3.4.1' + compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' + compile 'com.squareup.retrofit2:converter-gson:2.1.0' + compile 'com.squareup.retrofit2:retrofit:2.1.0' debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2' // Image processor compile 'com.github.bumptech.glide:glide:3.7.0' // DI - compile 'com.google.dagger:dagger:+' - apt 'com.google.dagger:dagger-compiler:+' + compile 'com.google.dagger:dagger:2.6' + apt 'com.google.dagger:dagger-compiler:2.6' provided 'javax.annotation:jsr250-api:1.0' // Rx compile 'io.reactivex:rxandroid:1.2.1' - compile 'io.reactivex:rxjava:1.1.6' + compile 'io.reactivex:rxjava:1.1.8' + + debugCompile 'com.facebook.stetho:stetho:1.2.0' } diff --git a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java index f2f44a0..f588796 100644 --- a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java +++ b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java @@ -12,11 +12,11 @@ import javax.inject.Inject; import ru.aleien.yapplication.base.BasePresenter; +import ru.aleien.yapplication.database.DBBackend; import ru.aleien.yapplication.dataprovider.ArtistsProvider; import ru.aleien.yapplication.dataprovider.WebArtistsProvider; import ru.aleien.yapplication.model.Artist; -import ru.aleien.yapplication.model.ArtistsDataSource; -import ru.aleien.yapplication.model.DBHelper; +import ru.aleien.yapplication.database.DBHelper; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoFragment; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoView; import ru.aleien.yapplication.screens.list.ArtistsListView; @@ -32,16 +32,15 @@ */ public class ArtistsPresenter extends BasePresenter implements ArtistsRequester, ArtistClickHandler, Serializable { ArtistsProvider artistsProvider; - DBHelper dbHelper; - private final ArtistsDataSource dbSource; + private DBHelper dbHelper; + private final DBBackend dbSource; private WeakReference> artistsListView; private WeakReference currentFragment; @Inject - public ArtistsPresenter(Context context, - DBHelper dbHelper, - ArtistsDataSource dbSource) { - artistsProvider = new WebArtistsProvider(this, context); + public ArtistsPresenter(DBHelper dbHelper, + DBBackend dbSource) { + artistsProvider = new WebArtistsProvider(this); this.dbHelper = dbHelper; this.dbSource = dbSource; } diff --git a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java b/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java index dee834c..aa74a94 100644 --- a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java +++ b/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java @@ -24,6 +24,8 @@ import android.view.MenuItem; import android.widget.Toast; +import com.facebook.stetho.Stetho; + import javax.inject.Inject; import ru.aleien.yapplication.di.AppComponent; @@ -39,6 +41,7 @@ public class ListArtistsActivity extends AppCompatActivity implements MainView { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Stetho.initializeWithDefaults(this.getApplicationContext()); AppComponent appComponent = DaggerAppComponent.builder() .appModule(new AppModule(this)) .build(); diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBArtistsProvider.java b/app/src/main/java/ru/aleien/yapplication/database/DBArtistsProvider.java new file mode 100644 index 0000000..6965177 --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/database/DBArtistsProvider.java @@ -0,0 +1,8 @@ +package ru.aleien.yapplication.database; + +/** + * Created by aleien on 08.08.16. + */ + +public class DBArtistsProvider { +} diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java b/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java new file mode 100644 index 0000000..e2d3b52 --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java @@ -0,0 +1,150 @@ +package ru.aleien.yapplication.database; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import ru.aleien.yapplication.model.Artist; +import rx.Observable; + +import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_ALBUMS; +import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_BIG_COVER; +import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_DESCRIPTION; +import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_ID; +import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_LINK; +import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_NAME; +import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_SMALL_COVER; +import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_TRACKS; +import static ru.aleien.yapplication.database.DBContract.ARTISTS_TABLE_NAME; +import static ru.aleien.yapplication.database.DBContract.ARTIST_COLUMN_ID; +import static ru.aleien.yapplication.database.DBContract.GENRES_COLUMN_GENRE; +import static ru.aleien.yapplication.database.DBContract.GENRES_COLUMN_ID; +import static ru.aleien.yapplication.database.DBContract.GENRES_TABLE_NAME; +import static ru.aleien.yapplication.database.DBContract.GENRE_TO_ARTIST_COLUMN_ID; +import static ru.aleien.yapplication.database.DBContract.GENRE_TO_ARTIST_TABLE_NAME; +import static ru.aleien.yapplication.database.DBContract.allColumns; + +public class DBBackend { + + private DBHelper dbOpenHelper; + + + @Inject + public DBBackend(DBHelper helper) { + dbOpenHelper = helper; + } + + public void insertArtist(Artist artist) { + insertArtist(artist.name, + artist.tracks, + artist.albums, + artist.link, + artist.description, + artist.cover.small, + artist.cover.big, + artist.genres); + } + + // Напрашивается билдер + void insertArtist(String name, + int tracks, + int albums, + String link, + String description, + String small_cover, + String big_cover, + List genres) { + SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); + db.beginTransaction(); + + try { + ContentValues contentValues = new ContentValues(); + contentValues.put(ARTISTS_COLUMN_NAME, name); + contentValues.put(ARTISTS_COLUMN_TRACKS, tracks); + contentValues.put(ARTISTS_COLUMN_ALBUMS, albums); + contentValues.put(ARTISTS_COLUMN_LINK, link); + contentValues.put(ARTISTS_COLUMN_DESCRIPTION, description); + contentValues.put(ARTISTS_COLUMN_SMALL_COVER, small_cover); + contentValues.put(ARTISTS_COLUMN_BIG_COVER, big_cover); + + long artistId = db.insert(ARTISTS_TABLE_NAME, null, contentValues); + List genresIds = insertGenres(genres); + + insertRelation(artistId, genresIds); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + + } + + void insertRelation(long artistId, List genresIds) { + SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); + for (Long genreId : genresIds) { + ContentValues contentValues = new ContentValues(); + contentValues.put(ARTIST_COLUMN_ID, artistId); + contentValues.put(GENRES_COLUMN_ID, genreId); + db.insert(GENRE_TO_ARTIST_TABLE_NAME, null, contentValues); + } + } + + List insertGenres(List genres) { + SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); + List rowIds = new ArrayList<>(); + ContentValues contentValues = new ContentValues(); + for (String genre : genres) { + contentValues.put(GENRES_COLUMN_GENRE, genre); + long rowId = db.insertWithOnConflict(GENRES_TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE); + rowIds.add(rowId); + } + + return rowIds; + + } + + public Observable> getAllArtists() { + return Observable.create(subscriber -> { + subscriber.onNext(loadArtists()); + subscriber.onCompleted(); + }); + } + + List loadArtists() { + List artists = new ArrayList<>(); + SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); + Cursor cursor = db.query(ARTISTS_TABLE_NAME, + allColumns, null, null, null, null, null); + cursor.moveToFirst(); + + while (!cursor.isAfterLast()) { + artists.add(cursorToArtist(cursor)); + cursor.moveToNext(); + } + + cursor.close(); + return artists; + } + + public void clearArtists() { + dbOpenHelper.getWritableDatabase().execSQL("DELETE FROM " + ARTISTS_TABLE_NAME); + } + + private Artist cursorToArtist(Cursor cursor) { + return new Artist(cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_ID)), + cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_NAME)), + new ArrayList<>(), + cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_TRACKS)), + cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_ALBUMS)), + cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_LINK)), + cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_DESCRIPTION)), + new Artist.Cover(cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_SMALL_COVER)), + cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_BIG_COVER)))); + } +} diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBContract.java b/app/src/main/java/ru/aleien/yapplication/database/DBContract.java new file mode 100644 index 0000000..9ee0bab --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/database/DBContract.java @@ -0,0 +1,39 @@ +package ru.aleien.yapplication.database; + +/** + * Created by aleien on 08.08.16. + */ + +public interface DBContract { + public static final String ARTISTS_TABLE_NAME = "artists"; + public static final String ARTISTS_COLUMN_ID = "id"; + public static final String ARTISTS_COLUMN_NAME = "name"; + public static final String ARTISTS_COLUMN_TRACKS = "tracks"; + public static final String ARTISTS_COLUMN_ALBUMS = "albums"; + public static final String ARTISTS_COLUMN_LINK = "link"; + public static final String ARTISTS_COLUMN_DESCRIPTION = "description"; + public static final String ARTISTS_COLUMN_SMALL_COVER = "small_cover"; + public static final String ARTISTS_COLUMN_BIG_COVER = "big_cover"; + + public static final String GENRES_TABLE_NAME = "genres"; + public static final String GENRES_COLUMN_ID = "id"; + public static final String GENRES_COLUMN_GENRE = "genre"; + + public static final String GENRE_TO_ARTIST_TABLE_NAME = "genre_to_artist"; + public static final String GENRE_TO_ARTIST_COLUMN_ID = "id"; + public static final String ARTIST_COLUMN_ID = "artist_id"; + public static final String GENRE_COLUMN_ID = "genre_id"; + + String DROP_TABLE_IF_EXISTS = "DROP TABLE IF EXISTS "; + + String[] allColumns = new String[]{ + ARTISTS_COLUMN_ID, + ARTISTS_COLUMN_NAME, + ARTISTS_COLUMN_TRACKS, + ARTISTS_COLUMN_ALBUMS, + ARTISTS_COLUMN_LINK, + ARTISTS_COLUMN_DESCRIPTION, + ARTISTS_COLUMN_SMALL_COVER, + ARTISTS_COLUMN_BIG_COVER + }; +} diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java b/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java new file mode 100644 index 0000000..ed4cc8e --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java @@ -0,0 +1,69 @@ +package ru.aleien.yapplication.database; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import java.util.ArrayList; + +import static ru.aleien.yapplication.database.DBContract.ARTISTS_TABLE_NAME; +import static ru.aleien.yapplication.database.DBContract.ARTIST_COLUMN_ID; +import static ru.aleien.yapplication.database.DBContract.DROP_TABLE_IF_EXISTS; +import static ru.aleien.yapplication.database.DBContract.GENRES_COLUMN_GENRE; +import static ru.aleien.yapplication.database.DBContract.GENRES_COLUMN_ID; +import static ru.aleien.yapplication.database.DBContract.GENRES_TABLE_NAME; +import static ru.aleien.yapplication.database.DBContract.GENRE_COLUMN_ID; +import static ru.aleien.yapplication.database.DBContract.GENRE_TO_ARTIST_COLUMN_ID; +import static ru.aleien.yapplication.database.DBContract.GENRE_TO_ARTIST_TABLE_NAME; + +/** + * Created by user on 22.07.16. + */ + +public class DBHelper extends SQLiteOpenHelper { + + public DBHelper(Context context, String name) { + super(context, name, null, 1); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL( + "CREATE TABLE ARTISTS " + + "(" + + "ID INTEGER PRIMARY KEY," + + "NAME TEXT," + + "TRACKS INTEGER," + + "ALBUMS INTEGER," + + "LINK TEXT," + + "DESCRIPTION TEXT," + + "SMALL_COVER TEXT," + + "BIG_COVER TEXT" + + ")" + ); + + db.execSQL( + "CREATE TABLE GENRES" + + " (" + + "ID INTEGER PRIMARY KEY, " + + "GENRE STRING UNIQUE" + + ")"); + + db.execSQL( + "CREATE TABLE " + GENRE_TO_ARTIST_TABLE_NAME + + " (" + GENRE_TO_ARTIST_COLUMN_ID + " INTEGER PRIMARY KEY, " + + ARTIST_COLUMN_ID + " INTEGER," + GENRE_COLUMN_ID + " INTEGER )"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int i, int i1) { + db.execSQL(DROP_TABLE_IF_EXISTS + ARTISTS_TABLE_NAME); + db.execSQL(DROP_TABLE_IF_EXISTS + GENRES_TABLE_NAME); + db.execSQL(DROP_TABLE_IF_EXISTS + GENRE_TO_ARTIST_TABLE_NAME); + onCreate(db); + } + + +} diff --git a/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java b/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java index d099a89..e235ab9 100644 --- a/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java +++ b/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java @@ -44,7 +44,7 @@ interface Api { Observable> getArtists(); } - public WebArtistsProvider(ArtistsRequester artistsRequester, Context context) { + public WebArtistsProvider(ArtistsRequester artistsRequester) { this.artistsRequester = artistsRequester; OkHttpClient loggingClient = new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor()) diff --git a/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java b/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java index d2ee602..bc2eaf3 100644 --- a/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java +++ b/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java @@ -4,7 +4,7 @@ import dagger.Component; import ru.aleien.yapplication.ListArtistsActivity; -import ru.aleien.yapplication.model.ArtistsDataSource; +import ru.aleien.yapplication.database.DBBackend; /** * Created by aleien on 22.07.16. @@ -15,5 +15,5 @@ public interface AppComponent { void inject(ListArtistsActivity listArtistsActivity); - void inject(ArtistsDataSource artistsDataSource); + void inject(DBBackend DBBackend); } diff --git a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java index d6490f4..6431658 100644 --- a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java +++ b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java @@ -8,8 +8,7 @@ import dagger.Module; import dagger.Provides; -import ru.aleien.yapplication.ArtistsPresenter; -import ru.aleien.yapplication.model.DBHelper; +import ru.aleien.yapplication.database.DBHelper; import static android.content.Context.MODE_PRIVATE; diff --git a/app/src/main/java/ru/aleien/yapplication/model/Artist.java b/app/src/main/java/ru/aleien/yapplication/model/Artist.java index b9d39de..9a49982 100644 --- a/app/src/main/java/ru/aleien/yapplication/model/Artist.java +++ b/app/src/main/java/ru/aleien/yapplication/model/Artist.java @@ -1,5 +1,7 @@ package ru.aleien.yapplication.model; +import android.os.Parcelable; + import java.util.List; public class Artist { diff --git a/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java b/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java deleted file mode 100644 index 9c34965..0000000 --- a/app/src/main/java/ru/aleien/yapplication/model/ArtistsDataSource.java +++ /dev/null @@ -1,114 +0,0 @@ -package ru.aleien.yapplication.model; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import rx.Observable; - -import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_ALBUMS; -import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_BIG_COVER; -import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_DESCRIPTION; -import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_ID; -import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_LINK; -import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_NAME; -import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_SMALL_COVER; -import static ru.aleien.yapplication.model.DBHelper.ARTISTS_COLUMN_TRACKS; -import static ru.aleien.yapplication.model.DBHelper.ARTISTS_TABLE_NAME; - -/** - * Created by aleien on 24.07.16. - */ - -public class ArtistsDataSource { - - private SQLiteDatabase mDatabase; - - DBHelper mHelper; - - private String[] allColumns = new String[]{ - ARTISTS_COLUMN_ID, - ARTISTS_COLUMN_NAME, - ARTISTS_COLUMN_TRACKS, - ARTISTS_COLUMN_ALBUMS, - ARTISTS_COLUMN_LINK, - ARTISTS_COLUMN_DESCRIPTION, - ARTISTS_COLUMN_SMALL_COVER, - ARTISTS_COLUMN_BIG_COVER - }; - - @Inject - public ArtistsDataSource(DBHelper helper) { - mHelper = helper; - } - - public void open() { - mHelper.getWritableDatabase(); - } - - public void close() { - mHelper.close(); - } - - public boolean insertArtist(Artist artist) { - return insertArtist(artist.name, artist.tracks, artist.albums, artist.link, artist.description, artist.cover.small, artist.cover.big); - } - - public boolean insertArtist(String name, int tracks, int albums, String link, String description, String small_cover, String big_cover) { - SQLiteDatabase db = mHelper.getWritableDatabase(); - ContentValues contentValues = new ContentValues(); - contentValues.put(ARTISTS_COLUMN_NAME, name); - contentValues.put(ARTISTS_COLUMN_TRACKS, tracks); - contentValues.put(ARTISTS_COLUMN_ALBUMS, albums); - contentValues.put(ARTISTS_COLUMN_LINK, link); - contentValues.put(ARTISTS_COLUMN_DESCRIPTION, description); - contentValues.put(ARTISTS_COLUMN_SMALL_COVER, small_cover); - contentValues.put(ARTISTS_COLUMN_BIG_COVER, big_cover); - db.insert(ARTISTS_TABLE_NAME, null, contentValues); - return true; - } - - public Observable> getAllArtists() { - return Observable.create(subscriber -> { - subscriber.onNext(loadArtists()); - subscriber.onCompleted(); - }); - } - - private List loadArtists() { - List artists = new ArrayList<>(); - SQLiteDatabase db = mHelper.getReadableDatabase(); - Cursor cursor = db.query(ARTISTS_TABLE_NAME, - allColumns, null, null, null, null, null); - cursor.moveToFirst(); - - while (!cursor.isAfterLast()) { - artists.add(cursorToArtist(cursor)); - cursor.moveToNext(); - } - - cursor.close(); - return artists; - } - - public void clearArtists() { - mHelper.getWritableDatabase().execSQL("DELETE FROM " + DBHelper.ARTISTS_TABLE_NAME); - } - - private Artist cursorToArtist(Cursor cursor) { - return new Artist(cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_ID)), - cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_NAME)), - new ArrayList<>(), - cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_TRACKS)), - cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_ALBUMS)), - cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_LINK)), - cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_DESCRIPTION)), - new Artist.Cover(cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_SMALL_COVER)), - cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_BIG_COVER)))); - } -} diff --git a/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java b/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java deleted file mode 100644 index 6f37cd9..0000000 --- a/app/src/main/java/ru/aleien/yapplication/model/DBHelper.java +++ /dev/null @@ -1,65 +0,0 @@ -package ru.aleien.yapplication.model; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -import java.util.ArrayList; - -/** - * Created by user on 22.07.16. - */ - -public class DBHelper extends SQLiteOpenHelper { - public static final String ARTISTS_TABLE_NAME = "artists"; - public static final String ARTISTS_COLUMN_ID = "id"; - public static final String ARTISTS_COLUMN_NAME = "name"; - public static final String ARTISTS_COLUMN_TRACKS = "tracks"; - public static final String ARTISTS_COLUMN_ALBUMS = "albums"; - public static final String ARTISTS_COLUMN_LINK = "link"; - public static final String ARTISTS_COLUMN_DESCRIPTION = "description"; - public static final String ARTISTS_COLUMN_SMALL_COVER = "small_cover"; - public static final String ARTISTS_COLUMN_BIG_COVER = "big_cover"; - - public static final String GENRES_TABLE_NAME = "genres"; - public static final String GENRES_COLUMN_ID = "id"; - public static final String GENRES_COLUMN_GENRE = "genre"; - - public static final String GENRE_TO_ARTIST_TABLE_NAME = "genre_to_artist"; - public static final String GENRE_TO_ARTIST_COLUMN_ID = "id"; - public static final String ARTIST_COLUMN_ID = "artist_id"; - public static final String GENRE_COLUMN_ID = "genre_id"; - - public DBHelper(Context context, String name) { - super(context, name, null, 1); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL( - "create table artists " + - "(id integer primary key, name text,tracks integer,albums integer, link text,description text, small_cover text, big_cover text)" - ); - - db.execSQL( - "CREATE TABLE " + GENRES_TABLE_NAME + - " (" + GENRES_COLUMN_ID + " integer primary key, " + GENRES_COLUMN_GENRE + " string)"); - - db.execSQL( - "CREATE TABLE " + GENRE_TO_ARTIST_TABLE_NAME + - " (" + GENRE_TO_ARTIST_COLUMN_ID + " integer primary key, " - + ARTIST_COLUMN_ID + " integer," + GENRE_COLUMN_ID + " integer )"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int i, int i1) { - db.execSQL("DROP TABLE IF EXISTS " + ARTISTS_TABLE_NAME); - db.execSQL("DROP TABLE IF EXISTS " + GENRES_TABLE_NAME); - db.execSQL("DROP TABLE IF EXISTS " + GENRE_TO_ARTIST_TABLE_NAME); - onCreate(db); - } - - -} diff --git a/app/src/main/java/ru/aleien/yapplication/screens/detailedinfo/ArtistInfoFragment.java b/app/src/main/java/ru/aleien/yapplication/screens/detailedinfo/ArtistInfoFragment.java index 90019a1..ecf63b1 100644 --- a/app/src/main/java/ru/aleien/yapplication/screens/detailedinfo/ArtistInfoFragment.java +++ b/app/src/main/java/ru/aleien/yapplication/screens/detailedinfo/ArtistInfoFragment.java @@ -12,7 +12,7 @@ import java.util.Locale; -import butterknife.Bind; +import butterknife.BindView; import butterknife.ButterKnife; import ru.aleien.yapplication.R; import ru.aleien.yapplication.model.Artist; @@ -25,13 +25,13 @@ * Фрагмент для отображения информации о музыканте. */ public class ArtistInfoFragment extends Fragment implements ArtistInfoView { - @Bind(R.id.info_cover) + @BindView(R.id.info_cover) ImageView cover; - @Bind(R.id.info_genres) + @BindView(R.id.info_genres) TextView genres; - @Bind(R.id.info_music) + @BindView(R.id.info_music) TextView infoMusic; - @Bind(R.id.info_bio) + @BindView(R.id.info_bio) TextView bio; private Artist artist; diff --git a/app/src/main/java/ru/aleien/yapplication/screens/list/ArtistsRecyclerFragment.java b/app/src/main/java/ru/aleien/yapplication/screens/list/ArtistsRecyclerFragment.java index d7a3a77..cfc2a05 100644 --- a/app/src/main/java/ru/aleien/yapplication/screens/list/ArtistsRecyclerFragment.java +++ b/app/src/main/java/ru/aleien/yapplication/screens/list/ArtistsRecyclerFragment.java @@ -9,8 +9,9 @@ import android.view.View; import android.view.ViewGroup; -import butterknife.Bind; +import butterknife.BindView; import butterknife.ButterKnife; +import butterknife.Unbinder; import ru.aleien.yapplication.R; import ru.aleien.yapplication.utils.adapters.ArtistsRecyclerAdapter; @@ -19,23 +20,30 @@ * Фрагмент для отображения списка музыкантов. */ public class ArtistsRecyclerFragment extends Fragment implements ArtistsListView { - @Bind(R.id.artists_list) + @BindView(R.id.artists_list) RecyclerView artistsRecycler; private ArtistsRecyclerAdapter adapter; + private Unbinder unbinder; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View fragmentView = inflater.inflate(R.layout.fragment_artists_recycler, container, false); - ButterKnife.bind(this, fragmentView); + unbinder = ButterKnife.bind(this, fragmentView); return fragmentView; } + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - artistsRecycler.setLayoutManager(new LinearLayoutManager(view.getContext(), LinearLayoutManager.VERTICAL, false)); + artistsRecycler.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); artistsRecycler.setAdapter(adapter); artistsRecycler.setHasFixedSize(true); } diff --git a/app/src/main/java/ru/aleien/yapplication/utils/adapters/ArtistsRecyclerAdapter.java b/app/src/main/java/ru/aleien/yapplication/utils/adapters/ArtistsRecyclerAdapter.java index e291a57..b9172ec 100644 --- a/app/src/main/java/ru/aleien/yapplication/utils/adapters/ArtistsRecyclerAdapter.java +++ b/app/src/main/java/ru/aleien/yapplication/utils/adapters/ArtistsRecyclerAdapter.java @@ -12,7 +12,7 @@ import java.util.List; import java.util.Locale; -import butterknife.Bind; +import butterknife.BindView; import butterknife.ButterKnife; import ru.aleien.yapplication.ArtistClickHandler; import ru.aleien.yapplication.R; @@ -59,15 +59,15 @@ public int getItemCount() { } static class ArtistHolder extends RecyclerView.ViewHolder { - @Bind(R.id.item_container) + @BindView(R.id.item_container) RelativeLayout container; - @Bind(R.id.cover) + @BindView(R.id.cover) ImageView cover; - @Bind(R.id.name) + @BindView(R.id.name) TextView name; - @Bind(R.id.genres) + @BindView(R.id.genres) TextView genres; - @Bind(R.id.music_info) + @BindView(R.id.music_info) TextView musicInfo; public ArtistHolder(View itemView) { diff --git a/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java b/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java index f73e8c1..3985118 100644 --- a/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java +++ b/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java @@ -7,30 +7,32 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import java.util.ArrayList; +import ru.aleien.yapplication.database.DBBackend; +import ru.aleien.yapplication.database.DBHelper; import ru.aleien.yapplication.dataprovider.ArtistsProvider; import ru.aleien.yapplication.model.Artist; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoFragment; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoView; import ru.aleien.yapplication.screens.list.ArtistsListView; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyBoolean; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; + /** * Created by aleien on 09.04.16. * Тесты!. */ -@RunWith(org.robolectric.RobolectricGradleTestRunner.class) +@RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21) public class ArtistsPresenterTest { ArtistsPresenter presenter; @@ -40,12 +42,16 @@ public class ArtistsPresenterTest { @Mock MainView mainMock; @Mock ArtistsListView listMock; @Mock ArtistInfoView infoMock; + @Mock + DBBackend dbBackend; + @Mock + DBHelper dbHelper; @Before public void setup() { MockitoAnnotations.initMocks(this); - presenter = new ArtistsPresenter(context); + presenter = new ArtistsPresenter(dbHelper, dbBackend); presenter.artistsProvider = provider; presenter.attachView(mainMock); diff --git a/app/src/test/java/ru/aleien/yapplication/UtilsTest.java b/app/src/test/java/ru/aleien/yapplication/UtilsTest.java index f49c172..0998579 100644 --- a/app/src/test/java/ru/aleien/yapplication/UtilsTest.java +++ b/app/src/test/java/ru/aleien/yapplication/UtilsTest.java @@ -13,7 +13,7 @@ import static junit.framework.Assert.assertEquals; -@RunWith(org.robolectric.RobolectricGradleTestRunner.class) +@RunWith(org.robolectric.RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21) public class UtilsTest { @Test diff --git a/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java b/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java new file mode 100644 index 0000000..7ff2c2b --- /dev/null +++ b/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java @@ -0,0 +1,82 @@ +package ru.aleien.yapplication.database; + +import android.database.sqlite.SQLiteDatabase; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +import ru.aleien.yapplication.BuildConfig; +import ru.aleien.yapplication.model.Artist; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; + +import static org.junit.Assert.*; + +/** + * Created by aleien on 09.08.16. + */ +@RunWith(RobolectricTestRunner.class) +@Config(constants = BuildConfig.class, sdk = 23) +public class DBBackendTest { + DBHelper dbHelper = new DBHelper(RuntimeEnvironment.application, "testDB"); + DBBackend dbBackend = new DBBackend(dbHelper); + + @Before + public void setUp() throws Exception { + + } + + @After + public void tearDown() throws Exception { + dbBackend.clearArtists(); + + } + + @Test + public void insertArtist() throws Exception { + List genres = new ArrayList<>(); + genres.add("pop"); + genres.add("jazz"); + Artist artist = new Artist(500, "Tove Lo", genres, 20, 2, "http://lalala.com", "Description", new Artist.Cover("http://firstimage", "http://seconimage")); + dbBackend.insertArtist(artist); + + List artists = dbBackend.loadArtists(); + Assert.assertEquals(artists.size(), 1); + } + + @Test + public void insertArtist1() throws Exception { + + } + + @Test + public void insertRelation() throws Exception { + + } + + @Test + public void insertGenres() throws Exception { + + } + + @Test + public void getAllArtists() throws Exception { + + } + + @Test + public void clearArtists() throws Exception { + + } + +} \ No newline at end of file From 3f5c7c4d82dcc51f8bf618eb48419a0100cb49e4 Mon Sep 17 00:00:00 2001 From: aleien Date: Wed, 10 Aug 2016 00:18:55 +0300 Subject: [PATCH 10/15] Review cleanup --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 3 +- .../main/java/ru/aleien/yapplication/App.java | 30 ++++++ .../aleien/yapplication/ArtistsPresenter.java | 31 ++++--- ...ArtistsActivity.java => MainActivity.java} | 92 +++++++++---------- .../database/DBArtistsProvider.java | 8 -- .../yapplication/database/DBBackend.java | 74 ++++++--------- .../yapplication/database/DBContract.java | 58 ++++++------ .../yapplication/database/DBHelper.java | 57 ++++++------ .../dataprovider/ArtistsProvider.java | 8 +- .../dataprovider/WebArtistsProvider.java | 61 ++---------- .../aleien/yapplication/di/AppComponent.java | 9 +- .../ru/aleien/yapplication/di/AppModule.java | 54 +++++++++-- .../ru/aleien/yapplication/model/Artist.java | 2 - .../screens/list/AboutDialogFragment.java | 34 +++++++ ...Builder.java => PendingIntentBuilder.java} | 10 +- .../ru/aleien/yapplication/utils/Utils.java | 61 +----------- app/src/main/res/layout/activity_main.xml | 24 ++--- app/src/main/res/values/strings.xml | 4 + .../yapplication/database/DBBackendTest.java | 7 -- 20 files changed, 309 insertions(+), 319 deletions(-) create mode 100644 app/src/main/java/ru/aleien/yapplication/App.java rename app/src/main/java/ru/aleien/yapplication/{ListArtistsActivity.java => MainActivity.java} (70%) delete mode 100644 app/src/main/java/ru/aleien/yapplication/database/DBArtistsProvider.java create mode 100644 app/src/main/java/ru/aleien/yapplication/screens/list/AboutDialogFragment.java rename app/src/main/java/ru/aleien/yapplication/utils/{IntentBuilder.java => PendingIntentBuilder.java} (76%) diff --git a/app/build.gradle b/app/build.gradle index af20ad3..453f328 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -86,5 +86,6 @@ dependencies { compile 'io.reactivex:rxjava:1.1.8' debugCompile 'com.facebook.stetho:stetho:1.2.0' + compile 'com.jakewharton.timber:timber:4.1.2' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1b143a3..0a40382 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,12 +8,13 @@ - + diff --git a/app/src/main/java/ru/aleien/yapplication/App.java b/app/src/main/java/ru/aleien/yapplication/App.java new file mode 100644 index 0000000..c91dcac --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/App.java @@ -0,0 +1,30 @@ +package ru.aleien.yapplication; + +import android.app.Application; + +import com.facebook.stetho.Stetho; + +import ru.aleien.yapplication.di.AppComponent; +import ru.aleien.yapplication.di.AppModule; +import ru.aleien.yapplication.di.DaggerAppComponent; +import timber.log.Timber; + +public class App extends Application { + private AppComponent component; + + + @Override + public void onCreate() { + super.onCreate(); + if (BuildConfig.DEBUG) Timber.plant(new Timber.DebugTree()); + Stetho.initializeWithDefaults(this.getApplicationContext()); + component = DaggerAppComponent.builder() + .appModule(new AppModule(this)) + .build(); + + } + + public AppComponent dagger() { + return component; + } +} diff --git a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java index f588796..0c4ef32 100644 --- a/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java +++ b/app/src/main/java/ru/aleien/yapplication/ArtistsPresenter.java @@ -1,6 +1,5 @@ package ru.aleien.yapplication; -import android.content.Context; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -16,14 +15,15 @@ import ru.aleien.yapplication.dataprovider.ArtistsProvider; import ru.aleien.yapplication.dataprovider.WebArtistsProvider; import ru.aleien.yapplication.model.Artist; -import ru.aleien.yapplication.database.DBHelper; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoFragment; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoView; import ru.aleien.yapplication.screens.list.ArtistsListView; import ru.aleien.yapplication.screens.list.ArtistsRecyclerFragment; import ru.aleien.yapplication.utils.adapters.ArtistsRecyclerAdapter; +import rx.Completable; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; +import timber.log.Timber; /** * Created by aleien on 09.04.16. @@ -32,17 +32,16 @@ */ public class ArtistsPresenter extends BasePresenter implements ArtistsRequester, ArtistClickHandler, Serializable { ArtistsProvider artistsProvider; - private DBHelper dbHelper; private final DBBackend dbSource; private WeakReference> artistsListView; private WeakReference currentFragment; @Inject - public ArtistsPresenter(DBHelper dbHelper, - DBBackend dbSource) { - artistsProvider = new WebArtistsProvider(this); - this.dbHelper = dbHelper; + public ArtistsPresenter(DBBackend dbSource, + // Как здесь получать интерфейс? + WebArtistsProvider artistsProvider) { this.dbSource = dbSource; + this.artistsProvider = artistsProvider; } @Override @@ -53,9 +52,13 @@ public void takeListView(ArtistsListView list) { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::provideData, - throwable -> Log.e("DBError", "Error while reading cached artists"))); + throwable -> Timber.e("DBError", "Error while reading cached artists"))); - artistsProvider.requestData(); + artistsProvider.requestData() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::provideData, + e -> Timber.e(e, "takeListView -> requestData")); } @Override @@ -68,9 +71,13 @@ public void takeDetailedView(ArtistInfoView info, Artist artist) { public void provideData(List response) { artistsListView.get().setAdapter(new ArtistsRecyclerAdapter(response, this)); dbSource.clearArtists(); - for (Artist artist : response) { - dbSource.insertArtist(artist); - } + Completable.fromAction(() -> { + Timber.e("Working on: " + Thread.currentThread().getName()); + for (Artist artist : response) { + dbSource.insertArtist(artist); + } + }).subscribeOn(Schedulers.io()).subscribe(); + } diff --git a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java b/app/src/main/java/ru/aleien/yapplication/MainActivity.java similarity index 70% rename from app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java rename to app/src/main/java/ru/aleien/yapplication/MainActivity.java index aa74a94..be4ddb9 100644 --- a/app/src/main/java/ru/aleien/yapplication/ListArtistsActivity.java +++ b/app/src/main/java/ru/aleien/yapplication/MainActivity.java @@ -6,48 +6,65 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.database.sqlite.SQLiteDatabase; import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; import android.os.PersistableBundle; +import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.NotificationCompat; import android.support.v4.content.ContextCompat; -import android.support.v4.content.res.ResourcesCompat; -import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; -import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.widget.Toast; - -import com.facebook.stetho.Stetho; import javax.inject.Inject; -import ru.aleien.yapplication.di.AppComponent; -import ru.aleien.yapplication.di.AppModule; -import ru.aleien.yapplication.di.DaggerAppComponent; -import ru.aleien.yapplication.utils.IntentBuilder; -import ru.aleien.yapplication.utils.Utils; +import butterknife.BindString; +import butterknife.ButterKnife; +import ru.aleien.yapplication.screens.list.AboutDialogFragment; +import ru.aleien.yapplication.utils.PendingIntentBuilder; + +public class MainActivity extends AppCompatActivity implements MainView { + private final static int MUSIC_ID = 1010; + private final static int RADIO_ID = 1020; -public class ListArtistsActivity extends AppCompatActivity implements MainView { + private BroadcastReceiver broadcastReceiver; @Inject ArtistsPresenter artistsPresenter; - @Inject SQLiteDatabase database; + + @BindString(R.string.about_title) String aboutTitle; + @BindString(R.string.about_message) String aboutMessage; + @BindString(R.string.title_dismiss) String dismissTitle; + @BindString(R.string.email_author) String emailTo; + @BindString(R.string.email_title) String emailTitle; + private final static Intent EMAIL_INTENT = new Intent(Intent.ACTION_SENDTO) + .setType("text/plain") + .setData(Uri.parse("mailto:")) + .putExtra(Intent.EXTRA_EMAIL, new String[]{"technogenom@gmail.com"}) + .putExtra(Intent.EXTRA_SUBJECT, "Re: Yapplication"); + + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Stetho.initializeWithDefaults(this.getApplicationContext()); - AppComponent appComponent = DaggerAppComponent.builder() - .appModule(new AppModule(this)) - .build(); - appComponent.inject(this); + + ((App) getApplication()).dagger().inject(this); + broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + showHeadphonesNotification(audioManager.isWiredHeadsetOn() + || audioManager.isBluetoothA2dpOn() + || audioManager.isBluetoothScoOn()); + + } + }; setContentView(R.layout.activity_main); + ButterKnife.bind(this); setupToolbar(); } @@ -57,16 +74,7 @@ protected void onStart() { artistsPresenter.attachView(this); artistsPresenter.onStart(); - registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - showHeadphonesNotification(audioManager.isWiredHeadsetOn() - || audioManager.isBluetoothA2dpOn() - || audioManager.isBluetoothScoOn()); - - } - }, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); + registerReceiver(broadcastReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); } @@ -74,6 +82,7 @@ public void onReceive(Context context, Intent intent) { protected void onStop() { super.onStop(); artistsPresenter.detachView(); + unregisterReceiver(broadcastReceiver); } @Override @@ -122,7 +131,7 @@ public void changeFragmentTo(Fragment fragment, boolean hideBackButton) { getSupportActionBar().setDisplayHomeAsUpEnabled(hideBackButton); getSupportFragmentManager().beginTransaction() .replace(R.id.fragment_container, fragment) - .addToBackStack("info") + .addToBackStack(null) .commit(); } @@ -136,11 +145,9 @@ public void onBackPressed() { // TODO: Вынести в отдельный класс // TODO: При открытой странице инфо об артисте, открывать страницу артиста private void showHeadphonesNotification(boolean wiredHeadsetOn) { - Intent musicIntent = IntentBuilder.buildOpenAppOrMarketPageIntent("ru.yandex.music", this); - Intent radioIntent = IntentBuilder.buildOpenAppOrMarketPageIntent("ru.yandex.radio", this); - PendingIntent musicPendingIntent = PendingIntent.getActivity(this, 1010, musicIntent, 0); - PendingIntent radioPendingIntent = PendingIntent.getActivity(this, 1020, radioIntent, 0); + PendingIntent musicPendingIntent = PendingIntentBuilder.buildOpenMarketPendingIntent(MUSIC_ID, "ru.yandex.music", this); + PendingIntent radioPendingIntent = PendingIntentBuilder.buildOpenMarketPendingIntent(RADIO_ID, "ru.yandex.radio", this); int musicNotificationId = 001; @@ -168,22 +175,15 @@ private void showHeadphonesNotification(boolean wiredHeadsetOn) { } private void composeEmail() { - Intent emailIntent = new Intent(Intent.ACTION_SENDTO); - emailIntent.setType("text/plain"); - emailIntent.setData(Uri.parse("mailto:")); - emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"technogenom@gmail.com"}); - emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Re: Yapplication"); - if (emailIntent.resolveActivity(getPackageManager()) != null) { - startActivity(emailIntent); + if (EMAIL_INTENT.resolveActivity(getPackageManager()) != null) { + startActivity(EMAIL_INTENT); } } private void showAbout() { - new AlertDialog.Builder(this) - .setTitle(getResources().getString(R.string.about_title)) - .setMessage(getResources().getString(R.string.about_message) + Utils.getAppVersion(this)) - .setNegativeButton(getResources().getString(R.string.title_dismiss), null) - .show(); + DialogFragment aboutFragment = AboutDialogFragment + .newInstance(aboutTitle, aboutMessage); + aboutFragment.show(getSupportFragmentManager(), "dialog"); } } diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBArtistsProvider.java b/app/src/main/java/ru/aleien/yapplication/database/DBArtistsProvider.java deleted file mode 100644 index 6965177..0000000 --- a/app/src/main/java/ru/aleien/yapplication/database/DBArtistsProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.aleien.yapplication.database; - -/** - * Created by aleien on 08.08.16. - */ - -public class DBArtistsProvider { -} diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java b/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java index e2d3b52..eda51f4 100644 --- a/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java +++ b/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java @@ -12,28 +12,15 @@ import ru.aleien.yapplication.model.Artist; import rx.Observable; -import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_ALBUMS; -import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_BIG_COVER; -import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_DESCRIPTION; -import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_ID; -import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_LINK; -import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_NAME; -import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_SMALL_COVER; -import static ru.aleien.yapplication.database.DBContract.ARTISTS_COLUMN_TRACKS; -import static ru.aleien.yapplication.database.DBContract.ARTISTS_TABLE_NAME; -import static ru.aleien.yapplication.database.DBContract.ARTIST_COLUMN_ID; -import static ru.aleien.yapplication.database.DBContract.GENRES_COLUMN_GENRE; -import static ru.aleien.yapplication.database.DBContract.GENRES_COLUMN_ID; -import static ru.aleien.yapplication.database.DBContract.GENRES_TABLE_NAME; -import static ru.aleien.yapplication.database.DBContract.GENRE_TO_ARTIST_COLUMN_ID; -import static ru.aleien.yapplication.database.DBContract.GENRE_TO_ARTIST_TABLE_NAME; +import static ru.aleien.yapplication.database.DBContract.GenreToArtist.ARTIST_ID; +import static ru.aleien.yapplication.database.DBContract.GenreToArtist.GENRE_ID; +import static ru.aleien.yapplication.database.DBContract.GenreToArtist.TABLE; import static ru.aleien.yapplication.database.DBContract.allColumns; public class DBBackend { private DBHelper dbOpenHelper; - @Inject public DBBackend(DBHelper helper) { dbOpenHelper = helper; @@ -64,15 +51,15 @@ void insertArtist(String name, try { ContentValues contentValues = new ContentValues(); - contentValues.put(ARTISTS_COLUMN_NAME, name); - contentValues.put(ARTISTS_COLUMN_TRACKS, tracks); - contentValues.put(ARTISTS_COLUMN_ALBUMS, albums); - contentValues.put(ARTISTS_COLUMN_LINK, link); - contentValues.put(ARTISTS_COLUMN_DESCRIPTION, description); - contentValues.put(ARTISTS_COLUMN_SMALL_COVER, small_cover); - contentValues.put(ARTISTS_COLUMN_BIG_COVER, big_cover); - - long artistId = db.insert(ARTISTS_TABLE_NAME, null, contentValues); + contentValues.put(DBContract.Artists.NAME, name); + contentValues.put(DBContract.Artists.TRACKS, tracks); + contentValues.put(DBContract.Artists.ALBUMS, albums); + contentValues.put(DBContract.Artists.LINK, link); + contentValues.put(DBContract.Artists.DESCRIPTION, description); + contentValues.put(DBContract.Artists.SMALL_COVER, small_cover); + contentValues.put(DBContract.Artists.BIG_COVER, big_cover); + + long artistId = db.insert(DBContract.Artists.TABLE, null, contentValues); List genresIds = insertGenres(genres); insertRelation(artistId, genresIds); @@ -89,9 +76,9 @@ void insertRelation(long artistId, List genresIds) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); for (Long genreId : genresIds) { ContentValues contentValues = new ContentValues(); - contentValues.put(ARTIST_COLUMN_ID, artistId); - contentValues.put(GENRES_COLUMN_ID, genreId); - db.insert(GENRE_TO_ARTIST_TABLE_NAME, null, contentValues); + contentValues.put(ARTIST_ID, artistId); + contentValues.put(GENRE_ID, genreId); + db.insert(TABLE, null, contentValues); } } @@ -100,8 +87,8 @@ List insertGenres(List genres) { List rowIds = new ArrayList<>(); ContentValues contentValues = new ContentValues(); for (String genre : genres) { - contentValues.put(GENRES_COLUMN_GENRE, genre); - long rowId = db.insertWithOnConflict(GENRES_TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE); + contentValues.put(DBContract.Genres.NAME, genre); + long rowId = db.insertWithOnConflict(DBContract.Genres.TABLE, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE); rowIds.add(rowId); } @@ -110,16 +97,13 @@ List insertGenres(List genres) { } public Observable> getAllArtists() { - return Observable.create(subscriber -> { - subscriber.onNext(loadArtists()); - subscriber.onCompleted(); - }); + return Observable.fromCallable(this::loadArtists); } - List loadArtists() { + List loadArtists() { List artists = new ArrayList<>(); SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); - Cursor cursor = db.query(ARTISTS_TABLE_NAME, + Cursor cursor = db.query(DBContract.Artists.TABLE, allColumns, null, null, null, null, null); cursor.moveToFirst(); @@ -133,18 +117,18 @@ List loadArtists() { } public void clearArtists() { - dbOpenHelper.getWritableDatabase().execSQL("DELETE FROM " + ARTISTS_TABLE_NAME); + dbOpenHelper.getWritableDatabase().execSQL("DELETE FROM " + DBContract.Artists.TABLE); } private Artist cursorToArtist(Cursor cursor) { - return new Artist(cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_ID)), - cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_NAME)), + return new Artist(cursor.getInt(cursor.getColumnIndex(DBContract.Artists.ID)), + cursor.getString(cursor.getColumnIndex(DBContract.Artists.NAME)), new ArrayList<>(), - cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_TRACKS)), - cursor.getInt(cursor.getColumnIndex(ARTISTS_COLUMN_ALBUMS)), - cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_LINK)), - cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_DESCRIPTION)), - new Artist.Cover(cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_SMALL_COVER)), - cursor.getString(cursor.getColumnIndex(ARTISTS_COLUMN_BIG_COVER)))); + cursor.getInt(cursor.getColumnIndex(DBContract.Artists.TRACKS)), + cursor.getInt(cursor.getColumnIndex(DBContract.Artists.ALBUMS)), + cursor.getString(cursor.getColumnIndex(DBContract.Artists.LINK)), + cursor.getString(cursor.getColumnIndex(DBContract.Artists.DESCRIPTION)), + new Artist.Cover(cursor.getString(cursor.getColumnIndex(DBContract.Artists.SMALL_COVER)), + cursor.getString(cursor.getColumnIndex(DBContract.Artists.BIG_COVER)))); } } diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBContract.java b/app/src/main/java/ru/aleien/yapplication/database/DBContract.java index 9ee0bab..abf6523 100644 --- a/app/src/main/java/ru/aleien/yapplication/database/DBContract.java +++ b/app/src/main/java/ru/aleien/yapplication/database/DBContract.java @@ -5,35 +5,41 @@ */ public interface DBContract { - public static final String ARTISTS_TABLE_NAME = "artists"; - public static final String ARTISTS_COLUMN_ID = "id"; - public static final String ARTISTS_COLUMN_NAME = "name"; - public static final String ARTISTS_COLUMN_TRACKS = "tracks"; - public static final String ARTISTS_COLUMN_ALBUMS = "albums"; - public static final String ARTISTS_COLUMN_LINK = "link"; - public static final String ARTISTS_COLUMN_DESCRIPTION = "description"; - public static final String ARTISTS_COLUMN_SMALL_COVER = "small_cover"; - public static final String ARTISTS_COLUMN_BIG_COVER = "big_cover"; - - public static final String GENRES_TABLE_NAME = "genres"; - public static final String GENRES_COLUMN_ID = "id"; - public static final String GENRES_COLUMN_GENRE = "genre"; - - public static final String GENRE_TO_ARTIST_TABLE_NAME = "genre_to_artist"; - public static final String GENRE_TO_ARTIST_COLUMN_ID = "id"; - public static final String ARTIST_COLUMN_ID = "artist_id"; - public static final String GENRE_COLUMN_ID = "genre_id"; + interface Artists { + String TABLE = "artists"; + String ID = "id"; + String NAME = "name"; + String TRACKS = "tracks"; + String ALBUMS = "albums"; + String LINK = "link"; + String DESCRIPTION = "description"; + String SMALL_COVER = "small_cover"; + String BIG_COVER = "big_cover"; + } + + interface Genres { + String TABLE = "genres"; + String NAME = "genre"; + + } + + interface GenreToArtist { + String TABLE = "genre_to_artist"; + String ARTIST_ID = "artist_id"; + String GENRE_ID = "genre_id"; + + } String DROP_TABLE_IF_EXISTS = "DROP TABLE IF EXISTS "; String[] allColumns = new String[]{ - ARTISTS_COLUMN_ID, - ARTISTS_COLUMN_NAME, - ARTISTS_COLUMN_TRACKS, - ARTISTS_COLUMN_ALBUMS, - ARTISTS_COLUMN_LINK, - ARTISTS_COLUMN_DESCRIPTION, - ARTISTS_COLUMN_SMALL_COVER, - ARTISTS_COLUMN_BIG_COVER + Artists.ID, + Artists.NAME, + Artists.TRACKS, + Artists.ALBUMS, + Artists.LINK, + Artists.DESCRIPTION, + Artists.SMALL_COVER, + Artists.BIG_COVER }; } diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java b/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java index ed4cc8e..9b68837 100644 --- a/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java +++ b/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java @@ -1,69 +1,64 @@ package ru.aleien.yapplication.database; -import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import java.util.ArrayList; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import ru.aleien.yapplication.model.Artist; -import static ru.aleien.yapplication.database.DBContract.ARTISTS_TABLE_NAME; -import static ru.aleien.yapplication.database.DBContract.ARTIST_COLUMN_ID; import static ru.aleien.yapplication.database.DBContract.DROP_TABLE_IF_EXISTS; -import static ru.aleien.yapplication.database.DBContract.GENRES_COLUMN_GENRE; -import static ru.aleien.yapplication.database.DBContract.GENRES_COLUMN_ID; -import static ru.aleien.yapplication.database.DBContract.GENRES_TABLE_NAME; -import static ru.aleien.yapplication.database.DBContract.GENRE_COLUMN_ID; -import static ru.aleien.yapplication.database.DBContract.GENRE_TO_ARTIST_COLUMN_ID; -import static ru.aleien.yapplication.database.DBContract.GENRE_TO_ARTIST_TABLE_NAME; /** * Created by user on 22.07.16. */ +@Singleton public class DBHelper extends SQLiteOpenHelper { - public DBHelper(Context context, String name) { + @Inject + public DBHelper(Context context, @Named("dbName") String name) { super(context, name, null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL( - "CREATE TABLE ARTISTS " + + "CREATE TABLE " + DBContract.Artists.TABLE + "(" + - "ID INTEGER PRIMARY KEY," + - "NAME TEXT," + - "TRACKS INTEGER," + - "ALBUMS INTEGER," + - "LINK TEXT," + - "DESCRIPTION TEXT," + - "SMALL_COVER TEXT," + - "BIG_COVER TEXT" + + DBContract.Artists.NAME + " TEXT," + + DBContract.Artists.TRACKS + " INTEGER," + + DBContract.Artists.ALBUMS + " INTEGER," + + DBContract.Artists.LINK + " TEXT," + + DBContract.Artists.DESCRIPTION + " TEXT," + + DBContract.Artists.SMALL_COVER + " TEXT," + + DBContract.Artists.BIG_COVER + " TEXT" + ")" ); db.execSQL( - "CREATE TABLE GENRES" + + "CREATE TABLE " + + DBContract.Genres.TABLE + " (" + - "ID INTEGER PRIMARY KEY, " + - "GENRE STRING UNIQUE" + + DBContract.Genres.NAME + " STRING UNIQUE" + ")"); db.execSQL( - "CREATE TABLE " + GENRE_TO_ARTIST_TABLE_NAME + - " (" + GENRE_TO_ARTIST_COLUMN_ID + " INTEGER PRIMARY KEY, " - + ARTIST_COLUMN_ID + " INTEGER," + GENRE_COLUMN_ID + " INTEGER )"); + "CREATE TABLE " + DBContract.GenreToArtist.TABLE + + " (" + + DBContract.GenreToArtist.ARTIST_ID + " INTEGER," + + DBContract.GenreToArtist.GENRE_ID + " INTEGER )"); } @Override public void onUpgrade(SQLiteDatabase db, int i, int i1) { - db.execSQL(DROP_TABLE_IF_EXISTS + ARTISTS_TABLE_NAME); - db.execSQL(DROP_TABLE_IF_EXISTS + GENRES_TABLE_NAME); - db.execSQL(DROP_TABLE_IF_EXISTS + GENRE_TO_ARTIST_TABLE_NAME); + db.execSQL(DROP_TABLE_IF_EXISTS + DBContract.Artists.TABLE); + db.execSQL(DROP_TABLE_IF_EXISTS + DBContract.Genres.TABLE); + db.execSQL(DROP_TABLE_IF_EXISTS + DBContract.GenreToArtist.TABLE); onCreate(db); } - } diff --git a/app/src/main/java/ru/aleien/yapplication/dataprovider/ArtistsProvider.java b/app/src/main/java/ru/aleien/yapplication/dataprovider/ArtistsProvider.java index 901e925..a9854fc 100644 --- a/app/src/main/java/ru/aleien/yapplication/dataprovider/ArtistsProvider.java +++ b/app/src/main/java/ru/aleien/yapplication/dataprovider/ArtistsProvider.java @@ -1,9 +1,15 @@ package ru.aleien.yapplication.dataprovider; +import java.util.List; + +import ru.aleien.yapplication.model.Artist; +import rx.Observable; + /** * Created by aleien on 09.04.16. * Интерфейс между контроллером и поставщиком данных */ public interface ArtistsProvider { - void requestData(); + // Лучше дженерик? + Observable> requestData(); } diff --git a/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java b/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java index e235ab9..720db16 100644 --- a/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java +++ b/app/src/main/java/ru/aleien/yapplication/dataprovider/WebArtistsProvider.java @@ -1,31 +1,12 @@ package ru.aleien.yapplication.dataprovider; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; - -import java.io.IOException; import java.util.List; -import okhttp3.Cache; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.logging.HttpLoggingInterceptor; -import retrofit2.Retrofit; -import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; -import retrofit2.converter.gson.GsonConverterFactory; -import retrofit2.http.GET; -import ru.aleien.yapplication.ArtistsRequester; +import javax.inject.Inject; + +import ru.aleien.yapplication.di.AppModule; import ru.aleien.yapplication.model.Artist; -import ru.aleien.yapplication.utils.Utils; import rx.Observable; -import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; /** * Created by aleien on 09.04.16. @@ -33,41 +14,17 @@ */ public class WebArtistsProvider implements ArtistsProvider { - private static final String BASE_URL = "http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/"; - private final ArtistsRequester artistsRequester; - private OkHttpClient client; - - private Api api; - - interface Api { - @GET("artists.json") - Observable> getArtists(); - } - - public WebArtistsProvider(ArtistsRequester artistsRequester) { - this.artistsRequester = artistsRequester; - OkHttpClient loggingClient = new OkHttpClient.Builder() - .addInterceptor(new HttpLoggingInterceptor()) - .build(); - Retrofit retrofit = new Retrofit.Builder() - .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) - .addConverterFactory(GsonConverterFactory.create()) - .baseUrl(BASE_URL) - .client(loggingClient) - .build(); + private AppModule.Api api; - api = retrofit.create(Api.class); + @Inject + public WebArtistsProvider(AppModule.Api api) { + this.api = api; } - //TODO: переделать через rx, параллельным запросом? @Override - public void requestData() { - api.getArtists() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(artistsRequester::provideData, - Throwable::printStackTrace); + public Observable> requestData() { + return api.getArtists(); } } diff --git a/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java b/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java index bc2eaf3..6a7ab21 100644 --- a/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java +++ b/app/src/main/java/ru/aleien/yapplication/di/AppComponent.java @@ -3,17 +3,12 @@ import javax.inject.Singleton; import dagger.Component; -import ru.aleien.yapplication.ListArtistsActivity; -import ru.aleien.yapplication.database.DBBackend; +import ru.aleien.yapplication.MainActivity; -/** - * Created by aleien on 22.07.16. - */ @Singleton @Component(modules = AppModule.class) public interface AppComponent { - void inject(ListArtistsActivity listArtistsActivity); + void inject(MainActivity mainActivity); - void inject(DBBackend DBBackend); } diff --git a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java index 6431658..97cc5cc 100644 --- a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java +++ b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java @@ -1,14 +1,24 @@ package ru.aleien.yapplication.di; -import android.app.Activity; +import android.app.Application; import android.content.Context; import android.database.sqlite.SQLiteDatabase; +import java.util.List; + +import javax.inject.Named; import javax.inject.Singleton; import dagger.Module; import dagger.Provides; -import ru.aleien.yapplication.database.DBHelper; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.http.GET; +import ru.aleien.yapplication.model.Artist; +import rx.Observable; import static android.content.Context.MODE_PRIVATE; @@ -17,12 +27,13 @@ */ @Module public class AppModule { + private static final String BASE_URL = "http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/"; private Context context; - public static String dbName = "ArtistsDB"; + private String dbName = "ArtistsDB"; - public AppModule(Activity activity) { - this.context = activity.getApplicationContext(); + public AppModule(Application application) { + this.context = application; } @Provides @@ -37,10 +48,39 @@ SQLiteDatabase provideDatabase(Context context) { return context.openOrCreateDatabase(dbName, MODE_PRIVATE, null); } + @Provides + @Named("dbName") + String provideDBName() { + return dbName; + } + @Provides @Singleton - DBHelper provideDBHelper(Context context) { - return new DBHelper(context, dbName); + Retrofit provideRestClient(OkHttpClient client) { + return new Retrofit.Builder() + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .baseUrl(BASE_URL) + .client(client) + .build(); + } + + @Provides + @Singleton + OkHttpClient provideOkhttpClient() { + return new OkHttpClient.Builder() + .addInterceptor(new HttpLoggingInterceptor()) + .build(); + } + + @Provides + Api provideApi(Retrofit rest) { + return rest.create(Api.class); + } + + public interface Api { + @GET("artists.json") + Observable> getArtists(); } } diff --git a/app/src/main/java/ru/aleien/yapplication/model/Artist.java b/app/src/main/java/ru/aleien/yapplication/model/Artist.java index 9a49982..b9d39de 100644 --- a/app/src/main/java/ru/aleien/yapplication/model/Artist.java +++ b/app/src/main/java/ru/aleien/yapplication/model/Artist.java @@ -1,7 +1,5 @@ package ru.aleien.yapplication.model; -import android.os.Parcelable; - import java.util.List; public class Artist { diff --git a/app/src/main/java/ru/aleien/yapplication/screens/list/AboutDialogFragment.java b/app/src/main/java/ru/aleien/yapplication/screens/list/AboutDialogFragment.java new file mode 100644 index 0000000..270ad35 --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/screens/list/AboutDialogFragment.java @@ -0,0 +1,34 @@ +package ru.aleien.yapplication.screens.list; + +import android.app.Dialog; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; + +import ru.aleien.yapplication.R; + +public class AboutDialogFragment extends DialogFragment { + + public static AboutDialogFragment newInstance(String title, String message) { + AboutDialogFragment frag = new AboutDialogFragment(); + Bundle args = new Bundle(); + args.putString("title", title); + args.putString("message", message); + frag.setArguments(args); + return frag; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + String title = getArguments().getString("title"); + String message = getArguments().getString("message"); + + return new AlertDialog.Builder(getActivity()) + .setTitle(title) + .setMessage(message) + .setPositiveButton(R.string.alert_dialog_ok, + (dialog, whichButton) -> dismiss() + ) + .create(); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/aleien/yapplication/utils/IntentBuilder.java b/app/src/main/java/ru/aleien/yapplication/utils/PendingIntentBuilder.java similarity index 76% rename from app/src/main/java/ru/aleien/yapplication/utils/IntentBuilder.java rename to app/src/main/java/ru/aleien/yapplication/utils/PendingIntentBuilder.java index a231202..04075e1 100644 --- a/app/src/main/java/ru/aleien/yapplication/utils/IntentBuilder.java +++ b/app/src/main/java/ru/aleien/yapplication/utils/PendingIntentBuilder.java @@ -1,5 +1,6 @@ package ru.aleien.yapplication.utils; +import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -9,9 +10,14 @@ /** * Created by user on 19.07.16. */ -public class IntentBuilder { +public class PendingIntentBuilder { private static final String playStore = "market://details?id="; + public static PendingIntent buildOpenMarketPendingIntent(int id, String pack, Context context) { + Intent intent = buildOpenAppOrMarketPageIntent(pack, context); + return PendingIntent.getActivity(context, id, intent, 0); + } + public static Intent buildOpenAppOrMarketPageIntent(String pack, Context context) { try { @@ -31,4 +37,6 @@ public static Intent buildOpenAppOrMarketPageIntent(String pack, Context context } + + } diff --git a/app/src/main/java/ru/aleien/yapplication/utils/Utils.java b/app/src/main/java/ru/aleien/yapplication/utils/Utils.java index eccb8de..b46b6ce 100644 --- a/app/src/main/java/ru/aleien/yapplication/utils/Utils.java +++ b/app/src/main/java/ru/aleien/yapplication/utils/Utils.java @@ -1,24 +1,11 @@ package ru.aleien.yapplication.utils; -import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.support.annotation.NonNull; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Type; import java.util.List; -import okhttp3.Cache; -import okhttp3.Interceptor; -import okhttp3.Response; -import ru.aleien.yapplication.model.Artist; +import timber.log.Timber; public class Utils { @@ -35,56 +22,12 @@ public static String convertToString(List list, Character separator) { return resultString.toString(); } - public static boolean isNetworkAvailable(Context context) { - ConnectivityManager connectivityManager = (ConnectivityManager) context.getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); - - NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); - return (activeNetworkInfo != null) && (activeNetworkInfo.isConnected()); - } - - @NonNull - public static Interceptor createInterceptor(Context context) { - return chain -> { - Response originalResponse = chain.proceed(chain.request()); - if (Utils.isNetworkAvailable(context)) { - int maxAge = 60; - return originalResponse.newBuilder() - .header("Cache-Control", "public, max-age=" + maxAge) - .build(); - } else { - int maxStale = 60 * 60 * 24 * 28; - return originalResponse.newBuilder() - .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) - .build(); - } - }; - } - - public static Cache getCache(Context context) { - File httpCacheDirectory = new File(context.getCacheDir(), "responses"); - int cacheSize = 10 * 1024 * 1024; // 10 MiB - return new Cache(httpCacheDirectory, cacheSize); - } - - public static List decodeResponse(Response response) { - List resultList = null; - Type listType = new TypeToken>() { - }.getType(); - try { - resultList = new Gson().fromJson(response.body().string(), listType); - } catch (IOException e) { - e.printStackTrace(); - } - - return resultList; - } - public static String getAppVersion(Context context) { String versionCode = "1.0"; try { versionCode = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; } catch (PackageManager.NameNotFoundException e) { + Timber.e(e, "getAppVersion: Could not get package info"); e.printStackTrace(); } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0ed90a1..f8e0c7f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,15 +1,15 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:theme="@style/AppTheme"> + android:layout_height="wrap_content" + android:orientation="vertical"> + app:popupTheme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/> - + android:background="@drawable/shadow"/> - - - + android:layout_marginTop="@dimen/toolbar"/> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b01b21..e6724ee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,6 +10,10 @@ About This is a simple application with simple needs. No worries, no trouble.\n Well, maybe just some. DISMISS + technogenom@gmail.com + Re: Yapplication + OK + CANCEL diff --git a/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java b/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java index 7ff2c2b..31cccbc 100644 --- a/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java +++ b/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java @@ -1,13 +1,10 @@ package ru.aleien.yapplication.database; -import android.database.sqlite.SQLiteDatabase; - import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @@ -17,10 +14,6 @@ import ru.aleien.yapplication.BuildConfig; import ru.aleien.yapplication.model.Artist; -import rx.observers.TestSubscriber; -import rx.schedulers.Schedulers; - -import static org.junit.Assert.*; /** * Created by aleien on 09.08.16. From 1cca5dfed8205d3a6c0dd542740dd106f00c9e2b Mon Sep 17 00:00:00 2001 From: aleien Date: Wed, 10 Aug 2016 00:21:52 +0300 Subject: [PATCH 11/15] Merge fixes --- .idea/gradle.xml | 9 ++++++++- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.idea/gradle.xml b/.idea/gradle.xml index f99298b..7990923 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -3,9 +3,16 @@ diff --git a/build.gradle b/build.gradle index b714fcc..0d7486e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0-alpha6' + classpath 'com.android.tools.build:gradle:2.2.0-alpha7' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'me.tatarka:gradle-retrolambda:3.3.0-beta4' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f37b0f3..00ece73 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Apr 10 21:36:34 MSK 2016 +#Wed Aug 10 00:21:08 MSK 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip From 47f1b40dedc491455ea8304197a8e5b5c891eb6a Mon Sep 17 00:00:00 2001 From: aleien Date: Wed, 10 Aug 2016 16:14:04 +0300 Subject: [PATCH 12/15] Tests --- .../ru/aleien/yapplication/MainActivity.java | 3 -- .../yapplication/database/DBBackend.java | 12 ++--- .../yapplication/database/DBContract.java | 1 + .../yapplication/database/DBHelper.java | 1 + .../yapplication/database/DBProvider.java | 9 ++++ .../ru/aleien/yapplication/model/Artist.java | 52 +++++++++++++++++++ .../yapplication/ArtistsPresenterTest.java | 5 +- .../yapplication/database/DBBackendTest.java | 1 + 8 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/ru/aleien/yapplication/database/DBProvider.java diff --git a/app/src/main/java/ru/aleien/yapplication/MainActivity.java b/app/src/main/java/ru/aleien/yapplication/MainActivity.java index be4ddb9..8f79e66 100644 --- a/app/src/main/java/ru/aleien/yapplication/MainActivity.java +++ b/app/src/main/java/ru/aleien/yapplication/MainActivity.java @@ -75,7 +75,6 @@ protected void onStart() { artistsPresenter.onStart(); registerReceiver(broadcastReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); - } @Override @@ -171,14 +170,12 @@ private void showHeadphonesNotification(boolean wiredHeadsetOn) { mNotifyMgr.cancel(musicNotificationId); } - } private void composeEmail() { if (EMAIL_INTENT.resolveActivity(getPackageManager()) != null) { startActivity(EMAIL_INTENT); } - } private void showAbout() { diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java b/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java index eda51f4..95064e4 100644 --- a/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java +++ b/app/src/main/java/ru/aleien/yapplication/database/DBBackend.java @@ -27,7 +27,8 @@ public DBBackend(DBHelper helper) { } public void insertArtist(Artist artist) { - insertArtist(artist.name, + insertArtist(artist.id, + artist.name, artist.tracks, artist.albums, artist.link, @@ -37,8 +38,8 @@ public void insertArtist(Artist artist) { artist.genres); } - // Напрашивается билдер - void insertArtist(String name, + void insertArtist(int id, + String name, int tracks, int albums, String link, @@ -51,6 +52,7 @@ void insertArtist(String name, try { ContentValues contentValues = new ContentValues(); + contentValues.put(DBContract.Artists.ID, id); contentValues.put(DBContract.Artists.NAME, name); contentValues.put(DBContract.Artists.TRACKS, tracks); contentValues.put(DBContract.Artists.ALBUMS, albums); @@ -68,8 +70,6 @@ void insertArtist(String name, } finally { db.endTransaction(); } - - } void insertRelation(long artistId, List genresIds) { @@ -93,7 +93,6 @@ List insertGenres(List genres) { } return rowIds; - } public Observable> getAllArtists() { @@ -113,6 +112,7 @@ List loadArtists() { } cursor.close(); + return artists; } diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBContract.java b/app/src/main/java/ru/aleien/yapplication/database/DBContract.java index abf6523..58f0a91 100644 --- a/app/src/main/java/ru/aleien/yapplication/database/DBContract.java +++ b/app/src/main/java/ru/aleien/yapplication/database/DBContract.java @@ -23,6 +23,7 @@ interface Genres { } + // А может быть тут можно не genre_id, а genre_name? interface GenreToArtist { String TABLE = "genre_to_artist"; String ARTIST_ID = "artist_id"; diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java b/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java index 9b68837..52658e9 100644 --- a/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java +++ b/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java @@ -29,6 +29,7 @@ public void onCreate(SQLiteDatabase db) { db.execSQL( "CREATE TABLE " + DBContract.Artists.TABLE + "(" + + DBContract.Artists.ID + " INTEGER PRIMARY KEY, " + DBContract.Artists.NAME + " TEXT," + DBContract.Artists.TRACKS + " INTEGER," + DBContract.Artists.ALBUMS + " INTEGER," + diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBProvider.java b/app/src/main/java/ru/aleien/yapplication/database/DBProvider.java new file mode 100644 index 0000000..b03b6e8 --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/database/DBProvider.java @@ -0,0 +1,9 @@ +package ru.aleien.yapplication.database; + +/** + * Created by aleien on 10.08.16. + */ + +public class DBProvider { + +} diff --git a/app/src/main/java/ru/aleien/yapplication/model/Artist.java b/app/src/main/java/ru/aleien/yapplication/model/Artist.java index b9d39de..4f6750b 100644 --- a/app/src/main/java/ru/aleien/yapplication/model/Artist.java +++ b/app/src/main/java/ru/aleien/yapplication/model/Artist.java @@ -23,6 +23,37 @@ public Artist(int id, String name, List genres, int tracks, int albums, this.cover = cover; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Artist artist = (Artist) o; + + if (id != artist.id) return false; + if (tracks != artist.tracks) return false; + if (albums != artist.albums) return false; + if (!name.equals(artist.name)) return false; + if (!genres.equals(artist.genres)) return false; + if (!link.equals(artist.link)) return false; + if (!description.equals(artist.description)) return false; + return cover.equals(artist.cover); + + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + name.hashCode(); + result = 31 * result + genres.hashCode(); + result = 31 * result + tracks; + result = 31 * result + albums; + result = 31 * result + link.hashCode(); + result = 31 * result + description.hashCode(); + result = 31 * result + cover.hashCode(); + return result; + } + public static class Cover { public final String small; public final String big; @@ -31,6 +62,27 @@ public Cover(String small, String big) { this.small = small; this.big = big; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Cover cover = (Cover) o; + + if (!small.equals(cover.small)) return false; + return big.equals(cover.big); + + } + + @Override + public int hashCode() { + int result = small.hashCode(); + result = 31 * result + big.hashCode(); + return result; + } } + + } diff --git a/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java b/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java index 3985118..101e98a 100644 --- a/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java +++ b/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java @@ -15,6 +15,7 @@ import ru.aleien.yapplication.database.DBBackend; import ru.aleien.yapplication.database.DBHelper; import ru.aleien.yapplication.dataprovider.ArtistsProvider; +import ru.aleien.yapplication.dataprovider.WebArtistsProvider; import ru.aleien.yapplication.model.Artist; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoFragment; import ru.aleien.yapplication.screens.detailedinfo.ArtistInfoView; @@ -38,6 +39,8 @@ public class ArtistsPresenterTest { ArtistsPresenter presenter; @Mock Context context; @Mock ArtistsProvider provider; + @Mock + WebArtistsProvider webArtistsProvider; @Mock Artist artistMock; @Mock MainView mainMock; @Mock ArtistsListView listMock; @@ -51,7 +54,7 @@ public class ArtistsPresenterTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - presenter = new ArtistsPresenter(dbHelper, dbBackend); + presenter = new ArtistsPresenter(dbBackend, webArtistsProvider); presenter.artistsProvider = provider; presenter.attachView(mainMock); diff --git a/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java b/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java index 31cccbc..a3042ec 100644 --- a/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java +++ b/app/src/test/java/ru/aleien/yapplication/database/DBBackendTest.java @@ -45,6 +45,7 @@ public void insertArtist() throws Exception { List artists = dbBackend.loadArtists(); Assert.assertEquals(artists.size(), 1); +// Assert.assertTrue(artist.equals(artists.get(0))); } @Test From 049aefe7fe9181e3c46bb42c5775b6189f82ed2f Mon Sep 17 00:00:00 2001 From: aleien Date: Wed, 10 Aug 2016 23:49:08 +0300 Subject: [PATCH 13/15] Very VERY basic content provider implementation --- .idea/gradle.xml | 6 -- app/src/main/AndroidManifest.xml | 19 ++-- .../ru/aleien/yapplication/MainActivity.java | 17 ++++ .../ArtistsContentProvider.java | 98 +++++++++++++++++++ .../yapplication/database/DBHelper.java | 10 +- .../yapplication/ArtistsPresenterTest.java | 4 +- 6 files changed, 133 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 7990923..4c73708 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -6,12 +6,6 @@ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0a40382..8d1eef0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,14 +1,14 @@ + package="ru.aleien.yapplication"> - - - + + + - + - + + + diff --git a/app/src/main/java/ru/aleien/yapplication/MainActivity.java b/app/src/main/java/ru/aleien/yapplication/MainActivity.java index 8f79e66..0478b1a 100644 --- a/app/src/main/java/ru/aleien/yapplication/MainActivity.java +++ b/app/src/main/java/ru/aleien/yapplication/MainActivity.java @@ -6,6 +6,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.database.Cursor; import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; @@ -24,8 +25,11 @@ import butterknife.BindString; import butterknife.ButterKnife; +import ru.aleien.yapplication.database.DBContract; +import ru.aleien.yapplication.model.Artist; import ru.aleien.yapplication.screens.list.AboutDialogFragment; import ru.aleien.yapplication.utils.PendingIntentBuilder; +import timber.log.Timber; public class MainActivity extends AppCompatActivity implements MainView { private final static int MUSIC_ID = 1010; @@ -46,11 +50,24 @@ public class MainActivity extends AppCompatActivity implements MainView { .putExtra(Intent.EXTRA_SUBJECT, "Re: Yapplication"); + final Uri ARTISTS_URI = Uri.parse("content://ru.aleien.yapplication.provider/Artists"); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // АХАХА РАБОТА С КОНТЕНТОМ К МЭИН ТРЕДЕ ОЛОЛОЛО + String[] from = {"id", "name"}; + Cursor cursor = getContentResolver().query(ARTISTS_URI, from, null, null, null); + cursor.moveToFirst(); + while (cursor.moveToNext()) { + String artistName = cursor.getString(cursor.getColumnIndex(DBContract.Artists.NAME)); + Timber.d(artistName); + } + + cursor.close(); + ((App) getApplication()).dagger().inject(this); broadcastReceiver = new BroadcastReceiver() { @Override diff --git a/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java b/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java new file mode 100644 index 0000000..bedc6b8 --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java @@ -0,0 +1,98 @@ +package ru.aleien.yapplication.contentprovider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import ru.aleien.yapplication.database.DBContract; +import ru.aleien.yapplication.database.DBHelper; +import ru.aleien.yapplication.model.Artist; +import timber.log.Timber; + +import static android.provider.ContactsContract.AUTHORITY; +import static ru.aleien.yapplication.database.DBContract.allColumns; + +/** + * Created by aleien on 10.08.16. + */ + +public class ArtistsContentProvider extends ContentProvider { + static final String AUTHORITY = "ru.aleien.yapplication.provider"; + + static final String ARTISTS_PATH = "Artists"; + static final int URI_ARTISTS = 1; + static final int URI_ARTISTS_ID = 2; + + private static final UriMatcher uriMatcher; + + static { + uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + uriMatcher.addURI(AUTHORITY, ARTISTS_PATH, URI_ARTISTS); + uriMatcher.addURI(AUTHORITY, ARTISTS_PATH + "/#", URI_ARTISTS_ID); + } + + DBHelper dbHelper; + SQLiteDatabase db; + + @Override + public boolean onCreate() { + Timber.d("Content provider created"); + dbHelper = new DBHelper(getContext()); + return true; + } + + @Nullable + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + Timber.v("Making query to content provider"); + Timber.d("Uri: " + uri.toString()); + switch (uriMatcher.match(uri)) { + case URI_ARTISTS: + Timber.v("Resolving query to ARTISTS"); + break; + case URI_ARTISTS_ID: + Timber.v("Resolving query to specific ARTIST"); + String id = uri.getLastPathSegment(); + + if (TextUtils.isEmpty(selection)) { + selection = DBContract.Artists.ID + " = " + id; + } else { + selection = selection + " AND " + DBContract.Artists.ID + " = " + id; + } + break; + default: + throw new IllegalArgumentException("Wrong URI: " + uri); + } + + db = dbHelper.getReadableDatabase(); + return db.query(DBContract.Artists.TABLE, + allColumns, selection, null, null, null, null); + } + + @Nullable + @Override + public String getType(Uri uri) { + return null; + } + + @Nullable + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java b/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java index 52658e9..260ef7a 100644 --- a/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java +++ b/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java @@ -12,16 +12,14 @@ import static ru.aleien.yapplication.database.DBContract.DROP_TABLE_IF_EXISTS; -/** - * Created by user on 22.07.16. - */ - @Singleton public class DBHelper extends SQLiteOpenHelper { + private final static int DB_VERSION = 1; + private final static String DB_NAME = "dbName"; @Inject - public DBHelper(Context context, @Named("dbName") String name) { - super(context, name, null, 1); + public DBHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); } @Override diff --git a/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java b/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java index 101e98a..25210f3 100644 --- a/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java +++ b/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java @@ -40,7 +40,7 @@ public class ArtistsPresenterTest { @Mock Context context; @Mock ArtistsProvider provider; @Mock - WebArtistsProvider webArtistsProvider; + WebArtistsProvider webProvider; @Mock Artist artistMock; @Mock MainView mainMock; @Mock ArtistsListView listMock; @@ -54,7 +54,7 @@ public class ArtistsPresenterTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - presenter = new ArtistsPresenter(dbBackend, webArtistsProvider); + presenter = new ArtistsPresenter(dbBackend, webProvider); presenter.artistsProvider = provider; presenter.attachView(mainMock); From dae27829715ba2bc9481ae49cc47a551b8b7682c Mon Sep 17 00:00:00 2001 From: aleien Date: Fri, 12 Aug 2016 02:29:59 +0300 Subject: [PATCH 14/15] More content provider implementation + read permission --- app/src/main/AndroidManifest.xml | 1 + .../ru/aleien/yapplication/MainActivity.java | 15 --- .../ArtistsContentProvider.java | 107 +++++++++++++++--- .../contentprovider/ProviderContract.java | 16 +++ .../yapplication/database/DBContract.java | 6 - .../ru/aleien/yapplication/di/AppModule.java | 4 - 6 files changed, 109 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/ru/aleien/yapplication/contentprovider/ProviderContract.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8d1eef0..bdf3034 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ diff --git a/app/src/main/java/ru/aleien/yapplication/MainActivity.java b/app/src/main/java/ru/aleien/yapplication/MainActivity.java index 0478b1a..3cb4beb 100644 --- a/app/src/main/java/ru/aleien/yapplication/MainActivity.java +++ b/app/src/main/java/ru/aleien/yapplication/MainActivity.java @@ -49,25 +49,10 @@ public class MainActivity extends AppCompatActivity implements MainView { .putExtra(Intent.EXTRA_EMAIL, new String[]{"technogenom@gmail.com"}) .putExtra(Intent.EXTRA_SUBJECT, "Re: Yapplication"); - - final Uri ARTISTS_URI = Uri.parse("content://ru.aleien.yapplication.provider/Artists"); - - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // АХАХА РАБОТА С КОНТЕНТОМ К МЭИН ТРЕДЕ ОЛОЛОЛО - String[] from = {"id", "name"}; - Cursor cursor = getContentResolver().query(ARTISTS_URI, from, null, null, null); - cursor.moveToFirst(); - while (cursor.moveToNext()) { - String artistName = cursor.getString(cursor.getColumnIndex(DBContract.Artists.NAME)); - Timber.d(artistName); - } - - cursor.close(); - ((App) getApplication()).dagger().inject(this); broadcastReceiver = new BroadcastReceiver() { @Override diff --git a/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java b/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java index bedc6b8..05163b3 100644 --- a/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java +++ b/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java @@ -1,9 +1,12 @@ package ru.aleien.yapplication.contentprovider; import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; +import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.support.annotation.Nullable; @@ -11,22 +14,14 @@ import ru.aleien.yapplication.database.DBContract; import ru.aleien.yapplication.database.DBHelper; -import ru.aleien.yapplication.model.Artist; import timber.log.Timber; -import static android.provider.ContactsContract.AUTHORITY; -import static ru.aleien.yapplication.database.DBContract.allColumns; - -/** - * Created by aleien on 10.08.16. - */ +import static ru.aleien.yapplication.contentprovider.ProviderContract.ARTISTS_PATH; +import static ru.aleien.yapplication.contentprovider.ProviderContract.AUTHORITY; +import static ru.aleien.yapplication.contentprovider.ProviderContract.URI_ARTISTS; +import static ru.aleien.yapplication.contentprovider.ProviderContract.URI_ARTISTS_ID; public class ArtistsContentProvider extends ContentProvider { - static final String AUTHORITY = "ru.aleien.yapplication.provider"; - - static final String ARTISTS_PATH = "Artists"; - static final int URI_ARTISTS = 1; - static final int URI_ARTISTS_ID = 2; private static final UriMatcher uriMatcher; @@ -71,7 +66,7 @@ public Cursor query(Uri uri, String[] projection, String selection, String[] sel db = dbHelper.getReadableDatabase(); return db.query(DBContract.Artists.TABLE, - allColumns, selection, null, null, null, null); + projection, selection, selectionArgs, null, null, sortOrder); } @Nullable @@ -83,16 +78,98 @@ public String getType(Uri uri) { @Nullable @Override public Uri insert(Uri uri, ContentValues values) { + if (uriMatcher.match(uri) != ProviderContract.URI_ARTISTS) { + throw new IllegalArgumentException( + "Unsupported URI for insertion: " + uri); + } + SQLiteDatabase db = dbHelper.getWritableDatabase(); + if (uriMatcher.match(uri) == URI_ARTISTS) { + long id = db.insert( + DBContract.Artists.TABLE, + null, + values); + return getUriForId(id, uri); + } + return null; } + private Uri getUriForId(long id, Uri uri) { + if (id > 0) { + Uri itemUri = ContentUris.withAppendedId(uri, id); + ContentResolver resolver = getContext() == null ? null : getContext().getContentResolver(); + if (resolver != null) { + getContext().getContentResolver() + .notifyChange(itemUri, null); + } + + return itemUri; + } + throw new SQLException( + "Problem while inserting into uri: " + uri); + } + @Override public int delete(Uri uri, String selection, String[] selectionArgs) { - return 0; + SQLiteDatabase db = dbHelper.getWritableDatabase(); + int delCount = 0; + switch (uriMatcher.match(uri)) { + case URI_ARTISTS: + delCount = db.delete( + DBContract.Artists.TABLE, + selection, + selectionArgs); + break; + case URI_ARTISTS_ID: + String idStr = uri.getLastPathSegment(); + String where = DBContract.Artists.ID + " = " + idStr; + if (!TextUtils.isEmpty(selection)) { + where += " AND " + selection; + } + delCount = db.delete( + DBContract.Artists.TABLE, + where, + selectionArgs); + break; + default: + throw new IllegalArgumentException("Unsupported URI: " + uri); + } + if (delCount > 0) { + getContext().getContentResolver().notifyChange(uri, null); + } + return delCount; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - return 0; + SQLiteDatabase db = dbHelper.getWritableDatabase(); + int updateCount = 0; + switch (uriMatcher.match(uri)) { + case URI_ARTISTS: + updateCount = db.update( + DBContract.Artists.TABLE, + values, + selection, + selectionArgs); + break; + case URI_ARTISTS_ID: + String idStr = uri.getLastPathSegment(); + String where = DBContract.Artists.ID + " = " + idStr; + if (!TextUtils.isEmpty(selection)) { + where += " AND " + selection; + } + updateCount = db.update( + DBContract.Artists.TABLE, + values, + where, + selectionArgs); + break; + default: + throw new IllegalArgumentException("Unsupported URI: " + uri); + } + if (updateCount > 0) { + getContext().getContentResolver().notifyChange(uri, null); + } + return updateCount; } } diff --git a/app/src/main/java/ru/aleien/yapplication/contentprovider/ProviderContract.java b/app/src/main/java/ru/aleien/yapplication/contentprovider/ProviderContract.java new file mode 100644 index 0000000..490a65d --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/contentprovider/ProviderContract.java @@ -0,0 +1,16 @@ +package ru.aleien.yapplication.contentprovider; + +/** + * Created by aleien on 12.08.16. + * Контракт поставщика контента + */ + +public final class ProviderContract { + + public static final String AUTHORITY = "ru.aleien.yapplication.provider"; + + public static final String ARTISTS_PATH = "Artists"; + public static final int URI_ARTISTS = 1; + public static final int URI_ARTISTS_ID = 2; + +} diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBContract.java b/app/src/main/java/ru/aleien/yapplication/database/DBContract.java index 58f0a91..1500aed 100644 --- a/app/src/main/java/ru/aleien/yapplication/database/DBContract.java +++ b/app/src/main/java/ru/aleien/yapplication/database/DBContract.java @@ -1,9 +1,5 @@ package ru.aleien.yapplication.database; -/** - * Created by aleien on 08.08.16. - */ - public interface DBContract { interface Artists { String TABLE = "artists"; @@ -20,7 +16,6 @@ interface Artists { interface Genres { String TABLE = "genres"; String NAME = "genre"; - } // А может быть тут можно не genre_id, а genre_name? @@ -28,7 +23,6 @@ interface GenreToArtist { String TABLE = "genre_to_artist"; String ARTIST_ID = "artist_id"; String GENRE_ID = "genre_id"; - } String DROP_TABLE_IF_EXISTS = "DROP TABLE IF EXISTS "; diff --git a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java index 97cc5cc..1b26d41 100644 --- a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java +++ b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java @@ -22,9 +22,6 @@ import static android.content.Context.MODE_PRIVATE; -/** - * Created by user on 23.07.16. - */ @Module public class AppModule { private static final String BASE_URL = "http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/"; @@ -82,5 +79,4 @@ public interface Api { @GET("artists.json") Observable> getArtists(); } - } From 20730d373009f9a589b069b0c09120e5254ceef5 Mon Sep 17 00:00:00 2001 From: aleien Date: Fri, 12 Aug 2016 02:34:39 +0300 Subject: [PATCH 15/15] Minor improvements --- app/src/main/AndroidManifest.xml | 1 + .../ru/aleien/yapplication/MainActivity.java | 15 --- .../ArtistsContentProvider.java | 107 +++++++++++++++--- .../contentprovider/ProviderContract.java | 16 +++ .../yapplication/database/DBContract.java | 6 +- .../yapplication/database/DBHelper.java | 14 +-- .../ru/aleien/yapplication/di/AppModule.java | 14 +-- .../ru/aleien/yapplication/model/Artist.java | 3 - .../yapplication/ArtistsPresenterTest.java | 9 +- 9 files changed, 123 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/ru/aleien/yapplication/contentprovider/ProviderContract.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8d1eef0..bdf3034 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ diff --git a/app/src/main/java/ru/aleien/yapplication/MainActivity.java b/app/src/main/java/ru/aleien/yapplication/MainActivity.java index 0478b1a..3cb4beb 100644 --- a/app/src/main/java/ru/aleien/yapplication/MainActivity.java +++ b/app/src/main/java/ru/aleien/yapplication/MainActivity.java @@ -49,25 +49,10 @@ public class MainActivity extends AppCompatActivity implements MainView { .putExtra(Intent.EXTRA_EMAIL, new String[]{"technogenom@gmail.com"}) .putExtra(Intent.EXTRA_SUBJECT, "Re: Yapplication"); - - final Uri ARTISTS_URI = Uri.parse("content://ru.aleien.yapplication.provider/Artists"); - - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // АХАХА РАБОТА С КОНТЕНТОМ К МЭИН ТРЕДЕ ОЛОЛОЛО - String[] from = {"id", "name"}; - Cursor cursor = getContentResolver().query(ARTISTS_URI, from, null, null, null); - cursor.moveToFirst(); - while (cursor.moveToNext()) { - String artistName = cursor.getString(cursor.getColumnIndex(DBContract.Artists.NAME)); - Timber.d(artistName); - } - - cursor.close(); - ((App) getApplication()).dagger().inject(this); broadcastReceiver = new BroadcastReceiver() { @Override diff --git a/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java b/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java index bedc6b8..05163b3 100644 --- a/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java +++ b/app/src/main/java/ru/aleien/yapplication/contentprovider/ArtistsContentProvider.java @@ -1,9 +1,12 @@ package ru.aleien.yapplication.contentprovider; import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; +import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.support.annotation.Nullable; @@ -11,22 +14,14 @@ import ru.aleien.yapplication.database.DBContract; import ru.aleien.yapplication.database.DBHelper; -import ru.aleien.yapplication.model.Artist; import timber.log.Timber; -import static android.provider.ContactsContract.AUTHORITY; -import static ru.aleien.yapplication.database.DBContract.allColumns; - -/** - * Created by aleien on 10.08.16. - */ +import static ru.aleien.yapplication.contentprovider.ProviderContract.ARTISTS_PATH; +import static ru.aleien.yapplication.contentprovider.ProviderContract.AUTHORITY; +import static ru.aleien.yapplication.contentprovider.ProviderContract.URI_ARTISTS; +import static ru.aleien.yapplication.contentprovider.ProviderContract.URI_ARTISTS_ID; public class ArtistsContentProvider extends ContentProvider { - static final String AUTHORITY = "ru.aleien.yapplication.provider"; - - static final String ARTISTS_PATH = "Artists"; - static final int URI_ARTISTS = 1; - static final int URI_ARTISTS_ID = 2; private static final UriMatcher uriMatcher; @@ -71,7 +66,7 @@ public Cursor query(Uri uri, String[] projection, String selection, String[] sel db = dbHelper.getReadableDatabase(); return db.query(DBContract.Artists.TABLE, - allColumns, selection, null, null, null, null); + projection, selection, selectionArgs, null, null, sortOrder); } @Nullable @@ -83,16 +78,98 @@ public String getType(Uri uri) { @Nullable @Override public Uri insert(Uri uri, ContentValues values) { + if (uriMatcher.match(uri) != ProviderContract.URI_ARTISTS) { + throw new IllegalArgumentException( + "Unsupported URI for insertion: " + uri); + } + SQLiteDatabase db = dbHelper.getWritableDatabase(); + if (uriMatcher.match(uri) == URI_ARTISTS) { + long id = db.insert( + DBContract.Artists.TABLE, + null, + values); + return getUriForId(id, uri); + } + return null; } + private Uri getUriForId(long id, Uri uri) { + if (id > 0) { + Uri itemUri = ContentUris.withAppendedId(uri, id); + ContentResolver resolver = getContext() == null ? null : getContext().getContentResolver(); + if (resolver != null) { + getContext().getContentResolver() + .notifyChange(itemUri, null); + } + + return itemUri; + } + throw new SQLException( + "Problem while inserting into uri: " + uri); + } + @Override public int delete(Uri uri, String selection, String[] selectionArgs) { - return 0; + SQLiteDatabase db = dbHelper.getWritableDatabase(); + int delCount = 0; + switch (uriMatcher.match(uri)) { + case URI_ARTISTS: + delCount = db.delete( + DBContract.Artists.TABLE, + selection, + selectionArgs); + break; + case URI_ARTISTS_ID: + String idStr = uri.getLastPathSegment(); + String where = DBContract.Artists.ID + " = " + idStr; + if (!TextUtils.isEmpty(selection)) { + where += " AND " + selection; + } + delCount = db.delete( + DBContract.Artists.TABLE, + where, + selectionArgs); + break; + default: + throw new IllegalArgumentException("Unsupported URI: " + uri); + } + if (delCount > 0) { + getContext().getContentResolver().notifyChange(uri, null); + } + return delCount; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - return 0; + SQLiteDatabase db = dbHelper.getWritableDatabase(); + int updateCount = 0; + switch (uriMatcher.match(uri)) { + case URI_ARTISTS: + updateCount = db.update( + DBContract.Artists.TABLE, + values, + selection, + selectionArgs); + break; + case URI_ARTISTS_ID: + String idStr = uri.getLastPathSegment(); + String where = DBContract.Artists.ID + " = " + idStr; + if (!TextUtils.isEmpty(selection)) { + where += " AND " + selection; + } + updateCount = db.update( + DBContract.Artists.TABLE, + values, + where, + selectionArgs); + break; + default: + throw new IllegalArgumentException("Unsupported URI: " + uri); + } + if (updateCount > 0) { + getContext().getContentResolver().notifyChange(uri, null); + } + return updateCount; } } diff --git a/app/src/main/java/ru/aleien/yapplication/contentprovider/ProviderContract.java b/app/src/main/java/ru/aleien/yapplication/contentprovider/ProviderContract.java new file mode 100644 index 0000000..490a65d --- /dev/null +++ b/app/src/main/java/ru/aleien/yapplication/contentprovider/ProviderContract.java @@ -0,0 +1,16 @@ +package ru.aleien.yapplication.contentprovider; + +/** + * Created by aleien on 12.08.16. + * Контракт поставщика контента + */ + +public final class ProviderContract { + + public static final String AUTHORITY = "ru.aleien.yapplication.provider"; + + public static final String ARTISTS_PATH = "Artists"; + public static final int URI_ARTISTS = 1; + public static final int URI_ARTISTS_ID = 2; + +} diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBContract.java b/app/src/main/java/ru/aleien/yapplication/database/DBContract.java index 58f0a91..01fdc3c 100644 --- a/app/src/main/java/ru/aleien/yapplication/database/DBContract.java +++ b/app/src/main/java/ru/aleien/yapplication/database/DBContract.java @@ -2,9 +2,13 @@ /** * Created by aleien on 08.08.16. + * Класс для хранения и структурирования полей в базе данных */ public interface DBContract { + String DBNAME = "ArtistsDB"; + int DB_VERSION = 1; + interface Artists { String TABLE = "artists"; String ID = "id"; @@ -20,7 +24,6 @@ interface Artists { interface Genres { String TABLE = "genres"; String NAME = "genre"; - } // А может быть тут можно не genre_id, а genre_name? @@ -28,7 +31,6 @@ interface GenreToArtist { String TABLE = "genre_to_artist"; String ARTIST_ID = "artist_id"; String GENRE_ID = "genre_id"; - } String DROP_TABLE_IF_EXISTS = "DROP TABLE IF EXISTS "; diff --git a/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java b/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java index 260ef7a..cef9051 100644 --- a/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java +++ b/app/src/main/java/ru/aleien/yapplication/database/DBHelper.java @@ -10,16 +10,15 @@ import ru.aleien.yapplication.model.Artist; +import static ru.aleien.yapplication.database.DBContract.DB_VERSION; import static ru.aleien.yapplication.database.DBContract.DROP_TABLE_IF_EXISTS; @Singleton public class DBHelper extends SQLiteOpenHelper { - private final static int DB_VERSION = 1; - private final static String DB_NAME = "dbName"; @Inject public DBHelper(Context context) { - super(context, DB_NAME, null, DB_VERSION); + super(context, DBContract.DBNAME, null, DB_VERSION); } @Override @@ -28,7 +27,7 @@ public void onCreate(SQLiteDatabase db) { "CREATE TABLE " + DBContract.Artists.TABLE + "(" + DBContract.Artists.ID + " INTEGER PRIMARY KEY, " + - DBContract.Artists.NAME + " TEXT," + + DBContract.Artists.NAME + " TEXT NOT NULL," + DBContract.Artists.TRACKS + " INTEGER," + DBContract.Artists.ALBUMS + " INTEGER," + DBContract.Artists.LINK + " TEXT," + @@ -42,14 +41,14 @@ public void onCreate(SQLiteDatabase db) { "CREATE TABLE " + DBContract.Genres.TABLE + " (" + - DBContract.Genres.NAME + " STRING UNIQUE" + + DBContract.Genres.NAME + " TEXT UNIQUE NOT NULL" + ")"); db.execSQL( "CREATE TABLE " + DBContract.GenreToArtist.TABLE + " (" + - DBContract.GenreToArtist.ARTIST_ID + " INTEGER," + - DBContract.GenreToArtist.GENRE_ID + " INTEGER )"); + DBContract.GenreToArtist.ARTIST_ID + " INTEGER NOT NULL," + + DBContract.GenreToArtist.GENRE_ID + " INTEGER NOT NULL)"); } @Override @@ -59,5 +58,4 @@ public void onUpgrade(SQLiteDatabase db, int i, int i1) { db.execSQL(DROP_TABLE_IF_EXISTS + DBContract.GenreToArtist.TABLE); onCreate(db); } - } diff --git a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java index 97cc5cc..d0a7119 100644 --- a/app/src/main/java/ru/aleien/yapplication/di/AppModule.java +++ b/app/src/main/java/ru/aleien/yapplication/di/AppModule.java @@ -17,20 +17,17 @@ import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.http.GET; +import ru.aleien.yapplication.database.DBContract; import ru.aleien.yapplication.model.Artist; import rx.Observable; import static android.content.Context.MODE_PRIVATE; -/** - * Created by user on 23.07.16. - */ @Module public class AppModule { private static final String BASE_URL = "http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/"; private Context context; - private String dbName = "ArtistsDB"; public AppModule(Application application) { this.context = application; @@ -45,13 +42,7 @@ Context provideContext() { @Provides @Singleton SQLiteDatabase provideDatabase(Context context) { - return context.openOrCreateDatabase(dbName, MODE_PRIVATE, null); - } - - @Provides - @Named("dbName") - String provideDBName() { - return dbName; + return context.openOrCreateDatabase(DBContract.DBNAME, MODE_PRIVATE, null); } @Provides @@ -82,5 +73,4 @@ public interface Api { @GET("artists.json") Observable> getArtists(); } - } diff --git a/app/src/main/java/ru/aleien/yapplication/model/Artist.java b/app/src/main/java/ru/aleien/yapplication/model/Artist.java index 4f6750b..66a5a24 100644 --- a/app/src/main/java/ru/aleien/yapplication/model/Artist.java +++ b/app/src/main/java/ru/aleien/yapplication/model/Artist.java @@ -38,7 +38,6 @@ public boolean equals(Object o) { if (!link.equals(artist.link)) return false; if (!description.equals(artist.description)) return false; return cover.equals(artist.cover); - } @Override @@ -83,6 +82,4 @@ public int hashCode() { } } - - } diff --git a/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java b/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java index 25210f3..0e707f4 100644 --- a/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java +++ b/app/src/test/java/ru/aleien/yapplication/ArtistsPresenterTest.java @@ -40,7 +40,7 @@ public class ArtistsPresenterTest { @Mock Context context; @Mock ArtistsProvider provider; @Mock - WebArtistsProvider webProvider; + WebArtistsProvider webArtistsProvider; @Mock Artist artistMock; @Mock MainView mainMock; @Mock ArtistsListView listMock; @@ -50,11 +50,10 @@ public class ArtistsPresenterTest { @Mock DBHelper dbHelper; - @Before public void setup() { MockitoAnnotations.initMocks(this); - presenter = new ArtistsPresenter(dbBackend, webProvider); + presenter = new ArtistsPresenter(dbBackend, webArtistsProvider); presenter.artistsProvider = provider; presenter.attachView(mainMock); @@ -67,7 +66,6 @@ public void setup() { })).when(provider).requestData(); } - @Test public void requestData() { presenter.takeListView(listMock); @@ -86,7 +84,4 @@ public void artistClicked() { verify(mainMock, times(1)).changeFragmentTo(any(ArtistInfoFragment.class), anyBoolean()); } - - - }