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">
<code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</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"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Android Studio default JDK" />
<option name="modules">
<set>
<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" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="testRunner" value="PLATFORM" />
</GradleProjectSettings>
</option>
</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"?>
<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" />
</component>
<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">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/ExoPlayer" vcs="Git" />
</component>
</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 {
compileSdkVersion 29
buildToolsVersion "29.0.2"
compileSdkVersion 32
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
}
defaultConfig {
applicationId "fr.forum_thalie.tsumugi"
applicationId "fr.riff_app.riff"
minSdkVersion 16
targetSdkVersion 29
versionCode 100
versionName "1.0.0"
targetSdkVersion 32
multiDexEnabled true
versionCode 121
versionName "1.2.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
@ -48,6 +48,9 @@ android {
}
dependencies {
def multidex_version = "2.0.1"
implementation "androidx.multidex:multidex:$multidex_version"
implementation fileTree(dir: 'libs', include: ['*.jar'])
/*
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@ -68,15 +71,18 @@ dependencies {
*/
implementation "androidx.media:media:1.1.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.media:media:1.5.0"
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
//noinspection GradleDependency
implementation 'com.google.android.exoplayer:exoplayer:2.11.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
//implementation project(':exoplayer-library') // uses local ExoPlayer clone, from dev-v2 branch (fixes ISO-8859-1 ICY metadata
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/
// 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"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
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.INTERNET" />
@ -10,9 +10,11 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<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.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.SET_ALARM"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:allowBackup="true"
@ -21,27 +23,25 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme.Launcher"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
tools:ignore="AllowBackup,GoogleAppIndexingWarning"
android:name="androidx.multidex.MultiDexApplication">
<service
android:name=".RadioService"
android:enabled="true"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<service android:name=".streamerNotificationService.StreamerMonitorService"
android:enabled="true"
android:exported="true"/>
<receiver android:name=".BootBroadcastReceiver"
android:directBootAware="true"
tools:targetApi="n">
tools:targetApi="n"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="fr.forum_thalie.tsumugi.PLAY_OR_FALLBACK" />
<action android:name="fr.forum_thalie.tsumugi.NOTIFY" /> <!-- this is for Snooze -->
<action android:name="fr.riff_app.riff.PLAY_OR_FALLBACK" />
<action android:name="fr.riff_app.riff.NOTIFY" /> <!-- this is for Snooze -->
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
@ -49,9 +49,9 @@
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="fullSensor">
android:screenOrientation="fullUser"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -63,9 +63,10 @@
android:configChanges="orientation|screenSize"
android:launchMode="singleTask"
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>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>

View File

@ -1,66 +1,78 @@
{
"planning": [
{
"title": "Musique Classique",
"title": "La playlist Base Rock",
"periodicity": "1111111",
"hour_begin": "6: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_begin": "00:00",
"hour_end": "21:00"
},
{
"title": "Anime Nuit",
"periodicity": "1111111",
"hour_begin": "00:00",
"hour_end": "01:00"
"title": "Only Covers",
"periodicity": "1000000",
"hour_begin": "20:30",
"hour_end": "00:00"
},
{
"title": "Jeux Vidéos Nuit",
"periodicity": "1111111",
"hour_begin": "01:00",
"hour_end": "02:00"
"title": "Only Indies",
"periodicity": "0100000",
"hour_begin": "20:30",
"hour_end": "00:00"
},
{
"title": "Programmation nocturne",
"periodicity": "1111111",
"hour_begin": "02:00",
"hour_end": "06:00"
"title": "Only Acoustic",
"periodicity": "0010000",
"hour_begin": "20:30",
"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",
"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
{

View File

@ -1,8 +1,8 @@
package fr.forum_thalie.tsumugi
package fr.riff_app.riff
import android.os.AsyncTask
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 = {},
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()
} 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? {
try {
return handler(parameters)
} catch (e: Exception) {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag,e.toString())
//[REMOVE LOG CALLS]Log.d(tag,e.toString())
onException(e)
}
return null
@ -63,7 +63,7 @@ class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {},
try {
post(result)
} catch (e: Exception) {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag,e.toString())
//[REMOVE LOG CALLS]Log.d(tag,e.toString())
onException(e)
}
}

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi
package fr.riff_app.riff
import android.content.Intent
import android.os.Bundle
@ -21,7 +21,7 @@ abstract class BaseActivity : AppCompatActivity() {
val height = ((rootLayout?.height ?: 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)
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
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
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() {
// do things when keyboard is hidden
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
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() {

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi
package fr.riff_app.riff
import android.app.Notification
import android.app.NotificationChannel
@ -50,6 +50,7 @@ abstract class BaseNotification(private val notificationChannelId: String,
notificationManager.notify(notificationId, notification)
}
@RequiresApi(Build.VERSION_CODES.M)
open fun create(c: Context) {
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"
val pendingIntent = PendingIntent.getActivity(
c, 0,
notificationIntent, 0
notificationIntent, PendingIntent.FLAG_IMMUTABLE
)
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.Context
@ -6,13 +6,14 @@ import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.preference.PreferenceManager
import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import fr.riff_app.riff.alarm.RadioAlarm
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.playerstore.PlayerStore
class BootBroadcastReceiver : BroadcastReceiver(){
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
preferenceStore = PreferenceManager.getDefaultSharedPreferences(context)
@ -22,7 +23,11 @@ class BootBroadcastReceiver : BroadcastReceiver(){
if (arg1.getStringExtra("action") == "$tag.${Actions.PLAY_OR_FALLBACK.name}" )
{
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())
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 com.google.android.material.bottomnavigation.BottomNavigationView
@ -11,12 +11,13 @@ import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.navigation.NavController
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import fr.riff_app.riff.playerstore.PlayerStore
import java.util.Timer
import android.view.MenuItem
import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.planning.Planning
import com.google.android.material.snackbar.Snackbar
import fr.riff_app.riff.alarm.RadioAlarm
import fr.riff_app.riff.planning.Planning
/* Log to file import
@ -86,6 +87,14 @@ class MainActivity : BaseActivity() {
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 -> {
val i = Intent(this, ParametersActivity::class.java)
startActivity(i)
@ -108,7 +117,7 @@ class MainActivity : BaseActivity() {
}
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState ?: Bundle())
// Now that BottomNavigationBar has restored its instance state
// 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))
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
if (PlayerStore.instance.isInitialized)
{
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "skipped initialization")
//[REMOVE LOG CALLS]Log.d(tag, "skipped initialization")
} else {
// 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).
@ -160,9 +175,6 @@ class MainActivity : BaseActivity() {
isTimerStarted = true
}
// fetch program
Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
// initialize the UI
setTheme(R.style.AppTheme)
setContentView(R.layout.activity_main)
@ -193,7 +205,7 @@ class MainActivity : BaseActivity() {
val i = Intent(this, RadioService::class.java)
i.putExtra("action", a.name)
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)
}
@ -238,7 +250,7 @@ class MainActivity : BaseActivity() {
// File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder")
val logDirectory = File("$appDirectory/log")
val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(
//[REMOVE LOG CALLS]Log.d(
tag,
"appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile"
)
@ -246,20 +258,20 @@ class MainActivity : BaseActivity() {
// create app folder
if (!appDirectory.exists()) {
appDirectory.mkdir()
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created")
//[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created")
}
// create log folder
if (!logDirectory.exists()) {
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
try {
Runtime.getRuntime().exec("logcat -c")
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) {
e.printStackTrace()
}

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi
package fr.riff_app.riff
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.content.Context
import android.content.Intent
import android.os.Build
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import fr.riff_app.riff.playerstore.PlayerStore
class NowPlayingNotification(
notificationChannelId: String,
@ -34,18 +36,19 @@ class NowPlayingNotification(
// got it right
val delIntent = Intent(c, RadioService::class.java)
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)
mediaStyle = androidx.media.app.NotificationCompat.DecoratedMediaCustomViewStyle().also {
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)
}
builder.setStyle(mediaStyle)
update(c)
}
@RequiresApi(Build.VERSION_CODES.M)
fun update(c: Context, isUpdatingNotificationButton: Boolean = false, isRinging: Boolean = false) {
if (isUpdatingNotificationButton)
@ -72,18 +75,18 @@ class NowPlayingNotification(
playPauseAction = if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING) {
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()
} else {
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()
}
builder.addAction(playPauseAction)
val intent2 = Intent(c, RadioService::class.java)
intent2.putExtra("action", Actions.KILL.name)
val pendingButtonIntent = PendingIntent.getService(c, 2, intent2, PendingIntent.FLAG_UPDATE_CURRENT)
val stopAction = NotificationCompat.Action.Builder(R.drawable.ic_stop,"Stop", pendingButtonIntent).build()
val pendingButtonIntent = PendingIntent.getService(c, 2, intent2, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE)
val stopAction = NotificationCompat.Action.Builder(R.drawable.ic_close,"Stop", pendingButtonIntent).build()
builder.addAction(stopAction)
if (isRinging) {
@ -92,7 +95,7 @@ class NowPlayingNotification(
val snoozeIntent = Intent(c, RadioService::class.java)
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()
if (snoozeMinutes > 0)
builder.addAction(snoozeAction)

View File

@ -1,7 +1,8 @@
package fr.forum_thalie.tsumugi
package fr.riff_app.riff
import android.os.Bundle
import fr.forum_thalie.tsumugi.preferences.*
import android.view.MenuItem
import fr.riff_app.riff.preferences.*
class ParametersActivity : BaseActivity() {
@ -38,4 +39,15 @@ class ParametersActivity : BaseActivity() {
.replace(R.id.parameters_host_container, fragmentToLoad)
.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.content.BroadcastReceiver
@ -32,10 +32,10 @@ import androidx.media.AudioManagerCompat
import androidx.preference.PreferenceManager
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.metadata.icy.*
import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.alarm.RadioSleeper
import fr.forum_thalie.tsumugi.planning.Planning
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import fr.riff_app.riff.alarm.RadioAlarm
import fr.riff_app.riff.alarm.RadioSleeper
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.playerstore.PlayerStore
import java.util.*
import kotlin.math.exp
import kotlin.math.ln
@ -68,14 +68,14 @@ class RadioService : MediaBrowserServiceCompat() {
// This *should* work in any case...
when (intent.getIntExtra("state", -1)) {
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 -> {
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is plugged")
headsetPluggedIn = true
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is plugged")
headsetPluggedIn = true && PreferenceManager.getDefaultSharedPreferences(context).getBoolean("autoStartOnPlug", false)
}
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
{
//[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> {
// 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
&& it != noConnectionValue)
{
PlayerStore.instance.updateLp()
PlayerStore.instance.updateQueue()
}
nowPlayingNotification.update(this)
Planning.instance.checkProgramme()
@ -163,9 +185,16 @@ class RadioService : MediaBrowserServiceCompat() {
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
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
//define the audioFocusRequest
@ -205,7 +234,7 @@ class RadioService : MediaBrowserServiceCompat() {
startForeground(radioServiceId, nowPlayingNotification.notification)
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()
@ -246,7 +275,7 @@ class RadioService : MediaBrowserServiceCompat() {
Actions.CANCEL_FADE_OUT.name -> { handler.removeCallbacks(lowerVolumeRunnable) }
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)
// 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
@ -258,7 +287,7 @@ class RadioService : MediaBrowserServiceCompat() {
stopSelf()
}
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() {
@ -290,7 +319,7 @@ class RadioService : MediaBrowserServiceCompat() {
}
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.
exitProcess(0)
}
@ -361,10 +390,10 @@ class RadioService : MediaBrowserServiceCompat() {
for (i in 0 until it.length()) {
val entry = it.get(i)
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) {
//[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.
numberOfSongs++
val data = entry.title!!
@ -399,7 +428,7 @@ class RadioService : MediaBrowserServiceCompat() {
)
// This is the MediaSource representing the media to be played.
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)
.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.
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)
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!!)
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
var i = 0
@ -439,7 +469,7 @@ class RadioService : MediaBrowserServiceCompat() {
{
Thread.sleep(1000)
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 = {
@ -470,6 +500,7 @@ class RadioService : MediaBrowserServiceCompat() {
.setUsage(C.USAGE_ALARM)
.build()
} 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)
audioFocusRequestBuilder.setAudioAttributes(audioAttributes.build())
audioFocusRequest = audioFocusRequestBuilder.build()
@ -484,9 +515,11 @@ class RadioService : MediaBrowserServiceCompat() {
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
return
}
if (mediaSession.controller.playbackState.state == PlaybackStateCompat.STATE_PLAYING && !isRinging && isAlarmStopped)
{
return //nothing to do here
}
if (mediaSession.controller.playbackState.state == PlaybackStateCompat.STATE_PLAYING && !isRinging)
return // nothing to do here
PlayerStore.instance.playbackState.value = PlaybackStateCompat.STATE_PLAYING
// Reinitialize media player. Otherwise the playback doesn't resume when beginPlaying. Dunno why.
@ -512,7 +545,8 @@ class RadioService : MediaBrowserServiceCompat() {
SystemClock.elapsedRealtime()
)
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()
@ -525,7 +559,6 @@ class RadioService : MediaBrowserServiceCompat() {
{
if (mediaSession.controller.playbackState.state == PlaybackStateCompat.STATE_STOPPED)
return // nothing to do here
if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING)
isAlarmStopped = true
@ -541,7 +574,7 @@ class RadioService : MediaBrowserServiceCompat() {
1.0f,
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())
}
@ -629,7 +662,7 @@ class RadioService : MediaBrowserServiceCompat() {
Player.STATE_ENDED -> state = "Player.STATE_ENDED"
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.res.ColorStateList
import kotlin.collections.ArrayList
const val tag = "fr.forum_thalie.tsumugi"
const val tag = "fr.riff_app.riff"
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")
const val newsDateTimePattern = "EEE, d MMM yyyy HH:mm:ss Z"
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.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.AlarmManagerCompat
import fr.forum_thalie.tsumugi.BootBroadcastReceiver
import fr.riff_app.riff.BootBroadcastReceiver
import androidx.preference.PreferenceManager
import fr.forum_thalie.tsumugi.*
import fr.riff_app.riff.*
import java.util.*
import kotlin.collections.ArrayList
@ -27,13 +29,31 @@ class RadioAlarm {
{
alarmIntent = Intent(c, BootBroadcastReceiver::class.java).let { intent ->
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 ->
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)
}
}
}
fun cancelAlarm(c: Context)
@ -43,6 +63,7 @@ class RadioAlarm {
alarmManager.cancel(alarmIntent)
}
@RequiresApi(Build.VERSION_CODES.M)
fun setNextAlarm(c: Context, isForce: Boolean = false, forceTime: Int? = null, forceDays: Set<String>? = null)
{
defineIntents(c)
@ -54,7 +75,7 @@ class RadioAlarm {
val showIntent = Intent(c, ParametersActivity::class.java).let { intent ->
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)
if (time > 0)
@ -97,7 +118,7 @@ class RadioAlarm {
calendar.isLenient = true
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

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.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.AlarmManagerCompat
import androidx.lifecycle.MutableLiveData
import androidx.preference.PreferenceManager
import fr.forum_thalie.tsumugi.*
import fr.riff_app.riff.*
class RadioSleeper {
@ -30,18 +32,20 @@ class RadioSleeper {
private lateinit var sleepIntent: PendingIntent
private lateinit var fadeOutIntent: PendingIntent
@RequiresApi(Build.VERSION_CODES.M)
private fun defineIntents(c: Context)
{
sleepIntent = Intent(c, RadioService::class.java).let { intent ->
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 ->
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)
{
defineIntents(c)
@ -61,7 +65,7 @@ class RadioSleeper {
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
//[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)
}
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "cancelled sleep")
//[REMOVE LOG CALLS]Log.d(tag, "cancelled sleep")
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 androidx.lifecycle.MutableLiveData
import fr.forum_thalie.tsumugi.Async
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.noConnectionValue
import fr.forum_thalie.tsumugi.weekdays
import fr.riff_app.riff.Async
import fr.riff_app.riff.R
import fr.riff_app.riff.noConnectionValue
import fr.riff_app.riff.weekdays
import org.json.JSONObject
import java.io.IOException
import java.net.URL
@ -64,6 +64,7 @@ class Planning {
if (result.has("planning"))
{
val programList = result.getJSONArray("planning")
programmes.clear()
for (i in 0 until programList.length())
{
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 fr.forum_thalie.tsumugi.tag
import fr.forum_thalie.tsumugi.weekdays
import fr.riff_app.riff.tag
import fr.riff_app.riff.weekdays
import java.util.*
import kotlin.collections.ArrayList
@ -35,7 +35,7 @@ class Programme (val title: String, private val periodicity: Int, private val ho
val isSpanningOverNight =
(((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.
// 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 {
//[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 fr.forum_thalie.tsumugi.noConnectionValue
import fr.riff_app.riff.noConnectionValue
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"
}
fun setTitleArtist(data: String)
fun setTitleArtist(dataHtml: String)
{
val data = HtmlCompat.fromHtml(dataHtml, HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
val hyphenPos = data.indexOf(" - ")
try {
if (hyphenPos < 0)
@ -46,12 +48,12 @@ class Song(artistTitle: String = "", _id : Int = 0) {
override fun equals(other: Any?) : Boolean
{
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) {
this.title.value = song.title.value
this.artist.value = song.artist.value
this.title. value = song.title.value
this.startTime.value = song.startTime.value
this.stopTime.value = song.stopTime.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.content.Context
import android.content.Intent
import android.media.AudioManager
import android.os.Bundle
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.preference.*
import fr.forum_thalie.tsumugi.*
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import fr.riff_app.riff.*
import fr.riff_app.riff.R
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 kotlin.coroutines.CoroutineContext
import kotlin.math.max
import kotlin.math.min
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?) {
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)
{
val dateLong = RadioAlarm.instance.findNextAlarmTime(context!!, forceTime, forceDays)
val dateLong = RadioAlarm.instance.findNextAlarmTime(requireContext(), forceTime, forceDays)
val calendar = Calendar.getInstance()
calendar.timeInMillis = dateLong
if (newValue == true && calendar.timeInMillis > 0)
@ -43,14 +65,14 @@ class AlarmFragment : PreferenceFragmentCompat() {
val hourOfDayDefault = 7
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))
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 minute = time % 60
timeSet?.summary = "${if (hourOfDay < 10) "0" else ""}$hourOfDay:${if (minute < 10) "0" else ""}$minute"
@ -58,16 +80,16 @@ class AlarmFragment : PreferenceFragmentCompat() {
timeSet?.setOnPreferenceClickListener {
val timePickerOnTimeSetListener = TimePickerDialog.OnTimeSetListener { _, hourOfDay, minute ->
PreferenceManager.getDefaultSharedPreferences(context!!).edit {
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit {
putInt("alarmTimeFromMidnight", (60*hourOfDay+minute))
commit()
}
timeSet.summary = "${if (hourOfDay < 10) "0" else ""}$hourOfDay:${if (minute < 10) "0" else ""}$minute"
RadioAlarm.instance.cancelAlarm(context!!)
RadioAlarm.instance.setNextAlarm(context!!, isForce = true, forceTime = hourOfDay*60+minute)
RadioAlarm.instance.cancelAlarm(requireContext())
RadioAlarm.instance.setNextAlarm(requireContext(), isForce = true, 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 minuteNew = timeNew % 60
val timePicker = TimePickerDialog(context, timePickerOnTimeSetListener, hourOfDayNew, minuteNew, true)
@ -81,8 +103,8 @@ class AlarmFragment : PreferenceFragmentCompat() {
val fullWeek = weekdays.toSet()
val workingWeek = weekdays.toSet().minusElement(weekdays.last()).minusElement(weekdays.elementAt(weekdays.size - 2))
val listOfDays = when (newValue) {
fullWeek -> context!!.getString(R.string.every_day)
workingWeek -> context!!.getString(R.string.working_days)
fullWeek -> requireContext().getString(R.string.every_day)
workingWeek -> requireContext().getString(R.string.working_days)
else -> {
// build ORDERED LIST of days... I don't know why the original one is in shambles!!
val fullWeekOrdered = weekdays
@ -97,12 +119,12 @@ class AlarmFragment : PreferenceFragmentCompat() {
preference?.summary = listOfDays
}
updateDays(alarmDays, PreferenceManager.getDefaultSharedPreferences(context).getStringSet("alarmDays", setOf()))
updateDays(alarmDays, PreferenceManager.getDefaultSharedPreferences(requireContext()).getStringSet("alarmDays", setOf()))
alarmDays?.setOnPreferenceChangeListener { preference, newValue ->
@Suppress("UNCHECKED_CAST")
updateDays(preference as MultiSelectListPreference, newValue as Set<String>)
RadioAlarm.instance.cancelAlarm(context!!)
RadioAlarm.instance.setNextAlarm(context!!, isForce = true, forceDays = newValue)
RadioAlarm.instance.cancelAlarm(requireContext())
RadioAlarm.instance.setNextAlarm(requireContext(), isForce = true, forceDays = newValue)
updateIsWakingUpSummary(isWakingUp, isWakingUp?.isChecked, forceDays = newValue)
true
}
@ -111,9 +133,9 @@ class AlarmFragment : PreferenceFragmentCompat() {
isWakingUp?.setOnPreferenceChangeListener { _, newValue ->
if (newValue as Boolean)
RadioAlarm.instance.setNextAlarm(context!!, isForce = true)
RadioAlarm.instance.setNextAlarm(requireContext(), isForce = true)
else
RadioAlarm.instance.cancelAlarm(context!!)
RadioAlarm.instance.cancelAlarm(requireContext())
timeSet?.isEnabled = newValue
alarmDays?.isEnabled = newValue
snoozeDuration?.isEnabled = newValue
@ -121,12 +143,10 @@ class AlarmFragment : PreferenceFragmentCompat() {
true
}
alarmDays?.isEnabled = isWakingUp?.isChecked ?: false
timeSet?.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.net.Uri
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.*
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.preferenceStore
import fr.riff_app.riff.R
import fr.riff_app.riff.preferenceStore
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?) {
setPreferencesFromResource(R.xml.customize_preferences, rootKey)
val snackbarPersistent = preferenceScreen.findPreference<SwitchPreferenceCompat>("snackbarPersistent")
snackbarPersistent!!.summary = if (preferenceStore.getBoolean("snackbarPersistent", true))
snackbarPersistent!!.summary = if (preferenceStore.getBoolean("snackbarPersistent", false))
getString(R.string.snackbarPersistent)
else
getString(R.string.snackbarNonPersistent)
@ -40,5 +44,25 @@ class CustomizeFragment : PreferenceFragmentCompat() {
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.net.Uri
import android.os.Bundle
import fr.forum_thalie.tsumugi.R
import fr.riff_app.riff.R
import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.*
class MainPreferenceFragment : PreferenceFragmentCompat() {
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).supportActionBar?.title = context?.getString(R.string.settings)
}
@SuppressLint("ApplySharedPref")
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 androidx.preference.*
import fr.forum_thalie.tsumugi.R
import fr.riff_app.riff.R
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() {
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?) {
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.FragmentPagerAdapter

View File

@ -1,4 +1,4 @@
package fr.forum_thalie.tsumugi.ui.news
package fr.riff_app.riff.ui.news
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.app.Activity
@ -20,8 +20,8 @@ import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.newsDisplayDatePattern
import fr.riff_app.riff.R
import fr.riff_app.riff.newsDisplayDatePattern
import java.io.IOException
import java.io.InputStream
import java.net.URL
@ -32,7 +32,7 @@ import java.util.*
// but without Picasso (just fetching image by myself.)
class ImageGetterAsyncTask(
private val context: Context,
private val source: String,
private val source: String?,
private val levelListDrawable: LevelListDrawable
) :
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.Bundle
@ -12,7 +12,7 @@ import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import fr.forum_thalie.tsumugi.R
import fr.riff_app.riff.R
class NewsFragment : Fragment() {
@ -43,9 +43,9 @@ class NewsFragment : Fragment() {
}
newsViewModel.isWebViewLoaded = true
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "webview created")
//[REMOVE LOG CALLS]Log.d(tag, "webview created")
} 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)
@ -101,7 +101,7 @@ class NewsFragment : Fragment() {
ViewModelProviders.of(this).get(NewsViewModel::class.java)
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)
}
}

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.os.Build
@ -8,9 +8,9 @@ import android.webkit.WebView
import androidx.lifecycle.ViewModel
import androidx.recyclerview.widget.RecyclerView
import com.prof.rssparser.Parser
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.newsDateTimePattern
import fr.forum_thalie.tsumugi.tag
import fr.riff_app.riff.R
import fr.riff_app.riff.newsDateTimePattern
import fr.riff_app.riff.tag
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -43,14 +43,14 @@ class NewsViewModel : ViewModel() {
val maxNumberOfArticles = 5
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()
try {
val articleList = parser.getArticles(urlToScrape)
newsArray.clear()
for (i in 0 until min(articleList.size, maxNumberOfArticles)) {
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()
news.title = item.title ?: ""
news.link = item.link ?: urlToScrape
@ -60,7 +60,7 @@ class NewsViewModel : ViewModel() {
val formatter6 = SimpleDateFormat(newsDateTimePattern, Locale.ENGLISH)
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)

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.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.content.ClipboardManager
@ -6,8 +6,6 @@ import android.content.Context
import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import android.util.TypedValue
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
@ -15,16 +13,14 @@ import android.view.ViewGroup
import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.widget.TextViewCompat
import androidx.lifecycle.Observer
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import fr.forum_thalie.tsumugi.*
import fr.forum_thalie.tsumugi.alarm.RadioSleeper
import fr.forum_thalie.tsumugi.planning.Planning
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.playerstore.Song
import kotlinx.android.synthetic.main.fragment_nowplaying.*
import fr.riff_app.riff.*
import fr.riff_app.riff.alarm.RadioSleeper
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.playerstore.PlayerStore
import fr.riff_app.riff.playerstore.Song
class NowPlayingFragment : Fragment() {
@ -49,12 +45,12 @@ class NowPlayingFragment : Fragment() {
val volumeText: TextView = root.findViewById(R.id.volume_text)
val progressBar: ProgressBar = root.findViewById(R.id.progressBar)
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)
// 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 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)
@ -69,15 +65,14 @@ class NowPlayingFragment : Fragment() {
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...
// so I observe a dedicated Mutable that gets set when the queue is updated.
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
songArtistNextText.text = t.artist.value
})
/*
PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer {
streamerNameText.text = it
})
@ -92,10 +87,12 @@ class NowPlayingFragment : Fragment() {
songTitleText.text = it
})
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 {
songArtistText.text = it
})
@ -169,10 +166,10 @@ class NowPlayingFragment : Fragment() {
}
})
seekBarVolume.setOnSeekBarChangeListener(nowPlayingViewModel.seekBarChangeListener)
seekBarVolume.progress = PlayerStore.instance.volume.value!!
progressBar.max = 1000
seekBarVolume.setOnSeekBarChangeListener(nowPlayingViewModel.seekBarChangeListener)
progressBar.max = 100
progressBar.progress = 0
syncPlayPauseButtonImage(root)
@ -187,10 +184,10 @@ class NowPlayingFragment : Fragment() {
val setClipboardListener: View.OnLongClickListener = View.OnLongClickListener {
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)
clipboard.setPrimaryClip(clip)
val snackBarLength = if (preferenceStore.getBoolean("snackbarPersistent", true))
val snackBarLength = if (preferenceStore.getBoolean("snackbarPersistent", false))
Snackbar.LENGTH_INDEFINITE
else Snackbar.LENGTH_LONG
val snackBar = Snackbar.make(it, "", snackBarLength)
@ -273,7 +270,7 @@ class NowPlayingFragment : Fragment() {
(viewHeight*100)/viewWidth
else
100
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "orientation set")
//[REMOVE LOG CALLS]Log.d(tag, "orientation set")
}
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 androidx.core.content.edit
import androidx.lifecycle.MutableLiveData
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() {
@ -21,6 +23,10 @@ class NowPlayingViewModel: ViewModel() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
// updated continuously as the user slides the thumb
PlayerStore.instance.volume.value = progress
preferenceStore.edit {
putInt("volume", progress)
commit()
}
}
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.util.TypedValue
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.*
import fr.forum_thalie.tsumugi.planning.Planning
import fr.forum_thalie.tsumugi.planning.Programme
import fr.riff_app.riff.R
import fr.riff_app.riff.colorAccent
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 kotlin.collections.ArrayList
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.view.LayoutInflater
@ -9,10 +9,10 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.planning.Planning
import fr.forum_thalie.tsumugi.planning.Programme
import fr.forum_thalie.tsumugi.weekdays
import fr.riff_app.riff.R
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.planning.Programme
import fr.riff_app.riff.weekdays
class ProgrammeDayFragment : Fragment() {
@ -32,13 +32,13 @@ class ProgrammeDayFragment : Fragment() {
viewModel.day = arguments?.getString("day") ?: ""
val root = inflater.inflate(R.layout.fragment_programme_day, container, false)
Planning.instance.programmes.forEach {
if (it.isThisDay(day = weekdays.indexOf(viewModel.day)))
programmeOfTheDay.add(it)
}
viewManager = LinearLayoutManager(context)
viewAdapter =
ProgrammeAdapter(programmeOfTheDay, viewModel.day)
viewAdapter = ProgrammeAdapter(programmeOfTheDay, viewModel.day)
recyclerView = root.findViewById<RecyclerView>(R.id.programme_recycler).apply {
// use this setting to improve performance if you know that changes
// 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

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.util.Log
@ -9,10 +9,10 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.planning.Planning
import fr.forum_thalie.tsumugi.ui.APagerAdapter
import fr.forum_thalie.tsumugi.weekdays
import fr.riff_app.riff.R
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.ui.APagerAdapter
import fr.riff_app.riff.weekdays
import java.util.*
class ProgrammeFragment : Fragment() {
@ -37,11 +37,11 @@ class ProgrammeFragment : Fragment() {
viewPager.adapter = adapter
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)
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
}

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.util.Log
@ -9,10 +9,10 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.ui.APagerAdapter
import fr.forum_thalie.tsumugi.ui.programme.ProgrammeDayFragment
import fr.forum_thalie.tsumugi.ui.songs.queuelp.LastPlayedFragment
import fr.riff_app.riff.R
import fr.riff_app.riff.ui.APagerAdapter
import fr.riff_app.riff.ui.programme.ProgrammeDayFragment
import fr.riff_app.riff.ui.songs.queuelp.LastPlayedFragment
class SongsFragment : Fragment() {
@ -39,7 +39,7 @@ class SongsFragment : Fragment() {
val tabLayout : TabLayout = root.findViewById(R.id.tabLayout)
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
}

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.util.Log
@ -9,8 +9,8 @@ import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import fr.riff_app.riff.R
import fr.riff_app.riff.playerstore.PlayerStore
/**
* A simple [Fragment] subclass.
@ -31,7 +31,7 @@ class LastPlayedFragment : Fragment() {
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()
}

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.graphics.Color
@ -8,11 +8,11 @@ import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.colorBlue
import fr.forum_thalie.tsumugi.colorWhited
import fr.forum_thalie.tsumugi.playerstore.Song
import fr.forum_thalie.tsumugi.tag
import fr.riff_app.riff.R
import fr.riff_app.riff.colorBlue
import fr.riff_app.riff.colorWhited
import fr.riff_app.riff.playerstore.Song
import fr.riff_app.riff.tag
import kotlinx.android.synthetic.main.song_view.view.*
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"
android:opacity="opaque">
<!-- 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 -->
<item
android:drawable="@drawable/ic_tsumugi_lineart"
android:drawable="@mipmap/ic_launcher"
android:gravity="center"
android:height="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"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108"
>
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:fillColor="@color/colorAccent"
android:pathData="M0,0h108v108h-108z" />
android:strokeWidth="1"
android:pathData="M-22.4,-18.02h249.46v189.63h-249.46z"
android:fillColor="#000000"
android:strokeColor="#ffffff"/>
</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"
android:opacity="opaque">
<!-- 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 -->
<item
>
<bitmap
android:src="@drawable/splash_logo"
android:src="@mipmap/ic_launcher"
android:gravity="center"
/>
</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:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
app:srcCompat="@drawable/logo_roundsquare"
app:srcCompat="@mipmap/ic_launcher"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:padding="12dp"

View File

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

View File

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