Compare commits

...

72 Commits
v1.0 ... master

Author SHA1 Message Date
Thomas Forest dc62f8f591 fix riff renaming 2023-01-06 18:10:02 +01:00
Thomas Forest eb8698a8e2 Riff v0.1 2022-12-16 17:27:38 +01:00
yattoz 24edc08bbf Merge branch 'devel' 2022-03-13 18:13:06 +09:00
yattoz a9109736be Merge branch 'update/api32' into devel 2022-03-13 18:13:02 +09:00
yattoz 355ac8cd01 permission for alarm schedule in Android 12 2022-03-13 13:05:07 +09:00
yattoz 185f0867e2 started updating intent calls. Crash when selecting day in alarm 2022-03-13 12:09:08 +09:00
yattoz 63d18856f1 updated many dependencies 2022-03-13 11:25:49 +09:00
yattoz c41ce1d550 updated planning for 2022 (cf https://tsumugi.forum-thalie.fr/?p=517 ) 2022-03-13 10:18:47 +09:00
yattoz c5c4bdd838 rollback change related to alarm playback, fix stutter when changing tab 2022-02-22 12:27:03 +09:00
yattoz 300a806d81 updated gradle 2022-02-22 11:55:10 +09:00
yattoz 7ea4d6ed67 Merge branch 'devel' 2022-02-19 15:08:38 +09:00
yattoz d238654842 version bump to 1.2.0 2022-02-19 15:08:29 +09:00
yattoz 25a906c79e Merge branch 'master' into devel 2022-02-19 14:35:55 +09:00
yattoz d22a10ec29 renamed classique, added runConfiguration? 2022-02-19 14:16:52 +09:00
yattoz acb57a9b35 fixed bug when images are not correctly specified by wordpress 2022-02-19 14:16:52 +09:00
yattoz e79a2f2be9 updated to JDK11, Android SDK 29 2022-02-19 14:16:52 +09:00
yattoz 7e4947cb83 avoid rotation in parameters 2022-02-19 14:15:30 +09:00
yattoz e3f1508b50 updated gradle, maybe fixed fallback issue 2022-02-19 14:15:30 +09:00
yattoz 0b7b5dd2f1 added slider for alarm volume 2022-02-19 14:15:29 +09:00
yattoz 2981be83de fixed bug when images are not correctly specified by wordpress 2022-02-19 14:00:07 +09:00
yattoz 37bc8867ab updated to JDK11, Android SDK 29 2022-02-19 13:59:43 +09:00
yattoz 99ab795320 added button to test and adjust alarm volume. 2020-08-05 11:49:20 +02:00
yattoz a180288d4f Merge branch 'common/bootbroadcast-permission-fix' into devel 2020-04-18 13:30:21 +02:00
yattoz b33510f81e Added missing permission for bootbroadcast alarm register 2020-04-18 13:29:19 +02:00
yattoz ef33f2090b Merge branch 'master' into upstream/devel 2020-04-18 13:24:54 +02:00
yattoz 77e7ccf114 Merge remote-tracking branch 'upstream/devel' 2020-04-18 13:24:24 +02:00
yattoz 134172b41e Merge branch 'master' 2020-04-14 00:31:41 +02:00
yattoz f28487b07c fixed bug in Planning displayed multiple times (not cleared in-between) 2020-04-14 00:31:18 +02:00
yattoz 6ca0569b26 Removed submodule ExoPlayer 2020-04-12 19:53:19 +02:00
yattoz f42c705bab Merge branch 'devel' 2020-04-12 19:49:28 +02:00
yattoz b77d9017a6 bump up version code 2020-04-12 19:49:18 +02:00
yattoz c8abee4e97 fixed DST for Paris 2020-04-02 15:44:01 +02:00
yattoz 3de813b524 version bump 2020-02-27 23:23:49 +01:00
yattoz f32bfd747c Parameters set their name in the Action Bar 2020-02-27 23:20:16 +01:00
yattoz f2f338f1d4 Fix : autoplay as setting, sensor to fullUser, close button, keep volume 2020-02-27 21:02:40 +01:00
yattoz 177acaddfe Use ExoPlayer 2.11.3 (no need for submodule anymore) 2020-02-27 20:47:25 +01:00
yattoz 8374a7888b Merge branch 'tsumugi/master' 2020-02-27 20:46:57 +01:00
yattoz 19fc1beab7 Merge branch 'tsumugi/devel' into devel 2020-02-27 20:46:46 +01:00
yattoz 9dff2f63d4 Merge branch 'tsumugi/devel' into tsumugi/master 2020-01-17 19:56:11 +01:00
yattoz 309b93ba51 version bump to 1.1.3 2020-01-17 19:53:51 +01:00
yattoz 8774558b51 tried to tweaked queue to be resilient even for ultra short jingles 2020-01-17 19:26:17 +01:00
yattoz 669e1cdc70 disabled latency compensator in RadioService 2020-01-17 17:47:07 +01:00
yattoz 06b4bfbc93 initialized planning at radio alarm. 2020-01-17 17:23:15 +01:00
yattoz 1fddd50b56 Merge branch 'tsumugi/devel' into tsumugi/master 2020-01-12 18:23:53 +01:00
yattoz 563c209165 version bump 112 2020-01-12 18:23:16 +01:00
yattoz d3fc4b495d fixed layout and reverted song copy to internal copy 2020-01-12 18:16:56 +01:00
yattoz e3d96aa843 Merge branch 'tsumugi/devel' into tsumugi/master 2020-01-12 14:55:45 +01:00
yattoz 157807b94d version bump 2020-01-12 14:55:34 +01:00
yattoz 1e5c72854a Merge branch 'tsumugi/devel' into tsumugi/master 2020-01-12 14:53:00 +01:00
yattoz 0b37a838e9 added Html decoding for UTF-8 chars decoded with ISO8859-1 by Airtime 2020-01-12 14:52:25 +01:00
yattoz a59d57a685 fixed layout, more readable (back to bigger fonts among other) 2020-01-12 14:24:40 +01:00
yattoz d03b06341a added refresh button, put font back to 16/20sp 2020-01-12 13:17:04 +01:00
yattoz 95e31272ff Merge branch 'tsumugi/master' into tsumugi/release 2020-01-12 12:44:22 +01:00
yattoz d92ae128fb Merge branch 'devel' into tsumugi/master 2020-01-12 12:44:16 +01:00
yattoz 4602911441 Merge branch 'devel' 2020-01-12 12:43:15 +01:00
yattoz 45ffa56fa9 tweaked appearance, snackbar opt-in, color blue changed, fixed API19- 2020-01-12 12:42:54 +01:00
yattoz ae1d1a1e44 Merge branch 'tsumugi/master' into tsumugi/release 2020-01-12 02:26:25 +01:00
yattoz 4769b2f0bf removed log calls and dupes comments 2020-01-12 02:22:29 +01:00
yattoz f133bea0ac merged api 2020-01-12 02:16:47 +01:00
yattoz 813dc66aa1 tweaked font size, should fix Fragment first selection. 2020-01-12 02:10:51 +01:00
yattoz 378fe32588 fixed color, fixed layout, removed todo, 2020-01-12 01:53:05 +01:00
yattoz 6a992458cc tweaked up next, added current programme, seems cool 2020-01-12 01:30:19 +01:00
yattoz c5d12c1742 adding up next 2020-01-12 00:29:20 +01:00
yattoz 912857c9cd added customization preference 2020-01-11 23:31:38 +01:00
yattoz c03a002448 added Airtime API and progress bar 2020-01-11 22:51:50 +01:00
yattoz 2f010fe698 Merge branch 'devel' into tsumugi/release 2019-12-31 12:32:29 +01:00
yattoz 6bf83a5a40 Merge branch 'devel' into tsumugi/master 2019-12-31 12:32:13 +01:00
yattoz 022fdde9ff Merge branch 'devel' 2019-12-31 12:32:03 +01:00
yattoz 1488d51388 added privacy 2019-12-31 12:31:49 +01:00
yattoz 9aa77b007e Changed launcher icon to a more modern cut 2019-12-31 10:28:59 +01:00
yattoz 3dfbf8cc9b Merge branch 'devel' 2019-12-31 00:38:00 +01:00
yattoz 4f32051da9 Merge branch 'devel' 2019-12-31 00:11:50 +01:00
131 changed files with 1604 additions and 683 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "ExoPlayer"]
path = ExoPlayer
url = https://github.com/google/ExoPlayer/

View File

@ -1 +0,0 @@
Tsumugi-app

View File

@ -1,8 +1,5 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -1,40 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Android Studio default JDK" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/av1" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/cast" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/cronet" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/ffmpeg" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/flac" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/gvr" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/ima" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/jobdispatcher" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/leanback" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/mediasession" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/okhttp" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/opus" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/rtmp" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/vp9" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/workmanager" />
<option value="$PROJECT_DIR$/ExoPlayer/library/all" />
<option value="$PROJECT_DIR$/ExoPlayer/library/core" />
<option value="$PROJECT_DIR$/ExoPlayer/library/dash" />
<option value="$PROJECT_DIR$/ExoPlayer/library/hls" />
<option value="$PROJECT_DIR$/ExoPlayer/library/smoothstreaming" />
<option value="$PROJECT_DIR$/ExoPlayer/library/ui" />
<option value="$PROJECT_DIR$/ExoPlayer/testutils" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="testRunner" value="PLATFORM" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

35
.idea/jarRepositories.xml Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component>
</project>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -2,6 +2,5 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/ExoPlayer" vcs="Git" />
</component> </component>
</project> </project>

@ -1 +0,0 @@
Subproject commit 24a19264dbf35e326bbd7bb36c232eb180e1e26d

12
PRIVACY.md Normal file
View File

@ -0,0 +1,12 @@
PRIVACY POLICY [ENGLISH]
This app does not gather nor upload any data from your device.
This app needs permission to read your phone state (android.permission.READ\_PHONE\_STATE) only and exclusively to cut down the volume of the radio when you receive a phone call. Please feel free to check the source code on GitHub at https://github.com/yattoz/webradio-app to assess this.
DECLARATION DE CONFIDENTIALITE [FRANCAIS]
Cette application ne récupère ni n'émet aucune donnée de votre appareil.
Cette application nécessite de lire l'état de votre téléphone (android.permission.READ\_PHONE\_STATE) uniquement et exclusivement pour couper le volume de la radio si vous recevez un appel téléphonique. Le code source est mis à votre disposition sur GitHub à l'adresse https://github.com/yattoz/webradio-app où vous pouvez vérifier ceci.

View File

