From efb935cffbea6f0ebb363dc65b271c1a281f9f1b Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Sat, 20 Jan 2024 12:51:29 +0530 Subject: [PATCH 01/25] Updated: Gradle version and CompileSDK version --- app/build.gradle.kts | 8 ++------ build.gradle.kts | 4 ++-- buildSrc/src/main/kotlin/Dependencies.kt | 4 ++-- gradle.properties | 4 +++- gradle/wrapper/gradle-wrapper.properties | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1579bb0..b25d8d7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,8 +1,4 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties -import com.google.protobuf.gradle.builtins -import com.google.protobuf.gradle.generateProtoTasks -import com.google.protobuf.gradle.protobuf -import com.google.protobuf.gradle.protoc import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -12,7 +8,7 @@ plugins { id("dagger.hilt.android.plugin") id("androidx.navigation.safeargs.kotlin") id("kotlin-parcelize") - id("com.google.protobuf") version "0.8.19" + id("com.google.protobuf") version "0.9.1" id("com.google.gms.google-services") id("com.google.firebase.crashlytics") id("dev.shreyaspatil.compose-compiler-report-generator") version "1.1.0" @@ -108,7 +104,7 @@ android { kotlinCompilerExtensionVersion = Versions.androidxComposeCompiler } - packagingOptions { + packaging { resources { excludes.add("/META-INF/{AL2.0,LGPL2.1}") } diff --git a/build.gradle.kts b/build.gradle.kts index e63a20c..8de62f9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ buildscript { } plugins { - id("com.android.application") version "7.4.2" apply false - id("com.android.library") version "7.4.2" apply false + id("com.android.application") version "8.2.1" apply false + id("com.android.library") version "8.2.1" apply false id("org.jetbrains.kotlin.android") version Versions.kotlin apply false } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 33df2de..a406745 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -132,9 +132,9 @@ object Libraries { } object Api { - const val compileSdk = 33 + const val compileSdk = 34 const val minSdk = 23 - const val targetSdk = 33 + const val targetSdk = 34 } object AnnotationProcessors { diff --git a/gradle.properties b/gradle.properties index 3c5031e..a2e90d8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,6 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.defaults.buildfeatures.buildconfig=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index abb39a3..bd2980b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Mar 06 03:00:59 IST 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From 3b9f5075c81961a9e7eade73e63240c66f9b997a Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Sat, 20 Jan 2024 15:19:14 +0530 Subject: [PATCH 02/25] Added: FOREGROUND_SERVICE_MEDIA_PLAYBACK permission --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 13e0177..e7c3a6d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + Date: Wed, 24 Jan 2024 21:00:09 +0530 Subject: [PATCH 03/25] Removed: .idea from vcs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 98f2766..f85f6d4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ /.idea/deploymentTargetDropDown.xml /.idea/kotlinc.xml /.idea/misc.xml +/.idea /buildSrc/build .DS_Store /build From 9eb121b3876a1cce081bd11887b2e9978c95f128 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Wed, 24 Jan 2024 21:10:24 +0530 Subject: [PATCH 04/25] Updated: build.gradle.kts --- app/build.gradle.kts | 2 +- gradle.properties | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b25d8d7..5aa5c50 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -97,7 +97,7 @@ android { buildFeatures { compose = true viewBinding = true - dataBinding = true + buildConfig = true } composeOptions { diff --git a/gradle.properties b/gradle.properties index a2e90d8..f19c7b9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,5 +21,4 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -android.defaults.buildfeatures.buildconfig=true android.nonFinalResIds=false \ No newline at end of file From 6eb3e8297f40d58f5d86baeb9ca8c6e2359a6fe0 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Wed, 24 Jan 2024 22:28:52 +0530 Subject: [PATCH 05/25] Updated: Icon --- .idea/gradle.xml | 3 +-- app/src/main/ic_launcher-playstore.png | Bin 0 -> 10974 bytes .../res/drawable/ic_launcher_foreground.xml | 15 +++++++++++++++ app/src/main/res/layout/fragment_splash.xml | 7 +------ .../music_control_widget_preview_layout.xml | 3 +-- .../main/res/mipmap-anydpi-v26/ic_launcher.xml | 6 +++--- .../res/mipmap-anydpi-v26/ic_launcher_round.xml | 6 +++--- app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2089 -> 0 bytes app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 928 bytes .../res/mipmap-hdpi/ic_launcher_background.png | Bin 1009 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_foreground.png | Bin 2818 -> 0 bytes .../main/res/mipmap-hdpi/ic_launcher_round.png | Bin 4072 -> 0 bytes .../main/res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2504 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1465 -> 0 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 686 bytes .../res/mipmap-mdpi/ic_launcher_background.png | Bin 679 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_foreground.png | Bin 1566 -> 0 bytes .../main/res/mipmap-mdpi/ic_launcher_round.png | Bin 2578 -> 0 bytes .../main/res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1678 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 2939 -> 0 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1292 bytes .../res/mipmap-xhdpi/ic_launcher_background.png | Bin 1352 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_foreground.png | Bin 4175 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher_round.png | Bin 5988 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3536 bytes app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 4960 -> 0 bytes app/src/main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 1910 bytes .../mipmap-xxhdpi/ic_launcher_background.png | Bin 2058 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 7292 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 9855 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5652 bytes app/src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 7235 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 2554 bytes .../mipmap-xxxhdpi/ic_launcher_background.png | Bin 3108 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 10869 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 14560 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7918 bytes .../main/res/values/ic_launcher_background.xml | 4 ++++ 38 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_background.png delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_background.png delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_background.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/values/ic_launcher_background.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 3a504e6..add82e5 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,8 +4,6 @@ diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..f0be135c5d0838c6b429a651caa5d4554cfbfb04 GIT binary patch literal 10974 zcmeHt`9IX%`~Pdk7THQkWGiddD6$Pnq^v3XmO@#w@7uJ8tP_$L*=3De_N6GhaoZ-! z5@Q?t&R~4bOZR>M2j3sQkH`BL^*CqFIoEZaYk8jM^$OL~(O{rsr-LAfLG!xWEeJw_ z|01Cy)Zm|W-#!uqozK)%yK>vta$)$T&q;jk=E`n!&555CKSMEW_#a(fH*K0-Y^LQO zxgDP#(i}5vL2X6O+v zwGY9Tp+x3}@RAxH@xeGb>C&x`J#3M{5eO=751BatGHi2Gfxk!f!JlI0|NZy>Z#yt? zu(Jp0Xb?J}%JO8#=qHb?duW5ZQ_4x}2|X>j zoP?!;MpGe(E0ucx>OGc8!7scAPOinuN$+QU?fp~smd1;0i6pUGRR_iMiXMiHeU@*o zo+7?K7!D{j^rku(%wowV%mvZdexF*1Ee?#>D|7u+lNc8;hx3z>YZRva$fJ1KF721> zc}wc3*It}7P=FqU7Sg9MW*}sb3%33oEam&`6nCb^RlkY?LF<2LmU0{ z&!hVr2uT(y=t%m@X8u;An{DrlhW457P=74C*wT_|`vo6A<6jGPTtPrz3E%pJOMd&7 zQTp$$%Q<*H8mXDB$tp~-K3(yP=d`1&yzB{L5jVPAA3`%5_-xfDB}r%(ICl+i1?yN9 z)91KQnO0X3Jg=tn73RAxp6(72fNEYMp=Z&ifzuO%YstSu46yO2sPh!g!^1tU>30~d zD)?3=^O6N=*RjVS7jU(_QV$4}JCZIb=5@i1Rlkd}%ZqdieLUqInetJAS|cqTZu>Qm z+9{w*OqAWJ8_pi38;u7)^_9cBQn)I+1GP6(3gV7k<1fT=pl9IiQsam3U_JX8+zDO! zTUT>jG&Ym#d0O)Z+Je%5c-OegO4Z9NfEUxNY|V`(V<+ ztlFsnH8CHUv&q{X(gmqTAtCw(GXwj!p>)A@KXp=~G=p~6xF#`$uIvC!UZZA~hqq7{ z7skX#G6qG)Anfpo93( zmQ1y{?Kq9t_??yc<|PjOQlNvnb&2X_aW5l@rD4y6iJ!iJ$hqG}>RfFjIN^67R~LAf z+9ZsGS)T5kN7ysDPUnS^d?I>{^2Jcd6=ex%k?~N-Ijm`Cq#DX$_<7j5=1C9~C6B+ET%X-$5+z!`tuME}oS$G%CXJAOC$jIWJBk z{O06rQ){6IM8$Q)vTE1Gi#LXd8(o;wBtN99J-j)p6)UmpC_yZJr6!zE zSay98#MG^!+xSZ4m^uO?&F&|9Bs+FDDO3&(2zZn)#h<#hBLHpl!b^jt#c+caFMLHD z!hg$sIo>b{jqSpx`HP*?Xk$7vFwL~0(zp5bBjujcSy^i2=yN2rjv>AC)3=R71-#O5 zpCmD~juc-@d41C>Spfn&js~B>3P_jFcTY2}=!si8#%xdKO@6r`%1Su{YsqkwF|O57 zW=8s@uc$ryyZ4tkxY&}%5;SH8kLy7lnj4eU zkLW1tfB^I==EYEsvqR`s`X-|u{}`_f6Y>n~L^kp9?Gat#K?BTDSt88Q49&W2(Qhx~ zD=D3>A|UE9AGw{90@7=g{i~NAwdn=|Zwfqh%nsIH~&aq)wn~o7Y3}+ z9h5$9JCASQNK~c2IZM7e&weBi9K+0}Z=Qx@u`wR1s+!z*eMX$Bf&B!;LrJ#(TWWTm zc1HR+TfCCJkI7S{6@;bKEddrGIV8VNHmY@_XQvHm67t4C;WY z$^xxqT27ORMdVv7h|pE1TQB~dB82aJJ3h0}YfpPYl4yy>O^L_ey;pGxB3}dR2)$39 z_xPr3WBHr9os{pK!|~f!bxDt%0*a`ObhO2JHR}*Pp0S-N!!=5>(mc_HQ?s9EU~FlY z;)?G1NUnrwgA(&;_GJnnqH0KD^>r+7(^!skN{B*`Of}Wl1mKcK_Win4Y1iG(rDCPL z_U-(_ybxno!T!%s9Z7-_1qCE}Ug~Uc^T7!iiKpAF5DNH`>deBt$t|x!L$KX)PG^cS z+mYbI;Ym${%=@EPf`XYF4crJ7dp7Q$=K?7N(m^s_za984!^o+7n&yI{!(E=TE(%$S zuez`RN|N$l2HD?kORyvq#R%FAo@XP2YNR?pG5DM`&x2}tJQTi~G_Q!?q1J+J@)|jY z%V}2Vzn4P#XJkIz8li`O9eQxQ^e-*0`wy+RDZzDp8pXXmhaXZ{AG{LCT&BGoAWdgJ zeMS<#=ucL5ozb=VRAcQq?NEqSu^RR8?;47%4?xUK)f@4ks%PUE$1z$$7OiP2%T&O4 zHoD(4Z>)YSBmLq*wT`!)_Y-#hIQP$zEEFd>FGmf_(If~mj5zibYh{;$H9*tMHVzk3 zC8kczc|6?~a&Uq6|5+e&Q9uVJW}J$nA5YIHv=ofezV4kI(7*?*-qgoAzJz-(B1$zj z>Xe5%SD%df>9vT!0Ed?Jl@+wALL!15F=I38CaXLWHI$don!ti=HPTAD?@Z`h%4Zj< zh0_zn?3Ez42vB;zm<^U@ESiR+WM4%cH~W0!MN?B{my$Id5RJOXp3p)bI!fx`LAA{n zRNwctshaymkp=4J-DM~DtP^)i*i*3{dp)*0EY%cWt$}RxT-az)mNvs#X`l{K0O!Up zVm{3!Nd*i_;pMyy(mfH(h=Q7K@y{@3RI9yrnrg*Fpukds>9wXS(2^4Dq$LEAb-!#5 zuU?cze$|5qM!Hh?gG6a&=JMqOwSp5bH3FqsV5R+}MU2?Z%r+9Q1OhxaN9aYSP@)ro z(r;@Nh0Qio^7`$mSM=YR{=DDUSC=dJc1kL4V^1~Ne}Ecp!$K``oW$9vu=3qoP@guR zo914Y2ZmFx?`Z3Xby_W7h|M1F1t$12?sESYw3l*{ChBsvGV$NR6VO5S>bx8bYQRaB z8bG)i<=$*LTo}tiR@Ns?a^-`>en7{MAL`;%i4PFrArg^+HUkiJ> zN#o%o#{^p2y#EfpYGX55FrRpMiyM(Tp%PXL@>^&;$ODq>7o`j>aUFld_5nH2n2}8# zq}jWh;jM%280W`9Ns)_OnwFIVUL%Mi5TJKALCJi`#7H}i1Nbqv>fI#zE*H?(4wrqu zbtlX2(CrkAp&r8`pJbngIrs_~bhQ!y7J~hoPpP(6>R~4}-jAjx64-e#YNGeP8>o`9 zuXN!nXgvY3y@JPK?4HLDVh>MAdP0@@1PQId_iPY_tW$jW5&rpCe&{Vo>}m}jt%-;6 zR(}+dMSN8RSAtB*gtlcAsUQQO;SpZ}VSs0hk2XZz<@{%f0WHS&M0fVXrNwwYL*%o^ zKuStQd6bEr(?}tXaWt#C+=$U42lL6QC|b7EeX@Ujb`?9@``$LjD&-C5-srd#V$I&DM^gY1Z+qB?o*U2b2Y_ z7G-z6ncpzW*d$(>om*S@gsx$FC7G zOtFD^rnzcZd}I>4O@nz=6Lmp>2ahYHDt>@ZKNYUa{NNe$j0YZY2+egM4b9qVs;< zzu-fbp+TTRKz!-KVk=1`Wyo?zZP~Z(t<}7|-JvTyQ#^^CO42}VVe5GgN*^RDqId^4 z2~&Xk8F z*LrbJYzakctnDy7Z;ZI!XV`GnX574)>QF%D4ZeouCeNT?Hq1nVc>7Dg=Ur>NB*$=> zOX&VyBGxQxp~IaYp!;dgWn(X>J1tg((a0QO>$g4UN$$r9UE6N z;i=tZcrxQpdUvF5*lJNI+N~=-!D15_tc|Ko@EE0VX9M$ zHtn|a>68nK^p)5C@-;1fr{5}^Huyw>z`3v+8RIjd#iCq))o(rtj8z`p*h0ct@K}Zn z)w_C+C~!jZVItd_E&6tXNPVS@<9FLtjm%QP)cP94wf#hg@~itxlyI&+dtIp-9Fez>9G6IHrV7`zvJ@v^adUlvMZj+iOp>Z401S|D$|-g^uJwi<1!fuy)%?ocG7FgG{Xyksw-`;O>zKEpB~wOscg=2!f)Nyqn6*$1cpD3F`f-Iu&! ziTgg$|K;(6Y19_BBV|eSN5-S$H3rA@PSJgj%XjU{B#-{1fOl_zIKKf_I3>KQaX-{J zV5`z-TGLs&X3M;wG_bVl;&P`FGFeGU6hS`$(xs5EQ(&yjquNtm`&Pqz+1%9;Bkf|* zcV0%dew%W1FQ{#-d^u5SE!TZ;0KsrWNIxpZPp&>Oep=q{Cj;k6!TR@7jV4+%c!T*W zWh5EEy|gC3c`a-e7abL}EDI_H|D_1+t@U4i8DMS?zz?<7cjuZ(Wk?IYo}Llj`gc-| zULDZhQ+Oib8^2FrQ^!%c-fp2HTP+_>Y3KmliT>(+^DxiR$&Vf}U3F_^UB@d)7bupQ z;Jt~*s;e!$WqJl(YfLS#Cy&e9K`=^8YssUs{`Mt()!aR6&KI#w0}P+WVPQd<-%tzN znoszxdAe|dYK$ACjj%qcQcH8@99KLS?22ABe`L`IIHYIzXC7`e$9^&L#?BykbN;vD zg_Og+(?$FtKgD;1ftHtmB-fU-+A*IO9|e)sm8gCSL+DZ1w*HDT*B*KLFR3^9nIoY< z@aR|jdo1RYdu^!!wq76ib@=OD z-!eXOWLX*IgefvnD5AibL?7uuG<(#NWKvmp?~gUw0Jz~6g#f&dbm${K zbJ0$ol{frjQm5_~AC1rU4LISd4ST^tkcD?Hq2w!&~NB_TChISn<0+ z5P?cj8ue4Xq6|(3xkPd7%O{p(K}5})XRDW1)ttiMgDJpiufjX=>!;83ix%d`+G7Tp zk1UJOL1V{GDH=kX@0BEq?26Awjv0Y7&%rO}Bra2ioX?es{Sbm102{7PfYJhxhIA&< z6yQYPiDUW8jFL8haDkS_d|OdEuACy!8X#z+e0|N+-a9+> z*t_0dD@r?l_#8Awyale|+mxDWkihZnsxI<||2K8N^Qca*e5eC}Lq&q`zHQ~~hi{PK z8O5`3vH(&|!AiIj#`$O0^bnVWGGm+{Be0;;WW*?U-Q(DO#dKijZ4J3XVbCL7R|H`PQdo}Y7C(P zE+yW>cou|8VH?QZ^3jAh{t)i9%IJuI>cFXrcGHQa$7j~MOd+Gf(P$>SPpr_GEWANH zx8hmOVWmTT`~xO+Jy@$ESE2_G4$i&*CI?cRpNyJ6gNyBaob!3+X`2-V=yD`8>ixlW7FI7kqsFMrgJLIz)` z|MBl$4-R&Y!Vv{ir}K1|g^YF9?@?I;qH*ksr3iHX!p(L-D6D`O{AWxQ%FV;KvV0Q$ zZ)jK17^3^UU}I+8paIUJaRy{!F2m#7GFU}17kivLP)Hgm#AJHggdd<%_%Lyc>aN%Z zbd(+#eph4n6i_(*_O5->X%$VdT;K_zI1>_H7eAEON!p9>2fK#AKfTqyXlPZJ2J+r? z-uSHnkhK67_w5ePH6V09mxnVFv8QQ*#rFg=OdpJ0X~gs>m7giT0Vo!Ll0ES&NJ%E( zYN_B2q*%$;{Tp1dzU2pygx|t95lwnmOEfP)g`}qdhy>G>|IT!S7l18pa1EAasJhBl z?U*hS3WmcUu-(3g4A5wf#sb}C07|817@O%EM1gapdnd10BJ>jVs%{;|-zl^SP(;I7 z!AqU=1Y1!XzrEV;J}UH?idqYfBZrAl2b}Yfj5KemKmp-KknB4EkWlOO=1Iy;7 z5wH$pjMNeDdEgft@PXAp_pCiYc_x-TDlI*+2+-%FLC^6waO(obD;h*%Ch5jg0H5MX z@pC2 zLJS6fRCi!cdUCZ+d#JivX{WzBAuTMHWI|NF2Ti3y4fq!kn$a*#b~>qC0Y8yo$Qcbd zsFqJRH+udaPRCtxPe`l|DnF#IYY=Zit+&$0-Z)rVAMA4QD*ZhFW=CP`a#VrW`ucJ% z>Y1>ztFdK!i`)L*;ZM_Z6;`mXwd11faS8JbX7_cLF|+Z+UiaM0Ya|le_V2dH-bMRH!5U;GIAfRY9s4fF+=jOFeWfAgc{ zPFJ_G=O-QcfQm@&2y@3JS8u95+%d>Q4;p(&6bbe!Fj=^w?6KyZEAR8A@+MYImiRM1 zBkpA38dU?}R(*Tm7at%{rQ9_WOJh?^Z@=^v$;}(6?BY1wLYOH+`wd`hF)NuOw#~Wh zsD*&bz-d8R^!*-CNhMWy|M}YM;M4>TQVn=hTg%WvX$~zd#xS`7h#(;iqPQknVJ9Ng zlwtXhaHMOXKLM#dA99+0{zi1?V#s6AQxLOI8u_^y9~iI(p)OxLyuo+-`V?P&)2c{1~frx%f733zFb`;DI*?w&&(Toa?okGCDbDIChh3ed8T;cE5JKp~D{ z9P^jAcWcw6BVaBLx6QqB?%pIQ_mh}CDyQbTHBZm2>#J>Lgv+E7;^Y=>Qp%$&C<*gCcI0|+{&4|TgZJjTv)l*{`(Wl@l}-M2e^hGov*fV9n`54qw{5~A_Mb_S9t59@bf zD3AhT3q_&>R=?!?fd^s)WyJ*cRTX9N^P3R*1K7$@w<+re=o0=~*Ld`;lGc?^7J(E< zgT@MT=Ex3n_?u$*jngWkjB^k)1&@N)oYlv!eq#MW=r{dcrTc}$1pgvP5}+vMna3As zyKIyLH+1Vby|`Nlbel*BFscnq?E2~u5U5G*X^o796BJ~6Rh0St&`rxSw$;P zNx0Vf9NwxznApUS@(wv)(epNF0ebxzh7vTI1=~SScYSp0*{^sLGw&9-if%E%+l{S0 z{DPDVo-HBorbPdYt@2NvtEGU$xI%Qug5TLH@J}2jNmlTZ#H1G%;^omvy9(R2k;N> z6DU!_^8H#JeSXdT^U6mc3bWC8n^EH3N5r9i{`B~`|K=Q=eN=;m zJ_^;n0}pI60Yz6z_{WzH4W%y)*)bA1Pi|ew@V2T`Q~`wbCux7wri~hfJf{P*sv7K>8^D7@dfC9#2V^WI37T)g*|F%bd z;&U)+fzzrheI6MRtd3~l3@XdG4SV6BN?=N13i`Fs`QP7_8<0Snzt2rz2y-8w_HZ7V zk$$p$dB@DB4fDxqo8ku=iC$WtmJ9!J_D7o2WlOO}mfqxKUcmG~2OmHzGK7Z40Y^V< z4~V?N0f-^6G<5=|&@7fHAhH^wVas1e*dT0k9hW`7|SXSO{V(2D#^EM~~FB$dt-*uRv&UpAnhpe-Mw!o&@C@ zFGm4^A~l6Ku)p44Y5H)^roZ8V`f!3#d;%3Un-^vjG;~*Iwll21*lbf&_y7>n*ZuagAK&G_`m`O0 zpRR0dT&k?j;qL#_YY+pStN2~@Z`HwdkZZXR^oq_aq#L9?bEMqZ;t2V6s_+|J#l=u3YJU`&hA%^S&g=K2iF2&1xFcLv^LbdR#O=nCq@ z1HzkNs*H{X?HFk;_9UUkr`J?0xb$pQoE4nz`R|hWy!z#*zqkI@oLvRq!r*bBl!LYb zPM?E8Nl{$i@un4Ok>sfwb>x2d)ETmy{me+u z)~I?)VYXnF`dQi#+t<&zJvy^sKziR9;s4~IuDTq5df5TrxTbBCyvyP)>x?<}jlZ0n z0<7N#AnU3F$#cgokR7{<@C6@t4Cu?U)d3h8tPu2BI_IFe_&SVYaMP#SPhANyD<*ia z#f2PHy99FWxscgKS>T?9l};kBuAqB43MvK9&HsJ3_rLEK|DQX-eu#(f^_-s{G)IBM OL7LZe)CyItpZp))9P + + + + diff --git a/app/src/main/res/layout/fragment_splash.xml b/app/src/main/res/layout/fragment_splash.xml index d4410d9..d59630e 100644 --- a/app/src/main/res/layout/fragment_splash.xml +++ b/app/src/main/res/layout/fragment_splash.xml @@ -21,12 +21,7 @@ - - + android:src="@mipmap/ic_launcher" /> + android:src="@drawable/ic_launcher_foreground" /> - - - + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 32d3639..1084c24 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,6 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f931db569a1d33fa7920411717414efde120e40c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2089 zcmV+^2-f$BP)>T6va#?>SY#-dpky8t59*6>Hlkrh{#Fj$^ddy~yiOSOW`oS=3{fx zyNjuGq7OdMaQkO4-LV>GIy2zihOO{c{m<~Sb3IIPq`@fre3fn0{$@YZ2#p{TwCBzB zl+`~$$KPK=#R3<^?r{Mgu3ZeCf! zZ@O#nwUvGF%Vii>5Fx;(<^oQD7Iw9i!jS4ju?An)Tc2OXU|d1c-W4wF;KFO=HRqUyH)Dyzz~A|MJkbU;J(s@aJtpO1=q^% z%O_xXB~G)du723Qd*iALqAyy3oV7|4jv@zxlmrJb>=3k!)L8YR>r386iw7!~^2TvNyeXX)v7#NE*W6Av!qYO1o&DvjpjR%wKh zte^=(Ddv+Xs0fSZs&pOzi_79{E;s8>D zg3bggXfFyvDdubx#C8${63Vu5p^40fGPw7^LWsp_Nb3#BNrW+ZU%{#>8?<-0q!EPI zP?HWW=W1#P1?8Zi&0T`B^azqx9$@|h1?7ZJ&hn}qAJpXa9FaNuvSRv>~ zyf%`in0Fn1J3aKYeOL&xLRw|ELeTcQqjvy=o$y`@RkSp~?YW<;tQ%~?M9_r7C2--2_rY1a+XZ*$eW|i;urZtz%ExxW!f=A*c#bQAc)_>t!dZo&w@&=r!#3=)Kd?Q+x)r%UKHl zI8VT~x+9QQk;Pps{!)8bl*jkU!uF+ykP8w!>B93~`ATADm;MBq^<~hCci(aezR1BF z*4uNxQ0Xt2^obxZbavWPe^PM@*UG8pI=Fkkzh^S7qw1;%Qgql2J&q`Espo=gg`PJ* zgl9Lt4?W0t82g#{c_;-bM~nQ6JW`M>{^G!f*2~nxD^)`2#Sns&9dD@4_iI|Qyi$_E z)ebHp6QnMNxC@UPj`k)ltX}AsjOj(`T>an@QbBBI5|a<5;_;)63#=D=TO-_mU?EpO zxP(kl&pcAFn0v;o7aBdwDl)IaBac21L>G5Qn*QZN>mmPQIgHvrpQ{~w`alpd0}rlE zevBd-IYlTp^i#`29|=^Y(9%yk^wdzm8`b3UJ+SEfZkSuP4W7meLAor^ zc2gP(3?RtZL1vyqbX^!j_D zE59m8W!|%YYh5Q4HR*K)erNpdl!3BPCNbOiq1P!x>udc?W8<#J*I49EWHd4+%%KdF z#bUb8a)XI72HeJ)t~EU{;wICmxWT587IRob87K>73PB^=kwCOXTeL-6ShW8E@0c__ T|79N$00000NkvXXu0mjfao_XW diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4e6bf44eeb13dd568eb0002623c21e05b4a5fdd5 GIT binary patch literal 928 zcmV;R17G}7Nk&GP0{{S5MM6+kP&iDB0{{RoN5ByfPX;-XBuPqe-}*}iZ~kboOsA{i zIFcl(DGnF-;lCaDgl+znPiwCgCt}#6o$w+Gf0LIK_TbA zDGLFUT)!_`^$Tgdy5Jm=)FiOIZ3`vj-$plE|jzqr!66Kiy z!I%Re_nbDE%RPtZ#cE|4Boudg|98!gYqLcoCx`w+;1GA#Ka7}|j>px51fmp2>-v6O zFYBl6{(gU~)z|%Q-On06a{6yMj4X#rltGH);EsfNJuSNW|K<~aeB&r?dSEm z;-5SFKHc)mA9vQHw+Bw?o@09CoPK%z8RT!o!4Bm?KQ8t*|xqmW;EOO zmR*0>x86_tJn6^hj_Cgci0=1@et){s2W zOzFZ<1!S(Igb^wjV5BrjDQz>QB}$l2X_MM;3!!4Qo<>5uvz4+b9Um0bchd8r^_K=SA&iwkk8@4{ho()pQkp7(d}9!7C!LhmN?@|3g*MgG9tq?d+k<`S z5{C2b!O=bvIMW`S7%YKP?ZL&-64==u*e6I}IkjWEwryCQCxMxyE^Hh(FhjOHgVMn< zQgw=z&H;l|F1SSIT%J@PZzQudRl^J?nFUhgB05Voq#vcTx0l)i3mvTMt&la#=p=;w zQ~RX{^S|mtpWS#Rbe?LtONIT9wcK|R@dRFJ`RMfcDEQph zD#JgkM1;jW@Vh+(;eQbq7NNprkyX!cT=DBZaQ$U3U?%!i+aD$?6;1$_Oe_UF~y`eXYG^44GDKVZo* zk;`dGlfX&^7p+0W?Ejjk9-l8>|Mtnozu$k)FV1)JvDvR*7hSXe^W8_MkEVC~m&NJZ zMda}9*x%JgAE+2i)zEAdZ`CpS~@BI$Ee*Uc~roQ9%#8=Y|nSWP(Jo@PH z$1)GzPc>%U{l{ziD)-sb>(4B8cKa36&mB8I&adOP;qsOF$71sLZh6tS zQQmU1%+3jco6V0L7Sw-V@z6FVt^14==jM_lZ@505H@t2axgz~kWPE^}e*TO8V|TVb zuUh~9;LJJpM`C(pl2exN*q1Tqn%$|tzlv(}=UuFHIicU@VJ?ZhH zueR13pK}{3$1iK%!+c%5!7eh~?(vt}TLRmY*Zq7`q#w=JuQT7TV#U8NifQ5Bj?a+h z`xra@?2PYUS)Nbz7k^ZCX0h1oebW_RpFC4`yKr+WdqqvhPY>Pn5V!r)HIL^=sv2{z zyKQmku+y0hbNnrHY%Hei5Azb|pRuiEOBV=uWcd#l{EC%)sRuByZQvVtC# znR)Lv-ri?@{!^1(=l? NJYD@<);T3K0RTh~ux|hW diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 0b9d49d11215140b3a896b4190c67e6131c00d45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2818 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 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index f5b64e6c9943ed324df4e675215e44c34b4cadc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4072 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 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..663f28c989411f50cccf59ea4986b8b2eeb48185 GIT binary patch literal 1678 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 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..19ad84cea5463e8748e2d3bd7cca5abb99e4916d GIT binary patch literal 1292 zcmV+n1@rn+Nk&El1pok7MM6+kP&iEZ1ONapU%(d-^@f7BZJ3ll>~UfN@)xGkun(HB|ZS?{PNh9(iZYF zaWnyQPK+s9U-=`<`}#H9d~YdwShiHUX4-h;2pS?8CU>x1&T%&~%_LohvanW6=p~BK=g003ywb!=m} zV9M$e8(z^IFPH@X#3r`1Bnl?NJcZ$vVw?*%uBz>Y8zlovnqdX=bLR`0={;@9FdOXY z)OK_LK-4lagRvqzcc4t7e=Idck{H|>H4Ti$FutLT9bFk_CoauHCx?fI`<9Zi!lS_a z0jH*8A?@Xf^A28s`tbae-v^L5&;jPm=1dZ#JuoXGQ#(qs(*Wu%_D238tb^JVirk^?Y~OwjzZ zp;}P*@tjn?GpHQ|xqeah5dMGQ8(IVPDhLT_IsaW@1dMvu`na%}{z>IHYONFuUmzs_b z{-f%=V=PNNjE4DZU0175DD3~|f2e+ZJjWUv!_~QPDXDK=&kcPw^!=V5?USA{oxpiv z4hYGujzcM@6Gjsh@t;^uDrt`996uj7pW#56&i4nm+ut6efv~@-c(k%M>9O52Vl!7j z^I%x##sZ+%B~&IFpxSSu`Bwu`|1aQDM3 zRQ;j$R(=+W1;_1`Kx@0}5Q(83*h| zBLI{1+xH$`|NRn*odkddK(@WXwtiG#^{mk9S&`&FCjW1E1^_|fljh%N(*HFlaqd!t Cdw=Z! literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png deleted file mode 100644 index 28bc6ce37a4e933dc1ccabb0bedc50d20dbf4a08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1352 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 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index d372b167b6f597712c6f21fd4b49aea851060d2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5988 zcmV-q7n|sbP)uR$U$wrs_vPd>h77ID+w<3eLpkPU0v_h|EgZSIy!LN z^>jVCO^6HPP)ak30XMjYkPkm|J$acZi8oSUQoT*oAbdHv%%j7vP=J6tP*Jdl?>L!W&L%7QY*GI+PsBHTe{oylgY6#&0^)H5L0W%5dmLS-!j*`R}xe!ri@RLop%X%}sqoPt~B z5gF3O1YdO`&^tgGb_3b3^D;pP&;@i-()Zd;f}iD+seDR%bE)-1OnRlAcubI~*iyrj z2$TcVQ<%7EZ1x5V-2( zs;D8VKv&RNt}abKkyejGJwBOjlmCS)bwVIs);BjQz{pMwLNLS$JYWxW)C4mTr1f|!UvwtAs#u`G#P!99Ty5Yz$cLhDDOr6xL|6AfC?rPjWD zyiO`*Z&|XCB|!%CemmP6sH1Hvy4Ch3y7}Z3nS5%&%5|2{^oAm+lU!Y!MiLLSCoQir zt=^4~mxCiu=aOk?!kKT-#*4XV>!lOu$dz(*;>sy>>`DbXbh!lWsLn@A&hJ8&vun}c z%0EPLIDfRg47ObHb@8X1PUJTY5cr76RGj=et|E0qO=esW6v3?``lRIxJNYMFwR*YA zvfnM}uF}~kqQ#2s0P@|3m9F4iX=we>qFG`+L2Z2jl?HUBS)fCGOC!j@ODg349VF0Py)X>yQ2+Us}<7 zOW#M;Dt*5i)YR9a8I|i%tnD?C99BN76Qi5RIQBXpOIXjPcBgJA;L(vN2F403_TBBs zfOi4CO6Q=Ox>^a5z5vFB;n*EXdx9(S1xWH<#l4``Tx>yx+(}D0M|2dxc>-@?b5ZgX z(SYke@(E}qVGOABaSV_Hgt0ww*)oKB12958j*O7$mvJd#j{Kka(cV~Wc|-^`%^pQN zYTzKHbIEj+eYsddps#>|`9J5k<4N4BB6+M1tX`~sPS;6fWXgL58McppLd=!_5j)f^%E24Cn={9!gLmp9hQwMajtc z%eb#GSM*edUl#9pOPY&bf$c&;pdbCdwDk*^iK7;987z7Jz|kues8`t>8GXFQRnj<5 zBqCGR$RT3xyifdqqC1hS!SVhv>i3faw3I9+pZt)m@R0=PzuynCm3ZFR)qa zOa)2Euw(Qr;Ldvk2qEXuiS2NqXG!ZX_IK2y1lkiGD4UBe)LoVk=Pwl3oJF_Wrpd_n z0l*P%tj3&)l~Jpn>qNK|7VkVt)PDux825v~w|)_dF!HgyJS+Oc@u_4PfhEpgIETyb zk9(O!2cMBld%?Cb3xGq<#{&L==N%#Q77c#aSpO^Hr)3 z=(Ua46ZD0~0SyHjO`(O>i><2?O04xTIg82|F$1_%KNj$78O9X@T3~@Wj>t!eeEvER z4lM{;lR&mhl3fAFr=RP}4@yY$C%(nyx5;{4Mh6kHEVSY5#mKSH-U3FEaNz{vvW?l} zaw8|+Ei7BBSSR9rzv*~FKgGBoS^_8lt@|LxZj7xKswk_x$8eAj__TPdgfxF*)9E9q z{gyXm^iTjaDyS#a7hEUQU(O)K$#hA-n2e&b zMy|x1(i7P(XUBs3Nso`l#GfQf!^g4z2e;yrOYt6~4u6H}R1gU$tv-joEZL2$N7tgn zBdgKdc|VgDexX)5l-+~J7@rNm0(jbGf!EJ7D+T~Eep>*~+%Mq@pJ+$Z-h zM61j9%Nha(dA}RL4(x`uVK1^d0YxnxG8DMQ25>$m@093B-ORKhaXt+Nu?3w9YV-bc>*IpY>Q&`v(OPEuC-nPYeiNRi0(8?taTv?&QiEgt?K z;8wW@2qScNF4&x&v|HW)4Ue8X-$l8v&0M7 zf9@pud5+_v>C z^iJLeH0IY8F7mTbn@sr7+1faeu~n4TqG!05w*3t_>^WUtGCeU82FEhv|UWur1M$8;ceT9%+c zg1FvsEleAe_LGMKVgMHIDoO7T#B%|bcmYXA)-Yu$5iNJk7kE$}&t=rgF^9OgRhW#l2RA|RCtc?2u!x@`tJb@?|5`GC}-ERsh`a3FMHp;87P2^s%8 zuA;@T-kp^U&@5`sGo4HsN^<~}!U6yY8Glunj4ih9KrL}J`^PD}Ab`Irq_!MNB22X2I`Jb}^<)i@^IIwU^Nx39o|S`Ix$fX$Ibs(y zviF}A4G>}Nd#gD;*@r>c-w;}{6%Ef>hAzr1Czn^BM-S}&K<3#d1fd748e0+mLa%O$ z1_&M8vz2*MQa*X8LBmkF7Udux(0AX*sPc;RxOxrl6;Ea_lzHA42>QtBB?T2LoDur4FvBUY18jML zfEro60TvI>z#PL&xEYs1_!b#}c8M0owc|hc&H%2IaCJ{EA4?_B1wc-Sct z@8S4RA+$9=%Dd=H>L32qpT>l1g|A7%?AkZZ9@zu9{f za7qD4Pb=L^Hr>@6qEgCpuBI9dIk<$E_L>ZM4)tG*_yu!&GvU~202ZgQ zeVXHcb%w8BN>zUV}&%2+-#wpJ8-CI9B{KQET^!z6;Z%?&STlBWP?2ruN}RYzX`RJ=;14r%2T?| zf(*dO_}v85BC*fQz%7n|LRi`ziB1keZ~AN8?dA=MMWhf_y?_PV3#@2T?kZ%fu9Ohx zFRU!xhgt<}gI(Onl)lSASOZEMWee`!0hXadooB#usZR&!1t!BcFD-{cOc|D1hcK~1 zG6E&UTTpD~YshkBT|ipB7<;RV@VMeQmKZol>!^rKYb|j_0Kkf<99<4l_Nrtc=^kiy8USS*bFF8}-PowL#L3l$Qw0s(m4sLQq)3<-gREAj#S z^24HGN4TgK@UmW*d-o%x$GgMP2~$*R{7h09__9o^XfSr)0^qfu)Z#6Qp<^J>6C0e> zBk+*pfVBR4I1Q(Lv)?@6lIK)qqHZWM^x@lk7}rlK<6oNdU{-8J9k)(JC6}rsBn1kv zEZuGUOc`Ao0gl19BIKU7_XC&e{J$fSGeYedd}RUvtiZPToB-x7r4fqJ7$r{ zgeA-0fD?0{6mP||VDZ_Y#sZCebf3L&EI3936ED4|zMoA&+`S#af;UH69vx#^p8ytC zH9Lk?&RvxS62knAEoYCT9y@1Aq$-29vN+httscnraDY{=)GKr%*5QZkSYlg9CQGa0AuO8n6zy;yF_p_q$F( zT=y7=VWWd@jlR>katuAZyV>g26~PM-a|$-e9w+ey;M#sVb-3w}&vT*w?1h3GJDBbU zuDCd(7m<&0YQ}Ymh5yhXHeyuocs#wXRl8h$E1nO$pT9}=6;V$CFV?}yh}roYQP=Ga zIqP)+A?jH%rZ()^yGH^?JXfMq5B^EPTzj(#e|FGK?>`GhdbQ&L0M>+lIe$_@s#-v# zv(&Z=J+${DWX@1K9|<^wQ_o17-1h_EhUZ9T$IJH=goCP3E}VmNEw}0B4Nil}283N? z>Md2y39wclw{034efTT1;Z!c!$Az~PS)v7in<=Y4hqhPdkyn)N+dYRrgxe&DN5XOg zto3WA_x}bsA>7bcQT^t6*wl>cpoftiw${4nKYngK?Do?^*}V}2w8at9gS+RU_=8K) zJ9$6jNz`8SZN)+ImrK4X-Gk;9q@yv|P;eR!mNe)~c+zqj56N(1qz=}4bhH1t33Jhv za6-9J*lzS+x`B(;knsR_v^(^lKEKTyBRE4$aFcN_3gF3yrG~kKc47{?6D}wxe)s!w ziM&n8b%-|N&o*3GaCiNJ`0aLgq;gESMgcybu5x}bco!EM+=FmHxghUZJ24f&L{CuA z0jDj-AGIG2`q|Tgx$)pd4-<};V4ts@JpkMe95Zh#8gv_sd?A6ro4B?n_#cMaVRX~Y z`sWJc+VRu~D&`5-4sc!gXh$>kA28s;pB!k%*BzE?8k~F~Q6ogRfG!9E#pyM=Lw6+cY-YGQJ*h@QWz#8M~ zv552r7@$2qaoq@@FH?|SYgqL%(oXNcHl(!?cC|W@x@kH1FH4#f`9cDPAPfuVV)U)? z=N~Y-YDYXUDeBYsTsR#-mM`p)lD8piAV5M(65$YEk9Cb&IOIsg^YBgkGe zgss@$68F>)y#mVsSs;^)PZkndf==3$ev87owCV%85}m2;upiPSU5>jXxTFvgF_<+l zKzGFG+WfZG4{0rZ=jv1Ab6||1GilN*sEZQZl+Uh;cP$S0LR<`K?N}y+m7s$*xzD`j z9a}vDx)B{gR~qF&t8ely$6W&DVU`$9x^k=r<_utnF2TXUJtBwPKVCbd|LUkk!wP{A zvg;=F3-3|Eq z2A$b9L<0W_U6~q!MoZwD;xGx_LT)iU81X`{N!qubS{n87(7kY~0Fy;FxPdsb69=u@ z(Nm-lPITj_3;seC_;e`4kT$v^YQfOGP&UW_Ss)W+6CFSo2l1feHRJ2ONrEB5NIb0% zZG~fNp;vSUVleIuyR+@%kwbco*Cs#qo@U0=U+F#^oUZ>QKHIQl#BsxyBW#8hqsxt7 zzjPV^-wn%Nw81_7C(ma?Ud_w_Y4B|1&|c%9EGUyF9sXKLM<{FEhiReFw6-V&d+==nn28q^0)$=AB~y9`>iUkB8mU;YkAg9j=A6 z)ZGVp_`I-O06rbcfU-J}vOxy*@33VEvafj`?`wj}L(2o73xtH@>70(d&gmOR}%(CI6 zK}>{!_-i0l47djOAP?lFWW%#i#&yDdNI;rk6ae|V791s24B@(vPk63Lr~d&15|p=n Sjqeu#00004KMOiNk&G<4FCXFMM6+kP&iDy4FCWyU%(d-6^DYhZKRk#?A^;CA|{}F&M3pO zvOn{zdG=ktpZ`xJJKMJ3PMDdQVUL-alUd9-42@L|&%d7P>h3DnDBHgSSi-)`TU=wrv}Gv>!ds zQLSq6+vno5>-&Byw(aCsjE3yUwpCjhb?;tljk!1O?#|~;fF#I=%#j62vT0j!k8NGs zwrxDywr$(CZQJkb+O}FWHe=hiZQI84eeZo= zEu@8Sr3}uTjQ+LmS*A_{Hr{z`^A3vm8_4LNr<1<{hXDYVP5=MJwr$O}ZAM|+HYh9( z`p?L18%Ojmrq7=07jV5pfd4v;3($CH=Px}@ocm1;-dMrN*_cFjF2|e9{j$p!Z_Kz@ z;}3WdeN=1wim_tvTB~yG&)@9Mdz<4zm)pMe*f}2&8y#4RzH7T0XQHakZX&r)xc?9) zk1%zF=_E`)VFn-A`tyXTee%O?&tHA;sfg(3Y-`b-wPK@3oJ?EopVw8{S%m2$3w4hJ zKY1UbZCh6&N;AUjCrnAKrBHBHQ7e+F=oSjGhTX3?#F~PiLyYQs=7e5SWp{B_RK?Lm zG0M=(MZx}Y)?k3Qcx=d02EYBZ`@p9AU#-B(Clg!PG1^zw46qS^qgA8*fU9kvNNfLj*uu$)3 z5oL0}3W+JR(N<-DkGZ8LLMA_FTf8w%97h{As^o#0s5^Xq#D_AsI|0;tBF7vglu$(k z+&q@+UV!mDs?Ki0z%}mf$XA3jeW%TUTOo*Sfw=I(zJt^y^(T&Z5K1W`>fPRNIJ+F9 z>cntn;y`ujZ1_POvI%hclOyL&R@M`J&UIKsiz+{3@p6NEXmF33FtyA~r=<^y)2x28 zXE%Y=DLo1|$%59po>dGC?ZEe?N}rkA!@3W|B1>*eQC8_8972(x-ka9|XueM=qbk$5 zP(>|8WW|#i#xmycun8*Co@*epQFRa#TE_B%CUPxKSWZ{KPDS?`1=>lGig=KlbDedR zHJP@zt>mcOWK@ZY*pt#ung@e1BThP*N-1-6sD(f(qoK?2fK6BL#@OQblEh_H<-FNk z%lx63b$sQm^feN@^BC%n$pQ-LZeV0p9n;y-mKiY0e4nt4&@TRhL^ zJsI*u@v!Ofu`$QZgrz9hse8&fx$ zcj9IM)6lUwFVT#s4wD6V#nV@FvMO$CmFW^Rc(xV5v<1SXwG!nGqV8vwG5gkkuv@oQ zY}P=7o$i%~fWguaYH_myTOylrB+#49{}wUE&7&oSQqep0F03F4$BLG#x9MR za?DYOcwyfv@9d)r+*M9~_49#gKLNn>{a8-`tgzlc>7eb)x(q4ms*||G?}0qaQ(k%E ze*iq>8giC91QC;LL5GTHHvZuNmAt5m%%60QOm2^8%N2X56=fwQ?yY?TjWDPQr)K_5 zjHDf7<<6L^v{_`RaQQS?hS&MFF1*?fI-IVJwpAoul+a}l!qs`{UUaS8939=8<)n(e zv{-PVxeTb@w{OYS<*g&#wN=j47&MNdAuZD4>*bTyQPysIYL#|n4gCj3HWuC}4?W8c z#!3;JX;B&0FnuSWTHn_BH_BPhT>CMIs#J+Q9rElk%sUIXQB`Qo%okduwU4jH1!&Ib z^Lw(ePBtyBxs2~oAN*0C66JC7M|s&AXM1hla{!2f)?b8z5?SVM`NQ}p$j}$Y8<6=Q zfXo5>qiHN$!2lqr^Snm`cl5on^TqF5i9ap7ZTh1!8W8VSKkrlYa{$5?9}&wJVP04f zxtA4-93IGj3S+Nhy+g(xHTSrsCagSd=LJ_84c9S{M+l=0YtgwqCojAhV*udvs%r)_ zv#vE4IMa8KwI7N_I#g}Ka0!Ee8!SJ(Ecr|4Em*pB^V%hs%3V?EFup4f^wYJSyww>D^`GU|qDugdMK zEvVPD2e1LCYJz?yNZ-|`wgM1!sP#YOt|OEW^sdb&}k(!g% zBAzXEX+iA-p@;#wIpfw6!4zj4B5n>%Ra*(c3AY5tk>&YE-5sF`xiiQ=vq z_CZnWi-k98)4!)3Ba)a*rP1fe0GRjr@0+12Hz}Heyk9-ksWYE`{A>x6A}79j$pfI7 z@Db1Ia4J2*A)11}>-8_Ddi*iI-yQG%nLuK40iaDjLkhrX;q{u|;0Jdx*S3w(eY0qB(PXFH8L8D5KF}p9_ z15hq~3llZ7(y!`KcM71~_qZU9hLNQSpHTuZ8SV@BKIJ4`)$UrhMf*G5nv*)*7hYeu zyd=?twS5ABDFD|V_58YqFvxh895utg-nrd>0eB4gM4RSj3;=8V7t{dE062GP^~-us z@OLdR?_K`hD&6%4fQy~FR&$d!>hm4tki%D4tkc!uj4C32<$APRf8|xiGrG)Doj-iY z>G5tVINB5P5#KQdU_amXWpTeEmG5Yclw05FTTazHJC|jJhu(}uDH!0Pc+7W90N9TA zOhD&$zklW=La98pf|t*FHGwE=uaDzB5p?VwfCmyPjwsUr_+!6e2*9#qq0P0iUvBYk zESsS+L>|dx`EGqmOBUQ(n>)_jB$bv(NzPwJc{%D4J~RhlH_~_cEI*pdgHoHXAr#j= zmGR6z@29TgK`RDkHZQY0SO2}iWzBzake+1Qr^b56=*+_>8(*VW9?ZRgnN7PX5^zF^ z{3C`Nyhrjj_r6|Sxwr05I=;ppe&(T{O4N-~ufL4?jB<<*OaWMTOg3Io%P!7^zQq(g z^A;lvvbSd3N4$iadfp>$=GH9dVH3RPFS9fkC!MUAj_31#{7YIQWyw-%lo!-vd}#Lk z;lly1`M1`(vQEDnn0C#3ArmMPaL%*8dP?CY^Z86h>-W^JvYd18t@jPgrh4=AaKQWU z=g)sNRZ3EVZP}J;zT->)_QL_^ai3S{{5!RAm3}|&U7Ar5Oel0Koqwk`J}?|`24H{Y9nGmW-9D`;T=?clZ!sA09kb^FI1C4zhXbBd zJzU46mtC4OW!-+!v#Njn^2r*UO!?r{hRQfC)xos?`ug%@8opHOS@plN?mS<1dCttg zU2BK!TL9qj{BgIwd7*(4_;LW)00e(Lbc}b*0a!ffb_@qxh6C4{$sdUvHtp?o z+#iPBJJuV4U;l>N0l?wGtaIoef6sE$KJ)npA3XNh zV;_9}%pV_j$0kG1O0|@nxKa}cT?>Gl+VR=Zo=`Lo)ztj0XLn3}_1c87CK8g4lTMd3 KrPGch>BSH#9`R!U literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 98ae68cf26a3def0feda79ebc90439389db9a2b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4960 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 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index cd2f01a3e7ec7a1b67e38ad3dec1e70b06e8ff5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14560 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 + + #247881 + \ No newline at end of file From 48884dcd3202ac918547dd3c30abf953269285e9 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Wed, 24 Jan 2024 23:36:58 +0530 Subject: [PATCH 06/25] Added: Animated logo in Splash screen --- app/src/debug/res/values/strings.xml | 2 +- app/src/main/AndroidManifest.xml | 2 +- app/src/main/res/animator/logo_animator.xml | 29 +++++++++++++++++++ app/src/main/res/drawable/animated_logo.xml | 9 ++++++ .../res/drawable/ic_launcher_foreground.xml | 4 ++- .../res/mipmap-anydpi-v26/ic_launcher.xml | 2 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 2 +- .../main/res/values-night/splash_theme.xml | 13 --------- app/src/main/res/values-v31/splash_theme.xml | 12 ++++++++ app/src/main/res/values/splash_theme.xml | 10 +++---- 10 files changed, 61 insertions(+), 24 deletions(-) create mode 100644 app/src/main/res/animator/logo_animator.xml create mode 100644 app/src/main/res/drawable/animated_logo.xml delete mode 100644 app/src/main/res/values-night/splash_theme.xml create mode 100644 app/src/main/res/values-v31/splash_theme.xml diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml index fb410b5..89e609b 100644 --- a/app/src/debug/res/values/strings.xml +++ b/app/src/debug/res/values/strings.xml @@ -1,3 +1,3 @@ - Debug Zen Music + Zen Music - Debug \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e7c3a6d..4c1f723 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,7 +23,7 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTask" - android:theme="@style/Theme.Zen"> + android:theme="@style/Theme.AppSplash"> diff --git a/app/src/main/res/animator/logo_animator.xml b/app/src/main/res/animator/logo_animator.xml new file mode 100644 index 0000000..59e056b --- /dev/null +++ b/app/src/main/res/animator/logo_animator.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/animated_logo.xml b/app/src/main/res/drawable/animated_logo.xml new file mode 100644 index 0000000..72823b7 --- /dev/null +++ b/app/src/main/res/drawable/animated_logo.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 204f2df..0a288a6 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -3,7 +3,9 @@ android:height="108dp" android:viewportWidth="466" android:viewportHeight="466"> - diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 1084c24..ef49c99 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 1084c24..ef49c99 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/values-night/splash_theme.xml b/app/src/main/res/values-night/splash_theme.xml deleted file mode 100644 index c974cab..0000000 --- a/app/src/main/res/values-night/splash_theme.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/values-v31/splash_theme.xml b/app/src/main/res/values-v31/splash_theme.xml new file mode 100644 index 0000000..90a2c9a --- /dev/null +++ b/app/src/main/res/values-v31/splash_theme.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/splash_theme.xml b/app/src/main/res/values/splash_theme.xml index ffd189c..2fec109 100644 --- a/app/src/main/res/values/splash_theme.xml +++ b/app/src/main/res/values/splash_theme.xml @@ -1,12 +1,10 @@ From 29e852c0b74faeed43e2bd2202f2e78a12e57bff Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Wed, 24 Jan 2024 23:56:47 +0530 Subject: [PATCH 07/25] Updated: Splash screen to a Stub --- .../pakka_papad/splash/SplashFragment.kt | 47 +++++------------- app/src/main/res/layout/fragment_splash.xml | 48 ------------------- app/src/main/res/navigation/app_nav.xml | 4 +- 3 files changed, 14 insertions(+), 85 deletions(-) delete mode 100644 app/src/main/res/layout/fragment_splash.xml diff --git a/app/src/main/java/com/github/pakka_papad/splash/SplashFragment.kt b/app/src/main/java/com/github/pakka_papad/splash/SplashFragment.kt index 740affd..9f5f971 100644 --- a/app/src/main/java/com/github/pakka_papad/splash/SplashFragment.kt +++ b/app/src/main/java/com/github/pakka_papad/splash/SplashFragment.kt @@ -1,32 +1,29 @@ package com.github.pakka_papad.splash -import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.NavController import androidx.navigation.fragment.findNavController import com.github.pakka_papad.R import com.github.pakka_papad.data.ZenPreferenceProvider -import com.github.pakka_papad.databinding.FragmentSplashBinding import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject +/** + * Stub Fragment with no view + * Used to decide if onboarding is to be shown or home screen + */ @AndroidEntryPoint class SplashFragment : Fragment() { private lateinit var navController: NavController - private var binding: FragmentSplashBinding? = null - @Inject lateinit var preferenceProvider: ZenPreferenceProvider @@ -36,35 +33,17 @@ class SplashFragment : Fragment() { savedInstanceState: Bundle? ): View? { navController = findNavController() - binding = FragmentSplashBinding.inflate(inflater, container, false) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){ - binding?.appIcon?.visibility = View.GONE - binding?.linearIndicator?.visibility = View.GONE - } else { - binding?.progressCircular?.visibility = View.GONE - } - return binding!!.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - preferenceProvider.isOnBoardingComplete.collectLatest { - if (it == null) return@collectLatest - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S){ - delay(400) - } - if (navController.currentDestination?.id != R.id.splashFragment) return@collectLatest - val nextFragment = when(it){ - true -> R.id.action_splashFragment_to_homeFragment - false -> R.id.action_splashFragment_to_onBoardingFragment - } - navController.navigate(nextFragment) + lifecycleScope.launch { + preferenceProvider.isOnBoardingComplete.collectLatest { + if (it == null) return@collectLatest + if (navController.currentDestination?.id != R.id.splashFragment) return@collectLatest + val nextFragment = when(it){ + true -> R.id.action_splashFragment_to_homeFragment + false -> R.id.action_splashFragment_to_onBoardingFragment } + navController.navigate(nextFragment) } } + return null } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_splash.xml b/app/src/main/res/layout/fragment_splash.xml deleted file mode 100644 index d59630e..0000000 --- a/app/src/main/res/layout/fragment_splash.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/app_nav.xml b/app/src/main/res/navigation/app_nav.xml index b446a28..c84b1cb 100644 --- a/app/src/main/res/navigation/app_nav.xml +++ b/app/src/main/res/navigation/app_nav.xml @@ -1,7 +1,6 @@ @@ -84,8 +83,7 @@ + android:label="fragment_splash"> Date: Thu, 25 Jan 2024 17:04:51 +0530 Subject: [PATCH 08/25] Updated: setKeepOnScreenCondition in MainActivity --- app/src/main/java/com/github/pakka_papad/MainActivity.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/github/pakka_papad/MainActivity.kt b/app/src/main/java/com/github/pakka_papad/MainActivity.kt index 97c5799..251e96a 100644 --- a/app/src/main/java/com/github/pakka_papad/MainActivity.kt +++ b/app/src/main/java/com/github/pakka_papad/MainActivity.kt @@ -1,7 +1,6 @@ package com.github.pakka_papad import android.graphics.Color -import android.os.Build import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen @@ -21,10 +20,8 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) installSplashScreen().apply { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){ - setKeepOnScreenCondition { preferencesProvider.isOnBoardingComplete.value == null } - } else { - setKeepOnScreenCondition { false } + setKeepOnScreenCondition { + preferencesProvider.isOnBoardingComplete.value == null } } binding = ActivityMainBinding.inflate(layoutInflater) From e57bcb01054b7b62f32637d87b52fe3cddfbb5d4 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Thu, 25 Jan 2024 17:05:04 +0530 Subject: [PATCH 09/25] Updated: proguard-rules.pro --- app/proguard-rules.pro | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 0418156..535725d 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -25,4 +25,14 @@ } -keep class com.github.pakka_papad.collection.CollectionType --keep class * extends com.google.protobuf.GeneratedMessageLite { *; } \ No newline at end of file +-keep class * extends com.google.protobuf.GeneratedMessageLite { *; } + +-dontwarn org.bouncycastle.jsse.BCSSLParameters +-dontwarn org.bouncycastle.jsse.BCSSLSocket +-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +-dontwarn org.conscrypt.Conscrypt$Version +-dontwarn org.conscrypt.Conscrypt +-dontwarn org.conscrypt.ConscryptHostnameVerifier +-dontwarn org.openjsse.javax.net.ssl.SSLParameters +-dontwarn org.openjsse.javax.net.ssl.SSLSocket +-dontwarn org.openjsse.net.ssl.OpenJSSE \ No newline at end of file From 3cd2c3c58bd1ec6165c4f535e007783a1942bf95 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Fri, 26 Jan 2024 00:15:02 +0530 Subject: [PATCH 10/25] Updated: Switched to MediaSessionService --- app/build.gradle.kts | 7 +- app/src/main/AndroidManifest.xml | 9 +- .../collection/CollectionViewModel.kt | 29 +- .../notification/ZenNotificationManager.kt | 12 +- .../data/services/PlayerService.kt | 45 ++- .../com/github/pakka_papad/di/AppModule.kt | 8 +- .../github/pakka_papad/home/HomeViewModel.kt | 35 ++- .../github/pakka_papad/player/ZenPlayer.kt | 275 +++++++----------- .../pakka_papad/search/SearchViewModel.kt | 6 +- buildSrc/src/main/kotlin/Dependencies.kt | 3 +- 10 files changed, 215 insertions(+), 214 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5aa5c50..be5b753 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -170,10 +170,11 @@ dependencies { implementation(platform(Libraries.firebaseBom)) implementation(Libraries.firebaseCrashlytics) - implementation(Libraries.exoPlayer) +// implementation(Libraries.exoPlayer) implementation(Libraries.media3ExoPlayer) - implementation(Libraries.media3Transformer) - implementation(Libraries.exoPlayerUi) + implementation(Libraries.media3Session) +// implementation(Libraries.media3Transformer) +// implementation(Libraries.exoPlayerUi) implementation(Libraries.coilCompose) implementation(Libraries.palette) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4c1f723..d87f0c8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,7 +43,14 @@ + android:foregroundServiceType="mediaPlayback" + android:exported="true" + tools:ignore="ExportedService"> + + + + + diff --git a/app/src/main/java/com/github/pakka_papad/collection/CollectionViewModel.kt b/app/src/main/java/com/github/pakka_papad/collection/CollectionViewModel.kt index d9effd0..2aab218 100644 --- a/app/src/main/java/com/github/pakka_papad/collection/CollectionViewModel.kt +++ b/app/src/main/java/com/github/pakka_papad/collection/CollectionViewModel.kt @@ -15,7 +15,16 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -178,15 +187,19 @@ class CollectionViewModel @Inject constructor( fun setQueue(songs: List?, startPlayingFromIndex: Int = 0) { if (songs == null) return - queueService.setQueue(songs, startPlayingFromIndex) - playerService.startServiceIfNotRunning(songs, startPlayingFromIndex) +// queueService.setQueue(songs, startPlayingFromIndex) + viewModelScope.launch { + playerService.startServiceIfNotRunning(songs, startPlayingFromIndex) + } showMessage(messageStore.getString(R.string.playing)) } fun addToQueue(song: Song) { if (queue.isEmpty()) { - queueService.setQueue(listOf(song), 0) - playerService.startServiceIfNotRunning(listOf(song), 0) +// queueService.setQueue(listOf(song), 0) + viewModelScope.launch { + playerService.startServiceIfNotRunning(listOf(song), 0) + } } else { val result = queueService.append(song) showMessage( @@ -198,8 +211,10 @@ class CollectionViewModel @Inject constructor( fun addToQueue(songs: List) { if (queue.isEmpty()) { - queueService.setQueue(songs, 0) - playerService.startServiceIfNotRunning(songs, 0) +// queueService.setQueue(songs, 0) + viewModelScope.launch { + playerService.startServiceIfNotRunning(songs, 0) + } } else { val result = queueService.append(songs) showMessage(messageStore.getString(if (result) R.string.done else R.string.song_already_in_queue)) diff --git a/app/src/main/java/com/github/pakka_papad/data/notification/ZenNotificationManager.kt b/app/src/main/java/com/github/pakka_papad/data/notification/ZenNotificationManager.kt index 8b1087c..29dad28 100644 --- a/app/src/main/java/com/github/pakka_papad/data/notification/ZenNotificationManager.kt +++ b/app/src/main/java/com/github/pakka_papad/data/notification/ZenNotificationManager.kt @@ -7,11 +7,13 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.os.Build -import android.support.v4.media.session.MediaSessionCompat +import androidx.annotation.OptIn import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import androidx.core.graphics.drawable.IconCompat +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.MediaSession +import androidx.media3.session.MediaStyleNotificationHelper import com.github.pakka_papad.Constants import com.github.pakka_papad.MainActivity import com.github.pakka_papad.R @@ -165,14 +167,14 @@ class ZenNotificationManager( PendingIntent.FLAG_IMMUTABLE ) + @OptIn(UnstableApi::class) fun getPlayerNotification( - session: MediaSessionCompat, + session: MediaSession, showPlayButton: Boolean, isLiked: Boolean, ): Notification { - val mediaStyle = androidx.media.app.NotificationCompat.MediaStyle() + val mediaStyle = MediaStyleNotificationHelper.MediaStyle(session) .setShowActionsInCompactView(1,2,3) - .setMediaSession(session.sessionToken) return NotificationCompat.Builder(context, PLAYER_SERVICE).apply { setSmallIcon(R.mipmap.ic_notification) setContentTitle("Now Playing") diff --git a/app/src/main/java/com/github/pakka_papad/data/services/PlayerService.kt b/app/src/main/java/com/github/pakka_papad/data/services/PlayerService.kt index aec56d8..6e18d4c 100644 --- a/app/src/main/java/com/github/pakka_papad/data/services/PlayerService.kt +++ b/app/src/main/java/com/github/pakka_papad/data/services/PlayerService.kt @@ -1,29 +1,52 @@ package com.github.pakka_papad.data.services +import android.annotation.SuppressLint +import android.content.ComponentName import android.content.Context -import android.content.Intent -import android.os.Build +import androidx.media3.session.MediaController +import androidx.media3.session.SessionToken +import androidx.work.await +import com.github.pakka_papad.data.ZenPreferenceProvider import com.github.pakka_papad.data.music.Song import com.github.pakka_papad.player.ZenPlayer +import com.github.pakka_papad.player.toMediaItem +import com.github.pakka_papad.toCorrectedParams +import com.github.pakka_papad.toExoPlayerPlaybackParameters +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withContext interface PlayerService { - fun startServiceIfNotRunning(songs: List, startPlayingFromPosition: Int) + suspend fun startServiceIfNotRunning(songs: List, startPlayingFromPosition: Int) } class PlayerServiceImpl( private val context: Context, + private val queueService: QueueService, + private val preferenceProvider: ZenPreferenceProvider, ): PlayerService { + @SuppressLint("RestrictedApi") @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) - override fun startServiceIfNotRunning(songs: List, startPlayingFromPosition: Int) { + override suspend fun startServiceIfNotRunning(songs: List, startPlayingFromPosition: Int) { + queueService.setQueue(songs, startPlayingFromPosition) if (ZenPlayer.isRunning.get()) return - val intent = Intent(context, ZenPlayer::class.java) - intent.putExtra("locations", songs.map { it.location }.toTypedArray()) - intent.putExtra("startPosition", startPlayingFromPosition) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.startForegroundService(intent) - } else { - context.startService(intent) + MediaController.Builder( + context, + SessionToken(context, ComponentName(context, ZenPlayer::class.java)) + ).buildAsync().await().apply { + withContext(Dispatchers.Main) { + stop() + clearMediaItems() + addMediaItems(songs.map(Song::toMediaItem)) + prepare() + seekTo(startPlayingFromPosition, 0) + repeatMode = queueService.repeatMode.first().toExoPlayerRepeatMode() + playbackParameters = preferenceProvider.playbackParams.value + .toCorrectedParams() + .toExoPlayerPlaybackParameters() + play() + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/di/AppModule.kt b/app/src/main/java/com/github/pakka_papad/di/AppModule.kt index e744f8b..6b3ceff 100644 --- a/app/src/main/java/com/github/pakka_papad/di/AppModule.kt +++ b/app/src/main/java/com/github/pakka_papad/di/AppModule.kt @@ -197,10 +197,14 @@ object AppModule { @Singleton @Provides fun providesPlayerService( - @ApplicationContext context: Context + @ApplicationContext context: Context, + queueService: QueueService, + preferenceProvider: ZenPreferenceProvider, ): PlayerService { return PlayerServiceImpl( - context = context + context = context, + queueService = queueService, + preferenceProvider = preferenceProvider, ) } diff --git a/app/src/main/java/com/github/pakka_papad/home/HomeViewModel.kt b/app/src/main/java/com/github/pakka_papad/home/HomeViewModel.kt index 530a4f4..25c748c 100644 --- a/app/src/main/java/com/github/pakka_papad/home/HomeViewModel.kt +++ b/app/src/main/java/com/github/pakka_papad/home/HomeViewModel.kt @@ -9,17 +9,32 @@ import com.github.pakka_papad.Constants import com.github.pakka_papad.R import com.github.pakka_papad.components.SortOptions import com.github.pakka_papad.data.ZenPreferenceProvider -import com.github.pakka_papad.data.music.* +import com.github.pakka_papad.data.music.MiniSong +import com.github.pakka_papad.data.music.PlaylistWithSongCount +import com.github.pakka_papad.data.music.Song +import com.github.pakka_papad.data.music.SongExtractor import com.github.pakka_papad.data.services.BlacklistService import com.github.pakka_papad.data.services.PlayerService import com.github.pakka_papad.data.services.PlaylistService import com.github.pakka_papad.data.services.QueueService import com.github.pakka_papad.data.services.SongService -import com.github.pakka_papad.storage_explorer.* +import com.github.pakka_papad.storage_explorer.Directory +import com.github.pakka_papad.storage_explorer.DirectoryContents +import com.github.pakka_papad.storage_explorer.MusicFileExplorer import com.github.pakka_papad.util.MessageStore import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -272,8 +287,10 @@ class HomeViewModel @Inject constructor( fun addToQueue(song: Song) { if (queue.isEmpty()) { // manager.setQueue(listOf(song), 0) - queueService.setQueue(listOf(song),0) - playerService.startServiceIfNotRunning(listOf(song), 0) +// queueService.setQueue(listOf(song),0) + viewModelScope.launch { + playerService.startServiceIfNotRunning(listOf(song), 0) + } } else { val result = queueService.append(song) if (result) { @@ -299,8 +316,10 @@ class HomeViewModel @Inject constructor( fun setQueue(songs: List?, startPlayingFromIndex: Int = 0) { if (songs == null) return // manager.setQueue(songs, startPlayingFromIndex) - queueService.setQueue(songs, startPlayingFromIndex) - playerService.startServiceIfNotRunning(songs, startPlayingFromIndex) +// queueService.setQueue(songs, startPlayingFromIndex) + viewModelScope.launch { + playerService.startServiceIfNotRunning(songs, startPlayingFromIndex) + } showMessage(messageStore.getString(R.string.playing)) } diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index 1783163..ffe9d26 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -1,25 +1,27 @@ package com.github.pakka_papad.player import android.app.NotificationManager -import android.app.Service import android.appwidget.AppWidgetManager import android.content.Context import android.content.Intent import android.content.IntentFilter import android.media.AudioManager import android.net.Uri -import android.os.IBinder -import android.support.v4.media.MediaMetadataCompat -import android.support.v4.media.session.MediaSessionCompat -import android.support.v4.media.session.PlaybackStateCompat +import android.os.Bundle import android.widget.Toast +import androidx.core.net.toUri import androidx.media3.common.MediaItem +import androidx.media3.common.MediaMetadata import androidx.media3.common.PlaybackException import androidx.media3.common.Player import androidx.media3.common.Timeline import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.analytics.PlaybackStatsListener +import androidx.media3.session.CommandButton +import androidx.media3.session.MediaNotification +import androidx.media3.session.MediaSession +import androidx.media3.session.MediaSessionService import com.github.pakka_papad.Constants import com.github.pakka_papad.data.ZenCrashReporter import com.github.pakka_papad.data.ZenPreferenceProvider @@ -33,6 +35,7 @@ import com.github.pakka_papad.data.services.SongService import com.github.pakka_papad.toCorrectedParams import com.github.pakka_papad.toExoPlayerPlaybackParameters import com.github.pakka_papad.widgets.WidgetBroadcast +import com.google.common.collect.ImmutableList import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -48,8 +51,11 @@ import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlin.time.Duration.Companion.seconds -@UnstableApi @AndroidEntryPoint -class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callback { +@UnstableApi +@AndroidEntryPoint +class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastReceiver.Callback { + + override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession @Inject lateinit var notificationManager: ZenNotificationManager @Inject lateinit var songExtractor: SongExtractor @@ -87,9 +93,67 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac val isRunning = AtomicBoolean(false) } - private lateinit var mediaSession: MediaSessionCompat + private lateinit var mediaSession: MediaSession + + override fun onCreate() { + super.onCreate() + broadcastReceiver = ZenBroadcastReceiver() + mediaSession = MediaSession.Builder(applicationContext, exoPlayer).build() + systemNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + isRunning.set(true) + queueService.addListener(this) + + IntentFilter(Constants.PACKAGE_NAME).also { + registerReceiver(broadcastReceiver, it) + } + broadcastReceiver?.startListening(this) + exoPlayer.addListener(exoPlayerListener) + exoPlayer.addAnalyticsListener(playbackStatsListener) + + scope.launch { + preferencesProvider.playbackParams.collect { + val params = it.toCorrectedParams().toExoPlayerPlaybackParameters() + withContext(Dispatchers.Main){ + exoPlayer.playbackParameters = params + } + } + } + scope.launch { + queueService.repeatMode.collect { + withContext(Dispatchers.Main) { exoPlayer.repeatMode = it.toExoPlayerRepeatMode() } + } + } + + val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager + audioManager.isSpeakerphoneOn = true + + setMediaNotificationProvider(object : MediaNotification.Provider { + override fun createNotification( + mediaSession: MediaSession, + customLayout: ImmutableList, + actionFactory: MediaNotification.ActionFactory, + onNotificationChangedCallback: MediaNotification.Provider.Callback + ): MediaNotification { + return MediaNotification( + ZenNotificationManager.PLAYER_NOTIFICATION_ID, + notificationManager.getPlayerNotification( + session = mediaSession, + showPlayButton = false, + isLiked = queueService.getSongAtIndex(exoPlayer.currentMediaItemIndex) + ?.favourite ?: false + ) + ) + } - override fun onBind(intent: Intent?): IBinder? = null + override fun handleCustomCommand( + session: MediaSession, + action: String, + extras: Bundle + ): Boolean { + return false + } + }) + } private val exoPlayerListener = object : Player.Listener { override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { @@ -110,8 +174,7 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac } catch (e: Exception) { Timber.e(e) } - updateMediaSessionState() - updateMediaSessionMetadata() + updateNotification() } override fun onIsPlayingChanged(isPlaying: Boolean) { @@ -121,8 +184,7 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac putExtra("isPlaying", isPlaying) } this@ZenPlayer.applicationContext.sendBroadcast(broadcast) - updateMediaSessionState() - updateMediaSessionMetadata() + updateNotification() } override fun onPlayerError(error: PlaybackException) { @@ -140,93 +202,6 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac } } - private val mediaSessionCallback = object : MediaSessionCompat.Callback() { - override fun onPlay() { - super.onPlay() - onBroadcastPausePlay() - } - - override fun onPause() { - super.onPause() - onBroadcastPausePlay() - } - - override fun onSkipToNext() { - super.onSkipToNext() - onBroadcastNext() - } - - override fun onSkipToPrevious() { - super.onSkipToPrevious() - onBroadcastPrevious() - } - - override fun onSeekTo(pos: Long) { - super.onSeekTo(pos) - exoPlayer.seekTo(pos) - updateMediaSessionState() - updateMediaSessionMetadata() - } - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - broadcastReceiver = ZenBroadcastReceiver() - mediaSession = MediaSessionCompat(this, MEDIA_SESSION) - systemNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - isRunning.set(true) - queueService.addListener(this) - if (intent == null) Toast.makeText(this, "null intent", Toast.LENGTH_SHORT).show() - intent?.let { - val locations = it.getStringArrayExtra("locations") ?: return@let - val startPosition = it.getIntExtra("startPosition", 0) - if (locations.isEmpty()) return@let - val mediaItems = locations.map { location -> - MediaItem.Builder().apply { - setUri(Uri.fromFile(File(location))) - setTag(location) - }.build() - } - setQueue(mediaItems, startPosition) - } - - IntentFilter(Constants.PACKAGE_NAME).also { - registerReceiver(broadcastReceiver, it) - } - broadcastReceiver?.startListening(this) - mediaSession.setCallback(mediaSessionCallback) - exoPlayer.addListener(exoPlayerListener) - exoPlayer.addAnalyticsListener(playbackStatsListener) - - startForeground( - ZenNotificationManager.PLAYER_NOTIFICATION_ID, - notificationManager.getPlayerNotification( - session = mediaSession, - showPlayButton = false, - isLiked = queueService.getSongAtIndex(exoPlayer.currentMediaItemIndex)?.favourite ?: false - ) - ) - - scope.launch { - preferencesProvider.playbackParams.collect { - updateMediaSessionState() - val params = it.toCorrectedParams().toExoPlayerPlaybackParameters() - withContext(Dispatchers.Main){ - exoPlayer.playbackParameters = params - } - } - } - scope.launch { - queueService.repeatMode.collect { - withContext(Dispatchers.Main) { exoPlayer.repeatMode = it.toExoPlayerRepeatMode() } - } - } - - val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager - audioManager.isSpeakerphoneOn = true - - return START_NOT_STICKY - } - override fun onDestroy() { super.onDestroy() stopService() @@ -245,7 +220,7 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac exoPlayer.clearMediaItems() exoPlayer.removeListener(exoPlayerListener) exoPlayer.removeAnalyticsListener(playbackStatsListener) - unregisterReceiver(broadcastReceiver) + broadcastReceiver?.let { unregisterReceiver(it) } mediaSession.release() broadcastReceiver?.stopListening() systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) @@ -263,33 +238,8 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac applicationContext.sendBroadcast(broadcast) } - private fun updateMediaSessionMetadata() { + private fun updateNotification() { scope.launch { - var currentSong: Song? = null - withContext(Dispatchers.Main) { - currentSong = queueService.getSongAtIndex(exoPlayer.currentMediaItemIndex) - } - if (currentSong == null) return@launch - mediaSession.setMetadata( - MediaMetadataCompat.Builder().apply { - putString( - MediaMetadataCompat.METADATA_KEY_TITLE, - currentSong!!.title - ) - putString( - MediaMetadataCompat.METADATA_KEY_ARTIST, - currentSong!!.artist - ) - putString( - MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, - currentSong!!.artUri - ) - putLong( - MediaMetadataCompat.METADATA_KEY_DURATION, - currentSong!!.durationMillis - ) - }.build() - ) delay(100) withContext(Dispatchers.Main) { systemNotificationManager?.notify( @@ -297,37 +247,14 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac notificationManager.getPlayerNotification( session = mediaSession, showPlayButton = !exoPlayer.isPlaying, - isLiked = queueService.getSongAtIndex(exoPlayer.currentMediaItemIndex)?.favourite ?: false + isLiked = queueService.getSongAtIndex(exoPlayer.currentMediaItemIndex) + ?.favourite ?: false ) ) } } } - private fun updateMediaSessionState() { - scope.launch { - delay(100) - val speed = preferencesProvider.playbackParams.value.playbackSpeed - withContext(Dispatchers.Main) { - mediaSession.setPlaybackState( - PlaybackStateCompat.Builder().apply { - setState( - if (exoPlayer.isPlaying) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED, - exoPlayer.currentPosition, - if (speed < 1 || speed > 200) 1f else speed.toFloat()/100, - ) - setActions( - (if (exoPlayer.isPlaying) PlaybackStateCompat.ACTION_PAUSE else PlaybackStateCompat.ACTION_PLAY) - or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - or PlaybackStateCompat.ACTION_SKIP_TO_NEXT - or PlaybackStateCompat.ACTION_SEEK_TO - ) - }.build() - ) - } - } - } - private fun setQueue(mediaItems: List, startPosition: Int){ scope.launch { val repeatMode = queueService.repeatMode.first() @@ -343,28 +270,17 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac .toExoPlayerPlaybackParameters() exoPlayer.play() } - updateMediaSessionState() - updateMediaSessionMetadata() + updateNotification() } } override fun onAppend(song: Song) { - exoPlayer.addMediaItem( - MediaItem.Builder().apply { - setUri(Uri.fromFile(File(song.location))) - setTag(song.location) - }.build() - ) + exoPlayer.addMediaItem(song.toMediaItem()) } override fun onAppend(songs: List) { exoPlayer.addMediaItems( - songs.map { - MediaItem.Builder().apply { - setUri(Uri.fromFile(File(it.location))) - setTag(it.location) - }.build() - } + songs.map(Song::toMediaItem) ) } @@ -374,8 +290,7 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac exoPlayer.currentMediaItemIndex == position } if (!performUpdate) return@launch - updateMediaSessionState() - updateMediaSessionMetadata() + updateNotification() } } @@ -388,12 +303,7 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac } override fun onSetQueue(songs: List, startPlayingFromPosition: Int) { - val mediaItems = songs.map { - MediaItem.Builder().apply { - setUri(Uri.fromFile(File(it.location))) - setTag(it.location) - }.build() - } + val mediaItems = songs.map(Song::toMediaItem) setQueue(mediaItems, startPlayingFromPosition) } @@ -455,4 +365,21 @@ class ZenPlayer : Service(), QueueService.Listener, ZenBroadcastReceiver.Callbac // stopForeground(true) stopSelf() } + +} + +fun Song.toMediaItem(): MediaItem { + return MediaItem.Builder().apply { + setUri(Uri.fromFile(File(this@toMediaItem.location))) + setTag(this@toMediaItem.location) + setMediaMetadata( + MediaMetadata.Builder().apply { + setArtworkUri(this@toMediaItem.artUri?.toUri()) + setTitle(this@toMediaItem.title) + setArtist(this@toMediaItem.artist) + setIsBrowsable(false) + setIsPlayable(true) + }.build() + ) + }.build() } \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/search/SearchViewModel.kt b/app/src/main/java/com/github/pakka_papad/search/SearchViewModel.kt index d003b4b..b9b8dde 100644 --- a/app/src/main/java/com/github/pakka_papad/search/SearchViewModel.kt +++ b/app/src/main/java/com/github/pakka_papad/search/SearchViewModel.kt @@ -78,8 +78,10 @@ class SearchViewModel @Inject constructor( fun setQueue(songs: List?, startPlayingFromIndex: Int = 0) { if (songs == null) return - queueService.setQueue(songs, startPlayingFromIndex) - playerService.startServiceIfNotRunning(songs, startPlayingFromIndex) +// queueService.setQueue(songs, startPlayingFromIndex) + viewModelScope.launch { + playerService.startServiceIfNotRunning(songs, startPlayingFromIndex) + } showMessage(messageStore.getString(R.string.playing)) } diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index a406745..61a897d 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -40,7 +40,7 @@ object Versions { const val timber = "5.0.1" const val exoPlayer = "2.18.1" - const val media3 = "1.0.0-beta02" + const val media3 = "1.2.1" const val coilCompose = "2.2.2" const val palette = "1.0.0" @@ -119,6 +119,7 @@ object Libraries { const val exoPlayer = "com.google.android.exoplayer:exoplayer:${Versions.exoPlayer}" const val media3ExoPlayer = "androidx.media3:media3-exoplayer:${Versions.media3}" const val media3Transformer = "androidx.media3:media3-transformer:${Versions.media3}" + const val media3Session = "androidx.media3:media3-session:${Versions.media3}" const val exoPlayerUi = "com.google.android.exoplayer:exoplayer-ui:${Versions.exoPlayer}" const val coilCompose = "io.coil-kt:coil-compose:${Versions.coilCompose}" From dd683b7275c85af31d4272e552aaf0427f825793 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:22:59 +0530 Subject: [PATCH 11/25] Fixed: ZenPlayer onDestroy not called --- .../github/pakka_papad/player/ZenPlayer.kt | 51 +++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index ffe9d26..aa4b442 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -1,5 +1,6 @@ package com.github.pakka_papad.player +import android.annotation.SuppressLint import android.app.NotificationManager import android.appwidget.AppWidgetManager import android.content.Context @@ -7,6 +8,7 @@ import android.content.Intent import android.content.IntentFilter import android.media.AudioManager import android.net.Uri +import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.core.net.toUri @@ -95,6 +97,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece private lateinit var mediaSession: MediaSession + @SuppressLint("UnspecifiedRegisterReceiverFlag") override fun onCreate() { super.onCreate() broadcastReceiver = ZenBroadcastReceiver() @@ -104,7 +107,11 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece queueService.addListener(this) IntentFilter(Constants.PACKAGE_NAME).also { - registerReceiver(broadcastReceiver, it) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + registerReceiver(broadcastReceiver, it, RECEIVER_NOT_EXPORTED) + } else { + registerReceiver(broadcastReceiver, it) + } } broadcastReceiver?.startListening(this) exoPlayer.addListener(exoPlayerListener) @@ -134,11 +141,12 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece actionFactory: MediaNotification.ActionFactory, onNotificationChangedCallback: MediaNotification.Provider.Callback ): MediaNotification { + Timber.d("MediaNotification.Provider.createNotification()") return MediaNotification( ZenNotificationManager.PLAYER_NOTIFICATION_ID, notificationManager.getPlayerNotification( session = mediaSession, - showPlayButton = false, + showPlayButton = !mediaSession.player.isPlaying, isLiked = queueService.getSongAtIndex(exoPlayer.currentMediaItemIndex) ?.favourite ?: false ) @@ -150,7 +158,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece action: String, extras: Bundle ): Boolean { - return false + return true } }) } @@ -174,7 +182,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece } catch (e: Exception) { Timber.e(e) } - updateNotification() +// updateNotification() } override fun onIsPlayingChanged(isPlaying: Boolean) { @@ -184,7 +192,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece putExtra("isPlaying", isPlaying) } this@ZenPlayer.applicationContext.sendBroadcast(broadcast) - updateNotification() +// updateNotification() } override fun onPlayerError(error: PlaybackException) { @@ -204,6 +212,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece override fun onDestroy() { super.onDestroy() + Timber.d("onDestroy") stopService() } @@ -213,29 +222,33 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece private fun stopService() { isRunning.set(false) - queueService.clearQueue() - queueService.removeListener(this) + with(queueService) { + clearQueue() + removeListener(this@ZenPlayer) + } sleepTimerService.cancel() - exoPlayer.stop() - exoPlayer.clearMediaItems() - exoPlayer.removeListener(exoPlayerListener) - exoPlayer.removeAnalyticsListener(playbackStatsListener) + with(exoPlayer) { + stop() + clearMediaItems() + removeAnalyticsListener(playbackStatsListener) + removeListener(exoPlayerListener) + } broadcastReceiver?.let { unregisterReceiver(it) } - mediaSession.release() broadcastReceiver?.stopListening() systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) scope.cancel() job.cancel() systemNotificationManager = null broadcastReceiver = null - val broadcast = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE).apply { + Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE).apply { putExtra(WidgetBroadcast.WIDGET_BROADCAST, WidgetBroadcast.SONG_CHANGED) putExtra("imageUri", "") putExtra("title", "") putExtra("artist", "") putExtra("album", "") + }.also { + applicationContext.sendBroadcast(it) } - applicationContext.sendBroadcast(broadcast) } private fun updateNotification() { @@ -270,7 +283,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece .toExoPlayerPlaybackParameters() exoPlayer.play() } - updateNotification() +// updateNotification() } } @@ -312,6 +325,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece * Player.Listener onIsPlayingChanged gets called. */ override fun onBroadcastPausePlay() { + Timber.d("onBroadcastPausePlay()") if (exoPlayer.isPlaying) exoPlayer.pause() else exoPlayer.play() } @@ -321,6 +335,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece * Player.Listener onMediaItemTransition gets called. */ override fun onBroadcastNext() { + Timber.d("onBroadcastNext()") if (!exoPlayer.hasNextMediaItem()) { showToast("No next song in queue") return @@ -334,6 +349,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece * Player.Listener onMediaItemTransition gets called. */ override fun onBroadcastPrevious() { + Timber.d("onBroadcastPrevious()") if (!exoPlayer.hasPreviousMediaItem()) { showToast("No previous song in queue") return @@ -347,10 +363,10 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece * DataManager then calls updateNotification of DataManager.Callback */ override fun onBroadcastLike() { + Timber.d("onBroadcastLike()") val currentSong = queueService.getSongAtIndex(exoPlayer.currentMediaItemIndex) ?: return val updatedSong = currentSong.copy(favourite = !currentSong.favourite) scope.launch { -// onUpdateCurrentSong() queueService.update(updatedSong) songService.updateSong(updatedSong) } @@ -363,6 +379,9 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece override fun onBroadcastCancel() { // Deprecated in api level 33 // stopForeground(true) + Timber.d("onBroadcastCancel()") + // https://github.com/androidx/media/issues/389#issuecomment-1546611545 + mediaSession.release() stopSelf() } From d5ed701599539427189535091551906f5e7bead9 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:35:34 +0530 Subject: [PATCH 12/25] Removed: AudioManager.isSpeakerPhoneOn --- app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index aa4b442..60f084b 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -6,7 +6,6 @@ import android.appwidget.AppWidgetManager import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.media.AudioManager import android.net.Uri import android.os.Build import android.os.Bundle @@ -131,9 +130,6 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece } } - val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager - audioManager.isSpeakerphoneOn = true - setMediaNotificationProvider(object : MediaNotification.Provider { override fun createNotification( mediaSession: MediaSession, From 2241319b65d101c0b37b78fa4eb94fc0ca033a02 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:00:06 +0530 Subject: [PATCH 13/25] Fixed: Tests --- .../collection/CollectionViewModelTest.kt | 28 +++++++++++-------- .../pakka_papad/search/SearchViewModelTest.kt | 14 ++++++---- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/app/src/test/java/com/github/pakka_papad/collection/CollectionViewModelTest.kt b/app/src/test/java/com/github/pakka_papad/collection/CollectionViewModelTest.kt index 51175bc..65b0f78 100644 --- a/app/src/test/java/com/github/pakka_papad/collection/CollectionViewModelTest.kt +++ b/app/src/test/java/com/github/pakka_papad/collection/CollectionViewModelTest.kt @@ -12,12 +12,13 @@ import com.github.pakka_papad.data.services.PlaylistService import com.github.pakka_papad.data.services.QueueService import com.github.pakka_papad.data.services.SongService import com.github.pakka_papad.util.MessageStore +import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.coVerifyOrder import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.verify -import io.mockk.verifyOrder import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect @@ -40,8 +41,8 @@ class CollectionViewModelTest { private val messageStore: MessageStore = mockk() private val playlistService: PlaylistService = mockk(relaxed = true) private val songService: SongService = mockk(relaxed = true) - private val playerService: PlayerService = mockk(relaxed = true) private val queueService: QueueService = mockk(relaxed = true) + private val playerService: PlayerService = mockk() private lateinit var viewModel: CollectionViewModel private val artistName = "Some artist" @@ -68,6 +69,9 @@ class CollectionViewModelTest { every { queueService.queue } returns queueList every { messageStore.getString(any()) } returns mockMessage every { messageStore.getString(allAny()) } returns mockMessage + coEvery { playerService.startServiceIfNotRunning(any(), any()) } answers { + queueService.setQueue(firstArg(), secondArg()) + } viewModel = CollectionViewModel( messageStore = messageStore, playlistService = playlistService, @@ -125,13 +129,13 @@ class CollectionViewModelTest { viewModel.setQueue(artistWithSongs.songs, 0) // Then - verify(exactly = 1) { + coVerify(exactly = 1) { queueService.setQueue(artistWithSongs.songs, 0) playerService.startServiceIfNotRunning(artistWithSongs.songs, 0) } - verifyOrder { - queueService.setQueue(artistWithSongs.songs, 0) + coVerifyOrder { playerService.startServiceIfNotRunning(artistWithSongs.songs, 0) + queueService.setQueue(artistWithSongs.songs, 0) } } @@ -146,13 +150,13 @@ class CollectionViewModelTest { viewModel.addToQueue(mockSong) // Then - verify(exactly = 1) { - queueService.setQueue(listOf(mockSong), 0) + coVerify(exactly = 1) { playerService.startServiceIfNotRunning(listOf(mockSong), 0) - } - verifyOrder { queueService.setQueue(listOf(mockSong), 0) + } + coVerifyOrder { playerService.startServiceIfNotRunning(listOf(mockSong), 0) + queueService.setQueue(listOf(mockSong), 0) } } @@ -202,13 +206,13 @@ class CollectionViewModelTest { viewModel.addToQueue(mockSongs) // Then - verify(exactly = 1) { + coVerify(exactly = 1) { queueService.setQueue(mockSongs, 0) playerService.startServiceIfNotRunning(mockSongs, 0) } - verifyOrder { - queueService.setQueue(mockSongs, 0) + coVerifyOrder { playerService.startServiceIfNotRunning(mockSongs, 0) + queueService.setQueue(mockSongs, 0) } } diff --git a/app/src/test/java/com/github/pakka_papad/search/SearchViewModelTest.kt b/app/src/test/java/com/github/pakka_papad/search/SearchViewModelTest.kt index abafa4c..6500209 100644 --- a/app/src/test/java/com/github/pakka_papad/search/SearchViewModelTest.kt +++ b/app/src/test/java/com/github/pakka_papad/search/SearchViewModelTest.kt @@ -8,10 +8,10 @@ import com.github.pakka_papad.data.services.QueueService import com.github.pakka_papad.data.services.SearchService import com.github.pakka_papad.util.MessageStore import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.coVerifyOrder import io.mockk.every import io.mockk.mockk -import io.mockk.verify -import io.mockk.verifyOrder import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch @@ -54,6 +54,9 @@ class SearchViewModelTest { } coEvery { searchService.searchSongs(query) } returns songs coEvery { searchService.searchAlbums(query) } returns albums + coEvery { playerService.startServiceIfNotRunning(any(), any()) } answers { + queueService.setQueue(firstArg(), secondArg()) + } viewModel = SearchViewModel( messageStore = messageStore, playerService = playerService, @@ -103,19 +106,18 @@ class SearchViewModelTest { // Given startCollection() every { queueService.setQueue(songs, 0) } returns Unit - every { playerService.startServiceIfNotRunning(songs, 0) } returns Unit // When viewModel.setQueue(songs, 0) // Then - verify(exactly = 1) { + coVerify(exactly = 1) { queueService.setQueue(songs, 0) playerService.startServiceIfNotRunning(songs, 0) } - verifyOrder { - queueService.setQueue(songs, 0) + coVerifyOrder { playerService.startServiceIfNotRunning(songs, 0) + queueService.setQueue(songs, 0) } } } \ No newline at end of file From 9fb3d42eb3d2d494f1a055eff24a6809caa82099 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Fri, 26 Jan 2024 19:19:21 +0530 Subject: [PATCH 14/25] Updated: Not releasing MediaSession --- .../github/pakka_papad/player/ZenPlayer.kt | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index 60f084b..d1cf78a 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -229,6 +229,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece removeAnalyticsListener(playbackStatsListener) removeListener(exoPlayerListener) } + mediaSession.release() broadcastReceiver?.let { unregisterReceiver(it) } broadcastReceiver?.stopListening() systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) @@ -236,15 +237,6 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece job.cancel() systemNotificationManager = null broadcastReceiver = null - Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE).apply { - putExtra(WidgetBroadcast.WIDGET_BROADCAST, WidgetBroadcast.SONG_CHANGED) - putExtra("imageUri", "") - putExtra("title", "") - putExtra("artist", "") - putExtra("album", "") - }.also { - applicationContext.sendBroadcast(it) - } } private fun updateNotification() { @@ -373,12 +365,23 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece * This stops the service and onDestroy is called */ override fun onBroadcastCancel() { - // Deprecated in api level 33 -// stopForeground(true) Timber.d("onBroadcastCancel()") - // https://github.com/androidx/media/issues/389#issuecomment-1546611545 - mediaSession.release() - stopSelf() + /** + * To close the media session, first call mediaSession.release followed by stopSelf() + * See issue: https://github.com/androidx/media/issues/389#issuecomment-1546611545 + */ +// mediaSession.release() +// stopSelf() + + /** + * Not releasing media session on cancel click + * Instead we pause the player and remove the notification + */ + exoPlayer.pause() + scope.launch { + delay(100) + systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) + } } } From ec93ff342dd9915a62fa34383698dfd605e8e5e9 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Fri, 26 Jan 2024 22:52:05 +0530 Subject: [PATCH 15/25] Added: Playback Resumption --- .idea/gradle.xml | 11 +++ app/src/main/AndroidManifest.xml | 7 ++ .../java/com/github/pakka_papad/Constants.kt | 1 + .../pakka_papad/data/QueueStateSerializer.kt | 19 ++++++ .../github/pakka_papad/data/daos/SongDao.kt | 18 ++++- .../pakka_papad/data/services/SongService.kt | 5 ++ .../com/github/pakka_papad/di/AppModule.kt | 13 ++++ .../pakka_papad/player/SessionCallback.kt | 60 +++++++++++++++++ .../github/pakka_papad/player/ZenPlayer.kt | 67 +++++++++++++------ .../github/pakka_papad/data/QueueState.proto | 10 +++ 10 files changed, 189 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/github/pakka_papad/data/QueueStateSerializer.kt create mode 100644 app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt create mode 100644 app/src/main/proto/com/github/pakka_papad/data/QueueState.proto diff --git a/.idea/gradle.xml b/.idea/gradle.xml index add82e5..c733d0b 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,6 +4,17 @@ + + + + + + { + override val defaultValue: QueueState + get() = QueueState.getDefaultInstance() + + override suspend fun readFrom(input: InputStream): QueueState = + try { + QueueState.parseFrom(input) + } catch (_: Exception) { + defaultValue + } + + override suspend fun writeTo(t: QueueState, output: OutputStream) = t.writeTo(output) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/data/daos/SongDao.kt b/app/src/main/java/com/github/pakka_papad/data/daos/SongDao.kt index 05bb650..6b9cc7d 100644 --- a/app/src/main/java/com/github/pakka_papad/data/daos/SongDao.kt +++ b/app/src/main/java/com/github/pakka_papad/data/daos/SongDao.kt @@ -1,8 +1,19 @@ package com.github.pakka_papad.data.daos -import androidx.room.* +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update import com.github.pakka_papad.Constants -import com.github.pakka_papad.data.music.* +import com.github.pakka_papad.data.music.AlbumArtistWithSongCount +import com.github.pakka_papad.data.music.ArtistWithSongCount +import com.github.pakka_papad.data.music.ComposerWithSongCount +import com.github.pakka_papad.data.music.GenreWithSongCount +import com.github.pakka_papad.data.music.LyricistWithSongCount +import com.github.pakka_papad.data.music.Song import kotlinx.coroutines.flow.Flow @Dao @@ -61,4 +72,7 @@ interface SongDao { @Query("SELECT * FROM ${Constants.Tables.SONG_TABLE} WHERE favourite = 1") fun getAllFavourites(): Flow> + + @Query("SELECT * FROM ${Constants.Tables.SONG_TABLE} WHERE location IN (:locations)") + suspend fun getSongsFromLocations(locations: List): List } \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/data/services/SongService.kt b/app/src/main/java/com/github/pakka_papad/data/services/SongService.kt index fc844ab..df245f8 100644 --- a/app/src/main/java/com/github/pakka_papad/data/services/SongService.kt +++ b/app/src/main/java/com/github/pakka_papad/data/services/SongService.kt @@ -41,6 +41,7 @@ interface SongService { fun getFavouriteSongs(): Flow> suspend fun updateSong(song: Song) + suspend fun getSongsFromLocations(locations: List): List } class SongServiceImpl( @@ -104,4 +105,8 @@ class SongServiceImpl( override suspend fun updateSong(song: Song) { songDao.updateSong(song) } + + override suspend fun getSongsFromLocations(locations: List): List { + return songDao.getSongsFromLocations(locations) + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/di/AppModule.kt b/app/src/main/java/com/github/pakka_papad/di/AppModule.kt index 6b3ceff..21f8aea 100644 --- a/app/src/main/java/com/github/pakka_papad/di/AppModule.kt +++ b/app/src/main/java/com/github/pakka_papad/di/AppModule.kt @@ -91,6 +91,19 @@ object AppModule { ) } + @Singleton + @Provides + fun providesQueueStateDatastore( + @ApplicationContext context: Context, + ): DataStore { + return DataStoreFactory.create( + serializer = QueueStateSerializer, + produceFile = { + context.dataStoreFile(Constants.QUEUE_STATE_FILE) + } + ) + } + @Singleton @Provides fun providesCoroutineScope(): CoroutineScope { diff --git a/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt b/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt new file mode 100644 index 0000000..2ca87f4 --- /dev/null +++ b/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt @@ -0,0 +1,60 @@ +package com.github.pakka_papad.player + +import androidx.datastore.core.DataStore +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.MediaSession +import com.github.pakka_papad.data.QueueState +import com.github.pakka_papad.data.music.Song +import com.github.pakka_papad.data.services.QueueService +import com.github.pakka_papad.data.services.SongService +import com.google.common.util.concurrent.ListenableFuture +import com.google.common.util.concurrent.SettableFuture +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SessionCallback @Inject constructor( + private val queueService: QueueService, + private val songService: SongService, + private val scope: CoroutineScope, + private val queueState: DataStore, +): MediaSession.Callback { + + @UnstableApi + override fun onPlaybackResumption( + mediaSession: MediaSession, + controller: MediaSession.ControllerInfo + ): ListenableFuture { + val result = SettableFuture.create() + scope.launch { + val state = queueState.data.first() + val songs = songService.getSongsFromLocations(state.locationsList) + val locationMap = buildMap { + for (song in songs) { + put(song.location, song) + } + } + val orderedSongs = buildList { + for (location in state.locationsList) { + if (locationMap.containsKey(location)) { + add(locationMap[location]!!) + } + } + } + queueService.clearQueue() + queueService.setQueue(orderedSongs, state.startIndex) + result.set( + MediaSession.MediaItemsWithStartPosition( + orderedSongs.map(Song::toMediaItem), + state.startIndex, + state.startPositionMs + ) + ) + } + return result + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index d1cf78a..37ea3dd 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -11,6 +11,7 @@ import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.core.net.toUri +import androidx.datastore.core.DataStore import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata import androidx.media3.common.PlaybackException @@ -24,8 +25,10 @@ import androidx.media3.session.MediaNotification import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService import com.github.pakka_papad.Constants +import com.github.pakka_papad.data.QueueState import com.github.pakka_papad.data.ZenCrashReporter import com.github.pakka_papad.data.ZenPreferenceProvider +import com.github.pakka_papad.data.copy import com.github.pakka_papad.data.music.Song import com.github.pakka_papad.data.music.SongExtractor import com.github.pakka_papad.data.notification.ZenNotificationManager @@ -67,6 +70,8 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece @Inject lateinit var exoPlayer: ExoPlayer @Inject lateinit var crashReporter: ZenCrashReporter @Inject lateinit var preferencesProvider: ZenPreferenceProvider + @Inject lateinit var queueState: DataStore + @Inject lateinit var sessionListener: SessionCallback private var broadcastReceiver: ZenBroadcastReceiver? = null @@ -100,7 +105,9 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece override fun onCreate() { super.onCreate() broadcastReceiver = ZenBroadcastReceiver() - mediaSession = MediaSession.Builder(applicationContext, exoPlayer).build() + mediaSession = MediaSession.Builder(applicationContext, exoPlayer) + .setCallback(sessionListener) + .build() systemNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager isRunning.set(true) queueService.addListener(this) @@ -218,23 +225,43 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece private fun stopService() { isRunning.set(false) - with(queueService) { - clearQueue() - removeListener(this@ZenPlayer) + + scope.launch { + queueState.updateData { + it.copy { + locations.apply { + clear() + addAll(queueService.queue.map { song -> song.location }) + } + startIndex = withContext(Dispatchers.Main) { exoPlayer.currentMediaItemIndex } + startPositionMs = withContext(Dispatchers.Main) { exoPlayer.currentPosition } + } + } + withContext(Dispatchers.Main) { + with(exoPlayer) { + stop() + clearMediaItems() + removeAnalyticsListener(playbackStatsListener) + removeListener(exoPlayerListener) + } + showToast("Saved") + } + }.invokeOnCompletion { + with(queueService) { + clearQueue() + removeListener(this@ZenPlayer) + } + scope.cancel() + job.cancel() } + sleepTimerService.cancel() - with(exoPlayer) { - stop() - clearMediaItems() - removeAnalyticsListener(playbackStatsListener) - removeListener(exoPlayerListener) - } - mediaSession.release() + +// mediaSession.release() broadcastReceiver?.let { unregisterReceiver(it) } broadcastReceiver?.stopListening() systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) - scope.cancel() - job.cancel() + systemNotificationManager = null broadcastReceiver = null } @@ -370,18 +397,18 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece * To close the media session, first call mediaSession.release followed by stopSelf() * See issue: https://github.com/androidx/media/issues/389#issuecomment-1546611545 */ -// mediaSession.release() -// stopSelf() + mediaSession.release() + stopSelf() /** * Not releasing media session on cancel click * Instead we pause the player and remove the notification */ - exoPlayer.pause() - scope.launch { - delay(100) - systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) - } +// exoPlayer.pause() +// scope.launch { +// delay(100) +// systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) +// } } } diff --git a/app/src/main/proto/com/github/pakka_papad/data/QueueState.proto b/app/src/main/proto/com/github/pakka_papad/data/QueueState.proto new file mode 100644 index 0000000..b120557 --- /dev/null +++ b/app/src/main/proto/com/github/pakka_papad/data/QueueState.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +option java_package = "com.github.pakka_papad.data"; +option java_multiple_files = true; + +message QueueState { + repeated string locations = 1; + int32 startIndex = 2; + int64 startPositionMs = 3; +} \ No newline at end of file From 362a88b80c53ee6bf5fb59d7704ba83d374d338c Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Fri, 26 Jan 2024 23:15:07 +0530 Subject: [PATCH 16/25] Fixed: SessionCallback memory leak --- .../com/github/pakka_papad/player/SessionCallback.kt | 5 +---- .../java/com/github/pakka_papad/player/ZenPlayer.kt | 12 +++++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt b/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt index 2ca87f4..13ab931 100644 --- a/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt +++ b/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt @@ -12,11 +12,8 @@ import com.google.common.util.concurrent.SettableFuture import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import javax.inject.Inject -import javax.inject.Singleton -@Singleton -class SessionCallback @Inject constructor( +class SessionCallback ( private val queueService: QueueService, private val songService: SongService, private val scope: CoroutineScope, diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index 37ea3dd..d9f43de 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -71,10 +71,9 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece @Inject lateinit var crashReporter: ZenCrashReporter @Inject lateinit var preferencesProvider: ZenPreferenceProvider @Inject lateinit var queueState: DataStore - @Inject lateinit var sessionListener: SessionCallback + private var sessionListener: SessionCallback? = null private var broadcastReceiver: ZenBroadcastReceiver? = null - private var systemNotificationManager: NotificationManager? = null private val job = SupervisorJob() @@ -105,8 +104,14 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece override fun onCreate() { super.onCreate() broadcastReceiver = ZenBroadcastReceiver() + sessionListener = SessionCallback( + queueService = queueService, + songService = songService, + scope = scope, + queueState = queueState, + ) mediaSession = MediaSession.Builder(applicationContext, exoPlayer) - .setCallback(sessionListener) + .setCallback(sessionListener!!) .build() systemNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager isRunning.set(true) @@ -258,6 +263,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece sleepTimerService.cancel() // mediaSession.release() + sessionListener = null broadcastReceiver?.let { unregisterReceiver(it) } broadcastReceiver?.stopListening() systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) From 9f85920cd7e652c69ba3eb1e9304521f0b585c76 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:00:57 +0530 Subject: [PATCH 17/25] Updated: SessionCallback is now service scoped --- .../pakka_papad/data/QueueStateProvider.kt | 32 ++++++++++ .../pakka_papad/player/SessionCallback.kt | 5 +- .../github/pakka_papad/player/ZenPlayer.kt | 61 ++++++------------- 3 files changed, 56 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/com/github/pakka_papad/data/QueueStateProvider.kt diff --git a/app/src/main/java/com/github/pakka_papad/data/QueueStateProvider.kt b/app/src/main/java/com/github/pakka_papad/data/QueueStateProvider.kt new file mode 100644 index 0000000..c07d639 --- /dev/null +++ b/app/src/main/java/com/github/pakka_papad/data/QueueStateProvider.kt @@ -0,0 +1,32 @@ +package com.github.pakka_papad.data + +import androidx.datastore.core.DataStore +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class QueueStateProvider @Inject constructor( + private val queueState: DataStore, + private val coroutineScope: CoroutineScope, +) { + val state: Flow + get() = queueState.data + + fun saveState(queue: List, startIndex: Int, startPosition: Long) { + coroutineScope.launch { + queueState.updateData { + it.copy { + this.locations.apply { + clear() + addAll(queue) + } + this.startIndex = startIndex + this.startPositionMs = startPosition + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt b/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt index 13ab931..6b111f5 100644 --- a/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt +++ b/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt @@ -9,11 +9,14 @@ import com.github.pakka_papad.data.services.QueueService import com.github.pakka_papad.data.services.SongService import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.SettableFuture +import dagger.hilt.android.scopes.ServiceScoped import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import javax.inject.Inject -class SessionCallback ( +@ServiceScoped +class SessionCallback @Inject constructor( private val queueService: QueueService, private val songService: SongService, private val scope: CoroutineScope, diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index d9f43de..2c90856 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -11,7 +11,6 @@ import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.core.net.toUri -import androidx.datastore.core.DataStore import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata import androidx.media3.common.PlaybackException @@ -25,10 +24,9 @@ import androidx.media3.session.MediaNotification import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService import com.github.pakka_papad.Constants -import com.github.pakka_papad.data.QueueState +import com.github.pakka_papad.data.QueueStateProvider import com.github.pakka_papad.data.ZenCrashReporter import com.github.pakka_papad.data.ZenPreferenceProvider -import com.github.pakka_papad.data.copy import com.github.pakka_papad.data.music.Song import com.github.pakka_papad.data.music.SongExtractor import com.github.pakka_papad.data.notification.ZenNotificationManager @@ -70,9 +68,9 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece @Inject lateinit var exoPlayer: ExoPlayer @Inject lateinit var crashReporter: ZenCrashReporter @Inject lateinit var preferencesProvider: ZenPreferenceProvider - @Inject lateinit var queueState: DataStore + @Inject lateinit var queueStateProvider: QueueStateProvider + @Inject lateinit var sessionCallback: SessionCallback - private var sessionListener: SessionCallback? = null private var broadcastReceiver: ZenBroadcastReceiver? = null private var systemNotificationManager: NotificationManager? = null @@ -104,14 +102,8 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece override fun onCreate() { super.onCreate() broadcastReceiver = ZenBroadcastReceiver() - sessionListener = SessionCallback( - queueService = queueService, - songService = songService, - scope = scope, - queueState = queueState, - ) mediaSession = MediaSession.Builder(applicationContext, exoPlayer) - .setCallback(sessionListener!!) + .setCallback(sessionCallback) .build() systemNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager isRunning.set(true) @@ -230,40 +222,27 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece private fun stopService() { isRunning.set(false) - - scope.launch { - queueState.updateData { - it.copy { - locations.apply { - clear() - addAll(queueService.queue.map { song -> song.location }) - } - startIndex = withContext(Dispatchers.Main) { exoPlayer.currentMediaItemIndex } - startPositionMs = withContext(Dispatchers.Main) { exoPlayer.currentPosition } - } - } - withContext(Dispatchers.Main) { - with(exoPlayer) { - stop() - clearMediaItems() - removeAnalyticsListener(playbackStatsListener) - removeListener(exoPlayerListener) - } - showToast("Saved") - } - }.invokeOnCompletion { - with(queueService) { - clearQueue() - removeListener(this@ZenPlayer) - } - scope.cancel() - job.cancel() + queueStateProvider.saveState( + queue = queueService.queue.map { it.location }, + startIndex = exoPlayer.currentMediaItemIndex, + startPosition = exoPlayer.currentPosition + ) + with(queueService) { + clearQueue() + removeListener(this@ZenPlayer) + } + with(exoPlayer) { + stop() + clearMediaItems() + removeAnalyticsListener(playbackStatsListener) + removeListener(exoPlayerListener) } + scope.cancel() + job.cancel() sleepTimerService.cancel() // mediaSession.release() - sessionListener = null broadcastReceiver?.let { unregisterReceiver(it) } broadcastReceiver?.stopListening() systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) From 35c28ca90111ef7593f616fff99dc3d18e13a58d Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Sat, 27 Jan 2024 22:24:45 +0530 Subject: [PATCH 18/25] Added: Custom layout for notification --- .../notification/ZenNotificationManager.kt | 194 ------------------ .../com/github/pakka_papad/di/AppModule.kt | 7 - .../player/NotificationProvider.kt | 173 ++++++++++++++++ .../pakka_papad/player/SessionCallback.kt | 32 +++ .../pakka_papad/player/ZenCommandButtons.kt | 65 ++++++ .../github/pakka_papad/player/ZenCommands.kt | 7 + .../github/pakka_papad/player/ZenPlayer.kt | 67 ++---- 7 files changed, 291 insertions(+), 254 deletions(-) delete mode 100644 app/src/main/java/com/github/pakka_papad/data/notification/ZenNotificationManager.kt create mode 100644 app/src/main/java/com/github/pakka_papad/player/NotificationProvider.kt create mode 100644 app/src/main/java/com/github/pakka_papad/player/ZenCommandButtons.kt create mode 100644 app/src/main/java/com/github/pakka_papad/player/ZenCommands.kt diff --git a/app/src/main/java/com/github/pakka_papad/data/notification/ZenNotificationManager.kt b/app/src/main/java/com/github/pakka_papad/data/notification/ZenNotificationManager.kt deleted file mode 100644 index 29dad28..0000000 --- a/app/src/main/java/com/github/pakka_papad/data/notification/ZenNotificationManager.kt +++ /dev/null @@ -1,194 +0,0 @@ -package com.github.pakka_papad.data.notification - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.os.Build -import androidx.annotation.OptIn -import androidx.annotation.RequiresApi -import androidx.core.app.NotificationCompat -import androidx.core.graphics.drawable.IconCompat -import androidx.media3.common.util.UnstableApi -import androidx.media3.session.MediaSession -import androidx.media3.session.MediaStyleNotificationHelper -import com.github.pakka_papad.Constants -import com.github.pakka_papad.MainActivity -import com.github.pakka_papad.R -import com.github.pakka_papad.player.ZenBroadcastReceiver - -class ZenNotificationManager( - val context: Context -) { - - init { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createNotificationChannels() - } - } - - companion object { -// const val RUNNING_SCAN = "running_scan" - const val PLAYER_SERVICE = "zen_player" -// const val SCANNING_NOTIFICATION_ID = 10 - const val PLAYER_NOTIFICATION_ID = 12 - } - - @RequiresApi(Build.VERSION_CODES.O) - private fun createNotificationChannels() { - val requiredChannels = listOf( -// NotificationChannel( -// RUNNING_SCAN, -// "Scan", -// NotificationManager.IMPORTANCE_HIGH -// ), - NotificationChannel( - PLAYER_SERVICE, - "Player", - NotificationManager.IMPORTANCE_HIGH - ) - ) - val notificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - notificationManager.createNotificationChannels(requiredChannels) - } - -// fun sendScanningNotification() { -// val notification = NotificationCompat.Builder(context, RUNNING_SCAN).apply { -// setSmallIcon(R.mipmap.ic_notification) -// setContentTitle("Scanning") -// setContentText("Looking for music \uD83E\uDDD0") -// setOngoing(true) -// priority = NotificationCompat.PRIORITY_HIGH -// setProgress(0, 0, true) -// setSilent(true) -// }.build() -// with(NotificationManagerCompat.from(context)) { -// notify(SCANNING_NOTIFICATION_ID, notification) -// } -// } -// -// fun removeScanningNotification() { -// with(NotificationManagerCompat.from(context)) { -// cancel(SCANNING_NOTIFICATION_ID) -// } -// } - - private val previousAction = NotificationCompat.Action.Builder( - IconCompat.createWithResource(context,R.drawable.ic_baseline_skip_previous_40), - "Previous", - PendingIntent.getBroadcast( - context, ZenBroadcastReceiver.PREVIOUS_ACTION_REQUEST_CODE, - Intent(Constants.PACKAGE_NAME).putExtra( - ZenBroadcastReceiver.AUDIO_CONTROL, - ZenBroadcastReceiver.ZEN_PLAYER_PREVIOUS - ), - PendingIntent.FLAG_IMMUTABLE - ) - ).build() - private val nextAction = NotificationCompat.Action.Builder( - IconCompat.createWithResource(context,R.drawable.ic_baseline_skip_next_40), - "Next", - PendingIntent.getBroadcast( - context, ZenBroadcastReceiver.NEXT_ACTION_REQUEST_CODE, - Intent(Constants.PACKAGE_NAME).putExtra( - ZenBroadcastReceiver.AUDIO_CONTROL, - ZenBroadcastReceiver.ZEN_PLAYER_NEXT - ), - PendingIntent.FLAG_IMMUTABLE - ) - ).build() - private val pauseAction = NotificationCompat.Action.Builder( - IconCompat.createWithResource(context,R.drawable.ic_baseline_pause_40), - "Pause", - PendingIntent.getBroadcast( - context, ZenBroadcastReceiver.PAUSE_PLAY_ACTION_REQUEST_CODE, - Intent(Constants.PACKAGE_NAME).putExtra( - ZenBroadcastReceiver.AUDIO_CONTROL, - ZenBroadcastReceiver.ZEN_PLAYER_PAUSE_PLAY - ), - PendingIntent.FLAG_IMMUTABLE - ) - ).build() - private val playAction = NotificationCompat.Action.Builder( - IconCompat.createWithResource(context,R.drawable.ic_baseline_play_arrow_40), - "Play", - PendingIntent.getBroadcast( - context, ZenBroadcastReceiver.PAUSE_PLAY_ACTION_REQUEST_CODE, - Intent(Constants.PACKAGE_NAME).putExtra( - ZenBroadcastReceiver.AUDIO_CONTROL, - ZenBroadcastReceiver.ZEN_PLAYER_PAUSE_PLAY - ), - PendingIntent.FLAG_IMMUTABLE - ) - ).build() - private val outlinedLikeAction = NotificationCompat.Action.Builder( - IconCompat.createWithResource(context,R.drawable.ic_baseline_favorite_border_24), - "Like", - PendingIntent.getBroadcast( - context, ZenBroadcastReceiver.LIKE_ACTION_REQUEST_CODE, - Intent(Constants.PACKAGE_NAME).putExtra( - ZenBroadcastReceiver.AUDIO_CONTROL, - ZenBroadcastReceiver.ZEN_PLAYER_LIKE - ), - PendingIntent.FLAG_IMMUTABLE - ) - ).build() - private val filledLikeAction = NotificationCompat.Action.Builder( - IconCompat.createWithResource(context,R.drawable.ic_baseline_favorite_24), - "Unlike", - PendingIntent.getBroadcast( - context, ZenBroadcastReceiver.LIKE_ACTION_REQUEST_CODE, - Intent(Constants.PACKAGE_NAME).putExtra( - ZenBroadcastReceiver.AUDIO_CONTROL, - ZenBroadcastReceiver.ZEN_PLAYER_LIKE - ), - PendingIntent.FLAG_IMMUTABLE - ) - ).build() - private val cancelAction = NotificationCompat.Action.Builder( - IconCompat.createWithResource(context,R.drawable.ic_baseline_close_30), - "Close", - PendingIntent.getBroadcast( - context, ZenBroadcastReceiver.CANCEL_ACTION_REQUEST_CODE, - Intent(Constants.PACKAGE_NAME).putExtra( - ZenBroadcastReceiver.AUDIO_CONTROL, - ZenBroadcastReceiver.ZEN_PLAYER_CANCEL - ), - PendingIntent.FLAG_IMMUTABLE - ) - ).build() - private val activityIntent = PendingIntent.getActivity( - context, - 0, - Intent(context,MainActivity::class.java), - PendingIntent.FLAG_IMMUTABLE - ) - - @OptIn(UnstableApi::class) - fun getPlayerNotification( - session: MediaSession, - showPlayButton: Boolean, - isLiked: Boolean, - ): Notification { - val mediaStyle = MediaStyleNotificationHelper.MediaStyle(session) - .setShowActionsInCompactView(1,2,3) - return NotificationCompat.Builder(context, PLAYER_SERVICE).apply { - setSmallIcon(R.mipmap.ic_notification) - setContentTitle("Now Playing") - setContentText("") - setOngoing(true) - priority = NotificationCompat.PRIORITY_MAX - setSilent(true) - setStyle(mediaStyle) - addAction(if (isLiked) filledLikeAction else outlinedLikeAction) - addAction(previousAction) - addAction(if (showPlayButton) playAction else pauseAction) - addAction(nextAction) - addAction(cancelAction) - setContentIntent(activityIntent) // use with android:launchMode="singleTask" in manifest - }.build() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/di/AppModule.kt b/app/src/main/java/com/github/pakka_papad/di/AppModule.kt index 21f8aea..8eb7d12 100644 --- a/app/src/main/java/com/github/pakka_papad/di/AppModule.kt +++ b/app/src/main/java/com/github/pakka_papad/di/AppModule.kt @@ -18,7 +18,6 @@ import androidx.room.Room import com.github.pakka_papad.Constants import com.github.pakka_papad.data.* import com.github.pakka_papad.data.music.SongExtractor -import com.github.pakka_papad.data.notification.ZenNotificationManager import com.github.pakka_papad.data.services.* import com.github.pakka_papad.player.ZenBroadcastReceiver import com.github.pakka_papad.util.MessageStore @@ -51,12 +50,6 @@ object AppModule { ).build() } - @Singleton - @Provides - fun providesNotificationManager(@ApplicationContext context: Context): ZenNotificationManager { - return ZenNotificationManager(context) - } - @SuppressLint("UnsafeOptInUsageError") @Singleton @Provides diff --git a/app/src/main/java/com/github/pakka_papad/player/NotificationProvider.kt b/app/src/main/java/com/github/pakka_papad/player/NotificationProvider.kt new file mode 100644 index 0000000..f255c3a --- /dev/null +++ b/app/src/main/java/com/github/pakka_papad/player/NotificationProvider.kt @@ -0,0 +1,173 @@ +package com.github.pakka_papad.player + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import androidx.core.graphics.drawable.IconCompat +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.CommandButton +import androidx.media3.session.MediaNotification +import androidx.media3.session.MediaSession +import androidx.media3.session.MediaStyleNotificationHelper +import com.github.pakka_papad.Constants +import com.github.pakka_papad.MainActivity +import com.github.pakka_papad.R +import com.github.pakka_papad.data.services.QueueService +import com.google.common.collect.ImmutableList +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.android.scopes.ServiceScoped +import timber.log.Timber +import javax.inject.Inject + +@ServiceScoped +@UnstableApi +class NotificationProvider @Inject constructor( + @ApplicationContext private val context: Context, + private val queueService: QueueService, +) : MediaNotification.Provider { + + init { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannels() + } + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createNotificationChannels() { + val requiredChannels = listOf( + NotificationChannel( + PLAYER_SERVICE_NOTIFICATION_CHANNEL_ID, + "Player", + NotificationManager.IMPORTANCE_HIGH + ) + ) + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannels(requiredChannels) + } + + private val activityIntent by lazy { + PendingIntent.getActivity( + context, + 0, + Intent(context, MainActivity::class.java), + PendingIntent.FLAG_IMMUTABLE + ) + } + + companion object { + const val PLAYER_NOTIFICATION_ID = 20 + const val PLAYER_SERVICE_NOTIFICATION_CHANNEL_ID = "zen_player" + } + + override fun createNotification( + mediaSession: MediaSession, + customLayout: ImmutableList, + actionFactory: MediaNotification.ActionFactory, + onNotificationChangedCallback: MediaNotification.Provider.Callback + ): MediaNotification { + Timber.d("createNotification()") + val mediaStyle = MediaStyleNotificationHelper.MediaStyle(mediaSession) + .setShowActionsInCompactView(1,2,3) + + val builder = NotificationCompat.Builder(context, PLAYER_SERVICE_NOTIFICATION_CHANNEL_ID) + .apply { + setSmallIcon(R.mipmap.ic_notification) + setContentTitle(mediaSession.player.currentMediaItem?.mediaMetadata?.title) + setOngoing(true) + priority = NotificationCompat.PRIORITY_MAX + setSilent(true) + setStyle(mediaStyle) + setContentIntent(activityIntent) + } + + /** + * queueService.currentSong.value may not have the value correctly updated immediately + */ + val isLiked = queueService.getSongAtIndex(mediaSession.player.currentMediaItemIndex) + ?.favourite ?: false + + customLayout.mapIndexed { index, commandButton -> + when(index) { + 0 -> { // Like or Unlike button + actionFactory.createCustomActionFromCustomCommandButton( + mediaSession, + if (isLiked) ZenCommandButtons.liked else ZenCommandButtons.unliked + ) + } + 1, 3 -> { // Previous and Next buttons + actionFactory.createMediaAction( + mediaSession, + IconCompat.createWithResource(context, commandButton.iconResId), + commandButton.displayName, + commandButton.playerCommand + ) + } + 2 -> { // Play or Pause button + actionFactory.createMediaAction( + mediaSession, + IconCompat.createWithResource( + context, + if (mediaSession.player.isPlaying) R.drawable.ic_baseline_pause_40 + else R.drawable.ic_baseline_play_arrow_40 + ), + if (mediaSession.player.isPlaying) "Pause" + else "Play", + commandButton.playerCommand + ) + } + 4 -> { // Close button + actionFactory + .createCustomActionFromCustomCommandButton(mediaSession, commandButton) + } + else -> { + null + } + } + }.forEach { builder.addAction(it) } + + return MediaNotification(PLAYER_NOTIFICATION_ID, builder.build()) + } + + private val closeAction = PendingIntent.getBroadcast( + context, ZenBroadcastReceiver.CANCEL_ACTION_REQUEST_CODE, + Intent(Constants.PACKAGE_NAME).putExtra( + ZenBroadcastReceiver.AUDIO_CONTROL, + ZenBroadcastReceiver.ZEN_PLAYER_CANCEL + ), + PendingIntent.FLAG_IMMUTABLE + ) + + private val likeUnlikeAction = PendingIntent.getBroadcast( + context, ZenBroadcastReceiver.LIKE_ACTION_REQUEST_CODE, + Intent(Constants.PACKAGE_NAME).putExtra( + ZenBroadcastReceiver.AUDIO_CONTROL, + ZenBroadcastReceiver.ZEN_PLAYER_LIKE + ), + PendingIntent.FLAG_IMMUTABLE + ) + + override fun handleCustomCommand( + session: MediaSession, + action: String, + extras: Bundle + ): Boolean { + when(action) { + ZenCommands.LIKE, ZenCommands.UNLIKE -> { + likeUnlikeAction.send() + return true + } + ZenCommands.CLOSE -> { + closeAction.send() + return true + } + } + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt b/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt index 6b111f5..035327a 100644 --- a/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt +++ b/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt @@ -13,6 +13,7 @@ import dagger.hilt.android.scopes.ServiceScoped import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @ServiceScoped @@ -23,6 +24,37 @@ class SessionCallback @Inject constructor( private val queueState: DataStore, ): MediaSession.Callback { + override fun onConnect( + session: MediaSession, + controller: MediaSession.ControllerInfo + ): MediaSession.ConnectionResult { + val connectionResult = super.onConnect(session, controller) + val availableCommands = connectionResult.availableSessionCommands.buildUpon() + availableCommands.add(ZenCommandButtons.liked.sessionCommand!!) + availableCommands.add(ZenCommandButtons.unliked.sessionCommand!!) + availableCommands.add(ZenCommandButtons.cancel.sessionCommand!!) + return MediaSession.ConnectionResult.accept( + availableCommands.build(), + connectionResult.availablePlayerCommands + ) + } + + override fun onPostConnect(session: MediaSession, controller: MediaSession.ControllerInfo) { + super.onPostConnect(session, controller) + val isLiked = queueService.currentSong.value?.favourite ?: false + Timber.d("onPostConnect() -> ${session.player.currentMediaItem?.mediaMetadata?.title} isLiked: $isLiked") + session.setCustomLayout( + controller, + listOf( + if (isLiked) ZenCommandButtons.liked else ZenCommandButtons.unliked, + ZenCommandButtons.previous, + ZenCommandButtons.playPause, + ZenCommandButtons.next, + ZenCommandButtons.cancel + ) + ) + } + @UnstableApi override fun onPlaybackResumption( mediaSession: MediaSession, diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenCommandButtons.kt b/app/src/main/java/com/github/pakka_papad/player/ZenCommandButtons.kt new file mode 100644 index 0000000..bf5051a --- /dev/null +++ b/app/src/main/java/com/github/pakka_papad/player/ZenCommandButtons.kt @@ -0,0 +1,65 @@ +package com.github.pakka_papad.player + +import android.os.Bundle +import androidx.media3.common.Player +import androidx.media3.session.CommandButton +import androidx.media3.session.SessionCommand +import com.github.pakka_papad.R + +object ZenCommandButtons { + + val liked by lazy { + CommandButton.Builder() + .apply { + setSessionCommand(SessionCommand(ZenCommands.UNLIKE, Bundle())) + setDisplayName("Unlike") + setIconResId(R.drawable.ic_baseline_favorite_24) + }.build() + } + + val unliked by lazy { + CommandButton.Builder() + .apply { + setSessionCommand(SessionCommand(ZenCommands.LIKE, Bundle())) + setDisplayName("Like") + setIconResId(R.drawable.ic_baseline_favorite_border_24) + }.build() + } + + val previous by lazy { + CommandButton.Builder() + .apply { + setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) + setDisplayName("Previous") + setIconResId(R.drawable.ic_baseline_skip_previous_40) + }.build() + } + + + val playPause by lazy { + CommandButton.Builder() + .apply { + setPlayerCommand(Player.COMMAND_PLAY_PAUSE) + setDisplayName("Previous") + setIconResId(R.drawable.ic_baseline_skip_previous_40) + }.build() + } + + val next by lazy { + CommandButton.Builder() + .apply { + setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT) + setDisplayName("Next") + setIconResId(R.drawable.ic_baseline_skip_next_40) + }.build() + } + + val cancel by lazy { + CommandButton.Builder() + .apply { + setSessionCommand(SessionCommand(ZenCommands.CLOSE, Bundle())) + setDisplayName("Close") + setIconResId(R.drawable.ic_baseline_close_40) + }.build() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenCommands.kt b/app/src/main/java/com/github/pakka_papad/player/ZenCommands.kt new file mode 100644 index 0000000..ddae818 --- /dev/null +++ b/app/src/main/java/com/github/pakka_papad/player/ZenCommands.kt @@ -0,0 +1,7 @@ +package com.github.pakka_papad.player + +object ZenCommands { + const val LIKE = "Like" + const val UNLIKE = "Unlike" + const val CLOSE = "Close" +} \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index 2c90856..11246f6 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -8,7 +8,6 @@ import android.content.Intent import android.content.IntentFilter import android.net.Uri import android.os.Build -import android.os.Bundle import android.widget.Toast import androidx.core.net.toUri import androidx.media3.common.MediaItem @@ -19,8 +18,6 @@ import androidx.media3.common.Timeline import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.analytics.PlaybackStatsListener -import androidx.media3.session.CommandButton -import androidx.media3.session.MediaNotification import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService import com.github.pakka_papad.Constants @@ -29,7 +26,6 @@ import com.github.pakka_papad.data.ZenCrashReporter import com.github.pakka_papad.data.ZenPreferenceProvider import com.github.pakka_papad.data.music.Song import com.github.pakka_papad.data.music.SongExtractor -import com.github.pakka_papad.data.notification.ZenNotificationManager import com.github.pakka_papad.data.services.AnalyticsService import com.github.pakka_papad.data.services.QueueService import com.github.pakka_papad.data.services.SleepTimerService @@ -37,13 +33,11 @@ import com.github.pakka_papad.data.services.SongService import com.github.pakka_papad.toCorrectedParams import com.github.pakka_papad.toExoPlayerPlaybackParameters import com.github.pakka_papad.widgets.WidgetBroadcast -import com.google.common.collect.ImmutableList import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -59,7 +53,6 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession - @Inject lateinit var notificationManager: ZenNotificationManager @Inject lateinit var songExtractor: SongExtractor @Inject lateinit var queueService: QueueService @Inject lateinit var songService: SongService @@ -70,6 +63,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece @Inject lateinit var preferencesProvider: ZenPreferenceProvider @Inject lateinit var queueStateProvider: QueueStateProvider @Inject lateinit var sessionCallback: SessionCallback + @Inject lateinit var notificationProvider: NotificationProvider private var broadcastReceiver: ZenBroadcastReceiver? = null private var systemNotificationManager: NotificationManager? = null @@ -105,6 +99,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece mediaSession = MediaSession.Builder(applicationContext, exoPlayer) .setCallback(sessionCallback) .build() + systemNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager isRunning.set(true) queueService.addListener(this) @@ -134,33 +129,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece } } - setMediaNotificationProvider(object : MediaNotification.Provider { - override fun createNotification( - mediaSession: MediaSession, - customLayout: ImmutableList, - actionFactory: MediaNotification.ActionFactory, - onNotificationChangedCallback: MediaNotification.Provider.Callback - ): MediaNotification { - Timber.d("MediaNotification.Provider.createNotification()") - return MediaNotification( - ZenNotificationManager.PLAYER_NOTIFICATION_ID, - notificationManager.getPlayerNotification( - session = mediaSession, - showPlayButton = !mediaSession.player.isPlaying, - isLiked = queueService.getSongAtIndex(exoPlayer.currentMediaItemIndex) - ?.favourite ?: false - ) - ) - } - - override fun handleCustomCommand( - session: MediaSession, - action: String, - extras: Bundle - ): Boolean { - return true - } - }) + setMediaNotificationProvider(notificationProvider) } private val exoPlayerListener = object : Player.Listener { @@ -245,29 +214,12 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece // mediaSession.release() broadcastReceiver?.let { unregisterReceiver(it) } broadcastReceiver?.stopListening() - systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) + systemNotificationManager?.cancel(NotificationProvider.PLAYER_NOTIFICATION_ID) systemNotificationManager = null broadcastReceiver = null } - private fun updateNotification() { - scope.launch { - delay(100) - withContext(Dispatchers.Main) { - systemNotificationManager?.notify( - ZenNotificationManager.PLAYER_NOTIFICATION_ID, - notificationManager.getPlayerNotification( - session = mediaSession, - showPlayButton = !exoPlayer.isPlaying, - isLiked = queueService.getSongAtIndex(exoPlayer.currentMediaItemIndex) - ?.favourite ?: false - ) - ) - } - } - } - private fun setQueue(mediaItems: List, startPosition: Int){ scope.launch { val repeatMode = queueService.repeatMode.first() @@ -303,7 +255,16 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece exoPlayer.currentMediaItemIndex == position } if (!performUpdate) return@launch - updateNotification() + mediaSession.setCustomLayout( + listOf( + if (updatedSong.favourite) ZenCommandButtons.liked + else ZenCommandButtons.unliked, + ZenCommandButtons.previous, + ZenCommandButtons.playPause, + ZenCommandButtons.next, + ZenCommandButtons.cancel, + ) + ) } } From 9b443f1b07339c7608efc10ba76bd3878fa1d11e Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Sat, 27 Jan 2024 23:50:04 +0530 Subject: [PATCH 19/25] Fixed: Custom actions not working and Inconsistent notification state on Android 13 and above --- .../player/NotificationProvider.kt | 37 ++------------- .../pakka_papad/player/SessionCallback.kt | 46 +++++++++++++++++++ .../github/pakka_papad/player/ZenPlayer.kt | 26 ++++++----- 3 files changed, 65 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/github/pakka_papad/player/NotificationProvider.kt b/app/src/main/java/com/github/pakka_papad/player/NotificationProvider.kt index f255c3a..c335ba4 100644 --- a/app/src/main/java/com/github/pakka_papad/player/NotificationProvider.kt +++ b/app/src/main/java/com/github/pakka_papad/player/NotificationProvider.kt @@ -15,7 +15,6 @@ import androidx.media3.session.CommandButton import androidx.media3.session.MediaNotification import androidx.media3.session.MediaSession import androidx.media3.session.MediaStyleNotificationHelper -import com.github.pakka_papad.Constants import com.github.pakka_papad.MainActivity import com.github.pakka_papad.R import com.github.pakka_papad.data.services.QueueService @@ -72,7 +71,6 @@ class NotificationProvider @Inject constructor( actionFactory: MediaNotification.ActionFactory, onNotificationChangedCallback: MediaNotification.Provider.Callback ): MediaNotification { - Timber.d("createNotification()") val mediaStyle = MediaStyleNotificationHelper.MediaStyle(mediaSession) .setShowActionsInCompactView(1,2,3) @@ -80,6 +78,7 @@ class NotificationProvider @Inject constructor( .apply { setSmallIcon(R.mipmap.ic_notification) setContentTitle(mediaSession.player.currentMediaItem?.mediaMetadata?.title) + setContentText(mediaSession.player.currentMediaItem?.mediaMetadata?.artist) setOngoing(true) priority = NotificationCompat.PRIORITY_MAX setSilent(true) @@ -93,6 +92,8 @@ class NotificationProvider @Inject constructor( val isLiked = queueService.getSongAtIndex(mediaSession.player.currentMediaItemIndex) ?.favourite ?: false + Timber.d("createNotification() ${mediaSession.player.currentMediaItem?.mediaMetadata?.title} isLiked: $isLiked") + customLayout.mapIndexed { index, commandButton -> when(index) { 0 -> { // Like or Unlike button @@ -135,39 +136,9 @@ class NotificationProvider @Inject constructor( return MediaNotification(PLAYER_NOTIFICATION_ID, builder.build()) } - private val closeAction = PendingIntent.getBroadcast( - context, ZenBroadcastReceiver.CANCEL_ACTION_REQUEST_CODE, - Intent(Constants.PACKAGE_NAME).putExtra( - ZenBroadcastReceiver.AUDIO_CONTROL, - ZenBroadcastReceiver.ZEN_PLAYER_CANCEL - ), - PendingIntent.FLAG_IMMUTABLE - ) - - private val likeUnlikeAction = PendingIntent.getBroadcast( - context, ZenBroadcastReceiver.LIKE_ACTION_REQUEST_CODE, - Intent(Constants.PACKAGE_NAME).putExtra( - ZenBroadcastReceiver.AUDIO_CONTROL, - ZenBroadcastReceiver.ZEN_PLAYER_LIKE - ), - PendingIntent.FLAG_IMMUTABLE - ) - override fun handleCustomCommand( session: MediaSession, action: String, extras: Bundle - ): Boolean { - when(action) { - ZenCommands.LIKE, ZenCommands.UNLIKE -> { - likeUnlikeAction.send() - return true - } - ZenCommands.CLOSE -> { - closeAction.send() - return true - } - } - return false - } + ): Boolean = false } \ No newline at end of file diff --git a/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt b/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt index 035327a..75c4fdb 100644 --- a/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt +++ b/app/src/main/java/com/github/pakka_papad/player/SessionCallback.kt @@ -1,14 +1,22 @@ package com.github.pakka_papad.player +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Bundle import androidx.datastore.core.DataStore import androidx.media3.common.util.UnstableApi import androidx.media3.session.MediaSession +import androidx.media3.session.SessionCommand +import androidx.media3.session.SessionResult +import com.github.pakka_papad.Constants import com.github.pakka_papad.data.QueueState import com.github.pakka_papad.data.music.Song import com.github.pakka_papad.data.services.QueueService import com.github.pakka_papad.data.services.SongService import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.SettableFuture +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.scopes.ServiceScoped import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first @@ -18,6 +26,7 @@ import javax.inject.Inject @ServiceScoped class SessionCallback @Inject constructor( + @ApplicationContext context: Context, private val queueService: QueueService, private val songService: SongService, private val scope: CoroutineScope, @@ -55,6 +64,43 @@ class SessionCallback @Inject constructor( ) } + private val closeAction = PendingIntent.getBroadcast( + context, ZenBroadcastReceiver.CANCEL_ACTION_REQUEST_CODE, + Intent(Constants.PACKAGE_NAME).putExtra( + ZenBroadcastReceiver.AUDIO_CONTROL, + ZenBroadcastReceiver.ZEN_PLAYER_CANCEL + ), + PendingIntent.FLAG_IMMUTABLE + ) + + private val likeUnlikeAction = PendingIntent.getBroadcast( + context, ZenBroadcastReceiver.LIKE_ACTION_REQUEST_CODE, + Intent(Constants.PACKAGE_NAME).putExtra( + ZenBroadcastReceiver.AUDIO_CONTROL, + ZenBroadcastReceiver.ZEN_PLAYER_LIKE + ), + PendingIntent.FLAG_IMMUTABLE + ) + + override fun onCustomCommand( + session: MediaSession, + controller: MediaSession.ControllerInfo, + customCommand: SessionCommand, + args: Bundle + ): ListenableFuture { + val result = SettableFuture.create() + when(customCommand.customAction) { + ZenCommands.LIKE, ZenCommands.UNLIKE -> { + likeUnlikeAction.send() + } + ZenCommands.CLOSE -> { + closeAction.send() + } + } + result.set(SessionResult(SessionResult.RESULT_SUCCESS)) + return result + } + @UnstableApi override fun onPlaybackResumption( mediaSession: MediaSession, diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index 11246f6..133daec 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -95,13 +95,13 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece @SuppressLint("UnspecifiedRegisterReceiverFlag") override fun onCreate() { super.onCreate() + isRunning.set(true) broadcastReceiver = ZenBroadcastReceiver() mediaSession = MediaSession.Builder(applicationContext, exoPlayer) .setCallback(sessionCallback) .build() systemNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - isRunning.set(true) queueService.addListener(this) IntentFilter(Constants.PACKAGE_NAME).also { @@ -139,6 +139,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece try { queueService.setCurrentSong(exoPlayer.currentMediaItemIndex) queueService.getSongAtIndex(exoPlayer.currentMediaItemIndex)?.let { song -> + updateNotification(song.favourite) val broadcast = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE).apply { putExtra(WidgetBroadcast.WIDGET_BROADCAST, WidgetBroadcast.SONG_CHANGED) putExtra("imageUri", song.artUri) @@ -220,6 +221,18 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece broadcastReceiver = null } + private fun updateNotification(isLiked: Boolean) { + mediaSession.setCustomLayout( + listOf( + if (isLiked) ZenCommandButtons.liked else ZenCommandButtons.unliked, + ZenCommandButtons.previous, + ZenCommandButtons.playPause, + ZenCommandButtons.next, + ZenCommandButtons.cancel + ) + ) + } + private fun setQueue(mediaItems: List, startPosition: Int){ scope.launch { val repeatMode = queueService.repeatMode.first() @@ -255,16 +268,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece exoPlayer.currentMediaItemIndex == position } if (!performUpdate) return@launch - mediaSession.setCustomLayout( - listOf( - if (updatedSong.favourite) ZenCommandButtons.liked - else ZenCommandButtons.unliked, - ZenCommandButtons.previous, - ZenCommandButtons.playPause, - ZenCommandButtons.next, - ZenCommandButtons.cancel, - ) - ) + updateNotification(updatedSong.favourite) } } From 4ad0374f1601a031490863eceff0ef8f264749ac Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Sat, 27 Jan 2024 23:58:38 +0530 Subject: [PATCH 20/25] Updated: Code cleanup --- .../github/pakka_papad/player/ZenPlayer.kt | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index 133daec..cf5907f 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -1,9 +1,7 @@ package com.github.pakka_papad.player import android.annotation.SuppressLint -import android.app.NotificationManager import android.appwidget.AppWidgetManager -import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.Uri @@ -66,7 +64,6 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece @Inject lateinit var notificationProvider: NotificationProvider private var broadcastReceiver: ZenBroadcastReceiver? = null - private var systemNotificationManager: NotificationManager? = null private val job = SupervisorJob() private val scope = CoroutineScope(job + Dispatchers.Default) @@ -101,7 +98,6 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece .setCallback(sessionCallback) .build() - systemNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager queueService.addListener(this) IntentFilter(Constants.PACKAGE_NAME).also { @@ -152,7 +148,6 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece } catch (e: Exception) { Timber.e(e) } -// updateNotification() } override fun onIsPlayingChanged(isPlaying: Boolean) { @@ -162,7 +157,6 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece putExtra("isPlaying", isPlaying) } this@ZenPlayer.applicationContext.sendBroadcast(broadcast) -// updateNotification() } override fun onPlayerError(error: PlaybackException) { @@ -212,12 +206,8 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece sleepTimerService.cancel() -// mediaSession.release() broadcastReceiver?.let { unregisterReceiver(it) } broadcastReceiver?.stopListening() - systemNotificationManager?.cancel(NotificationProvider.PLAYER_NOTIFICATION_ID) - - systemNotificationManager = null broadcastReceiver = null } @@ -248,7 +238,6 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece .toExoPlayerPlaybackParameters() exoPlayer.play() } -// updateNotification() } } @@ -349,16 +338,6 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece */ mediaSession.release() stopSelf() - - /** - * Not releasing media session on cancel click - * Instead we pause the player and remove the notification - */ -// exoPlayer.pause() -// scope.launch { -// delay(100) -// systemNotificationManager?.cancel(ZenNotificationManager.PLAYER_NOTIFICATION_ID) -// } } } From bb13eb6472773a30cb33ac8bf61ae5b065f09497 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:05:34 +0530 Subject: [PATCH 21/25] Fixed: Avoiding multiple startService calls within small duration --- .../com/github/pakka_papad/data/services/PlayerService.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/com/github/pakka_papad/data/services/PlayerService.kt b/app/src/main/java/com/github/pakka_papad/data/services/PlayerService.kt index 6e18d4c..2d9b815 100644 --- a/app/src/main/java/com/github/pakka_papad/data/services/PlayerService.kt +++ b/app/src/main/java/com/github/pakka_papad/data/services/PlayerService.kt @@ -15,6 +15,7 @@ import com.github.pakka_papad.toExoPlayerPlaybackParameters import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext +import java.util.concurrent.atomic.AtomicLong interface PlayerService { suspend fun startServiceIfNotRunning(songs: List, startPlayingFromPosition: Int) @@ -26,9 +27,16 @@ class PlayerServiceImpl( private val preferenceProvider: ZenPreferenceProvider, ): PlayerService { + private val lastCallTime = AtomicLong(0) + @SuppressLint("RestrictedApi") @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) override suspend fun startServiceIfNotRunning(songs: List, startPlayingFromPosition: Int) { + synchronized(lastCallTime) { + if (lastCallTime.get() + 1000 >= System.currentTimeMillis()) return + lastCallTime.set(System.currentTimeMillis()) + } + queueService.setQueue(songs, startPlayingFromPosition) if (ZenPlayer.isRunning.get()) return MediaController.Builder( From e90453dfb0fdd075db5fdc5902c30857353577a7 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:09:23 +0530 Subject: [PATCH 22/25] Fixed: Blocking implicit intents for receivers which have not been exported on Android 14 --- app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt index cf5907f..888ab1c 100644 --- a/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt +++ b/app/src/main/java/com/github/pakka_papad/player/ZenPlayer.kt @@ -102,7 +102,7 @@ class ZenPlayer : MediaSessionService(), QueueService.Listener, ZenBroadcastRece IntentFilter(Constants.PACKAGE_NAME).also { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(broadcastReceiver, it, RECEIVER_NOT_EXPORTED) + registerReceiver(broadcastReceiver, it, RECEIVER_EXPORTED) } else { registerReceiver(broadcastReceiver, it) } From 3910991d6689094f69a585f4cd5625ab7fda1266 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:16:37 +0530 Subject: [PATCH 23/25] Updated: Version change --- buildSrc/src/main/kotlin/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 61a897d..5d91438 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -147,7 +147,7 @@ object AnnotationProcessors { object AppVersion { private const val Major = 1 private const val Minor = 2 - private const val Patch = 2 + private const val Patch = 3 const val Code = Major*10000 + Minor*100 + Patch const val Name = "$Major.$Minor.$Patch" } From 7d0550b77d4bc251cb8374aabb0a85a44b40aa3a Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:25:25 +0530 Subject: [PATCH 24/25] Updated: Changelogs --- app/src/main/assets/changelogs.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/assets/changelogs.json b/app/src/main/assets/changelogs.json index 1e8ae15..a8d1812 100644 --- a/app/src/main/assets/changelogs.json +++ b/app/src/main/assets/changelogs.json @@ -36,7 +36,7 @@ ] }, { - "versionCode": 102002, + "versionCode": 10202, "versionName": "1.2.2", "date": "6 January, 2024", "changes": [ @@ -44,5 +44,13 @@ "Updated player screen UI", "Updated playlist tab UI" ] + }, + { + "versionCode": 10203, + "versionName": "1.2.3", + "date": "29 January, 2024", + "changes": [ + "Added support for playback resumption" + ] } ] \ No newline at end of file From bcae2f30024de89438eb5edbf15c4b47e084cdb5 Mon Sep 17 00:00:00 2001 From: pakka-papad <76241334+pakka-papad@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:38:42 +0530 Subject: [PATCH 25/25] Updated: Changelogs --- app/src/main/assets/changelogs.json | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/assets/changelogs.json b/app/src/main/assets/changelogs.json index a8d1812..a980b92 100644 --- a/app/src/main/assets/changelogs.json +++ b/app/src/main/assets/changelogs.json @@ -50,6 +50,7 @@ "versionName": "1.2.3", "date": "29 January, 2024", "changes": [ + "Updated logo", "Added support for playback resumption" ] }