diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6594d52b1..bcef43d29 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,5 +1,5 @@
cache_version_keys: &cache_version_keys
- CACHE_VERSION_OF_PROJECT_DEPS: v1
+ CACHE_VERSION_OF_PROJECT_DEPS: v2
CACHE_VERSION_OF_DANGER_CACHE: v1
cache_keys:
@@ -8,13 +8,11 @@ cache_keys:
keys: &all_keys_of_gradle_cache
- *primary_key_of_gradle_cache
- gradle-cache-{{ checksum "~/CACHE_VERSION_OF_PROJECT_DEPS" }}-
- - gradle-cache-
danger_cache:
primary: &primary_key_of_danger_cache danger-cache-{{ checksum "~/CACHE_VERSION_OF_DANGER_CACHE" }}-{{ checksum "~/danger_cache" }}
keys: &all_keys_of_danger_cache
- *primary_key_of_danger_cache
- danger-cache-{{ checksum "~/CACHE_VERSION_OF_DANGER_CACHE" }}-
- - danger-cache-
docker_env:
android_defaults: &android_defaults
@@ -54,20 +52,19 @@ jobs:
- run: *init_bash
- restore_cache: &restore_gradle_cache
keys: *all_keys_of_gradle_cache
- - run:
+ - run: &download_all_dependencies
name: Download Dependencies
- command: ./gradlew androidDependencies
+ command: retry_command ./gradlew androidDependenciesExtra getDependencies
- save_cache: &save_gradle_cache
paths:
- ~/.android
- ~/.gradle
- .gradle
- - ~/.m2
key: *primary_key_of_gradle_cache
- run:
name: Assemble apk
command: |
- ./gradlew assembleDebug # --offline # build with online-mode for now
+ ./gradlew clean assembleDebug --offline
- store_artifacts:
path: frontend/android/build/outputs/apk
- run: *download_dpg
@@ -108,7 +105,9 @@ jobs:
- checkout
- run: *init_bash
- restore_cache: *restore_gradle_cache
- - run: ./gradlew testDebugUnitTest lintDebug ktlint --continue
+ - run: *download_all_dependencies
+ - save_cache: *save_gradle_cache
+ - run: ./gradlew testDebugUnitTest lintDebug ktlint --continue --offline
- run:
name: Merge junit report files
command: |
diff --git a/.circleci/scripts/retry_command b/.circleci/scripts/retry_command
new file mode 100755
index 000000000..e9dd154f6
--- /dev/null
+++ b/.circleci/scripts/retry_command
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+set -eu
+
+: "${MAX_RETRY_COUNT:=3}"
+
+retry() {
+ local retry=0
+
+ while let "$MAX_RETRY_COUNT > $retry"; do
+ let "retry=$retry+1"
+
+ "$@" && exit 0
+
+ sleep 3
+ done
+
+ echo "Failed to process : $@" 1>&2
+ exit 1
+}
+
+retry "$@"
\ No newline at end of file
diff --git a/.idea/dictionaries/dic.xml b/.idea/dictionaries/dic.xml
index dc0229ef3..194022d1d 100644
--- a/.idea/dictionaries/dic.xml
+++ b/.idea/dictionaries/dic.xml
@@ -3,6 +3,7 @@
circleci
confsched
+ coroutine
databinding
deploygate
droidkaigi
@@ -16,4 +17,4 @@
stetho
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index ef94684c0..ea0b59ab6 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@
You can download the binary built on master branch from [](https://dply.me/t6sc7f#install)
+NOTE: Google Play Protect will show a warning dialog on some of devices when installing the current apk. The detailed specification of Google Play Protect is not public so we cannot address this matter. Please ignore the dialog for now. If you cannot install this apk without any error message, please disable Google Play Protect from Google Play Store's menus. Sorry for the inconvenience.
# Features
@@ -18,7 +19,7 @@ You can download the binary built on master branch from [Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
- }
-}
diff --git a/data/api/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/api/response/SessionResponse.kt b/data/api/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/api/response/SessionResponse.kt
index 549125023..d7387d6cc 100644
--- a/data/api/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/api/response/SessionResponse.kt
+++ b/data/api/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/api/response/SessionResponse.kt
@@ -15,5 +15,7 @@ interface SessionResponse {
val message: SessionMessageResponse?
val isPlenumSession: Boolean
val sessionType: String?
+ val videoUrl: String?
+ val slideUrl: String?
val interpretationTarget: Boolean
}
diff --git a/data/db-room/build.gradle b/data/db-room/build.gradle
index 673230ddb..ab6c0b9b8 100644
--- a/data/db-room/build.gradle
+++ b/data/db-room/build.gradle
@@ -14,7 +14,7 @@ dependencies {
api project(":ext:android-extension")
implementation Dep.Kotlin.stdlibJvm
- implementation Dep.Kotlin.serialization
+ implementation Dep.Kotlin.serializationCommon
api Dep.Kotlin.coroutines
api Dep.AndroidX.Room.runtime
api Dep.AndroidX.Room.coroutine
diff --git a/data/db-room/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/data/db-room/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
deleted file mode 100644
index 461cd5732..000000000
--- a/data/db-room/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.widget;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
- }
-}
diff --git a/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/CacheDatabase.kt b/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/CacheDatabase.kt
index 3b309bbfd..251ae9439 100644
--- a/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/CacheDatabase.kt
+++ b/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/CacheDatabase.kt
@@ -22,7 +22,7 @@ import io.github.droidkaigi.confsched2019.data.db.entity.SponsorEntityImpl
(SponsorEntityImpl::class),
(SessionFeedbackImpl::class)
],
- version = 8
+ version = 9
)
abstract class CacheDatabase : RoomDatabase() {
abstract fun sessionDao(): SessionDao
diff --git a/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/entity/SessionEntityImpl.kt b/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/entity/SessionEntityImpl.kt
index 25139ea31..8cdd53536 100644
--- a/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/entity/SessionEntityImpl.kt
+++ b/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/entity/SessionEntityImpl.kt
@@ -16,6 +16,8 @@ data class SessionEntityImpl(
override var sessionFormat: String?,
override val sessionType: String?,
override val intendedAudience: String?,
+ override val videoUrl: String?,
+ override val slideUrl: String?,
override val isInterpretationTarget: Boolean,
@Embedded override var language: LanguageEntityImpl?,
@Embedded override val category: CategoryEntityImpl?,
diff --git a/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/entity/mapper/SessionDataMapperExt.kt b/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/entity/mapper/SessionDataMapperExt.kt
index 9738d0fb8..376a92173 100644
--- a/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/entity/mapper/SessionDataMapperExt.kt
+++ b/data/db-room/src/main/java/io/github/droidkaigi/confsched2019/data/db/entity/mapper/SessionDataMapperExt.kt
@@ -77,6 +77,8 @@ fun SessionResponse.toSessionEntityImpl(
requireNotNull(category.translatedName?.en)
),
intendedAudience = intendedAudience,
+ videoUrl = videoUrl,
+ slideUrl = slideUrl,
isInterpretationTarget = interpretationTarget,
room = RoomEntityImpl(roomId, rooms.roomName(roomId)),
sessionType = sessionType
@@ -95,6 +97,8 @@ fun SessionResponse.toSessionEntityImpl(
category = null,
room = RoomEntityImpl(roomId, rooms.roomName(roomId)),
intendedAudience = null,
+ videoUrl = videoUrl,
+ slideUrl = slideUrl,
isInterpretationTarget = interpretationTarget,
message = message?.let {
MessageEntityImpl(requireNotNull(it.ja), requireNotNull(it.en))
diff --git a/data/db/build.gradle b/data/db/build.gradle
index 4eabcd6b3..7e60b98cc 100644
--- a/data/db/build.gradle
+++ b/data/db/build.gradle
@@ -8,6 +8,13 @@ apply from: rootProject.file('gradle/android.gradle')
kotlin {
targets {
fromPreset(presets.android, 'android')
+
+ final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
+ ? presets.iosArm64 : presets.iosX64
+
+ fromPreset(iOSTarget, 'iOS') {
+ compilations.main.outputKinds('FRAMEWORK')
+ }
}
sourceSets {
commonMain.dependencies {
diff --git a/data/db/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java b/data/db/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java
deleted file mode 100644
index e367b66e7..000000000
--- a/data/db/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.widget;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
- }
-}
diff --git a/data/db/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/db/entity/SessionEntity.kt b/data/db/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/db/entity/SessionEntity.kt
index c8b50364e..fad5335fa 100644
--- a/data/db/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/db/entity/SessionEntity.kt
+++ b/data/db/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/db/entity/SessionEntity.kt
@@ -11,6 +11,8 @@ interface SessionEntity {
val language: LanguageEntity?
val category: CategoryEntity?
val intendedAudience: String?
+ val videoUrl: String?
+ val slideUrl: String?
val isInterpretationTarget: Boolean
val room: RoomEntity?
val message: MessageEntity?
diff --git a/data/firestore-impl/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/data/firestore-impl/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
deleted file mode 100644
index e367b66e7..000000000
--- a/data/firestore-impl/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.widget;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
- }
-}
diff --git a/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FireStoreComponent.kt b/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreComponent.kt
similarity index 69%
rename from data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FireStoreComponent.kt
rename to data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreComponent.kt
index 78f9958fd..55ebaccaa 100644
--- a/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FireStoreComponent.kt
+++ b/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreComponent.kt
@@ -2,7 +2,7 @@ package io.github.droidkaigi.confsched2019.data.repository
import dagger.BindsInstance
import dagger.Component
-import io.github.droidkaigi.confsched2019.data.firestore.FireStore
+import io.github.droidkaigi.confsched2019.data.firestore.Firestore
import io.github.droidkaigi.confsched2019.data.firestore.FirestoreModule
import javax.inject.Singleton
import kotlin.coroutines.CoroutineContext
@@ -13,18 +13,18 @@ import kotlin.coroutines.CoroutineContext
FirestoreModule::class
]
)
-interface FireStoreComponent {
- fun fireStore(): FireStore
+interface FirestoreComponent {
+ fun firestore(): Firestore
@Component.Builder
interface Builder {
@BindsInstance fun coroutineContext(coroutineContext: CoroutineContext): Builder
- fun build(): FireStoreComponent
+ fun build(): FirestoreComponent
}
companion object {
- fun builder(): Builder = DaggerFireStoreComponent.builder()
+ fun builder(): Builder = DaggerFirestoreComponent.builder()
}
}
diff --git a/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreImpl.kt b/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreImpl.kt
index b139b7ca1..17a6163ba 100644
--- a/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreImpl.kt
+++ b/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreImpl.kt
@@ -13,9 +13,9 @@ import io.github.droidkaigi.confsched2019.model.Announcement
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
-class FirestoreImpl @Inject constructor() : FireStore {
+class FirestoreImpl @Inject constructor() : Firestore {
- override suspend fun getFavoriteSessionIds(): List {
+ override suspend fun getFavoriteSessionIds(): List {
if (FirebaseAuth.getInstance().currentUser?.uid == null) return listOf()
val favoritesRef = getFavoritesRef()
val snapshot = favoritesRef
@@ -25,7 +25,7 @@ class FirestoreImpl @Inject constructor() : FireStore {
}
val favorites = favoritesRef.whereEqualTo("favorite", true).fastGet()
- return favorites.documents.mapNotNull { it.id.toIntOrNull() }
+ return favorites.documents.mapNotNull { it.id }
}
override suspend fun toggleFavorite(sessionId: String) {
diff --git a/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreModule.kt b/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreModule.kt
index d3829fd76..3c6d27be9 100644
--- a/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreModule.kt
+++ b/data/firestore-impl/src/main/java/io/github/droidkaigi/confsched2019/data/firestore/FirestoreModule.kt
@@ -5,7 +5,7 @@ import dagger.Module
@Module(includes = [FirestoreModule.Providers::class])
internal abstract class FirestoreModule {
- @Binds abstract fun fireStore(impl: FirestoreImpl): FireStore
+ @Binds abstract fun firestore(impl: FirestoreImpl): Firestore
@Module
internal object Providers
diff --git a/data/firestore/build.gradle b/data/firestore/build.gradle
index 6856c1f99..efd94ad76 100644
--- a/data/firestore/build.gradle
+++ b/data/firestore/build.gradle
@@ -8,6 +8,13 @@ apply from: rootProject.file('gradle/android.gradle')
kotlin {
targets {
fromPreset(presets.android, 'android')
+
+ final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
+ ? presets.iosArm64 : presets.iosX64
+
+ fromPreset(iOSTarget, 'iOS') {
+ compilations.main.outputKinds('FRAMEWORK')
+ }
}
sourceSets {
commonMain.dependencies {
diff --git a/data/firestore/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java b/data/firestore/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java
deleted file mode 100644
index e367b66e7..000000000
--- a/data/firestore/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.widget;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
- }
-}
diff --git a/data/firestore/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/firestore/FireStore.kt b/data/firestore/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/firestore/FireStore.kt
index 90becd5ce..62d1cd2fb 100644
--- a/data/firestore/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/firestore/FireStore.kt
+++ b/data/firestore/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/firestore/FireStore.kt
@@ -2,8 +2,8 @@ package io.github.droidkaigi.confsched2019.data.firestore
import io.github.droidkaigi.confsched2019.model.Announcement
-interface FireStore {
- suspend fun getFavoriteSessionIds(): List
+interface Firestore {
+ suspend fun getFavoriteSessionIds(): List
suspend fun toggleFavorite(sessionId: String)
suspend fun getAnnouncements(): List
}
diff --git a/data/repository-impl/build.gradle b/data/repository-impl/build.gradle
index f62c40096..ee5e37047 100644
--- a/data/repository-impl/build.gradle
+++ b/data/repository-impl/build.gradle
@@ -11,11 +11,10 @@ dependencies {
implementation project(":data:api")
implementation project(":data:db")
implementation project(":data:firestore")
- implementation project(":ext:log")
api project(":model")
implementation Dep.Kotlin.stdlibJvm
- implementation Dep.Kotlin.serialization
+ implementation Dep.Kotlin.serializationCommon
api Dep.Kotlin.coroutines
implementation Dep.AndroidX.lifecycleLiveData
kapt Dep.AndroidX.Room.compiler
diff --git a/data/repository-impl/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/data/repository-impl/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
deleted file mode 100644
index e367b66e7..000000000
--- a/data/repository-impl/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.widget;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
- }
-}
diff --git a/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/DataSessionRepository.kt b/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/DataSessionRepository.kt
index c85c9389c..bd666e579 100644
--- a/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/DataSessionRepository.kt
+++ b/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/DataSessionRepository.kt
@@ -4,7 +4,7 @@ import com.soywiz.klock.DateTime
import io.github.droidkaigi.confsched2019.data.api.DroidKaigiApi
import io.github.droidkaigi.confsched2019.data.api.GoogleFormApi
import io.github.droidkaigi.confsched2019.data.db.SessionDatabase
-import io.github.droidkaigi.confsched2019.data.firestore.FireStore
+import io.github.droidkaigi.confsched2019.data.firestore.Firestore
import io.github.droidkaigi.confsched2019.data.repository.mapper.toSession
import io.github.droidkaigi.confsched2019.model.Lang
import io.github.droidkaigi.confsched2019.model.Session
@@ -19,7 +19,7 @@ class DataSessionRepository @Inject constructor(
private val droidKaigiApi: DroidKaigiApi,
private val googleFormApi: GoogleFormApi,
private val sessionDatabase: SessionDatabase,
- private val fireStore: FireStore
+ private val firestore: Firestore
) : SessionRepository {
override suspend fun sessionContents(): SessionContents = coroutineScope {
@@ -39,7 +39,7 @@ class DataSessionRepository @Inject constructor(
val sessionsAsync = async { sessionDatabase.sessions() }
val allSpeakersAsync = async { sessionDatabase.allSpeaker() }
val fabSessionIdsAsync = async {
- fireStore.getFavoriteSessionIds()
+ firestore.getFavoriteSessionIds()
}
val sessionEntities = sessionsAsync.await()
@@ -55,8 +55,8 @@ class DataSessionRepository @Inject constructor(
))
}
- override suspend fun toggleFavorite(session: Session.SpeechSession) {
- fireStore.toggleFavorite(session.id)
+ override suspend fun toggleFavorite(session: Session) {
+ firestore.toggleFavorite(session.id)
}
override suspend fun submitSessionFeedback(
diff --git a/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/RepositoryComponent.kt b/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/RepositoryComponent.kt
index 253050ea1..bb8d541f7 100644
--- a/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/RepositoryComponent.kt
+++ b/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/RepositoryComponent.kt
@@ -6,7 +6,7 @@ import io.github.droidkaigi.confsched2019.data.api.DroidKaigiApi
import io.github.droidkaigi.confsched2019.data.api.GoogleFormApi
import io.github.droidkaigi.confsched2019.data.db.SessionDatabase
import io.github.droidkaigi.confsched2019.data.db.SponsorDatabase
-import io.github.droidkaigi.confsched2019.data.firestore.FireStore
+import io.github.droidkaigi.confsched2019.data.firestore.Firestore
import javax.inject.Singleton
@Singleton
@@ -28,7 +28,7 @@ interface RepositoryComponent {
@BindsInstance fun database(database: SessionDatabase): Builder
@BindsInstance fun sponsorDatabase(database: SponsorDatabase): Builder
- @BindsInstance fun fireStore(fireStore: FireStore): Builder
+ @BindsInstance fun firestore(firestore: Firestore): Builder
fun build(): RepositoryComponent
}
diff --git a/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/mapper/SessionMappers.kt b/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/mapper/SessionMappers.kt
index 18fef898e..cb4081f11 100644
--- a/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/mapper/SessionMappers.kt
+++ b/data/repository-impl/src/main/java/io/github/droidkaigi/confsched2019/data/repository/mapper/SessionMappers.kt
@@ -4,16 +4,15 @@ import com.soywiz.klock.DateTime
import io.github.droidkaigi.confsched2019.data.db.entity.SessionWithSpeakers
import io.github.droidkaigi.confsched2019.data.db.entity.SpeakerEntity
import io.github.droidkaigi.confsched2019.model.Category
-import io.github.droidkaigi.confsched2019.model.LocaledString
import io.github.droidkaigi.confsched2019.model.Room
import io.github.droidkaigi.confsched2019.model.Session
-import io.github.droidkaigi.confsched2019.model.SessionMessage
import io.github.droidkaigi.confsched2019.model.SessionType
import io.github.droidkaigi.confsched2019.model.Speaker
+import io.github.droidkaigi.confsched2019.model.LocaledString
fun SessionWithSpeakers.toSession(
speakerEntities: List,
- favList: List?,
+ favList: List?,
firstDay: DateTime
): Session {
return if (session.isServiceSession) {
@@ -28,7 +27,8 @@ fun SessionWithSpeakers.toSession(
room = requireNotNull(session.room).let { room ->
Room(room.id, room.name)
},
- sessionType = SessionType.of(session.sessionType)
+ sessionType = SessionType.of(session.sessionType),
+ isFavorited = favList!!.contains(session.id)
)
} else {
require(speakerIdList.isNotEmpty())
@@ -64,11 +64,13 @@ fun SessionWithSpeakers.toSession(
)
},
intendedAudience = session.intendedAudience,
+ videoUrl = session.videoUrl,
+ slideUrl = session.slideUrl,
isInterpretationTarget = session.isInterpretationTarget,
- isFavorited = favList!!.map { it.toString() }.contains(session.id),
+ isFavorited = favList!!.contains(session.id),
speakers = speakers,
message = session.message?.let {
- SessionMessage(it.ja, it.en)
+ LocaledString(it.ja, it.en)
}
)
}
diff --git a/data/repository/build.gradle b/data/repository/build.gradle
index 6856c1f99..efd94ad76 100644
--- a/data/repository/build.gradle
+++ b/data/repository/build.gradle
@@ -8,6 +8,13 @@ apply from: rootProject.file('gradle/android.gradle')
kotlin {
targets {
fromPreset(presets.android, 'android')
+
+ final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
+ ? presets.iosArm64 : presets.iosX64
+
+ fromPreset(iOSTarget, 'iOS') {
+ compilations.main.outputKinds('FRAMEWORK')
+ }
}
sourceSets {
commonMain.dependencies {
diff --git a/data/repository/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java b/data/repository/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java
deleted file mode 100644
index e367b66e7..000000000
--- a/data/repository/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.widget;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
- }
-}
diff --git a/data/repository/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/repository/SessionRepository.kt b/data/repository/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/repository/SessionRepository.kt
index e42ef532d..bd320259d 100644
--- a/data/repository/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/repository/SessionRepository.kt
+++ b/data/repository/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/data/repository/SessionRepository.kt
@@ -7,7 +7,7 @@ import io.github.droidkaigi.confsched2019.model.SessionFeedback
interface SessionRepository {
suspend fun sessionContents(): SessionContents
suspend fun refresh()
- suspend fun toggleFavorite(session: Session.SpeechSession)
+ suspend fun toggleFavorite(session: Session)
suspend fun submitSessionFeedback(
session: Session.SpeechSession,
sessionFeedback: SessionFeedback
diff --git a/ext/android-extension/src/androidTest/java/io/github/droidkaigi/confsched2019/ext/android/ExampleInstrumentedTest.java b/ext/android-extension/src/androidTest/java/io/github/droidkaigi/confsched2019/ext/android/ExampleInstrumentedTest.java
deleted file mode 100644
index 5346f9796..000000000
--- a/ext/android-extension/src/androidTest/java/io/github/droidkaigi/confsched2019/ext/android/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.ext.android;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ext.android.test", appContext.getPackageName());
- }
-}
diff --git a/ext/log/build.gradle b/ext/log/build.gradle
index 8470119cf..e3f4d5940 100644
--- a/ext/log/build.gradle
+++ b/ext/log/build.gradle
@@ -8,14 +8,23 @@ apply from: rootProject.file('gradle/android.gradle')
kotlin {
targets {
fromPreset(presets.android, 'android')
+
+ final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
+ ? presets.iosArm64 : presets.iosX64
+
+ fromPreset(iOSTarget, 'iOS') {
+ compilations.main.outputKinds('FRAMEWORK')
+ }
}
sourceSets {
commonMain.dependencies {
api Dep.Kotlin.stdlibCommon
+ api Dep.Timber.common
}
androidMain {
dependencies {
api Dep.Kotlin.stdlibJvm
+ api Dep.Timber.jdk
}
}
commonTest.dependencies {
diff --git a/ext/log/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java b/ext/log/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java
deleted file mode 100644
index e367b66e7..000000000
--- a/ext/log/src/androidTest/java/io/github/droidkaigi/confsched2019/ui/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.widget;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
- }
-}
diff --git a/ext/log/src/commonMain/kotlin/Logger.kt b/ext/log/src/commonMain/kotlin/Logger.kt
deleted file mode 100644
index eb4839dca..000000000
--- a/ext/log/src/commonMain/kotlin/Logger.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.util
-
-enum class LogLevel {
- DEBUG, WARN, ERROR
-}
-
-var logHandler = { logLevel: LogLevel, tag: String, e: Throwable?, messageHandler: () -> String -> }
-
-fun logd(
- tag: String = "droidkaigi",
- e: Throwable? = null,
- messageHandler: () -> String = { "" }
-) = logHandler(LogLevel.DEBUG, tag, e, messageHandler)
-
-fun logw(
- tag: String = "droidkaigi",
- e: Throwable? = null,
- messageHandler: () -> String = { "" }
-) = logHandler(LogLevel.WARN, tag, e, messageHandler)
-
-fun loge(
- tag: String = "droidkaigi",
- e: Throwable? = null,
- messageHandler: () -> String = { "" }
-) = logHandler(LogLevel.ERROR, tag, e, messageHandler)
diff --git a/ext/log/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/timber/Timber.kt b/ext/log/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/timber/Timber.kt
new file mode 100644
index 000000000..8ea4f05dd
--- /dev/null
+++ b/ext/log/src/commonMain/kotlin/io/github/droidkaigi/confsched2019/timber/Timber.kt
@@ -0,0 +1,29 @@
+@file:Suppress("NOTHING_TO_INLINE")
+
+package io.github.droidkaigi.confsched2019.timber
+
+import timber.log.Timber
+
+inline fun Timber.assert(throwable: Throwable) {
+ Timber.log(ASSERT, null, throwable, null)
+}
+
+inline fun Timber.error(throwable: Throwable) {
+ Timber.log(ERROR, null, throwable, null)
+}
+
+inline fun Timber.warn(throwable: Throwable) {
+ Timber.log(WARNING, null, throwable, null)
+}
+
+inline fun Timber.info(throwable: Throwable) {
+ Timber.log(INFO, null, throwable, null)
+}
+
+inline fun Timber.debug(throwable: Throwable) {
+ Timber.log(DEBUG, null, throwable, null)
+}
+
+inline fun Timber.verbose(throwable: Throwable) {
+ Timber.log(VERBOSE, null, throwable, null)
+}
diff --git a/feature/about/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/feature/about/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
index 461cd5732..497b0644c 100644
--- a/feature/about/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ b/feature/about/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
@@ -20,6 +20,6 @@ public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
+ assertEquals("io.github.droidkaigi.confsched2019.about.test", appContext.getPackageName());
}
}
diff --git a/feature/about/src/main/java/io/github/droidkaigi/confsched2019/about/ui/item/AboutSection.kt b/feature/about/src/main/java/io/github/droidkaigi/confsched2019/about/ui/item/AboutSection.kt
index 30cb5d184..fe2e666bc 100644
--- a/feature/about/src/main/java/io/github/droidkaigi/confsched2019/about/ui/item/AboutSection.kt
+++ b/feature/about/src/main/java/io/github/droidkaigi/confsched2019/about/ui/item/AboutSection.kt
@@ -34,7 +34,7 @@ class AboutSection @Inject constructor(
R.string.about_privacy_policy,
R.string.about_check
) {
- Toast.makeText(it, "FIXME!!", Toast.LENGTH_SHORT).show()
+ activityActionCreator.openUrl("http://www.association.droidkaigi.jp/privacy")
},
AboutItem(
R.string.about_license,
diff --git a/feature/about/src/main/res/drawable/ic_github_black_24dp.xml b/feature/about/src/main/res/drawable/ic_github_black_36dp.xml
similarity index 95%
rename from feature/about/src/main/res/drawable/ic_github_black_24dp.xml
rename to feature/about/src/main/res/drawable/ic_github_black_36dp.xml
index 505243e1f..30fed3ee6 100644
--- a/feature/about/src/main/res/drawable/ic_github_black_24dp.xml
+++ b/feature/about/src/main/res/drawable/ic_github_black_36dp.xml
@@ -1,6 +1,6 @@
+ android:paddingBottom="20dp">
diff --git a/feature/announcement/build.gradle b/feature/announcement/build.gradle
index 91dfa9074..416ec97dc 100644
--- a/feature/announcement/build.gradle
+++ b/feature/announcement/build.gradle
@@ -45,6 +45,9 @@ dependencies {
implementation Dep.Groupie.databinding
testImplementation Dep.Test.junit
+ testImplementation Dep.Test.kotlinTestAssertions
+ testImplementation Dep.MockK.jvm
+ testImplementation project(':frontendcomponent:androidtestcomponent')
androidTestImplementation Dep.Test.testRunner
androidTestImplementation Dep.Test.espressoCore
}
diff --git a/feature/announcement/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/feature/announcement/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
index 461cd5732..332b5c131 100644
--- a/feature/announcement/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ b/feature/announcement/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
@@ -20,6 +20,6 @@ public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
+ assertEquals("io.github.droidkaigi.confsched2019.announcement.test", appContext.getPackageName());
}
}
diff --git a/feature/announcement/src/main/java/io/github/droidkaigi/confsched2019/announcement/ui/actioncreator/AnnouncementActionCreator.kt b/feature/announcement/src/main/java/io/github/droidkaigi/confsched2019/announcement/ui/actioncreator/AnnouncementActionCreator.kt
index 923e0f955..8d5192779 100644
--- a/feature/announcement/src/main/java/io/github/droidkaigi/confsched2019/announcement/ui/actioncreator/AnnouncementActionCreator.kt
+++ b/feature/announcement/src/main/java/io/github/droidkaigi/confsched2019/announcement/ui/actioncreator/AnnouncementActionCreator.kt
@@ -2,7 +2,7 @@ package io.github.droidkaigi.confsched2019.announcement.ui.actioncreator
import androidx.lifecycle.Lifecycle
import io.github.droidkaigi.confsched2019.action.Action
-import io.github.droidkaigi.confsched2019.data.firestore.FireStore
+import io.github.droidkaigi.confsched2019.data.firestore.Firestore
import io.github.droidkaigi.confsched2019.di.PageScope
import io.github.droidkaigi.confsched2019.dispatcher.Dispatcher
import io.github.droidkaigi.confsched2019.ext.android.coroutineScope
@@ -14,7 +14,7 @@ import javax.inject.Inject
class AnnouncementActionCreator @Inject constructor(
override val dispatcher: Dispatcher,
- private val fireStore: FireStore,
+ private val firestore: Firestore,
@PageScope private val lifecycle: Lifecycle
) : CoroutineScope by lifecycle.coroutineScope,
ErrorHandler {
@@ -22,7 +22,7 @@ class AnnouncementActionCreator @Inject constructor(
fun load() = launch {
try {
dispatcher.dispatch(Action.AnnouncementLoadingStateChanged(LoadingState.LOADING))
- dispatcher.dispatch(Action.AnnouncementLoaded(fireStore.getAnnouncements()))
+ dispatcher.dispatch(Action.AnnouncementLoaded(firestore.getAnnouncements()))
dispatcher.dispatch(Action.AnnouncementLoadingStateChanged(LoadingState.LOADED))
} catch (e: Exception) {
onError(e)
diff --git a/feature/announcement/src/test/java/io/github/droidkaigi/confsched2019/AnnouncementDummyDatas.kt b/feature/announcement/src/test/java/io/github/droidkaigi/confsched2019/AnnouncementDummyDatas.kt
new file mode 100644
index 000000000..aecbfa097
--- /dev/null
+++ b/feature/announcement/src/test/java/io/github/droidkaigi/confsched2019/AnnouncementDummyDatas.kt
@@ -0,0 +1,29 @@
+package io.github.droidkaigi.confsched2019
+
+import com.soywiz.klock.DateTime
+import com.soywiz.klock.minutes
+import io.github.droidkaigi.confsched2019.model.Announcement
+
+private val startTime = DateTime.createAdjusted(2019, 2, 7, 10, 0)
+fun dummyAnnouncementsData(): List {
+ return listOf(
+ Announcement(
+ "title1",
+ "content1",
+ startTime,
+ Announcement.Type.NOTIFICATION
+ ),
+ Announcement(
+ "title2",
+ "content2",
+ startTime + 30.minutes,
+ Announcement.Type.ALERT
+ ),
+ Announcement(
+ "title3",
+ "content3",
+ startTime + 60.minutes,
+ Announcement.Type.FEEDBACK
+ )
+ )
+}
diff --git a/feature/announcement/src/test/java/io/github/droidkaigi/confsched2019/announcement/ui/actioncreator/AnnouncementActionCreatorTest.kt b/feature/announcement/src/test/java/io/github/droidkaigi/confsched2019/announcement/ui/actioncreator/AnnouncementActionCreatorTest.kt
new file mode 100644
index 000000000..5547ccc47
--- /dev/null
+++ b/feature/announcement/src/test/java/io/github/droidkaigi/confsched2019/announcement/ui/actioncreator/AnnouncementActionCreatorTest.kt
@@ -0,0 +1,48 @@
+package io.github.droidkaigi.confsched2019.announcement.ui.actioncreator
+
+import androidx.lifecycle.Lifecycle
+import io.github.droidkaigi.confsched2019.action.Action
+import io.github.droidkaigi.confsched2019.data.firestore.Firestore
+import io.github.droidkaigi.confsched2019.dispatcher.Dispatcher
+import io.github.droidkaigi.confsched2019.dummyAnnouncementsData
+import io.github.droidkaigi.confsched2019.ext.android.CoroutinePlugin
+import io.github.droidkaigi.confsched2019.model.LoadingState
+import io.github.droidkaigi.confsched2019.widget.component.TestLifecycleOwner
+import io.mockk.MockKAnnotations
+import io.mockk.Ordering
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.impl.annotations.RelaxedMockK
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+
+class AnnouncementActionCreatorTest {
+ @RelaxedMockK lateinit var dispatcher: Dispatcher
+ @RelaxedMockK lateinit var firestore: Firestore
+
+ @Before fun setUp() {
+ MockKAnnotations.init(this, relaxUnitFun = true)
+ CoroutinePlugin.mainDispatcherHandler = { Dispatchers.Default }
+ }
+
+ @Test fun load() = runBlocking {
+ val lifecycleOwner = TestLifecycleOwner().handleEvent(Lifecycle.Event.ON_RESUME)
+ coEvery { firestore.getAnnouncements() } returns dummyAnnouncementsData()
+ val announcementActionCreator = AnnouncementActionCreator(
+ dispatcher,
+ firestore,
+ lifecycleOwner.lifecycle
+ )
+
+ announcementActionCreator.load()
+
+ coVerify(ordering = Ordering.SEQUENCE) {
+ dispatcher.dispatch(Action.AnnouncementLoadingStateChanged(LoadingState.LOADING))
+ firestore.getAnnouncements()
+ dispatcher.dispatch(Action.AnnouncementLoaded(dummyAnnouncementsData()))
+ dispatcher.dispatch(Action.AnnouncementLoadingStateChanged(LoadingState.LOADED))
+ }
+ }
+}
diff --git a/feature/announcement/src/test/java/io/github/droidkaigi/confsched2019/announcement/ui/store/AnnouncementStoreTest.kt b/feature/announcement/src/test/java/io/github/droidkaigi/confsched2019/announcement/ui/store/AnnouncementStoreTest.kt
new file mode 100644
index 000000000..9e75e3008
--- /dev/null
+++ b/feature/announcement/src/test/java/io/github/droidkaigi/confsched2019/announcement/ui/store/AnnouncementStoreTest.kt
@@ -0,0 +1,60 @@
+package io.github.droidkaigi.confsched2019.announcement.ui.store
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import io.github.droidkaigi.confsched2019.action.Action
+import io.github.droidkaigi.confsched2019.dispatcher.Dispatcher
+import io.github.droidkaigi.confsched2019.dummyAnnouncementsData
+import io.github.droidkaigi.confsched2019.ext.android.CoroutinePlugin
+import io.github.droidkaigi.confsched2019.ext.android.changedForever
+import io.github.droidkaigi.confsched2019.model.Announcement
+import io.github.droidkaigi.confsched2019.model.LoadingState
+import io.mockk.MockKAnnotations
+import io.mockk.mockk
+import io.mockk.verifySequence
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class AnnouncementStoreTest {
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+
+ @Before fun setUp() {
+ MockKAnnotations.init(this, relaxUnitFun = true)
+ CoroutinePlugin.mainDispatcherHandler = { Dispatchers.Default }
+ }
+
+ @Test fun loadingState() = runBlocking {
+ val dispatcher = Dispatcher()
+ val announcementStore = AnnouncementStore(dispatcher)
+ val observer = mockk<(LoadingState?) -> Unit>(relaxed = true)
+
+ announcementStore.loadingState.changedForever(observer)
+
+ dispatcher.dispatch(Action.AnnouncementLoadingStateChanged(LoadingState.LOADING))
+ dispatcher.dispatch(Action.AnnouncementLoadingStateChanged(LoadingState.LOADED))
+
+ verifySequence {
+ observer(LoadingState.LOADING)
+ observer(LoadingState.LOADED)
+ }
+ }
+
+ @Test fun announcements() = runBlocking {
+ val dispatcher = Dispatcher()
+ val announcementStore = AnnouncementStore(dispatcher)
+ val observer: (List) -> Unit = mockk(relaxed = true)
+ announcementStore.announcements.changedForever(observer)
+ val dummyAnnouncements = dummyAnnouncementsData()
+
+ dispatcher.dispatch(
+ Action.AnnouncementLoaded(dummyAnnouncements)
+ )
+
+ verifySequence {
+ observer(emptyList())
+ observer(dummyAnnouncements)
+ }
+ }
+}
diff --git a/feature/floormap/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/feature/floormap/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
index 461cd5732..9057d1743 100644
--- a/feature/floormap/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ b/feature/floormap/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
@@ -20,6 +20,6 @@ public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
+ assertEquals("io.github.droidkaigi.confsched2019.floormap.test", appContext.getPackageName());
}
}
diff --git a/feature/floormap/src/main/res/drawable-hdpi/ic_floor1.png b/feature/floormap/src/main/res/drawable-hdpi/ic_floor1.png
new file mode 100644
index 000000000..a31fd35d0
Binary files /dev/null and b/feature/floormap/src/main/res/drawable-hdpi/ic_floor1.png differ
diff --git a/feature/floormap/src/main/res/drawable-hdpi/ic_floor2.png b/feature/floormap/src/main/res/drawable-hdpi/ic_floor2.png
new file mode 100644
index 000000000..bffc412de
Binary files /dev/null and b/feature/floormap/src/main/res/drawable-hdpi/ic_floor2.png differ
diff --git a/feature/floormap/src/main/res/drawable-xhdpi/ic_floor1.png b/feature/floormap/src/main/res/drawable-xhdpi/ic_floor1.png
new file mode 100644
index 000000000..ad9f52c2e
Binary files /dev/null and b/feature/floormap/src/main/res/drawable-xhdpi/ic_floor1.png differ
diff --git a/feature/floormap/src/main/res/drawable-xhdpi/ic_floor2.png b/feature/floormap/src/main/res/drawable-xhdpi/ic_floor2.png
new file mode 100644
index 000000000..911174998
Binary files /dev/null and b/feature/floormap/src/main/res/drawable-xhdpi/ic_floor2.png differ
diff --git a/feature/floormap/src/main/res/drawable-xxhdpi/ic_floor1.png b/feature/floormap/src/main/res/drawable-xxhdpi/ic_floor1.png
new file mode 100644
index 000000000..4656cca94
Binary files /dev/null and b/feature/floormap/src/main/res/drawable-xxhdpi/ic_floor1.png differ
diff --git a/feature/floormap/src/main/res/drawable-xxhdpi/ic_floor2.png b/feature/floormap/src/main/res/drawable-xxhdpi/ic_floor2.png
new file mode 100644
index 000000000..b2a5a925b
Binary files /dev/null and b/feature/floormap/src/main/res/drawable-xxhdpi/ic_floor2.png differ
diff --git a/feature/session/build.gradle b/feature/session/build.gradle
index 25229d58f..90b84022a 100644
--- a/feature/session/build.gradle
+++ b/feature/session/build.gradle
@@ -27,7 +27,6 @@ dependencies {
api Dep.AndroidX.emoji
implementation Dep.AndroidX.design
-
api Dep.AndroidX.lifecycleExtensions
api Dep.AndroidX.Navigation.runtime
api Dep.AndroidX.Navigation.runtimeKtx
@@ -49,6 +48,8 @@ dependencies {
implementation Dep.Picasso.picasso
implementation Dep.Picasso.picassoTransformation
+ implementation Dep.Timber.android
+
testImplementation Dep.Test.junit
testImplementation Dep.Test.kotlinTestAssertions
testImplementation Dep.MockK.jvm
diff --git a/feature/session/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/feature/session/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
index 461cd5732..e2ee118c1 100644
--- a/feature/session/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ b/feature/session/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
@@ -20,6 +20,6 @@ public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
+ assertEquals("io.github.droidkaigi.confsched2019.session.test", appContext.getPackageName());
}
}
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/BottomSheetDaySessionsFragment.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/BottomSheetDaySessionsFragment.kt
index 3c53885b7..c339e2635 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/BottomSheetDaySessionsFragment.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/BottomSheetDaySessionsFragment.kt
@@ -40,6 +40,7 @@ class BottomSheetDaySessionsFragment : DaggerFragment() {
@Inject lateinit var sessionPageFragmentProvider: Provider
@Inject lateinit var speechSessionItemFactory: SpeechSessionItem.Factory
@Inject lateinit var sessionPageStoreFactory: SessionPageStore.Factory
+ @Inject lateinit var serviceSessionItemFactory: ServiceSessionItem.Factory
private val sessionPageStore: SessionPageStore by lazy {
sessionPageFragmentProvider.get().injectedViewModelProvider
.get(SessionPageStore::class.java.name) {
@@ -93,11 +94,13 @@ class BottomSheetDaySessionsFragment : DaggerFragment() {
true
)
is Session.ServiceSession ->
- ServiceSessionItem(session)
+ serviceSessionItemFactory.create(session)
}
}
groupAdapter.update(items)
+ binding.shouldShowEmptyStateView = false
+
val titleText = items
.asSequence()
.filterIsInstance()
@@ -106,6 +109,9 @@ class BottomSheetDaySessionsFragment : DaggerFragment() {
?.startDayText ?: return@changed
binding.sessionsBottomSheetTitle.text = titleText
}
+ sessionPagesStore.filters.changed(viewLifecycleOwner) {
+ binding.isFiltered = it.isFiltered()
+ }
sessionPageStore.filterSheetState.changed(viewLifecycleOwner) { newState ->
if (newState == BottomSheetBehavior.STATE_EXPANDED ||
newState == BottomSheetBehavior.STATE_COLLAPSED
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/BottomSheetFavoriteSessionsFragment.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/BottomSheetFavoriteSessionsFragment.kt
index b8e8abb9a..749e14e68 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/BottomSheetFavoriteSessionsFragment.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/BottomSheetFavoriteSessionsFragment.kt
@@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.LinearLayoutManager
@@ -12,15 +11,19 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.xwray.groupie.GroupAdapter
+import com.xwray.groupie.Item
import com.xwray.groupie.databinding.ViewHolder
import dagger.Module
import dagger.Provides
import io.github.droidkaigi.confsched2019.ext.android.changed
+import io.github.droidkaigi.confsched2019.model.Session
import io.github.droidkaigi.confsched2019.model.SessionPage
import io.github.droidkaigi.confsched2019.session.R
import io.github.droidkaigi.confsched2019.session.databinding.FragmentBottomSheetSessionsBinding
import io.github.droidkaigi.confsched2019.session.ui.actioncreator.SessionContentsActionCreator
import io.github.droidkaigi.confsched2019.session.ui.actioncreator.SessionPageActionCreator
+import io.github.droidkaigi.confsched2019.session.ui.item.ServiceSessionItem
+import io.github.droidkaigi.confsched2019.session.ui.item.SessionItem
import io.github.droidkaigi.confsched2019.session.ui.item.SpeechSessionItem
import io.github.droidkaigi.confsched2019.session.ui.store.SessionContentsStore
import io.github.droidkaigi.confsched2019.session.ui.store.SessionPageStore
@@ -41,6 +44,7 @@ class BottomSheetFavoriteSessionsFragment : DaggerFragment() {
@Inject lateinit var sessionPageActionCreator: SessionPageActionCreator
@Inject lateinit var sessionPageFragmentProvider: Provider
@Inject lateinit var speechSessionItemFactory: SpeechSessionItem.Factory
+ @Inject lateinit var serviceSessionItemFactory: ServiceSessionItem.Factory
@Inject lateinit var sessionDetailStoreFactory: SessionPageStore.Factory
private val sessionPageStore: SessionPageStore by lazy {
@@ -89,18 +93,27 @@ class BottomSheetFavoriteSessionsFragment : DaggerFragment() {
sessionPagesStore.filteredFavoritedSessions().changed(viewLifecycleOwner) { sessions ->
val items = sessions
- .map { session ->
- speechSessionItemFactory.create(
- session,
- SessionPagesFragmentDirections.actionSessionToSessionDetail(
- session.id
- ),
- true
- )
+ .map> { session ->
+ when (session) {
+ is Session.SpeechSession ->
+ speechSessionItemFactory.create(
+ session,
+ SessionPagesFragmentDirections.actionSessionToSessionDetail(
+ session.id
+ ),
+ true
+ )
+ is Session.ServiceSession ->
+ serviceSessionItemFactory.create(session)
+ }
}
groupAdapter.update(items)
applyTitleText()
+ binding.shouldShowEmptyStateView = items.isEmpty()
+ }
+ sessionPagesStore.filters.changed(viewLifecycleOwner) {
+ binding.isFiltered = it.isFiltered()
}
sessionPageStore.filterSheetState.changed(viewLifecycleOwner) { newState ->
if (newState == BottomSheetBehavior.STATE_EXPANDED ||
@@ -112,8 +125,7 @@ class BottomSheetFavoriteSessionsFragment : DaggerFragment() {
excludeChildren(binding.sessionsRecycler, true)
})
val isCollapsed = newState == BottomSheetBehavior.STATE_COLLAPSED
- binding.sessionsBottomSheetShowFilterButton.isVisible = !isCollapsed
- binding.sessionsBottomSheetHideFilterButton.isVisible = isCollapsed
+ binding.isCollapsed = isCollapsed
}
}
}
@@ -125,7 +137,7 @@ class BottomSheetFavoriteSessionsFragment : DaggerFragment() {
return
}
binding.sessionsBottomSheetTitle.text = (groupAdapter
- .getItem(firstPosition) as SpeechSessionItem)
+ .getItem(firstPosition) as SessionItem)
.session
.startDayText
}
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SearchFragment.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SearchFragment.kt
index 4d90f7006..91bad4ba3 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SearchFragment.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SearchFragment.kt
@@ -6,7 +6,9 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
import androidx.appcompat.widget.SearchView
+import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
@@ -37,6 +39,7 @@ class SearchFragment : DaggerFragment() {
@Inject lateinit var searchActionCreator: SearchActionCreator
@Inject lateinit var speechSessionItemFactory: SpeechSessionItem.Factory
+ @Inject lateinit var serviceSessionItemFactory: ServiceSessionItem.Factory
@Inject lateinit var sessionContentsStore: SessionContentsStore
private var searchView: SearchView? = null
@Inject lateinit var searchStoreProvider: Provider
@@ -93,7 +96,7 @@ class SearchFragment : DaggerFragment() {
false
)
is Session.ServiceSession ->
- ServiceSessionItem(session)
+ serviceSessionItemFactory.create(session)
}
}
groupAdapter.update(items)
@@ -127,6 +130,14 @@ class SearchFragment : DaggerFragment() {
searchView.setOnCloseListener { false }
}
}
+
+ override fun onPause() {
+ super.onPause()
+
+ val imm = ContextCompat.getSystemService(requireContext(), InputMethodManager::class.java)
+ val view = activity?.currentFocus
+ imm?.hideSoftInputFromWindow(view?.windowToken, 0)
+ }
}
@Module
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionDetailFragment.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionDetailFragment.kt
index 50947f693..3161a61ea 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionDetailFragment.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionDetailFragment.kt
@@ -5,8 +5,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
+import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
-import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.databinding.ViewHolder
@@ -15,6 +15,7 @@ import dagger.Provides
import dagger.android.support.AndroidSupportInjection
import io.github.droidkaigi.confsched2019.di.PageScope
import io.github.droidkaigi.confsched2019.ext.android.changed
+import io.github.droidkaigi.confsched2019.model.LoadingState
import io.github.droidkaigi.confsched2019.model.Session
import io.github.droidkaigi.confsched2019.model.defaultLang
import io.github.droidkaigi.confsched2019.session.R
@@ -23,7 +24,9 @@ import io.github.droidkaigi.confsched2019.session.ui.actioncreator.SessionConten
import io.github.droidkaigi.confsched2019.session.ui.item.SpeakerItem
import io.github.droidkaigi.confsched2019.session.ui.store.SessionContentsStore
import io.github.droidkaigi.confsched2019.session.ui.widget.DaggerFragment
+import io.github.droidkaigi.confsched2019.system.actioncreator.ActivityActionCreator
import io.github.droidkaigi.confsched2019.system.store.SystemStore
+import io.github.droidkaigi.confsched2019.util.ProgressTimeLatch
import javax.inject.Inject
class SessionDetailFragment : DaggerFragment() {
@@ -33,6 +36,9 @@ class SessionDetailFragment : DaggerFragment() {
@Inject lateinit var systemStore: SystemStore
@Inject lateinit var sessionContentsStore: SessionContentsStore
@Inject lateinit var speakerItemFactory: SpeakerItem.Factory
+ @Inject lateinit var activityActionCreator: ActivityActionCreator
+
+ private lateinit var progressTimeLatch: ProgressTimeLatch
private lateinit var sessionDetailFragmentArgs: SessionDetailFragmentArgs
private val groupAdapter = GroupAdapter>()
@@ -62,12 +68,13 @@ class SessionDetailFragment : DaggerFragment() {
binding.bottomAppBar.replaceMenu(R.menu.menu_session_detail_bottomappbar)
binding.bottomAppBar.setOnMenuItemClickListener { item ->
when (item.itemId) {
- R.id.session_share ->
- Toast.makeText(
- requireContext(),
- "not implemented yet",
- Toast.LENGTH_SHORT
- ).show()
+ R.id.session_share -> {
+ val session = binding.session ?: return@setOnMenuItemClickListener false
+ activityActionCreator.shareUrl(getString(
+ R.string.session_detail_share_url,
+ session.id
+ ))
+ }
R.id.session_place ->
Toast.makeText(
requireContext(),
@@ -82,8 +89,17 @@ class SessionDetailFragment : DaggerFragment() {
.changed(viewLifecycleOwner) { session ->
applySessionLayout(session)
}
+
+ progressTimeLatch = ProgressTimeLatch { showProgress ->
+ binding.progressBar.isVisible = showProgress
+ }
+ sessionContentsStore.loadingState.changed(viewLifecycleOwner) {
+ progressTimeLatch.loading = it == LoadingState.LOADING
+ }
+
binding.sessionFavorite.setOnClickListener {
val session = binding.session ?: return@setOnClickListener
+ progressTimeLatch.loading = true
sessionContentsActionCreator.toggleFavorite(session)
}
}
@@ -97,8 +113,13 @@ class SessionDetailFragment : DaggerFragment() {
session.timeInMinutes,
session.room.name
)
+ binding.sessionIntendedAudienceDescription.text = session.intendedAudience
binding.categoryChip.text = session.category.name.getByLang(systemStore.lang)
+ session.message?.let { message ->
+ binding.sessionMessage.text = message.getByLang(systemStore.lang)
+ }
+
val sessionItems = session
.speakers
.map {
@@ -108,6 +129,17 @@ class SessionDetailFragment : DaggerFragment() {
)
}
groupAdapter.update(sessionItems)
+
+ binding.sessionVideoButton.setOnClickListener {
+ session.videoUrl?.let { urlString ->
+ activityActionCreator.openUrl(urlString)
+ }
+ }
+ binding.sessionSlideButton.setOnClickListener {
+ session.slideUrl?.let { urlString ->
+ activityActionCreator.openUrl(urlString)
+ }
+ }
}
}
@@ -121,11 +153,5 @@ abstract class SessionDetailFragmentModule {
fun providesLifecycle(sessionsFragment: SessionDetailFragment): Lifecycle {
return sessionsFragment.viewLifecycleOwner.lifecycle
}
-
- @JvmStatic @Provides fun provideActivity(
- sessionsFragment: SessionDetailFragment
- ): FragmentActivity {
- return sessionsFragment.requireActivity()
- }
}
}
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPageFragment.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPageFragment.kt
index ceb2b2d51..0637e7b89 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPageFragment.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPageFragment.kt
@@ -35,6 +35,8 @@ import io.github.droidkaigi.confsched2019.session.ui.store.SessionPagesStore
import io.github.droidkaigi.confsched2019.session.ui.widget.DaggerFragment
import io.github.droidkaigi.confsched2019.system.store.SystemStore
import io.github.droidkaigi.confsched2019.widget.BottomSheetBehavior
+import io.github.droidkaigi.confsched2019.widget.FilterChip
+import io.github.droidkaigi.confsched2019.widget.onCheckedChanged
import me.tatarka.injectedvmprovider.InjectedViewModelProviders
import me.tatarka.injectedvmprovider.ktx.injectedViewModelProvider
import javax.inject.Inject
@@ -68,6 +70,13 @@ class SessionPageFragment : DaggerFragment() {
private val bottomSheetBehavior: BottomSheetBehavior<*>
get() = BottomSheetBehavior.from(binding.sessionsSheet)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (savedInstanceState == null) {
+ setupSessionsFragment()
+ }
+ }
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -82,7 +91,7 @@ class SessionPageFragment : DaggerFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
- setupBottomSheet(savedInstanceState)
+ setupBottomSheetBehavior()
binding.sessionsFilterReset.setOnClickListener {
sessionPagesActionCreator.clearFilters()
@@ -120,27 +129,29 @@ class SessionPageFragment : DaggerFragment() {
}
}
- private fun setupBottomSheet(savedInstanceState: Bundle?) {
- if (savedInstanceState == null) {
- val fragment: Fragment = when (val tab = SessionPage.pages[args.tabIndex]) {
- is SessionPage.Day -> {
- BottomSheetDaySessionsFragment.newInstance(
- BottomSheetDaySessionsFragmentArgs
- .Builder(tab.day)
- .build()
- )
- }
- SessionPage.Favorite -> {
- BottomSheetFavoriteSessionsFragment.newInstance()
- }
+ private fun setupSessionsFragment() {
+ val tab = SessionPage.pages[args.tabIndex]
+ val fragment: Fragment = when (tab) {
+ is SessionPage.Day -> {
+ BottomSheetDaySessionsFragment.newInstance(
+ BottomSheetDaySessionsFragmentArgs
+ .Builder(tab.day)
+ .build()
+ )
+ }
+ SessionPage.Favorite -> {
+ BottomSheetFavoriteSessionsFragment.newInstance()
}
-
- childFragmentManager
- .beginTransaction()
- .replace(R.id.sessions_sheet, fragment)
- .disallowAddToBackStack()
- .commit()
}
+
+ childFragmentManager
+ .beginTransaction()
+ .replace(R.id.sessions_sheet, fragment, tab.title)
+ .disallowAddToBackStack()
+ .commit()
+ }
+
+ private fun setupBottomSheetBehavior() {
bottomSheetBehavior.isHideable = false
binding.sessionsSheet.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
@@ -176,7 +187,7 @@ class SessionPageFragment : DaggerFragment() {
R.layout.layout_chip,
this,
false
- ) as Chip
+ ) as FilterChip
chip.apply {
text = chipText(item)
tag = item
@@ -191,43 +202,43 @@ class SessionPageFragment : DaggerFragment() {
private fun applyFilters() {
val filterRooms = sessionPagesStore.filtersValue.rooms
binding.sessionsFilterRoomChip.forEach {
- val chip = it as? Chip ?: return@forEach
+ val chip = it as? FilterChip ?: return@forEach
val room = it.tag as? Room ?: return@forEach
- chip.setOnCheckedChangeListener(null)
+ chip.onCheckedChangeListener = null
if (filterRooms.isNotEmpty()) {
chip.isChecked = filterRooms.contains(room)
} else {
chip.isChecked = false
}
- chip.setOnCheckedChangeListener { _, isChecked ->
+ chip.onCheckedChanged { _, isChecked ->
sessionPagesActionCreator.changeFilter(room, isChecked)
}
}
val filterCategorys = sessionPagesStore.filtersValue.categories
binding.sessionsFilterCategoryChip.forEach {
- val chip = it as? Chip ?: return@forEach
+ val chip = it as? FilterChip ?: return@forEach
val category = it.tag as? Category ?: return@forEach
- chip.setOnCheckedChangeListener(null)
+ chip.onCheckedChangeListener = null
if (filterCategorys.isNotEmpty()) {
chip.isChecked = filterCategorys.contains(category)
} else {
chip.isChecked = false
}
- chip.setOnCheckedChangeListener { _, isChecked ->
+ chip.onCheckedChanged { _, isChecked ->
sessionPagesActionCreator.changeFilter(category, isChecked)
}
}
val filterLangs = sessionPagesStore.filtersValue.langs
binding.sessionsFilterLangChip.forEach {
- val chip = it as? Chip ?: return@forEach
+ val chip = it as? FilterChip ?: return@forEach
val lang = it.tag as? Lang ?: return@forEach
- chip.setOnCheckedChangeListener(null)
+ chip.onCheckedChangeListener = null
if (filterLangs.isNotEmpty()) {
chip.isChecked = filterLangs.contains(lang)
} else {
chip.isChecked = false
}
- chip.setOnCheckedChangeListener { _, isChecked ->
+ chip.onCheckedChanged { _, isChecked ->
sessionPagesActionCreator.changeFilter(lang, isChecked)
}
}
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPagesFragment.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPagesFragment.kt
index 2565f7686..1817cfeab 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPagesFragment.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPagesFragment.kt
@@ -2,6 +2,8 @@ package io.github.droidkaigi.confsched2019.session.ui
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.core.view.isVisible
@@ -10,7 +12,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.lifecycle.Lifecycle
import androidx.viewpager.widget.ViewPager
-import com.google.android.material.chip.Chip
import dagger.Module
import dagger.Provides
import dagger.android.ContributesAndroidInjector
@@ -51,6 +52,7 @@ class SessionPagesFragment : DaggerFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
+ setHasOptionsMenu(true)
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_session_pages,
@@ -107,18 +109,11 @@ class SessionPagesFragment : DaggerFragment() {
}
}
)
+ }
- (0 until binding.sessionsTabLayout.tabCount).forEach {
- val view = layoutInflater.inflate(
- R.layout.layout_title_chip, binding.sessionsTabLayout, false
- ) as ViewGroup
- val chip = view.getChildAt(0) as Chip
- val tab = binding.sessionsTabLayout.getTabAt(it)
- tab?.let {
- chip.text = tab.text
- tab.setCustomView(view)
- }
- }
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ inflater.inflate(R.menu.menu_toolbar, menu)
}
}
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/actioncreator/SessionContentsActionCreator.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/actioncreator/SessionContentsActionCreator.kt
index c19170d9a..e7b2d0d71 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/actioncreator/SessionContentsActionCreator.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/actioncreator/SessionContentsActionCreator.kt
@@ -9,9 +9,10 @@ import io.github.droidkaigi.confsched2019.ext.android.coroutineScope
import io.github.droidkaigi.confsched2019.model.LoadingState
import io.github.droidkaigi.confsched2019.model.Session
import io.github.droidkaigi.confsched2019.system.actioncreator.ErrorHandler
-import io.github.droidkaigi.confsched2019.util.logd
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+import timber.log.Timber
+import timber.log.debug
import javax.inject.Inject
@PageScope
@@ -23,22 +24,22 @@ class SessionContentsActionCreator @Inject constructor(
ErrorHandler {
fun refresh() = launch {
try {
- logd { "SessionContentsActionCreator: refresh start" }
+ Timber.debug { "SessionContentsActionCreator: refresh start" }
dispatcher.dispatchLoadingState(LoadingState.LOADING)
- logd { "SessionContentsActionCreator: At first, load db data" }
+ Timber.debug { "SessionContentsActionCreator: At first, load db data" }
// At first, load db data
val sessionContents = sessionRepository.sessionContents()
dispatcher.dispatch(Action.SessionContentsLoaded(sessionContents))
// fetch api data
- logd { "SessionContentsActionCreator: fetch api data" }
+ Timber.debug { "SessionContentsActionCreator: fetch api data" }
sessionRepository.refresh()
// reload db data
- logd { "SessionContentsActionCreator: reload db data" }
+ Timber.debug { "SessionContentsActionCreator: reload db data" }
val refreshedSessionContents = sessionRepository.sessionContents()
dispatcher.dispatch(Action.SessionContentsLoaded(refreshedSessionContents))
- logd { "SessionContentsActionCreator: refresh end" }
+ Timber.debug { "SessionContentsActionCreator: refresh end" }
dispatcher.dispatchLoadingState(LoadingState.LOADED)
} catch (e: Exception) {
onError(e)
@@ -60,7 +61,7 @@ class SessionContentsActionCreator @Inject constructor(
}
}
- fun toggleFavorite(session: Session.SpeechSession) {
+ fun toggleFavorite(session: Session) {
launch {
try {
dispatcher.dispatchLoadingState(LoadingState.LOADING)
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/item/ServiceSessionItem.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/item/ServiceSessionItem.kt
index ed3839e6a..059080479 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/item/ServiceSessionItem.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/item/ServiceSessionItem.kt
@@ -1,31 +1,49 @@
package io.github.droidkaigi.confsched2019.session.ui.item
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
import com.xwray.groupie.databinding.BindableItem
import io.github.droidkaigi.confsched2019.model.Session
import io.github.droidkaigi.confsched2019.session.R
-import io.github.droidkaigi.confsched2019.session.databinding.ItemSpecialSessionBinding
+import io.github.droidkaigi.confsched2019.session.databinding.ItemServiceSessionBinding
+import io.github.droidkaigi.confsched2019.session.ui.actioncreator.SessionContentsActionCreator
-class ServiceSessionItem(
- override val session: Session.ServiceSession
-) : BindableItem(
+class ServiceSessionItem @AssistedInject constructor(
+ @Assisted override val session: Session.ServiceSession,
+ sessionContentsActionCreator: SessionContentsActionCreator
+) : BindableItem(
session.id.hashCode().toLong()
), SessionItem {
- val specialSession get() = session
+ val serviceSession get() = session
- override fun bind(viewBinding: ItemSpecialSessionBinding, position: Int) {
+ @AssistedInject.Factory
+ interface Factory {
+ fun create(
+ session: Session.ServiceSession
+ ): ServiceSessionItem
+ }
+
+ private val onFavoriteClickListener: (Session.ServiceSession) -> Unit = { session ->
+ sessionContentsActionCreator.toggleFavorite(session)
+ }
+
+ override fun bind(viewBinding: ItemServiceSessionBinding, position: Int) {
with(viewBinding) {
- session = specialSession
+ session = serviceSession
@Suppress("StringFormatMatches") // FIXME
timeAndRoom.text = root.context.getString(
R.string.session_duration_room_format,
- specialSession.timeInMinutes,
- specialSession.room.name
+ serviceSession.timeInMinutes,
+ serviceSession.room.name
)
+ favorite.setOnClickListener {
+ onFavoriteClickListener(serviceSession)
+ }
}
}
- override fun getLayout(): Int = R.layout.item_special_session
+ override fun getLayout(): Int = R.layout.item_service_session
override fun equals(other: Any?): Boolean {
if (this === other) return true
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/item/SpeechSessionItem.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/item/SpeechSessionItem.kt
index 546004a1f..628740d5b 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/item/SpeechSessionItem.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/item/SpeechSessionItem.kt
@@ -1,9 +1,11 @@
package io.github.droidkaigi.confsched2019.session.ui.item
import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.ViewGroup
-import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
@@ -13,6 +15,8 @@ import androidx.navigation.NavDirections
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
+import com.squareup.picasso.Picasso
+import com.squareup.picasso.Target
import com.xwray.groupie.databinding.BindableItem
import io.github.droidkaigi.confsched2019.model.Session
import io.github.droidkaigi.confsched2019.model.Speaker
@@ -20,9 +24,9 @@ import io.github.droidkaigi.confsched2019.model.defaultLang
import io.github.droidkaigi.confsched2019.session.R
import io.github.droidkaigi.confsched2019.session.databinding.ItemSessionBinding
import io.github.droidkaigi.confsched2019.session.ui.actioncreator.SessionContentsActionCreator
-import io.github.droidkaigi.confsched2019.session.ui.bindingadapter.loadImage
import io.github.droidkaigi.confsched2019.system.store.SystemStore
import io.github.droidkaigi.confsched2019.util.lazyWithParam
+import jp.wasabeef.picasso.transformations.CropCircleTransformation
import kotlin.math.max
class SpeechSessionItem @AssistedInject constructor(
@@ -79,7 +83,7 @@ class SpeechSessionItem @AssistedInject constructor(
bindSpeaker()
speechSession.message?.let { message ->
- this@with.message.text = message.getBodyByLang(systemStore.lang)
+ this@with.message.text = message.getByLang(systemStore.lang)
}
}
}
@@ -102,11 +106,8 @@ class SpeechSessionItem @AssistedInject constructor(
val speakerView = layoutInflater.get(root.context).inflate(
R.layout.layout_speaker, speakers, false
) as ViewGroup
- val imageView: ImageView = speakerView.findViewById(
- R.id.speaker_image
- )
val textView: TextView = speakerView.findViewById(R.id.speaker)
- bindSpeakerData(speaker, textView, imageView)
+ bindSpeakerData(speaker, textView)
speakers.addView(speakerView)
return@forEach
@@ -114,35 +115,74 @@ class SpeechSessionItem @AssistedInject constructor(
if (existSpeakerView != null && speaker != null) {
val textView: TextView = existSpeakerView.findViewById(R.id.speaker)
textView.text = speaker.name
- val imageView = existSpeakerView.findViewById(R.id.speaker_image)
- bindSpeakerData(speaker, textView, imageView)
+ bindSpeakerData(speaker, textView)
}
}
}
private fun bindSpeakerData(
speaker: Speaker,
- textView: TextView,
- imageView: ImageView
+ textView: TextView
) {
textView.text = speaker.name
- val context = imageView.context
- val placeHolder = VectorDrawableCompat.create(
- context.resources,
- R.drawable.ic_person_outline_black_24dp,
- null
- )
- val placeHolderColor = ContextCompat.getColor(
- context,
- R.color.gray2
- )
- loadImage(
- imageView = imageView,
- imageUrl = speaker.imageUrl,
- circleCrop = true,
- rawPlaceHolder = placeHolder,
- placeHolderTint = placeHolderColor
- )
+ val imageUrl = speaker.imageUrl
+ val context = textView.context
+ val placeHolder = run {
+ VectorDrawableCompat.create(
+ context.resources,
+ R.drawable.ic_person_outline_black_24dp,
+ null
+ )?.apply {
+ setTint(
+ ContextCompat.getColor(
+ context,
+ R.color.gray2
+ )
+ )
+ }
+ }
+
+ imageUrl ?: run {
+ placeHolder?.let {
+ textView.setLeftDrawable(it)
+ }
+ }
+
+ Picasso
+ .get()
+ .load(imageUrl)
+ .transform(CropCircleTransformation())
+ .apply {
+ if (placeHolder != null) {
+ placeholder(placeHolder)
+ }
+ }
+ .into(object : Target {
+ override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
+ placeHolderDrawable?.let {
+ textView.setLeftDrawable(it)
+ }
+ }
+
+ override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
+ }
+
+ override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
+ val res = textView.context.resources
+ val drawable = BitmapDrawable(res, bitmap)
+ textView.setLeftDrawable(drawable)
+ }
+ })
+ }
+
+ fun TextView.setLeftDrawable(drawable: Drawable) {
+ val res = context.resources
+ val widthDp = 16
+ val heightDp = 16
+ val widthPx = (widthDp * res.displayMetrics.density).toInt()
+ val heightPx = (heightDp * res.displayMetrics.density).toInt()
+ drawable.setBounds(0, 0, widthPx, heightPx)
+ setCompoundDrawables(drawable, null, null, null)
}
override fun getLayout(): Int = R.layout.item_session
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/store/SessionPagesStore.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/store/SessionPagesStore.kt
index 6969acea7..258cf81e3 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/store/SessionPagesStore.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/store/SessionPagesStore.kt
@@ -94,10 +94,10 @@ class SessionPagesStore @Inject constructor(
}
}
- fun filteredFavoritedSessions(): LiveData> {
+ fun filteredFavoritedSessions(): LiveData> {
return filteredSessions
.map { sessions ->
- sessions.orEmpty().filterIsInstance()
+ sessions.orEmpty()
.filter { session -> session.isFavorited }
}
}
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/widget/SessionBottomAppBarBehavior.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/widget/SessionBottomAppBarBehavior.kt
new file mode 100644
index 000000000..5eb09b02e
--- /dev/null
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/widget/SessionBottomAppBarBehavior.kt
@@ -0,0 +1,57 @@
+package io.github.droidkaigi.confsched2019.session.ui.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.children
+import androidx.core.widget.NestedScrollView
+import com.google.android.material.bottomappbar.BottomAppBar
+
+class SessionBottomAppBarBehavior(
+ context: Context? = null,
+ attrs: AttributeSet? = null
+) : BottomAppBar.Behavior(context, attrs) {
+
+ private var state: Int = STATE_SCROLLED_UP
+
+ override fun onInterceptTouchEvent(
+ parent: CoordinatorLayout,
+ child: BottomAppBar,
+ ev: MotionEvent
+ ): Boolean {
+ if (!child.isShown || ev.action != MotionEvent.ACTION_DOWN) {
+ return super.onInterceptTouchEvent(parent, child, ev)
+ }
+
+ // If screen be able not to scrolled, it makes to slide the bottom app bar.
+ if (!canScroll(parent)) {
+ state = if (state == STATE_SCROLLED_UP) {
+ super.slideDown(child)
+ STATE_SCROLLED_DOWN
+ } else {
+ super.slideUp(child)
+ STATE_SCROLLED_UP
+ }
+ }
+ return super.onInterceptTouchEvent(parent, child, ev)
+ }
+
+ private fun canScroll(parent: CoordinatorLayout): Boolean {
+ val nestedScrollView = if (parent.childCount > 0) {
+ (parent.children.find { it is NestedScrollView } as? NestedScrollView)
+ } else {
+ null
+ }
+ return nestedScrollView?.canScrollVertically(POSITIVE_DIRECTION) ?: true ||
+ nestedScrollView?.canScrollVertically(NEGATIVE_DIRECTION) ?: true
+ }
+
+ companion object {
+ private const val POSITIVE_DIRECTION = 1
+ private const val NEGATIVE_DIRECTION = -1
+
+ private const val STATE_SCROLLED_DOWN = 1
+ private const val STATE_SCROLLED_UP = 2
+ }
+}
diff --git a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/widget/SessionsItemDecoration.kt b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/widget/SessionsItemDecoration.kt
index fd2121f94..27317cbc1 100644
--- a/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/widget/SessionsItemDecoration.kt
+++ b/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/widget/SessionsItemDecoration.kt
@@ -5,12 +5,16 @@ import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
+import android.util.SparseArray
+import android.view.View
import androidx.core.content.res.ResourcesCompat
+import androidx.core.util.forEach
import androidx.recyclerview.widget.RecyclerView
import com.xwray.groupie.GroupAdapter
import io.github.droidkaigi.confsched2019.session.R
import io.github.droidkaigi.confsched2019.session.ui.item.SessionItem
-import io.github.droidkaigi.confsched2019.util.logd
+import io.github.droidkaigi.confsched2019.timber.debug
+import timber.log.Timber
class SessionsItemDecoration(
val context: Context,
@@ -29,6 +33,8 @@ class SessionsItemDecoration(
private val textPaddingBottom = resources.getDimensionPixelSize(
R.dimen.session_bottom_sheet_left_time_text_padding_bottom
)
+ // Keep SparseArray instance on property to avoid object creation in every onDrawOver()
+ private val adapterPositionToViews = SparseArray()
val paint = Paint().apply {
style = Paint.Style.FILL
@@ -38,20 +44,25 @@ class SessionsItemDecoration(
try {
typeface = ResourcesCompat.getFont(context, R.font.lekton)
} catch (e: Resources.NotFoundException) {
- logd(e = e)
+ Timber.debug(e)
}
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
- var lastTime: String? = null
+ // Sort child views by adapter position
for (i in 0 until parent.childCount) {
val view = parent.getChildAt(i)
val position = parent.getChildAdapterPosition(view)
- if (position == -1 || position >= groupAdapter.itemCount) return
+ if (position != RecyclerView.NO_POSITION && position < groupAdapter.itemCount) {
+ adapterPositionToViews.put(position, view)
+ }
+ }
- val time = getSessionTime(position) ?: continue
+ var lastTime: String? = null
+ adapterPositionToViews.forEach { position, view ->
+ val time = getSessionTime(position) ?: return@forEach
- if (lastTime == time) continue
+ if (lastTime == time) return@forEach
lastTime = time
val nextTime = getSessionTime(position + 1)
@@ -68,6 +79,8 @@ class SessionsItemDecoration(
paint
)
}
+
+ adapterPositionToViews.clear()
}
private fun getSessionTime(position: Int): String? {
diff --git a/feature/session/src/main/res/color/selector_tab_title.xml b/feature/session/src/main/res/color/selector_tab_title.xml
deleted file mode 100644
index b9e75233c..000000000
--- a/feature/session/src/main/res/color/selector_tab_title.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/feature/session/src/main/res/drawable/ic_file_copy_outline_gray2_24dp.xml b/feature/session/src/main/res/drawable/ic_file_copy_outline_gray2_24dp.xml
new file mode 100644
index 000000000..51c3c69cf
--- /dev/null
+++ b/feature/session/src/main/res/drawable/ic_file_copy_outline_gray2_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/feature/session/src/main/res/drawable/ic_local_movies_outline_gray2_24dp.xml b/feature/session/src/main/res/drawable/ic_local_movies_outline_gray2_24dp.xml
new file mode 100644
index 000000000..a92fb656b
--- /dev/null
+++ b/feature/session/src/main/res/drawable/ic_local_movies_outline_gray2_24dp.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/feature/session/src/main/res/drawable/ic_room_outline_black_24dp.xml b/feature/session/src/main/res/drawable/ic_room_outline_black_24dp.xml
new file mode 100644
index 000000000..64b261c45
--- /dev/null
+++ b/feature/session/src/main/res/drawable/ic_room_outline_black_24dp.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/feature/session/src/main/res/drawable/shape_pill_indicator.xml b/feature/session/src/main/res/drawable/shape_pill_indicator.xml
new file mode 100644
index 000000000..c506c2fa6
--- /dev/null
+++ b/feature/session/src/main/res/drawable/shape_pill_indicator.xml
@@ -0,0 +1,25 @@
+
+
+ -
+
+
+
+
+
+
+
diff --git a/feature/session/src/main/res/layout/fragment_bottom_sheet_sessions.xml b/feature/session/src/main/res/layout/fragment_bottom_sheet_sessions.xml
index f573dde03..765c4fb50 100644
--- a/feature/session/src/main/res/layout/fragment_bottom_sheet_sessions.xml
+++ b/feature/session/src/main/res/layout/fragment_bottom_sheet_sessions.xml
@@ -8,9 +8,19 @@
+ name="isCollapsed"
+ type="Boolean"
+ />
+
+
+
+
@@ -22,12 +32,29 @@
tools:context="io.github.droidkaigi.confsched2019.session.ui.SessionPagesFragment"
>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/feature/session/src/main/res/layout/fragment_session_detail.xml b/feature/session/src/main/res/layout/fragment_session_detail.xml
index ad37665d8..fe330270b 100644
--- a/feature/session/src/main/res/layout/fragment_session_detail.xml
+++ b/feature/session/src/main/res/layout/fragment_session_detail.xml
@@ -34,6 +34,14 @@
android:layout_height="match_parent"
>
+
+
+
+
+
+
-
+
+
+
+
+
+
@@ -173,15 +258,15 @@
style="@style/Widget.MaterialComponents.Chip.Action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:clickable="false"
+ android:enabled="false"
+ android:focusable="false"
android:text="@{session.language.getByLang(lang)}"
android:textAppearance="?textAppearanceCaption"
android:textColor="@color/white"
app:chipBackgroundColor="#7982e1"
app:chipMinTouchTargetSize="0dp"
tools:text="Japanese"
- android:clickable="false"
- android:enabled="false"
- android:focusable="false"
/>
@@ -237,9 +325,9 @@
android:layout_marginTop="26dp"
android:text="@string/session_detail_survey"
android:textAppearance="@style/TextAppearance.App.Subtitle1"
- app:visibleGone="@{session.isFinished}"
app:layout_constraintStart_toStartOf="@id/session_title"
- app:layout_constraintTop_toBottomOf="@id/divider3"
+ app:layout_constraintTop_toBottomOf="@id/divider_top_survey"
+ app:visibleGone="@{session.isFinished}"
/>
-
+
+
+
+
+
+
@@ -319,6 +467,7 @@
android:layout_gravity="bottom"
app:fabAlignmentMode="end"
app:hideOnScroll="true"
+ app:layout_behavior="@string/session_bottom_appbar_behavior"
/>
@@ -93,12 +92,11 @@
@@ -118,12 +116,11 @@
diff --git a/feature/session/src/main/res/layout/fragment_session_pages.xml b/feature/session/src/main/res/layout/fragment_session_pages.xml
index 15b0ea0db..0f048acef 100644
--- a/feature/session/src/main/res/layout/fragment_session_pages.xml
+++ b/feature/session/src/main/res/layout/fragment_session_pages.xml
@@ -8,6 +8,7 @@
@@ -15,20 +16,23 @@
android:id="@+id/sessions_tab_layout"
style="@style/Widget.MaterialComponents.TabLayout.Colored"
android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:background="?colorPrimary"
+ android:layout_height="40dp"
android:tabStripEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:tabIndicatorColor="@android:color/transparent"
- app:tabRippleColor="@null"
+ app:tabGravity="fill"
+ app:tabIndicator="@drawable/shape_pill_indicator"
+ app:tabIndicatorGravity="stretch"
+ app:tabTextAppearance="@style/TabTextAppearance"
+ app:tabTextColor="@color/white"
/>
+
+
+
+
@@ -54,12 +58,26 @@
android:text="@{session.title}"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.App.Subtitle1"
+ app:layout_constraintEnd_toEndOf="@id/favorite"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/live"
tools:text="テストセッション"
/>
+
+
-
-
diff --git a/feature/session/src/main/res/layout/layout_speaker.xml b/feature/session/src/main/res/layout/layout_speaker.xml
index 52e6076fe..7ea443412 100644
--- a/feature/session/src/main/res/layout/layout_speaker.xml
+++ b/feature/session/src/main/res/layout/layout_speaker.xml
@@ -6,18 +6,11 @@
android:layout_height="wrap_content"
>
-
-
diff --git a/feature/session/src/main/res/layout/layout_title_chip.xml b/feature/session/src/main/res/layout/layout_title_chip.xml
deleted file mode 100644
index a2ac5e0f8..000000000
--- a/feature/session/src/main/res/layout/layout_title_chip.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
diff --git a/frontend/android/src/main/res/menu/menu_toolbar.xml b/feature/session/src/main/res/menu/menu_toolbar.xml
similarity index 100%
rename from frontend/android/src/main/res/menu/menu_toolbar.xml
rename to feature/session/src/main/res/menu/menu_toolbar.xml
diff --git a/feature/session/src/main/res/values-ja/strings.xml b/feature/session/src/main/res/values-ja/strings.xml
index 3be46132d..abc80bfd5 100644
--- a/feature/session/src/main/res/values-ja/strings.xml
+++ b/feature/session/src/main/res/values-ja/strings.xml
@@ -3,9 +3,16 @@
フィルター
リセット
絞り込む
+ 絞り込み中
+ 対象者
アンケート
回答する
+ 資料
+ 動画
+ スライド
+ https://droidkaigi.jp/2019/timetable/%s
検索する…
アンケートに回答する
同時通訳
+ お気に入りのセッションを登録すると\nあなた専用のプランを作ることが\nできます。
diff --git a/feature/session/src/main/res/values/dimens.xml b/feature/session/src/main/res/values/dimens.xml
index 48038b1ba..3e6aa518c 100644
--- a/feature/session/src/main/res/values/dimens.xml
+++ b/feature/session/src/main/res/values/dimens.xml
@@ -8,5 +8,4 @@
8dp
0dp
4dp
- 34dp
diff --git a/feature/session/src/main/res/values/strings.xml b/feature/session/src/main/res/values/strings.xml
index be9a09d3f..b3b82f56e 100644
--- a/feature/session/src/main/res/values/strings.xml
+++ b/feature/session/src/main/res/values/strings.xml
@@ -5,15 +5,21 @@
header image
%1$d min / %2$s
Filtering
+ Filtered
Filter
Reset
Room
Language
Category
+ Intended Audience
Tags
Survey
Go to survey
Speaker
+ Resources
+ Video
+ Slides
+ https://droidkaigi.jp/2019/en/timetable/%s
Session
Share
Place
@@ -21,4 +27,6 @@
Go to survey
Live
simultaneous interpretation
+ io.github.droidkaigi.confsched2019.session.ui.widget.SessionBottomAppBarBehavior
+ Add sessions to your plan\nby tapping the bookmark button.
diff --git a/feature/session/src/main/res/values/styles.xml b/feature/session/src/main/res/values/styles.xml
new file mode 100644
index 000000000..e6aef88f8
--- /dev/null
+++ b/feature/session/src/main/res/values/styles.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/feature/session/src/test/java/io/github/droidkaigi/confsched2019/SessionDummyDatas.kt b/feature/session/src/test/java/io/github/droidkaigi/confsched2019/SessionDummyDatas.kt
index 722918416..7a51a46c0 100644
--- a/feature/session/src/test/java/io/github/droidkaigi/confsched2019/SessionDummyDatas.kt
+++ b/feature/session/src/test/java/io/github/droidkaigi/confsched2019/SessionDummyDatas.kt
@@ -6,7 +6,6 @@ import io.github.droidkaigi.confsched2019.model.Category
import io.github.droidkaigi.confsched2019.model.LocaledString
import io.github.droidkaigi.confsched2019.model.Room
import io.github.droidkaigi.confsched2019.model.Session
-import io.github.droidkaigi.confsched2019.model.SessionMessage
import io.github.droidkaigi.confsched2019.model.SessionType
private val startTime = DateTime.createAdjusted(2019, 2, 7, 10, 0)
@@ -19,7 +18,8 @@ fun dummySessionData(): List {
startTime + 30.minutes,
"session",
Room(0, "Hall"),
- SessionType.WelcomeTalk
+ SessionType.WelcomeTalk,
+ true
),
firstDummySpeechSession(),
Session.SpeechSession(
@@ -37,10 +37,12 @@ fun dummySessionData(): List {
language = LocaledString("英語", "English"),
category = Category(10, LocaledString("ツール", "Tool")),
intendedAudience = "extream",
+ videoUrl = "https://droidkaigi.jp/2019/#might_be_null",
+ slideUrl = "https://droidkaigi.jp/2019/#might_be_null",
isInterpretationTarget = true,
isFavorited = true,
speakers = listOf(),
- message = SessionMessage("部屋移動", "room moved")
+ message = LocaledString("部屋移動", "room moved")
)
)
}
@@ -61,6 +63,8 @@ fun firstDummySpeechSession(): Session.SpeechSession {
language = LocaledString("日本語", "Japanese"),
category = Category(id = 10, name = LocaledString("アーキテクチャ", "App Architecture")),
intendedAudience = "intermediate",
+ videoUrl = "https://droidkaigi.jp/2019/#might_be_null",
+ slideUrl = "https://droidkaigi.jp/2019/#might_be_null",
isInterpretationTarget = false,
isFavorited = false,
speakers = listOf(),
diff --git a/feature/session/src/test/java/io/github/droidkaigi/confsched2019/session/ui/store/SessionContentsStoreTest.kt b/feature/session/src/test/java/io/github/droidkaigi/confsched2019/session/ui/store/SessionContentsStoreTest.kt
index 0bdab1290..0c4f6fcf6 100644
--- a/feature/session/src/test/java/io/github/droidkaigi/confsched2019/session/ui/store/SessionContentsStoreTest.kt
+++ b/feature/session/src/test/java/io/github/droidkaigi/confsched2019/session/ui/store/SessionContentsStoreTest.kt
@@ -7,7 +7,6 @@ import io.github.droidkaigi.confsched2019.dummySessionData
import io.github.droidkaigi.confsched2019.ext.android.CoroutinePlugin
import io.github.droidkaigi.confsched2019.ext.android.changedForever
import io.github.droidkaigi.confsched2019.model.LoadingState
-import io.github.droidkaigi.confsched2019.model.Session
import io.github.droidkaigi.confsched2019.model.SessionContents
import io.kotlintest.shouldBe
import io.mockk.MockKAnnotations
diff --git a/feature/settings/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/feature/settings/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
index 461cd5732..f237963dd 100644
--- a/feature/settings/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ b/feature/settings/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
@@ -20,6 +20,6 @@ public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
+ assertEquals("io.github.droidkaigi.confsched2019.settings.test", appContext.getPackageName());
}
}
diff --git a/feature/sponsor/build.gradle b/feature/sponsor/build.gradle
index cf0e069c2..6f38001f5 100644
--- a/feature/sponsor/build.gradle
+++ b/feature/sponsor/build.gradle
@@ -14,7 +14,6 @@ dependencies {
implementation project(":model")
implementation project(":data:repository")
implementation project(':ext:android-extension')
- implementation project(':ext:log')
implementation Dep.Kotlin.stdlibJvm
api Dep.Kotlin.coroutines
diff --git a/feature/sponsor/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/feature/sponsor/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
index 461cd5732..3a1c372bc 100644
--- a/feature/sponsor/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ b/feature/sponsor/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
@@ -20,6 +20,6 @@ public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
+ assertEquals("io.github.droidkaigi.confsched2019.sponsor.test", appContext.getPackageName());
}
}
diff --git a/feature/system/build.gradle b/feature/system/build.gradle
index d6fdbd989..6b873ffdf 100644
--- a/feature/system/build.gradle
+++ b/feature/system/build.gradle
@@ -19,6 +19,7 @@ dependencies {
api Dep.Kotlin.coroutines
implementation Dep.Kotlin.androidCoroutinesDispatcher
implementation Dep.AndroidX.appCompat
+ implementation Dep.AndroidX.browser
implementation Dep.Ktor.clientAndroid
@@ -29,6 +30,8 @@ dependencies {
kapt Dep.Dagger.compiler
kapt Dep.Dagger.androidProcessor
+ implementation Dep.Timber.android
+
testImplementation Dep.Test.junit
androidTestImplementation Dep.Test.testRunner
testImplementation Dep.Test.slf4j
diff --git a/feature/system/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/feature/system/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
index 461cd5732..7b42d1cf2 100644
--- a/feature/system/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ b/feature/system/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
@@ -20,6 +20,6 @@ public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
+ assertEquals("io.github.droidkaigi.confsched2019.system.test", appContext.getPackageName());
}
}
diff --git a/feature/system/src/main/java/io/github/droidkaigi/confsched2019/system/actioncreator/ActivityActionCreator.kt b/feature/system/src/main/java/io/github/droidkaigi/confsched2019/system/actioncreator/ActivityActionCreator.kt
index 31c37b1f5..5d958eb16 100644
--- a/feature/system/src/main/java/io/github/droidkaigi/confsched2019/system/actioncreator/ActivityActionCreator.kt
+++ b/feature/system/src/main/java/io/github/droidkaigi/confsched2019/system/actioncreator/ActivityActionCreator.kt
@@ -2,14 +2,21 @@ package io.github.droidkaigi.confsched2019.system.actioncreator
import android.content.Intent
import android.net.Uri
+import androidx.browser.customtabs.CustomTabsIntent
+import androidx.core.app.ShareCompat
+import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import io.github.droidkaigi.confsched2019.system.R
import javax.inject.Inject
class ActivityActionCreator @Inject constructor(val activity: FragmentActivity) {
fun openUrl(url: String) {
- val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
- activity.startActivity(intent)
+ val customTabsIntent = CustomTabsIntent.Builder()
+ .setShowTitle(true)
+ .enableUrlBarHiding()
+ .setToolbarColor(ContextCompat.getColor(activity, R.color.white))
+ .build()
+ customTabsIntent.launchUrl(activity, Uri.parse(url))
}
fun openVenueOnGoogleMap() {
@@ -24,6 +31,13 @@ class ActivityActionCreator @Inject constructor(val activity: FragmentActivity)
}
}
+ fun shareUrl(url: String) {
+ val builder: ShareCompat.IntentBuilder = ShareCompat.IntentBuilder.from(activity)
+ builder.setText(url)
+ .setType("text/plain")
+ .startChooser()
+ }
+
companion object {
const val LATITUDE_LOCATION = "35.696065"
const val LONGITUDE_LOCATION = "139.690426"
diff --git a/feature/system/src/main/java/io/github/droidkaigi/confsched2019/system/actioncreator/ErrorHandler.kt b/feature/system/src/main/java/io/github/droidkaigi/confsched2019/system/actioncreator/ErrorHandler.kt
index 5ad6c2881..44f2b41e5 100644
--- a/feature/system/src/main/java/io/github/droidkaigi/confsched2019/system/actioncreator/ErrorHandler.kt
+++ b/feature/system/src/main/java/io/github/droidkaigi/confsched2019/system/actioncreator/ErrorHandler.kt
@@ -8,10 +8,12 @@ import io.github.droidkaigi.confsched2019.dispatcher.Dispatcher
import io.github.droidkaigi.confsched2019.model.ErrorMessage
import io.github.droidkaigi.confsched2019.system.BuildConfig
import io.github.droidkaigi.confsched2019.system.R
-import io.github.droidkaigi.confsched2019.util.logd
-import io.github.droidkaigi.confsched2019.util.loge
+import io.github.droidkaigi.confsched2019.timber.error
import io.ktor.client.features.BadResponseStatusException
import kotlinx.coroutines.CancellationException
+import timber.log.Timber
+import timber.log.debug
+import timber.log.error
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
@@ -20,14 +22,14 @@ interface ErrorHandler {
val dispatcher: Dispatcher
fun onError(e: Throwable, msg: String? = null) {
if (e.cause is CancellationException) {
- logd(e = e) {
+ Timber.debug(e) {
"coroutine canceled"
}
return
}
when (e) {
is CancellationException ->
- logd(e = e) {
+ Timber.debug(e) {
"coroutine canceled"
}
is UnknownHostException,
@@ -37,17 +39,17 @@ interface ErrorHandler {
is FirebaseApiNotAvailableException,
is FirebaseNetworkException -> {
val message = ErrorMessage.of(R.string.system_error_network, e)
- loge(e = e)
+ Timber.error(e)
dispatcher.launchAndDispatch(Action.Error(message))
}
is BadResponseStatusException -> {
val message = ErrorMessage.of(R.string.system_error_server, e)
- loge(e = e)
+ Timber.error(e)
dispatcher.launchAndDispatch(Action.Error(message))
}
else -> {
val message = ErrorMessage.of(msg ?: e.message ?: e.javaClass.name ?: "", e)
- loge(e = e) {
+ Timber.error(e) {
(message as ErrorMessage.Message).message
}
if (BuildConfig.DEBUG) throw UnknownException(e)
diff --git a/feature/user/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java b/feature/user/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
index 461cd5732..b84d79025 100644
--- a/feature/user/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
+++ b/feature/user/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.java
@@ -20,6 +20,6 @@ public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
- assertEquals("io.github.droidkaigi.confsched2019.ui.test", appContext.getPackageName());
+ assertEquals("io.github.droidkaigi.confsched2019.test", appContext.getPackageName());
}
}
diff --git a/frontend/android/build.gradle b/frontend/android/build.gradle
index bc655515f..e4e31f8f5 100644
--- a/frontend/android/build.gradle
+++ b/frontend/android/build.gradle
@@ -25,6 +25,7 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
+ resConfigs "ja"
// For Android Studio Gradle sync
applicationIdSuffix ".debug"
}
@@ -41,8 +42,8 @@ android {
lintOptions {
lintConfig file("${project.projectDir}/lint.xml")
- xmlReport System.getenv("CI") == "true"
- htmlReport System.getenv("CI") != "true"
+ xmlReport isCi
+ htmlReport !isCi
xmlOutput file("lint-results.xml")
htmlOutput file("lint-results.html")
@@ -76,6 +77,8 @@ dependencies {
implementation Dep.Kotlin.stdlibJvm
api Dep.Kotlin.coroutines
+ implementation Dep.Kotlin.androidCoroutinesDispatcher
+ implementation Dep.OkHttp.okio
implementation Dep.Firebase.fireStore
@@ -107,6 +110,8 @@ dependencies {
compileOnly Dep.Dagger.assistedInjectAnnotations
kapt Dep.Dagger.assistedInjectProcessor
+ implementation Dep.Timber.android
+
testImplementation Dep.Test.junit
androidTestImplementation Dep.Test.testRunner
diff --git a/frontend/android/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.kt b/frontend/android/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.kt
index fa966205b..2291937d9 100644
--- a/frontend/android/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.kt
+++ b/frontend/android/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/ExampleInstrumentedTest.kt
@@ -16,6 +16,6 @@ class ExampleInstrumentedTest {
@Test fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
- assertEquals("io.github.droidkaigi.confsched2019.ui", appContext.packageName)
+ assertEquals("io.github.droidkaigi.confsched2019.debug", appContext.packageName)
}
}
diff --git a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/App.kt b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/App.kt
index 1d83909dd..95d20a66e 100644
--- a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/App.kt
+++ b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/App.kt
@@ -15,7 +15,8 @@ import io.github.droidkaigi.confsched2019.di.createAppComponent
import io.github.droidkaigi.confsched2019.ext.android.changedForever
import io.github.droidkaigi.confsched2019.system.actioncreator.SystemActionCreator
import io.github.droidkaigi.confsched2019.system.store.SystemStore
-import io.github.droidkaigi.confsched2019.util.logd
+import timber.log.Timber
+import timber.log.debug
import javax.inject.Inject
open class App : DaggerApplication() {
@@ -38,7 +39,7 @@ open class App : DaggerApplication() {
// fetch font for cache
ResourcesCompat.getFont(this, R.font.lekton, object : ResourcesCompat.FontCallback() {
override fun onFontRetrievalFailed(reason: Int) {
- logd { "onFontRetrievalFailed$reason" }
+ Timber.debug { "onFontRetrievalFailed$reason" }
}
override fun onFontRetrieved(typeface: Typeface) {
diff --git a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/DebugApp.kt b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/DebugApp.kt
index a2f5696ad..e184c29a2 100644
--- a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/DebugApp.kt
+++ b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/DebugApp.kt
@@ -1,10 +1,9 @@
package io.github.droidkaigi.confsched2019
-import android.util.Log
import com.facebook.stetho.Stetho
import com.squareup.leakcanary.LeakCanary
-import io.github.droidkaigi.confsched2019.util.LogLevel
-import io.github.droidkaigi.confsched2019.util.logHandler
+import timber.log.LogcatTree
+import timber.log.Timber
class DebugApp : App() {
override fun onCreate() {
@@ -26,21 +25,6 @@ class DebugApp : App() {
}
private fun setupLogHandler() {
- logHandler = { logLevel: LogLevel, tag: String, e: Throwable?, logHandler: () -> String ->
- val message = if (e != null) {
- logHandler() + Log.getStackTraceString(e)
- } else {
- logHandler()
- }
- Log.println(
- when (logLevel) {
- LogLevel.DEBUG -> Log.DEBUG
- LogLevel.WARN -> Log.WARN
- LogLevel.ERROR -> Log.ERROR
- },
- tag,
- message
- )
- }
+ Timber.plant(LogcatTree("droidkaigi"))
}
}
diff --git a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/AppComponent.kt b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/AppComponent.kt
index 31ca8520e..5d6a7951f 100644
--- a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/AppComponent.kt
+++ b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/AppComponent.kt
@@ -16,7 +16,7 @@ import javax.inject.Singleton
MainActivityModule.MainActivityBuilder::class,
DbComponentModule::class,
RepositoryComponentModule::class,
- FireStoreComponentModule::class,
+ FirestoreComponentModule::class,
ApiComponentModule::class
]
)
diff --git a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/FireStoreComponentModule.kt b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/FirestoreComponentModule.kt
similarity index 59%
rename from frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/FireStoreComponentModule.kt
rename to frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/FirestoreComponentModule.kt
index 521ec408a..e50454145 100644
--- a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/FireStoreComponentModule.kt
+++ b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/FirestoreComponentModule.kt
@@ -2,17 +2,17 @@ package io.github.droidkaigi.confsched2019.di
import dagger.Module
import dagger.Provides
-import io.github.droidkaigi.confsched2019.data.firestore.FireStore
-import io.github.droidkaigi.confsched2019.data.repository.FireStoreComponent
+import io.github.droidkaigi.confsched2019.data.firestore.Firestore
+import io.github.droidkaigi.confsched2019.data.repository.FirestoreComponent
import io.github.droidkaigi.confsched2019.ext.android.Dispatchers
import javax.inject.Singleton
@Module
-object FireStoreComponentModule {
- @JvmStatic @Provides @Singleton fun provideRepository(): FireStore {
- return FireStoreComponent.builder()
+object FirestoreComponentModule {
+ @JvmStatic @Provides @Singleton fun provideRepository(): Firestore {
+ return FirestoreComponent.builder()
.coroutineContext(Dispatchers.IO)
.build()
- .fireStore()
+ .firestore()
}
}
diff --git a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/RepositoryComponentModule.kt b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/RepositoryComponentModule.kt
index 4cb50c516..0fe749d71 100644
--- a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/RepositoryComponentModule.kt
+++ b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/di/RepositoryComponentModule.kt
@@ -6,7 +6,7 @@ import io.github.droidkaigi.confsched2019.data.api.DroidKaigiApi
import io.github.droidkaigi.confsched2019.data.api.GoogleFormApi
import io.github.droidkaigi.confsched2019.data.db.SessionDatabase
import io.github.droidkaigi.confsched2019.data.db.SponsorDatabase
-import io.github.droidkaigi.confsched2019.data.firestore.FireStore
+import io.github.droidkaigi.confsched2019.data.firestore.Firestore
import io.github.droidkaigi.confsched2019.data.repository.RepositoryComponent
import io.github.droidkaigi.confsched2019.data.repository.SessionRepository
import io.github.droidkaigi.confsched2019.data.repository.SponsorRepository
@@ -19,14 +19,14 @@ object RepositoryComponentModule {
googleFormApi: GoogleFormApi,
database: SessionDatabase,
sponsorDatabase: SponsorDatabase,
- fireStore: FireStore
+ firestore: Firestore
): SessionRepository {
return RepositoryComponent.builder()
.droidKaigiApi(droidKaigiApi)
.googleFormApi(googleFormApi)
.database(database)
.sponsorDatabase(sponsorDatabase)
- .fireStore(fireStore)
+ .firestore(firestore)
.build()
.sessionRepository()
}
@@ -36,14 +36,14 @@ object RepositoryComponentModule {
googleFormApi: GoogleFormApi,
database: SessionDatabase,
sponsorDatabase: SponsorDatabase,
- fireStore: FireStore
+ firestore: Firestore
): SponsorRepository {
return RepositoryComponent.builder()
.droidKaigiApi(droidKaigiApi)
.googleFormApi(googleFormApi)
.database(database)
.sponsorDatabase(sponsorDatabase)
- .fireStore(fireStore)
+ .firestore(firestore)
.build()
.sponsorRepository()
}
diff --git a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/ui/MainActivity.kt b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/ui/MainActivity.kt
index ff113f66a..c78584a2e 100644
--- a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/ui/MainActivity.kt
+++ b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/ui/MainActivity.kt
@@ -3,10 +3,10 @@ package io.github.droidkaigi.confsched2019.ui
import android.graphics.PorterDuff
import android.os.Build
import android.os.Bundle
-import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.core.content.ContextCompat
+import androidx.core.view.GravityCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
@@ -130,15 +130,18 @@ class MainActivity : DaggerAppCompatActivity() {
}
}
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- menuInflater.inflate(R.menu.menu_toolbar, menu)
- return true
- }
-
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return NavigationUI.onNavDestinationSelected(item, navController) ||
super.onOptionsItemSelected(item)
}
+
+ override fun onBackPressed() {
+ if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
+ binding.drawerLayout.closeDrawer(GravityCompat.START)
+ return
+ }
+ super.onBackPressed()
+ }
}
@Module
diff --git a/frontend/android/src/main/res/drawable/bg_navigation_footer.xml b/frontend/android/src/main/res/drawable/bg_navigation_footer.xml
new file mode 100644
index 000000000..8971a41ca
--- /dev/null
+++ b/frontend/android/src/main/res/drawable/bg_navigation_footer.xml
@@ -0,0 +1,17 @@
+
+
+ -
+
+
+
+
+
+
+
diff --git a/frontend/android/src/main/res/drawable/ic_settings_outline_black_24px.xml b/frontend/android/src/main/res/drawable/ic_settings_outline_black_24dp.xml
similarity index 100%
rename from frontend/android/src/main/res/drawable/ic_settings_outline_black_24px.xml
rename to frontend/android/src/main/res/drawable/ic_settings_outline_black_24dp.xml
diff --git a/frontend/android/src/main/res/layout/activity_main.xml b/frontend/android/src/main/res/layout/activity_main.xml
index f7672759d..07403b2ae 100644
--- a/frontend/android/src/main/res/layout/activity_main.xml
+++ b/frontend/android/src/main/res/layout/activity_main.xml
@@ -74,6 +74,9 @@
app:headerLayout="@layout/layout_navigation_header"
app:menu="@menu/menu_nav_drawer"
>
+
+
+
diff --git a/frontend/android/src/main/res/layout/layout_navigation_footer.xml b/frontend/android/src/main/res/layout/layout_navigation_footer.xml
new file mode 100644
index 000000000..062183f3f
--- /dev/null
+++ b/frontend/android/src/main/res/layout/layout_navigation_footer.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
diff --git a/frontend/android/src/main/res/menu/menu_nav_drawer.xml b/frontend/android/src/main/res/menu/menu_nav_drawer.xml
index 56cfccac0..80acd0410 100644
--- a/frontend/android/src/main/res/menu/menu_nav_drawer.xml
+++ b/frontend/android/src/main/res/menu/menu_nav_drawer.xml
@@ -42,18 +42,17 @@
/>
-
-
+
+
diff --git a/frontend/android/src/main/res/values-ja/strings.xml b/frontend/android/src/main/res/values-ja/strings.xml
new file mode 100644
index 000000000..525128fb3
--- /dev/null
+++ b/frontend/android/src/main/res/values-ja/strings.xml
@@ -0,0 +1,3 @@
+
+ 全体アンケートに\nご協力ください
+
diff --git a/frontend/android/src/main/res/values/strings.xml b/frontend/android/src/main/res/values/strings.xml
index 2df81721d..589f26d07 100644
--- a/frontend/android/src/main/res/values/strings.xml
+++ b/frontend/android/src/main/res/values/strings.xml
@@ -1,4 +1,5 @@
DroidKaigi 2019
logo image
+ Please fill out the entire survey
diff --git a/frontendcomponent/androidcomponent/build.gradle b/frontendcomponent/androidcomponent/build.gradle
index 388441ba7..945a00a07 100644
--- a/frontendcomponent/androidcomponent/build.gradle
+++ b/frontendcomponent/androidcomponent/build.gradle
@@ -9,6 +9,7 @@ apply from: rootProject.file('gradle/android.gradle')
dependencies {
api project(":model")
api Dep.AndroidX.design
+ api Dep.AndroidX.coreKtx
implementation Dep.Kotlin.stdlibJvm
api Dep.Dagger.androidSupport
@@ -31,6 +32,7 @@ dependencies {
androidTestImplementation Dep.Test.espressoCore
}
repositories {
+ if (!isCi) { maven { url "https://maven-central-asia.storage-download.googleapis.com/repos/central/data/" } }
mavenCentral()
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
diff --git a/frontendcomponent/androidcomponent/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/component/ExampleInstrumentedTest.java b/frontendcomponent/androidcomponent/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/component/ExampleInstrumentedTest.java
deleted file mode 100644
index f8a05565d..000000000
--- a/frontendcomponent/androidcomponent/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/component/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.widget.component;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ui.component.test", appContext.getPackageName());
- }
-}
diff --git a/frontendcomponent/androidcomponent/src/main/java/io/github/droidkaigi/confsched2019/action/Action.kt b/frontendcomponent/androidcomponent/src/main/java/io/github/droidkaigi/confsched2019/action/Action.kt
index fce9c1590..718c0fdc6 100644
--- a/frontendcomponent/androidcomponent/src/main/java/io/github/droidkaigi/confsched2019/action/Action.kt
+++ b/frontendcomponent/androidcomponent/src/main/java/io/github/droidkaigi/confsched2019/action/Action.kt
@@ -52,8 +52,8 @@ sealed class Action {
object UserRegistered : Action()
- class AnnouncementLoadingStateChanged(val loadingState: LoadingState) : Action()
- class AnnouncementLoaded(val announcements: List) : Action()
+ data class AnnouncementLoadingStateChanged(val loadingState: LoadingState) : Action()
+ data class AnnouncementLoaded(val announcements: List) : Action()
data class SponsorLoadingStateChanged(val loadingState: LoadingState) : Action()
data class SponsorLoaded(val sponsors: List) : Action()
diff --git a/frontendcomponent/androidcomponent/src/main/java/io/github/droidkaigi/confsched2019/widget/FilterChip.kt b/frontendcomponent/androidcomponent/src/main/java/io/github/droidkaigi/confsched2019/widget/FilterChip.kt
new file mode 100644
index 000000000..f5156e88a
--- /dev/null
+++ b/frontendcomponent/androidcomponent/src/main/java/io/github/droidkaigi/confsched2019/widget/FilterChip.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2018 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 io.github.droidkaigi.confsched2019.widget
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Outline
+import android.graphics.Paint
+import android.graphics.Paint.ANTI_ALIAS_FLAG
+import android.graphics.Paint.Style.STROKE
+import android.graphics.drawable.Drawable
+import android.os.Build.VERSION.SDK_INT
+import android.os.Build.VERSION_CODES.M
+import android.text.Layout.Alignment.ALIGN_NORMAL
+import android.text.StaticLayout
+import android.text.TextPaint
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.view.animation.AnimationUtils
+import android.widget.Checkable
+import androidx.annotation.ColorInt
+import androidx.core.animation.doOnEnd
+import androidx.core.content.res.getColorOrThrow
+import androidx.core.content.res.getDimensionOrThrow
+import androidx.core.content.res.getDimensionPixelSizeOrThrow
+import androidx.core.content.res.getDrawableOrThrow
+import androidx.core.graphics.ColorUtils
+import androidx.core.graphics.withScale
+import androidx.core.graphics.withTranslation
+import com.google.android.material.math.MathUtils.lerp
+import io.github.droidkaigi.confsched2019.widget.component.R
+
+/**
+ * A custom view for displaying filters. Allows a custom presentation of the tag color and selection
+ * state.
+ */
+class FilterChip @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr), Checkable {
+
+ var color: Int = 0
+ set(value) {
+ if (field != value) {
+ field = value
+ dotPaint.color = value
+ postInvalidateOnAnimation()
+ }
+ }
+
+ var selectedTextColor: Int? = null
+
+ var text: CharSequence = ""
+ set(value) {
+ field = value
+ updateContentDescription()
+ requestLayout()
+ }
+
+ var showIcons: Boolean = true
+ set(value) {
+ if (field != value) {
+ field = value
+ requestLayout()
+ }
+ }
+
+ var onCheckedChangeListener: OnCheckedChangeListener? = null
+
+ private var progress = 0f
+ set(value) {
+ if (field != value) {
+ val prevChecked = isChecked
+ field = value
+ val newChecked = isChecked
+ postInvalidateOnAnimation()
+ if (value == 0f || value == 1f) {
+ updateContentDescription()
+ }
+ if (newChecked != prevChecked) {
+ onCheckedChangeListener?.onCheckedChanged(this, newChecked)
+ }
+ }
+ }
+
+ private val padding: Int
+
+ private val outlinePaint: Paint
+
+ private val textPaint: TextPaint
+
+ private val dotPaint: Paint
+
+ private val clear: Drawable
+
+ private val touchFeedback: Drawable
+
+ private lateinit var textLayout: StaticLayout
+
+ private var progressAnimator: ValueAnimator? = null
+
+ private val interp =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in)
+
+ @ColorInt private val defaultTextColor: Int
+
+ init {
+ val a = context.obtainStyledAttributes(
+ attrs,
+ R.styleable.FilterChip,
+ R.attr.filterChipStyle,
+ 0
+ )
+ outlinePaint = Paint(ANTI_ALIAS_FLAG).apply {
+ color = a.getColorOrThrow(R.styleable.FilterChip_strokeColor)
+ strokeWidth = a.getDimensionOrThrow(R.styleable.FilterChip_strokeWidth)
+ style = STROKE
+ }
+ defaultTextColor = a.getColorOrThrow(R.styleable.FilterChip_android_textColor)
+ selectedTextColor = a.getColor(R.styleable.FilterChip_selectedTextColor, 0)
+ textPaint = TextPaint(ANTI_ALIAS_FLAG).apply {
+ color = defaultTextColor
+ textSize = a.getDimensionOrThrow(R.styleable.FilterChip_android_textSize)
+ }
+ dotPaint = Paint(ANTI_ALIAS_FLAG)
+ color = a.getColor(R.styleable.FilterChip_android_color, 0)
+ clear = a.getDrawableOrThrow(R.styleable.FilterChip_clearIcon).apply {
+ setBounds(
+ -intrinsicWidth / 2, -intrinsicHeight / 2, intrinsicWidth / 2, intrinsicHeight / 2
+ )
+ }
+ touchFeedback = a.getDrawableOrThrow(R.styleable.FilterChip_foreground).apply {
+ callback = this@FilterChip
+ }
+ padding = a.getDimensionPixelSizeOrThrow(R.styleable.FilterChip_android_padding)
+ isChecked = a.getBoolean(R.styleable.FilterChip_android_checked, false)
+ showIcons = a.getBoolean(R.styleable.FilterChip_showIcons, true)
+ a.recycle()
+ clipToOutline = true
+ setOnClickListener { toggleWithAnimation() }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val nonTextWidth = (4 * padding) +
+ (2 * outlinePaint.strokeWidth).toInt() +
+ if (showIcons) clear.intrinsicWidth else 0
+ val availableTextWidth = when (MeasureSpec.getMode(widthMeasureSpec)) {
+ MeasureSpec.EXACTLY -> MeasureSpec.getSize(widthMeasureSpec) - nonTextWidth
+ MeasureSpec.AT_MOST -> MeasureSpec.getSize(widthMeasureSpec) - nonTextWidth
+ MeasureSpec.UNSPECIFIED -> Int.MAX_VALUE
+ else -> Int.MAX_VALUE
+ }
+ createLayout(availableTextWidth)
+ val w = nonTextWidth + textLayout.textWidth()
+ val h = padding + textLayout.height + padding
+ setMeasuredDimension(w, h)
+ outlineProvider = object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(0, 0, w, h, h / 2f)
+ }
+ }
+ touchFeedback.setBounds(0, 0, w, h)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ val strokeWidth = outlinePaint.strokeWidth
+ val iconRadius = clear.intrinsicWidth / 2f
+ val halfStroke = strokeWidth / 2f
+ val rounding = (height - strokeWidth) / 2f
+
+ // Outline
+ if (progress < 1f) {
+ canvas.drawRoundRect(
+ halfStroke,
+ halfStroke,
+ width - halfStroke,
+ height - halfStroke,
+ rounding,
+ rounding,
+ outlinePaint
+ )
+ }
+
+ // Tag color dot/background
+ if (showIcons) {
+ // Draws beyond bounds and relies on clipToOutline to enforce pill shape
+ val dotRadius = lerp(
+ strokeWidth + iconRadius,
+ width.toFloat(),
+ progress
+ )
+ canvas.drawCircle(strokeWidth + padding + iconRadius, height / 2f, dotRadius, dotPaint)
+ } else {
+ canvas.drawRoundRect(
+ halfStroke,
+ halfStroke,
+ width - halfStroke,
+ height - halfStroke,
+ rounding,
+ rounding,
+ dotPaint
+ )
+ }
+
+ // Text
+ val textX = if (showIcons) {
+ lerp(
+ strokeWidth + padding + clear.intrinsicWidth + padding,
+ strokeWidth + padding * 2f,
+ progress
+ )
+ } else {
+ strokeWidth + padding * 2f
+ }
+ val selectedColor = selectedTextColor
+ textPaint.color = if (selectedColor != null && selectedColor != 0 && progress > 0) {
+ ColorUtils.blendARGB(defaultTextColor, selectedColor, progress)
+ } else {
+ defaultTextColor
+ }
+ canvas.withTranslation(
+ x = textX,
+ y = (height - textLayout.height) / 2f
+ ) {
+ textLayout.draw(canvas)
+ }
+
+ // Clear icon
+ if (showIcons && progress > 0f) {
+ canvas.withTranslation(
+ x = width - strokeWidth - padding - iconRadius,
+ y = height / 2f
+ ) {
+ canvas.withScale(progress, progress) {
+ clear.draw(canvas)
+ }
+ }
+ }
+
+ // Touch feedback
+ touchFeedback.draw(canvas)
+ }
+
+ /**
+ * Starts the animation to enable/disable a filter and invokes a function when done.
+ */
+ fun animateCheckedAndInvoke(checked: Boolean, onEnd: (() -> Unit)?) {
+ val newProgress = if (checked) 1f else 0f
+ if (newProgress != progress) {
+ progressAnimator?.cancel()
+ progressAnimator = ValueAnimator.ofFloat(progress, newProgress).apply {
+ addUpdateListener {
+ progress = it.animatedValue as Float
+ }
+ doOnEnd {
+ progress = newProgress
+ onEnd?.invoke()
+ }
+ interpolator = interp
+ duration = if (checked) SELECTING_DURATION else DESELECTING_DURATION
+ start()
+ }
+ }
+ }
+
+ override fun isChecked() = progress == 1f
+
+ override fun toggle() {
+ progress = if (isChecked) 0f else 1f
+ }
+
+ override fun setChecked(checked: Boolean) {
+ progress = if (checked) 1f else 0f
+ }
+
+ fun toggleWithAnimation() {
+ setCheckedWithAnimation(!isChecked)
+ }
+
+ fun setCheckedWithAnimation(checked: Boolean) {
+ animateCheckedAndInvoke(checked, null)
+ }
+
+ override fun verifyDrawable(who: Drawable): Boolean {
+ return super.verifyDrawable(who) || who == touchFeedback
+ }
+
+ override fun drawableStateChanged() {
+ super.drawableStateChanged()
+ touchFeedback.state = drawableState
+ }
+
+ override fun jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState()
+ touchFeedback.jumpToCurrentState()
+ }
+
+ override fun drawableHotspotChanged(x: Float, y: Float) {
+ super.drawableHotspotChanged(x, y)
+ touchFeedback.setHotspot(x, y)
+ }
+
+ private fun createLayout(textWidth: Int) {
+ textLayout = if (SDK_INT >= M) {
+ StaticLayout.Builder.obtain(text, 0, text.length, textPaint, textWidth).build()
+ } else {
+ @Suppress("DEPRECATION")
+ StaticLayout(text, textPaint, textWidth, ALIGN_NORMAL, 1f, 0f, true)
+ }
+ }
+
+ private fun updateContentDescription() {
+ val desc = if (isChecked) {
+ R.string.description_filter_applied
+ } else {
+ R.string.description_filter_not_applied
+ }
+ contentDescription = resources.getString(desc, text)
+ }
+
+ /**
+ * Calculated the widest line in a [StaticLayout].
+ */
+ private fun StaticLayout.textWidth(): Int {
+ var width = 0f
+ for (i in 0 until lineCount) {
+ width = width.coerceAtLeast(getLineWidth(i))
+ }
+ return width.toInt()
+ }
+
+ interface OnCheckedChangeListener {
+ fun onCheckedChanged(chip: FilterChip, isChecked: Boolean)
+ }
+
+ companion object {
+ private const val SELECTING_DURATION = 350L
+ private const val DESELECTING_DURATION = 200L
+ }
+}
+
+fun FilterChip.onCheckedChanged(action: (FilterChip, Boolean) -> Unit) {
+ onCheckedChangeListener = object : FilterChip.OnCheckedChangeListener {
+ override fun onCheckedChanged(chip: FilterChip, isChecked: Boolean) {
+ action(chip, isChecked)
+ }
+ }
+}
diff --git a/frontendcomponent/androidcomponent/src/main/res/drawable-hdpi/ic_droidkaigi.png b/frontendcomponent/androidcomponent/src/main/res/drawable-hdpi/ic_droidkaigi.png
new file mode 100644
index 000000000..f35bfc4de
Binary files /dev/null and b/frontendcomponent/androidcomponent/src/main/res/drawable-hdpi/ic_droidkaigi.png differ
diff --git a/frontendcomponent/androidcomponent/src/main/res/drawable-xhdpi/ic_droidkaigi.png b/frontendcomponent/androidcomponent/src/main/res/drawable-xhdpi/ic_droidkaigi.png
new file mode 100644
index 000000000..55d3b61ad
Binary files /dev/null and b/frontendcomponent/androidcomponent/src/main/res/drawable-xhdpi/ic_droidkaigi.png differ
diff --git a/frontendcomponent/androidcomponent/src/main/res/drawable-xxhdpi/ic_droidkaigi.png b/frontendcomponent/androidcomponent/src/main/res/drawable-xxhdpi/ic_droidkaigi.png
new file mode 100644
index 000000000..d8e1c81d0
Binary files /dev/null and b/frontendcomponent/androidcomponent/src/main/res/drawable-xxhdpi/ic_droidkaigi.png differ
diff --git a/frontendcomponent/androidcomponent/src/main/res/drawable-xxxhdpi/ic_droidkaigi.png b/frontendcomponent/androidcomponent/src/main/res/drawable-xxxhdpi/ic_droidkaigi.png
new file mode 100644
index 000000000..422ef5683
Binary files /dev/null and b/frontendcomponent/androidcomponent/src/main/res/drawable-xxxhdpi/ic_droidkaigi.png differ
diff --git a/frontendcomponent/androidcomponent/src/main/res/drawable/tag_clear.xml b/frontendcomponent/androidcomponent/src/main/res/drawable/tag_clear.xml
new file mode 100644
index 000000000..71afac4c5
--- /dev/null
+++ b/frontendcomponent/androidcomponent/src/main/res/drawable/tag_clear.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/frontendcomponent/androidcomponent/src/main/res/values-ja/strings.xml b/frontendcomponent/androidcomponent/src/main/res/values-ja/strings.xml
index 9d80623bb..d8f13296b 100644
--- a/frontendcomponent/androidcomponent/src/main/res/values-ja/strings.xml
+++ b/frontendcomponent/androidcomponent/src/main/res/values-ja/strings.xml
@@ -6,4 +6,6 @@
Droidkaigiとは
スポンサーページ
全体アンケート
+ フィルター%1$sを適用
+ フィルター%1$sを解除
diff --git a/frontendcomponent/androidcomponent/src/main/res/values/attrs.xml b/frontendcomponent/androidcomponent/src/main/res/values/attrs.xml
new file mode 100644
index 000000000..65cae3e7d
--- /dev/null
+++ b/frontendcomponent/androidcomponent/src/main/res/values/attrs.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontendcomponent/androidcomponent/src/main/res/values/strings.xml b/frontendcomponent/androidcomponent/src/main/res/values/strings.xml
index 882ab46b4..dc1b96338 100644
--- a/frontendcomponent/androidcomponent/src/main/res/values/strings.xml
+++ b/frontendcomponent/androidcomponent/src/main/res/values/strings.xml
@@ -19,4 +19,7 @@
Search
Setting
+
+ %1$s filter applied
+ %1$s filter not applied
diff --git a/frontendcomponent/androidcomponent/src/main/res/values/styles.xml b/frontendcomponent/androidcomponent/src/main/res/values/styles.xml
index 045e125f3..dfd3ed4f0 100644
--- a/frontendcomponent/androidcomponent/src/main/res/values/styles.xml
+++ b/frontendcomponent/androidcomponent/src/main/res/values/styles.xml
@@ -1,3 +1,14 @@
+
+
diff --git a/frontendcomponent/androidcomponent/src/main/res/values/themes.xml b/frontendcomponent/androidcomponent/src/main/res/values/themes.xml
index 7b0286375..1f1e5098c 100644
--- a/frontendcomponent/androidcomponent/src/main/res/values/themes.xml
+++ b/frontendcomponent/androidcomponent/src/main/res/values/themes.xml
@@ -13,6 +13,7 @@
- true
- @android:color/transparent
- @style/SearchViewStyle
+ - @style/Widget.App.FilterChip
- @style/TextAppearance.App.Headline5
diff --git a/frontendcomponent/androidtestcomponent/build.gradle b/frontendcomponent/androidtestcomponent/build.gradle
index 0640d3aa6..b905a6bd3 100644
--- a/frontendcomponent/androidtestcomponent/build.gradle
+++ b/frontendcomponent/androidtestcomponent/build.gradle
@@ -23,6 +23,7 @@ dependencies {
}
}
repositories {
+ if (!isCi) { maven { url "https://maven-central-asia.storage-download.googleapis.com/repos/central/data/" } }
mavenCentral()
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
diff --git a/frontendcomponent/androidtestcomponent/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/component/ExampleInstrumentedTest.java b/frontendcomponent/androidtestcomponent/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/component/ExampleInstrumentedTest.java
deleted file mode 100644
index f8a05565d..000000000
--- a/frontendcomponent/androidtestcomponent/src/androidTest/java/io/github/droidkaigi/confsched2019/widget/component/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package io.github.droidkaigi.confsched2019.widget.component;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("io.github.droidkaigi.confsched2019.ui.component.test", appContext.getPackageName());
- }
-}
diff --git a/model/src/commonMain/kotlin/Filters.kt b/model/src/commonMain/kotlin/Filters.kt
index 55f4c6cf2..b4a9b371f 100644
--- a/model/src/commonMain/kotlin/Filters.kt
+++ b/model/src/commonMain/kotlin/Filters.kt
@@ -23,4 +23,8 @@ data class Filters(
}
return roomFilterOk && categoryFilterOk && langFilterOk
}
+
+ fun isFiltered(): Boolean {
+ return rooms.isNotEmpty() || categories.isNotEmpty() || langs.isNotEmpty()
+ }
}
diff --git a/model/src/commonMain/kotlin/LocaleMessage.kt b/model/src/commonMain/kotlin/LocaleMessage.kt
deleted file mode 100644
index 6c30e43c8..000000000
--- a/model/src/commonMain/kotlin/LocaleMessage.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package io.github.droidkaigi.confsched2019.model
-
-class LocaleMessage(val enMessage: String, val jaMessage: String)
-
-fun LocaleMessage.get() = if (defaultLang() != Lang.JA) enMessage else jaMessage
diff --git a/model/src/commonMain/kotlin/Session.kt b/model/src/commonMain/kotlin/Session.kt
index e69a4e4e0..b3c365190 100644
--- a/model/src/commonMain/kotlin/Session.kt
+++ b/model/src/commonMain/kotlin/Session.kt
@@ -8,7 +8,8 @@ sealed class Session(
open val dayNumber: Int,
open val startTime: DateTime,
open val endTime: DateTime,
- open val room: Room
+ open val room: Room,
+ open val isFavorited: Boolean
) {
data class SpeechSession(
override val id: String,
@@ -22,11 +23,16 @@ sealed class Session(
val language: LocaledString,
val category: Category,
val intendedAudience: String?,
+ val videoUrl: String?,
+ val slideUrl: String?,
val isInterpretationTarget: Boolean,
- val isFavorited: Boolean,
+ override val isFavorited: Boolean,
val speakers: List,
- val message: SessionMessage?
- ) : Session(id, dayNumber, startTime, endTime, room)
+ val message: LocaledString?
+ ) : Session(id, dayNumber, startTime, endTime, room, isFavorited) {
+ val hasVideo: Boolean = videoUrl.isNullOrEmpty().not()
+ val hasSlide: Boolean = slideUrl.isNullOrEmpty().not()
+ }
data class ServiceSession(
override val id: String,
@@ -35,8 +41,9 @@ sealed class Session(
override val endTime: DateTime,
val title: String,
override val room: Room,
- val sessionType: SessionType
- ) : Session(id, dayNumber, startTime, endTime, room)
+ val sessionType: SessionType,
+ override val isFavorited: Boolean
+ ) : Session(id, dayNumber, startTime, endTime, room, isFavorited)
val startDayText by lazy { startTime.format("yyyy.M.d") }
@@ -53,9 +60,9 @@ sealed class Session(
append("日")
}
append(" ")
- append(startTime.format("hh:mm"))
+ append(startTime.format("HH:mm"))
append(" - ")
- append(endTime.format("hh:mm"))
+ append(endTime.format("HH:mm"))
}
fun summary(lang: Lang) = buildString {
diff --git a/model/src/commonMain/kotlin/SessionMessage.kt b/model/src/commonMain/kotlin/SessionMessage.kt
deleted file mode 100644
index b9bd1be8c..000000000
--- a/model/src/commonMain/kotlin/SessionMessage.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package io.github.droidkaigi.confsched2019.model
-
-data class SessionMessage(
- val jaMessage: String,
- val enMessage: String
-) {
-
- fun getBodyByLang(lang: Lang): String = if (lang == Lang.JA) {
- jaMessage
- } else {
- enMessage
- }
-}
diff --git a/model/src/commonMain/kotlin/SessionType.kt b/model/src/commonMain/kotlin/SessionType.kt
index 89bd3c741..e59fbebb9 100644
--- a/model/src/commonMain/kotlin/SessionType.kt
+++ b/model/src/commonMain/kotlin/SessionType.kt
@@ -1,15 +1,15 @@
package io.github.droidkaigi.confsched2019.model
-enum class SessionType(val id: String) {
- Normal("normal"),
- WelcomeTalk("welcome_talk"),
- Reserved("reserved"),
- Codelabs("codelabs"),
- FiresideChat("fireside_chat"),
- Lunch("lunch"),
- Break("break"),
- AfterParty("after_party"),
- Unknown("unknown");
+enum class SessionType(val id: String, val isFavoritable: Boolean) {
+ Normal("normal", false),
+ WelcomeTalk("welcome_talk", false),
+ Reserved("reserved", false),
+ Codelabs("codelabs", true),
+ FiresideChat("fireside_chat", false),
+ Lunch("lunch", false),
+ Break("break", false),
+ AfterParty("after_party", false),
+ Unknown("unknown", false);
companion object {
fun of(id: String?) = values().find { it.id == id } ?: Unknown
diff --git a/model/src/jvmTest/kotlin/io/github/droidkaigi/confsched2019/model/FiltersTest.kt b/model/src/jvmTest/kotlin/io/github/droidkaigi/confsched2019/model/FiltersTest.kt
index 907478803..cd833d95c 100644
--- a/model/src/jvmTest/kotlin/io/github/droidkaigi/confsched2019/model/FiltersTest.kt
+++ b/model/src/jvmTest/kotlin/io/github/droidkaigi/confsched2019/model/FiltersTest.kt
@@ -11,7 +11,7 @@ class FiltersTest {
assertTrue { Filters().isPass(mockk()) }
}
- @Test fun isPass_WhenSpecialSession() {
+ @Test fun isPass_WhenServiceSession() {
assertTrue { Filters().isPass(mockk()) }
}
diff --git a/scripts/checkout_pr_branch b/scripts/checkout_pr_branch
new file mode 100755
index 000000000..8be069b2b
--- /dev/null
+++ b/scripts/checkout_pr_branch
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+set -eu
+set -o pipefail
+
+user_name="${CIRCLE_PROJECT_USERNAME:-DroidKaigi}"
+repo_name="${CIRCLE_PROJECT_REPONAME:-conference-app-2019}"
+
+get_pr() {
+ local -r pr_number="$1"
+
+ # DANGER_GITHUB_API_TOKEN, GITHUB_API_TOKEN, GITHUB_ACCESS_TOKEN are supported
+ local -r gh_token="${DANGER_GITHUB_API_TOKEN:-${GITHUB_API_TOKEN:-$GITHUB_ACCESS_TOKEN}}"
+
+ curl -H "Authorization: token $gh_token" \
+ "https://api.github.com/repos/$user_name/$repo_name/pulls/$pr_number"
+}
+
+get_pr_meta() {
+ cat - | jq -r '.head | .repo.full_name, .ref'
+}
+
+sync() {
+ local -r remote_name="$1"
+ local full_name= ref=
+
+ read full_name
+ read ref
+
+ git remote add "$remote_name" "git@github.com:$full_name.git" || :
+ git fetch "$remote_name" "$ref"
+ git checkout -b "${remote_name}_${ref}" "$remote_name/$ref"
+}
+
+sync_forked_pr() {
+ local -r pr_number="$1"
+
+ get_pr "$pr_number" | get_pr_meta | sync "pr_$pr_number"
+}
+
+sync_forked_pr "$1"
\ No newline at end of file
diff --git a/scripts/danger/Dangerfile.assertions b/scripts/danger/Dangerfile.assertions
index f2854e2e5..8c3206480 100644
--- a/scripts/danger/Dangerfile.assertions
+++ b/scripts/danger/Dangerfile.assertions
@@ -24,9 +24,9 @@ begin
if File.exists?(ENV.fetch('MERGED_JUNIT_RESULT_FILE'))
# junit report
junit.parse ENV.fetch('MERGED_JUNIT_RESULT_FILE')
- fail("Failed tests found") unless junit.failures.empty?
+ fail("Failed tests found. Please fix test failures.") unless junit.failures.empty?
else
- fail("Test results not found")
+ fail("The previous test step failed. Please check the CI log.")
end
end
@@ -44,6 +44,6 @@ end
# LGTM
begin
if status_report[:errors].length.zero? && status_report[:warnings].length.zero?
- markdown("ALL GREEN :100:")
+ markdown("Asserted successfully. :100:")
end
end
diff --git a/scripts/fetch_droidkaigi_master b/scripts/fetch_droidkaigi_master
new file mode 100755
index 000000000..c8a0eb3ee
--- /dev/null
+++ b/scripts/fetch_droidkaigi_master
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+set -eu
+set -o pipefail
+
+user_name="${CIRCLE_PROJECT_USERNAME:-DroidKaigi}"
+repo_name="${CIRCLE_PROJECT_REPONAME:-conference-app-2019}"
+
+sync_origin_master() {
+ git remote add "$user_name" "git@github.com:$user_name/$repo_name.git"
+ git fetch "$user_name" master
+
+ cat<