@ -16,21 +16,21 @@ configurations.all {
} }
android { android {
compileSdkVersion 29 compileSdkVersion 32
buildToolsVersion "29.0.2"
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_11
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "11"
} }
defaultConfig { defaultConfig {
applicationId "fr.forum_thalie.tsumugi" applicationId "fr.riff_app.riff"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 32
versionCode 100 multiDexEnabled true
versionName "1.0.0" versionCode 121
versionName "1.2.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
@ -48,6 +48,9 @@ android {
} }
dependencies { dependencies {
def multidex_version = "2.0.1"
implementation "androidx.multidex:multidex:$multidex_version"
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
/* /*
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@ -68,15 +71,18 @@ dependencies {
*/ */
implementation "androidx.media:media:1.1.0" implementation "androidx.media:media:1.5.0"
//implementation 'com.google.android.exoplayer:exoplayer:2.11.1'
implementation project(':exoplayer-library') // uses local ExoPlayer clone, from dev-v2 branch (fixes ISO-8859-1 ICY metadata
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' //noinspection GradleDependency
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0' implementation 'com.google.android.exoplayer:exoplayer:2.11.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0' //implementation project(':exoplayer-library') // uses local ExoPlayer clone, from dev-v2 branch (fixes ISO-8859-1 ICY metadata
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.preference:preference-ktx:1.2.0'
// jsoup HTML parser library @ https://jsoup.org/ // jsoup HTML parser library @ https://jsoup.org/
// implementation 'org.jsoup:jsoup:1.12.1' // implementation 'org.jsoup:jsoup:1.12.1'

View File

@ -1,24 +0,0 @@
package fr.forum_thalie.tsumugi
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("fr.forum_thalie.tsumugi", appContext.packageName)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.84375"
android:scaleY="0.84375">
<path
android:strokeWidth="1"
android:pathData="M-22.4,-18.02h249.46v189.63h-249.46z"
android:fillColor="#000000"
android:strokeColor="#ffffff"/>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="fr.forum_thalie.tsumugi"> package="fr.riff_app.riff">
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@ -10,9 +10,11 @@
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- needed for API 28 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- needed for API 28 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SET_ALARM"/> <uses-permission android:name="android.permission.SET_ALARM"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
@ -21,27 +23,25 @@
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme.Launcher" android:theme="@style/AppTheme.Launcher"
tools:ignore="AllowBackup,GoogleAppIndexingWarning"> tools:ignore="AllowBackup,GoogleAppIndexingWarning"
android:name="androidx.multidex.MultiDexApplication">
<service <service
android:name=".RadioService" android:name=".RadioService"
android:enabled="true" android:enabled="true"
android:exported="true"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />
</intent-filter> </intent-filter>
</service> </service>
<service android:name=".streamerNotificationService.StreamerMonitorService"
android:enabled="true"
android:exported="true"/>
<receiver android:name=".BootBroadcastReceiver" <receiver android:name=".BootBroadcastReceiver"
android:directBootAware="true" android:directBootAware="true"
tools:targetApi="n"> tools:targetApi="n"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="fr.forum_thalie.tsumugi.PLAY_OR_FALLBACK" /> <action android:name="fr.riff_app.riff.PLAY_OR_FALLBACK" />
<action android:name="fr.forum_thalie.tsumugi.NOTIFY" /> <!-- this is for Snooze --> <action android:name="fr.riff_app.riff.NOTIFY" /> <!-- this is for Snooze -->
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
@ -49,9 +49,9 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask" android:launchMode="singleTask"
android:screenOrientation="fullSensor"> android:screenOrientation="fullUser"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -63,9 +63,10 @@
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:launchMode="singleTask" android:launchMode="singleTask"
android:parentActivityName=".MainActivity" android:parentActivityName=".MainActivity"
android:screenOrientation="fullSensor" /> android:screenOrientation="fullUser" />
<receiver android:name="androidx.media.session.MediaButtonReceiver"> <receiver android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>

View File

@ -1,66 +1,78 @@
{ {
"planning": [ "planning": [
{ {
"title": "Musique Classique", "title": "La playlist Base Rock",
"periodicity": "1111111", "periodicity": "1111111",
"hour_begin": "6:00", "hour_begin": "00:00",
"hour_end": "7:00"
},
{
"title": "Jeux Vidéos Matin",
"periodicity": "1111100",
"hour_begin": "09:00",
"hour_end": "10:30"
},
{
"title": "Anime Matin",
"periodicity": "1111100",
"hour_begin": "10:30",
"hour_end": "12:00"
},
{
"title": "Jeux Vidéos Aprem'",
"periodicity": "1111111",
"hour_begin": "14:00",
"hour_end": "15:30"
},
{
"title": "Anime Aprem'",
"periodicity": "1111111",
"hour_begin": "15:30",
"hour_end": "17:00"
},
{
"title": "Jeux Vidéos Soirée",
"periodicity": "1111100",
"hour_begin": "19:00",
"hour_end": "20:00"
},
{
"title": "Anime Soirée",
"periodicity": "1111100",
"hour_begin": "20:00",
"hour_end": "21:00" "hour_end": "21:00"
}, },
{ {
"title": "Anime Nuit", "title": "Only Covers",
"periodicity": "1111111", "periodicity": "1000000",
"hour_begin": "00:00", "hour_begin": "20:30",
"hour_end": "01:00" "hour_end": "00:00"
}, },
{ {
"title": "Jeux Vidéos Nuit", "title": "Only Indies",
"periodicity": "1111111", "periodicity": "0100000",
"hour_begin": "01:00", "hour_begin": "20:30",
"hour_end": "02:00" "hour_end": "00:00"
}, },
{ {
"title": "Programmation nocturne", "title": "Only Acoustic",
"periodicity": "1111111", "periodicity": "0010000",
"hour_begin": "02:00", "hour_begin": "20:30",
"hour_end": "06:00" "hour_end": "00:00"
},
{
"title": "Only Sweden",
"periodicity": "0001000",
"hour_begin": "20:30",
"hour_end": "00:00"
},
{
"title": "Only Lives",
"periodicity": "0000100",
"hour_begin": "20:30",
"hour_end": "00:00"
},
{
"title": "Only Games",
"periodicity": "0000010",
"hour_begin": "21:00",
"hour_end": "23:15"
},
{
"title": "Beyond [Azraël]",
"periodicity": "0100000",
"hour_begin": "21:00",
"hour_end": "23:00"
},
{
"title": "Piste Rouge [Louise et Trotsky]",
"periodicity": "0010000",
"hour_begin": "21:00",
"hour_end": "23:00"
},
{
"title": "Katamariff [Herondil & co.]",
"periodicity": "0000100",
"hour_begin": "21:00",
"hour_end": "23:00"
},
{
"title": "Le 4/4 Jazz [Melomaniaque]",
"periodicity": "0000001",
"hour_begin": "21:00",
"hour_end": "22:00"
},
{
"title": "Beyond, la playlist",
"periodicity": "0000001",
"hour_begin": "22:10",
"hour_end": "00:00"
} }
], ],
"regular_programme" : "Programmation habituelle", "regular_programme" : "Programmation habituelle",
"timezone": "GMT+1" "timezone": "Europe/Paris"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -1,11 +0,0 @@
package fr.forum_thalie.tsumugi
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import java.util.*
class Tick : TimerTask() {
override fun run() {
PlayerStore.instance.currentTime.postValue(PlayerStore.instance.currentTime.value!! + 500)
}
}

View File

@ -1,81 +0,0 @@
package fr.forum_thalie.tsumugi.playerstore
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import androidx.lifecycle.MutableLiveData
import fr.forum_thalie.tsumugi.*
class PlayerStore {
val isPlaying: MutableLiveData<Boolean> = MutableLiveData()
val isServiceStarted: MutableLiveData<Boolean> = MutableLiveData()
val volume: MutableLiveData<Int> = MutableLiveData()
val playbackState: MutableLiveData<Int> = MutableLiveData()
val currentTime: MutableLiveData<Long> = MutableLiveData()
val streamerPicture: MutableLiveData<Bitmap> = MutableLiveData()
val streamerName: MutableLiveData<String> = MutableLiveData()
val currentSong : Song = Song()
val currentSongBackup: Song = Song()
val lp : ArrayList<Song> = ArrayList()
val queue : ArrayList<Song> = ArrayList()
val isQueueUpdated: MutableLiveData<Boolean> = MutableLiveData()
val isLpUpdated: MutableLiveData<Boolean> = MutableLiveData()
val isMuted : MutableLiveData<Boolean> = MutableLiveData()
val listenersCount: MutableLiveData<Int> = MutableLiveData()
var latencyCompensator : Long = 0
var isInitialized: Boolean = false
init {
playbackState.value = PlaybackStateCompat.STATE_STOPPED
isPlaying.value = false
isServiceStarted.value = false
streamerName.value = ""
volume.value = preferenceStore.getInt("volume", 100)
currentTime.value = System.currentTimeMillis()
isQueueUpdated.value = false
isLpUpdated.value = false
isMuted.value = false
currentSong.title.value = noConnectionValue
currentSongBackup.title.value = noConnectionValue
listenersCount.value = 0
}
// ##################################################
// ############## QUEUE / LP FUNCTIONS ##############
// ##################################################
fun updateLp() {
// note : lp is empty at initialization. This check was needed when we used the R/a/dio API.
//if (lp.isNotEmpty()){
val n = Song()
n.copy(currentSongBackup)
if (n.title.value != noConnectionValue && n.title.value != streamDownValue)
lp.add(0, n)
currentSongBackup.copy(currentSong)
isLpUpdated.value = true
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + lp.toString())
//}
}
// ##################################################
// ############## PICTURE FUNCTIONS #################
// ##################################################
fun initPicture(c: Context) {
streamerPicture.value = BitmapFactory.decodeResource(c.resources,
R.drawable.logo_roundsquare
)
}
private val playerStoreTag = "====PlayerStore===="
companion object {
val instance by lazy {
PlayerStore()
}
}
}

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
enum class Actions enum class Actions
{ {

View File

@ -1,8 +1,8 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
import android.os.AsyncTask import android.os.AsyncTask
import android.util.Log import android.util.Log
import fr.forum_thalie.tsumugi.playerstore.PlayerStore import fr.riff_app.riff.playerstore.PlayerStore
class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {}, class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {},
private val actionOnError: ActionOnError = ActionOnError.RESET, private val parameters: Any? = null) : private val actionOnError: ActionOnError = ActionOnError.RESET, private val parameters: Any? = null) :
@ -13,7 +13,7 @@ class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {},
execute() execute()
} catch (e: Exception) } catch (e: Exception)
{ {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag,e.toString()) //[REMOVE LOG CALLS]Log.d(tag,e.toString())
} }
} }
@ -46,14 +46,14 @@ class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {},
} }
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "fallback for no network. Store reset : $storeReset") //[REMOVE LOG CALLS]Log.d(tag, "fallback for no network. Store reset : $storeReset")
} }
override fun doInBackground(vararg params: Any?): Any? { override fun doInBackground(vararg params: Any?): Any? {
try { try {
return handler(parameters) return handler(parameters)
} catch (e: Exception) { } catch (e: Exception) {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag,e.toString()) //[REMOVE LOG CALLS]Log.d(tag,e.toString())
onException(e) onException(e)
} }
return null return null
@ -63,7 +63,7 @@ class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {},
try { try {
post(result) post(result)
} catch (e: Exception) { } catch (e: Exception) {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag,e.toString()) //[REMOVE LOG CALLS]Log.d(tag,e.toString())
onException(e) onException(e)
} }
} }

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -21,7 +21,7 @@ abstract class BaseActivity : AppCompatActivity() {
val height = ((rootLayout?.height ?: 0)) val height = ((rootLayout?.height ?: 0))
val width = ((rootLayout?.width ?: 0)) val width = ((rootLayout?.width ?: 0))
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$viewWidth, $viewHeight, $width, $height, ${viewHeight.toDouble()/viewWidth.toDouble()}, ${height.toDouble()/width.toDouble()}") //[REMOVE LOG CALLS]Log.d(tag, "$viewWidth, $viewHeight, $width, $height, ${viewHeight.toDouble()/viewWidth.toDouble()}, ${height.toDouble()/width.toDouble()}")
val broadcastManager = LocalBroadcastManager.getInstance(this@BaseActivity) val broadcastManager = LocalBroadcastManager.getInstance(this@BaseActivity)
if(height <= viewHeight * 2 / 3 /*height.toDouble()/width.toDouble() < 1.20 */){ if(height <= viewHeight * 2 / 3 /*height.toDouble()/width.toDouble() < 1.20 */){
@ -49,14 +49,14 @@ abstract class BaseActivity : AppCompatActivity() {
// do things when keyboard is shown // do things when keyboard is shown
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav) val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.visibility = View.GONE bottomNavigationView.visibility = View.GONE
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to GONE (height $keyboardHeight)") //[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to GONE (height $keyboardHeight)")
} }
private fun onHideKeyboard() { private fun onHideKeyboard() {
// do things when keyboard is hidden // do things when keyboard is hidden
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav) val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.visibility = View.VISIBLE bottomNavigationView.visibility = View.VISIBLE
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to VISIBLE") //[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to VISIBLE")
} }
protected fun attachKeyboardListeners() { protected fun attachKeyboardListeners() {

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
@ -50,6 +50,7 @@ abstract class BaseNotification(private val notificationChannelId: String,
notificationManager.notify(notificationId, notification) notificationManager.notify(notificationId, notification)
} }
@RequiresApi(Build.VERSION_CODES.M)
open fun create(c: Context) { open fun create(c: Context) {
notificationManager = c.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager = c.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@ -58,7 +59,7 @@ abstract class BaseNotification(private val notificationChannelId: String,
// thanks to the launchMode specified in the Manifest : android:launchMode="singleTop" // thanks to the launchMode specified in the Manifest : android:launchMode="singleTop"
val pendingIntent = PendingIntent.getActivity( val pendingIntent = PendingIntent.getActivity(
c, 0, c, 0,
notificationIntent, 0 notificationIntent, PendingIntent.FLAG_IMMUTABLE
) )
var channelID = "" var channelID = ""

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
@ -6,13 +6,14 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import fr.forum_thalie.tsumugi.alarm.RadioAlarm import fr.riff_app.riff.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.playerstore.PlayerStore import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.playerstore.PlayerStore
class BootBroadcastReceiver : BroadcastReceiver(){ class BootBroadcastReceiver : BroadcastReceiver(){
override fun onReceive(context: Context, arg1: Intent) { override fun onReceive(context: Context, arg1: Intent) {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "Broadcast Receiver received $arg1") //[REMOVE LOG CALLS]Log.d(tag, "Broadcast Receiver received $arg1")
// define preferenceStore for places of the program that needs to access Preferences without a context // define preferenceStore for places of the program that needs to access Preferences without a context
preferenceStore = PreferenceManager.getDefaultSharedPreferences(context) preferenceStore = PreferenceManager.getDefaultSharedPreferences(context)
@ -22,7 +23,11 @@ class BootBroadcastReceiver : BroadcastReceiver(){
if (arg1.getStringExtra("action") == "$tag.${Actions.PLAY_OR_FALLBACK.name}" ) if (arg1.getStringExtra("action") == "$tag.${Actions.PLAY_OR_FALLBACK.name}" )
{ {
RadioAlarm.instance.setNextAlarm(context) // schedule next alarm RadioAlarm.instance.setNextAlarm(context) // schedule next alarm
Planning.instance.parseUrl(context = context)
if (!PlayerStore.instance.isInitialized)
PlayerStore.instance.initApi()
if (PlayerStore.instance.streamerName.value.isNullOrBlank()) if (PlayerStore.instance.streamerName.value.isNullOrBlank())
PlayerStore.instance.initPicture(context) PlayerStore.instance.initPicture(context)

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
import android.os.Bundle import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
@ -11,12 +11,13 @@ import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.navigation.NavController import androidx.navigation.NavController
import fr.forum_thalie.tsumugi.playerstore.PlayerStore import fr.riff_app.riff.playerstore.PlayerStore
import java.util.Timer import java.util.Timer
import android.view.MenuItem import android.view.MenuItem
import fr.forum_thalie.tsumugi.alarm.RadioAlarm import com.google.android.material.snackbar.Snackbar
import fr.forum_thalie.tsumugi.planning.Planning import fr.riff_app.riff.alarm.RadioAlarm
import fr.riff_app.riff.planning.Planning
/* Log to file import /* Log to file import
@ -86,6 +87,14 @@ class MainActivity : BaseActivity() {
true true
} }
*/ */
R.id.action_refresh -> {
PlayerStore.instance.queue.clear()
//PlayerStore.instance.lp.clear()
PlayerStore.instance.initApi()
val s = Snackbar.make(findViewById(R.id.nav_host_container), getString(R.string.refreshing) as CharSequence, Snackbar.LENGTH_LONG)
s.show()
true
}
R.id.action_settings -> { R.id.action_settings -> {
val i = Intent(this, ParametersActivity::class.java) val i = Intent(this, ParametersActivity::class.java)
startActivity(i) startActivity(i)
@ -108,7 +117,7 @@ class MainActivity : BaseActivity() {
} }
} }
override fun onRestoreInstanceState(savedInstanceState: Bundle?) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState ?: Bundle()) super.onRestoreInstanceState(savedInstanceState ?: Bundle())
// Now that BottomNavigationBar has restored its instance state // Now that BottomNavigationBar has restored its instance state
// and its selectedItemId, we can proceed with setting up the // and its selectedItemId, we can proceed with setting up the
@ -129,10 +138,16 @@ class MainActivity : BaseActivity() {
colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null)) colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null))
colorAccent = (ResourcesCompat.getColor(resources, R.color.colorAccent, null)) colorAccent = (ResourcesCompat.getColor(resources, R.color.colorAccent, null))
// fetch program
Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
PlayerStore.instance.initUrl(this)
PlayerStore.instance.initApi()
// Post-UI Launch // Post-UI Launch
if (PlayerStore.instance.isInitialized) if (PlayerStore.instance.isInitialized)
{ {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "skipped initialization") //[REMOVE LOG CALLS]Log.d(tag, "skipped initialization")
} else { } else {
// if the service is not started, start it in STOP mode. // if the service is not started, start it in STOP mode.
// It's not a dummy action : with STOP mode, the player does not buffer audio (and does not use data connection without the user's consent). // It's not a dummy action : with STOP mode, the player does not buffer audio (and does not use data connection without the user's consent).
@ -160,9 +175,6 @@ class MainActivity : BaseActivity() {
isTimerStarted = true isTimerStarted = true
} }
// fetch program
Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
// initialize the UI // initialize the UI
setTheme(R.style.AppTheme) setTheme(R.style.AppTheme)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
@ -193,7 +205,7 @@ class MainActivity : BaseActivity() {
val i = Intent(this, RadioService::class.java) val i = Intent(this, RadioService::class.java)
i.putExtra("action", a.name) i.putExtra("action", a.name)
i.putExtra("value", v) i.putExtra("value", v)
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "Sending intent ${a.name}") //[REMOVE LOG CALLS]Log.d(tag, "Sending intent ${a.name}")
startService(i) startService(i)
} }
@ -238,7 +250,7 @@ class MainActivity : BaseActivity() {
// File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder") // File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder")
val logDirectory = File("$appDirectory/log") val logDirectory = File("$appDirectory/log")
val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt") val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d( //[REMOVE LOG CALLS]Log.d(
tag, tag,
"appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile" "appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile"
) )
@ -246,20 +258,20 @@ class MainActivity : BaseActivity() {
// create app folder // create app folder
if (!appDirectory.exists()) { if (!appDirectory.exists()) {
appDirectory.mkdir() appDirectory.mkdir()
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created") //[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created")
} }
// create log folder // create log folder
if (!logDirectory.exists()) { if (!logDirectory.exists()) {
logDirectory.mkdir() logDirectory.mkdir()
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$logDirectory created") //[REMOVE LOG CALLS]Log.d(tag, "$logDirectory created")
} }
// clear the previous logcat and then write the new one to the file // clear the previous logcat and then write the new one to the file
try { try {
Runtime.getRuntime().exec("logcat -c") Runtime.getRuntime().exec("logcat -c")
Runtime.getRuntime().exec("logcat -v time -f $logFile *:E $tag:V ") Runtime.getRuntime().exec("logcat -v time -f $logFile *:E $tag:V ")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "logcat started") //[REMOVE LOG CALLS]Log.d(tag, "logcat started")
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} }

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
import android.content.Intent import android.content.Intent

View File

@ -1,12 +1,14 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat import android.support.v4.media.session.PlaybackStateCompat
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import fr.forum_thalie.tsumugi.playerstore.PlayerStore import fr.riff_app.riff.playerstore.PlayerStore
class NowPlayingNotification( class NowPlayingNotification(
notificationChannelId: String, notificationChannelId: String,
@ -34,18 +36,19 @@ class NowPlayingNotification(
// got it right // got it right
val delIntent = Intent(c, RadioService::class.java) val delIntent = Intent(c, RadioService::class.java)
delIntent.putExtra("action", Actions.KILL.name) delIntent.putExtra("action", Actions.KILL.name)
val deleteIntent = PendingIntent.getService(c, 0, delIntent, PendingIntent.FLAG_NO_CREATE) val deleteIntent = PendingIntent.getService(c, 0, delIntent, PendingIntent.FLAG_NO_CREATE + PendingIntent.FLAG_IMMUTABLE)
builder.setDeleteIntent(deleteIntent) builder.setDeleteIntent(deleteIntent)
mediaStyle = androidx.media.app.NotificationCompat.DecoratedMediaCustomViewStyle().also { mediaStyle = androidx.media.app.NotificationCompat.DecoratedMediaCustomViewStyle().also {
it.setMediaSession(m.sessionToken) it.setMediaSession(m.sessionToken)
it.setShowActionsInCompactView(0) // index 0 = show actions 0 and 1 (show action #0 (play/pause)) it.setShowActionsInCompactView(0, 1) // index 0 = show actions 0 and 1 (show action #0 (play/pause))
it.setCancelButtonIntent(deleteIntent) it.setCancelButtonIntent(deleteIntent)
} }
builder.setStyle(mediaStyle) builder.setStyle(mediaStyle)
update(c) update(c)
} }
@RequiresApi(Build.VERSION_CODES.M)
fun update(c: Context, isUpdatingNotificationButton: Boolean = false, isRinging: Boolean = false) { fun update(c: Context, isUpdatingNotificationButton: Boolean = false, isRinging: Boolean = false) {
if (isUpdatingNotificationButton) if (isUpdatingNotificationButton)
@ -72,18 +75,18 @@ class NowPlayingNotification(
playPauseAction = if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING) { playPauseAction = if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING) {
intent.putExtra("action", Actions.PAUSE.name) intent.putExtra("action", Actions.PAUSE.name)
val pendingButtonIntent = PendingIntent.getService(c, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT) val pendingButtonIntent = PendingIntent.getService(c, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE)
NotificationCompat.Action.Builder(R.drawable.ic_pause, "Pause", pendingButtonIntent).build() NotificationCompat.Action.Builder(R.drawable.ic_pause, "Pause", pendingButtonIntent).build()
} else { } else {
intent.putExtra("action", Actions.PLAY.name) intent.putExtra("action", Actions.PLAY.name)
val pendingButtonIntent = PendingIntent.getService(c, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT) val pendingButtonIntent = PendingIntent.getService(c, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE)
NotificationCompat.Action.Builder(R.drawable.ic_play,"Play", pendingButtonIntent).build() NotificationCompat.Action.Builder(R.drawable.ic_play,"Play", pendingButtonIntent).build()
} }
builder.addAction(playPauseAction) builder.addAction(playPauseAction)
val intent2 = Intent(c, RadioService::class.java) val intent2 = Intent(c, RadioService::class.java)
intent2.putExtra("action", Actions.KILL.name) intent2.putExtra("action", Actions.KILL.name)
val pendingButtonIntent = PendingIntent.getService(c, 2, intent2, PendingIntent.FLAG_UPDATE_CURRENT) val pendingButtonIntent = PendingIntent.getService(c, 2, intent2, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE)
val stopAction = NotificationCompat.Action.Builder(R.drawable.ic_stop,"Stop", pendingButtonIntent).build() val stopAction = NotificationCompat.Action.Builder(R.drawable.ic_close,"Stop", pendingButtonIntent).build()
builder.addAction(stopAction) builder.addAction(stopAction)
if (isRinging) { if (isRinging) {
@ -92,7 +95,7 @@ class NowPlayingNotification(
val snoozeIntent = Intent(c, RadioService::class.java) val snoozeIntent = Intent(c, RadioService::class.java)
snoozeIntent.putExtra("action", Actions.SNOOZE.name) snoozeIntent.putExtra("action", Actions.SNOOZE.name)
val pendingSnoozeIntent = PendingIntent.getService(c, 5, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT) val pendingSnoozeIntent = PendingIntent.getService(c, 5, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE)
val snoozeAction = NotificationCompat.Action.Builder(R.drawable.ic_alarm, "Snooze ($snoozeMinutes min.)", pendingSnoozeIntent ).build() val snoozeAction = NotificationCompat.Action.Builder(R.drawable.ic_alarm, "Snooze ($snoozeMinutes min.)", pendingSnoozeIntent ).build()
if (snoozeMinutes > 0) if (snoozeMinutes > 0)
builder.addAction(snoozeAction) builder.addAction(snoozeAction)

View File

@ -1,7 +1,8 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
import android.os.Bundle import android.os.Bundle
import fr.forum_thalie.tsumugi.preferences.* import android.view.MenuItem
import fr.riff_app.riff.preferences.*
class ParametersActivity : BaseActivity() { class ParametersActivity : BaseActivity() {
@ -38,4 +39,15 @@ class ParametersActivity : BaseActivity() {
.replace(R.id.parameters_host_container, fragmentToLoad) .replace(R.id.parameters_host_container, fragmentToLoad)
.commit() .commit()
} }
// Make the Up button function as back instead of always bringing us to the main activity
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
}
} }

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
import android.app.Service import android.app.Service
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
@ -32,10 +32,10 @@ import androidx.media.AudioManagerCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.exoplayer2.* import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.metadata.icy.* import com.google.android.exoplayer2.metadata.icy.*
import fr.forum_thalie.tsumugi.alarm.RadioAlarm import fr.riff_app.riff.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.alarm.RadioSleeper import fr.riff_app.riff.alarm.RadioSleeper
import fr.forum_thalie.tsumugi.planning.Planning import fr.riff_app.riff.planning.Planning
import fr.forum_thalie.tsumugi.playerstore.PlayerStore import fr.riff_app.riff.playerstore.PlayerStore
import java.util.* import java.util.*
import kotlin.math.exp import kotlin.math.exp
import kotlin.math.ln import kotlin.math.ln
@ -68,14 +68,14 @@ class RadioService : MediaBrowserServiceCompat() {
// This *should* work in any case... // This *should* work in any case...
when (intent.getIntExtra("state", -1)) { when (intent.getIntExtra("state", -1)) {
0 -> { 0 -> {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is unplugged") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is unplugged")
} }
1 -> { 1 -> {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is plugged") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is plugged")
headsetPluggedIn = true headsetPluggedIn = true && PreferenceManager.getDefaultSharedPreferences(context).getBoolean("autoStartOnPlug", false)
} }
else -> { else -> {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "I have no idea what the headset state is") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "I have no idea what the headset state is")
} }
} }
/* /*
@ -91,7 +91,7 @@ class RadioService : MediaBrowserServiceCompat() {
} }
else else
{ {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Can't get state?") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Can't get state?")
} }
*/ */
@ -124,10 +124,32 @@ class RadioService : MediaBrowserServiceCompat() {
private val titleObserver = Observer<String> { private val titleObserver = Observer<String> {
// We're checking if a new song arrives. If so, we put the currentSong in Lp and update the backup. // We're checking if a new song arrives. If so, we put the currentSong in Lp and update the backup.
if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING)
{
//[REMOVE LOG CALLS]Log.d((tag, radioTag + "SONG CHANGED AND PLAYING")
// we activate latency compensation only if it's been at least 2 songs...
when {
PlayerStore.instance.isStreamDown -> {
// if we reach here, it means that the observer has been called by a new song and that the stream was down previously.
// so the stream is now back to normal.
PlayerStore.instance.isStreamDown = false
PlayerStore.instance.initApi()
}
PlayerStore.instance.currentSong.title.value == noConnectionValue -> {
PlayerStore.instance.isStreamDown = true
}
else -> {
PlayerStore.instance.fetchApi(/* numberOfSongs >= 2 */)
}
}
}
if (PlayerStore.instance.currentSong != PlayerStore.instance.currentSongBackup if (PlayerStore.instance.currentSong != PlayerStore.instance.currentSongBackup
&& it != noConnectionValue) && it != noConnectionValue)
{ {
PlayerStore.instance.updateLp() PlayerStore.instance.updateLp()
PlayerStore.instance.updateQueue()
} }
nowPlayingNotification.update(this) nowPlayingNotification.update(this)
Planning.instance.checkProgramme() Planning.instance.checkProgramme()
@ -163,9 +185,16 @@ class RadioService : MediaBrowserServiceCompat() {
preferenceStore = PreferenceManager.getDefaultSharedPreferences(this) preferenceStore = PreferenceManager.getDefaultSharedPreferences(this)
// start ticker for when the player is stopped
val periodString = PreferenceManager.getDefaultSharedPreferences(this).getString("fetchPeriod", "10") ?: "10"
val period: Long = Integer.parseInt(periodString).toLong()
if (period > 0)
apiTicker.schedule(ApiFetchTick(), 0, period * 1000)
// Define managers // Define managers
telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
telephonyManager?.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE) // telephonyManager?.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
//define the audioFocusRequest //define the audioFocusRequest
@ -205,7 +234,7 @@ class RadioService : MediaBrowserServiceCompat() {
startForeground(radioServiceId, nowPlayingNotification.notification) startForeground(radioServiceId, nowPlayingNotification.notification)
PlayerStore.instance.isServiceStarted.value = true PlayerStore.instance.isServiceStarted.value = true
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "created") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "created")
} }
private val handler = Handler() private val handler = Handler()
@ -246,7 +275,7 @@ class RadioService : MediaBrowserServiceCompat() {
Actions.CANCEL_FADE_OUT.name -> { handler.removeCallbacks(lowerVolumeRunnable) } Actions.CANCEL_FADE_OUT.name -> { handler.removeCallbacks(lowerVolumeRunnable) }
Actions.SNOOZE.name -> { RadioAlarm.instance.snooze(this) } Actions.SNOOZE.name -> { RadioAlarm.instance.snooze(this) }
} }
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "intent received : " + intent.getStringExtra("action")) //[REMOVE LOG CALLS]Log.d(tag, radioTag + "intent received : " + intent.getStringExtra("action"))
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
// The service must be re-created if it is destroyed by the system. This allows the user to keep actions like Bluetooth and headphones plug available. // The service must be re-created if it is destroyed by the system. This allows the user to keep actions like Bluetooth and headphones plug available.
return START_STICKY return START_STICKY
@ -258,7 +287,7 @@ class RadioService : MediaBrowserServiceCompat() {
stopSelf() stopSelf()
} }
super.onTaskRemoved(rootIntent) super.onTaskRemoved(rootIntent)
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "task removed") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "task removed")
} }
override fun onDestroy() { override fun onDestroy() {
@ -290,7 +319,7 @@ class RadioService : MediaBrowserServiceCompat() {
} }
apiTicker.cancel() // stops the timer. apiTicker.cancel() // stops the timer.
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "destroyed") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "destroyed")
// if the service is destroyed, the application had become useless. // if the service is destroyed, the application had become useless.
exitProcess(0) exitProcess(0)
} }
@ -361,10 +390,10 @@ class RadioService : MediaBrowserServiceCompat() {
for (i in 0 until it.length()) { for (i in 0 until it.length()) {
val entry = it.get(i) val entry = it.get(i)
if (entry is IcyHeaders) { if (entry is IcyHeaders) {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: IcyHeaders $entry") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: IcyHeaders $entry")
} }
if (entry is IcyInfo) { if (entry is IcyInfo) {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: Title ----> ${entry.title}") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: Title ----> ${entry.title}")
// Note : Kotlin supports UTF-8 by default. // Note : Kotlin supports UTF-8 by default.
numberOfSongs++ numberOfSongs++
val data = entry.title!! val data = entry.title!!
@ -399,7 +428,7 @@ class RadioService : MediaBrowserServiceCompat() {
) )
// This is the MediaSource representing the media to be played. // This is the MediaSource representing the media to be played.
radioMediaSource = ProgressiveMediaSource.Factory(dataSourceFactory) radioMediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(getString(R.string.STREAM_URL_TSUMUGI))) .createMediaSource(Uri.parse(getString(R.string.STREAM_URL_RIFF)))
fallbackMediaSource = ProgressiveMediaSource.Factory(dataSourceFactory) fallbackMediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse("file:///android_asset/the_stream_is_down.mp3")) .createMediaSource(Uri.parse("file:///android_asset/the_stream_is_down.mp3"))
@ -426,12 +455,13 @@ class RadioService : MediaBrowserServiceCompat() {
// this function is playing the stream if available, or a default sound if there's a problem. // this function is playing the stream if available, or a default sound if there's a problem.
private fun beginPlayingOrFallback() private fun beginPlayingOrFallback()
{ {
PlayerStore.instance.volume.value = 100 // we set the max volume for exoPlayer to be sure it rings correctly. PlayerStore.instance.volume.value = PreferenceManager.getDefaultSharedPreferences(this).getInt("alarmVolume", 100)
// we set the max volume for exoPlayer to be sure it rings correctly.
beginPlaying(isRinging = true, isFallback = false) beginPlaying(isRinging = true, isFallback = false)
val wait: (Any?) -> Any = { val wait: (Any?) -> Any = {
/* /*
Here we lower the isAlarmStopped flag and we wait for 17s. (seems like 12 could be a bit too short since I increased the buffer!!) Here we lower the isAlarmStopped flag and we wait for 17s. (seems like 12 could be a bit too short since I increased the buffer!!)
If the player stops the alarm (by calling an intent), the isAlarmStopped flag will be raised. If the user stops the alarm (by calling an intent), the isAlarmStopped flag will be raised.
*/ */
isAlarmStopped = false // reset the flag isAlarmStopped = false // reset the flag
var i = 0 var i = 0
@ -439,7 +469,7 @@ class RadioService : MediaBrowserServiceCompat() {
{ {
Thread.sleep(1000) Thread.sleep(1000)
i++ i++
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$i, isAlarmStopped=$isAlarmStopped") //[REMOVE LOG CALLS]Log.d(tag, "$i, isAlarmStopped=$isAlarmStopped")
} }
} }
val post: (Any?) -> Unit = { val post: (Any?) -> Unit = {
@ -470,6 +500,7 @@ class RadioService : MediaBrowserServiceCompat() {
.setUsage(C.USAGE_ALARM) .setUsage(C.USAGE_ALARM)
.build() .build()
} else { } else {
isAlarmStopped = true // if we're not ringing and it tries playing, it means the user opened the app somehow
audioAttributes.setUsage(AudioAttributesCompat.USAGE_MEDIA) audioAttributes.setUsage(AudioAttributesCompat.USAGE_MEDIA)
audioFocusRequestBuilder.setAudioAttributes(audioAttributes.build()) audioFocusRequestBuilder.setAudioAttributes(audioAttributes.build())
audioFocusRequest = audioFocusRequestBuilder.build() audioFocusRequest = audioFocusRequestBuilder.build()
@ -484,9 +515,11 @@ class RadioService : MediaBrowserServiceCompat() {
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
return return
} }
if (mediaSession.controller.playbackState.state == PlaybackStateCompat.STATE_PLAYING && !isRinging && isAlarmStopped)
if (mediaSession.controller.playbackState.state == PlaybackStateCompat.STATE_PLAYING && !isRinging) {
return //nothing to do here return //nothing to do here
}
PlayerStore.instance.playbackState.value = PlaybackStateCompat.STATE_PLAYING PlayerStore.instance.playbackState.value = PlaybackStateCompat.STATE_PLAYING
// Reinitialize media player. Otherwise the playback doesn't resume when beginPlaying. Dunno why. // Reinitialize media player. Otherwise the playback doesn't resume when beginPlaying. Dunno why.
@ -512,7 +545,8 @@ class RadioService : MediaBrowserServiceCompat() {
SystemClock.elapsedRealtime() SystemClock.elapsedRealtime()
) )
mediaSession.setPlaybackState(playbackStateBuilder.build()) mediaSession.setPlaybackState(playbackStateBuilder.build())
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "begin playing")
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "begin playing")
} }
private fun pausePlaying() private fun pausePlaying()
@ -525,7 +559,6 @@ class RadioService : MediaBrowserServiceCompat() {
{ {
if (mediaSession.controller.playbackState.state == PlaybackStateCompat.STATE_STOPPED) if (mediaSession.controller.playbackState.state == PlaybackStateCompat.STATE_STOPPED)
return // nothing to do here return // nothing to do here
if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING) if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING)
isAlarmStopped = true isAlarmStopped = true
@ -541,7 +574,7 @@ class RadioService : MediaBrowserServiceCompat() {
1.0f, 1.0f,
SystemClock.elapsedRealtime() SystemClock.elapsedRealtime()
) )
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "stopped") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "stopped")
mediaSession.setPlaybackState(playbackStateBuilder.build()) mediaSession.setPlaybackState(playbackStateBuilder.build())
} }
@ -629,7 +662,7 @@ class RadioService : MediaBrowserServiceCompat() {
Player.STATE_ENDED -> state = "Player.STATE_ENDED" Player.STATE_ENDED -> state = "Player.STATE_ENDED"
Player.STATE_READY -> state = "Player.STATE_READY" Player.STATE_READY -> state = "Player.STATE_READY"
} }
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Player changed state: ${state}. numberOfSongs reset.") //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Player changed state: ${state}. numberOfSongs reset.")
} }
} }

