diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000..08bebc3
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000..08bebc3
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_detail.xml b/app/src/main/res/layout/book_detail.xml
new file mode 100644
index 0000000..9729ec8
--- /dev/null
+++ b/app/src/main/res/layout/book_detail.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000..08bebc3
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_detail.xml b/app/src/main/res/layout/book_detail.xml
new file mode 100644
index 0000000..9729ec8
--- /dev/null
+++ b/app/src/main/res/layout/book_detail.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_item.xml b/app/src/main/res/layout/book_item.xml
new file mode 100644
index 0000000..ada1307
--- /dev/null
+++ b/app/src/main/res/layout/book_item.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000..08bebc3
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_detail.xml b/app/src/main/res/layout/book_detail.xml
new file mode 100644
index 0000000..9729ec8
--- /dev/null
+++ b/app/src/main/res/layout/book_detail.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_item.xml b/app/src/main/res/layout/book_item.xml
new file mode 100644
index 0000000..ada1307
--- /dev/null
+++ b/app/src/main/res/layout/book_item.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000..08bebc3
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_detail.xml b/app/src/main/res/layout/book_detail.xml
new file mode 100644
index 0000000..9729ec8
--- /dev/null
+++ b/app/src/main/res/layout/book_detail.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_item.xml b/app/src/main/res/layout/book_item.xml
new file mode 100644
index 0000000..ada1307
--- /dev/null
+++ b/app/src/main/res/layout/book_item.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000..08bebc3
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_detail.xml b/app/src/main/res/layout/book_detail.xml
new file mode 100644
index 0000000..9729ec8
--- /dev/null
+++ b/app/src/main/res/layout/book_detail.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_item.xml b/app/src/main/res/layout/book_item.xml
new file mode 100644
index 0000000..ada1307
--- /dev/null
+++ b/app/src/main/res/layout/book_item.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000..08bebc3
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_detail.xml b/app/src/main/res/layout/book_detail.xml
new file mode 100644
index 0000000..9729ec8
--- /dev/null
+++ b/app/src/main/res/layout/book_detail.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_item.xml b/app/src/main/res/layout/book_item.xml
new file mode 100644
index 0000000..ada1307
--- /dev/null
+++ b/app/src/main/res/layout/book_item.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000..08bebc3
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_detail.xml b/app/src/main/res/layout/book_detail.xml
new file mode 100644
index 0000000..9729ec8
--- /dev/null
+++ b/app/src/main/res/layout/book_detail.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_item.xml b/app/src/main/res/layout/book_item.xml
new file mode 100644
index 0000000..ada1307
--- /dev/null
+++ b/app/src/main/res/layout/book_item.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData>()
+ fetchList()
+ }
+
+ fun fetchList() {
+ viewModelScope.launch {
+ books.value = fetchListUseCase.fetchList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000..08bebc3
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_detail.xml b/app/src/main/res/layout/book_detail.xml
new file mode 100644
index 0000000..9729ec8
--- /dev/null
+++ b/app/src/main/res/layout/book_detail.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/book_item.xml b/app/src/main/res/layout/book_item.xml
new file mode 100644
index 0000000..ada1307
--- /dev/null
+++ b/app/src/main/res/layout/book_item.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..d53b70e
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,80 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+ namespace 'fr.tf1.player.atelierplayermvvm'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "fr.tf1.player.atelierplayermvvm"
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+
+ // AAC
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.activity:activity-ktx:1.6.1"
+ //implementation "androidx.fragment:fragment-ktx:1.3.6"
+
+ //Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ //Hiilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ //Glide
+ implementation "com.github.bumptech.glide:glide:4.13.2"
+ annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..97fcf2a
--- /dev/null
+++ b/app/src/androidTest/java/fr/tf1/player/atelierplayermvvm/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package fr.tf1.player.atelierplayermvvm
+
+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.tf1.player.atelierplayermvvm", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e5f69bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
new file mode 100644
index 0000000..7dec4ba
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/MyApp.kt
@@ -0,0 +1,10 @@
+package fr.tf1.player.atelierplayermvvm
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp: Application() {
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
new file mode 100644
index 0000000..b096b47
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/Book.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class Book(
+ var id: String? = null,
+ var bookName: String? = null,
+ var picUrl: String? = null,
+ var authorName: String? = null,
+ var bookDesc: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
new file mode 100644
index 0000000..093551a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/BookListPager.kt
@@ -0,0 +1,9 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+data class BookListPager(
+ var pageNum: Int? = null,
+ var pageSize: Int? = null,
+ var total: Int? = null,
+ var list: List? = null,
+
+ )
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
new file mode 100644
index 0000000..af80c0a
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/model/ResponseWrapper.kt
@@ -0,0 +1,8 @@
+package fr.tf1.player.atelierplayermvvm.data.model
+
+
+class ResponseWrapper {
+ var code: Int? = null
+ var msg: String? = null
+ var data: T? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
new file mode 100644
index 0000000..00d7916
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/network/BookApi.kt
@@ -0,0 +1,21 @@
+package fr.tf1.player.atelierplayermvvm.data.network
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.model.BookListPager
+import fr.tf1.player.atelierplayermvvm.data.model.ResponseWrapper
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface BookApi {
+
+ @GET("book/searchByPage")
+ suspend fun fetchBookList(
+ @Query("curr") currentPage: Int = 0,
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("book/queryBookDetail/{bookId}")
+ suspend fun fetchBookDetailById(@Path("bookId") bookId: Int = 0): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
new file mode 100644
index 0000000..de31d71
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/repo/BookRepository.kt
@@ -0,0 +1,33 @@
+package fr.tf1.player.atelierplayermvvm.data.repo
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import javax.inject.Inject
+
+interface BookRepository {
+ suspend fun fetchList(): List
+ suspend fun fetchDetail(id: Int): Book?
+}
+
+
+class BookRepositoryImpl @Inject constructor(val bookApi: BookApi) : BookRepository {
+
+ override suspend fun fetchList(): List {
+ val response = bookApi.fetchBookList()
+ return if (response.isSuccessful) {
+ response.body()?.data?.list ?: emptyList()
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun fetchDetail(id: Int): Book? {
+ val response = bookApi.fetchBookDetailById(id)
+ return if (response.isSuccessful) {
+ response.body()?.data
+ } else {
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
new file mode 100644
index 0000000..29e34ab
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchDetailUseCase.kt
@@ -0,0 +1,11 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchDetailUseCase @Inject constructor(val bookRepository: BookRepository) {
+ suspend fun fetchBookDetail(bookId: Int): Book? {
+ return bookRepository.fetchDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
new file mode 100644
index 0000000..7cf37dc
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/data/usecases/FetchListUseCase.kt
@@ -0,0 +1,14 @@
+package fr.tf1.player.atelierplayermvvm.data.usecases
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import javax.inject.Inject
+
+class FetchListUseCase @Inject constructor(val bookRepository: BookRepository) {
+
+ suspend fun fetchList(): List {
+ return bookRepository.fetchList()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
new file mode 100644
index 0000000..9d86be1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/di/module/AppModule.kt
@@ -0,0 +1,58 @@
+package fr.tf1.player.atelierplayermvvm.di.module
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import fr.tf1.player.atelierplayermvvm.data.network.BookApi
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepository
+import fr.tf1.player.atelierplayermvvm.data.repo.BookRepositoryImpl
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Provides
+ fun providesOkHttpClientFactory(): OkHttpClient {
+ return OkHttpClient.Builder()
+ .build()
+ }
+
+ @Provides
+ fun providesBookApi(
+ okHttpClient: OkHttpClient
+ ): BookApi {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(BookApi::class.java)
+ }
+
+ @Provides
+ fun provideBookRepository(bookApi: BookApi): BookRepository {
+ return BookRepositoryImpl(bookApi)
+ }
+
+ @Provides
+ fun provideFetchListUseCase(bookRepository: BookRepository): FetchListUseCase {
+ return FetchListUseCase(bookRepository)
+ }
+
+ @Provides
+ fun provideFetchDetailUseCase(bookRepository: BookRepository): FetchDetailUseCase {
+ return FetchDetailUseCase(bookRepository)
+ }
+
+}
+
+const val BASE_URL = "https://duoyi.org/"
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
new file mode 100644
index 0000000..f6b6960
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailActivity.kt
@@ -0,0 +1,48 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.bumptech.glide.Glide
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.list.ListViewModel
+
+@AndroidEntryPoint
+class BookDetailActivity : AppCompatActivity() {
+ private val viewModel: BookDetailViewModel by viewModels()
+
+ private lateinit var bookCover: ImageView
+ private lateinit var bookTitle: TextView
+ private lateinit var authorName: TextView
+ private lateinit var bookDescription: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.book_detail)
+
+ bookTitle = findViewById(R.id.bookTitle)
+ authorName = findViewById(R.id.authorName)
+ bookDescription = findViewById(R.id.bookDescription)
+ bookCover = findViewById(R.id.bookCover)
+
+ val bookId = intent.getIntExtra("BOOK_ID", 0)
+ viewModel.book.observe(this) { book ->
+ if (book != null) {
+ bookTitle.text = book.bookName
+ authorName.text = book.authorName
+ bookDescription.text = book.bookDesc
+ Glide.with(this@BookDetailActivity)
+ .load(book.picUrl)
+ .into(bookCover)
+ } else {
+ Toast.makeText(this@BookDetailActivity, "No book found", Toast.LENGTH_LONG).show()
+ }
+
+ }
+ viewModel.fetchBookDetail(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
new file mode 100644
index 0000000..45517d0
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/details/BookDetailViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.details
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchDetailUseCase
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookDetailViewModel @Inject constructor(val fetchDetailUseCase: FetchDetailUseCase) :
+ ViewModel() {
+ val book: MutableLiveData
+
+ init {
+ book = MutableLiveData()
+
+ }
+
+ fun fetchBookDetail(bookId: Int) {
+ viewModelScope.launch {
+ book.value = fetchDetailUseCase.fetchBookDetail(bookId)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
new file mode 100644
index 0000000..630f1a1
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/BookListAdapter.kt
@@ -0,0 +1,55 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+
+class BookListAdapter(val onBookItemClick: (id: Int) -> Unit) : RecyclerView.Adapter() {
+ private val items: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooklListViewHolder {
+ val v: View =
+ LayoutInflater.from(parent.context).inflate(R.layout.book_item, parent, false)
+ return BooklListViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: BooklListViewHolder, position: Int) {
+ val book = items[position]
+ holder.bookName.text = book.bookName
+ val context = holder.bookCover.context
+ Glide.with(context)
+ .load(book.picUrl)
+ .into(holder.bookCover)
+ holder.itemView.setOnClickListener{
+ book.id?.let {
+ onBookItemClick(it.toInt())
+ }
+ }
+ }
+
+ fun refresh(newItems: List? = null) {
+ newItems?.let {
+ items.clear()
+ items.addAll(newItems)
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ class BooklListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val bookCover: ImageView = itemView.findViewById(R.id.bookCover)
+ val bookName: TextView = itemView.findViewById(R.id.bookName)
+
+ }
+}
+
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
new file mode 100644
index 0000000..fab08ec
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListActivity.kt
@@ -0,0 +1,34 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import fr.tf1.player.atelierplayermvvm.R
+import fr.tf1.player.atelierplayermvvm.ui.details.BookDetailActivity
+
+@AndroidEntryPoint
+class ListActivity : AppCompatActivity() {
+
+ private val viewModel: ListViewModel by viewModels()
+ private lateinit var bookListView: RecyclerView
+ private var bookListAdapter = BookListAdapter { bookId ->
+ val intent = Intent(this@ListActivity, BookDetailActivity::class.java)
+ intent.putExtra("BOOK_ID", bookId)
+ startActivity(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_list)
+ bookListView = findViewById(R.id.booklist)
+ bookListView.adapter = bookListAdapter
+ viewModel.books.observe(this) { bookList ->
+ bookListAdapter.refresh(bookList)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
new file mode 100644
index 0000000..d32da0d
--- /dev/null
+++ b/app/src/main/java/fr/tf1/player/atelierplayermvvm/ui/list/ListViewModel.kt
@@ -0,0 +1,28 @@
+package fr.tf1.player.atelierplayermvvm.ui.list
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import fr.tf1.player.atelierplayermvvm.data.model.Book
+import fr.tf1.player.atelierplayermvvm.data.usecases.FetchListUseCase
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListViewModel @Inject constructor(val fetchListUseCase: FetchListUseCase) : ViewModel() {
+
+ val books: MutableLiveData>
+
+ init {
+ books = MutableLiveData