Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f37edb81e | ||
|
|
6f5adc687e |
No files matched your search
+3
-1
@@ -10,5 +10,7 @@ captures/
|
||||
keystore.properties
|
||||
*.jks
|
||||
.gitea-token
|
||||
Cachette-*.apk
|
||||
# Motif large : un motif au nom du projet a laissé passer 95 Mo lors du renommage.
|
||||
*.apk
|
||||
update-token.properties
|
||||
app/src/main/assets/CHANGELOG.md
|
||||
+30
-3
@@ -1,10 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
Toutes les évolutions notables de Cachette sont documentées ici.
|
||||
Toutes les évolutions notables de Noxely sont documentées ici.
|
||||
|
||||
Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/)
|
||||
et le versionnage [SemVer](https://semver.org/lang/fr/).
|
||||
|
||||
## [1.6.0] — 2026-09-13
|
||||
|
||||
### Changé
|
||||
|
||||
- **L'application s'appelle désormais Noxely.** Le nom affiché, l'identifiant d'application
|
||||
(`fr.tuxgyver.noxely`), le paquet Kotlin, le dépôt et le dossier d'enregistrements
|
||||
(`Music/Noxely`) suivent tous.
|
||||
|
||||
⚠️ **Android considère le nouvel identifiant comme une application distincte.** Une
|
||||
installation en 1.5.0 ne se met donc pas à jour toute seule : il faut la désinstaller, puis
|
||||
installer Noxely. Les enregistrements déjà rangés dans `Music/Cachette` ne sont pas perdus —
|
||||
ils sont dans le stockage partagé, restent lisibles par tout lecteur, et le scan continue de
|
||||
les écarter pour ne pas les afficher en double.
|
||||
|
||||
- Nouvelle icône. Le visuel fourni est détouré de son fond, décliné aux cinq densités et posé
|
||||
sur une couleur échantillonnée dans l'image : sans cela, le masque du système aurait découpé
|
||||
une seconde fois un badge qui l'est déjà, en laissant du blanc sur les bords.
|
||||
|
||||
### Ajouté
|
||||
|
||||
- Page « À propos », accessible depuis l'écran Sources : ce que fait l'application, ce qu'elle
|
||||
garantit, avec quoi elle est construite, ce qui a changé et qui en répond. La version est lue
|
||||
du manifeste et le journal du CHANGELOG du dépôt, recopié à la compilation — l'un et l'autre
|
||||
ne peuvent donc pas diverger de la réalité.
|
||||
- La pastille de licence signale en rouge qu'aucune licence n'est déclarée dans le dépôt, plutôt
|
||||
que d'en afficher une par défaut.
|
||||
|
||||
## [1.5.0] — 2026-09-13
|
||||
|
||||
### Ajouté
|
||||
@@ -25,7 +52,7 @@ et le versionnage [SemVer](https://semver.org/lang/fr/).
|
||||
|
||||
### Modifié
|
||||
|
||||
- Les enregistrements sont désormais écrits dans `Music/Cachette` et déclarés dans MediaStore
|
||||
- Les enregistrements sont désormais écrits dans `Music/Noxely` et déclarés dans MediaStore
|
||||
(Android 10 et plus) : ils deviennent visibles des autres lecteurs et survivent à la
|
||||
désinstallation. Le scan local ignore ce dossier, faute de quoi chaque enregistrement
|
||||
apparaîtrait deux fois.
|
||||
@@ -109,7 +136,7 @@ et le versionnage [SemVer](https://semver.org/lang/fr/).
|
||||
|
||||
### Ajouté
|
||||
|
||||
- Mise à jour intégrée : Cachette surveille les releases du dépôt, télécharge l'APK en Wi-Fi
|
||||
- Mise à jour intégrée : Noxely surveille les releases du dépôt, télécharge l'APK en Wi-Fi
|
||||
et propose l'installation. Vérification quotidienne en tâche de fond, et à la demande depuis
|
||||
l'écran Sources, qui affiche aussi la version installée.
|
||||
- La taille approximative de chaque résultat Internet Archive est affichée avant sélection : un
|
||||
|
||||
@@ -15,7 +15,7 @@ DEBUG_APK := app/build/outputs/apk/debug/app-debug.apk
|
||||
# app-release-unsigned.apk) : on liste le répertoire au lieu de deviner.
|
||||
RELEASE_DIR := app/build/outputs/apk/release
|
||||
SIGNED_APK := $(RELEASE_DIR)/app-release.apk
|
||||
OUTPUT_APK := Cachette-$(VERSION).apk
|
||||
OUTPUT_APK := Noxely-$(VERSION).apk
|
||||
|
||||
# --- Publication -----------------------------------------------------------
|
||||
# Déléguée au script de BorisSkill, qui sait déjà tout faire : résolution du
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 🎧 Cachette
|
||||
# 🎧 Noxely
|
||||
|
||||
> Mettez la musique et les vidéos **de côté** pendant que vous avez du réseau. Écoutez-les quand vous n'en avez plus.
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
[](https://kotlinlang.org)
|
||||
[](https://developer.android.com/jetpack/compose)
|
||||
[](https://developer.android.com/media/media3)
|
||||
[](VERSION)
|
||||
[](VERSION)
|
||||
[](#-comment-ça-marche)
|
||||
|
||||
Cachette est un lecteur audio et vidéo pensé pour les trajets, les tunnels, les avions et les
|
||||
Noxely est un lecteur audio et vidéo pensé pour les trajets, les tunnels, les avions et les
|
||||
forfaits épuisés. On choisit ce qu'on emporte, l'application le télécharge, et tout reste lisible
|
||||
sans la moindre connexion.
|
||||
|
||||
@@ -44,6 +44,7 @@ sans la moindre connexion.
|
||||
| 🔁 **Reprise automatique** | Réseau coupé : mise en attente et reprise. Échec serveur : relance seule après 5 s, 15 s, 45 s |
|
||||
| 📻 **Radio** | Les stations les plus écoutées, en direct — et enregistrables d'un appui |
|
||||
| 🏷️ **Titrage automatique** | L'enregistrement prend le nom du morceau annoncé par la station |
|
||||
| ℹ️ **Page « À propos »** | Fonctions, garanties, pile technique, journal et auteur |
|
||||
| 👀 **Titre en cours** | Ce que diffuse chaque station, lu dans son flux |
|
||||
| ⏰ **Guet d'un titre** | L'enregistrement se déclenche quand le morceau voulu passe, et s'arrête à sa fin |
|
||||
| 🆕 **Mise à jour intégrée** | Surveille les releases du dépôt, télécharge l'APK en Wi-Fi et propose l'installation |
|
||||
@@ -71,7 +72,7 @@ sans la moindre connexion.
|
||||
| **ccMixter** | La recherche fonctionne, mais les fichiers ne sont servis qu'avec un en-tête `Referer` — que Media3 ne permet pas de poser par téléchargement. |
|
||||
| **Radio Browser** | Flux radio continus : rien à mettre en cache. |
|
||||
|
||||
### ⚖️ Ce que Cachette ne fait pas — et pourquoi
|
||||
### ⚖️ Ce que Noxely ne fait pas — et pourquoi
|
||||
|
||||
| Service | Pourquoi |
|
||||
|---|---|
|
||||
@@ -194,16 +195,24 @@ sortantes sont celles des URL que vous ajoutez et des catalogues que vous interr
|
||||
|
||||
---
|
||||
|
||||
> ⚠️ **Changement de nom en 1.6.0.** L'application s'appelait Cachette et son identifiant était
|
||||
> `fr.tuxgyver.cachette`. Android traite le nouvel identifiant `fr.tuxgyver.noxely` comme une
|
||||
> application distincte : une installation en 1.5.0 ne se met pas à jour toute seule, il faut la
|
||||
> désinstaller puis installer Noxely. Les enregistrements déjà rangés dans `Music/Cachette`
|
||||
> survivent — ils sont dans le stockage partagé — et restent lisibles par tout lecteur.
|
||||
|
||||
---
|
||||
|
||||
## 📼 Où vont les enregistrements
|
||||
|
||||
À partir d'Android 10, dans `Music/Cachette/`, déclarés dans MediaStore : ils sont donc visibles
|
||||
À partir d'Android 10, dans `Music/Noxely/`, déclarés dans MediaStore : ils sont donc visibles
|
||||
de tous les lecteurs de l'appareil et survivent à la désinstallation. En deçà, ils restent dans le
|
||||
dossier privé de l'application, MediaStore y réclamant une permission de stockage dont Cachette n'a
|
||||
dossier privé de l'application, MediaStore y réclamant une permission de stockage dont Noxely n'a
|
||||
pas besoin par ailleurs.
|
||||
|
||||
Le nom du fichier porte le titre du morceau — et pas seulement la colonne `TITLE` : en publiant le
|
||||
fichier, MediaStore le relit et réécrit ce champ à partir du nom. Le scan local, lui, ignore
|
||||
`Music/Cachette` pour ne pas afficher deux fois ce que la bibliothèque connaît déjà.
|
||||
`Music/Noxely` pour ne pas afficher deux fois ce que la bibliothèque connaît déjà.
|
||||
|
||||
Chaque enregistrement se supprime individuellement depuis l'écran **Hors ligne** : fichier, entrée
|
||||
MediaStore et référence en bibliothèque partent ensemble. « Tout libérer » ne les touche pas.
|
||||
@@ -212,7 +221,7 @@ MediaStore et référence en bibliothèque partent ensemble. « Tout libérer »
|
||||
|
||||
## 🆕 Mises à jour
|
||||
|
||||
Cachette étant distribuée hors magasin, elle surveille elle-même les releases du dépôt.
|
||||
Noxely étant distribuée hors magasin, elle surveille elle-même les releases du dépôt.
|
||||
|
||||
```
|
||||
Release publiée ──► vérification quotidienne (WorkManager, Wi-Fi)
|
||||
@@ -245,7 +254,7 @@ giteaUpdateToken=…
|
||||
|
||||
```
|
||||
app/src/main/java/fr/tuxgyver/cachette/
|
||||
├── CachetteApp.kt Application + conteneur de dépendances (assemblé à la main)
|
||||
├── NoxelyApp.kt Application + conteneur de dépendances (assemblé à la main)
|
||||
├── MainActivity.kt
|
||||
├── model/ MediaEntry, MediaKind, SourceKind
|
||||
├── library/ MediaStore, persistance des imports, bibliothèque unifiée
|
||||
|
||||
+10
-3
@@ -8,11 +8,11 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "fr.tuxgyver.cachette"
|
||||
namespace = "fr.tuxgyver.noxely"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "fr.tuxgyver.cachette"
|
||||
applicationId = "fr.tuxgyver.noxely"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
|
||||
@@ -33,7 +33,7 @@ android {
|
||||
""
|
||||
}
|
||||
buildConfigField("String", "GITEA_UPDATE_TOKEN", "\"$updateToken\"")
|
||||
buildConfigField("String", "UPDATE_API", "\"https://git.tuxtech.fr/api/v1/repos/tuxgyver/Cachette\"")
|
||||
buildConfigField("String", "UPDATE_API", "\"https://git.tuxtech.fr/api/v1/repos/tuxgyver/Noxely\"")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -77,6 +77,13 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
/** Le journal affiché dans « À propos » est celui du dépôt, pas une copie qui dériverait. */
|
||||
val copyChangelog = tasks.register<Copy>("copyChangelogToAssets") {
|
||||
from(rootProject.file("CHANGELOG.md"))
|
||||
into(layout.projectDirectory.dir("src/main/assets"))
|
||||
}
|
||||
tasks.named("preBuild") { dependsOn(copyChangelog) }
|
||||
|
||||
dependencies {
|
||||
implementation(platform("androidx.compose:compose-bom:2025.10.00"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
android:maxSdkVersion="32" />
|
||||
|
||||
<application
|
||||
android:name=".CachetteApp"
|
||||
android:name=".NoxelyApp"
|
||||
android:allowBackup="false"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@@ -26,13 +26,13 @@
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:theme="@style/Theme.Cachette">
|
||||
android:theme="@style/Theme.Noxely">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/Theme.Cachette"
|
||||
android:theme="@style/Theme.Noxely"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@@ -50,7 +50,7 @@
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".offline.CachetteDownloadService"
|
||||
android:name=".offline.NoxelyDownloadService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync">
|
||||
<intent-filter>
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette
|
||||
package fr.tuxgyver.noxely
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
@@ -9,8 +9,8 @@ import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import fr.tuxgyver.cachette.ui.CachetteRoot
|
||||
import fr.tuxgyver.cachette.ui.theme.CachetteTheme
|
||||
import fr.tuxgyver.noxely.ui.NoxelyRoot
|
||||
import fr.tuxgyver.noxely.ui.theme.NoxelyTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@@ -21,20 +21,20 @@ class MainActivity : ComponentActivity() {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val container = (application as CachetteApp).container
|
||||
val container = (application as NoxelyApp).container
|
||||
container.player.connect()
|
||||
askForNotificationsIfNeeded()
|
||||
|
||||
setContent {
|
||||
CachetteTheme {
|
||||
CachetteRoot(container)
|
||||
NoxelyTheme {
|
||||
NoxelyRoot(container)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (isFinishing) {
|
||||
(application as CachetteApp).container.player.release()
|
||||
(application as NoxelyApp).container.player.release()
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
+20
-20
@@ -1,25 +1,25 @@
|
||||
package fr.tuxgyver.cachette
|
||||
package fr.tuxgyver.noxely
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import fr.tuxgyver.cachette.library.ImportedStore
|
||||
import fr.tuxgyver.cachette.library.LibraryRepository
|
||||
import fr.tuxgyver.cachette.library.LocalMediaScanner
|
||||
import fr.tuxgyver.cachette.library.PlaylistStore
|
||||
import fr.tuxgyver.cachette.offline.DownloadTracker
|
||||
import fr.tuxgyver.cachette.offline.OfflineModule
|
||||
import fr.tuxgyver.cachette.playback.PlayerController
|
||||
import fr.tuxgyver.cachette.radio.RadioDirectory
|
||||
import fr.tuxgyver.cachette.sources.ArchiveOrgCatalog
|
||||
import fr.tuxgyver.cachette.sources.Catalog
|
||||
import fr.tuxgyver.cachette.sources.CatalogId
|
||||
import fr.tuxgyver.cachette.sources.CatalogItem
|
||||
import fr.tuxgyver.cachette.sources.WikimediaCatalog
|
||||
import fr.tuxgyver.cachette.sources.LibriVoxCatalog
|
||||
import fr.tuxgyver.cachette.sources.RssImporter
|
||||
import fr.tuxgyver.cachette.sources.UrlImporter
|
||||
import fr.tuxgyver.cachette.update.UpdateCheckWorker
|
||||
import fr.tuxgyver.cachette.update.UpdateRepository
|
||||
import fr.tuxgyver.noxely.library.ImportedStore
|
||||
import fr.tuxgyver.noxely.library.LibraryRepository
|
||||
import fr.tuxgyver.noxely.library.LocalMediaScanner
|
||||
import fr.tuxgyver.noxely.library.PlaylistStore
|
||||
import fr.tuxgyver.noxely.offline.DownloadTracker
|
||||
import fr.tuxgyver.noxely.offline.OfflineModule
|
||||
import fr.tuxgyver.noxely.playback.PlayerController
|
||||
import fr.tuxgyver.noxely.radio.RadioDirectory
|
||||
import fr.tuxgyver.noxely.sources.ArchiveOrgCatalog
|
||||
import fr.tuxgyver.noxely.sources.Catalog
|
||||
import fr.tuxgyver.noxely.sources.CatalogId
|
||||
import fr.tuxgyver.noxely.sources.CatalogItem
|
||||
import fr.tuxgyver.noxely.sources.WikimediaCatalog
|
||||
import fr.tuxgyver.noxely.sources.LibriVoxCatalog
|
||||
import fr.tuxgyver.noxely.sources.RssImporter
|
||||
import fr.tuxgyver.noxely.sources.UrlImporter
|
||||
import fr.tuxgyver.noxely.update.UpdateCheckWorker
|
||||
import fr.tuxgyver.noxely.update.UpdateRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -67,7 +67,7 @@ class AppContainer(context: Context) {
|
||||
val updates = UpdateRepository(context, BuildConfig.VERSION_NAME)
|
||||
}
|
||||
|
||||
class CachetteApp : Application() {
|
||||
class NoxelyApp : Application() {
|
||||
|
||||
lateinit var container: AppContainer
|
||||
private set
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package fr.tuxgyver.cachette.library
|
||||
package fr.tuxgyver.noxely.library
|
||||
|
||||
import android.content.Context
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package fr.tuxgyver.cachette.library
|
||||
package fr.tuxgyver.noxely.library
|
||||
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
+9
-6
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.library
|
||||
package fr.tuxgyver.noxely.library
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
@@ -8,9 +8,9 @@ import androidx.core.net.toUri
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import androidx.core.content.ContextCompat
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -124,13 +124,16 @@ class LocalMediaScanner(private val context: Context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Musique et podcasts, hors enregistrements de Cachette : ceux-ci sont déjà décrits par la
|
||||
* Musique et podcasts, hors enregistrements de Noxely : ceux-ci sont déjà décrits par la
|
||||
* bibliothèque, et les relire ici les afficherait en double.
|
||||
*/
|
||||
private fun audioSelection(): String {
|
||||
val base = "(${MediaStore.Audio.Media.IS_MUSIC} != 0 OR ${MediaStore.Audio.Media.IS_PODCAST} != 0)"
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
"$base AND ${MediaStore.Audio.Media.RELATIVE_PATH} NOT LIKE 'Music/Cachette%'"
|
||||
// L'ancien dossier reste exclu : les enregistrements d'avant le changement de nom
|
||||
// sont toujours décrits par la bibliothèque et apparaîtraient sinon en double.
|
||||
"$base AND ${MediaStore.Audio.Media.RELATIVE_PATH} NOT LIKE 'Music/Noxely%'" +
|
||||
" AND ${MediaStore.Audio.Media.RELATIVE_PATH} NOT LIKE 'Music/Cachette%'"
|
||||
} else {
|
||||
base
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package fr.tuxgyver.cachette.library
|
||||
package fr.tuxgyver.noxely.library
|
||||
|
||||
import android.content.Context
|
||||
import fr.tuxgyver.cachette.model.Playlist
|
||||
import fr.tuxgyver.noxely.model.Playlist
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.model
|
||||
package fr.tuxgyver.noxely.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.model
|
||||
package fr.tuxgyver.noxely.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.offline
|
||||
package fr.tuxgyver.noxely.offline
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.OptIn
|
||||
@@ -8,7 +8,7 @@ import androidx.media3.exoplayer.offline.Download
|
||||
import androidx.media3.exoplayer.offline.DownloadManager
|
||||
import androidx.media3.exoplayer.offline.DownloadRequest
|
||||
import androidx.media3.exoplayer.offline.DownloadService
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -110,7 +110,7 @@ class DownloadTracker(
|
||||
// Ré-ajouter la même requête relance un téléchargement en échec.
|
||||
DownloadService.sendAddDownload(
|
||||
context,
|
||||
CachetteDownloadService::class.java,
|
||||
NoxelyDownloadService::class.java,
|
||||
download.request,
|
||||
false,
|
||||
)
|
||||
@@ -129,15 +129,15 @@ class DownloadTracker(
|
||||
val request = DownloadRequest.Builder(entry.id, entry.uri.toUri())
|
||||
.setData(entry.title.toByteArray())
|
||||
.build()
|
||||
DownloadService.sendAddDownload(context, CachetteDownloadService::class.java, request, false)
|
||||
DownloadService.sendAddDownload(context, NoxelyDownloadService::class.java, request, false)
|
||||
}
|
||||
|
||||
fun remove(id: String) {
|
||||
DownloadService.sendRemoveDownload(context, CachetteDownloadService::class.java, id, false)
|
||||
DownloadService.sendRemoveDownload(context, NoxelyDownloadService::class.java, id, false)
|
||||
}
|
||||
|
||||
fun removeAll() {
|
||||
DownloadService.sendRemoveAllDownloads(context, CachetteDownloadService::class.java, false)
|
||||
DownloadService.sendRemoveAllDownloads(context, NoxelyDownloadService::class.java, false)
|
||||
}
|
||||
|
||||
private suspend fun reload() = withContext(Dispatchers.IO) {
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.offline
|
||||
package fr.tuxgyver.noxely.offline
|
||||
|
||||
import android.app.Notification
|
||||
import androidx.annotation.OptIn
|
||||
@@ -9,15 +9,15 @@ import androidx.media3.exoplayer.offline.DownloadService
|
||||
import androidx.media3.exoplayer.scheduler.Scheduler
|
||||
import androidx.media3.exoplayer.workmanager.WorkManagerScheduler
|
||||
import androidx.media3.exoplayer.offline.DownloadNotificationHelper
|
||||
import fr.tuxgyver.cachette.CachetteApp
|
||||
import fr.tuxgyver.cachette.R
|
||||
import fr.tuxgyver.noxely.NoxelyApp
|
||||
import fr.tuxgyver.noxely.R
|
||||
|
||||
/**
|
||||
* Service de mise en cache. Il tourne en premier plan pendant les téléchargements et délègue la
|
||||
* reprise après coupure réseau ou redémarrage à WorkManager.
|
||||
*/
|
||||
@OptIn(UnstableApi::class)
|
||||
class CachetteDownloadService : DownloadService(
|
||||
class NoxelyDownloadService : DownloadService(
|
||||
NOTIFICATION_ID,
|
||||
DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL,
|
||||
CHANNEL_ID,
|
||||
@@ -29,7 +29,7 @@ class CachetteDownloadService : DownloadService(
|
||||
}
|
||||
|
||||
override fun getDownloadManager(): DownloadManager =
|
||||
(application as CachetteApp).container.offline.downloadManager
|
||||
(application as NoxelyApp).container.offline.downloadManager
|
||||
|
||||
override fun getScheduler(): Scheduler = WorkManagerScheduler(this, WORK_NAME)
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.offline
|
||||
package fr.tuxgyver.noxely.offline
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.OptIn
|
||||
@@ -31,7 +31,7 @@ class OfflineModule(private val context: Context) {
|
||||
|
||||
private val httpDataSourceFactory: DataSource.Factory by lazy {
|
||||
DefaultHttpDataSource.Factory()
|
||||
.setUserAgent("Cachette/1.0 (Android)")
|
||||
.setUserAgent("Noxely/1.0 (Android)")
|
||||
.setAllowCrossProtocolRedirects(true)
|
||||
.setConnectTimeoutMs(20_000)
|
||||
.setReadTimeoutMs(30_000)
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package fr.tuxgyver.cachette.playback
|
||||
package fr.tuxgyver.noxely.playback
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
|
||||
/**
|
||||
* Les `MediaItem` transmis par un contrôleur perdent leur URI en chemin : c'est le service qui la
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.playback
|
||||
package fr.tuxgyver.noxely.playback
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
@@ -13,8 +13,8 @@ import androidx.media3.session.MediaSession
|
||||
import androidx.media3.session.MediaSessionService
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import fr.tuxgyver.cachette.CachetteApp
|
||||
import fr.tuxgyver.cachette.MainActivity
|
||||
import fr.tuxgyver.noxely.NoxelyApp
|
||||
import fr.tuxgyver.noxely.MainActivity
|
||||
|
||||
/** Lecture audio et vidéo en arrière-plan, avec notification et commandes système. */
|
||||
@OptIn(UnstableApi::class)
|
||||
@@ -24,7 +24,7 @@ class PlaybackService : MediaSessionService() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
val container = (application as CachetteApp).container
|
||||
val container = (application as NoxelyApp).container
|
||||
|
||||
val player = ExoPlayer.Builder(this)
|
||||
.setMediaSourceFactory(DefaultMediaSourceFactory(container.offline.playbackDataSourceFactory()))
|
||||
@@ -76,7 +76,7 @@ class PlaybackService : MediaSessionService() {
|
||||
controller: MediaSession.ControllerInfo,
|
||||
mediaItems: MutableList<MediaItem>,
|
||||
): ListenableFuture<MutableList<MediaItem>> {
|
||||
val library = (application as CachetteApp).container.library
|
||||
val library = (application as NoxelyApp).container.library
|
||||
val resolved = mediaItems.mapTo(mutableListOf()) { item ->
|
||||
library.entry(item.mediaId)?.toMediaItem() ?: item
|
||||
}
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.playback
|
||||
package fr.tuxgyver.noxely.playback
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
@@ -6,8 +6,8 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.session.MediaController
|
||||
import androidx.media3.session.SessionToken
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package fr.tuxgyver.cachette.radio
|
||||
package fr.tuxgyver.noxely.radio
|
||||
|
||||
import fr.tuxgyver.cachette.sources.openConnection
|
||||
import fr.tuxgyver.noxely.sources.openConnection
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.radio
|
||||
package fr.tuxgyver.noxely.radio
|
||||
|
||||
/** Une station de radio et son flux direct. */
|
||||
data class RadioStation(
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.radio
|
||||
package fr.tuxgyver.noxely.radio
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
+11
-11
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.radio
|
||||
package fr.tuxgyver.noxely.radio
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
@@ -11,13 +11,13 @@ import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import fr.tuxgyver.cachette.CachetteApp
|
||||
import fr.tuxgyver.cachette.MainActivity
|
||||
import fr.tuxgyver.cachette.R
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.cachette.sources.openConnection
|
||||
import fr.tuxgyver.noxely.NoxelyApp
|
||||
import fr.tuxgyver.noxely.MainActivity
|
||||
import fr.tuxgyver.noxely.R
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import fr.tuxgyver.noxely.sources.openConnection
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -246,7 +246,7 @@ class RecordingService : Service() {
|
||||
val published = output.finish(title, elapsed)
|
||||
if (published != null) {
|
||||
val (uri, size) = published
|
||||
val container = (application as CachetteApp).container
|
||||
val container = (application as NoxelyApp).container
|
||||
val entry = MediaEntry(
|
||||
id = "rec:${uri.hashCode()}",
|
||||
source = SourceKind.RECORDING,
|
||||
@@ -343,8 +343,8 @@ class RecordingService : Service() {
|
||||
replace(Regex("[^\\p{L}\\p{N} _-]"), "").trim().replace(' ', '_').take(40).ifBlank { "radio" }
|
||||
|
||||
companion object {
|
||||
private const val ACTION_START = "fr.tuxgyver.cachette.RECORD_START"
|
||||
private const val ACTION_STOP = "fr.tuxgyver.cachette.RECORD_STOP"
|
||||
private const val ACTION_START = "fr.tuxgyver.noxely.RECORD_START"
|
||||
private const val ACTION_STOP = "fr.tuxgyver.noxely.RECORD_STOP"
|
||||
private const val EXTRA_ID = "id"
|
||||
private const val EXTRA_NAME = "name"
|
||||
private const val EXTRA_URL = "url"
|
||||
+4
-4
@@ -1,18 +1,18 @@
|
||||
package fr.tuxgyver.cachette.radio
|
||||
package fr.tuxgyver.noxely.radio
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* Destination d'un enregistrement radio.
|
||||
*
|
||||
* À partir d'Android 10, le fichier est écrit dans `Music/Cachette` via MediaStore : il devient
|
||||
* À partir d'Android 10, le fichier est écrit dans `Music/Noxely` via MediaStore : il devient
|
||||
* visible de tous les lecteurs de l'appareil et survit à la désinstallation. En deçà, l'écriture
|
||||
* dans MediaStore réclamerait une permission de stockage que l'application n'a pas besoin de
|
||||
* demander par ailleurs, donc le fichier reste dans le dossier privé.
|
||||
@@ -100,7 +100,7 @@ class RecordingSink(
|
||||
replace(Regex("[^\\p{L}\\p{N} ._-]"), "").trim().take(80).ifBlank { "enregistrement" }
|
||||
|
||||
companion object {
|
||||
const val RELATIVE_PATH = "Music/Cachette"
|
||||
const val RELATIVE_PATH = "Music/Noxely"
|
||||
private const val MIN_USEFUL_BYTES = 16 * 1024L
|
||||
|
||||
/** Supprime un enregistrement, quel que soit l'endroit où il avait été écrit. */
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
package fr.tuxgyver.cachette.sources
|
||||
package fr.tuxgyver.noxely.sources
|
||||
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package fr.tuxgyver.cachette.sources
|
||||
package fr.tuxgyver.noxely.sources
|
||||
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
|
||||
/** Un résultat de recherche : un album, un livre ou un morceau, pas encore un fichier. */
|
||||
data class CatalogItem(
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.sources
|
||||
package fr.tuxgyver.noxely.sources
|
||||
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
@@ -19,5 +19,5 @@ internal fun openConnection(
|
||||
instanceFollowRedirects = true
|
||||
connectTimeout = 15_000
|
||||
readTimeout = readTimeoutMs
|
||||
setRequestProperty("User-Agent", "Cachette/1.1 (Android)")
|
||||
setRequestProperty("User-Agent", "Noxely/1.1 (Android)")
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package fr.tuxgyver.cachette.sources
|
||||
package fr.tuxgyver.noxely.sources
|
||||
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
package fr.tuxgyver.cachette.sources
|
||||
package fr.tuxgyver.noxely.sources
|
||||
|
||||
import android.util.Xml
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
package fr.tuxgyver.cachette.sources
|
||||
package fr.tuxgyver.noxely.sources
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
package fr.tuxgyver.cachette.sources
|
||||
package fr.tuxgyver.noxely.sources
|
||||
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
+25
-18
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui
|
||||
package fr.tuxgyver.noxely.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
@@ -35,22 +35,23 @@ import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.cachette.sources.CatalogId
|
||||
import fr.tuxgyver.cachette.ui.browse.CatalogBrowseScreen
|
||||
import fr.tuxgyver.cachette.ui.browse.CatalogItemScreen
|
||||
import fr.tuxgyver.cachette.ui.browse.SourceBrowseScreen
|
||||
import fr.tuxgyver.cachette.ui.library.LibraryScreen
|
||||
import fr.tuxgyver.cachette.ui.offline.DownloadsScreen
|
||||
import fr.tuxgyver.cachette.ui.player.MiniPlayer
|
||||
import fr.tuxgyver.cachette.ui.radio.RadioScreen
|
||||
import fr.tuxgyver.cachette.ui.playlists.AddToPlaylistSheet
|
||||
import fr.tuxgyver.cachette.ui.playlists.PlaylistDetailScreen
|
||||
import fr.tuxgyver.cachette.ui.playlists.PlaylistsScreen
|
||||
import fr.tuxgyver.cachette.ui.player.NowPlayingScreen
|
||||
import fr.tuxgyver.cachette.ui.sources.SourcesScreen
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import fr.tuxgyver.noxely.sources.CatalogId
|
||||
import fr.tuxgyver.noxely.ui.about.AboutScreen
|
||||
import fr.tuxgyver.noxely.ui.browse.CatalogBrowseScreen
|
||||
import fr.tuxgyver.noxely.ui.browse.CatalogItemScreen
|
||||
import fr.tuxgyver.noxely.ui.browse.SourceBrowseScreen
|
||||
import fr.tuxgyver.noxely.ui.library.LibraryScreen
|
||||
import fr.tuxgyver.noxely.ui.offline.DownloadsScreen
|
||||
import fr.tuxgyver.noxely.ui.player.MiniPlayer
|
||||
import fr.tuxgyver.noxely.ui.radio.RadioScreen
|
||||
import fr.tuxgyver.noxely.ui.playlists.AddToPlaylistSheet
|
||||
import fr.tuxgyver.noxely.ui.playlists.PlaylistDetailScreen
|
||||
import fr.tuxgyver.noxely.ui.playlists.PlaylistsScreen
|
||||
import fr.tuxgyver.noxely.ui.player.NowPlayingScreen
|
||||
import fr.tuxgyver.noxely.ui.sources.SourcesScreen
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private enum class Tab(val route: String, val label: String, val icon: ImageVector) {
|
||||
@@ -65,9 +66,10 @@ private const val CATALOG_ROUTE = "catalog/{catalog}"
|
||||
private const val CATALOG_ITEM_ROUTE = "catalog/{catalog}/item"
|
||||
private const val PLAYLIST_ROUTE = "playlist/{playlist}"
|
||||
private const val RADIO_ROUTE = "radio"
|
||||
private const val ABOUT_ROUTE = "about"
|
||||
|
||||
@Composable
|
||||
fun CachetteRoot(container: AppContainer) {
|
||||
fun NoxelyRoot(container: AppContainer) {
|
||||
val navController = rememberNavController()
|
||||
val backStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute = backStackEntry?.destination?.route
|
||||
@@ -145,6 +147,10 @@ fun CachetteRoot(container: AppContainer) {
|
||||
)
|
||||
}
|
||||
|
||||
composable(ABOUT_ROUTE) {
|
||||
AboutScreen(onBack = navController::popBackStack, contentPadding = padding)
|
||||
}
|
||||
|
||||
composable(RADIO_ROUTE) {
|
||||
RadioScreen(
|
||||
container = container,
|
||||
@@ -174,6 +180,7 @@ fun CachetteRoot(container: AppContainer) {
|
||||
onBrowseSource = { navController.navigate("browse/${it.name}") },
|
||||
onBrowseCatalog = { navController.navigate("catalog/${it.name}") },
|
||||
onBrowseRadio = { navController.navigate(RADIO_ROUTE) },
|
||||
onOpenAbout = { navController.navigate(ABOUT_ROUTE) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
package fr.tuxgyver.noxely.ui.about
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.tuxgyver.noxely.R
|
||||
|
||||
private val TABS = listOf(
|
||||
"⭐ Fonctions",
|
||||
"🛡️ Garanties",
|
||||
"🧱 Technique",
|
||||
"🕓 Journal",
|
||||
"👤 Auteur",
|
||||
)
|
||||
|
||||
/**
|
||||
* Page « À propos » : ce que fait l'application, ce qu'elle garantit, avec quoi elle est faite,
|
||||
* ce qui a changé et qui en répond. L'en-tête ne défile pas ; seul l'onglet courant défile.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AboutScreen(onBack: () -> Unit, contentPadding: PaddingValues) {
|
||||
var tab by rememberSaveable { mutableIntStateOf(0) }
|
||||
val listPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding())
|
||||
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
TopAppBar(
|
||||
title = { Text("À propos") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Retour")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Identity()
|
||||
|
||||
ScrollableTabRow(selectedTabIndex = tab, edgePadding = 16.dp) {
|
||||
TABS.forEachIndexed { index, label ->
|
||||
Tab(
|
||||
selected = tab == index,
|
||||
onClick = { tab = index },
|
||||
text = { Text(label, style = MaterialTheme.typography.labelLarge) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
when (tab) {
|
||||
0 -> FeaturesTab(listPadding)
|
||||
1 -> GuaranteesTab(listPadding)
|
||||
2 -> StackTab(listPadding)
|
||||
3 -> ChangelogTab(listPadding)
|
||||
else -> AuthorTab(listPadding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Identity() {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.ic_launcher_foreground),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.size(80.dp)
|
||||
.clip(RoundedCornerShape(20.dp)),
|
||||
)
|
||||
Spacer(Modifier.size(6.dp))
|
||||
Text(AppInfo.NAME, style = MaterialTheme.typography.headlineSmall)
|
||||
Text(
|
||||
text = AppInfo.TAGLINE,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(Modifier.size(10.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Pill("v${AppInfo.VERSION}", MaterialTheme.colorScheme.primary)
|
||||
// Rouge tant qu'aucune licence n'est déclarée : une garantie qui manque doit se voir.
|
||||
Pill(
|
||||
text = AppInfo.LICENSE,
|
||||
color = if (AppInfo.LICENSE_DEFINED) MaterialTheme.colorScheme.secondary
|
||||
else MaterialTheme.colorScheme.error,
|
||||
)
|
||||
Pill("Hors ligne", Color(0xFF2E7D32))
|
||||
}
|
||||
Spacer(Modifier.size(12.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Pill(text: String, color: Color) {
|
||||
Surface(color = color.copy(alpha = 0.14f), shape = CircleShape) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = color,
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 5.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeaturesTab(padding: PaddingValues) {
|
||||
LazyColumn(
|
||||
contentPadding = padding,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
items(AppInfo.FEATURES) { (title, description) ->
|
||||
Column {
|
||||
Text(title, style = MaterialTheme.typography.titleSmall)
|
||||
Text(
|
||||
text = description,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
item { Spacer(Modifier.size(16.dp)) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GuaranteesTab(padding: PaddingValues) {
|
||||
LazyColumn(
|
||||
contentPadding = padding,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = AppInfo.GUARANTEES_INTRO,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(Modifier.size(4.dp))
|
||||
}
|
||||
items(AppInfo.GUARANTEES) { guarantee ->
|
||||
Row {
|
||||
Text("•", color = MaterialTheme.colorScheme.secondary)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Text(
|
||||
text = guarantee,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
item { Spacer(Modifier.size(16.dp)) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StackTab(padding: PaddingValues) {
|
||||
LazyColumn(
|
||||
contentPadding = padding,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
items(AppInfo.STACK) { (layer, tools) ->
|
||||
Row(verticalAlignment = Alignment.Top) {
|
||||
Text(
|
||||
text = layer,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier.width(116.dp),
|
||||
)
|
||||
Text(
|
||||
text = tools,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
item { Spacer(Modifier.size(16.dp)) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChangelogTab(padding: PaddingValues) {
|
||||
val context = LocalContext.current
|
||||
val lines = remember { readChangelog(context) }
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = padding,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
if (lines.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = "Journal indisponible.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
items(lines) { line ->
|
||||
when (line) {
|
||||
is ChangelogLine.Version -> {
|
||||
Spacer(Modifier.size(8.dp))
|
||||
Text(
|
||||
text = line.text,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
|
||||
is ChangelogLine.Section -> Text(
|
||||
text = line.text,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
|
||||
is ChangelogLine.Bullet -> Row {
|
||||
Text("•", color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = emphasised(line.text),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item { Spacer(Modifier.size(16.dp)) }
|
||||
}
|
||||
}
|
||||
|
||||
/** Le fichier n'emploie que du gras : inutile d'embarquer un analyseur Markdown complet. */
|
||||
@Composable
|
||||
private fun emphasised(text: String) = buildAnnotatedString {
|
||||
var rest = text
|
||||
while (true) {
|
||||
val open = rest.indexOf("**")
|
||||
if (open < 0) break
|
||||
val close = rest.indexOf("**", open + 2)
|
||||
if (close < 0) break
|
||||
append(rest.substring(0, open))
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) {
|
||||
append(rest.substring(open + 2, close))
|
||||
}
|
||||
rest = rest.substring(close + 2)
|
||||
}
|
||||
append(rest)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AuthorTab(padding: PaddingValues) {
|
||||
LazyColumn(
|
||||
contentPadding = padding,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
item {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = MaterialTheme.shapes.large,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(Modifier.padding(20.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.size(52.dp)
|
||||
.clip(CircleShape),
|
||||
) {
|
||||
Surface(color = MaterialTheme.colorScheme.primary, shape = CircleShape) {
|
||||
Box(Modifier.size(52.dp), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
text = AppInfo.AUTHOR.take(1).uppercase(),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(16.dp))
|
||||
Column {
|
||||
Text(AppInfo.AUTHOR, style = MaterialTheme.typography.titleMedium)
|
||||
Text(
|
||||
text = AppInfo.AUTHOR_ROLE,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.size(18.dp))
|
||||
Text("Dépôt", style = MaterialTheme.typography.labelMedium)
|
||||
Text(
|
||||
text = AppInfo.REPOSITORY,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
|
||||
Spacer(Modifier.size(18.dp))
|
||||
Text(
|
||||
text = AppInfo.AUTHORSHIP,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.size(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package fr.tuxgyver.noxely.ui.about
|
||||
|
||||
import fr.tuxgyver.noxely.BuildConfig
|
||||
|
||||
/**
|
||||
* Contenu de la page « À propos », séparé de son rendu.
|
||||
*
|
||||
* Tout ce qui est écrit ici se vérifie dans le dépôt : une fonction décrite existe, une garantie
|
||||
* annoncée est tenue par du code. La version n'est pas recopiée mais lue du manifeste.
|
||||
*/
|
||||
object AppInfo {
|
||||
|
||||
const val NAME = "Noxely"
|
||||
const val TAGLINE = "Mettez la musique de côté pendant que vous avez du réseau."
|
||||
val VERSION: String get() = BuildConfig.VERSION_NAME
|
||||
|
||||
const val REPOSITORY = "https://git.tuxtech.fr/tuxgyver/Noxely"
|
||||
const val AUTHOR = "Johnny"
|
||||
const val AUTHOR_ROLE = "Conception, direction et supervision"
|
||||
const val AUTHORSHIP =
|
||||
"Application développée avec l'assistance de l'IA, sous la direction et la supervision de Johnny."
|
||||
|
||||
/** Aucune licence n'est déclarée dans le dépôt : la pastille le signale plutôt que d'en inventer une. */
|
||||
const val LICENSE = "Licence à définir"
|
||||
const val LICENSE_DEFINED = false
|
||||
|
||||
/** Ce que fait l'application, du point de vue de qui l'utilise. */
|
||||
val FEATURES = listOf(
|
||||
"Écouter sans réseau" to
|
||||
"Ce que vous mettez de côté reste lisible dans le métro, en avion ou forfait épuisé. " +
|
||||
"Audio et vidéo, avec les commandes au casque et sur l'écran verrouillé.",
|
||||
"Choisir piste par piste" to
|
||||
"Un album ou un livre affiche ses pistes avec leur durée et leur taille. Vous cochez " +
|
||||
"ce que vous emportez, et le bouton annonce le poids exact avant de lancer.",
|
||||
"Essayer avant de garder" to
|
||||
"Un appui sur une piste la joue en ligne, sans rien mettre en cache. On décide après.",
|
||||
"Puiser dans des catalogues libres" to
|
||||
"Internet Archive, Underground (netlabels et Free Music Archive), LibriVox et " +
|
||||
"Wikimedia Commons, sans compte ni clé, avec filtrage par langue.",
|
||||
"Écouter et enregistrer la radio" to
|
||||
"Les stations les plus écoutées, ce qu'elles diffusent à l'instant, et " +
|
||||
"l'enregistrement d'un direct — qui prend le nom du morceau annoncé.",
|
||||
"Guetter un morceau" to
|
||||
"Vous nommez un titre : l'enregistrement se déclenche dès que la station l'annonce, " +
|
||||
"et s'arrête à la fin du morceau.",
|
||||
"Organiser en playlists" to
|
||||
"Des listes nommées, alimentées d'un appui long, lues à la suite.",
|
||||
"Rester à jour" to
|
||||
"L'application surveille les publications du dépôt, télécharge la nouvelle version " +
|
||||
"en Wi-Fi et propose de l'installer.",
|
||||
)
|
||||
|
||||
/** Chaque promesse est tenue par du code, et dit où. */
|
||||
val GUARANTEES_INTRO =
|
||||
"Noxely est un outil personnel : rien n'y transite par un service tiers, et rien ne s'y " +
|
||||
"télécharge sans un geste de votre part."
|
||||
|
||||
val GUARANTEES = listOf(
|
||||
"Aucun compte, aucune inscription, aucune télémétrie : le projet n'embarque aucune " +
|
||||
"bibliothèque d'analyse d'audience.",
|
||||
"Les seules connexions sortantes sont celles des adresses que vous ajoutez et des " +
|
||||
"catalogues que vous interrogez.",
|
||||
"Le cache ne contient que ce que vous avez demandé à garder : rien n'y est écrit pendant " +
|
||||
"une écoute en ligne, et rien n'en est effacé sans votre geste.",
|
||||
"Le contenu mis en cache est exclu des sauvegardes dans le nuage et des transferts " +
|
||||
"d'appareil à appareil.",
|
||||
"Un téléchargement interrompu par une panne de serveur repart seul, trois fois, en " +
|
||||
"espaçant les tentatives ; sans réseau, il attend son retour.",
|
||||
"Les catalogues proposés ne présentent que des œuvres libres de droits ou du domaine " +
|
||||
"public ; les éléments en accès restreint sont écartés avant même d'être affichés.",
|
||||
"Le jeton qui sert à vérifier les mises à jour est en lecture seule et ne peut rien publier.",
|
||||
)
|
||||
|
||||
/** Couche → outils. Six lignes suffisent. */
|
||||
val STACK = listOf(
|
||||
"Interface" to "Jetpack Compose, Material 3, thèmes clair et sombre",
|
||||
"Lecture" to "Media3 / ExoPlayer, MediaSessionService, notification système",
|
||||
"Hors connexion" to "Cache Media3 non évinçant, WorkManager pour la reprise",
|
||||
"Bibliothèque" to "MediaStore pour l'appareil, fichiers JSON pour les imports",
|
||||
"Réseau" to "HttpURLConnection, kotlinx.serialization, flux ICY pour la radio",
|
||||
"Publication" to "APK signé, releases Gitea, mise à jour intégrée",
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package fr.tuxgyver.noxely.ui.about
|
||||
|
||||
import android.content.Context
|
||||
|
||||
/** Une ligne du journal, telle qu'elle sera rendue. */
|
||||
sealed interface ChangelogLine {
|
||||
data class Version(val text: String) : ChangelogLine
|
||||
data class Section(val text: String) : ChangelogLine
|
||||
data class Bullet(val text: String) : ChangelogLine
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit le CHANGELOG livré dans les ressources.
|
||||
*
|
||||
* Le fichier est celui du dépôt, recopié dans les assets à la compilation : le journal affiché
|
||||
* ne peut donc pas diverger de celui que la publication utilise pour ses notes de version.
|
||||
* Seules les formes réellement employées par le fichier sont interprétées — inutile d'embarquer
|
||||
* un analyseur Markdown complet pour trois motifs. Le préambule qui précède la première version
|
||||
* est ignoré : il décrit la convention du fichier, pas l'histoire du logiciel.
|
||||
*/
|
||||
fun readChangelog(context: Context, limitVersions: Int = 6): List<ChangelogLine> {
|
||||
val raw = runCatching {
|
||||
context.assets.open("CHANGELOG.md").bufferedReader().readText()
|
||||
}.getOrNull() ?: return emptyList()
|
||||
|
||||
val lines = mutableListOf<ChangelogLine>()
|
||||
var versions = 0
|
||||
|
||||
for (line in raw.lineSequence()) {
|
||||
val trimmed = line.trim()
|
||||
when {
|
||||
trimmed.startsWith("## ") -> {
|
||||
versions++
|
||||
if (versions > limitVersions) break
|
||||
lines += ChangelogLine.Version(trimmed.removePrefix("## ").replace("[", "").replace("]", ""))
|
||||
}
|
||||
|
||||
trimmed.startsWith("### ") ->
|
||||
lines += ChangelogLine.Section(trimmed.removePrefix("### "))
|
||||
|
||||
trimmed.startsWith("- ") && versions > 0 ->
|
||||
lines += ChangelogLine.Bullet(trimmed.removePrefix("- "))
|
||||
|
||||
// Une puce peut courir sur plusieurs lignes : on la recolle à la précédente.
|
||||
trimmed.isNotEmpty() && versions > 0 && lines.lastOrNull() is ChangelogLine.Bullet -> {
|
||||
val previous = lines.removeAt(lines.lastIndex) as ChangelogLine.Bullet
|
||||
lines += ChangelogLine.Bullet("${previous.text} $trimmed")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.browse
|
||||
package fr.tuxgyver.noxely.ui.browse
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
@@ -47,12 +47,12 @@ import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.cachette.sources.CatalogId
|
||||
import fr.tuxgyver.cachette.sources.CatalogItem
|
||||
import fr.tuxgyver.cachette.ui.components.Artwork
|
||||
import fr.tuxgyver.cachette.ui.components.formatBytes
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import fr.tuxgyver.noxely.sources.CatalogId
|
||||
import fr.tuxgyver.noxely.sources.CatalogItem
|
||||
import fr.tuxgyver.noxely.ui.components.Artwork
|
||||
import fr.tuxgyver.noxely.ui.components.formatBytes
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.browse
|
||||
package fr.tuxgyver.noxely.ui.browse
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -40,11 +40,11 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.sources.CatalogId
|
||||
import fr.tuxgyver.cachette.ui.components.formatBytes
|
||||
import fr.tuxgyver.cachette.ui.components.formatDuration
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.sources.CatalogId
|
||||
import fr.tuxgyver.noxely.ui.components.formatBytes
|
||||
import fr.tuxgyver.noxely.ui.components.formatDuration
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.browse
|
||||
package fr.tuxgyver.noxely.ui.browse
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.slideInVertically
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.browse
|
||||
package fr.tuxgyver.noxely.ui.browse
|
||||
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -35,10 +35,10 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.cachette.ui.components.MediaRow
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import fr.tuxgyver.noxely.ui.components.MediaRow
|
||||
|
||||
/**
|
||||
* Parcours d'une source déjà présente dans la bibliothèque : on y choisit, un par un ou en lot,
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.components
|
||||
package fr.tuxgyver.noxely.ui.components
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
@@ -28,7 +28,7 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.net.toUri
|
||||
import coil.compose.AsyncImage
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.components
|
||||
package fr.tuxgyver.noxely.ui.components
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.components
|
||||
package fr.tuxgyver.noxely.ui.components
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
@@ -32,8 +32,8 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.offline.OfflineState
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.offline.OfflineState
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
+8
-8
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.library
|
||||
package fr.tuxgyver.noxely.ui.library
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -40,12 +40,12 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.library.LocalMediaScanner
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.cachette.offline.OfflineState
|
||||
import fr.tuxgyver.cachette.ui.components.MediaRow
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.library.LocalMediaScanner
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import fr.tuxgyver.noxely.offline.OfflineState
|
||||
import fr.tuxgyver.noxely.ui.components.MediaRow
|
||||
|
||||
private enum class LibraryFilter(val label: String) {
|
||||
ALL("Tout"),
|
||||
@@ -201,7 +201,7 @@ private fun PermissionCard(onGrant: () -> Unit) {
|
||||
Text("Accéder à vos médias", style = MaterialTheme.typography.titleMedium)
|
||||
Spacer(Modifier.size(6.dp))
|
||||
Text(
|
||||
"Cachette lit la musique et les vidéos déjà présentes sur l'appareil. Rien n'est envoyé ailleurs.",
|
||||
"Noxely lit la musique et les vidéos déjà présentes sur l'appareil. Rien n'est envoyé ailleurs.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(Modifier.size(14.dp))
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.offline
|
||||
package fr.tuxgyver.noxely.ui.offline
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -37,12 +37,12 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.cachette.radio.RecordingSink
|
||||
import fr.tuxgyver.cachette.ui.components.MediaRow
|
||||
import fr.tuxgyver.cachette.ui.components.formatBytes
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import fr.tuxgyver.noxely.radio.RecordingSink
|
||||
import fr.tuxgyver.noxely.ui.components.MediaRow
|
||||
import fr.tuxgyver.noxely.ui.components.formatBytes
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.player
|
||||
package fr.tuxgyver.noxely.ui.player
|
||||
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -26,8 +26,8 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.tuxgyver.cachette.playback.PlayerUiState
|
||||
import fr.tuxgyver.cachette.ui.components.Artwork
|
||||
import fr.tuxgyver.noxely.playback.PlayerUiState
|
||||
import fr.tuxgyver.noxely.ui.components.Artwork
|
||||
|
||||
@Composable
|
||||
fun MiniPlayer(
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.player
|
||||
package fr.tuxgyver.noxely.ui.player
|
||||
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.compose.foundation.background
|
||||
@@ -54,12 +54,12 @@ import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.PlayerView
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.cachette.offline.OfflineState
|
||||
import fr.tuxgyver.cachette.playback.PlayerUiState
|
||||
import fr.tuxgyver.cachette.ui.components.Artwork
|
||||
import fr.tuxgyver.cachette.ui.components.formatDuration
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import fr.tuxgyver.noxely.offline.OfflineState
|
||||
import fr.tuxgyver.noxely.playback.PlayerUiState
|
||||
import fr.tuxgyver.noxely.ui.components.Artwork
|
||||
import fr.tuxgyver.noxely.ui.components.formatDuration
|
||||
|
||||
@Composable
|
||||
fun NowPlayingScreen(
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.playlists
|
||||
package fr.tuxgyver.noxely.ui.playlists
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -35,8 +35,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** Choix de la playlist d'accueil, avec création à la volée si aucune ne convient. */
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.playlists
|
||||
package fr.tuxgyver.noxely.ui.playlists
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -33,9 +33,9 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.ui.components.MediaRow
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.ui.components.MediaRow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** Le contenu d'une playlist, dans l'ordre d'ajout. */
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.playlists
|
||||
package fr.tuxgyver.noxely.ui.playlists
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -39,7 +39,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
+14
-14
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.radio
|
||||
package fr.tuxgyver.noxely.ui.radio
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
@@ -57,17 +57,17 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.model.MediaEntry
|
||||
import fr.tuxgyver.cachette.model.MediaKind
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.cachette.radio.RADIO_GENRES
|
||||
import fr.tuxgyver.cachette.radio.RadioStation
|
||||
import fr.tuxgyver.cachette.radio.Recorder
|
||||
import fr.tuxgyver.cachette.radio.RecordingService
|
||||
import fr.tuxgyver.cachette.ui.components.Artwork
|
||||
import fr.tuxgyver.cachette.ui.components.formatBytes
|
||||
import fr.tuxgyver.cachette.ui.components.formatDuration
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.model.MediaEntry
|
||||
import fr.tuxgyver.noxely.model.MediaKind
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import fr.tuxgyver.noxely.radio.RADIO_GENRES
|
||||
import fr.tuxgyver.noxely.radio.RadioStation
|
||||
import fr.tuxgyver.noxely.radio.Recorder
|
||||
import fr.tuxgyver.noxely.radio.RecordingService
|
||||
import fr.tuxgyver.noxely.ui.components.Artwork
|
||||
import fr.tuxgyver.noxely.ui.components.formatBytes
|
||||
import fr.tuxgyver.noxely.ui.components.formatDuration
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
@@ -258,7 +258,7 @@ fun RadioScreen(
|
||||
onDismiss = { scheduleFor = null },
|
||||
onConfirm = { wanted ->
|
||||
RecordingService.start(context, station, whenTitleContains = wanted)
|
||||
onMessage("Cachette guette « $wanted » sur ${station.name}")
|
||||
onMessage("Noxely guette « $wanted » sur ${station.name}")
|
||||
scheduleFor = null
|
||||
},
|
||||
)
|
||||
@@ -293,7 +293,7 @@ private fun ScheduleDialog(
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
text = "Cachette écoutera ${station.name} et déclenchera l'enregistrement dès " +
|
||||
text = "Noxely écoutera ${station.name} et déclenchera l'enregistrement dès " +
|
||||
"qu'un titre contenant ce texte sera annoncé, puis s'arrêtera à la fin du morceau.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
+18
-4
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.sources
|
||||
package fr.tuxgyver.noxely.ui.sources
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.automirrored.rounded.MenuBook
|
||||
import androidx.compose.material.icons.rounded.Headphones
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.icons.rounded.LibraryMusic
|
||||
import androidx.compose.material.icons.rounded.Public
|
||||
import androidx.compose.material.icons.rounded.Radio
|
||||
@@ -49,9 +50,9 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.model.SourceKind
|
||||
import fr.tuxgyver.cachette.sources.CatalogId
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.model.SourceKind
|
||||
import fr.tuxgyver.noxely.sources.CatalogId
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
@@ -62,6 +63,7 @@ fun SourcesScreen(
|
||||
onBrowseSource: (SourceKind) -> Unit,
|
||||
onBrowseCatalog: (CatalogId) -> Unit,
|
||||
onBrowseRadio: () -> Unit,
|
||||
onOpenAbout: () -> Unit,
|
||||
) {
|
||||
val entries by container.library.entries.collectAsStateWithLifecycle()
|
||||
var sheet by remember { mutableStateOf<ImportKind?>(null) }
|
||||
@@ -176,6 +178,18 @@ fun SourcesScreen(
|
||||
UpdateCard(container)
|
||||
}
|
||||
|
||||
item {
|
||||
SourceCard(
|
||||
icon = Icons.Rounded.Info,
|
||||
title = "À propos",
|
||||
status = "Version ${fr.tuxgyver.noxely.BuildConfig.VERSION_NAME}",
|
||||
description = "Ce que fait Noxely, ce qu'elle garantit, avec quoi elle est faite et qui en répond.",
|
||||
actionLabel = "Ouvrir",
|
||||
onAction = onOpenAbout,
|
||||
onOpen = onOpenAbout,
|
||||
)
|
||||
}
|
||||
|
||||
unavailableSourceCards(
|
||||
listOf(
|
||||
UnavailableSource(
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.sources
|
||||
package fr.tuxgyver.noxely.ui.sources
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -24,10 +24,10 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.tuxgyver.cachette.AppContainer
|
||||
import fr.tuxgyver.cachette.BuildConfig
|
||||
import fr.tuxgyver.cachette.update.UpdateInfo
|
||||
import fr.tuxgyver.cachette.update.UpdateNotifier
|
||||
import fr.tuxgyver.noxely.AppContainer
|
||||
import fr.tuxgyver.noxely.BuildConfig
|
||||
import fr.tuxgyver.noxely.update.UpdateInfo
|
||||
import fr.tuxgyver.noxely.update.UpdateNotifier
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
@@ -82,7 +82,7 @@ fun UpdateCard(container: AppContainer) {
|
||||
Text(
|
||||
text = when (current) {
|
||||
UpdateUiState.Idle ->
|
||||
"Cachette regarde chaque jour si une nouvelle version a été publiée, et la télécharge en Wi-Fi."
|
||||
"Noxely regarde chaque jour si une nouvelle version a été publiée, et la télécharge en Wi-Fi."
|
||||
UpdateUiState.Checking -> "Vérification en cours…"
|
||||
UpdateUiState.UpToDate -> "Vous avez déjà la dernière version."
|
||||
is UpdateUiState.Available -> "Version ${current.info.version} disponible."
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.ui.theme
|
||||
package fr.tuxgyver.noxely.ui.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -57,7 +57,7 @@ private val DarkColors = darkColorScheme(
|
||||
outlineVariant = Color(0xFF3A3048),
|
||||
)
|
||||
|
||||
private val CachetteShapes = Shapes(
|
||||
private val NoxelyShapes = Shapes(
|
||||
extraSmall = RoundedCornerShape(8.dp),
|
||||
small = RoundedCornerShape(14.dp),
|
||||
medium = RoundedCornerShape(20.dp),
|
||||
@@ -65,7 +65,7 @@ private val CachetteShapes = Shapes(
|
||||
extraLarge = RoundedCornerShape(36.dp),
|
||||
)
|
||||
|
||||
private val CachetteTypography = Typography().run {
|
||||
private val NoxelyTypography = Typography().run {
|
||||
copy(
|
||||
displaySmall = displaySmall.copy(fontWeight = FontWeight.Bold, letterSpacing = (-0.5).sp),
|
||||
headlineMedium = headlineMedium.copy(fontWeight = FontWeight.Bold, letterSpacing = (-0.4).sp),
|
||||
@@ -77,7 +77,7 @@ private val CachetteTypography = Typography().run {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CachetteTheme(
|
||||
fun NoxelyTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
@@ -87,8 +87,8 @@ fun CachetteTheme(
|
||||
val colors = if (darkTheme) DarkColors else LightColors
|
||||
MaterialTheme(
|
||||
colorScheme = colors,
|
||||
shapes = CachetteShapes,
|
||||
typography = CachetteTypography,
|
||||
shapes = NoxelyShapes,
|
||||
typography = NoxelyTypography,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.update
|
||||
package fr.tuxgyver.noxely.update
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.Constraints
|
||||
@@ -8,7 +8,7 @@ import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import fr.tuxgyver.cachette.CachetteApp
|
||||
import fr.tuxgyver.noxely.NoxelyApp
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
@@ -21,7 +21,7 @@ class UpdateCheckWorker(
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val repository = (applicationContext as CachetteApp).container.updates
|
||||
val repository = (applicationContext as NoxelyApp).container.updates
|
||||
val update = runCatching { repository.checkForUpdate() }.getOrNull()
|
||||
?: return Result.success()
|
||||
val apk = runCatching { repository.downloadApk(update) }.getOrNull()
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
package fr.tuxgyver.cachette.update
|
||||
package fr.tuxgyver.noxely.update
|
||||
|
||||
import android.Manifest
|
||||
import android.app.NotificationChannel
|
||||
@@ -12,7 +12,7 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import fr.tuxgyver.cachette.R
|
||||
import fr.tuxgyver.noxely.R
|
||||
import java.io.File
|
||||
|
||||
/** Prévient que la mise à jour est téléchargée et ouvre l'installateur système d'un appui. */
|
||||
@@ -38,7 +38,7 @@ object UpdateNotifier {
|
||||
|
||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_offline)
|
||||
.setContentTitle("Cachette $version est prête")
|
||||
.setContentTitle("Noxely $version est prête")
|
||||
.setContentText("Appuyez pour installer la mise à jour.")
|
||||
.setContentIntent(pending)
|
||||
.setAutoCancel(true)
|
||||
@@ -73,7 +73,7 @@ object UpdateNotifier {
|
||||
CHANNEL_ID,
|
||||
"Mises à jour",
|
||||
NotificationManager.IMPORTANCE_DEFAULT,
|
||||
).apply { description = "Disponibilité d'une nouvelle version de Cachette" }
|
||||
).apply { description = "Disponibilité d'une nouvelle version de Noxely" }
|
||||
context.getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package fr.tuxgyver.cachette.update
|
||||
package fr.tuxgyver.noxely.update
|
||||
|
||||
import android.content.Context
|
||||
import fr.tuxgyver.cachette.BuildConfig
|
||||
import fr.tuxgyver.noxely.BuildConfig
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -103,7 +103,7 @@ class UpdateRepository(
|
||||
instanceFollowRedirects = true
|
||||
connectTimeout = 15_000
|
||||
readTimeout = 60_000
|
||||
setRequestProperty("User-Agent", "Cachette/$currentVersion (Android)")
|
||||
setRequestProperty("User-Agent", "Noxely/$currentVersion (Android)")
|
||||
setRequestProperty("Authorization", "token ${BuildConfig.GITEA_UPDATE_TOKEN}")
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<!-- Note de musique posée sur une flèche descendante : « mis de côté, hors ligne ». -->
|
||||
<path
|
||||
android:fillColor="#FFB79CFF"
|
||||
android:pathData="M64,28 L64,58.5 a10,10 0 1,1 -6,-9.2 L58,37.5 L44,40.6 L44,64 a10,10 0 1,1 -6,-9.2 L38,35.8 L64,28 Z" />
|
||||
<path
|
||||
android:fillColor="#FF7CE7D6"
|
||||
android:pathData="M54,70 L54,80 L47,80 L54,88 L61,80 L54,80 Z" />
|
||||
</vector>
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 134 KiB |
@@ -1,5 +1,5 @@
|
||||
<resources>
|
||||
<style name="Theme.Cachette" parent="android:Theme.Material.NoActionBar">
|
||||
<style name="Theme.Noxely" parent="android:Theme.Material.NoActionBar">
|
||||
<item name="android:windowBackground">@color/splash_background</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<resources>
|
||||
<color name="splash_background">#FFF7F2FA</color>
|
||||
<color name="ic_launcher_background">#FF1B1033</color>
|
||||
<color name="ic_launcher_background">#FF0E9CC7</color>
|
||||
</resources>
|
||||
@@ -1,4 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">Cachette</string>
|
||||
<string name="app_name">Noxely</string>
|
||||
<string name="download_channel_name">Téléchargements</string>
|
||||
</resources>
|
||||
@@ -1,5 +1,5 @@
|
||||
<resources>
|
||||
<style name="Theme.Cachette" parent="android:Theme.Material.Light.NoActionBar">
|
||||
<style name="Theme.Noxely" parent="android:Theme.Material.Light.NoActionBar">
|
||||
<item name="android:windowBackground">@color/splash_background</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
|
||||
+1
-1
@@ -14,5 +14,5 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Cachette"
|
||||
rootProject.name = "Noxely"
|
||||
include(":app")
|
||||
Reference in new issue
Block a user