View File

@ -0,0 +1,21 @@
package fr.riff_app.riff
import android.support.v4.media.session.PlaybackStateCompat
import fr.riff_app.riff.playerstore.PlayerStore
import java.util.*
class ApiFetchTick : TimerTask() {
override fun run() {
if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_STOPPED)
{
PlayerStore.instance.fetchApi()
}
}
}
class Tick : TimerTask() {
override fun run() {
PlayerStore.instance.currentTime.postValue(PlayerStore.instance.currentTime.value!! + 500)
}
}

View File

@ -1,12 +1,12 @@
package fr.forum_thalie.tsumugi package fr.riff_app.riff
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.ColorStateList import android.content.res.ColorStateList
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
const val tag = "fr.forum_thalie.tsumugi" const val tag = "fr.riff_app.riff"
const val noConnectionValue = "" const val noConnectionValue = ""
const val streamDownValue = "Tsumugi est HS !" // we don't want this value to be displaed in the "last played" screen. const val streamDownValue = "Riff est HS !" // we don't want this value to be displaed in the "last played" screen.
val weekdaysArray : Array<String> = arrayOf( "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche") val weekdaysArray : Array<String> = arrayOf( "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
const val newsDateTimePattern = "EEE, d MMM yyyy HH:mm:ss Z" const val newsDateTimePattern = "EEE, d MMM yyyy HH:mm:ss Z"
const val newsDisplayDatePattern = "dd MMM yyyy" const val newsDisplayDatePattern = "dd MMM yyyy"

View File

@ -1,14 +1,16 @@
package fr.forum_thalie.tsumugi.alarm package fr.riff_app.riff.alarm
import android.app.AlarmManager import android.app.AlarmManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.AlarmManagerCompat import androidx.core.app.AlarmManagerCompat
import fr.forum_thalie.tsumugi.BootBroadcastReceiver import fr.riff_app.riff.BootBroadcastReceiver
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import fr.forum_thalie.tsumugi.* import fr.riff_app.riff.*
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -27,13 +29,31 @@ class RadioAlarm {
{ {
alarmIntent = Intent(c, BootBroadcastReceiver::class.java).let { intent -> alarmIntent = Intent(c, BootBroadcastReceiver::class.java).let { intent ->
intent.putExtra("action", "$tag.${Actions.PLAY_OR_FALLBACK.name}") intent.putExtra("action", "$tag.${Actions.PLAY_OR_FALLBACK.name}")
PendingIntent.getBroadcast(c, 0, intent, 0) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getBroadcast(
c,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
} else {
PendingIntent.getBroadcast(
c,
0,
intent,
0
)
}
} }
showIntent = Intent(c, ParametersActivity::class.java).let { intent -> showIntent = Intent(c, ParametersActivity::class.java).let { intent ->
intent.putExtra("action", "alarm") intent.putExtra("action", "alarm")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getActivity(c, 0, intent, PendingIntent.FLAG_IMMUTABLE)
} else {
PendingIntent.getActivity(c, 0, intent, 0) PendingIntent.getActivity(c, 0, intent, 0)
} }
} }
}
fun cancelAlarm(c: Context) fun cancelAlarm(c: Context)
@ -43,6 +63,7 @@ class RadioAlarm {
alarmManager.cancel(alarmIntent) alarmManager.cancel(alarmIntent)
} }
@RequiresApi(Build.VERSION_CODES.M)
fun setNextAlarm(c: Context, isForce: Boolean = false, forceTime: Int? = null, forceDays: Set<String>? = null) fun setNextAlarm(c: Context, isForce: Boolean = false, forceTime: Int? = null, forceDays: Set<String>? = null)
{ {
defineIntents(c) defineIntents(c)
@ -54,7 +75,7 @@ class RadioAlarm {
val showIntent = Intent(c, ParametersActivity::class.java).let { intent -> val showIntent = Intent(c, ParametersActivity::class.java).let { intent ->
intent.putExtra("action", ActionOpenParam.ALARM.name) intent.putExtra("action", ActionOpenParam.ALARM.name)
PendingIntent.getActivity(c, 0, intent, 0) PendingIntent.getActivity(c, 0, intent, PendingIntent.FLAG_IMMUTABLE)
} }
val time = findNextAlarmTime(c, forceTime, forceDays) val time = findNextAlarmTime(c, forceTime, forceDays)
if (time > 0) if (time > 0)
@ -97,7 +118,7 @@ class RadioAlarm {
calendar.isLenient = true calendar.isLenient = true
calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH) + i, hourOfDay, minute) calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH) + i, hourOfDay, minute)
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, calendar.toString()) //[REMOVE LOG CALLS]Log.d(tag, calendar.toString())
return calendar.timeInMillis return calendar.timeInMillis

