diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..f519c570
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,31 @@
+## Issue
+
+- close #TBD
+
+## Overview
+
+TBD
+
+## Details (Optional)
+
+TBD
+
+## Screenshots (Optional)
+
+|Before|After|
+|:--:|:--:|
+|TBD|TBD|
+
+## Checklist
+
+- [ ] Format code (⌥⌘L in Android Studio)
+- [ ] Optimize imports (^⌥O in Android Studio)
+- [ ] Resolve Android Studio warning
+
+## What to check (Optional)
+
+- [ ] TBD
+
+## Reference(s) (Optional)
+
+- https://example.com
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fba712b1..9f13a4d8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,49 +18,39 @@ on:
- LICENSE
jobs:
- test:
+ build:
runs-on: ubuntu-latest
steps:
- # チェックアウト
- uses: actions/checkout@v2
- # JDKのセットアップ
- - name: set up JDK 1.8
- uses: actions/setup-java@v1
- with:
- java-version: 1.8
-
- # Gradleのキャッシュ復元
- - uses: actions/cache@v2
+ - uses: ./.github/workflows/templates/setup-android
with:
- path: ~/.gradle/caches
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
-
- # 依存関係の出力
- - name: Displays the Android dependencies of the project
- run: ./gradlew androidDependencies
+ use-cache: true
- # コンパイル
- name: Run Compile
run: ./gradlew assembleDebug
- # テスト
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: ./.github/workflows/templates/setup-android
+ with:
+ use-cache: true
+
- name: Test with Gradle
run: ./gradlew test --stacktrace
- # コードカバレッジをJaCoCo形式で取得
- name: Get code coverage for JaCoCo
run: ./gradlew jacocoDevelopDebugTestReport
- # コードカバレッジをCodecovへアップロード
- name: Upload code coverage to Codecov
- run: bash <(curl -s https://codecov.io/bash)
- env:
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+ uses: codecov/codecov-action@v2
+ with:
+ fail_ci_if_error: true
+ verbose: true
- # テスト結果とコードカバレッジのアップロード
- name: Upload test results and code coverage Artifact
uses: actions/upload-artifact@v2
if: success() || failure()
@@ -76,36 +66,35 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- # チェックアウト
- uses: actions/checkout@v2
- # 静的解析
- name: Run Inspection
run: ./gradlew lint
- # アーティファクトへアップロード
+ - name: Show results on GitHub
+ uses: yutailang0119/action-android-lint@v2
+ with:
+ report-path: '**/build/reports/lint-results-*.xml'
+
- name: Upload results Artifact
uses: actions/upload-artifact@v2
if: success() || failure()
with:
name: results
path: |
- **/build/reports/lint-results.html
- **/build/reports/lint-results.xml
+ **/build/reports/lint-results-*.html
+ **/build/reports/lint-results-*.xml
if-no-files-found: error
retention-days: 14
detekt:
runs-on: ubuntu-latest
steps:
- # チェックアウト
- uses: actions/checkout@v2
- # 静的解析
- name: Lint with detekt
run: ./gradlew detekt
- # アーティファクトへアップロード
- name: Upload results Artifact
uses: actions/upload-artifact@v2
if: failure()
@@ -116,3 +105,33 @@ jobs:
if-no-files-found: error
retention-days: 14
+ ktlint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install ktlint
+ run: |
+ curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.42.1/ktlint
+ chmod a+x ktlint
+ sudo mv ktlint /usr/local/bin/
+
+ - name: Lint with ktlint
+ run: ktlint --reporter=checkstyle,output=build/ktlint-report.xml
+ continue-on-error: true
+
+ - name: Show results on GitHub
+ uses: yutailang0119/action-ktlint@v2
+ with:
+ report-path: build/ktlint-report.xml
+ continue-on-error: true
+
+ - name: Upload results Artifact
+ uses: actions/upload-artifact@v2
+ if: failure()
+ with:
+ name: results
+ path: |
+ **/build/ktlint-report.xml
+ if-no-files-found: error
+ retention-days: 14
diff --git a/.github/workflows/generate-aab.yml b/.github/workflows/generate-aab.yml
index 26312427..e8d208d4 100644
--- a/.github/workflows/generate-aab.yml
+++ b/.github/workflows/generate-aab.yml
@@ -13,31 +13,21 @@ jobs:
generate-aab:
runs-on: ubuntu-latest
steps:
- # チェックアウト
- uses: actions/checkout@v2
- # JDKのセットアップ
- - name: set up JDK 1.8
- uses: actions/setup-java@v1
+ - uses: ./.github/workflows/templates/setup-android
with:
- java-version: 1.8
+ use-cache: false
- # 依存関係の出力
- - name: Displays the Android dependencies of the project
- run: ./gradlew androidDependencies
-
- # キーストアのデコード
- name: Decode Keystore
run: echo ${{ secrets.ENCODED_RELEASE_KEYSTORE }} | base64 --decode > ./app/release.keystore
- # AABの生成
- name: Generate AAB
run: ./gradlew :app:bundleProductionRelease
env:
RELEASE_KEYSTORE_STORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_STORE_PASSWORD }}
RELEASE_KEYSTORE_KEY_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_KEY_PASSWORD }}
- # AABのアップロード
- name: Upload AAB Artifact
uses: actions/upload-artifact@v2
with:
diff --git a/.github/workflows/templates/setup-android/action.yml b/.github/workflows/templates/setup-android/action.yml
new file mode 100644
index 00000000..26477fce
--- /dev/null
+++ b/.github/workflows/templates/setup-android/action.yml
@@ -0,0 +1,29 @@
+name: "Setup for Android"
+description: "Install dependencies for Android"
+
+inputs:
+ use-cache:
+ description: "Whether to use cache"
+ required: true
+
+runs:
+ using: "composite"
+ steps:
+ - name: Setup JDK 11
+ uses: actions/setup-java@v2
+ with:
+ distribution: 'zulu'
+ java-version: '11'
+
+ - uses: actions/cache@v2
+ if: ${{ inputs.use-cache }}
+ with:
+ path: ~/.gradle/caches
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-
+
+ - name: Displays the Android dependencies of the project
+ run: ./gradlew androidDependencies
+ shell: bash
+
diff --git a/.gitignore b/.gitignore
index d035872a..a14bd606 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
.idea/scopes/scope_settings.xml
.idea/assetWizardSettings.xml
.idea/compiler.xml
+.idea/deploymentTargetDropDown.xml
.idea/gradle.xml
.idea/jarRepositories.xml
.idea/misc.xml
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 9c7dcd69..88ea3aa1 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,24 +1,6 @@
-
-
-
-
diff --git a/.idea/dictionaries/Project.xml b/.idea/dictionaries/Project.xml
index c7112900..3b5f6f69 100644
--- a/.idea/dictionaries/Project.xml
+++ b/.idea/dictionaries/Project.xml
@@ -1,10 +1,14 @@
+ appname
cardview
codecov
crashlytics
dancingmonster
+ dorg
+ endlocal
+ errorlevel
fileprovider
firestore
imageview
@@ -14,13 +18,15 @@
monsterlist
mvvm
noninfringement
+ setlocal
textview
theuhooi
uhooi
+ uhooi's
uhooipicbook
viewmodel
viewmodels
webview
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index c104f822..8da7e880 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ You can develop this project.
### Environment
-- Android Studio: 4.1.3
+- Android Studio: Arctic Fox | 2020.3.1 Patch 4
### Configuration
@@ -55,10 +55,10 @@ You can develop this project.
1. Clone the project.
-```shell
-$ git clone https://github.com/uhooi/UhooiPicBook-Android.git
-$ cd UhooiPicBook-Android
-```
+ ```shell
+ $ git clone https://github.com/uhooi/UhooiPicBook-Android.git
+ $ cd UhooiPicBook-Android
+ ```
2. Open the project in Android Studio.
@@ -68,3 +68,7 @@ I would be happy if you contribute :)
- [New issue](https://github.com/uhooi/UhooiPicBook-Android/issues/new)
- [New pull request](https://github.com/uhooi/UhooiPicBook-Android/compare)
+
+## Stats
+
+[![Stats](https://repobeats.axiom.co/api/embed/854645b4486364c77380b9cce747b91feb127715.svg "Repobeats analytics image")](https://github.com/uhooi/UhooiPicBook-Android)
diff --git a/app/build.gradle b/app/build.gradle
index 8fe6ecf1..7aa0ffd8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,28 +1,28 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
- id 'kotlin-android-extensions'
+ id 'kotlin-parcelize'
+ id 'kotlin-kapt'
id 'androidx.navigation.safeargs.kotlin'
+ id 'dagger.hilt.android.plugin'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id 'com.google.firebase.firebase-perf'
- id 'io.gitlab.arturbosch.detekt'
- id 'kotlin-kapt'
id 'com.google.android.gms.oss-licenses-plugin'
- id 'dagger.hilt.android.plugin'
+ id 'io.gitlab.arturbosch.detekt' version '1.17.0'
id 'jacoco'
}
android {
- compileSdkVersion 29
- buildToolsVersion "29.0.3"
+ compileSdk 31
+ buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.theuhooi.uhooipicbook"
- minSdkVersion 26
- targetSdkVersion 29
- versionCode 6
- versionName "1.5.0"
+ minSdk 26
+ targetSdk 31
+ versionCode 7
+ versionName "1.6.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -48,8 +48,13 @@ android {
}
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+
kotlinOptions {
- jvmTarget = '1.8'
+ jvmTarget = '11'
}
testOptions {
@@ -59,12 +64,7 @@ android {
}
buildFeatures {
- dataBinding = true
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ dataBinding true
}
flavorDimensions 'environment'
@@ -81,49 +81,55 @@ android {
}
dependencies {
- def coil_version = '1.1.1'
- implementation "io.coil-kt:coil:$coil_version"
- implementation "io.coil-kt:coil-base:$coil_version"
- implementation "io.coil-kt:coil-gif:$coil_version"
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.4.0'
+ implementation 'com.google.android.material:material:1.4.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
+
+ // Lifecycle
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
+
+ // Navigation
+ implementation "androidx.navigation:navigation-fragment-ktx:$rootProject.nav_version"
+ implementation "androidx.navigation:navigation-ui-ktx:$rootProject.nav_version"
+
+ implementation 'androidx.recyclerview:recyclerview:1.2.1'
+
+ def coroutines_version = '1.6.0'
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutines_version"
+
+ // Hilt
+ implementation "com.google.dagger:hilt-android:$rootProject.hilt_version"
+ kapt "com.google.dagger:hilt-android-compiler:$rootProject.hilt_version"
+ implementation 'androidx.hilt:hilt-navigation-fragment:1.0.0'
// Firebase
- implementation platform('com.google.firebase:firebase-bom:27.0.0')
+ implementation platform('com.google.firebase:firebase-bom:29.0.3')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-crashlytics-ktx'
implementation 'com.google.firebase:firebase-firestore-ktx'
implementation 'com.google.firebase:firebase-messaging'
implementation 'com.google.firebase:firebase-perf'
- implementation 'androidx.recyclerview:recyclerview:1.2.0'
-
- implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
- implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
-
- def lifecycle_version = '2.2.0'
- implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
-
- implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation 'androidx.core:core-ktx:1.3.2'
- implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
-
- implementation 'com.google.android.material:material:1.3.0'
-
- implementation "com.google.dagger:hilt-android:$rootProject.hilt_version"
- kapt "com.google.dagger:hilt-android-compiler:$rootProject.hilt_version"
+ implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
- implementation "com.google.android.gms:play-services-oss-licenses:17.0.0"
+ implementation 'com.jakewharton.timber:timber:5.0.1'
- implementation 'com.jakewharton.timber:timber:4.7.1'
+ def coil_version = '1.4.0'
+ implementation "io.coil-kt:coil:$coil_version"
+ implementation "io.coil-kt:coil-base:$coil_version"
+ implementation "io.coil-kt:coil-gif:$coil_version"
- testImplementation 'org.robolectric:robolectric:4.3.1'
- testImplementation 'androidx.test:runner:1.3.0'
+ testImplementation 'org.robolectric:robolectric:4.6.1'
+ testImplementation 'androidx.test:runner:1.4.0'
+ testImplementation "androidx.test.ext:junit:1.1.3"
// LeakCanary
- def leakcanary_version = '2.6'
+ def leakcanary_version = '2.7'
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakcanary_version"
androidTestImplementation "com.squareup.leakcanary:leakcanary-android-instrumentation:$leakcanary_version"
}
@@ -133,8 +139,8 @@ kapt {
}
detekt {
- failFast = true
buildUponDefaultConfig = true
+ allRules = false
config = files("$projectDir/config/detekt/detekt.yml")
baseline = file("$projectDir/config/detekt/baseline.xml")
reports {
@@ -150,11 +156,15 @@ detekt {
enabled = true
destination = file("$buildDir/reports/detekt/detekt.txt")
}
+ sarif {
+ enabled = true
+ destination = file("$buildDir/reports/detekt/detekt.sarif")
+ }
}
}
jacoco {
- toolVersion = "0.8.6"
+ toolVersion = "0.8.7"
}
task jacocoDevelopDebugTestReport(type: JacocoReport, dependsOn: 'testDevelopDebugUnitTest', group: 'verification') {
@@ -183,3 +193,15 @@ jacocoDevelopDebugTestReport {
}
}
}
+
+// Workaround for using the enableAggregatingTask flag.
+// ref: https://github.com/google/dagger/issues/2744#issuecomment-901277926
+tasks.named("getDependencies").configure { task ->
+ def configured = false
+ project.android.applicationVariants.all {
+ if (!configured) {
+ task.inputs.files(project.files(project.configurations.getByName("productionDebugAndroidTestRuntimeClasspath")))
+ configured = true
+ }
+ }
+}
diff --git a/app/src/developDebug/ic_launcher-playstore.png b/app/src/developDebug/ic_launcher-playstore.png
new file mode 100644
index 00000000..22ccc4de
Binary files /dev/null and b/app/src/developDebug/ic_launcher-playstore.png differ
diff --git a/app/src/developDebug/res/mipmap-anydpi/ic_launcher.xml b/app/src/developDebug/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 00000000..036d09bc
--- /dev/null
+++ b/app/src/developDebug/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/developDebug/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/developDebug/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 00000000..036d09bc
--- /dev/null
+++ b/app/src/developDebug/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/developDebug/res/mipmap-hdpi/ic_launcher.png b/app/src/developDebug/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..4d2a666b
Binary files /dev/null and b/app/src/developDebug/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/developDebug/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/developDebug/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..f25b3f25
Binary files /dev/null and b/app/src/developDebug/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src/developDebug/res/mipmap-hdpi/ic_launcher_round.png b/app/src/developDebug/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..c803c288
Binary files /dev/null and b/app/src/developDebug/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/developDebug/res/mipmap-mdpi/ic_launcher.png b/app/src/developDebug/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..63216158
Binary files /dev/null and b/app/src/developDebug/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/developDebug/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/developDebug/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..8790beac
Binary files /dev/null and b/app/src/developDebug/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src/developDebug/res/mipmap-mdpi/ic_launcher_round.png b/app/src/developDebug/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9d812998
Binary files /dev/null and b/app/src/developDebug/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/developDebug/res/mipmap-xhdpi/ic_launcher.png b/app/src/developDebug/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..3596bad9
Binary files /dev/null and b/app/src/developDebug/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/developDebug/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/developDebug/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..8fd21789
Binary files /dev/null and b/app/src/developDebug/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/developDebug/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/developDebug/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..d15a1573
Binary files /dev/null and b/app/src/developDebug/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/developDebug/res/mipmap-xxhdpi/ic_launcher.png b/app/src/developDebug/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..69f54d70
Binary files /dev/null and b/app/src/developDebug/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/developDebug/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/developDebug/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..2da9b6c8
Binary files /dev/null and b/app/src/developDebug/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/developDebug/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/developDebug/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..ea243539
Binary files /dev/null and b/app/src/developDebug/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/developDebug/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/developDebug/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..b34671fe
Binary files /dev/null and b/app/src/developDebug/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/developDebug/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/developDebug/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..c6783692
Binary files /dev/null and b/app/src/developDebug/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/developDebug/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/developDebug/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..b64eb78f
Binary files /dev/null and b/app/src/developDebug/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/develop/res/values-en/strings.xml b/app/src/developDebug/res/values-en/strings.xml
similarity index 52%
rename from app/src/develop/res/values-en/strings.xml
rename to app/src/developDebug/res/values-en/strings.xml
index 4cff0e73..a13c1216 100644
--- a/app/src/develop/res/values-en/strings.xml
+++ b/app/src/developDebug/res/values-en/strings.xml
@@ -1,4 +1,4 @@
- dev-UhooiPicBook
+ devDbg-UhooiPicBook
diff --git a/app/src/developDebug/res/values/ic_launcher_background.xml b/app/src/developDebug/res/values/ic_launcher_background.xml
new file mode 100644
index 00000000..c5d5899f
--- /dev/null
+++ b/app/src/developDebug/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/app/src/developDebug/res/values/strings.xml b/app/src/developDebug/res/values/strings.xml
new file mode 100644
index 00000000..e9ddc5f4
--- /dev/null
+++ b/app/src/developDebug/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ devDbg-ウホーイ図鑑
+
diff --git a/app/src/developRelease/ic_launcher-playstore.png b/app/src/developRelease/ic_launcher-playstore.png
new file mode 100644
index 00000000..85d6e065
Binary files /dev/null and b/app/src/developRelease/ic_launcher-playstore.png differ
diff --git a/app/src/developRelease/res/mipmap-anydpi/ic_launcher.xml b/app/src/developRelease/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 00000000..036d09bc
--- /dev/null
+++ b/app/src/developRelease/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/developRelease/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/developRelease/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 00000000..036d09bc
--- /dev/null
+++ b/app/src/developRelease/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/developRelease/res/mipmap-hdpi/ic_launcher.png b/app/src/developRelease/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..97af1f70
Binary files /dev/null and b/app/src/developRelease/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/developRelease/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/developRelease/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..dde65549
Binary files /dev/null and b/app/src/developRelease/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src/developRelease/res/mipmap-hdpi/ic_launcher_round.png b/app/src/developRelease/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..7d053ea8
Binary files /dev/null and b/app/src/developRelease/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/developRelease/res/mipmap-mdpi/ic_launcher.png b/app/src/developRelease/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..b42dbed0
Binary files /dev/null and b/app/src/developRelease/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/developRelease/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/developRelease/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..d8610153
Binary files /dev/null and b/app/src/developRelease/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src/developRelease/res/mipmap-mdpi/ic_launcher_round.png b/app/src/developRelease/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..c13f54ac
Binary files /dev/null and b/app/src/developRelease/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/developRelease/res/mipmap-xhdpi/ic_launcher.png b/app/src/developRelease/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..fd95fdcd
Binary files /dev/null and b/app/src/developRelease/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/developRelease/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/developRelease/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..d12a6f1f
Binary files /dev/null and b/app/src/developRelease/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/developRelease/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/developRelease/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..e3203126
Binary files /dev/null and b/app/src/developRelease/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/developRelease/res/mipmap-xxhdpi/ic_launcher.png b/app/src/developRelease/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..698f13b4
Binary files /dev/null and b/app/src/developRelease/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/developRelease/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/developRelease/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..ea020564
Binary files /dev/null and b/app/src/developRelease/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/developRelease/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/developRelease/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..94608432
Binary files /dev/null and b/app/src/developRelease/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/developRelease/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/developRelease/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..37183aab
Binary files /dev/null and b/app/src/developRelease/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/developRelease/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/developRelease/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..2bf128fa
Binary files /dev/null and b/app/src/developRelease/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/developRelease/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/developRelease/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..4e659c77
Binary files /dev/null and b/app/src/developRelease/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/develop/res/values/strings.xml b/app/src/developRelease/res/values-en/strings.xml
similarity index 51%
rename from app/src/develop/res/values/strings.xml
rename to app/src/developRelease/res/values-en/strings.xml
index 168214b3..49640ef9 100644
--- a/app/src/develop/res/values/strings.xml
+++ b/app/src/developRelease/res/values-en/strings.xml
@@ -1,4 +1,4 @@
- dev-ウホーイ図鑑
+ devRls-UhooiPicBook
diff --git a/app/src/developRelease/res/values/ic_launcher_background.xml b/app/src/developRelease/res/values/ic_launcher_background.xml
new file mode 100644
index 00000000..c5d5899f
--- /dev/null
+++ b/app/src/developRelease/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/app/src/developRelease/res/values/strings.xml b/app/src/developRelease/res/values/strings.xml
new file mode 100644
index 00000000..41184a50
--- /dev/null
+++ b/app/src/developRelease/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ devRls-ウホーイ図鑑
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7760895f..92a00be7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
@@ -15,9 +16,11 @@
android:supportsRtl="true"
android:theme="@style/Theme.UhooiPicBook">
+ android:theme="@style/Theme.UhooiPicBook.NoActionBar"
+ tools:ignore="LockedOrientationActivity"
+ android:exported="true">
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
index 94480464..36536712 100644
Binary files a/app/src/main/ic_launcher-playstore.png and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/UhooiPicBookApp.kt b/app/src/main/java/com/theuhooi/uhooipicbook/UhooiPicBookApp.kt
index 28971f0c..6a4cf4d6 100644
--- a/app/src/main/java/com/theuhooi/uhooipicbook/UhooiPicBookApp.kt
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/UhooiPicBookApp.kt
@@ -76,7 +76,7 @@ class UhooiPicBookApp : Application() {
}
.componentRegistry {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- add(ImageDecoderDecoder())
+ add(ImageDecoderDecoder(applicationContext))
} else {
add(GifDecoder())
}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/data/Result.kt b/app/src/main/java/com/theuhooi/uhooipicbook/data/Result.kt
new file mode 100644
index 00000000..0f295a70
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/data/Result.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.theuhooi.uhooipicbook.data
+
+/**
+ * A generic class that holds a value or an exception
+ */
+sealed class Result {
+ data class Success(val data: T) : Result()
+ data class Error(val exception: Exception) : Result()
+}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/data/monsters/MonstersRepository.kt b/app/src/main/java/com/theuhooi/uhooipicbook/data/monsters/MonstersRepository.kt
new file mode 100644
index 00000000..be813cc6
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/data/monsters/MonstersRepository.kt
@@ -0,0 +1,8 @@
+package com.theuhooi.uhooipicbook.data.monsters
+
+import com.theuhooi.uhooipicbook.data.Result
+import com.theuhooi.uhooipicbook.data.monsters.impl.MonsterDto
+
+interface MonstersRepository {
+ suspend fun fetchMonsters(): Result>
+}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/entities/MonsterItem.kt b/app/src/main/java/com/theuhooi/uhooipicbook/data/monsters/impl/MonsterDto.kt
similarity index 71%
rename from app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/entities/MonsterItem.kt
rename to app/src/main/java/com/theuhooi/uhooipicbook/data/monsters/impl/MonsterDto.kt
index 7ae4fe8b..855953b8 100644
--- a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/entities/MonsterItem.kt
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/data/monsters/impl/MonsterDto.kt
@@ -1,11 +1,8 @@
-package com.theuhooi.uhooipicbook.modules.monsterlist.entities
+package com.theuhooi.uhooipicbook.data.monsters.impl
-import android.os.Parcelable
import com.google.firebase.firestore.PropertyName
-import kotlinx.android.parcel.Parcelize
-@Parcelize
-data class MonsterItem(
+data class MonsterDto(
val name: String = "",
val description: String = "",
@get:PropertyName("base_color")
@@ -18,4 +15,4 @@ data class MonsterItem(
@set:PropertyName("dancing_url")
var dancingUrlString: String = "",
val order: Int = 0
-) : Parcelable
+)
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/data/monsters/impl/MonstersFirestoreClient.kt b/app/src/main/java/com/theuhooi/uhooipicbook/data/monsters/impl/MonstersFirestoreClient.kt
new file mode 100644
index 00000000..1abe5931
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/data/monsters/impl/MonstersFirestoreClient.kt
@@ -0,0 +1,42 @@
+package com.theuhooi.uhooipicbook.data.monsters.impl
+
+import com.google.firebase.firestore.FirebaseFirestoreException
+import com.google.firebase.firestore.ktx.firestore
+import com.google.firebase.ktx.Firebase
+import com.theuhooi.uhooipicbook.data.Result
+import com.theuhooi.uhooipicbook.data.monsters.MonstersRepository
+import com.theuhooi.uhooipicbook.di.IoDispatcher
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.tasks.await
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+class MonstersFirestoreClient @Inject constructor(
+ @IoDispatcher private val ioDispatcher: CoroutineDispatcher
+) : MonstersRepository {
+
+ // region Stored Instance Properties
+
+ private val firestore = Firebase.firestore
+
+ // endregion
+
+ // region MonstersRepository
+
+ override suspend fun fetchMonsters(): Result> {
+ return withContext(ioDispatcher) {
+ val querySnapshot = try {
+ firestore.collection("monsters")
+ .orderBy(MonsterDto::order.name)
+ .get()
+ .await()
+ } catch (e: FirebaseFirestoreException) {
+ return@withContext Result.Error(e)
+ }
+ Result.Success(querySnapshot.toObjects(MonsterDto::class.java))
+ }
+ }
+
+ // endregion
+
+}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/di/AppModule.kt b/app/src/main/java/com/theuhooi/uhooipicbook/di/AppModule.kt
index c6770b21..187fec76 100644
--- a/app/src/main/java/com/theuhooi/uhooipicbook/di/AppModule.kt
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/di/AppModule.kt
@@ -1,7 +1,7 @@
package com.theuhooi.uhooipicbook.di
-import com.theuhooi.uhooipicbook.modules.monsterlist.MonstersRepository
-import com.theuhooi.uhooipicbook.repository.monsters.firebase.MonstersFirestoreClient
+import com.theuhooi.uhooipicbook.data.monsters.MonstersRepository
+import com.theuhooi.uhooipicbook.data.monsters.impl.MonstersFirestoreClient
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/di/CoroutinesModule.kt b/app/src/main/java/com/theuhooi/uhooipicbook/di/CoroutinesModule.kt
new file mode 100644
index 00000000..0c305487
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/di/CoroutinesModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.theuhooi.uhooipicbook.di
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+
+@InstallIn(SingletonComponent::class)
+@Module
+object CoroutinesModule {
+
+ @IoDispatcher
+ @Provides
+ fun providesIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/di/CoroutinesQualifiers.kt b/app/src/main/java/com/theuhooi/uhooipicbook/di/CoroutinesQualifiers.kt
new file mode 100644
index 00000000..9f3e97a1
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/di/CoroutinesQualifiers.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.theuhooi.uhooipicbook.di
+
+import javax.inject.Qualifier
+
+@Retention(AnnotationRetention.BINARY)
+@Qualifier
+annotation class IoDispatcher
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/domain/models/MonsterItem.kt b/app/src/main/java/com/theuhooi/uhooipicbook/domain/models/MonsterItem.kt
new file mode 100644
index 00000000..695514b1
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/domain/models/MonsterItem.kt
@@ -0,0 +1,39 @@
+package com.theuhooi.uhooipicbook.domain.models
+
+import android.os.Parcelable
+import androidx.recyclerview.widget.DiffUtil
+import com.theuhooi.uhooipicbook.data.monsters.impl.MonsterDto
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class MonsterItem(
+ val name: String,
+ val description: String,
+ val baseColorCode: String,
+ val iconUrlString: String,
+ val dancingUrlString: String,
+ val order: Int
+) : Parcelable {
+ companion object {
+ val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: MonsterItem, newItem: MonsterItem): Boolean {
+ return oldItem.order == newItem.order
+ }
+
+ override fun areContentsTheSame(oldItem: MonsterItem, newItem: MonsterItem): Boolean {
+ return oldItem == newItem
+ }
+ }
+
+ internal fun create(dto: MonsterDto): MonsterItem {
+ return MonsterItem(
+ dto.name,
+ dto.description,
+ dto.baseColorCode,
+ dto.iconUrlString,
+ dto.dancingUrlString,
+ dto.order
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/MonsterListRecyclerViewAdapter.kt b/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/MonsterListRecyclerViewAdapter.kt
deleted file mode 100644
index ea454a13..00000000
--- a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/MonsterListRecyclerViewAdapter.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.theuhooi.uhooipicbook.modules.monsterlist
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LiveData
-import androidx.recyclerview.widget.RecyclerView
-import com.theuhooi.uhooipicbook.databinding.ItemMonsterListBinding
-import com.theuhooi.uhooipicbook.modules.monsterlist.MonsterListFragment.OnListFragmentInteractionListener
-import com.theuhooi.uhooipicbook.modules.monsterlist.entities.MonsterItem
-
-class MonsterListRecyclerViewAdapter(
- private val listener: OnListFragmentInteractionListener?,
- private val monsters: LiveData>,
- private val viewLifecycleOwner: LifecycleOwner
-) : RecyclerView.Adapter() {
-
- // region Stored Instance Properties
-
- private val onClickListener = View.OnClickListener { v ->
- val item = v.tag as MonsterItem
- this.listener?.onListFragmentInteraction(item)
- }
-
- // endregion
-
- // region View Life-Cycle Methods
-
- override fun onCreateViewHolder(
- parent: ViewGroup,
- viewType: Int
- ): MonsterListRecyclerViewHolder {
- val binding = ItemMonsterListBinding.inflate(
- LayoutInflater.from(parent.context), parent, false
- )
- return MonsterListRecyclerViewHolder(binding)
- }
-
- override fun onBindViewHolder(holder: MonsterListRecyclerViewHolder, position: Int) {
- holder.binding.lifecycleOwner = this.viewLifecycleOwner
- val monster = this.monsters.value?.get(position)
- holder.binding.monsterItem = monster
- holder.binding.cardView.tag = monster
- holder.binding.cardView.setOnClickListener(this.onClickListener)
- }
-
- override fun getItemCount(): Int = this.monsters.value?.size ?: 0
-
- // endregion
-
- // region ViewHolder
-
- class MonsterListRecyclerViewHolder(val binding: ItemMonsterListBinding) : RecyclerView.ViewHolder(binding.root)
-
- // endregion
-
-}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/MonstersRepository.kt b/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/MonstersRepository.kt
deleted file mode 100644
index cf90912e..00000000
--- a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/MonstersRepository.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.theuhooi.uhooipicbook.modules.monsterlist
-
-import com.theuhooi.uhooipicbook.modules.monsterlist.entities.MonsterItem
-
-interface MonstersRepository {
- fun loadMonsters(
- onSuccess: (monsters: List) -> Unit,
- onFailure: (error: Throwable) -> Unit
- )
-}
-
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/viewmodels/MonsterListViewModel.kt b/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/viewmodels/MonsterListViewModel.kt
deleted file mode 100644
index 980352ef..00000000
--- a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/viewmodels/MonsterListViewModel.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.theuhooi.uhooipicbook.modules.monsterlist.viewmodels
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import com.theuhooi.uhooipicbook.modules.monsterlist.MonstersRepository
-import com.theuhooi.uhooipicbook.modules.monsterlist.entities.MonsterItem
-import dagger.hilt.android.lifecycle.HiltViewModel
-import javax.inject.Inject
-
-@HiltViewModel
-class MonsterListViewModel @Inject constructor(
- private val repository: MonstersRepository
-) : ViewModel() {
-
- // region Stored Instance Properties
-
- private val _monsters = MutableLiveData>()
- val monsters: LiveData>
- get() = _monsters
-
- private val _isLoading = MutableLiveData(false)
- val isLoading: LiveData
- get() = _isLoading
-
- // endregion
-
- // region Initializers
-
- init {
- loadMonsters()
- }
-
- // endregion
-
- // region Other Private Methods
-
- private fun loadMonsters() {
- _isLoading.value = true
- this.repository.loadMonsters(
- onSuccess = { monsters ->
- _monsters.value = monsters
- _isLoading.value = false
- },
- onFailure = {
- // TODO: エラーハンドリング
- }
- )
- }
-
- // endregion
-
-}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/notification/services/MessagingService.kt b/app/src/main/java/com/theuhooi/uhooipicbook/notification/services/MessagingService.kt
index 3592c2ae..eb43c2b8 100644
--- a/app/src/main/java/com/theuhooi/uhooipicbook/notification/services/MessagingService.kt
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/notification/services/MessagingService.kt
@@ -9,7 +9,7 @@ import android.media.RingtoneManager
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
-import com.theuhooi.uhooipicbook.MainActivity
+import com.theuhooi.uhooipicbook.ui.MainActivity
import com.theuhooi.uhooipicbook.R
class MessagingService : FirebaseMessagingService() {
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/repository/monsters/firebase/MonstersFirestoreClient.kt b/app/src/main/java/com/theuhooi/uhooipicbook/repository/monsters/firebase/MonstersFirestoreClient.kt
deleted file mode 100644
index a57d7dc3..00000000
--- a/app/src/main/java/com/theuhooi/uhooipicbook/repository/monsters/firebase/MonstersFirestoreClient.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.theuhooi.uhooipicbook.repository.monsters.firebase
-
-import com.google.firebase.firestore.ktx.firestore
-import com.google.firebase.firestore.ktx.toObjects
-import com.google.firebase.ktx.Firebase
-import com.theuhooi.uhooipicbook.modules.monsterlist.MonstersRepository
-import com.theuhooi.uhooipicbook.modules.monsterlist.entities.MonsterItem
-import javax.inject.Inject
-
-class MonstersFirestoreClient @Inject constructor() : MonstersRepository {
-
- // region Stored Instance Properties
-
- private val firestore = Firebase.firestore
-
- // endregion
-
- // region MonstersRepository
-
- override fun loadMonsters(
- onSuccess: (monsters: List) -> Unit,
- onFailure: (error: Throwable) -> Unit
- ) {
- this.firestore.collection("monsters")
- .orderBy(MonsterItem::order.name)
- .get()
- .addOnSuccessListener { result ->
- onSuccess(result.toObjects())
- }
- .addOnFailureListener { exception ->
- onFailure(exception)
- }
- }
-
- // endregion
-
-}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/MainActivity.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/MainActivity.kt
similarity index 59%
rename from app/src/main/java/com/theuhooi/uhooipicbook/MainActivity.kt
rename to app/src/main/java/com/theuhooi/uhooipicbook/ui/MainActivity.kt
index d037ca12..70e61902 100644
--- a/app/src/main/java/com/theuhooi/uhooipicbook/MainActivity.kt
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/MainActivity.kt
@@ -1,7 +1,6 @@
-package com.theuhooi.uhooipicbook
+package com.theuhooi.uhooipicbook.ui
import android.content.Intent
-import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
@@ -10,22 +9,18 @@ import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import com.google.android.material.color.MaterialColors
-import com.theuhooi.uhooipicbook.extensions.IntColorInterface
-import com.theuhooi.uhooipicbook.modules.monsterlist.MonsterListFragment
-import com.theuhooi.uhooipicbook.modules.monsterlist.MonsterListFragmentDirections
-import com.theuhooi.uhooipicbook.modules.monsterlist.entities.MonsterItem
+import com.theuhooi.uhooipicbook.R
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
-class MainActivity : AppCompatActivity(), MonsterListFragment.OnListFragmentInteractionListener,
- IntColorInterface {
+class MainActivity : AppCompatActivity() {
// region View Life-Cycle Methods
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
+ setContentView(R.layout.main_activity)
configureToolBar()
openNotificationUrlIfNeeded()
@@ -43,7 +38,7 @@ class MainActivity : AppCompatActivity(), MonsterListFragment.OnListFragmentInte
val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == R.id.monster_list_fragment) {
- this.supportActionBar?.setBackgroundDrawable(
+ supportActionBar?.setBackgroundDrawable(
ColorDrawable(
MaterialColors.getColor(
this,
@@ -52,7 +47,7 @@ class MainActivity : AppCompatActivity(), MonsterListFragment.OnListFragmentInte
)
)
)
- this.window.statusBarColor = MaterialColors.getColor(
+ window.statusBarColor = MaterialColors.getColor(
this,
R.attr.colorPrimaryVariant,
"colorPrimaryVariant is not set in the current theme"
@@ -64,7 +59,7 @@ class MainActivity : AppCompatActivity(), MonsterListFragment.OnListFragmentInte
}
private fun openNotificationUrlIfNeeded() {
- val urlString = this.intent.getStringExtra(getString(R.string.notification_url_extra_name))
+ val urlString = intent.getStringExtra(getString(R.string.notification_url_extra_name))
if (urlString != null) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(urlString))
startActivity(intent)
@@ -73,19 +68,4 @@ class MainActivity : AppCompatActivity(), MonsterListFragment.OnListFragmentInte
// endregion
- // region MonsterListFragment.OnListFragmentInteractionListener
-
- override fun onListFragmentInteraction(item: MonsterItem) {
- val action = MonsterListFragmentDirections.actionListToDetail(item)
- findNavController(R.id.nav_host_fragment).navigate(action)
-
- if (item.baseColorCode.isNotEmpty()) {
- val actionBarColor = Color.parseColor(item.baseColorCode)
- this.supportActionBar?.setBackgroundDrawable(ColorDrawable(actionBarColor))
- this.window.statusBarColor = actionBarColor.actionBarColorToStatusBarColor
- }
- }
-
- // endregion
-
}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterdetail/dancingmonster/DancingMonsterFragment.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/dancingmonster/DancingMonsterFragment.kt
similarity index 55%
rename from app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterdetail/dancingmonster/DancingMonsterFragment.kt
rename to app/src/main/java/com/theuhooi/uhooipicbook/ui/dancingmonster/DancingMonsterFragment.kt
index 60841e52..eacc619a 100644
--- a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterdetail/dancingmonster/DancingMonsterFragment.kt
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/dancingmonster/DancingMonsterFragment.kt
@@ -1,23 +1,24 @@
-package com.theuhooi.uhooipicbook.modules.monsterdetail.dancingmonster
+package com.theuhooi.uhooipicbook.ui.dancingmonster
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
+import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.WindowInsets
import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.navigation.fragment.navArgs
-import coil.load
-import com.theuhooi.uhooipicbook.R
-import kotlinx.android.synthetic.main.fragment_dancing_monster.view.*
+import com.theuhooi.uhooipicbook.databinding.DancingMonsterFragmentBinding
class DancingMonsterFragment : AppCompatDialogFragment() {
// region Stored Instance Properties
- private val args: DancingMonsterFragmentArgs by navArgs()
+ private var _binding: DancingMonsterFragmentBinding? = null
+ private val binding get() = _binding!!
// endregion
@@ -28,23 +29,38 @@ class DancingMonsterFragment : AppCompatDialogFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- return inflater.inflate(R.layout.fragment_dancing_monster, container, false)
+ _binding = DancingMonsterFragmentBinding.inflate(inflater, container, false)
+
+ val args: DancingMonsterFragmentArgs by navArgs()
+ binding.args = args
+
+ return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- view.dancing_imageview.load(this.args.dancingUrlString)
- view.close_button.setOnClickListener { dismiss() }
+ binding.closeButton.setOnClickListener { dismiss() }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+
+ _binding = null
}
override fun onStart() {
super.onStart()
+
dialog?.window?.apply {
- setFlags(
- WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- )
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ insetsController?.hide(WindowInsets.Type.statusBars())
+ } else {
+ setFlags(
+ WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ )
+ }
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val layoutParams = attributes.apply {
width = WindowManager.LayoutParams.MATCH_PARENT
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterdetail/MonsterDetailFragment.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterdetail/MonsterDetailFragment.kt
similarity index 63%
rename from app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterdetail/MonsterDetailFragment.kt
rename to app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterdetail/MonsterDetailFragment.kt
index 88cfccd7..12780ef9 100644
--- a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterdetail/MonsterDetailFragment.kt
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterdetail/MonsterDetailFragment.kt
@@ -1,69 +1,83 @@
-package com.theuhooi.uhooipicbook.modules.monsterdetail
+package com.theuhooi.uhooipicbook.ui.monsterdetail
import android.content.Context
import android.graphics.Bitmap
+import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
-import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
-import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
+import androidx.hilt.navigation.fragment.hiltNavGraphViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import coil.ImageLoader
-import coil.load
import coil.request.Disposable
import coil.request.ImageRequest
import com.theuhooi.uhooipicbook.R
-import kotlinx.android.synthetic.main.fragment_monster_detail.view.*
+import com.theuhooi.uhooipicbook.databinding.MonsterDetailFragmentBinding
+import com.theuhooi.uhooipicbook.domain.models.MonsterItem
+import com.theuhooi.uhooipicbook.extensions.IntColorInterface
+import com.theuhooi.uhooipicbook.ui.monsterlist.MonsterViewModel
import java.io.File
import java.io.FileOutputStream
-class MonsterDetailFragment : Fragment() {
+class MonsterDetailFragment : Fragment(R.layout.monster_detail_fragment), IntColorInterface {
// region Stored Instance Properties
private val args: MonsterDetailFragmentArgs by navArgs()
+ private val viewModel: MonsterViewModel by hiltNavGraphViewModels(R.id.monster_nav_graph)
+
private var disposable: Disposable? = null
// endregion
- // region View Life-Cycle Methods
+ // region Computed Instance Properties
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- setHasOptionsMenu(true)
- return inflater.inflate(R.layout.fragment_monster_detail, container, false)
- }
+ private val monster: MonsterItem by lazy { viewModel.findMonster(args.monsterOrder) }
+
+ // endregion
+
+ // region View Life-Cycle Methods
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- view.icon_imageview.load(this.args.monster.iconUrlString)
- view.dancing_imageview.load(this.args.monster.dancingUrlString)
- view.dancing_imageview.setOnClickListener {
+ setHasOptionsMenu(true)
+
+ val binding = MonsterDetailFragmentBinding.bind(view)
+ binding.monster = monster
+
+ binding.dancingImageview.setOnClickListener {
val action =
- MonsterDetailFragmentDirections.actionDetailToDancing(this.args.monster.dancingUrlString)
+ MonsterDetailFragmentDirections.actionDetailToDancing(monster.dancingUrlString)
findNavController().navigate(action)
}
- view.name_textview.text = this.args.monster.name
- view.description_textview.text = unescapeNewline(this.args.monster.description)
+
+ val baseColorCode = monster.baseColorCode
+ if (baseColorCode.isNotEmpty()) {
+ val activity = requireActivity()
+ val actionBarColor = Color.parseColor(baseColorCode)
+ (activity as AppCompatActivity).supportActionBar?.setBackgroundDrawable(
+ ColorDrawable(actionBarColor)
+ )
+ activity.window.statusBarColor = actionBarColor.actionBarColorToStatusBarColor
+ }
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
- inflater.inflate(R.menu.menu_monster_detail, menu)
+ inflater.inflate(R.menu.monster_detail_menu, menu)
val shareMenuItem = menu.findItem(R.id.share_menu_item)
shareMenuItem.setOnMenuItemClickListener {
shareMonster()
@@ -74,7 +88,7 @@ class MonsterDetailFragment : Fragment() {
override fun onDestroyOptionsMenu() {
super.onDestroyOptionsMenu()
- this.disposable?.dispose()
+ disposable?.dispose()
}
// endregion
@@ -82,13 +96,11 @@ class MonsterDetailFragment : Fragment() {
// region Other Private Methods
private fun shareMonster() {
- val monster = this.args.monster
val context = requireContext()
val request = ImageRequest.Builder(context)
.data(monster.iconUrlString)
.target { drawable ->
- ShareCompat.IntentBuilder
- .from(requireActivity())
+ ShareCompat.IntentBuilder(context)
.setText(monster.name + "\n" + unescapeNewline(monster.description) + "\n#UhooiPicBook")
.setStream(createTempPngFileUri(context, drawable))
.setType("image/png")
@@ -96,7 +108,7 @@ class MonsterDetailFragment : Fragment() {
.startChooser()
}
.build()
- this.disposable = ImageLoader(context).enqueue(request)
+ disposable = ImageLoader(context).enqueue(request)
}
private fun createTempPngFileUri(context: Context, drawable: Drawable): Uri? {
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterlist/MonsterListAdapter.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterlist/MonsterListAdapter.kt
new file mode 100644
index 00000000..d9954e06
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterlist/MonsterListAdapter.kt
@@ -0,0 +1,52 @@
+package com.theuhooi.uhooipicbook.ui.monsterlist
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.navigation.findNavController
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.theuhooi.uhooipicbook.databinding.MonsterListItemBinding
+import com.theuhooi.uhooipicbook.domain.models.MonsterItem
+
+class MonsterListAdapter :
+ ListAdapter(MonsterItem.DIFF_CALLBACK) {
+
+ // region View Life-Cycle Methods
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): MonsterViewHolder {
+ val binding = MonsterListItemBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
+ return MonsterViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: MonsterViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ // endregion
+
+ // region ViewHolder
+
+ class MonsterViewHolder(val binding: MonsterListItemBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ private val onClickListener = View.OnClickListener { view ->
+ val item = view.tag as MonsterItem
+ val action = MonsterListFragmentDirections.actionListToDetail(item.order)
+ view.findNavController().navigate(action)
+ }
+
+ fun bind(monster: MonsterItem) {
+ binding.monsterItem = monster
+ binding.cardView.tag = monster
+ binding.cardView.setOnClickListener(onClickListener)
+ }
+ }
+
+ // endregion
+
+}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/MonsterListFragment.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterlist/MonsterListFragment.kt
similarity index 51%
rename from app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/MonsterListFragment.kt
rename to app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterlist/MonsterListFragment.kt
index 6ace9f94..fac1b1fc 100644
--- a/app/src/main/java/com/theuhooi/uhooipicbook/modules/monsterlist/MonsterListFragment.kt
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterlist/MonsterListFragment.kt
@@ -1,89 +1,78 @@
-package com.theuhooi.uhooipicbook.modules.monsterlist
+package com.theuhooi.uhooipicbook.ui.monsterlist
-import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
-import android.view.ViewGroup
import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
+import androidx.hilt.navigation.fragment.hiltNavGraphViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.transition.TransitionManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.theuhooi.uhooipicbook.BuildConfig
import com.theuhooi.uhooipicbook.R
-import com.theuhooi.uhooipicbook.databinding.FragmentMonsterListBinding
-import com.theuhooi.uhooipicbook.modules.monsterlist.entities.MonsterItem
-import com.theuhooi.uhooipicbook.modules.monsterlist.viewmodels.MonsterListViewModel
+import com.theuhooi.uhooipicbook.databinding.MonsterListFragmentBinding
+import com.theuhooi.uhooipicbook.ui.motion.Stagger
import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
@AndroidEntryPoint
-class MonsterListFragment : Fragment() {
+class MonsterListFragment : Fragment(R.layout.monster_list_fragment) {
// region Stored Instance Properties
- private var listener: OnListFragmentInteractionListener? = null
-
- private val viewModel: MonsterListViewModel by viewModels()
+ private val viewModel: MonsterViewModel by hiltNavGraphViewModels(R.id.monster_nav_graph)
// endregion
// region View Life-Cycle Methods
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- setHasOptionsMenu(true)
- val binding = FragmentMonsterListBinding.inflate(inflater, container, false)
- binding.monsterListRecyclerview.adapter =
- MonsterListRecyclerViewAdapter(
- this.listener,
- this.viewModel.monsters,
- this.viewLifecycleOwner
- )
- binding.monsterListRecyclerview.layoutManager = LinearLayoutManager(this.context)
- binding.viewModel = this.viewModel
- binding.lifecycleOwner = this.viewLifecycleOwner
- return binding.root
- }
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
- override fun onAttach(context: Context) {
- super.onAttach(context)
+ setHasOptionsMenu(true)
- if (context is OnListFragmentInteractionListener) {
- this.listener = context
- } else {
- throw RuntimeException("$context must implement OnListFragmentInteractionListener")
+ val binding = MonsterListFragmentBinding.bind(view)
+ val list = binding.monsterListRecyclerview
+ val adapter = MonsterListAdapter()
+ list.adapter = adapter
+ list.layoutManager = LinearLayoutManager(context)
+ binding.viewModel = viewModel
+ binding.lifecycleOwner = viewLifecycleOwner
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.uiState.collect {
+ TransitionManager.beginDelayedTransition(list, Stagger())
+ adapter.submitList(it.monsterItems)
+ }
+ }
}
}
- override fun onDetach() {
- super.onDetach()
-
- this.listener = null
- }
-
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
- inflater.inflate(R.menu.menu_monster_list, menu)
+ inflater.inflate(R.menu.monster_list_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.contact_us_menu_item -> {
- val action = MonsterListFragmentDirections.actionListToWebView(getString(R.string.contact_us_url))
+ val action =
+ MonsterListFragmentDirections.actionListToWebView(getString(R.string.contact_us_url))
findNavController().navigate(action)
true
}
R.id.privacy_policy_menu_item -> {
- val intent = Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.privacy_policy_url)))
+ val intent =
+ Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.privacy_policy_url)))
startActivity(intent)
true
}
@@ -113,12 +102,4 @@ class MonsterListFragment : Fragment() {
// endregion
- // region Interfaces
-
- interface OnListFragmentInteractionListener {
- fun onListFragmentInteraction(item: MonsterItem)
- }
-
- // endregion
-
}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterlist/MonsterViewModel.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterlist/MonsterViewModel.kt
new file mode 100644
index 00000000..04e9f2c7
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/monsterlist/MonsterViewModel.kt
@@ -0,0 +1,68 @@
+package com.theuhooi.uhooipicbook.ui.monsterlist
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.theuhooi.uhooipicbook.data.Result
+import com.theuhooi.uhooipicbook.data.monsters.MonstersRepository
+import com.theuhooi.uhooipicbook.domain.models.MonsterItem
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+data class MonsterUiState(
+ val isLoading: Boolean = false,
+ val monsterItems: List = listOf()
+)
+
+@HiltViewModel
+class MonsterViewModel @Inject constructor(
+ private val repository: MonstersRepository
+) : ViewModel() {
+
+ // region Stored Instance Properties
+
+ private val _uiState = MutableStateFlow(MonsterUiState(isLoading = true))
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ // endregion
+
+ // region Initializers
+
+ init {
+ refreshMonsters()
+ }
+
+ // endregion
+
+ // region Other Public Methods
+
+ fun findMonster(order: Int): MonsterItem =
+ requireNotNull(uiState.value.monsterItems.find { it.order == order })
+
+ // endregion
+
+ // region Other Private Methods
+
+ private fun refreshMonsters() {
+ _uiState.update { it.copy(isLoading = true) }
+ viewModelScope.launch {
+ val result = repository.fetchMonsters()
+ _uiState.update {
+ when (result) {
+ is Result.Success -> it.copy(
+ monsterItems = result.data.map { dto -> MonsterItem.create(dto) },
+ isLoading = false
+ )
+ is Result.Error -> it.copy(isLoading = false) // TODO: エラーハンドリング
+ }
+ }
+ }
+ }
+
+ // endregion
+
+}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/ui/motion/Durations.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/motion/Durations.kt
new file mode 100644
index 00000000..5d7af5fe
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/motion/Durations.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.theuhooi.uhooipicbook.ui.motion
+
+// Animation durations.
+// See https://material.io/design/motion/speed.html#duration for the detail.
+
+const val LARGE_EXPAND_DURATION = 300L
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/ui/motion/Interpolators.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/motion/Interpolators.kt
new file mode 100644
index 00000000..228c1644
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/motion/Interpolators.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.theuhooi.uhooipicbook.ui.motion
+
+import android.animation.TimeInterpolator
+import androidx.core.view.animation.PathInterpolatorCompat
+
+/**
+ * Decelerate easing.
+ *
+ * Incoming elements are animated using deceleration easing, which starts a transition at peak
+ * velocity (the fastest point of an element’s movement) and ends at rest.
+ */
+val LINEAR_OUT_SLOW_IN: TimeInterpolator by lazy(LazyThreadSafetyMode.NONE) {
+ PathInterpolatorCompat.create(0f, 0f, 0.2f, 1f)
+}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/ui/motion/Stagger.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/motion/Stagger.kt
new file mode 100644
index 00000000..4d04c50f
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/motion/Stagger.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.theuhooi.uhooipicbook.ui.motion
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import androidx.transition.Fade
+import androidx.transition.SidePropagation
+import androidx.transition.TransitionValues
+
+/**
+ * Transition for stagger effect.
+ */
+// We extend Fade, so fade-in effect is handled by the parent. We customize and add a slight
+// slide-up effect to it.
+class Stagger : Fade(IN) {
+
+ init {
+ // This duration is for a single item. See the comment below about propagation.
+ duration = LARGE_EXPAND_DURATION / 2
+ interpolator = LINEAR_OUT_SLOW_IN
+ propagation = SidePropagation().apply {
+ setSide(Gravity.BOTTOM)
+ // We want the stagger effect to take as long as the duration of a single item.
+ // In other words, the last item starts to fade in around the time when the first item
+ // finishes animating. The overall animation will take about twice the duration of one
+ // item fading in.
+ setPropagationSpeed(1f)
+ }
+ }
+
+ override fun createAnimator(
+ sceneRoot: ViewGroup,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator? {
+ val view = startValues?.view ?: endValues?.view ?: return null
+ // The parent can create an Animator for the fade-in.
+ val fadeAnimator = super.createAnimator(sceneRoot, startValues, endValues) ?: return null
+ return AnimatorSet().apply {
+ playTogether(
+ fadeAnimator,
+ // We make the view to slide up a little as it fades in.
+ ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, view.height * 0.5f, 0f)
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/util/ViewBindingAdapters.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/utils/ViewBindingAdapters.kt
similarity index 89%
rename from app/src/main/java/com/theuhooi/uhooipicbook/util/ViewBindingAdapters.kt
rename to app/src/main/java/com/theuhooi/uhooipicbook/ui/utils/ViewBindingAdapters.kt
index 5d75939a..2600be8e 100644
--- a/app/src/main/java/com/theuhooi/uhooipicbook/util/ViewBindingAdapters.kt
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/utils/ViewBindingAdapters.kt
@@ -1,4 +1,4 @@
-package com.theuhooi.uhooipicbook.util
+package com.theuhooi.uhooipicbook.ui.utils
import android.view.View
import android.view.View.GONE
@@ -18,6 +18,7 @@ fun load(imageView: ImageView, imageUrl: String?) {
imageView.load(imageUrl)
}
+@Suppress("UnusedPrivateMember")
@BindingAdapter("observedList")
fun observeList(recyclerView: RecyclerView, observedList: List?) {
recyclerView.adapter?.notifyDataSetChanged()
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/modules/webview/WebViewFragment.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/webview/WebViewFragment.kt
similarity index 72%
rename from app/src/main/java/com/theuhooi/uhooipicbook/modules/webview/WebViewFragment.kt
rename to app/src/main/java/com/theuhooi/uhooipicbook/ui/webview/WebViewFragment.kt
index f44618f2..bc5f001c 100644
--- a/app/src/main/java/com/theuhooi/uhooipicbook/modules/webview/WebViewFragment.kt
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/webview/WebViewFragment.kt
@@ -1,45 +1,37 @@
-package com.theuhooi.uhooipicbook.modules.webview
+package com.theuhooi.uhooipicbook.ui.webview
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
-import android.view.LayoutInflater
import android.view.View
-import android.view.ViewGroup
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
import androidx.navigation.fragment.navArgs
import com.theuhooi.uhooipicbook.R
+import com.theuhooi.uhooipicbook.databinding.WebViewFragmentBinding
+import timber.log.Timber
-class WebViewFragment : Fragment() {
+class WebViewFragment : Fragment(R.layout.web_view_fragment) {
// region Stored Instance Properties
- private val args: WebViewFragmentArgs by navArgs()
-
- private var webView: WebView? = null
+ @Suppress("UnusedPrivateMember")
+ private val viewModel: WebViewViewModel by viewModels() // TODO: Use
// endregion
// region View Life-Cycle Methods
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- val view = inflater.inflate(R.layout.fragment_web_view, container, false)
- this.webView = view.findViewById(R.id.webview)
- return view
- }
-
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- this.webView?.webViewClient = object : WebViewClient() {
+ val binding = WebViewFragmentBinding.bind(view)
+ binding.webview.settings.javaScriptEnabled = true
+ binding.webview.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
@@ -56,12 +48,12 @@ class WebViewFragment : Fragment() {
if (intent.scheme == "http" || intent.scheme == "https") {
val fallbackUrlString = intent.getStringExtra(
BROWSER_FALLBACK_URL_EXTRA_NAME
- )
- webView?.loadUrl(fallbackUrlString)
+ ) ?: return false
+ binding.webview.loadUrl(fallbackUrlString)
return true
}
- val context = webView?.context
+ val context = binding.webview.context
val info = context?.packageManager?.resolveActivity(
intent,
PackageManager.MATCH_DEFAULT_ONLY
@@ -78,20 +70,21 @@ class WebViewFragment : Fragment() {
}
true
} catch (e: Exception) {
+ Timber.e(e)
false
}
}
else -> {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(urlString))
- webView?.context?.startActivity(intent)
+ binding.webview.context?.startActivity(intent)
true
}
}
}
}
- this.webView?.settings?.javaScriptEnabled = true
- this.webView?.loadUrl(this.args.urlString)
+ val args: WebViewFragmentArgs by navArgs()
+ binding.webview.loadUrl(args.urlString)
}
// endregion
@@ -99,7 +92,7 @@ class WebViewFragment : Fragment() {
// region Companion Object
companion object {
- const val BROWSER_FALLBACK_URL_EXTRA_NAME = "browser_fallback_url"
+ private const val BROWSER_FALLBACK_URL_EXTRA_NAME = "browser_fallback_url"
}
// endregion
diff --git a/app/src/main/java/com/theuhooi/uhooipicbook/ui/webview/WebViewViewModel.kt b/app/src/main/java/com/theuhooi/uhooipicbook/ui/webview/WebViewViewModel.kt
new file mode 100644
index 00000000..b7729677
--- /dev/null
+++ b/app/src/main/java/com/theuhooi/uhooipicbook/ui/webview/WebViewViewModel.kt
@@ -0,0 +1,5 @@
+package com.theuhooi.uhooipicbook.ui.webview
+
+import androidx.lifecycle.ViewModel
+
+class WebViewViewModel : ViewModel()
diff --git a/app/src/main/res/layout/dancing_monster_fragment.xml b/app/src/main/res/layout/dancing_monster_fragment.xml
new file mode 100644
index 00000000..63568854
--- /dev/null
+++ b/app/src/main/res/layout/dancing_monster_fragment.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_dancing_monster.xml b/app/src/main/res/layout/fragment_dancing_monster.xml
deleted file mode 100644
index 15670ddb..00000000
--- a/app/src/main/res/layout/fragment_dancing_monster.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_monster_detail.xml b/app/src/main/res/layout/fragment_monster_detail.xml
deleted file mode 100644
index eb9e606e..00000000
--- a/app/src/main/res/layout/fragment_monster_detail.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_web_view.xml b/app/src/main/res/layout/fragment_web_view.xml
deleted file mode 100644
index 1cad6e79..00000000
--- a/app/src/main/res/layout/fragment_web_view.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/main_activity.xml
similarity index 100%
rename from app/src/main/res/layout/activity_main.xml
rename to app/src/main/res/layout/main_activity.xml
diff --git a/app/src/main/res/layout/monster_detail_fragment.xml b/app/src/main/res/layout/monster_detail_fragment.xml
new file mode 100644
index 00000000..4085e9d2
--- /dev/null
+++ b/app/src/main/res/layout/monster_detail_fragment.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_monster_list.xml b/app/src/main/res/layout/monster_list_fragment.xml
similarity index 77%
rename from app/src/main/res/layout/fragment_monster_list.xml
rename to app/src/main/res/layout/monster_list_fragment.xml
index f634f3b3..b93cbaa0 100644
--- a/app/src/main/res/layout/fragment_monster_list.xml
+++ b/app/src/main/res/layout/monster_list_fragment.xml
@@ -4,14 +4,16 @@
xmlns:tools="http://schemas.android.com/tools">
+
+ type="com.theuhooi.uhooipicbook.ui.monsterlist.MonsterViewModel" />
+ android:layout_height="match_parent"
+ tools:context=".ui.monsterlist.MonsterListFragment">
+ app:observedList="@{viewModel.uiState.monsterItems}"
+ tools:listitem="@layout/monster_list_item" />
+
+ type="com.theuhooi.uhooipicbook.domain.models.MonsterItem" />
+ app:imageUrl="@{monsterItem.iconUrlString}"
+ tools:src="@tools:sample/avatars" />
+ android:textStyle="bold"
+ tools:text="@string/name_dummy" />
diff --git a/app/src/main/res/layout/web_view_fragment.xml b/app/src/main/res/layout/web_view_fragment.xml
new file mode 100644
index 00000000..535c78bf
--- /dev/null
+++ b/app/src/main/res/layout/web_view_fragment.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_monster_detail.xml b/app/src/main/res/menu/monster_detail_menu.xml
similarity index 100%
rename from app/src/main/res/menu/menu_monster_detail.xml
rename to app/src/main/res/menu/monster_detail_menu.xml
diff --git a/app/src/main/res/menu/menu_monster_list.xml b/app/src/main/res/menu/monster_list_menu.xml
similarity index 100%
rename from app/src/main/res/menu/menu_monster_list.xml
rename to app/src/main/res/menu/monster_list_menu.xml
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
index 15b4e0d2..83404c2f 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
index 4320a459..a721356f 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
index e88175e2..eeae753e 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 339204b4..e93af4d6 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
index 90764b27..77994d89 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
index 01d6dbb3..5c414ceb 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 830679a8..fd271d41 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
index aa2fc924..417f63e1 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
index 6c76d564..efc89556 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index c8a2ab40..63c12dfb 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
index e5660973..9b1945fc 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
index 70eef091..ddeff9d6 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 7f0c26ef..ec5955a6 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
index 384304fa..c1dafb4f 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
index 9d12ca0c..33e9d7af 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
index 7394aed1..fbd10044 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -3,43 +3,48 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
- app:startDestination="@id/monster_list_fragment">
+ app:startDestination="@id/monster_nav_graph">
-
-
-
-
-
+
-
-
+
+
+
+
+
-
-
+
+
+
+
+
+