View File

@ -1,14 +1,16 @@
package fr.forum_thalie.tsumugi.alarm package fr.riff_app.riff.alarm
import android.app.AlarmManager import android.app.AlarmManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.AlarmManagerCompat import androidx.core.app.AlarmManagerCompat
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import fr.forum_thalie.tsumugi.* import fr.riff_app.riff.*
class RadioSleeper { class RadioSleeper {
@ -30,18 +32,20 @@ class RadioSleeper {
private lateinit var sleepIntent: PendingIntent private lateinit var sleepIntent: PendingIntent
private lateinit var fadeOutIntent: PendingIntent private lateinit var fadeOutIntent: PendingIntent
@RequiresApi(Build.VERSION_CODES.M)
private fun defineIntents(c: Context) private fun defineIntents(c: Context)
{ {
sleepIntent = Intent(c, RadioService::class.java).let { intent -> sleepIntent = Intent(c, RadioService::class.java).let { intent ->
intent.putExtra("action", Actions.KILL.name) intent.putExtra("action", Actions.KILL.name)
PendingIntent.getService(c, 99, intent, 0) PendingIntent.getService(c, 99, intent, PendingIntent.FLAG_IMMUTABLE)
} }
fadeOutIntent = Intent(c, RadioService::class.java).let { intent -> fadeOutIntent = Intent(c, RadioService::class.java).let { intent ->
intent.putExtra("action", Actions.FADE_OUT.name) intent.putExtra("action", Actions.FADE_OUT.name)
PendingIntent.getService(c, 98, intent, 0) PendingIntent.getService(c, 98, intent, PendingIntent.FLAG_IMMUTABLE)
} }
} }
@RequiresApi(Build.VERSION_CODES.M)
fun setSleep(c: Context, isForce: Boolean = false, forceDuration: Long? = null) fun setSleep(c: Context, isForce: Boolean = false, forceDuration: Long? = null)
{ {
defineIntents(c) defineIntents(c)
@ -61,7 +65,7 @@ class RadioSleeper {
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, currentMillis + (minutes * 60 * 1000) - (1 * 60 * 1000), fadeOutIntent) AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, currentMillis + (minutes * 60 * 1000) - (1 * 60 * 1000), fadeOutIntent)
sleepAtMillis.value = System.currentTimeMillis() + (minutes * 60 * 1000) - 1 // this -1 allows to round the division for display at the right integer sleepAtMillis.value = System.currentTimeMillis() + (minutes * 60 * 1000) - 1 // this -1 allows to round the division for display at the right integer
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "set sleep to $minutes minutes") //[REMOVE LOG CALLS]Log.d(tag, "set sleep to $minutes minutes")
} }
} }
@ -79,7 +83,7 @@ class RadioSleeper {
c.startService(cancelFadeOutIntent) c.startService(cancelFadeOutIntent)
} }
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "cancelled sleep") //[REMOVE LOG CALLS]Log.d(tag, "cancelled sleep")
sleepAtMillis.value = null sleepAtMillis.value = null
} }
} }

View File

@ -1,11 +1,11 @@
package fr.forum_thalie.tsumugi.planning package fr.riff_app.riff.planning
import android.content.Context import android.content.Context
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import fr.forum_thalie.tsumugi.Async import fr.riff_app.riff.Async
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.noConnectionValue import fr.riff_app.riff.noConnectionValue
import fr.forum_thalie.tsumugi.weekdays import fr.riff_app.riff.weekdays
import org.json.JSONObject import org.json.JSONObject
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
@ -64,6 +64,7 @@ class Planning {
if (result.has("planning")) if (result.has("planning"))
{ {
val programList = result.getJSONArray("planning") val programList = result.getJSONArray("planning")
programmes.clear()
for (i in 0 until programList.length()) for (i in 0 until programList.length())
{ {
val item = programList[i] as JSONObject val item = programList[i] as JSONObject

View File

@ -1,8 +1,8 @@
package fr.forum_thalie.tsumugi.planning package fr.riff_app.riff.planning
import android.util.Log import android.util.Log
import fr.forum_thalie.tsumugi.tag import fr.riff_app.riff.tag
import fr.forum_thalie.tsumugi.weekdays import fr.riff_app.riff.weekdays
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -35,7 +35,7 @@ class Programme (val title: String, private val periodicity: Int, private val ho
val isSpanningOverNight = val isSpanningOverNight =
(((0b1000000 shr ((currentDay - 1) % 7) and (periodicity)) != 0) && hourEnd < hourBegin) (((0b1000000 shr ((currentDay - 1) % 7) and (periodicity)) != 0) && hourEnd < hourBegin)
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$title is today: $isToday or spanning $isSpanningOverNight") //[REMOVE LOG CALLS]Log.d(tag, "$title is today: $isToday or spanning $isSpanningOverNight")
// shr = shift-right. It's a binary mask. // shr = shift-right. It's a binary mask.
// if the program started yesterday, and spanned over night, it means that there could be a chance that it's still active. // if the program started yesterday, and spanned over night, it means that there could be a chance that it's still active.
@ -87,6 +87,6 @@ class Programme (val title: String, private val periodicity: Int, private val ho
*/ */
init { init {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, this.toString()) //[REMOVE LOG CALLS]Log.d(tag, this.toString())
} }
} }

View File

@ -0,0 +1,277 @@
package fr.riff_app.riff.playerstore
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import androidx.lifecycle.MutableLiveData
import fr.riff_app.riff.*
import fr.riff_app.riff.planning.Planning
import org.json.JSONObject
import java.net.URL
import java.text.ParseException
import java.text.SimpleDateFormat
import java.time.format.TextStyle
import java.util.*
import kotlin.collections.ArrayList
class PlayerStore {
private lateinit var urlToScrape: String
val isPlaying: MutableLiveData<Boolean> = MutableLiveData()
val isServiceStarted: MutableLiveData<Boolean> = MutableLiveData()
val volume: MutableLiveData<Int> = MutableLiveData()
val playbackState: MutableLiveData<Int> = MutableLiveData()
val currentTime: MutableLiveData<Long> = MutableLiveData()
val streamerPicture: MutableLiveData<Bitmap> = MutableLiveData()
val streamerName: MutableLiveData<String> = MutableLiveData()
val currentSong : Song = Song()
val currentSongBackup: Song = Song()
val lp : ArrayList<Song> = ArrayList()
val queue : ArrayList<Song> = ArrayList()
val isQueueUpdated: MutableLiveData<Boolean> = MutableLiveData()
val isLpUpdated: MutableLiveData<Boolean> = MutableLiveData()
val isMuted : MutableLiveData<Boolean> = MutableLiveData()
val listenersCount: MutableLiveData<Int> = MutableLiveData()
var latencyCompensator : Long = 0
var isInitialized: Boolean = false
var isStreamDown: Boolean = false
init {
playbackState.value = PlaybackStateCompat.STATE_STOPPED
isPlaying.value = false
isServiceStarted.value = false
streamerName.value = ""
volume.value = preferenceStore.getInt("volume", 100)
currentTime.value = System.currentTimeMillis()
isQueueUpdated.value = false
isLpUpdated.value = false
isMuted.value = false
currentSong.setTitleArtist(noConnectionValue)
currentSongBackup.setTitleArtist(noConnectionValue)
listenersCount.value = 0
}
fun initUrl(c: Context)
{
urlToScrape = c.getString(R.string.API_URL)
}
private fun getTimestamp(s: String) : Long
{
val dateFormat = SimpleDateFormat("yyyy-MM-dd hh:mm:ss z", Locale.getDefault())
try {
val t: Date? = dateFormat.parse("$s ${Planning.instance.timeZone.getDisplayName(Planning.instance.timeZone.useDaylightTime(), TimeZone.SHORT)}")
//[REMOVE LOG CALLS]Log.d(tag, "date: $s -> $t")
return t!!.time
} catch (e: ParseException) {
e.printStackTrace()
}
return 0
}
// ##################################################
// ################# API FUNCTIONS ##################
// ##################################################
private fun updateApi(res: JSONObject, isCompensatingLatency : Boolean = false) {
// If we're not in PLAYING state, update title / artist metadata. If we're playing, the ICY will take care of that.
val resMain = res.getJSONObject("tracks").getJSONObject("current")
val s = extractSong(resMain)
if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty()
|| currentSong.title.value == noConnectionValue)
currentSong.setTitleArtist("${s.artist.value} - ${s.title.value}")
val starts = s.startTime.value
val ends = s.stopTime.value
if (currentSong.startTime.value != starts)
currentSong.startTime.value = starts
currentSong.stopTime.value = ends
val apiTime = getTimestamp(res.getJSONObject("station").getString("schedulerTime"))
// I noticed that the server has a big (3 to 9 seconds !!) offset for current time.
// we can measure it when the player is playing, to compensate it and have our progress bar perfectly timed
// latencyCompensator is set to null when beginPlaying() (we can't measure it at the moment we start playing, since we're in the middle of a song),
// at this moment, we set it to 0. Then, next time the updateApi is called when we're playing, we measure the latency and we set out latencyComparator.
if(isCompensatingLatency)
{
latencyCompensator = apiTime - (currentSong.startTime.value!!)
//[REMOVE LOG CALLS]Log.d(tag, "latency compensator set to ${(latencyCompensator).toFloat() / 1000} s")
}
currentTime.value = apiTime - (latencyCompensator)
/*
val listeners = resMain.getInt("listeners")
listenersCount.value = listeners
//[REMOVE LOG CALLS]Log.d((tag, playerStoreTag + "store updated")
*/
}
private val scrape : (Any?) -> String =
{
URL(urlToScrape).readText()
}
/* initApi is called :
- at startup
- when a streamer changes.
the idea is to fetch the queue when a streamer changes (potentially Hanyuu), and at startup.
The Last Played is only fetched if it's empty (so, only at startup), not when a streamer changes.
*/
fun initApi()
{
val post : (parameter: Any?) -> Unit = {
val result = JSONObject(it as String)
if (result.has("tracks"))
{
updateApi(result)
currentSongBackup.copy(currentSong)
fetchLastRequest()
isQueueUpdated.value = true
isLpUpdated.value = true
}
isInitialized = true
}
Async(scrape, post)
}
fun fetchApi(isCompensatingLatency: Boolean = false) {
val post: (parameter: Any?) -> Unit = {
val result = JSONObject(it as String)
if (!result.isNull("tracks"))
{
updateApi(result, isCompensatingLatency)
}
}
Async(scrape, post)
}
private fun extractSong(songJSON: JSONObject) : Song {
val song = Song()
song.setTitleArtist(songJSON.getString("name"))
song.startTime.value = getTimestamp(songJSON.getString("starts"))
song.stopTime.value = getTimestamp(songJSON.getString("ends"))
song.type.value = 0 // only used for R/a/dio
return song
}
// ##################################################
// ############## QUEUE / LP FUNCTIONS ##############
// ##################################################
fun updateQueue() {
//[REMOVE LOG CALLS]Log.d(tag, queue.toString())
fetchLastRequest()
}
fun updateLp() {
// note : lp is empty at initialization. This check was needed when we used the R/a/dio API.
//if (lp.isNotEmpty()){
val n = Song()
n.copy(currentSongBackup)
if (n != Song(noConnectionValue) && n != Song(streamDownValue))
lp.add(0, n)
currentSongBackup.copy(currentSong)
isLpUpdated.value = true
//[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + lp.toString())
//}
}
private fun fetchLastRequest()
{
isQueueUpdated.value = false
val sleepScrape: (Any?) -> String = {
/* we can maximize our chances to retrieve the last queued song by specifically waiting for the number of seconds we measure between ICY metadata and API change.
we add 2 seconds just to get a higher probability that the API has correctly updated. (the latency compensator can have a jitter of 1 second usually)
If, against all odds, the API hasn't updated yet, we will retry in the same amount of seconds. So we'll have the data anyway.
This way to fetch at the most probable time is a good compromise between fetch speed and fetch frequency
We don't fetch too often, and we start to fetch at the most *probable* time.
If there's no latencyCompensator measured yet, we only wait for 3 seconds.
If the song is the same, it will be called again. 3 seconds is a good compromise between speed and frequency:
it might be called twice, rarely 3 times, and it's only the 2 first songs ; after these, the latencyCompensator is set to fetch at the most probable time.
*/
val sleepTime: Long = if (latencyCompensator > 0) latencyCompensator + 2000 else 3000
Thread.sleep(sleepTime) // we wait a bit (10s) for the API to get updated on R/a/dio side!
URL(urlToScrape).readText()
}
lateinit var post: (parameter: Any?) -> Unit
fun postFun(result: JSONObject)
{
if (result.has("tracks")) {
val resMain = result.getJSONObject("tracks")
/*
if ((resMain.has("isafkstream") && !resMain.getBoolean("isafkstream")) &&
queue.isNotEmpty())
{
queue.clear() //we're not requesting anything anymore.
isQueueUpdated.value = true
} else if (resMain.has("isafkstream") && resMain.getBoolean("isafkstream") &&
queue.isEmpty())
{
initApi()
} else
*/
if (resMain.has("next")) {
val queueJSON =
resMain.getJSONObject("next")
val t = extractSong(queueJSON)
if (queue.isNotEmpty() && (t == queue.last() || t == currentSong) && isQueueUpdated.value == false)
{
//[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + "Song already in there: $t\nQueue:$queue")
Async(sleepScrape, post)
} else {
if (queue.isNotEmpty())
queue.remove(queue.first())
queue.add(queue.size, t)
//[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + "added last queue song: $t")
isQueueUpdated.value = true
return // FUUUCK IT WAS CALLING THE ASYNC ONE MORE TIME AFTERWARDS !?
}
}
}
}
post = {
val result = JSONObject(it as String)
/* The goal is to pass the result to a function that will process it (postFun).
The magic trick is, under circumstances, the last queue song might not have been updated yet when we fetch it.
So if this is detected ==> if (t == queue.last() )
Then the function re-schedule an Async(sleepScrape, post).
To do that, the "post" must be defined BEFORE the function, but the function must be defined BEFORE the "post" value.
So I declare "post" as lateinit var, define the function, then define the "post" that calls the function. IT SHOULD WORK.
*/
postFun(result)
}
Async(sleepScrape, post)
}
// ##################################################
// ############## PICTURE FUNCTIONS #################
// ##################################################
fun initPicture(c: Context) {
streamerPicture.value = BitmapFactory.decodeResource(c.resources,
R.drawable.logo_roundsquare
)
}
private val playerStoreTag = "====PlayerStore===="
companion object {
val instance by lazy {
PlayerStore()
}
}
}

View File

@ -1,7 +1,8 @@
package fr.forum_thalie.tsumugi.playerstore package fr.riff_app.riff.playerstore
import androidx.core.text.HtmlCompat
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import fr.forum_thalie.tsumugi.noConnectionValue import fr.riff_app.riff.noConnectionValue
class Song(artistTitle: String = "", _id : Int = 0) { class Song(artistTitle: String = "", _id : Int = 0) {
@ -24,8 +25,9 @@ class Song(artistTitle: String = "", _id : Int = 0) {
return "id=$id | ${artist.value} - ${title.value} | type=${type.value} | times ${startTime.value} - ${stopTime.value}\n" return "id=$id | ${artist.value} - ${title.value} | type=${type.value} | times ${startTime.value} - ${stopTime.value}\n"
} }
fun setTitleArtist(data: String) fun setTitleArtist(dataHtml: String)
{ {
val data = HtmlCompat.fromHtml(dataHtml, HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
val hyphenPos = data.indexOf(" - ") val hyphenPos = data.indexOf(" - ")
try { try {
if (hyphenPos < 0) if (hyphenPos < 0)
@ -46,12 +48,12 @@ class Song(artistTitle: String = "", _id : Int = 0) {
override fun equals(other: Any?) : Boolean override fun equals(other: Any?) : Boolean
{ {
val song: Song = other as Song val song: Song = other as Song
return this.title.value == song.title.value && this.artist.value == song.artist.value return this.title.value === song.title.value && this.artist.value === song.artist.value
} }
fun copy(song: Song) { fun copy(song: Song) {
this.title.value = song.title.value
this.artist.value = song.artist.value this.artist.value = song.artist.value
this.title. value = song.title.value
this.startTime.value = song.startTime.value this.startTime.value = song.startTime.value
this.stopTime.value = song.stopTime.value this.stopTime.value = song.stopTime.value
this.type.value = song.type.value this.type.value = song.type.value

View File

@ -0,0 +1,129 @@
package fr.riff_app.riff.preferences
import android.content.Context
import android.content.Intent
import android.media.AudioManager
import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat
import fr.riff_app.riff.Actions
import fr.riff_app.riff.R
import fr.riff_app.riff.RadioService
import fr.riff_app.riff.playerstore.PlayerStore
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.max
import kotlin.math.min
class AlarmAdjustVolumeFragment : PreferenceFragmentCompat() {
override fun onAttach(context: Context) {
(activity as AppCompatActivity).supportActionBar?.title = context.getString(R.string.test_alarm_volume)
super.onAttach(context)
}
// get previous state: if it's playing, we'll resume playing as multimedia; if it was stopped, we'll stop
private var isPlayingMultimedia: Boolean = PlayerStore.instance.isPlaying.value ?: false
override fun onStop() {
if (isPlayingMultimedia)
{
actionOnService(Actions.PLAY)
} else {
actionOnService(Actions.PAUSE)
}
PlayerStore.instance.volume.value = PreferenceManager.getDefaultSharedPreferences(requireContext()).getInt("volume", 100)
super.onStop()
}
override fun onResume() {
isPlayingMultimedia = PlayerStore.instance.isPlaying.value ?: false
// start as alarm
actionOnService(Actions.PLAY_OR_FALLBACK)
super.onResume()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fun <T> debounce(delayMs: Long = 500L,
coroutineContext: CoroutineContext,
f: (T) -> Unit): (T) -> Unit {
var debounceJob: Job? = null
return { param: T ->
if (debounceJob?.isCompleted != false) {
debounceJob = CoroutineScope(coroutineContext).launch {
delay(delayMs)
f(param)
}
}
}
}
val adjustAlarmVolume: (Int) -> Unit = debounce<Int>(50, GlobalScope.coroutineContext) {
android.util.Log.d(tag, "button $it pushed")
val keyCode = it
val audioManager = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.apply {
val currentVolume = this.getStreamVolume(AudioManager.STREAM_ALARM)
val minVolume = 0 // audioManager.getStreamMinVolume(AudioManager.STREAM_ALARM) <- require API28+
val maxVolume = this.getStreamMaxVolume(AudioManager.STREAM_ALARM)
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
this.setStreamVolume(AudioManager.STREAM_ALARM, max(currentVolume - 1, minVolume), AudioManager.FLAG_SHOW_UI)
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP){
this.setStreamVolume(AudioManager.STREAM_ALARM, min(currentVolume + 1, maxVolume), AudioManager.FLAG_SHOW_UI)
} else {
}
}
}
view.isFocusableInTouchMode = true;
view.requestFocus();
view.setOnKeyListener { _, i, event ->
if (i == KeyEvent.KEYCODE_VOLUME_DOWN || i == KeyEvent.KEYCODE_VOLUME_UP) {
adjustAlarmVolume(i)
true
} else {
false
}
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.alarm_adjust_volume_preferences, rootKey)
val alarmVolumeSeekBar = findPreference<SeekBarPreference>("alarmVolume")
alarmVolumeSeekBar?.apply {
min = 0
max = 100
updatesContinuously = true
setOnPreferenceChangeListener { _, newValue ->
actionOnService(Actions.VOLUME, newValue as Int)
true
}
}
}
private fun actionOnService(a: Actions, v: Int = 0)
{
val i = Intent(requireContext(), RadioService::class.java)
i.putExtra("action", a.name)
i.putExtra("value", v)
//[REMOVE LOG CALLS]Log.d(tag, "Sending intent ${a.name}")
requireContext().startService(i)
}
}

View File

@ -1,16 +1,38 @@
package fr.forum_thalie.tsumugi.preferences package fr.riff_app.riff.preferences
import android.app.TimePickerDialog import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.media.AudioManager
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.KeyEvent
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.* import androidx.preference.*
import fr.forum_thalie.tsumugi.* import fr.riff_app.riff.*
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.alarm.RadioAlarm import fr.riff_app.riff.alarm.RadioAlarm
import fr.riff_app.riff.playerstore.PlayerStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope.coroutineContext
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.max
import kotlin.math.min
class AlarmFragment : PreferenceFragmentCompat() { class AlarmFragment : PreferenceFragmentCompat() {
override fun onAttach(context: Context) {
(activity as AppCompatActivity).supportActionBar?.title = context.getString(R.string.setupAlarmClock)
super.onAttach(context)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.alarm_preferences, rootKey) setPreferencesFromResource(R.xml.alarm_preferences, rootKey)
@ -25,7 +47,7 @@ class AlarmFragment : PreferenceFragmentCompat() {
fun updateIsWakingUpSummary(preference: SwitchPreferenceCompat?, newValue: Boolean? = true, forceTime: Int? = null, forceDays: Set<String>? = null) fun updateIsWakingUpSummary(preference: SwitchPreferenceCompat?, newValue: Boolean? = true, forceTime: Int? = null, forceDays: Set<String>? = null)
{ {
val dateLong = RadioAlarm.instance.findNextAlarmTime(context!!, forceTime, forceDays) val dateLong = RadioAlarm.instance.findNextAlarmTime(requireContext(), forceTime, forceDays)
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
calendar.timeInMillis = dateLong calendar.timeInMillis = dateLong
if (newValue == true && calendar.timeInMillis > 0) if (newValue == true && calendar.timeInMillis > 0)
@ -43,14 +65,14 @@ class AlarmFragment : PreferenceFragmentCompat() {
val hourOfDayDefault = 7 val hourOfDayDefault = 7
val minuteDefault = 0 val minuteDefault = 0
if (!PreferenceManager.getDefaultSharedPreferences(context!!).contains("alarmTimeFromMidnight")) if (!PreferenceManager.getDefaultSharedPreferences(requireContext()).contains("alarmTimeFromMidnight"))
{ {
PreferenceManager.getDefaultSharedPreferences(context!!).edit { PreferenceManager.getDefaultSharedPreferences(requireContext()).edit {
putInt("alarmTimeFromMidnight", (60*hourOfDayDefault+minuteDefault)) putInt("alarmTimeFromMidnight", (60*hourOfDayDefault+minuteDefault))
commit() commit()
} }
} }
val time = PreferenceManager.getDefaultSharedPreferences(context!!).getInt("alarmTimeFromMidnight", (60*hourOfDayDefault+minuteDefault)) val time = PreferenceManager.getDefaultSharedPreferences(requireContext()).getInt("alarmTimeFromMidnight", (60*hourOfDayDefault+minuteDefault))
val hourOfDay = time / 60 val hourOfDay = time / 60
val minute = time % 60 val minute = time % 60
timeSet?.summary = "${if (hourOfDay < 10) "0" else ""}$hourOfDay:${if (minute < 10) "0" else ""}$minute" timeSet?.summary = "${if (hourOfDay < 10) "0" else ""}$hourOfDay:${if (minute < 10) "0" else ""}$minute"
@ -58,16 +80,16 @@ class AlarmFragment : PreferenceFragmentCompat() {
timeSet?.setOnPreferenceClickListener { timeSet?.setOnPreferenceClickListener {
val timePickerOnTimeSetListener = TimePickerDialog.OnTimeSetListener { _, hourOfDay, minute -> val timePickerOnTimeSetListener = TimePickerDialog.OnTimeSetListener { _, hourOfDay, minute ->
PreferenceManager.getDefaultSharedPreferences(context!!).edit { PreferenceManager.getDefaultSharedPreferences(requireContext()).edit {
putInt("alarmTimeFromMidnight", (60*hourOfDay+minute)) putInt("alarmTimeFromMidnight", (60*hourOfDay+minute))
commit() commit()
} }
timeSet.summary = "${if (hourOfDay < 10) "0" else ""}$hourOfDay:${if (minute < 10) "0" else ""}$minute" timeSet.summary = "${if (hourOfDay < 10) "0" else ""}$hourOfDay:${if (minute < 10) "0" else ""}$minute"
RadioAlarm.instance.cancelAlarm(context!!) RadioAlarm.instance.cancelAlarm(requireContext())
RadioAlarm.instance.setNextAlarm(context!!, isForce = true, forceTime = hourOfDay*60+minute) RadioAlarm.instance.setNextAlarm(requireContext(), isForce = true, forceTime = hourOfDay*60+minute)
updateIsWakingUpSummary(isWakingUp, isWakingUp?.isChecked, forceTime = hourOfDay*60+minute) updateIsWakingUpSummary(isWakingUp, isWakingUp?.isChecked, forceTime = hourOfDay*60+minute)
} }
val timeNew = PreferenceManager.getDefaultSharedPreferences(context!!).getInt("alarmTimeFromMidnight", 7*60) val timeNew = PreferenceManager.getDefaultSharedPreferences(requireContext()).getInt("alarmTimeFromMidnight", 7*60)
val hourOfDayNew = timeNew / 60 val hourOfDayNew = timeNew / 60
val minuteNew = timeNew % 60 val minuteNew = timeNew % 60
val timePicker = TimePickerDialog(context, timePickerOnTimeSetListener, hourOfDayNew, minuteNew, true) val timePicker = TimePickerDialog(context, timePickerOnTimeSetListener, hourOfDayNew, minuteNew, true)
@ -81,8 +103,8 @@ class AlarmFragment : PreferenceFragmentCompat() {
val fullWeek = weekdays.toSet() val fullWeek = weekdays.toSet()
val workingWeek = weekdays.toSet().minusElement(weekdays.last()).minusElement(weekdays.elementAt(weekdays.size - 2)) val workingWeek = weekdays.toSet().minusElement(weekdays.last()).minusElement(weekdays.elementAt(weekdays.size - 2))
val listOfDays = when (newValue) { val listOfDays = when (newValue) {
fullWeek -> context!!.getString(R.string.every_day) fullWeek -> requireContext().getString(R.string.every_day)
workingWeek -> context!!.getString(R.string.working_days) workingWeek -> requireContext().getString(R.string.working_days)
else -> { else -> {
// build ORDERED LIST of days... I don't know why the original one is in shambles!! // build ORDERED LIST of days... I don't know why the original one is in shambles!!
val fullWeekOrdered = weekdays val fullWeekOrdered = weekdays
@ -97,12 +119,12 @@ class AlarmFragment : PreferenceFragmentCompat() {
preference?.summary = listOfDays preference?.summary = listOfDays
} }
updateDays(alarmDays, PreferenceManager.getDefaultSharedPreferences(context).getStringSet("alarmDays", setOf())) updateDays(alarmDays, PreferenceManager.getDefaultSharedPreferences(requireContext()).getStringSet("alarmDays", setOf()))
alarmDays?.setOnPreferenceChangeListener { preference, newValue -> alarmDays?.setOnPreferenceChangeListener { preference, newValue ->
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
updateDays(preference as MultiSelectListPreference, newValue as Set<String>) updateDays(preference as MultiSelectListPreference, newValue as Set<String>)
RadioAlarm.instance.cancelAlarm(context!!) RadioAlarm.instance.cancelAlarm(requireContext())
RadioAlarm.instance.setNextAlarm(context!!, isForce = true, forceDays = newValue) RadioAlarm.instance.setNextAlarm(requireContext(), isForce = true, forceDays = newValue)
updateIsWakingUpSummary(isWakingUp, isWakingUp?.isChecked, forceDays = newValue) updateIsWakingUpSummary(isWakingUp, isWakingUp?.isChecked, forceDays = newValue)
true true
} }
@ -111,9 +133,9 @@ class AlarmFragment : PreferenceFragmentCompat() {
isWakingUp?.setOnPreferenceChangeListener { _, newValue -> isWakingUp?.setOnPreferenceChangeListener { _, newValue ->
if (newValue as Boolean) if (newValue as Boolean)
RadioAlarm.instance.setNextAlarm(context!!, isForce = true) RadioAlarm.instance.setNextAlarm(requireContext(), isForce = true)
else else
RadioAlarm.instance.cancelAlarm(context!!) RadioAlarm.instance.cancelAlarm(requireContext())
timeSet?.isEnabled = newValue timeSet?.isEnabled = newValue
alarmDays?.isEnabled = newValue alarmDays?.isEnabled = newValue
snoozeDuration?.isEnabled = newValue snoozeDuration?.isEnabled = newValue
@ -121,12 +143,10 @@ class AlarmFragment : PreferenceFragmentCompat() {
true true
} }
alarmDays?.isEnabled = isWakingUp?.isChecked ?: false alarmDays?.isEnabled = isWakingUp?.isChecked ?: false
timeSet?.isEnabled = isWakingUp?.isChecked ?: false timeSet?.isEnabled = isWakingUp?.isChecked ?: false
snoozeDuration?.isEnabled = isWakingUp?.isChecked ?: false snoozeDuration?.isEnabled = isWakingUp?.isChecked ?: false
} }
} }

View File

@ -1,21 +1,25 @@
package fr.forum_thalie.tsumugi.preferences package fr.riff_app.riff.preferences
import android.content.Intent import android.content.Context
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.* import androidx.preference.*
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.preferenceStore import fr.riff_app.riff.preferenceStore
class CustomizeFragment : PreferenceFragmentCompat() { class CustomizeFragment : PreferenceFragmentCompat() {
override fun onAttach(context: Context) {
(activity as AppCompatActivity).supportActionBar?.title = context.getString(R.string.customizeAppBehavior)
super.onAttach(context)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.customize_preferences, rootKey) setPreferencesFromResource(R.xml.customize_preferences, rootKey)
val snackbarPersistent = preferenceScreen.findPreference<SwitchPreferenceCompat>("snackbarPersistent") val snackbarPersistent = preferenceScreen.findPreference<SwitchPreferenceCompat>("snackbarPersistent")
snackbarPersistent!!.summary = if (preferenceStore.getBoolean("snackbarPersistent", true)) snackbarPersistent!!.summary = if (preferenceStore.getBoolean("snackbarPersistent", false))
getString(R.string.snackbarPersistent) getString(R.string.snackbarPersistent)
else else
getString(R.string.snackbarNonPersistent) getString(R.string.snackbarNonPersistent)
@ -40,5 +44,25 @@ class CustomizeFragment : PreferenceFragmentCompat() {
true true
} }
val fetchPeriod = preferenceScreen.findPreference<ListPreference>("fetchPeriod")
fetchPeriod?.summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
fetchPeriod?.setOnPreferenceChangeListener { _, newValue ->
val builder1 = AlertDialog.Builder(context!!)
if (Integer.parseInt(newValue as String) == 0)
builder1.setMessage(R.string.restart_the_app)
else
builder1.setMessage(R.string.restart_the_app)
builder1.setCancelable(true)
builder1.setPositiveButton("Close" ) { dialog, _ ->
dialog.cancel()
}
val alert11 = builder1.create()
alert11.show()
true
}
} }
} }

View File

@ -1,13 +1,18 @@
package fr.forum_thalie.tsumugi.preferences package fr.riff_app.riff.preferences
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.* import androidx.preference.*
class MainPreferenceFragment : PreferenceFragmentCompat() { class MainPreferenceFragment : PreferenceFragmentCompat() {
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).supportActionBar?.title = context?.getString(R.string.settings)
}
@SuppressLint("ApplySharedPref") @SuppressLint("ApplySharedPref")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

View File

@ -1,13 +1,20 @@
package fr.forum_thalie.tsumugi.preferences package fr.riff_app.riff.preferences
import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.preference.* import androidx.preference.*
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import android.text.InputType import android.text.InputType
import fr.forum_thalie.tsumugi.alarm.RadioSleeper import androidx.appcompat.app.AppCompatActivity
import fr.riff_app.riff.alarm.RadioSleeper
class SleepFragment : PreferenceFragmentCompat() { class SleepFragment : PreferenceFragmentCompat() {
override fun onAttach(context: Context) {
(activity as AppCompatActivity).supportActionBar?.title = context.getString(R.string.setSleepTimer)
super.onAttach(context)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.sleep_preference, rootKey) setPreferencesFromResource(R.xml.sleep_preference, rootKey)

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui package fr.riff_app.riff.ui
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter import androidx.fragment.app.FragmentPagerAdapter

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.news package fr.riff_app.riff.ui.news
import java.util.* import java.util.*

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.news package fr.riff_app.riff.ui.news
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
@ -20,8 +20,8 @@ import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.widget.TextViewCompat import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.newsDisplayDatePattern import fr.riff_app.riff.newsDisplayDatePattern
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.net.URL import java.net.URL
@ -32,7 +32,7 @@ import java.util.*
// but without Picasso (just fetching image by myself.) // but without Picasso (just fetching image by myself.)
class ImageGetterAsyncTask( class ImageGetterAsyncTask(
private val context: Context, private val context: Context,
private val source: String, private val source: String?,
private val levelListDrawable: LevelListDrawable private val levelListDrawable: LevelListDrawable
) : ) :
AsyncTask<TextView?, Void?, Bitmap?>() { AsyncTask<TextView?, Void?, Bitmap?>() {

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.news package fr.riff_app.riff.ui.news
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -12,7 +12,7 @@ import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
class NewsFragment : Fragment() { class NewsFragment : Fragment() {
@ -43,9 +43,9 @@ class NewsFragment : Fragment() {
} }
newsViewModel.isWebViewLoaded = true newsViewModel.isWebViewLoaded = true
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "webview created") //[REMOVE LOG CALLS]Log.d(tag, "webview created")
} else { } else {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "webview already created!?") //[REMOVE LOG CALLS]Log.d(tag, "webview already created!?")
} }
newsViewModel.root.addOnLayoutChangeListener(orientationLayoutListener) newsViewModel.root.addOnLayoutChangeListener(orientationLayoutListener)
@ -101,7 +101,7 @@ class NewsFragment : Fragment() {
ViewModelProviders.of(this).get(NewsViewModel::class.java) ViewModelProviders.of(this).get(NewsViewModel::class.java)
newsViewModel.fetch(c = context!!, isPreloading = true) newsViewModel.fetch(c = context!!, isPreloading = true)
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "news fetched onCreate") //[REMOVE LOG CALLS]Log.d(tag, "news fetched onCreate")
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
} }
} }

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.news package fr.riff_app.riff.ui.news
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
@ -8,9 +8,9 @@ import android.webkit.WebView
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.prof.rssparser.Parser import com.prof.rssparser.Parser
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.newsDateTimePattern import fr.riff_app.riff.newsDateTimePattern
import fr.forum_thalie.tsumugi.tag import fr.riff_app.riff.tag
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -43,14 +43,14 @@ class NewsViewModel : ViewModel() {
val maxNumberOfArticles = 5 val maxNumberOfArticles = 5
coroutineScope.launch(Dispatchers.Main) { coroutineScope.launch(Dispatchers.Main) {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "launching coroutine") //[REMOVE LOG CALLS]Log.d(tag, "launching coroutine")
val parser = Parser() val parser = Parser()
try { try {
val articleList = parser.getArticles(urlToScrape) val articleList = parser.getArticles(urlToScrape)
newsArray.clear() newsArray.clear()
for (i in 0 until min(articleList.size, maxNumberOfArticles)) { for (i in 0 until min(articleList.size, maxNumberOfArticles)) {
val item = articleList[i] val item = articleList[i]
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "i = $i / ${articleList.size}") //[REMOVE LOG CALLS]Log.d(tag, "i = $i / ${articleList.size}")
val news = News() val news = News()
news.title = item.title ?: "" news.title = item.title ?: ""
news.link = item.link ?: urlToScrape news.link = item.link ?: urlToScrape
@ -60,7 +60,7 @@ class NewsViewModel : ViewModel() {
val formatter6 = SimpleDateFormat(newsDateTimePattern, Locale.ENGLISH) val formatter6 = SimpleDateFormat(newsDateTimePattern, Locale.ENGLISH)
val dateString = item.pubDate.toString() val dateString = item.pubDate.toString()
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$news --- $dateString") //[REMOVE LOG CALLS]Log.d(tag, "$news --- $dateString")
news.date = formatter6.parse(dateString) ?: Date(0) news.date = formatter6.parse(dateString) ?: Date(0)

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.news package fr.riff_app.riff.ui.news
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.nowplaying package fr.riff_app.riff.ui.nowplaying
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ClipboardManager import android.content.ClipboardManager
@ -6,8 +6,6 @@ import android.content.Context
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import android.os.Bundle import android.os.Bundle
import android.support.v4.media.session.PlaybackStateCompat import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import android.util.TypedValue
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -15,16 +13,14 @@ import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.widget.TextViewCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import fr.forum_thalie.tsumugi.* import fr.riff_app.riff.*
import fr.forum_thalie.tsumugi.alarm.RadioSleeper import fr.riff_app.riff.alarm.RadioSleeper
import fr.forum_thalie.tsumugi.planning.Planning import fr.riff_app.riff.planning.Planning
import fr.forum_thalie.tsumugi.playerstore.PlayerStore import fr.riff_app.riff.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.playerstore.Song import fr.riff_app.riff.playerstore.Song
import kotlinx.android.synthetic.main.fragment_nowplaying.*
class NowPlayingFragment : Fragment() { class NowPlayingFragment : Fragment() {
@ -49,12 +45,12 @@ class NowPlayingFragment : Fragment() {
val volumeText: TextView = root.findViewById(R.id.volume_text) val volumeText: TextView = root.findViewById(R.id.volume_text)
val progressBar: ProgressBar = root.findViewById(R.id.progressBar) val progressBar: ProgressBar = root.findViewById(R.id.progressBar)
val volumeIconImage : ImageView = root.findViewById(R.id.volume_icon) val volumeIconImage : ImageView = root.findViewById(R.id.volume_icon)
val currentProgrammeText: TextView = root.findViewById(R.id.current_programme)
val streamerPictureImageView: ImageView = root.findViewById(R.id.streamerPicture) val streamerPictureImageView: ImageView = root.findViewById(R.id.streamerPicture)
// Note: these values are not used in the generic app, but if you want to, you can use them. // Note: these values are not used in the generic app, but if you want to, you can use them.
val songTitleNextText: TextView = root.findViewById(R.id.text_song_title_next) val songTitleNextText: TextView = root.findViewById(R.id.text_song_title_next)
//val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next) val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next)
/* /*
val streamerNameText : TextView = root.findViewById(R.id.streamerName) val streamerNameText : TextView = root.findViewById(R.id.streamerName)
@ -69,15 +65,14 @@ class NowPlayingFragment : Fragment() {
listenersText,8, 16, 2, TypedValue.COMPLEX_UNIT_SP) listenersText,8, 16, 2, TypedValue.COMPLEX_UNIT_SP)
*/ */
/*
// trick : I can't observe the queue because it's an ArrayDeque that doesn't trigger any change... // trick : I can't observe the queue because it's an ArrayDeque that doesn't trigger any change...
// so I observe a dedicated Mutable that gets set when the queue is updated. // so I observe a dedicated Mutable that gets set when the queue is updated.
PlayerStore.instance.isQueueUpdated.observe(viewLifecycleOwner, Observer { PlayerStore.instance.isQueueUpdated.observe(viewLifecycleOwner, Observer {
val t = if (PlayerStore.instance.queue.size > 0) PlayerStore.instance.queue[0] else Song("No queue - ") // (it.peekFirst != null ? it.peekFirst : Song() ) val t = if (PlayerStore.instance.queue.size > 0) PlayerStore.instance.queue[0] else Song(noConnectionValue) // (it.peekFirst != null ? it.peekFirst : Song() )
songTitleNextText.text = t.title.value songTitleNextText.text = t.title.value
songArtistNextText.text = t.artist.value songArtistNextText.text = t.artist.value
}) })
/*
PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer { PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer {
streamerNameText.text = it streamerNameText.text = it
}) })
@ -92,10 +87,12 @@ class NowPlayingFragment : Fragment() {
songTitleText.text = it songTitleText.text = it
}) })
Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer { Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer {
songTitleNextText.text = it currentProgrammeText.text = "${requireContext().getString(R.string.current_programme)} $it"
}) })
PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer { PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer {
songArtistText.text = it songArtistText.text = it
}) })
@ -169,10 +166,10 @@ class NowPlayingFragment : Fragment() {
} }
}) })
seekBarVolume.setOnSeekBarChangeListener(nowPlayingViewModel.seekBarChangeListener)
seekBarVolume.progress = PlayerStore.instance.volume.value!! seekBarVolume.progress = PlayerStore.instance.volume.value!!
progressBar.max = 1000 seekBarVolume.setOnSeekBarChangeListener(nowPlayingViewModel.seekBarChangeListener)
progressBar.max = 100
progressBar.progress = 0 progressBar.progress = 0
syncPlayPauseButtonImage(root) syncPlayPauseButtonImage(root)
@ -187,10 +184,10 @@ class NowPlayingFragment : Fragment() {
val setClipboardListener: View.OnLongClickListener = View.OnLongClickListener { val setClipboardListener: View.OnLongClickListener = View.OnLongClickListener {
val text = PlayerStore.instance.currentSong.artist.value + " - " + PlayerStore.instance.currentSong.title.value val text = PlayerStore.instance.currentSong.artist.value + " - " + PlayerStore.instance.currentSong.title.value
val clipboard = context!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = android.content.ClipData.newPlainText("Copied Text", text) val clip = android.content.ClipData.newPlainText("Copied Text", text)
clipboard.setPrimaryClip(clip) clipboard.setPrimaryClip(clip)
val snackBarLength = if (preferenceStore.getBoolean("snackbarPersistent", true)) val snackBarLength = if (preferenceStore.getBoolean("snackbarPersistent", false))
Snackbar.LENGTH_INDEFINITE Snackbar.LENGTH_INDEFINITE
else Snackbar.LENGTH_LONG else Snackbar.LENGTH_LONG
val snackBar = Snackbar.make(it, "", snackBarLength) val snackBar = Snackbar.make(it, "", snackBarLength)
@ -273,7 +270,7 @@ class NowPlayingFragment : Fragment() {
(viewHeight*100)/viewWidth (viewHeight*100)/viewWidth
else else
100 100
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "orientation set") //[REMOVE LOG CALLS]Log.d(tag, "orientation set")
} }
override fun onResume() { override fun onResume() {

View File

@ -1,9 +1,11 @@
package fr.forum_thalie.tsumugi.ui.nowplaying package fr.riff_app.riff.ui.nowplaying
import android.widget.SeekBar import android.widget.SeekBar
import androidx.core.content.edit
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import fr.forum_thalie.tsumugi.playerstore.PlayerStore import fr.riff_app.riff.playerstore.PlayerStore
import fr.riff_app.riff.preferenceStore
class NowPlayingViewModel: ViewModel() { class NowPlayingViewModel: ViewModel() {
@ -21,6 +23,10 @@ class NowPlayingViewModel: ViewModel() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
// updated continuously as the user slides the thumb // updated continuously as the user slides the thumb
PlayerStore.instance.volume.value = progress PlayerStore.instance.volume.value = progress
preferenceStore.edit {
putInt("volume", progress)
commit()
}
} }
override fun onStartTrackingTouch(seekBar: SeekBar) { override fun onStartTrackingTouch(seekBar: SeekBar) {

View File

@ -1,18 +1,18 @@
package fr.forum_thalie.tsumugi.ui.programme package fr.riff_app.riff.ui.programme
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.* import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.planning.Planning import fr.riff_app.riff.colorAccent
import fr.forum_thalie.tsumugi.planning.Programme import fr.riff_app.riff.colorWhited
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.planning.Programme
import fr.riff_app.riff.weekdaysSundayFirst
import java.util.* import java.util.*
import kotlin.collections.ArrayList
class ProgrammeAdapter(private val dataSet: ArrayList<Programme>, private val day: String class ProgrammeAdapter(private val dataSet: ArrayList<Programme>, private val day: String
/*, /*,

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.programme package fr.riff_app.riff.ui.programme
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -9,10 +9,10 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.planning.Planning import fr.riff_app.riff.planning.Planning
import fr.forum_thalie.tsumugi.planning.Programme import fr.riff_app.riff.planning.Programme
import fr.forum_thalie.tsumugi.weekdays import fr.riff_app.riff.weekdays
class ProgrammeDayFragment : Fragment() { class ProgrammeDayFragment : Fragment() {
@ -32,13 +32,13 @@ class ProgrammeDayFragment : Fragment() {
viewModel.day = arguments?.getString("day") ?: "" viewModel.day = arguments?.getString("day") ?: ""
val root = inflater.inflate(R.layout.fragment_programme_day, container, false) val root = inflater.inflate(R.layout.fragment_programme_day, container, false)
Planning.instance.programmes.forEach { Planning.instance.programmes.forEach {
if (it.isThisDay(day = weekdays.indexOf(viewModel.day))) if (it.isThisDay(day = weekdays.indexOf(viewModel.day)))
programmeOfTheDay.add(it) programmeOfTheDay.add(it)
} }
viewManager = LinearLayoutManager(context) viewManager = LinearLayoutManager(context)
viewAdapter = viewAdapter = ProgrammeAdapter(programmeOfTheDay, viewModel.day)
ProgrammeAdapter(programmeOfTheDay, viewModel.day)
recyclerView = root.findViewById<RecyclerView>(R.id.programme_recycler).apply { recyclerView = root.findViewById<RecyclerView>(R.id.programme_recycler).apply {
// use this setting to improve performance if you know that changes // use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView // in content do not change the layout size of the RecyclerView

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.programme package fr.riff_app.riff.ui.programme
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.programme package fr.riff_app.riff.ui.programme
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
@ -9,10 +9,10 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.planning.Planning import fr.riff_app.riff.planning.Planning
import fr.forum_thalie.tsumugi.ui.APagerAdapter import fr.riff_app.riff.ui.APagerAdapter
import fr.forum_thalie.tsumugi.weekdays import fr.riff_app.riff.weekdays
import java.util.* import java.util.*
class ProgrammeFragment : Fragment() { class ProgrammeFragment : Fragment() {
@ -37,11 +37,11 @@ class ProgrammeFragment : Fragment() {
viewPager.adapter = adapter viewPager.adapter = adapter
val todaySundayFirst = Calendar.getInstance(Planning.instance.timeZone).get(Calendar.DAY_OF_WEEK) - 1 val todaySundayFirst = Calendar.getInstance(Planning.instance.timeZone).get(Calendar.DAY_OF_WEEK) - 1
viewPager.currentItem = (todaySundayFirst - 1)%7 viewPager.currentItem = (todaySundayFirst - 1 + 7)%7 // don't do modulos on negative, seems like it's weird
val tabLayout : TabLayout = root.findViewById(R.id.dayTabLayout) val tabLayout : TabLayout = root.findViewById(R.id.dayTabLayout)
tabLayout.setupWithViewPager(viewPager) tabLayout.setupWithViewPager(viewPager)
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created") //[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created")
return root return root
} }

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.songs package fr.riff_app.riff.ui.songs
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
@ -9,10 +9,10 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.ui.APagerAdapter import fr.riff_app.riff.ui.APagerAdapter
import fr.forum_thalie.tsumugi.ui.programme.ProgrammeDayFragment import fr.riff_app.riff.ui.programme.ProgrammeDayFragment
import fr.forum_thalie.tsumugi.ui.songs.queuelp.LastPlayedFragment import fr.riff_app.riff.ui.songs.queuelp.LastPlayedFragment
class SongsFragment : Fragment() { class SongsFragment : Fragment() {
@ -39,7 +39,7 @@ class SongsFragment : Fragment() {
val tabLayout : TabLayout = root.findViewById(R.id.tabLayout) val tabLayout : TabLayout = root.findViewById(R.id.tabLayout)
tabLayout.setupWithViewPager(viewPager) tabLayout.setupWithViewPager(viewPager)
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created") //[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created")
return root return root
} }

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.songs.queuelp package fr.riff_app.riff.ui.songs.queuelp
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
@ -9,8 +9,8 @@ import android.view.ViewGroup
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.playerstore.PlayerStore import fr.riff_app.riff.playerstore.PlayerStore
/** /**
* A simple [Fragment] subclass. * A simple [Fragment] subclass.
@ -31,7 +31,7 @@ class LastPlayedFragment : Fragment() {
private val queueObserver = Observer<Boolean> { private val queueObserver = Observer<Boolean> {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, lastPlayedFragmentTag + "queue changed") //[REMOVE LOG CALLS]Log.d(tag, lastPlayedFragmentTag + "queue changed")
viewAdapter.notifyDataSetChanged() viewAdapter.notifyDataSetChanged()
} }

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.songs.queuelp package fr.riff_app.riff.ui.songs.queuelp
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.Color import android.graphics.Color
@ -8,11 +8,11 @@ import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.R import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.colorBlue import fr.riff_app.riff.colorBlue
import fr.forum_thalie.tsumugi.colorWhited import fr.riff_app.riff.colorWhited
import fr.forum_thalie.tsumugi.playerstore.Song import fr.riff_app.riff.playerstore.Song
import fr.forum_thalie.tsumugi.tag import fr.riff_app.riff.tag
import kotlinx.android.synthetic.main.song_view.view.* import kotlinx.android.synthetic.main.song_view.view.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="#FF000000"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

View File

@ -1,11 +1,11 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque"> android:opacity="opaque">
<!-- The background color, preferably the same as your normal theme --> <!-- The background color, preferably the same as your normal theme -->
<item android:drawable="@color/colorAccent"/> <item android:drawable="@color/black"/>
<!-- Your product logo - 144dp color version of your app icon --> <!-- Your product logo - 144dp color version of your app icon -->
<item <item
android:drawable="@drawable/ic_tsumugi_lineart" android:drawable="@mipmap/ic_launcher"
android:gravity="center" android:gravity="center"
android:height="144dp" android:height="144dp"
android:width="144dp" android:width="144dp"

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" android:width="128dp"
android:height="108dp" android:height="128dp"
android:viewportWidth="108" android:viewportWidth="128"
android:viewportHeight="108" android:viewportHeight="128">
>
<path <path
android:fillColor="@color/colorAccent" android:strokeWidth="1"
android:pathData="M0,0h108v108h-108z" /> android:pathData="M-22.4,-18.02h249.46v189.63h-249.46z"
android:fillColor="#000000"
android:strokeColor="#ffffff"/>
</vector> </vector>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,13 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque"> android:opacity="opaque">
<!-- The background color, preferably the same as your normal theme --> <!-- The background color, preferably the same as your normal theme -->
<item android:drawable="@color/colorPrimaryDark"/> <item android:drawable="@color/black"/>
<!-- Your product logo - 144dp color version of your app icon --> <!-- Your product logo - 144dp color version of your app icon -->
<item <item
> >
<bitmap <bitmap
android:src="@drawable/splash_logo" android:src="@mipmap/ic_launcher"
android:gravity="center" android:gravity="center"
/> />
</item> </item>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -42,7 +42,7 @@
android:id="@+id/logo_persistent" android:id="@+id/logo_persistent"
android:layout_width="?attr/actionBarSize" android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:srcCompat="@drawable/logo_roundsquare" app:srcCompat="@mipmap/ic_launcher"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:padding="12dp" android:padding="12dp"

View File

@ -36,10 +36,10 @@
android:contentDescription="dj-image" android:contentDescription="dj-image"
android:scaleType="fitStart" android:scaleType="fitStart"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="@id/scrollViewMetadataNext"
app:layout_constraintStart_toStartOf="@id/imageGuideline" app:layout_constraintStart_toStartOf="@id/imageGuideline"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/logo_roundsquare" app:srcCompat="@mipmap/ic_launcher"
android:layout_margin="8dp" android:layout_margin="8dp"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<!-- app:layout_constraintBottom_toTopOf="@id/imageLeftGuideline" --> <!-- app:layout_constraintBottom_toTopOf="@id/imageLeftGuideline" -->
@ -49,7 +49,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintGuide_percent="0.68" /> app:layout_constraintGuide_percent="0.71" />
<androidx.constraintlayout.widget.Guideline <androidx.constraintlayout.widget.Guideline
android:id="@+id/imageLeftGuideline" android:id="@+id/imageLeftGuideline"
@ -93,13 +93,6 @@
android:visibility="gone" android:visibility="gone"
/> />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topInfoGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.95" />
<TextView <TextView
android:id="@+id/sleepInfo" android:id="@+id/sleepInfo"
android:layout_width="0dp" android:layout_width="0dp"
@ -147,7 +140,7 @@
<ImageView <ImageView
android:id="@+id/volume_icon" android:id="@+id/volume_icon"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="24dp" android:layout_height="20dp"
android:contentDescription="@string/volume" android:contentDescription="@string/volume"
android:src="@drawable/ic_volume_high" android:src="@drawable/ic_volume_high"
android:textSize="12sp" android:textSize="12sp"
@ -162,10 +155,12 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:fillViewport="true" android:fillViewport="true"
app:layout_constraintBottom_toTopOf="@id/topInfoGuideline" android:layout_marginBottom="4dp"
app:layout_constraintBottom_toTopOf="@id/scrollProgramme"
app:layout_constraintEnd_toStartOf="@id/streamerPicture" app:layout_constraintEnd_toStartOf="@id/streamerPicture"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/seek_bar_volume"> app:layout_constraintTop_toBottomOf="@id/seek_bar_volume"
>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -184,8 +179,8 @@
android:layout_gravity="top" android:layout_gravity="top"
android:text="@string/up_next" android:text="@string/up_next"
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/whited3" android:textColor="@color/whited5"
android:layout_marginTop="8dp" android:layout_marginTop="0dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -195,32 +190,94 @@
android:id="@+id/text_song_artist_next" android:id="@+id/text_song_artist_next"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="bottom" android:gravity="bottom|center_horizontal"
android:text="" android:text=""
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/whited3" android:textColor="@color/whited3"
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/upNext" app:layout_constraintTop_toBottomOf="@id/upNext"
android:visibility="gone"/> android:visibility="visible"/>
<TextView <TextView
android:id="@+id/text_song_title_next" android:id="@+id/text_song_title_next"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="bottom" android:gravity="bottom|center_horizontal"
android:text="" android:text=""
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/whited" android:textColor="@color/whited"
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_song_artist_next" /> app:layout_constraintTop_toBottomOf="@id/text_song_artist_next"
android:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView> </ScrollView>
<ScrollView
android:id="@+id/scrollProgramme"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fillViewport="true"
android:layout_marginBottom="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/scrollViewMetadataNext"
app:layout_constraintBottom_toBottomOf="@id/topInfoGuideline"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="visible"
>
<TextView
android:id="@+id/current_programme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top|center_horizontal"
android:text="@string/current_programme"
android:textAlignment="center"
android:textColor="@color/whited3"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="visible" />
<TextView
android:id="@+id/text_current_programme"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""
android:layout_marginStart="8sp"
android:layout_marginLeft="8sp"
android:gravity="start|center_horizontal"
android:textAlignment="textStart"
android:textColor="@color/whited"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@id/current_programme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topInfoGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.97" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -264,7 +321,7 @@
android:id="@+id/text_song_title" android:id="@+id/text_song_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="bottom" android:gravity="bottom|center_horizontal"
android:text="" android:text=""
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/whited" android:textColor="@color/whited"
@ -276,7 +333,7 @@
android:id="@+id/text_song_artist" android:id="@+id/text_song_artist"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="bottom" android:gravity="bottom|center_horizontal"
android:text="" android:text=""
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/whited3" android:textColor="@color/whited3"
@ -305,7 +362,7 @@
android:progressDrawable="@drawable/progress_bar_progress" android:progressDrawable="@drawable/progress_bar_progress"
app:layout_constraintBottom_toTopOf="@id/play_pause" app:layout_constraintBottom_toTopOf="@id/play_pause"
tools:layout_editor_absoluteX="0dp" tools:layout_editor_absoluteX="0dp"
android:visibility="gone"/> android:visibility="visible"/>
<!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR --> <!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
<TextView <TextView
@ -318,7 +375,7 @@
android:textAlignment="textEnd" android:textAlignment="textEnd"
app:layout_constraintEnd_toEndOf="@id/progressBar" app:layout_constraintEnd_toEndOf="@id/progressBar"
app:layout_constraintTop_toBottomOf="@id/progressBar" app:layout_constraintTop_toBottomOf="@id/progressBar"
android:visibility="gone"/> android:visibility="visible"/>
<!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR --> <!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
<TextView <TextView
@ -331,7 +388,7 @@
android:textAlignment="textStart" android:textAlignment="textStart"
app:layout_constraintStart_toStartOf="@id/progressBar" app:layout_constraintStart_toStartOf="@id/progressBar"
app:layout_constraintTop_toBottomOf="@id/progressBar" app:layout_constraintTop_toBottomOf="@id/progressBar"
android:visibility="gone"/> android:visibility="visible"/>
@ -346,7 +403,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintGuide_percent="0.58" /> app:layout_constraintGuide_percent="0.63" />
<ImageButton <ImageButton
@ -381,7 +438,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintGuide_percent="0.33" /> app:layout_constraintGuide_percent="0.38" />
<androidx.constraintlayout.widget.Guideline <androidx.constraintlayout.widget.Guideline
android:id="@+id/splitHorizontalLayout" android:id="@+id/splitHorizontalLayout"

View File

@ -15,23 +15,22 @@
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<!-- <!--
<item
android:id="@+id/action_refresh"
android:title="@string/action_refresh"
app:showAsAction="never"/>
-->
<!--
<item <item
android:id="@+id/action_bug_submit" android:id="@+id/action_bug_submit"
android:title="@string/action_bug_submit" android:title="@string/action_bug_submit"
app:showAsAction="never"/> app:showAsAction="never"/>
--> -->
<item
android:id="@+id/action_refresh"
android:title="@string/action_refresh"
app:showAsAction="never"/>
<item android:id="@+id/action_settings" <item android:id="@+id/action_settings"
android:title="@string/action_settings" android:title="@string/action_settings"
android:icon="@drawable/ic_settings" android:icon="@drawable/ic_settings"
app:showAsAction="ifRoom"/> app:showAsAction="never"/>
</menu> </menu>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Some files were not shown because too many files have changed in this diff Show More