├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libraries │ └── stickyheaders │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── gradle.properties │ │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ ├── android │ │ └── support │ │ │ └── v7 │ │ │ └── widget │ │ │ └── RecyclerViewHelper.java │ │ └── com │ │ └── eowise │ │ └── recyclerview │ │ └── stickyheaders │ │ ├── DrawOrder.java │ │ ├── HeaderStore.java │ │ ├── OnHeaderClickListener.java │ │ ├── StickyHeadersAdapter.java │ │ ├── StickyHeadersBuilder.java │ │ ├── StickyHeadersItemDecoration.java │ │ └── StickyHeadersTouchListener.java ├── proguard-rules.pro └── src │ ├── droidconBerlin │ ├── AndroidManifest.xml │ ├── java │ │ └── de │ │ │ └── droidcon │ │ │ ├── CrashlyticsTimberTree.kt │ │ │ ├── DroidconBerlinApp.kt │ │ │ ├── dagger │ │ │ └── DroidconBerlinNetworkModule.kt │ │ │ └── model │ │ │ └── backend │ │ │ ├── DroidconBerlinBackend.java │ │ │ ├── DroidconBerlinBackendScheduleAdapter.kt │ │ │ ├── DroidconBerlinLink.java │ │ │ ├── DroidconBerlinLocation.java │ │ │ ├── DroidconBerlinSession.java │ │ │ ├── DroidconBerlinSpeaker.java │ │ │ └── InstantIsoTypeConverter.kt │ └── res │ │ ├── values-de │ │ └── strings.xml │ │ └── values │ │ └── strings.xml │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── openconference │ │ │ ├── Navigator.kt │ │ │ ├── OpenConfApp.kt │ │ │ ├── PhoneNavigator.kt │ │ │ ├── dagger │ │ │ ├── ApplicationComponent.kt │ │ │ ├── ApplicationContext.kt │ │ │ ├── ApplicationModule.kt │ │ │ ├── DaoModule.kt │ │ │ ├── ErrorMessageModule.kt │ │ │ ├── LoadersModule.kt │ │ │ ├── NetworkModule.kt │ │ │ ├── PicassoModule.kt │ │ │ ├── ScheduleModule.kt │ │ │ ├── SchedulingModule.kt │ │ │ └── SearchModule.kt │ │ │ ├── main │ │ │ ├── MainActivityComponent.kt │ │ │ ├── MainActivityModule.kt │ │ │ ├── MainActivityScope.java │ │ │ ├── MainScreensPagerAdapter.kt │ │ │ └── ViewPagerMainActivity.kt │ │ │ ├── model │ │ │ ├── Location.kt │ │ │ ├── ScheduleDataAwareObservableFactory.kt │ │ │ ├── Session.kt │ │ │ ├── SessionsLoader.kt │ │ │ ├── Speaker.kt │ │ │ ├── SpeakerNotFoundException.kt │ │ │ ├── SpeakersLoader.kt │ │ │ ├── backend │ │ │ │ └── schedule │ │ │ │ │ ├── BackendScheduleAdapter.kt │ │ │ │ │ ├── BackendScheduleResponse.kt │ │ │ │ │ ├── ScheduleDataStateDeterminer.kt │ │ │ │ │ ├── ScheduleSync.kt │ │ │ │ │ └── TimebaseScheduleDataStateDeterminer.kt │ │ │ ├── database │ │ │ │ ├── EndTimeInstantCursorAdapter.kt │ │ │ │ ├── InstantParcelableTypeAdapter.kt │ │ │ │ ├── LocationAutoValue.kt │ │ │ │ ├── SessionAutoValue.kt │ │ │ │ ├── SessionDateTimeComparator.kt │ │ │ │ ├── SpeakerAutoValue.kt │ │ │ │ ├── StartTimeInstantCursorAdapter.kt │ │ │ │ └── dao │ │ │ │ │ ├── LocationDao.kt │ │ │ │ │ ├── LocationDaoSqlite.kt │ │ │ │ │ ├── SessionDao.kt │ │ │ │ │ ├── SessionDaoSqlite.kt │ │ │ │ │ ├── SessionJoinResult.kt │ │ │ │ │ ├── SpeakerDao.kt │ │ │ │ │ └── SpeakerDaoSqlite.kt │ │ │ ├── errormessage │ │ │ │ ├── DefaultErrorMessageDeterminer.kt │ │ │ │ └── ErrorMessageDeterminer.kt │ │ │ ├── notification │ │ │ │ ├── DefaultNotificationScheduler.kt │ │ │ │ ├── NotificationBroadcastReceiver.kt │ │ │ │ ├── NotificationBuilderIntentService.kt │ │ │ │ ├── NotificationScheduler.kt │ │ │ │ └── NotificationSchedulerCommand.kt │ │ │ ├── screen │ │ │ │ ├── MyScheduleScreen.kt │ │ │ │ ├── Screen.kt │ │ │ │ ├── Screens.kt │ │ │ │ ├── SessionsScreen.kt │ │ │ │ ├── SpeakersScreen.kt │ │ │ │ └── TwitterScreen.kt │ │ │ └── search │ │ │ │ ├── DefaultSearchEngine.kt │ │ │ │ ├── SearchEngine.kt │ │ │ │ ├── SearchSource.kt │ │ │ │ ├── SearchableItem.kt │ │ │ │ ├── SessionSearchableItem.kt │ │ │ │ ├── SpeakerSearchableItem.kt │ │ │ │ └── source │ │ │ │ ├── LocalStorageSessionsSearchSource.kt │ │ │ │ └── LocalStorageSpeakersSearchSource.kt │ │ │ ├── myschedule │ │ │ ├── MyScheduleComponent.kt │ │ │ ├── MyScheduleFragment.kt │ │ │ ├── MyScheduleModule.kt │ │ │ ├── MySchedulePresenter.kt │ │ │ ├── MyScheduleScope.kt │ │ │ ├── MyScheduleView.kt │ │ │ └── presentationmodel │ │ │ │ ├── MySchedulePresentationModel.kt │ │ │ │ └── MySchedulePresentationModelTransformer.kt │ │ │ ├── search │ │ │ ├── SearchActivity.kt │ │ │ ├── SearchComponent.kt │ │ │ ├── SearchPresenter.kt │ │ │ ├── SearchScope.kt │ │ │ ├── SearchView.kt │ │ │ ├── SearchViewModule.kt │ │ │ ├── SearchViewState.kt │ │ │ ├── SessionItemAdapterDelegate.kt │ │ │ └── SpeakerAdapterDelegate.kt │ │ │ ├── sessiondetails │ │ │ ├── DetailsDateAdapterDelegate.kt │ │ │ ├── DetailsDescriptionAdapterDelegate.kt │ │ │ ├── DetailsLocationAdapterDelegate.kt │ │ │ ├── DetailsSeparatorAdapterDelegate.kt │ │ │ ├── DetailsSpeakerAdapterDelegate.kt │ │ │ ├── SessionDetailsActivity.kt │ │ │ ├── SessionDetailsComponent.kt │ │ │ ├── SessionDetailsFragment.kt │ │ │ ├── SessionDetailsModule.kt │ │ │ ├── SessionDetailsPresenter.kt │ │ │ ├── SessionDetailsScope.kt │ │ │ ├── SessionDetailsView.kt │ │ │ └── presentationmodel │ │ │ │ ├── SessionDetail.kt │ │ │ │ ├── SessionDetailItem.kt │ │ │ │ └── SessionDetailsPresentationModelTransformer.kt │ │ │ ├── sessions │ │ │ ├── SessionDateStickyHeaderAdapter.kt │ │ │ ├── SessionItemAdapterDelegate.kt │ │ │ ├── SessionsComponent.kt │ │ │ ├── SessionsFragment.kt │ │ │ ├── SessionsModule.kt │ │ │ ├── SessionsPresenter.kt │ │ │ ├── SessionsScope.java │ │ │ ├── SessionsView.kt │ │ │ └── presentationmodel │ │ │ │ ├── GroupableSession.kt │ │ │ │ ├── PhoneSessionPresentationModelTransformer.kt │ │ │ │ ├── SessionPresentationModel.kt │ │ │ │ └── SessionPresentationModelTransformer.kt │ │ │ ├── speakerdetails │ │ │ ├── SpeakerDetailsActivity.kt │ │ │ ├── SpeakerDetailsBioAdapterDelegate.kt │ │ │ ├── SpeakerDetailsComponent.kt │ │ │ ├── SpeakerDetailsFragment.kt │ │ │ ├── SpeakerDetailsJobInfoAdapterDelegate.kt │ │ │ ├── SpeakerDetailsLinkAdapterDelegate.kt │ │ │ ├── SpeakerDetailsModule.kt │ │ │ ├── SpeakerDetailsPresenter.kt │ │ │ ├── SpeakerDetailsScope.kt │ │ │ ├── SpeakerDetailsView.kt │ │ │ ├── SpeakerDetailsViewHolder.kt │ │ │ └── presentationmodel │ │ │ │ ├── SpeakerDetail.kt │ │ │ │ ├── SpeakerDetailsItem.kt │ │ │ │ ├── SpeakerDetailsPresentationModelTransformer.kt │ │ │ │ └── SpeakerDetailsSessionDelegate.kt │ │ │ ├── speakers │ │ │ ├── SpeakerAdapterDelegate.kt │ │ │ ├── SpeakersComponent.kt │ │ │ ├── SpeakersFragment.kt │ │ │ ├── SpeakersModule.kt │ │ │ ├── SpeakersPresenter.kt │ │ │ ├── SpeakersScope.java │ │ │ └── SpeakersView.kt │ │ │ ├── splash │ │ │ ├── SplashActivity.kt │ │ │ ├── SplashComponent.kt │ │ │ ├── SplashPresenter.kt │ │ │ ├── SplashScope.kt │ │ │ └── SplashView.kt │ │ │ ├── twitter │ │ │ ├── TwitterTimelineFragment.kt │ │ │ ├── TwitterTimelinePresenter.kt │ │ │ └── TwitterTimelineView.kt │ │ │ └── util │ │ │ ├── ActivityExtensions.kt │ │ │ ├── ContentValuesExtensions.kt │ │ │ ├── FragmentExtensions.kt │ │ │ ├── FragmentScope.kt │ │ │ ├── RxPresenter.kt │ │ │ ├── SchedulerTransformer.kt │ │ │ ├── StableIdListAdapter.kt │ │ │ ├── ViewExtensions.kt │ │ │ ├── lce │ │ │ ├── LceAnimatable.kt │ │ │ ├── LceView.kt │ │ │ └── LceViewState.kt │ │ │ ├── picasso │ │ │ ├── CircleImageTransformation.java │ │ │ └── PicassoScrollListener.kt │ │ │ └── ui │ │ │ └── SquaredImageView.kt │ └── res │ │ ├── anim │ │ ├── fade_in.xml │ │ └── fade_out.xml │ │ ├── animator │ │ ├── add_to_schedule_line_1_move.xml │ │ ├── add_to_schedule_line_1_trim.xml │ │ ├── add_to_schedule_line_2_move.xml │ │ ├── add_to_schedule_line_2_untrim.xml │ │ ├── add_to_schedule_rotate.xml │ │ ├── remove_from_schedule_line_1_move.xml │ │ ├── remove_from_schedule_line_1_untrim.xml │ │ ├── remove_from_schedule_line_2_move.xml │ │ ├── remove_from_schedule_line_2_trim.xml │ │ └── remove_from_schedule_rotate.xml │ │ ├── drawable-hdpi │ │ ├── ic_launcher.png │ │ └── ic_notification_small.png │ │ ├── drawable-mdpi │ │ ├── ic_launcher.png │ │ └── ic_notification_small.png │ │ ├── drawable-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_notification_small.png │ │ ├── drawable-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_notification_small.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_notification_small.png │ │ ├── drawable │ │ ├── avd_add_to_schedule.xml │ │ ├── avd_remove_from_schedule.xml │ │ ├── ic_add_to_schedule_anim.xml │ │ ├── ic_arrow_back.xml │ │ ├── ic_arrow_back_white.xml │ │ ├── ic_bio.xml │ │ ├── ic_done.xml │ │ ├── ic_empty.xml │ │ ├── ic_error.xml │ │ ├── ic_link.xml │ │ ├── ic_location.xml │ │ ├── ic_my_schedule.xml │ │ ├── ic_person.xml │ │ ├── ic_remove_from_schedule_anim.xml │ │ ├── ic_search.xml │ │ ├── ic_search_big.xml │ │ ├── ic_sessions.xml │ │ ├── ic_sessions_details.xml │ │ ├── ic_speakers.xml │ │ ├── ic_time.xml │ │ ├── ic_twitter.xml │ │ ├── ic_work.xml │ │ ├── rounded_profile_pic_placeholder.xml │ │ ├── separator_line.xml │ │ ├── splash_logo.xml │ │ └── viewpager_separator.xml │ │ ├── layout-land │ │ └── fragment_speaker_details.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_search.xml │ │ ├── activity_session_details.xml │ │ ├── activity_splash.xml │ │ ├── fragment_my_schedule.xml │ │ ├── fragment_session_details.xml │ │ ├── fragment_sessions.xml │ │ ├── fragment_speaker_details.xml │ │ ├── fragment_twitter_timeline.xml │ │ ├── item_details_icon_link_text.xml │ │ ├── item_details_icon_text.xml │ │ ├── item_search_session.xml │ │ ├── item_search_speaker.xml │ │ ├── item_session.xml │ │ ├── item_session_date_sticky_header.xml │ │ ├── item_session_details_date.xml │ │ ├── item_session_details_description.xml │ │ ├── item_session_details_location.xml │ │ ├── item_session_details_separator.xml │ │ ├── item_session_details_speaker.xml │ │ ├── item_speaker.xml │ │ ├── item_speaker_details_jobinfo.xml │ │ ├── view_error.xml │ │ └── view_loading.xml │ │ ├── menu │ │ └── main_menu.xml │ │ ├── raw │ │ └── notices.xml │ │ ├── transition │ │ └── auto.xml │ │ ├── values-de │ │ └── strings.xml │ │ ├── values-land │ │ └── integers.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── fractions.xml │ │ ├── integers.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── playground │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── openconfernce │ │ ├── MockNetworkModule.kt │ │ ├── PlaygroundApp.kt │ │ └── mock │ │ └── MockSchedulerAdapterStub.kt │ └── test │ └── java │ └── com │ └── openconference │ ├── TestApplication.kt │ └── model │ ├── ScheduleDataAwareObservableFactoryTest.kt │ ├── backend │ └── schedule │ │ ├── ScheduleSyncTest.kt │ │ └── TimebaseScheduleDataStateDeterminerTest.kt │ ├── database │ ├── InstantParcelableTypeAdapterTest.kt │ └── dao │ │ ├── LocationDaoSqliteTest.kt │ │ ├── SessionDaoSqliteTest.kt │ │ ├── SortByStartDateTimeTest.kt │ │ └── SpeakerDaoSqliteTest.kt │ └── notification │ └── NotificationSchedulerCommandTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | bin/ 5 | gen/ 6 | classes/ 7 | gen-external-apklibs/ 8 | 9 | # Eclipse project files 10 | .classpath 11 | .project 12 | .metadata 13 | .settings 14 | 15 | # IntelliJ files 16 | .idea 17 | *.iml 18 | *.iml 19 | *.ipr 20 | *.iws 21 | **/.idea/workspace.xml 22 | **/.idea/tasks.xml 23 | **/.idea/datasources.xml 24 | **/.idea/dataSources.ids 25 | **/.idea/gradle.xml 26 | **/.idea/misc.xml 27 | **/.idea/modules.xml 28 | **/.idea/libraries 29 | **/.idea/dictionaries 30 | 31 | # OSX files 32 | .DS_Store 33 | 34 | # Windows files 35 | Thumbs.db 36 | 37 | # vi swap files 38 | *.swp 39 | 40 | # backup files 41 | *.bak 42 | 43 | 44 | # Files for the Dalvik VM 45 | *.dex 46 | 47 | # Java class files 48 | *.class 49 | 50 | # Generated files 51 | bin/ 52 | gen/ 53 | 54 | # Gradle files 55 | .gradle/ 56 | build/ 57 | .gradle 58 | 59 | #maven files 60 | target/ 61 | /null 62 | 63 | # Local configuration file (sdk path, etc) 64 | 65 | local.properties 66 | 67 | # Proguard folder generated by Eclipse 68 | proguard/ 69 | 70 | #Log Files 71 | *.log 72 | 73 | 74 | #crashlytics 75 | **/com_crashlytics_export_strings.xml 76 | 77 | #infer 78 | infer-out/ 79 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | android: 4 | components: 5 | - platform-tools 6 | - tools 7 | - android-23 8 | - build-tools-23.0.3 9 | - extra-android-support 10 | - extra-android-m2repository 11 | 12 | licenses: 13 | - 'android-sdk-license-.+' 14 | 15 | jdk: 16 | - oraclejdk8 17 | 18 | install: true 19 | 20 | script: ./gradlew clean build --info --stacktrace 21 | 22 | branches: 23 | except: 24 | - gh-pages 25 | 26 | notifications: 27 | email: false 28 | 29 | sudo: false 30 | 31 | cache: 32 | directories: 33 | - $HOME/.gradle 34 | - $HOME/.m2 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenConference 2 | 3 | This repository offers an android app that can be used by any conference to display information like sessions, speaker etc. about the conference in a native android app 4 | 5 | Originally this app has been build for droidcon Berlin, but the app can be styled and configured freely so that it can be used by other conferences as well. 6 | 7 | [![Build Status](https://travis-ci.org/OpenConference/OpenConference-android.svg?branch=master)](https://travis-ci.org/OpenConference/OpenConference-android) 8 | 9 | # For your conference 10 | 11 | The documentation is not available yet, but basically there are two ways to create your own android app for your conference (based on this source code): 12 | 13 | 1. Create a product flavor for your conference: Android's gradle plugin allows us to have multiple product flavors. If you use product flavors, you can benefit from some features like CI and testing. If you need help, please create an issue here on github. 14 | 15 | 2. Fork the whole repository and do your own thing. 16 | 17 | 18 | # Connect to your backend 19 | This app is has been built with changeable, modular and components in mind. We use dagger 2 for dependency injection and so you can override almost all components to match your needs. The most important one is: [BackendScheduleAdapter](https://github.com/OpenConference/OpenConference-android/blob/master/app/src/main/java/com/openconference/model/backend/schedule/BackendScheduleAdapter.kt) where you define how to load data from your backend (i.e. by using Retrofit). 20 | 21 | Custom styling can be applied by overriding default xml styles and layouts files. 22 | 23 | 24 | # License 25 | ``` 26 | Copyright 2016 Open Conference 27 | 28 | Licensed under the Apache License, Version 2.0 (the "License"); 29 | you may not use this file except in compliance with the License. 30 | You may obtain a copy of the License at 31 | 32 | http://www.apache.org/licenses/LICENSE-2.0 33 | 34 | Unless required by applicable law or agreed to in writing, software 35 | distributed under the License is distributed on an "AS IS" BASIS, 36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | See the License for the specific language governing permissions and 38 | limitations under the License. 39 | ``` 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | bin/ 5 | gen/ 6 | classes/ 7 | gen-external-apklibs/ 8 | 9 | # Eclipse project files 10 | .classpath 11 | .project 12 | .metadata 13 | .settings 14 | 15 | # IntelliJ files 16 | .idea 17 | *.iml 18 | *.iml 19 | *.ipr 20 | *.iws 21 | **/.idea/workspace.xml 22 | **/.idea/tasks.xml 23 | **/.idea/datasources.xml 24 | **/.idea/dataSources.ids 25 | **/.idea/gradle.xml 26 | **/.idea/misc.xml 27 | **/.idea/modules.xml 28 | **/.idea/libraries 29 | **/.idea/dictionaries 30 | 31 | # OSX files 32 | .DS_Store 33 | 34 | # Windows files 35 | Thumbs.db 36 | 37 | # vi swap files 38 | *.swp 39 | 40 | # backup files 41 | *.bak 42 | 43 | 44 | # Files for the Dalvik VM 45 | *.dex 46 | 47 | # Java class files 48 | *.class 49 | 50 | # Generated files 51 | bin/ 52 | gen/ 53 | 54 | # Gradle files 55 | .gradle/ 56 | build/ 57 | .gradle 58 | 59 | #maven files 60 | target/ 61 | /null 62 | 63 | # Local configuration file (sdk path, etc) 64 | 65 | local.properties 66 | 67 | # Proguard folder generated by Eclipse 68 | proguard/ 69 | 70 | #Log Files 71 | *.log 72 | 73 | #infer 74 | infer-out/ 75 | -------------------------------------------------------------------------------- /app/libraries/stickyheaders/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | bin/ 5 | gen/ 6 | classes/ 7 | gen-external-apklibs/ 8 | 9 | # Eclipse project files 10 | .classpath 11 | .project 12 | .metadata 13 | .settings 14 | 15 | # IntelliJ files 16 | .idea 17 | *.iml 18 | *.iml 19 | *.ipr 20 | *.iws 21 | **/.idea/workspace.xml 22 | **/.idea/tasks.xml 23 | **/.idea/datasources.xml 24 | **/.idea/dataSources.ids 25 | **/.idea/gradle.xml 26 | **/.idea/misc.xml 27 | **/.idea/modules.xml 28 | **/.idea/libraries 29 | **/.idea/dictionaries 30 | 31 | # OSX files 32 | .DS_Store 33 | 34 | # Windows files 35 | Thumbs.db 36 | 37 | # vi swap files 38 | *.swp 39 | 40 | # backup files 41 | *.bak 42 | 43 | 44 | # Files for the Dalvik VM 45 | *.dex 46 | 47 | # Java class files 48 | *.class 49 | 50 | # Generated files 51 | bin/ 52 | gen/ 53 | 54 | # Gradle files 55 | .gradle/ 56 | build/ 57 | .gradle 58 | 59 | #maven files 60 | target/ 61 | /null 62 | 63 | # Local configuration file (sdk path, etc) 64 | 65 | local.properties 66 | 67 | # Proguard folder generated by Eclipse 68 | proguard/ 69 | 70 | #Log Files 71 | *.log 72 | 73 | #infer 74 | infer-out/ 75 | -------------------------------------------------------------------------------- /app/libraries/stickyheaders/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | dependencies { 4 | compile libs.recyclerView 5 | } 6 | 7 | android { 8 | 9 | compileOptions.incremental = false 10 | 11 | compileSdkVersion compileSdk 12 | buildToolsVersion buildTools 13 | 14 | defaultConfig { 15 | minSdkVersion minSdk 16 | targetSdkVersion targetSdk 17 | } 18 | } -------------------------------------------------------------------------------- /app/libraries/stickyheaders/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Sticky headers library 2 | POM_ARTIFACT_ID=recyclerview-stickyheaders 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /app/libraries/stickyheaders/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/libraries/stickyheaders/src/main/java/android/support/v7/widget/RecyclerViewHelper.java: -------------------------------------------------------------------------------- 1 | package android.support.v7.widget; 2 | 3 | /** 4 | * Created by aurel on 19/10/14. 5 | */ 6 | public class RecyclerViewHelper { 7 | 8 | public static int convertPreLayoutPositionToPostLayout(RecyclerView recyclerView, int position) { 9 | return recyclerView.mRecycler.convertPreLayoutPositionToPostLayout(position); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/libraries/stickyheaders/src/main/java/com/eowise/recyclerview/stickyheaders/DrawOrder.java: -------------------------------------------------------------------------------- 1 | package com.eowise.recyclerview.stickyheaders; 2 | 3 | /** 4 | * Created by aurel on 10/11/14. 5 | */ 6 | public enum DrawOrder { 7 | OverItems, 8 | UnderItems 9 | } 10 | -------------------------------------------------------------------------------- /app/libraries/stickyheaders/src/main/java/com/eowise/recyclerview/stickyheaders/OnHeaderClickListener.java: -------------------------------------------------------------------------------- 1 | package com.eowise.recyclerview.stickyheaders; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by aurel on 08/11/14. 7 | */ 8 | public interface OnHeaderClickListener { 9 | void onHeaderClick(View header, long headerId); 10 | } 11 | -------------------------------------------------------------------------------- /app/libraries/stickyheaders/src/main/java/com/eowise/recyclerview/stickyheaders/StickyHeadersAdapter.java: -------------------------------------------------------------------------------- 1 | package com.eowise.recyclerview.stickyheaders; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.ViewGroup; 5 | 6 | public interface StickyHeadersAdapter { 7 | 8 | /** 9 | * Create a header {@link android.support.v7.widget.RecyclerView.ViewHolder ViewHolder} witch encapsulate the header view 10 | * You can either create a View manually or inflate it from an XML layout file. 11 | * 12 | * @param parent the parent {@link android.view.ViewGroup ViewGroup} 13 | * @return the newly created {@link android.support.v7.widget.RecyclerView.ViewHolder ViewHolder} 14 | */ 15 | HeaderViewHolder onCreateViewHolder(ViewGroup parent); 16 | 17 | /** 18 | * Binds a header view according to the current item data. 19 | * 20 | * @param headerViewHolder the ViewHolder containing the view to bind 21 | * @param position the current item position 22 | */ 23 | void onBindViewHolder(HeaderViewHolder headerViewHolder, int position); 24 | 25 | /** 26 | * Get the header id associated with the specified item position. 27 | * 28 | * @param position the current item position 29 | * @return the header id for the current item 30 | */ 31 | long getHeaderId(int position); 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/hannes/android-sdks/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | -dontwarn com.squareup.okhttp.** -------------------------------------------------------------------------------- /app/src/droidconBerlin/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/droidconBerlin/java/de/droidcon/CrashlyticsTimberTree.kt: -------------------------------------------------------------------------------- 1 | package de.droidcon 2 | 3 | import com.crashlytics.android.Crashlytics 4 | import timber.log.Timber 5 | 6 | /** 7 | * 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | class CrashlyticsTimberTree : Timber.Tree() { 12 | override fun log(priority: Int, tag: String?, message: String?, t: Throwable?) { 13 | 14 | if (message != null) { 15 | Crashlytics.log(message) 16 | } 17 | 18 | if (t != null) { 19 | Crashlytics.logException(t) 20 | } 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/droidconBerlin/java/de/droidcon/DroidconBerlinApp.kt: -------------------------------------------------------------------------------- 1 | package de.droidcon 2 | 3 | import com.crashlytics.android.Crashlytics 4 | import com.openconference.OpenConfApp 5 | import com.openconference.dagger.DaggerApplicationComponent 6 | import com.twitter.sdk.android.Twitter 7 | import com.twitter.sdk.android.core.TwitterAuthConfig 8 | import de.droidcon.dagger.DroidconBerlinNetworkModule 9 | import io.fabric.sdk.android.Fabric 10 | import timber.log.Timber 11 | 12 | /** 13 | * 14 | * 15 | * @author Hannes Dorfmann 16 | */ 17 | class DroidconBerlinApp : OpenConfApp() { 18 | 19 | 20 | override fun onCreate() { 21 | 22 | val config = TwitterAuthConfig("API-KEY", 23 | "API-SECRET") 24 | Fabric.with(this, Crashlytics(), Twitter(config)); 25 | 26 | 27 | super.onCreate() 28 | } 29 | 30 | override fun buildApplicationComponent(): DaggerApplicationComponent.Builder { 31 | return super.buildApplicationComponent().networkModule(DroidconBerlinNetworkModule(this)) 32 | } 33 | 34 | override fun plantProductionTimberTree() { 35 | super.plantProductionTimberTree() 36 | Timber.plant(CrashlyticsTimberTree()) 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/droidconBerlin/java/de/droidcon/dagger/DroidconBerlinNetworkModule.kt: -------------------------------------------------------------------------------- 1 | package de.droidcon.dagger 2 | 3 | import android.content.Context 4 | import com.github.aurae.retrofit2.LoganSquareConverterFactory 5 | import com.openconference.dagger.NetworkModule 6 | import com.openconference.model.backend.schedule.BackendScheduleAdapter 7 | import de.droidcon.model.backend.DroidconBerlinBackend 8 | import de.droidcon.model.backend.DroidconBerlinBackendScheduleAdapter 9 | import okhttp3.Cache 10 | import okhttp3.OkHttpClient 11 | import retrofit2.Retrofit 12 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory 13 | 14 | class DroidconBerlinNetworkModule(context: Context) : NetworkModule(context) { 15 | 16 | private val retrofit: Retrofit 17 | private val okHttp: OkHttpClient 18 | private val backendAdapter: BackendScheduleAdapter 19 | private val backend: DroidconBerlinBackend 20 | 21 | init { 22 | okHttp = OkHttpClient.Builder().cache(Cache(context.cacheDir, 48 * 1024 * 1024)) 23 | .build() 24 | 25 | retrofit = Retrofit.Builder() 26 | .client(okHttp) 27 | .baseUrl("http://droidcon.de/rest/") 28 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 29 | .addConverterFactory(LoganSquareConverterFactory.create()) 30 | .build() 31 | 32 | 33 | backend = retrofit.create(DroidconBerlinBackend::class.java) 34 | 35 | backendAdapter = DroidconBerlinBackendScheduleAdapter(backend) 36 | } 37 | 38 | override fun provideOkHttp(): OkHttpClient = okHttp 39 | 40 | override fun provideBackendAdapter(): BackendScheduleAdapter = backendAdapter 41 | } -------------------------------------------------------------------------------- /app/src/droidconBerlin/java/de/droidcon/model/backend/DroidconBerlinBackend.java: -------------------------------------------------------------------------------- 1 | package de.droidcon.model.backend; 2 | 3 | import java.util.List; 4 | import retrofit2.http.GET; 5 | import rx.Observable; 6 | 7 | /** 8 | * @author Hannes Dorfmann 9 | */ 10 | public interface DroidconBerlinBackend { 11 | 12 | @GET("sessions.json") Observable> getSessions(); 13 | 14 | @GET("speakers.json") Observable> getSpeakers(); 15 | 16 | @GET("rooms.json") Observable> getLocations(); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/droidconBerlin/java/de/droidcon/model/backend/DroidconBerlinBackendScheduleAdapter.kt: -------------------------------------------------------------------------------- 1 | package de.droidcon.model.backend 2 | 3 | import com.openconference.model.Location 4 | import com.openconference.model.Session 5 | import com.openconference.model.Speaker 6 | import com.openconference.model.backend.schedule.BackendScheduleAdapter 7 | import com.openconference.model.backend.schedule.BackendScheduleResponse 8 | import rx.Observable 9 | 10 | /** 11 | * [BackendScheduleAdapter] for droidcon Berlin backend 12 | * 13 | * @author Hannes Dorfmann 14 | */ 15 | class DroidconBerlinBackendScheduleAdapter(private val backend: DroidconBerlinBackend) : BackendScheduleAdapter { 16 | 17 | override fun getSpeakers(): Observable> = 18 | backend.getSpeakers().map { 19 | BackendScheduleResponse.dataChanged(it as List) 20 | } 21 | 22 | override fun getLocations(): Observable> 23 | = backend.getLocations().map { 24 | BackendScheduleResponse.dataChanged(it as List) 25 | } 26 | 27 | override fun getSessions(): Observable> = 28 | backend.getSessions().map { 29 | BackendScheduleResponse.dataChanged(it as List) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/droidconBerlin/java/de/droidcon/model/backend/DroidconBerlinLink.java: -------------------------------------------------------------------------------- 1 | package de.droidcon.model.backend; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import com.bluelinelabs.logansquare.annotation.JsonField; 6 | import com.bluelinelabs.logansquare.annotation.JsonObject; 7 | 8 | /** 9 | * @author Hannes Dorfmann 10 | */ 11 | @JsonObject 12 | public class DroidconBerlinLink implements Parcelable{ 13 | @JsonField String url; 14 | 15 | @Override public int describeContents() { 16 | return 0; 17 | } 18 | 19 | @Override public void writeToParcel(Parcel dest, int flags) { 20 | dest.writeString(this.url); 21 | } 22 | 23 | public DroidconBerlinLink() { 24 | } 25 | 26 | protected DroidconBerlinLink(Parcel in) { 27 | this.url = in.readString(); 28 | } 29 | 30 | public static final Creator CREATOR = new Creator() { 31 | @Override public DroidconBerlinLink createFromParcel(Parcel source) { 32 | return new DroidconBerlinLink(source); 33 | } 34 | 35 | @Override public DroidconBerlinLink[] newArray(int size) { 36 | return new DroidconBerlinLink[size]; 37 | } 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /app/src/droidconBerlin/java/de/droidcon/model/backend/DroidconBerlinLocation.java: -------------------------------------------------------------------------------- 1 | package de.droidcon.model.backend; 2 | 3 | import android.os.Parcel; 4 | import com.bluelinelabs.logansquare.annotation.JsonField; 5 | import com.bluelinelabs.logansquare.annotation.JsonObject; 6 | import com.openconference.model.Location; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | /** 10 | * @author Hannes Dorfmann 11 | */ 12 | @JsonObject public class DroidconBerlinLocation implements Location { 13 | 14 | @JsonField(name = "title") String name; 15 | @JsonField(name = "nid") String id; 16 | 17 | @NotNull @Override public String name() { 18 | return name; 19 | } 20 | 21 | @NotNull @Override public String id() { 22 | return id; 23 | } 24 | 25 | @Override public int describeContents() { 26 | return 0; 27 | } 28 | 29 | @Override public void writeToParcel(Parcel dest, int flags) { 30 | dest.writeString(this.name); 31 | dest.writeString(this.id); 32 | } 33 | 34 | public DroidconBerlinLocation() { 35 | } 36 | 37 | protected DroidconBerlinLocation(Parcel in) { 38 | this.name = in.readString(); 39 | this.id = in.readString(); 40 | } 41 | 42 | public static final Creator CREATOR = 43 | new Creator() { 44 | @Override public DroidconBerlinLocation createFromParcel(Parcel source) { 45 | return new DroidconBerlinLocation(source); 46 | } 47 | 48 | @Override public DroidconBerlinLocation[] newArray(int size) { 49 | return new DroidconBerlinLocation[size]; 50 | } 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /app/src/droidconBerlin/java/de/droidcon/model/backend/InstantIsoTypeConverter.kt: -------------------------------------------------------------------------------- 1 | package de.droidcon.model.backend 2 | 3 | import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter 4 | import org.threeten.bp.Instant 5 | import org.threeten.bp.format.DateTimeFormatter 6 | 7 | /** 8 | * Logan Square json parser type converter for Dates ins ISO format 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | class InstantIsoTypeConverter : StringBasedTypeConverter() { 13 | 14 | override fun convertToString(o: Instant?): String? = if (o == null) { 15 | null 16 | } else { 17 | val timeFormatter = DateTimeFormatter.ISO_DATE_TIME; 18 | timeFormatter.format(o) 19 | 20 | } 21 | 22 | override fun getFromString(str: String?): Instant? = if (str == null) { 23 | null 24 | } else { 25 | val timeFormatter = DateTimeFormatter.ISO_DATE_TIME; 26 | val accessor = timeFormatter.parse(str); 27 | Instant.from(accessor) 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/droidconBerlin/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | droidcon Berlin 4 | -------------------------------------------------------------------------------- /app/src/droidconBerlin/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | droidcon Berlin 4 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/Navigator.kt: -------------------------------------------------------------------------------- 1 | package com.openconference 2 | 3 | import com.openconference.model.Session 4 | import com.openconference.model.Speaker 5 | 6 | /** 7 | * 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface Navigator { 12 | 13 | fun showSessions() 14 | 15 | fun showSpeakers() 16 | 17 | fun showSessionDetails(session: Session) 18 | 19 | fun showMySchedule() 20 | 21 | fun showSpeakerDetails(speaker: Speaker) 22 | 23 | fun showSearch() 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/OpenConfApp.kt: -------------------------------------------------------------------------------- 1 | package com.openconference 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.jakewharton.threetenabp.AndroidThreeTen 6 | import com.openconference.dagger.* 7 | import timber.log.Timber 8 | 9 | /** 10 | * Custom application mainly to integrate dagger 11 | * 12 | * @author Hannes Dorfmann 13 | */ 14 | open class OpenConfApp : Application() { 15 | 16 | private lateinit var applicationComponent: ApplicationComponent 17 | 18 | override fun onCreate() { 19 | super.onCreate() 20 | AndroidThreeTen.init(this) 21 | if (BuildConfig.DEBUG) { 22 | plantDebugTimberTree() 23 | } else { 24 | plantProductionTimberTree() 25 | } 26 | applicationComponent = buildApplicationComponent().build() 27 | } 28 | 29 | companion object { 30 | fun getApplicationComponent(context: Context): ApplicationComponent { 31 | val app = context.applicationContext as OpenConfApp 32 | return app.applicationComponent 33 | } 34 | } 35 | 36 | open fun plantDebugTimberTree(){ 37 | Timber.plant(Timber.DebugTree()) 38 | } 39 | 40 | open fun buildApplicationComponent(): DaggerApplicationComponent.Builder { 41 | 42 | return DaggerApplicationComponent.builder() 43 | .daoModule(DaoModule(this)) 44 | .loadersModule(LoadersModule()) 45 | .schedulingModule(SchedulingModule()) 46 | .networkModule(NetworkModule(this)) 47 | .applicationModule(ApplicationModule(this)) 48 | .scheduleModule(ScheduleModule(this)) 49 | .picassoModule(PicassoModule(this)) 50 | } 51 | 52 | /** 53 | * Override this method to use a Timber Tree for production with crashreporting software like crashlytics 54 | */ 55 | open fun plantProductionTimberTree() { 56 | 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/PhoneNavigator.kt: -------------------------------------------------------------------------------- 1 | package com.openconference 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import com.openconference.main.ViewPagerMainActivity 6 | import com.openconference.model.Session 7 | import com.openconference.model.Speaker 8 | import com.openconference.model.screen.SessionsScreen 9 | import com.openconference.search.SearchActivity 10 | import com.openconference.sessiondetails.SessionDetailsActivity 11 | import com.openconference.sessiondetails.SpeakerDetailsActivity 12 | 13 | /** 14 | * A Navigator that is responsible to navigate between the different screens 15 | * 16 | * @author Hannes Dorfmann 17 | */ 18 | // TODO Tablet Navigator 19 | class PhoneNavigator(private val activity: Activity) : Navigator { 20 | 21 | override fun showSessionDetails(session: Session) { 22 | val i = SessionDetailsActivity.buildIntent(activity, session) 23 | activity.startActivity(i) 24 | } 25 | 26 | override fun showMySchedule() { 27 | throw UnsupportedOperationException() 28 | } 29 | 30 | override fun showSpeakerDetails(speaker: Speaker) { 31 | val i = Intent(activity, SpeakerDetailsActivity::class.java) 32 | i.putExtra(SpeakerDetailsActivity.KEY_SPEAKER, speaker) 33 | activity.startActivity(i) 34 | } 35 | 36 | override fun showSessions() = if (activity is ViewPagerMainActivity) { 37 | activity.jumpToScreen({ it is SessionsScreen }) 38 | } else throw UnsupportedOperationException("Oops, something in Navigation is not setup properly") 39 | 40 | override fun showSpeakers() { 41 | throw UnsupportedOperationException() 42 | } 43 | 44 | override fun showSearch() { 45 | val i = Intent(activity, SearchActivity::class.java) 46 | activity.startActivity(i) 47 | activity.overridePendingTransition(R.anim.fade_in, R.anim.fade_out) 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/ApplicationComponent.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import android.content.Context 4 | import com.openconference.model.ScheduleDataAwareObservableFactory 5 | import com.openconference.model.SessionsLoader 6 | import com.openconference.model.SpeakersLoader 7 | import com.openconference.model.database.dao.SessionDao 8 | import com.openconference.model.errormessage.ErrorMessageDeterminer 9 | import com.openconference.model.notification.NotificationScheduler 10 | import com.openconference.model.search.SearchEngine 11 | import com.openconference.util.SchedulerTransformer 12 | import com.squareup.picasso.Picasso 13 | import dagger.Component 14 | import javax.inject.Singleton 15 | 16 | /** 17 | * A Dagger component providing application wide dependencies 18 | * @author Hannes Dorfmann 19 | */ 20 | @Component(modules = arrayOf(ApplicationModule::class)) 21 | interface ApplicationComponent { 22 | 23 | fun schedulerTransformer(): SchedulerTransformer 24 | 25 | fun scheduleDataAwareObservableFactory(): ScheduleDataAwareObservableFactory 26 | 27 | fun sessionLoader(): SessionsLoader 28 | 29 | fun speakersLoader(): SpeakersLoader 30 | 31 | fun sessionDao(): SessionDao 32 | 33 | fun errorMessageDeterminer(): ErrorMessageDeterminer 34 | 35 | fun picasso(): Picasso 36 | 37 | fun notificationScheduler(): NotificationScheduler 38 | 39 | fun searchEngine(): SearchEngine 40 | 41 | @ApplicationContext 42 | fun applicationContext(): Context 43 | 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/ApplicationContext.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import javax.inject.Named 4 | 5 | /** 6 | * This indicates that the annotated Context is an ApplicationContext 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | @Named 11 | annotation class ApplicationContext -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/ApplicationModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import android.content.Context 4 | import com.openconference.model.backend.schedule.BackendScheduleAdapter 5 | import com.openconference.model.backend.schedule.ScheduleSync 6 | import com.openconference.model.database.dao.LocationDao 7 | import com.openconference.model.database.dao.SessionDao 8 | import com.openconference.model.database.dao.SpeakerDao 9 | import com.openconference.model.notification.NotificationScheduler 10 | import dagger.Module 11 | import dagger.Provides 12 | import javax.inject.Singleton 13 | 14 | /** 15 | * 16 | * 17 | * @author Hannes Dorfmann 18 | */ 19 | @Module(includes = arrayOf( 20 | DaoModule::class, 21 | LoadersModule::class, 22 | SchedulingModule::class, 23 | NetworkModule::class, 24 | ScheduleModule::class, 25 | ErrorMessageModule::class, 26 | PicassoModule::class, 27 | SearchModule::class) 28 | ) 29 | class ApplicationModule(c: Context) { 30 | 31 | private val applicationContext = c.applicationContext 32 | 33 | @Provides 34 | @Singleton 35 | fun provideScheduleSync(backend: BackendScheduleAdapter, 36 | notificationScheduler: NotificationScheduler, 37 | sessionDao: SessionDao, 38 | speakerDao: SpeakerDao, 39 | locationDao: LocationDao) = ScheduleSync(backend, notificationScheduler, sessionDao, 40 | speakerDao, locationDao) 41 | 42 | @Provides 43 | @Singleton 44 | @ApplicationContext 45 | fun provideApplicationContext() = applicationContext 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/DaoModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import android.content.Context 4 | import com.hannesdorfmann.sqlbrite.dao.DaoManager 5 | import com.openconference.model.database.dao.* 6 | import dagger.Module 7 | import dagger.Provides 8 | import javax.inject.Singleton 9 | 10 | /** 11 | * Module providing DAO's 12 | * 13 | * @author Hannes Dorfmann 14 | */ 15 | @Module 16 | @Singleton 17 | class DaoModule(context: Context) { 18 | 19 | 20 | private val sessionDao: SessionDao 21 | private val speakerDao: SpeakerDao 22 | private val locationDao: LocationDao 23 | 24 | init { 25 | 26 | // DAO's 27 | sessionDao = SessionDaoSqlite() 28 | speakerDao = SpeakerDaoSqlite() 29 | locationDao = LocationDaoSqlite() 30 | DaoManager.with(context.applicationContext) 31 | .add(sessionDao) 32 | .add(speakerDao) 33 | .add(locationDao) 34 | .version(1) 35 | .databaseName("schedule.db") 36 | .build() 37 | } 38 | 39 | @Provides 40 | @Singleton 41 | fun provideSessionDao() = sessionDao 42 | 43 | @Provides 44 | @Singleton 45 | fun provideSpeakerDao() = speakerDao 46 | 47 | @Provides 48 | @Singleton 49 | fun provideLocationDao() = locationDao 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/ErrorMessageModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import com.openconference.model.errormessage.DefaultErrorMessageDeterminer 4 | import com.openconference.model.errormessage.ErrorMessageDeterminer 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Singleton 8 | 9 | /** 10 | * Dagger module for Error Message 11 | * 12 | * @author Hannes Dorfmann 13 | */ 14 | @Module 15 | class ErrorMessageModule { 16 | 17 | @Provides 18 | @Singleton 19 | fun provideErrorMessageDeterminer() : ErrorMessageDeterminer = DefaultErrorMessageDeterminer() 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/LoadersModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import com.openconference.model.* 4 | import com.openconference.model.backend.schedule.ScheduleDataStateDeterminer 5 | import com.openconference.model.backend.schedule.ScheduleSync 6 | import com.openconference.model.database.dao.SessionDao 7 | import com.openconference.model.database.dao.SpeakerDao 8 | import com.openconference.model.notification.NotificationScheduler 9 | import dagger.Module 10 | import dagger.Provides 11 | import rx.schedulers.Schedulers 12 | import javax.inject.Singleton 13 | 14 | /** 15 | * 16 | * 17 | * @author Hannes Dorfmann 18 | */ 19 | @Module 20 | class LoadersModule { 21 | 22 | @Provides 23 | @Singleton 24 | fun providesScheduleDataAwareObservableFactory(scheduleSync: ScheduleSync, 25 | scheduleDataStateDeterminer: ScheduleDataStateDeterminer) = 26 | ScheduleDataAwareObservableFactory(scheduleSync, scheduleDataStateDeterminer, Schedulers.io()) 27 | 28 | @Provides 29 | @Singleton 30 | fun providesSessionLoader(factory: ScheduleDataAwareObservableFactory, 31 | sessionDao: SessionDao, 32 | notificationScheduler: NotificationScheduler): SessionsLoader = LocalStorageSessionsLoader( 33 | factory, sessionDao, notificationScheduler) 34 | 35 | @Provides 36 | @Singleton 37 | fun provideSpeakersLoader(factory: ScheduleDataAwareObservableFactory, 38 | speakerDao: SpeakerDao): SpeakersLoader = LocalStorageSpeakersLoader(factory, speakerDao) 39 | 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import android.content.Context 4 | import com.openconference.model.backend.schedule.BackendScheduleAdapter 5 | import dagger.Module 6 | import dagger.Provides 7 | import okhttp3.Cache 8 | import okhttp3.OkHttpClient 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * @author Hannes Dorfmann 13 | */ 14 | @Module 15 | open class NetworkModule(c: Context) { 16 | 17 | protected val context: Context 18 | protected val okHttpClient: OkHttpClient 19 | 20 | init { 21 | context = c.applicationContext 22 | okHttpClient = OkHttpClient.Builder() 23 | .cache(Cache(context.cacheDir, 48 * 1024 * 1024)) 24 | .build() 25 | } 26 | 27 | @Provides 28 | @Singleton 29 | open fun provideOkHttp() = okHttpClient 30 | 31 | @Provides 32 | @Singleton 33 | open fun provideBackendAdapter(): BackendScheduleAdapter = 34 | throw UnsupportedOperationException( 35 | "Every Build Flavor / conference app has to provide his own BackendAdpater") 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/PicassoModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import android.content.Context 4 | import com.jakewharton.picasso.OkHttp3Downloader 5 | import com.squareup.picasso.Picasso 6 | import dagger.Module 7 | import dagger.Provides 8 | import okhttp3.OkHttpClient 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * 13 | * 14 | * @author Hannes Dorfmann 15 | */ 16 | @Module 17 | class PicassoModule(c: Context) { 18 | 19 | private val context = c.applicationContext 20 | 21 | @Singleton 22 | @Provides 23 | fun providePicasso(okhttp: OkHttpClient) = 24 | Picasso.Builder(context).downloader(OkHttp3Downloader(okhttp)).build() 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/ScheduleModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import android.content.Context 4 | import com.openconference.model.backend.schedule.ScheduleDataStateDeterminer 5 | import com.openconference.model.backend.schedule.TimebaseScheduleDataStateDeterminer 6 | import com.openconference.model.notification.DefaultNotificationScheduler 7 | import com.openconference.model.notification.NotificationScheduler 8 | import dagger.Module 9 | import dagger.Provides 10 | import javax.inject.Singleton 11 | 12 | /** 13 | * 14 | * Provides some things related to the conferences schedule 15 | * 16 | * @author Hannes Dorfmann 17 | */ 18 | @Module 19 | open class ScheduleModule(c: Context) { 20 | 21 | protected val context: Context = c.applicationContext 22 | 23 | @Provides 24 | @Singleton 25 | fun provideScheduleDataStateDeterminer(): ScheduleDataStateDeterminer { 26 | val sharedPrefs = context.getSharedPreferences("TimebaseScheduleDeterminer", 27 | Context.MODE_PRIVATE) 28 | return TimebaseScheduleDataStateDeterminer(sharedPrefs) 29 | } 30 | 31 | @Provides 32 | @Singleton 33 | fun provideNotificationScheduler(): NotificationScheduler = DefaultNotificationScheduler(context) 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/SchedulingModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import com.openconference.util.DefaultSchedulerTransformer 4 | import com.openconference.util.SchedulerTransformer 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Singleton 8 | 9 | /** 10 | * Dagger Module providing Threading and Scheduling related stuff 11 | * @author Hannes Dorfmann 12 | */ 13 | @Module 14 | class SchedulingModule() { 15 | 16 | @Provides 17 | @Singleton 18 | fun provideSchedulerTransformer() :SchedulerTransformer = DefaultSchedulerTransformer() 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/dagger/SearchModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.dagger 2 | 3 | import android.content.Context 4 | import com.openconference.R 5 | import com.openconference.model.ScheduleDataAwareObservableFactory 6 | import com.openconference.model.database.dao.SessionDao 7 | import com.openconference.model.database.dao.SpeakerDao 8 | import com.openconference.model.search.DefaultSearchEngine 9 | import com.openconference.model.search.SearchEngine 10 | import com.openconference.model.search.SearchSource 11 | import com.openconference.model.search.source.LocalStorageSessionsSearchSource 12 | import com.openconference.model.search.source.LocalStorageSpeakersSearchSource 13 | import com.openconference.sessions.presentationmodel.PhoneSessionPresentationModelTransformer 14 | import dagger.Module 15 | import dagger.Provides 16 | import javax.inject.Singleton 17 | 18 | /** 19 | * 20 | * 21 | * @author Hannes Dorfmann 22 | */ 23 | @Module 24 | class SearchModule { 25 | 26 | 27 | data class SearchSources(val sources: List) 28 | 29 | @Provides 30 | @Singleton 31 | fun provideSearchSources( 32 | @ApplicationContext context: Context, 33 | awareObservableFactory: ScheduleDataAwareObservableFactory, 34 | sessionDao: SessionDao, speakerDao: SpeakerDao) = SearchSources( 35 | listOf( 36 | LocalStorageSessionsSearchSource(awareObservableFactory, sessionDao, 37 | PhoneSessionPresentationModelTransformer(context.getString( 38 | R.string.sessions_sticky_date_format))), 39 | LocalStorageSpeakersSearchSource(awareObservableFactory, speakerDao))) 40 | 41 | @Provides 42 | @Singleton 43 | fun provideSearchEngine(searchSources: SearchSources): SearchEngine = DefaultSearchEngine( 44 | searchSources.sources) 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/main/MainActivityComponent.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.main 2 | 3 | import com.openconference.dagger.ApplicationComponent 4 | import dagger.Component 5 | 6 | /** 7 | * Dagger Component for 8 | * @author Hannes Dorfmann 9 | */ 10 | @Component ( 11 | modules = arrayOf(MainActivityModule::class), dependencies = arrayOf(ApplicationComponent::class)) 12 | @MainActivityScope 13 | interface MainActivityComponent { 14 | fun inject(activity: ViewPagerMainActivity) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/main/MainActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.main 2 | 3 | import android.app.Activity 4 | import com.openconference.Navigator 5 | import com.openconference.PhoneNavigator 6 | import com.openconference.model.screen.* 7 | import dagger.Module 8 | import dagger.Provides 9 | 10 | /** 11 | * 12 | * 13 | * @author Hannes Dorfmann 14 | */ 15 | @Module 16 | class MainActivityModule(private val activity: Activity) { 17 | 18 | @Provides 19 | @MainActivityScope 20 | fun provideScreens(): Screens = Screens(listOf(SessionsScreen(), MyScheduleScreen(), 21 | SpeakersScreen(), TwitterScreen())) 22 | 23 | @Provides 24 | @MainActivityScope 25 | fun provideNavigator(): Navigator = PhoneNavigator(activity) 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/main/MainActivityScope.java: -------------------------------------------------------------------------------- 1 | package com.openconference.main; 2 | 3 | import javax.inject.Scope; 4 | 5 | /** 6 | * @author Hannes Dorfmann 7 | */ 8 | @Scope public @interface MainActivityScope { 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/main/MainScreensPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.main 2 | 3 | import android.graphics.Rect 4 | import android.support.v4.app.Fragment 5 | import android.support.v4.app.FragmentStatePagerAdapter 6 | import android.support.v4.content.ContextCompat 7 | import android.support.v7.app.AppCompatActivity 8 | import android.text.Spannable 9 | import android.text.SpannableString 10 | import android.text.style.ImageSpan 11 | import com.openconference.model.screen.* 12 | import com.openconference.sessions.MyScheduleFragmentBuilder 13 | import com.openconference.sessions.SessionsFragmentBuilder 14 | import com.openconference.sessions.SpeakersFragmentBuilder 15 | import com.openconference.twitter.TwitterTimelineFragmentBuilder 16 | 17 | /** 18 | * ViewPagerAdapter for MainScreens 19 | * @author Hannes Dorfmann 20 | */ 21 | open class MainScreensPagerAdapter(private val activity: AppCompatActivity, private val screens: List) : FragmentStatePagerAdapter( 22 | activity.supportFragmentManager) { 23 | 24 | 25 | override fun getItem(position: Int): Fragment = when (screens[position]) { 26 | is SessionsScreen -> SessionsFragmentBuilder().build() 27 | is MyScheduleScreen -> MyScheduleFragmentBuilder().build() 28 | is SpeakersScreen -> SpeakersFragmentBuilder().build() 29 | is TwitterScreen -> TwitterTimelineFragmentBuilder().build() 30 | else -> throw IllegalArgumentException("Unknown type for screen at position $position") 31 | } 32 | 33 | override fun getCount(): Int = screens.size 34 | 35 | override fun getPageTitle(position: Int): CharSequence? { 36 | 37 | val image = ContextCompat.getDrawable(activity, screens[position].iconRes()) 38 | image.bounds = Rect(0, 0, image.intrinsicWidth, image.intrinsicHeight) 39 | val sb = SpannableString(" ") // TODO tablet layout 40 | val imageSpan = ImageSpan(image, ImageSpan.ALIGN_BOTTOM) 41 | sb.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) 42 | return sb 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/Location.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model 2 | 3 | import android.os.Parcelable 4 | import com.openconference.model.database.dao.LocationDaoSqlite 5 | import com.gabrielittner.auto.value.cursor.ColumnName 6 | 7 | /** 8 | * Represents a location where a session takes place 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | interface Location : Parcelable { 13 | 14 | /** 15 | * The id of the location 16 | */ 17 | fun id(): String 18 | 19 | /** 20 | * The name of the location 21 | */ 22 | fun name(): String 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/Session.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model 2 | 3 | import android.os.Parcelable 4 | import android.support.annotation.NonNull 5 | import android.support.annotation.Nullable 6 | import org.threeten.bp.Instant 7 | 8 | /** 9 | * 10 | * Contains the description of a Session 11 | * @author Hannes Dorfmann 12 | */ 13 | interface Session : Parcelable { 14 | 15 | /** 16 | * The session id 17 | */ 18 | @NonNull 19 | fun id(): String 20 | 21 | /** 22 | * The title of the sessions 23 | */ 24 | @Nullable 25 | fun title(): String? 26 | 27 | /** 28 | * The description of the speaker 29 | */ 30 | @Nullable 31 | fun description(): String? 32 | 33 | /** 34 | * Optional tags for this session 35 | */ 36 | @Nullable 37 | fun tags(): String? 38 | 39 | /** 40 | * The location id 41 | */ 42 | @Nullable fun locationId(): String? 43 | 44 | /** 45 | * The location name 46 | */ 47 | @Nullable fun locationName(): String? 48 | 49 | /** 50 | * Start date / time 51 | */ 52 | @Nullable 53 | fun startTime(): Instant? 54 | 55 | /** 56 | * End date / time 57 | */ 58 | @Nullable 59 | fun endTime(): Instant? 60 | 61 | @Nullable 62 | fun speakers(): List 63 | 64 | /** 65 | * Specifies whether or not this seesion has been marked by the user of the app as his favorite 66 | * which means it will be display in the users personal schedule. 67 | */ 68 | @NonNull 69 | fun favorite(): Boolean 70 | 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/Speaker.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model 2 | 3 | import android.os.Parcelable 4 | import android.support.annotation.NonNull 5 | import android.support.annotation.Nullable 6 | 7 | /** 8 | * Represents an Speaker 9 | * @author Hannes Dorfmann 10 | */ 11 | interface Speaker : Parcelable { 12 | 13 | @NonNull 14 | fun id(): String 15 | 16 | /** 17 | * The full name of the speaker 18 | */ 19 | @NonNull 20 | fun name(): String 21 | 22 | /** 23 | * The bio / info about the speaker 24 | */ 25 | @Nullable 26 | fun info(): String ? 27 | 28 | /** 29 | * The company's name the speaker is working for 30 | */ 31 | @Nullable 32 | fun company(): String? 33 | 34 | /** 35 | * The job title / role in the company 36 | */ 37 | @Nullable 38 | fun jobTitle(): String? 39 | 40 | /** 41 | * The first link i.e. link to twitter profile of the speaker 42 | */ 43 | @Nullable 44 | fun link1(): String? 45 | 46 | /** 47 | * The second link i.e. link to Google+ profile 48 | */ 49 | @Nullable 50 | fun link2(): String ? 51 | 52 | /** 53 | * The third link i.e. to personal website / blog 54 | */ 55 | @Nullable 56 | fun link3(): String? 57 | 58 | /** 59 | * The url to the profile picture of this speaker 60 | */ 61 | @Nullable 62 | fun profilePic(): String? 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/SpeakerNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model 2 | 3 | /** 4 | * Indicates that a certain speaker has not been found. 5 | * 6 | * @author Hannes Dorfmann 7 | */ 8 | class SpeakerNotFoundException(msg: String) : Exception(msg) -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/SpeakersLoader.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model 2 | 3 | import com.openconference.model.database.dao.SpeakerDao 4 | import rx.Observable 5 | 6 | /** 7 | * Responsible to load Speakers 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface SpeakersLoader { 12 | 13 | /** 14 | * Get a list of all speakers 15 | */ 16 | fun allSpeakers(): Observable> 17 | 18 | fun getSpeaker(id: String): Observable 19 | } 20 | 21 | /** 22 | * A [SpeakersLoader] that uses the speakers from local database 23 | */ 24 | class LocalStorageSpeakersLoader(private val scheduleDataAwareObservableFactory: ScheduleDataAwareObservableFactory, private val speakerDao: SpeakerDao) : SpeakersLoader { 25 | 26 | override fun allSpeakers(): Observable> = scheduleDataAwareObservableFactory.create( 27 | speakerDao.getSpeakers()) 28 | 29 | override fun getSpeaker(id: String): Observable = 30 | scheduleDataAwareObservableFactory.create( 31 | speakerDao.getSpeaker(id)).map { 32 | if (it == null) throw SpeakerNotFoundException( 33 | "Speaker with the id = $id has not been found in database") 34 | else it 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/backend/schedule/BackendScheduleAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.backend.schedule 2 | 3 | import com.openconference.model.Location 4 | import com.openconference.model.Session 5 | import com.openconference.model.Speaker 6 | import rx.Observable 7 | 8 | /** 9 | * API to communicate with the backend. This is where you have to communicate with 10 | * your conferences backend 11 | * @author Hannes Dorfmann 12 | */ 13 | interface BackendScheduleAdapter { 14 | 15 | /** 16 | * Get a list of all Speakers 17 | */ 18 | fun getSpeakers(): Observable> 19 | 20 | /** 21 | * Get a list of all locations where sessions will be heldt 22 | */ 23 | fun getLocations(): Observable> 24 | 25 | /** 26 | * Get list of all sessions 27 | */ 28 | fun getSessions(): Observable> 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/backend/schedule/BackendScheduleResponse.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.backend.schedule 2 | 3 | /** 4 | * This data structure will be retuned from 5 | * 6 | * @author Hannes Dorfmann 7 | */ 8 | data class BackendScheduleResponse private constructor(val isNewerDataAvailable: Boolean, val data: List) { 9 | 10 | companion object { 11 | fun nothingChanged() = BackendScheduleResponse(false, emptyList()) 12 | 13 | fun dataChanged(data: List) = BackendScheduleResponse(true, data) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/backend/schedule/ScheduleDataStateDeterminer.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.backend.schedule 2 | 3 | import rx.Observable 4 | 5 | /** 6 | * Determine the state of the schedule. 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | interface ScheduleDataStateDeterminer { 11 | 12 | /** 13 | * Enum representing whether or not [ScheduleSync] should running or not 14 | */ 15 | enum class ScheduleDataState { 16 | 17 | /** 18 | * Syncing has to be executed right now, before continue with the rest of your data flow 19 | */ 20 | NO_DATA, 21 | /** 22 | * Schedule data is available. That data might not be up-to-date (stale data) but can be used 23 | * in the mean time i.e. to display the schedule in the UI, while starting a refresh in the 24 | * background 25 | */ 26 | RUN_BACKGROUND_SYNC, 27 | 28 | /** 29 | * Schedule data is up to date. Running sync is not needed. 30 | */ 31 | UP_TO_DATE 32 | } 33 | 34 | 35 | /** 36 | * Determine the current state of the schedule sync data 37 | */ 38 | fun getScheduleSyncDataState(): Observable 39 | 40 | /** 41 | * Marks that the schedule has been run successfully 42 | */ 43 | fun markScheduleSyncedSuccessful(): Observable 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/backend/schedule/TimebaseScheduleDataStateDeterminer.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.backend.schedule 2 | 3 | import android.content.SharedPreferences 4 | import org.threeten.bp.Instant 5 | import rx.Observable 6 | 7 | /** 8 | * This [TimebaseScheduleDataStateDeterminer] basically says that every two hours a sync will run in background 9 | * @author Hannes Dorfmann 10 | */ 11 | class TimebaseScheduleDataStateDeterminer(private val sharedPrefs: SharedPreferences, private val runAfter: Long = 2 * 3600 * 1000) : ScheduleDataStateDeterminer { 12 | 13 | companion object { 14 | val KEY_LAST_SYNC = "lastSync" 15 | val KEY_RUN_AT_LEAST_ONCE = "atLeastOnce" 16 | } 17 | 18 | override fun getScheduleSyncDataState(): Observable = 19 | Observable.fromCallable { 20 | val atLeastOnce = sharedPrefs.getBoolean(KEY_RUN_AT_LEAST_ONCE, false) 21 | 22 | if (!atLeastOnce) { 23 | ScheduleDataStateDeterminer.ScheduleDataState.NO_DATA 24 | } else { 25 | val lastSyncMS = sharedPrefs.getLong(KEY_LAST_SYNC, 0) 26 | val lastSync = Instant.ofEpochMilli(lastSyncMS) 27 | val currentTime = currentTime() 28 | val expiresAt = lastSync.plusMillis(runAfter) 29 | if (expiresAt.isAfter(currentTime)) { 30 | ScheduleDataStateDeterminer.ScheduleDataState.UP_TO_DATE 31 | } else { 32 | ScheduleDataStateDeterminer.ScheduleDataState.RUN_BACKGROUND_SYNC 33 | } 34 | } 35 | 36 | } 37 | 38 | override fun markScheduleSyncedSuccessful(): Observable = 39 | Observable.fromCallable { 40 | sharedPrefs.edit() 41 | .putBoolean(KEY_RUN_AT_LEAST_ONCE, true) 42 | .putLong(KEY_LAST_SYNC, currentTime().toEpochMilli()) 43 | .commit() 44 | } 45 | 46 | fun currentTime() = Instant.now() 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/database/EndTimeInstantCursorAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.database 2 | 3 | import android.database.Cursor 4 | import com.openconference.model.database.dao.SessionDaoSqlite 5 | import org.threeten.bp.Instant 6 | 7 | /** 8 | * A auto-value-cursor CursorAdapter 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | class EndTimeInstantCursorAdapter { 13 | 14 | companion object { 15 | @JvmStatic 16 | fun createFromCursor(c: Cursor): Instant? { 17 | val index = c.getColumnIndex(SessionDaoSqlite.COL_END_TIME) 18 | return if (index >= 0) { 19 | if (c.isNull(index)) { 20 | null 21 | } else { 22 | Instant.ofEpochMilli(c.getLong(index)) 23 | } 24 | } else null 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/database/InstantParcelableTypeAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.database 2 | 3 | import android.os.Parcel 4 | import com.ryanharter.auto.value.parcel.TypeAdapter 5 | import org.threeten.bp.Instant 6 | 7 | /** 8 | * A TypeAdapter for Auto-Value parcelable plugin 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | class InstantParcelableTypeAdapter : TypeAdapter { 13 | 14 | private val NULL_VALUE = -1L; 15 | 16 | override fun toParcel(value: Instant?, dest: Parcel) { 17 | if (value == null) 18 | dest.writeLong(NULL_VALUE) 19 | else 20 | dest.writeLong(value.toEpochMilli()) 21 | } 22 | 23 | override fun fromParcel(parcel: Parcel): Instant? { 24 | val ms = parcel.readLong() 25 | return if (ms == NULL_VALUE) 26 | null 27 | else 28 | Instant.ofEpochMilli(ms) 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/database/LocationAutoValue.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.database 2 | 3 | import android.content.ContentValues 4 | import android.database.Cursor 5 | import com.openconference.model.Location 6 | import com.openconference.model.database.dao.LocationDaoSqlite 7 | import com.gabrielittner.auto.value.cursor.ColumnName 8 | import com.google.auto.value.AutoValue 9 | import rx.functions.Func1 10 | 11 | /** 12 | * A [Location] implementation with AutoValue 13 | * 14 | * @author Hannes Dorfmann 15 | */ 16 | @AutoValue 17 | abstract class LocationAutoValue : Location { 18 | 19 | @ColumnName(LocationDaoSqlite.COL_ID) 20 | override abstract fun id(): String 21 | 22 | @ColumnName(LocationDaoSqlite.COL_NAME) 23 | override abstract fun name(): String 24 | 25 | abstract fun toContentValues(): ContentValues 26 | 27 | companion object { 28 | @JvmStatic 29 | fun mapper(): Func1 = 30 | AutoValue_LocationAutoValue.MAPPER 31 | 32 | fun create(id: String, name: String): LocationAutoValue = AutoValue_LocationAutoValue(id, name) 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/database/SessionDateTimeComparator.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.database 2 | 3 | import com.openconference.model.Session 4 | import java.util.* 5 | 6 | /** 7 | * 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | class SessionDateTimeComparator : Comparator { 12 | 13 | override fun compare(lhs: Session, rhs: Session): Int { 14 | 15 | if (lhs.startTime() == null && rhs.startTime() == null) { 16 | return 0 17 | } 18 | 19 | if (lhs.startTime() == null && rhs.startTime() != null){ 20 | return 1 21 | } 22 | 23 | if (lhs.startTime() != null && rhs.startTime() == null){ 24 | return -1 25 | } 26 | 27 | 28 | val dateComparisonResult = lhs.startTime()!!.compareTo(rhs.startTime()) 29 | if (dateComparisonResult != 0){ 30 | return dateComparisonResult 31 | } 32 | 33 | val lhsLocationName = lhs.locationName() 34 | val rhsLocationName = rhs.locationName() 35 | 36 | if (lhsLocationName == null && rhsLocationName == null){ 37 | return 0 38 | } 39 | 40 | 41 | if (lhsLocationName == null && rhsLocationName != null){ 42 | return 1 43 | } 44 | 45 | if (lhsLocationName != null && rhsLocationName == null){ 46 | return -1 47 | } 48 | 49 | return lhsLocationName!!.compareTo(rhsLocationName!!) 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/database/StartTimeInstantCursorAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.database 2 | 3 | import android.database.Cursor 4 | import com.openconference.model.database.dao.SessionDaoSqlite 5 | import org.threeten.bp.Instant 6 | 7 | /** 8 | * A auto-value-cursor CursorAdapter 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | class StartTimeInstantCursorAdapter { 13 | 14 | companion object { 15 | @JvmStatic 16 | fun createFromCursor(c: Cursor): Instant? { 17 | val index = c.getColumnIndex(SessionDaoSqlite.COL_START_TIME) 18 | return if (index >= 0) { 19 | if (c.isNull(index)) { 20 | null 21 | } else { 22 | Instant.ofEpochMilli(c.getLong(index)) 23 | } 24 | } else null 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/database/dao/LocationDao.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.database.dao 2 | 3 | import com.openconference.model.Location 4 | import rx.Observable 5 | 6 | /** 7 | * Data-Access-Object for [com.droidcon.model.Location] 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface LocationDao { 12 | 13 | /** 14 | * Get all locations 15 | */ 16 | fun getLocations(): Observable> 17 | 18 | /** 19 | * Get a location by id 20 | */ 21 | fun getById(id: String): Observable 22 | 23 | /** 24 | * Insert or update a location 25 | */ 26 | fun insertOrUpdate(id: String, name: String): Observable 27 | 28 | /** 29 | * Removes a location 30 | */ 31 | fun remove(id: String): Observable 32 | 33 | /** 34 | * Removes all locations 35 | */ 36 | fun removeAll(): Observable 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/database/dao/LocationDaoSqlite.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.database.dao 2 | 3 | import android.database.sqlite.SQLiteDatabase 4 | import com.openconference.model.Location 5 | import com.openconference.model.database.LocationAutoValue 6 | import com.hannesdorfmann.sqlbrite.dao.Dao 7 | import rx.Observable 8 | 9 | /** 10 | * DAO implementation that uses SqlBrite 11 | * 12 | * @author Hannes Dorfmann 13 | */ 14 | open class LocationDaoSqlite : LocationDao, Dao() { 15 | 16 | companion object { 17 | const val TABLE = "Location" 18 | const val COL_ID = "id" 19 | const val COL_NAME = "name" 20 | } 21 | 22 | 23 | override fun createTable(database: SQLiteDatabase) { 24 | CREATE_TABLE(TABLE, 25 | "$COL_ID VARCHAR(20) PRIMARY KEY", 26 | "$COL_NAME TEXT NOT NULL") 27 | .execute(database) 28 | 29 | } 30 | 31 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { 32 | 33 | } 34 | 35 | override fun getLocations(): Observable> = 36 | query( 37 | SELECT(COL_ID, COL_NAME).FROM(TABLE) 38 | ).run() 39 | .mapToList(LocationAutoValue.mapper()) 40 | .map { it as List } 41 | 42 | override fun getById(id: String): Observable { 43 | throw UnsupportedOperationException() 44 | } 45 | 46 | override fun insertOrUpdate(id: String, name: String): Observable 47 | = insert(TABLE, LocationAutoValue.create(id, name).toContentValues(), 48 | SQLiteDatabase.CONFLICT_REPLACE) 49 | 50 | override fun remove(id: String): Observable = delete(TABLE, "$COL_ID = ?", id) 51 | 52 | override fun removeAll(): Observable = delete(TABLE) 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/database/dao/SessionDao.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.database.dao 2 | 3 | import com.openconference.model.Session 4 | import com.squareup.sqlbrite.BriteDatabase 5 | import rx.Observable 6 | 7 | /** 8 | * Data-Access-Object for [com.droidcon.model.Session] 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | interface SessionDao { 13 | 14 | /** 15 | * Get the list with all sessions 16 | */ 17 | fun getSessions(): Observable> 18 | 19 | /** 20 | * Get a single session by his id 21 | */ 22 | fun getById(id: String): Observable 23 | 24 | /** 25 | * Insert or update a Session 26 | */ 27 | fun insertOrUpdate(session: Session, favorite: Boolean): Observable 28 | 29 | /** 30 | * Removes a session 31 | * @return Observable with the number of deleted Items (should be 1 or 0) 32 | */ 33 | fun remove(id: String): Observable 34 | 35 | /** 36 | * Removes all sessions 37 | * @return Observable with the number of deleted sessions 38 | */ 39 | fun removeAll(): Observable 40 | 41 | /** 42 | * See [Session.favorite] 43 | */ 44 | fun setFavorite(sessionId: String, favorite: Boolean): Observable 45 | 46 | /** 47 | * Get all sessions given by a speaker 48 | */ 49 | fun getSessionsOfSpeaker(speakerId: String): Observable> 50 | 51 | /** 52 | * Get a list with all sessions marked as favorite 53 | */ 54 | fun getFavoriteSessions(): Observable> 55 | 56 | /** 57 | * Find sessions containing the specified query 58 | */ 59 | fun findSessionsWith(query: String): Observable> 60 | 61 | // TODO refactor that 62 | fun getBriteDatabase(): BriteDatabase 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/database/dao/SpeakerDao.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.database.dao 2 | 3 | import com.openconference.model.Speaker 4 | import rx.Observable 5 | 6 | /** 7 | * Data-Access-Object for [com.droidcon.model.Speaker] 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface SpeakerDao { 12 | 13 | /** 14 | * Insert or update a given Speaker 15 | */ 16 | fun insertOrUpdate(id: String, name: String, 17 | info: String?, profilePicUrl: String?, company: String?, 18 | jobTitle: String?, link1: String?, link2: String?, link3: String?): Observable 19 | 20 | /** 21 | * Remove a speaker 22 | * @return Observable containing the number of deleted speakers (should be 1 or 0) 23 | */ 24 | fun remove(id: String): Observable 25 | 26 | /** 27 | * Removes all speakers 28 | * @return Observable containing the number of deleted speakers 29 | */ 30 | fun removeAll(): Observable 31 | 32 | /** 33 | * Get a given Speaker by his id 34 | */ 35 | fun getSpeaker(id: String): Observable 36 | 37 | /** 38 | * Get all speakers 39 | */ 40 | fun getSpeakers(): Observable> 41 | 42 | /** 43 | * Find speakers that match the following criteria 44 | */ 45 | fun findSessionsWith(query: String): Observable> 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/errormessage/DefaultErrorMessageDeterminer.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.errormessage 2 | 3 | import com.openconference.R 4 | 5 | /** 6 | * The default error message determiner 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | class DefaultErrorMessageDeterminer : ErrorMessageDeterminer { 11 | 12 | override fun getErrorMessageRes(t: Throwable): Int = R.string.error_unknown 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/errormessage/ErrorMessageDeterminer.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.errormessage 2 | 3 | import android.support.annotation.StringRes 4 | 5 | /** 6 | * Responsible to determine the error message for a given error 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | interface ErrorMessageDeterminer { 11 | 12 | @StringRes 13 | fun getErrorMessageRes(t : Throwable) : Int 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/notification/NotificationBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.notification 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.support.v4.content.WakefulBroadcastReceiver 6 | import timber.log.Timber 7 | 8 | /** 9 | * 10 | * 11 | * @author Hannes Dorfmann 12 | */ 13 | class NotificationBroadcastReceiver : WakefulBroadcastReceiver() { 14 | 15 | companion object { 16 | val SESSION_ID = "NotificationBroadcastReceiver.SESSION_ID" 17 | } 18 | 19 | override fun onReceive(context: Context, intent: Intent) { 20 | 21 | val sessionId = intent.getStringExtra(SESSION_ID) 22 | Timber.d("onReceive() $sessionId") 23 | 24 | val serviceIntent = Intent(context, NotificationBuilderIntentService::class.java) 25 | serviceIntent.putExtra(NotificationBuilderIntentService.KEY_SESSION_ID, sessionId) 26 | 27 | startWakefulService(context, serviceIntent); 28 | 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/notification/NotificationScheduler.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.notification 2 | 3 | import com.openconference.model.Session 4 | 5 | /** 6 | * Responsible to interact with the android systems AlarmManager to schedule Notifications 7 | * to be shown before the session starts. 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface NotificationScheduler { 12 | 13 | /** 14 | * Remove any scheduled notification for a given session. 15 | * Does nothing if the no notification for this session has been scheduled before 16 | */ 17 | fun removeNotification(session: Session) 18 | 19 | /** 20 | * Schedule a notification for the given session or reschedule an existing scheduled 21 | * notification for the same session 22 | */ 23 | fun addOrRescheduleNotification(session: Session) 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/notification/NotificationSchedulerCommand.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.notification 2 | 3 | import com.openconference.model.notification.NotificationScheduler 4 | import com.openconference.model.Session 5 | 6 | /** 7 | * Implementation of the command pattern 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface NotificationSchedulerCommand { 12 | 13 | /** 14 | * Executes the command 15 | */ 16 | fun execute() 17 | } 18 | 19 | /** 20 | * Removes a Scheduled Notification for the given session 21 | */ 22 | class RemoveScheduledNotificationCommand(private val session: Session, private val notificationScheduler: NotificationScheduler) : NotificationSchedulerCommand { 23 | 24 | override fun execute() { 25 | notificationScheduler.removeNotification(session) 26 | } 27 | } 28 | 29 | /** 30 | * Adds or reschedule a Notification for the given Session 31 | */ 32 | class AddOrRescheduleNotificationCommand(private val session: Session, private val notificationScheduler: NotificationScheduler) : NotificationSchedulerCommand { 33 | 34 | override fun execute() { 35 | notificationScheduler.addOrRescheduleNotification(session) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/screen/MyScheduleScreen.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.screen 2 | 3 | import com.openconference.R 4 | 5 | /** 6 | * 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | class MyScheduleScreen : Screen { 11 | 12 | override fun titleRes(): Int = R.string.screen_title_my_schedule 13 | 14 | override fun iconRes(): Int = R.drawable.ic_my_schedule 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/screen/Screen.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.screen 2 | 3 | import android.support.annotation.DrawableRes 4 | import android.support.annotation.StringRes 5 | 6 | /** 7 | * A Screen represents a visual component (like a Fragment) 8 | * @author Hannes Dorfmann 9 | */ 10 | interface Screen { 11 | 12 | @StringRes 13 | fun titleRes(): Int 14 | 15 | @DrawableRes 16 | fun iconRes(): Int 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/screen/Screens.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.screen 2 | 3 | /** 4 | * Screens container 5 | * 6 | * @author Hannes Dorfmann 7 | */ 8 | data class Screens(val screens: List) -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/screen/SessionsScreen.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.screen 2 | 3 | import com.openconference.R 4 | 5 | /** 6 | * 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | class SessionsScreen : Screen { 11 | 12 | override fun titleRes(): Int = R.string.screen_title_sessions 13 | 14 | override fun iconRes(): Int = R.drawable.ic_sessions 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/screen/SpeakersScreen.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.screen 2 | 3 | import com.openconference.R 4 | 5 | /** 6 | * 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | class SpeakersScreen : Screen { 11 | 12 | override fun titleRes(): Int = R.string.screen_title_speakers 13 | 14 | override fun iconRes(): Int = R.drawable.ic_speakers 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/screen/TwitterScreen.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.screen 2 | 3 | import com.openconference.R 4 | 5 | /** 6 | * 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | class TwitterScreen : Screen { 11 | 12 | override fun titleRes(): Int = R.string.screen_title_twitter 13 | 14 | override fun iconRes(): Int = R.drawable.ic_twitter 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/search/DefaultSearchEngine.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.search 2 | 3 | import rx.Observable 4 | import timber.log.Timber 5 | import java.util.* 6 | 7 | /** 8 | * 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | class DefaultSearchEngine(private val searchSources: List) : SearchEngine { 13 | 14 | override fun search(query: String): Observable> { 15 | 16 | var errorsCount = 0 17 | val errorCatchingSources = searchSources.map { source -> 18 | source.search(query).onErrorReturn { 19 | errorsCount++ 20 | Timber.e(it, "Error in SearchSource $source") 21 | if (errorsCount < searchSources.size) { 22 | emptyList() 23 | } else { 24 | throw it 25 | } 26 | } 27 | } 28 | 29 | return Observable.combineLatest(errorCatchingSources, { 30 | val result = ArrayList() 31 | it.forEach { 32 | result.addAll(it as List) 33 | } 34 | result 35 | }) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/search/SearchEngine.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.search 2 | 3 | import com.openconference.model.search.SearchableItem 4 | import rx.Observable 5 | 6 | /** 7 | * 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface SearchEngine { 12 | 13 | fun search(query: String): Observable> 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/search/SearchSource.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.search 2 | 3 | import com.openconference.model.search.SearchableItem 4 | import rx.Observable 5 | 6 | /** 7 | * 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface SearchSource { 12 | 13 | fun search(query: String): Observable> 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/search/SearchableItem.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.search 2 | 3 | /** 4 | * 5 | * 6 | * @author Hannes Dorfmann 7 | */ 8 | interface SearchableItem { 9 | 10 | // TODO implement relevance factor for each item 11 | //fun relevance() :Int 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/search/SessionSearchableItem.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.search 2 | 3 | import com.openconference.model.Session 4 | 5 | /** 6 | * 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | data class SessionSearchableItem (val session : Session) : SearchableItem -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/search/SpeakerSearchableItem.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.search 2 | 3 | import com.openconference.model.Speaker 4 | 5 | /** 6 | * 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | data class SpeakerSearchableItem(val speaker: Speaker) : SearchableItem -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/search/source/LocalStorageSessionsSearchSource.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.search.source 2 | 3 | import com.openconference.model.ScheduleDataAwareObservableFactory 4 | import com.openconference.model.database.dao.SessionDao 5 | import com.openconference.model.search.SearchSource 6 | import com.openconference.model.search.SearchableItem 7 | import com.openconference.sessions.presentationmodel.SessionPresentationModelTransformer 8 | import rx.Observable 9 | 10 | /** 11 | * 12 | * 13 | * @author Hannes Dorfmann 14 | */ 15 | class LocalStorageSessionsSearchSource(private val awareObservableFactory: ScheduleDataAwareObservableFactory, private val sessionDao: SessionDao, private val modelTransformer: SessionPresentationModelTransformer) : SearchSource { 16 | 17 | override fun search( 18 | query: String): Observable> = awareObservableFactory.create>( 19 | sessionDao.findSessionsWith(query) 20 | .map { modelTransformer.transform(it) } 21 | ) 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/model/search/source/LocalStorageSpeakersSearchSource.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.search.source 2 | 3 | import com.openconference.model.ScheduleDataAwareObservableFactory 4 | import com.openconference.model.database.dao.SpeakerDao 5 | import com.openconference.model.search.SearchSource 6 | import com.openconference.model.search.SearchableItem 7 | import com.openconference.model.search.SpeakerSearchableItem 8 | import rx.Observable 9 | import java.util.* 10 | 11 | /** 12 | * 13 | * 14 | * @author Hannes Dorfmann 15 | */ 16 | class LocalStorageSpeakersSearchSource(private val awareObservableFactory: ScheduleDataAwareObservableFactory, private val speakerDao: SpeakerDao) : SearchSource { 17 | 18 | override fun search( 19 | query: String): Observable> = 20 | awareObservableFactory.create>( 21 | speakerDao.findSessionsWith(query) 22 | .map { 23 | val searchItems = ArrayList(it.size) 24 | it.forEach { 25 | searchItems.add(SpeakerSearchableItem(it)) 26 | } 27 | searchItems 28 | } 29 | ) 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/myschedule/MyScheduleComponent.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import com.openconference.dagger.ApplicationComponent 4 | import com.openconference.myschedule.MyScheduleScope 5 | import dagger.Component 6 | 7 | /** 8 | * 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | @Component ( 13 | modules = arrayOf(MyScheduleModule::class), dependencies = arrayOf(ApplicationComponent::class)) 14 | @MyScheduleScope 15 | interface MyScheduleComponent { 16 | 17 | fun mySchedulePresenter(): MySchedulePresenter 18 | fun inject(fragment: MyScheduleFragment) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/myschedule/MyScheduleModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import com.openconference.Navigator 6 | import com.openconference.PhoneNavigator 7 | import com.openconference.R 8 | import com.openconference.dagger.ApplicationContext 9 | import com.openconference.myschedule.MyScheduleScope 10 | import com.openconference.myschedule.presentationmodel.MySchedulePresentationModelTransformer 11 | import com.openconference.sessions.presentationmodel.PhoneSessionPresentationModelTransformer 12 | import com.openconference.sessions.presentationmodel.SessionPresentationModelTransformer 13 | import dagger.Module 14 | import dagger.Provides 15 | 16 | /** 17 | * @author Hannes Dorfmann 18 | */ 19 | @Module 20 | class MyScheduleModule(private val activity: Activity) { 21 | 22 | @Provides 23 | @MyScheduleScope 24 | fun provideNavigator(): Navigator = PhoneNavigator(activity) 25 | 26 | @Provides 27 | @MyScheduleScope 28 | fun providesMySchedulePresentationTransformer() = MySchedulePresentationModelTransformer() 29 | 30 | @Provides 31 | @MyScheduleScope 32 | fun provideSessionPresentationModelTransformer( 33 | @ApplicationContext context: Context): SessionPresentationModelTransformer = 34 | PhoneSessionPresentationModelTransformer(context.getString( 35 | R.string.sessions_sticky_date_format)) // TODO make that changeable at runtime as any other config change 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/myschedule/MySchedulePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import com.openconference.model.SessionsLoader 4 | import com.openconference.model.errormessage.ErrorMessageDeterminer 5 | import com.openconference.myschedule.presentationmodel.MySchedulePresentationModelTransformer 6 | import com.openconference.sessions.presentationmodel.SessionPresentationModelTransformer 7 | import com.openconference.util.RxPresenter 8 | import com.openconference.util.SchedulerTransformer 9 | import javax.inject.Inject 10 | 11 | /** 12 | * Presenter responsible to show display sessions in [SessionsView] 13 | * 14 | * @author Hannes Dorfmann 15 | */ 16 | class MySchedulePresenter @Inject constructor(scheduler: SchedulerTransformer, 17 | private val sessionsLoader: SessionsLoader, 18 | private val sessionGroupTransformer: SessionPresentationModelTransformer, 19 | private val presentationModelTransformer: MySchedulePresentationModelTransformer, 20 | errorMessageDeterminer: ErrorMessageDeterminer) : RxPresenter( 21 | scheduler, errorMessageDeterminer) { 22 | 23 | fun loadSessions() { 24 | view?.showLoading() 25 | subscribe(sessionsLoader.favoriteSessions() 26 | .map { sessionGroupTransformer.transform(it) } 27 | .map { presentationModelTransformer.transform(it) }, 28 | { 29 | view?.showContent(it) 30 | }, 31 | { 32 | view?.showError(errorMessageDeterminer.getErrorMessageRes(it)) 33 | } 34 | ) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/myschedule/MyScheduleScope.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.myschedule 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Defines the scope of the MySchedule components 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | @Scope 11 | annotation class MyScheduleScope -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/myschedule/MyScheduleView.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import com.openconference.myschedule.presentationmodel.MySchedulePresentationModel 4 | import com.openconference.util.lce.LceView 5 | 6 | /** 7 | * Displays a list of users favorite sessions 8 | * @author Hannes Dorfmann 9 | */ 10 | interface MyScheduleView : LceView -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/myschedule/presentationmodel/MySchedulePresentationModel.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.myschedule.presentationmodel 2 | 3 | import com.openconference.sessions.presentationmodel.SessionPresentationModel 4 | 5 | /** 6 | * Presentation model for easier use in [com.openconference.sessions.MyScheduleView] 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | data class MySchedulePresentationModel(val items: List, var scrollToPosition: Int?) -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/myschedule/presentationmodel/MySchedulePresentationModelTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.myschedule.presentationmodel 2 | 3 | import com.openconference.sessions.presentationmodel.SessionPresentationModel 4 | 5 | /** 6 | * Transforms a list of [SessionPresentationModel] to [MySchedulePresentationModel] 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | class MySchedulePresentationModelTransformer { 11 | 12 | fun transform(sessions: List): MySchedulePresentationModel { 13 | 14 | // TODO implement 15 | return MySchedulePresentationModel(sessions, null) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/search/SearchComponent.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.search 2 | 3 | import com.openconference.dagger.ApplicationComponent 4 | import dagger.Component 5 | 6 | /** 7 | * 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | @Component ( 12 | modules = arrayOf(SearchViewModule::class), dependencies = arrayOf(ApplicationComponent::class)) 13 | @SearchScope 14 | interface SearchComponent { 15 | 16 | fun searchPresenter(): SearchPresenter 17 | 18 | fun inject(a: SearchActivity) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/search/SearchPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.search 2 | 3 | import com.openconference.model.errormessage.ErrorMessageDeterminer 4 | import com.openconference.model.search.SearchEngine 5 | import com.openconference.util.RxPresenter 6 | import com.openconference.util.SchedulerTransformer 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Presenter for [SearchView] 11 | * 12 | * @author Hannes Dorfmann 13 | */ 14 | class SearchPresenter @Inject constructor(scheduler: SchedulerTransformer, 15 | private val searchEngine: SearchEngine, 16 | errorMessageDeterminer: ErrorMessageDeterminer) : RxPresenter( 17 | scheduler, errorMessageDeterminer) { 18 | 19 | fun search(query: String) { 20 | 21 | val trimmedQuery = query.trim() 22 | 23 | unsubscribe() // Unsubscribe previous search 24 | 25 | if (trimmedQuery.isBlank()) { 26 | view?.showSearchNotStarted() 27 | return 28 | } 29 | 30 | view?.showLoading() 31 | 32 | subscribe(searchEngine.search(trimmedQuery), 33 | { view?.showResults(it) }, 34 | { view?.showError(errorMessageDeterminer.getErrorMessageRes(it)) } 35 | ) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/search/SearchScope.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.search 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | @Scope 11 | annotation class SearchScope -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/search/SearchView.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.search 2 | 3 | import android.support.annotation.StringRes 4 | import com.hannesdorfmann.mosby.mvp.MvpView 5 | import com.openconference.model.search.SearchableItem 6 | 7 | /** 8 | * 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | interface SearchView : MvpView { 13 | fun showLoading(); 14 | fun showSearchNotStarted(); 15 | fun showError(@StringRes errorMesgId: Int) 16 | fun showResults(result: List) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/search/SearchViewModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.search 2 | 3 | import com.openconference.Navigator 4 | import com.openconference.PhoneNavigator 5 | import dagger.Module 6 | import dagger.Provides 7 | 8 | /** 9 | * 10 | * 11 | * @author Hannes Dorfmann 12 | */ 13 | @Module 14 | class SearchViewModule(private val activity: SearchActivity){ 15 | 16 | @Provides 17 | @SearchScope 18 | fun provideNavigator() : Navigator = PhoneNavigator(activity) 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/search/SearchViewState.kt: -------------------------------------------------------------------------------- 1 | package com.primetime.search 2 | 3 | import android.support.annotation.StringRes 4 | import com.hannesdorfmann.mosby.mvp.viewstate.ViewState 5 | import com.openconference.model.search.SearchableItem 6 | import com.openconference.search.SearchView 7 | 8 | /** 9 | * 10 | * 11 | * @author Hannes Dorfmann 12 | */ 13 | class SearchViewState : ViewState { 14 | 15 | private enum class State { 16 | NOT_STARTED, SHOW_LOADING, SHOW_ERROR, SHOW_RESULTS 17 | } 18 | 19 | private var state = State.NOT_STARTED 20 | private var error: Int = -1 21 | private var results: List? = null 22 | 23 | override fun apply(view: SearchView, retained: Boolean) = when (state) { 24 | State.NOT_STARTED -> view.showSearchNotStarted() 25 | State.SHOW_ERROR -> view.showError(error) 26 | State.SHOW_LOADING -> view.showLoading() 27 | State.SHOW_RESULTS -> view.showResults(results!!) 28 | } 29 | 30 | fun setShowError(@StringRes errorMesgId: Int) { 31 | error = errorMesgId 32 | results = null 33 | state = State.SHOW_ERROR 34 | 35 | } 36 | 37 | fun setSearchNotStarted() { 38 | state = State.NOT_STARTED 39 | 40 | this.error = -1 41 | this.results = null 42 | } 43 | 44 | fun setShowLoading() { 45 | state = State.SHOW_LOADING 46 | this.error = -1 47 | this.results = null 48 | } 49 | 50 | fun setShowResults(results: List) { 51 | state = State.SHOW_RESULTS 52 | this.results = results 53 | this.error = -1 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/DetailsDateAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import com.hannesdorfmann.adapterdelegates2.AbsListItemAdapterDelegate 9 | import com.openconference.R 10 | import com.openconference.sessiondetails.presentationmodel.SessionDateTimeItem 11 | import com.openconference.sessiondetails.presentationmodel.SessionDetailItem 12 | import com.openconference.util.findView 13 | 14 | /** 15 | * Displays the Date and time of a session 16 | * 17 | * @author Hannes Dorfmann 18 | */ 19 | open class DetailsDateAdapterDelegate(protected val inflater: LayoutInflater) : AbsListItemAdapterDelegate() { 20 | 21 | 22 | override fun isForViewType(item: SessionDetailItem, items: MutableList?, 23 | position: Int): Boolean = item is SessionDateTimeItem 24 | 25 | override fun onCreateViewHolder(parent: ViewGroup): DetailsViewHolder = 26 | DetailsViewHolder( 27 | inflater.inflate(R.layout.item_session_details_date, parent, false)) 28 | 29 | override fun onBindViewHolder(item: SessionDateTimeItem, viewHolder: DetailsViewHolder) { 30 | viewHolder.text.text = item.dateTime 31 | } 32 | 33 | } 34 | 35 | class DetailsViewHolder(v: View) : RecyclerView.ViewHolder(v) { 36 | val text: TextView = v.findView(R.id.text) 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/DetailsDescriptionAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import com.hannesdorfmann.adapterdelegates2.AbsListItemAdapterDelegate 9 | import com.openconference.R 10 | import com.openconference.sessiondetails.presentationmodel.SessionDescriptionItem 11 | import com.openconference.sessiondetails.presentationmodel.SessionDetailItem 12 | import com.openconference.util.findView 13 | 14 | /** 15 | * Displays the description of a session 16 | * 17 | * @author Hannes Dorfmann 18 | */ 19 | open class DetailsDescriptionAdapterDelegate(protected val inflater: LayoutInflater) : AbsListItemAdapterDelegate() { 20 | 21 | 22 | override fun isForViewType(item: SessionDetailItem, items: MutableList?, 23 | position: Int): Boolean = item is SessionDescriptionItem 24 | 25 | override fun onCreateViewHolder(parent: ViewGroup): DescriptionViewHolder = 26 | DescriptionViewHolder( 27 | inflater.inflate(R.layout.item_session_details_description, parent, false)) 28 | 29 | override fun onBindViewHolder(item: SessionDescriptionItem, viewHolder: DescriptionViewHolder) { 30 | viewHolder.description.text = item.description 31 | } 32 | 33 | class DescriptionViewHolder(v: View) : RecyclerView.ViewHolder(v) { 34 | val description: TextView = v.findView(R.id.description) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/DetailsLocationAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import com.hannesdorfmann.adapterdelegates2.AbsListItemAdapterDelegate 6 | import com.openconference.R 7 | import com.openconference.sessiondetails.presentationmodel.SessionDetailItem 8 | import com.openconference.sessiondetails.presentationmodel.SessionLocationItem 9 | 10 | /** 11 | * Displays the location of a session 12 | * 13 | * @author Hannes Dorfmann 14 | */ 15 | open class DetailsLocationAdapterDelegate(protected val inflater: LayoutInflater) : AbsListItemAdapterDelegate() { 16 | 17 | 18 | override fun isForViewType(item: SessionDetailItem, items: MutableList?, 19 | position: Int): Boolean = item is SessionLocationItem 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup): DetailsViewHolder = 22 | DetailsViewHolder( 23 | inflater.inflate(R.layout.item_session_details_location, parent, false)) 24 | 25 | override fun onBindViewHolder(item: SessionLocationItem, viewHolder: DetailsViewHolder) { 26 | viewHolder.text.text = item.locationName 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/DetailsSeparatorAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.hannesdorfmann.adapterdelegates2.AbsListItemAdapterDelegate 8 | import com.openconference.R 9 | import com.openconference.sessiondetails.presentationmodel.SessionDetailItem 10 | import com.openconference.sessiondetails.presentationmodel.SessionSeparatorItem 11 | 12 | /** 13 | * Displays the description of a session 14 | * 15 | * @author Hannes Dorfmann 16 | */ 17 | open class DetailsSeparatorAdapterDelegate(protected val inflater: LayoutInflater) : AbsListItemAdapterDelegate() { 18 | 19 | 20 | override fun isForViewType(item: SessionDetailItem, items: MutableList?, 21 | position: Int): Boolean = item is SessionSeparatorItem 22 | 23 | override fun onCreateViewHolder(parent: ViewGroup): SeparatorViewHolder = 24 | SeparatorViewHolder( 25 | inflater.inflate(R.layout.item_session_details_separator, parent, false)) 26 | 27 | override fun onBindViewHolder(item: SessionSeparatorItem, viewHolder: SeparatorViewHolder) { 28 | 29 | } 30 | 31 | class SeparatorViewHolder(v: View) : RecyclerView.ViewHolder(v) 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/SessionDetailsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.support.v7.app.AppCompatActivity 7 | import com.openconference.R 8 | import com.openconference.model.Session 9 | 10 | /*** 11 | * Simple Activity that hosts a "SessionDetailsFragment" 12 | */ 13 | class SessionDetailsActivity : AppCompatActivity() { 14 | 15 | companion object { 16 | val KEY_SESSION = "SessionDetailsActivity.SESSION" 17 | 18 | fun buildIntent(c: Context, s: Session): Intent { 19 | val intent = Intent(c, SessionDetailsActivity::class.java) 20 | intent.putExtra(SessionDetailsActivity.KEY_SESSION, s) 21 | return intent 22 | } 23 | } 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | setContentView(R.layout.activity_session_details) 28 | 29 | if (savedInstanceState == null) { 30 | val session: Session = intent.getParcelableExtra(KEY_SESSION) 31 | 32 | supportFragmentManager.beginTransaction() 33 | .replace(R.id.fragmentContainer, SessionDetailsFragmentBuilder(session).build()) 34 | .commit() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/SessionDetailsComponent.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import com.openconference.dagger.ApplicationComponent 4 | import dagger.Component 5 | 6 | /** 7 | * SessionDetails component 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | @Component(modules = arrayOf(SessionDetailsModule::class), 12 | dependencies = arrayOf(ApplicationComponent::class)) 13 | @SessionDetailsScope 14 | interface SessionDetailsComponent { 15 | 16 | fun sessionDetailsPresenter(): SessionDetailsPresenter 17 | fun inject(f: SessionDetailsFragment) 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/SessionDetailsModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import android.app.Activity 4 | import com.openconference.Navigator 5 | import com.openconference.PhoneNavigator 6 | import com.openconference.sessiondetails.presentationmodel.PhoneSessionDetailsPresentationModelTransformer 7 | import com.openconference.sessiondetails.presentationmodel.SessionDetailsPresentationModelTransformer 8 | import dagger.Module 9 | import dagger.Provides 10 | 11 | /** 12 | * Dagger Module for SessionDetails 13 | * @author Hannes Dorfmann 14 | */ 15 | @Module 16 | class SessionDetailsModule(private val activity: Activity) { 17 | 18 | @Provides 19 | @SessionDetailsScope 20 | fun provideNavigator(): Navigator = PhoneNavigator(activity) 21 | 22 | @Provides 23 | @SessionDetailsScope 24 | fun providePresentationModelTransformer(): SessionDetailsPresentationModelTransformer = 25 | PhoneSessionDetailsPresentationModelTransformer() 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/SessionDetailsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import com.openconference.model.Session 4 | import com.openconference.model.SessionsLoader 5 | import com.openconference.model.errormessage.ErrorMessageDeterminer 6 | import com.openconference.sessiondetails.presentationmodel.SessionDetailsPresentationModelTransformer 7 | import com.openconference.util.RxPresenter 8 | import com.openconference.util.SchedulerTransformer 9 | import javax.inject.Inject 10 | 11 | /** 12 | * 13 | * 14 | * @author Hannes Dorfmann 15 | */ 16 | class SessionDetailsPresenter @Inject constructor(scheduler: SchedulerTransformer, 17 | private val sessionsLoader: SessionsLoader, 18 | private val presentationModelTransformer: SessionDetailsPresentationModelTransformer, 19 | errorMessageDeterminer: ErrorMessageDeterminer) : RxPresenter( 20 | scheduler, errorMessageDeterminer) { 21 | 22 | 23 | fun loadSession(sessionId: String) { 24 | view?.showLoading() 25 | subscribe( 26 | sessionsLoader.getSession(sessionId).map { presentationModelTransformer.transform(it) }, 27 | { 28 | view?.showContent(it) 29 | }, 30 | { 31 | view?.showError(errorMessageDeterminer.getErrorMessageRes(it)) 32 | }) 33 | } 34 | 35 | fun addSessionToSchedule(session : Session) { 36 | 37 | schedulerTransformer.schedule(sessionsLoader.addSessionToSchedule(session)).subscribe ({ 38 | view?.showSessionAddedToSchedule() 39 | }, { 40 | view?.showErrorWhileAddingSessionToSchedule() 41 | } 42 | ) 43 | 44 | 45 | } 46 | 47 | fun removeSessionFromSchedule(session: Session) { 48 | schedulerTransformer.schedule(sessionsLoader.removeSessionFromSchedule(session)).subscribe ({ 49 | view?.showSessionRemovedFromSchedule() 50 | }, { 51 | view?.showErrorWhileRemovingSessionFromSchedule() 52 | } 53 | ) 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/SessionDetailsScope.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Scope for SessionDetails 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | @Scope 11 | annotation class SessionDetailsScope -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/SessionDetailsView.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import com.openconference.sessiondetails.presentationmodel.SessionDetail 4 | import com.openconference.util.lce.LceView 5 | 6 | /** 7 | * Displays details about a certain Session 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface SessionDetailsView : LceView { 12 | fun showSessionAddedToSchedule() 13 | fun showErrorWhileAddingSessionToSchedule() 14 | 15 | fun showSessionRemovedFromSchedule() 16 | fun showErrorWhileRemovingSessionFromSchedule() 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/presentationmodel/SessionDetail.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails.presentationmodel 2 | 3 | import com.openconference.model.Session 4 | 5 | /** 6 | * 7 | * Presentation Model for [com.openconference.sessiondetails.SessionDetailsView] 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | data class SessionDetail(val sessionId: String, val sessionName: String?, val session: Session, val detailsItems: List, val inMySchedule: Boolean) -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessiondetails/presentationmodel/SessionDetailItem.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails.presentationmodel 2 | 3 | import com.openconference.model.Speaker 4 | import com.openconference.model.search.SearchableItem 5 | 6 | /** 7 | * Represents the base class of elements that can be represented in this app 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface SessionDetailItem 12 | 13 | /** 14 | * Holds the data for displaying session details 15 | * @author Hannes Dorfmann 16 | */ 17 | data class SessionDescriptionItem(val description: String) : SessionDetailItem 18 | 19 | /** 20 | * Tags with which this item has been tagged 21 | * @author Hannes Dorfmann 22 | */ 23 | data class SessionTagsItem(val tags: String) : SessionDetailItem 24 | 25 | /** 26 | * Contains the location id 27 | * @author Hannes Dorfmann 28 | */ 29 | data class SessionLocationItem(val locationName: String) : SessionDetailItem 30 | 31 | /** 32 | * Contains the start - end date of a sessoin 33 | */ 34 | data class SessionDateTimeItem(val dateTime: String) : SessionDetailItem 35 | 36 | /** 37 | * Speaker of a session 38 | */ 39 | data class SessionSpeakerItem(val speaker: Speaker) : SessionDetailItem 40 | 41 | /** 42 | * Represents a separator line 43 | */ 44 | class SessionSeparatorItem() : SessionDetailItem, SearchableItem -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessions/SessionDateStickyHeaderAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import butterknife.bindView 9 | import com.eowise.recyclerview.stickyheaders.StickyHeadersAdapter 10 | import com.openconference.R 11 | import com.openconference.sessions.presentationmodel.SessionPresentationModel 12 | 13 | /** 14 | * 15 | * 16 | * @author Hannes Dorfmann 17 | */ 18 | class SessionDateStickyHeaderAdapter(private val inflater: LayoutInflater) : StickyHeadersAdapter { 19 | 20 | var sessions: List = emptyList() 21 | 22 | override fun onCreateViewHolder(parent: ViewGroup?) = 23 | DateViewHolder(inflater.inflate(R.layout.item_session_date_sticky_header, parent, false)) 24 | 25 | override fun onBindViewHolder(vh: DateViewHolder, position: Int) { 26 | val session = sessions[position] 27 | if (session.dateShort() != null) { 28 | vh.date.text = session.dateShort() 29 | vh.date.visibility = View.VISIBLE 30 | } else { 31 | vh.date.visibility = View.GONE 32 | } 33 | 34 | if (session.dayInWeek() != null){ 35 | vh.dayInWeek.text = session.dayInWeek() 36 | vh.dayInWeek.visibility = View.VISIBLE 37 | } else { 38 | vh.dayInWeek.visibility = View.GONE 39 | } 40 | } 41 | 42 | override fun getHeaderId(position: Int): Long = sessions[position].getSectionId() 43 | 44 | class DateViewHolder(v: View) : RecyclerView.ViewHolder(v) { 45 | val dayInWeek: TextView by bindView(R.id.dayInWeek) 46 | val date: TextView by bindView(R.id.date) 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessions/SessionsComponent.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import com.openconference.dagger.ApplicationComponent 4 | import dagger.Component 5 | 6 | /** 7 | * 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | @Component ( 12 | modules = arrayOf(SessionsModule::class), dependencies = arrayOf(ApplicationComponent::class)) 13 | @SessionsScope 14 | interface SessionsComponent { 15 | 16 | fun sessionPresenter(): SessionsPresenter 17 | 18 | fun inject(f: SessionsFragment) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessions/SessionsModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import com.openconference.Navigator 6 | import com.openconference.PhoneNavigator 7 | import com.openconference.R 8 | import com.openconference.dagger.ApplicationContext 9 | import com.openconference.sessions.presentationmodel.PhoneSessionPresentationModelTransformer 10 | import com.openconference.sessions.presentationmodel.SessionPresentationModelTransformer 11 | import dagger.Module 12 | import dagger.Provides 13 | 14 | /** 15 | * @author Hannes Dorfmann 16 | */ 17 | @Module 18 | class SessionsModule(private val activity: Activity) { 19 | 20 | 21 | @Provides 22 | @SessionsScope 23 | fun provideNavigator(): Navigator = PhoneNavigator(activity) 24 | 25 | @Provides 26 | @SessionsScope 27 | fun provideSessionPresentationModelTransformer( 28 | @ApplicationContext context: Context): SessionPresentationModelTransformer = 29 | PhoneSessionPresentationModelTransformer(context.getString( 30 | R.string.sessions_sticky_date_format)) // TODO make that changeable at runtime as any other config change 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessions/SessionsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import com.openconference.model.SessionsLoader 4 | import com.openconference.model.errormessage.ErrorMessageDeterminer 5 | import com.openconference.sessions.presentationmodel.SessionPresentationModelTransformer 6 | import com.openconference.util.RxPresenter 7 | import com.openconference.util.SchedulerTransformer 8 | import javax.inject.Inject 9 | 10 | /** 11 | * Presenter responsible to show display sessions in [SessionsView] 12 | * 13 | * @author Hannes Dorfmann 14 | */ 15 | class SessionsPresenter @Inject constructor(scheduler: SchedulerTransformer, 16 | private val sessionsLoader: SessionsLoader, 17 | private val presentationModelTransformer: SessionPresentationModelTransformer, 18 | errorMessageDeterminer: ErrorMessageDeterminer) : RxPresenter( 19 | scheduler, errorMessageDeterminer) { 20 | 21 | fun loadSessions() { 22 | view?.showLoading() 23 | subscribe(sessionsLoader.allSessions().map { presentationModelTransformer.transform(it) }, 24 | { 25 | view?.showContent(it) 26 | }, 27 | { 28 | view?.showError(errorMessageDeterminer.getErrorMessageRes(it)) 29 | } 30 | ) 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessions/SessionsScope.java: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions; 2 | 3 | import javax.inject.Scope; 4 | 5 | /** 6 | * @author Hannes Dorfmann 7 | */ 8 | @Scope public @interface SessionsScope { 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessions/SessionsView.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import com.openconference.sessions.presentationmodel.GroupableSession 4 | import com.openconference.sessions.presentationmodel.SessionPresentationModel 5 | import com.openconference.util.lce.LceView 6 | 7 | /** 8 | * Displays a list of sessions 9 | * @author Hannes Dorfmann 10 | */ 11 | interface SessionsView : LceView> -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessions/presentationmodel/GroupableSession.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions.presentationmodel 2 | 3 | import android.os.Parcelable 4 | 5 | /** 6 | * Common interface to group session together 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | interface GroupableSession : Parcelable { 11 | 12 | 13 | /** 14 | * Specify to which section this Session belongs. Typically sections are grouped by days 15 | */ 16 | fun getSectionId(): Long 17 | 18 | /** 19 | * Specifies the id of the session 20 | */ 21 | fun getSessionId(): String 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessions/presentationmodel/SessionPresentationModel.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions.presentationmodel 2 | 3 | import com.google.auto.value.AutoValue 4 | import com.openconference.model.Session 5 | import com.openconference.model.search.SearchableItem 6 | 7 | /** 8 | * Presentation Model of [com.openconference.model.Session] used by [com.openconference.sessions.SessionsView] 9 | * @author Hannes Dorfmann 10 | */ 11 | @AutoValue 12 | abstract class SessionPresentationModel : GroupableSession, SearchableItem { 13 | 14 | 15 | abstract fun dayInWeek(): String? 16 | 17 | abstract fun dateShort(): String? 18 | 19 | abstract fun time(): String? 20 | 21 | abstract fun speakers(): String? 22 | 23 | abstract fun session(): Session 24 | 25 | companion object { 26 | fun create(id: Long, dayInWeek: String?, dateShort: String?, speakers: String?, time: String?, 27 | session: Session): SessionPresentationModel = 28 | AutoValue_SessionPresentationModel(id, session.id().toString(), dayInWeek, dateShort, time, 29 | speakers, session) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/sessions/presentationmodel/SessionPresentationModelTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions.presentationmodel 2 | 3 | import com.openconference.model.Session 4 | 5 | /** 6 | *Transforms a [com.openconference.model.Session] to [SessionPresentationModel] 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | interface SessionPresentationModelTransformer { 11 | 12 | fun transform(s: List): List 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/SpeakerDetailsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import com.openconference.R 6 | import com.openconference.model.Speaker 7 | 8 | /*** 9 | * Simple Activity that hosts a "SessionDetailsFragment" 10 | */ 11 | class SpeakerDetailsActivity : AppCompatActivity() { 12 | 13 | companion object { 14 | val KEY_SPEAKER = "SpeakerDetailsActivity.SPEAKER" 15 | } 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(R.layout.activity_session_details) 20 | 21 | if (savedInstanceState == null) { 22 | val speaker: Speaker = intent.getParcelableExtra(KEY_SPEAKER) 23 | 24 | supportFragmentManager.beginTransaction() 25 | .replace(R.id.fragmentContainer, SpeakerDetailsFragmentBuilder(speaker).build()) 26 | .commit() 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/SpeakerDetailsBioAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakerdetails 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import com.hannesdorfmann.adapterdelegates2.AbsListItemAdapterDelegate 6 | import com.openconference.R 7 | import com.openconference.speakerdetails.presentationmodel.SpeakerBioItem 8 | import com.openconference.speakerdetails.presentationmodel.SpeakerDetailsItem 9 | 10 | /** 11 | * 12 | * 13 | * @author Hannes Dorfmann 14 | */ 15 | class SpeakerDetailsBioAdapterDelegate(val inflater: LayoutInflater) : AbsListItemAdapterDelegate() { 16 | 17 | 18 | override fun isForViewType(item: SpeakerDetailsItem, items: MutableList?, 19 | position: Int) = item is SpeakerBioItem 20 | 21 | override fun onBindViewHolder(item: SpeakerBioItem, viewHolder: SpeakerDetailsViewHolder) = 22 | viewHolder.bind(null, item.bio) 23 | 24 | override fun onCreateViewHolder(parent: ViewGroup): SpeakerDetailsViewHolder { 25 | val view = inflater.inflate(R.layout.item_details_icon_text, parent, false) 26 | view.isClickable = false 27 | return SpeakerDetailsViewHolder(view) 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/SpeakerDetailsComponent.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import com.openconference.dagger.ApplicationComponent 4 | import com.openconference.speakerdetails.SpeakerDetailsPresenter 5 | import dagger.Component 6 | 7 | /** 8 | * SessionDetails component 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | @Component(modules = arrayOf(SpeakerDetailsModule::class), 13 | dependencies = arrayOf(ApplicationComponent::class)) 14 | @SpeakerDetailsScope 15 | interface SpeakerDetailsComponent { 16 | 17 | fun sessionDetailsPresenter(): SpeakerDetailsPresenter 18 | fun inject(f: SpeakerDetailsFragment) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/SpeakerDetailsJobInfoAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakerdetails 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import butterknife.bindView 9 | import com.hannesdorfmann.adapterdelegates2.AbsListItemAdapterDelegate 10 | import com.openconference.R 11 | import com.openconference.speakerdetails.presentationmodel.SpeakerDetailsItem 12 | import com.openconference.speakerdetails.presentationmodel.SpeakerJobInfoItem 13 | 14 | /** 15 | * 16 | * 17 | * @author Hannes Dorfmann 18 | */ 19 | class SpeakerDetailsJobInfoAdapterDelegate(val inflater: LayoutInflater) : AbsListItemAdapterDelegate() { 20 | 21 | 22 | override fun isForViewType(item: SpeakerDetailsItem, items: MutableList?, 23 | position: Int) = item is SpeakerJobInfoItem 24 | 25 | override fun onBindViewHolder(item: SpeakerJobInfoItem, viewHolder: JobInfoViewHolder) = 26 | viewHolder.bind(item) 27 | 28 | override fun onCreateViewHolder(parent: ViewGroup) = JobInfoViewHolder( 29 | inflater.inflate(R.layout.item_speaker_details_jobinfo, parent, false)) 30 | 31 | class JobInfoViewHolder(v: View) : RecyclerView.ViewHolder(v) { 32 | val jobTitle by bindView(R.id.jobTitle) 33 | val companyName by bindView(R.id.company) 34 | 35 | inline fun bind(info: SpeakerJobInfoItem) { 36 | val title = info.jobTitle 37 | if (title != null) { 38 | jobTitle.text = title 39 | jobTitle.visibility = View.VISIBLE 40 | } else { 41 | jobTitle.visibility = View.GONE 42 | } 43 | 44 | val company = info.company 45 | if (company != null) { 46 | companyName.text = company 47 | companyName.visibility = View.VISIBLE 48 | } else { 49 | companyName.visibility = View.GONE 50 | } 51 | } 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/SpeakerDetailsLinkAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakerdetails 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import com.hannesdorfmann.adapterdelegates2.AbsListItemAdapterDelegate 6 | import com.openconference.R 7 | import com.openconference.speakerdetails.presentationmodel.SpeakerDetailsItem 8 | import com.openconference.speakerdetails.presentationmodel.SpeakerLinkItem 9 | 10 | /** 11 | * 12 | * 13 | * @author Hannes Dorfmann 14 | */ 15 | class SpeakerDetailsLinkAdapterDelegate(val inflater: LayoutInflater) : AbsListItemAdapterDelegate() { 16 | 17 | 18 | override fun isForViewType(item: SpeakerDetailsItem, items: MutableList?, 19 | position: Int) = item is SpeakerLinkItem 20 | 21 | override fun onBindViewHolder(item: SpeakerLinkItem, viewHolder: SpeakerDetailsViewHolder) { 22 | viewHolder.bind(if (item.showIcon) { 23 | R.drawable.ic_link 24 | } else null, item.url) 25 | } 26 | 27 | override fun onCreateViewHolder(parent: ViewGroup) = SpeakerDetailsViewHolder( 28 | inflater.inflate(R.layout.item_details_icon_link_text, parent, false)) 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/SpeakerDetailsModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import android.app.Activity 4 | import com.openconference.Navigator 5 | import com.openconference.PhoneNavigator 6 | import com.openconference.sessiondetails.presentationmodel.PhoneSpeakerDetailsPresentationModelTransformer 7 | import com.openconference.sessiondetails.presentationmodel.SpeakerDetailsPresentationModelTransformer 8 | import dagger.Module 9 | import dagger.Provides 10 | 11 | /** 12 | * Dagger Module for SessionDetails 13 | * @author Hannes Dorfmann 14 | */ 15 | @Module 16 | class SpeakerDetailsModule(private val activity: Activity) { 17 | 18 | @Provides 19 | @SpeakerDetailsScope 20 | fun provideNavigator(): Navigator = PhoneNavigator(activity) 21 | 22 | @Provides 23 | @SpeakerDetailsScope 24 | fun providePresentationModelTransformer(): SpeakerDetailsPresentationModelTransformer = 25 | PhoneSpeakerDetailsPresentationModelTransformer() 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/SpeakerDetailsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakerdetails 2 | 3 | import com.openconference.model.SessionsLoader 4 | import com.openconference.model.SpeakersLoader 5 | import com.openconference.model.errormessage.ErrorMessageDeterminer 6 | import com.openconference.sessiondetails.presentationmodel.SpeakerDetailsPresentationModelTransformer 7 | import com.openconference.util.RxPresenter 8 | import com.openconference.util.SchedulerTransformer 9 | import rx.Observable 10 | import javax.inject.Inject 11 | 12 | /** 13 | * 14 | * 15 | * @author Hannes Dorfmann 16 | */ 17 | class SpeakerDetailsPresenter @Inject constructor(scheduler: SchedulerTransformer, 18 | private val sessionsLoader: SessionsLoader, 19 | private val speakersLoader: SpeakersLoader, 20 | private val presentationModelTransformer: SpeakerDetailsPresentationModelTransformer, 21 | errorMessageDeterminer: ErrorMessageDeterminer) : RxPresenter( 22 | scheduler, errorMessageDeterminer) { 23 | 24 | 25 | fun loadSpeakerDetails(speakerId: String) { 26 | view?.showLoading() 27 | subscribe( 28 | // TODO move this in a own class 29 | Observable.combineLatest( 30 | sessionsLoader.getSessionsOfSpeaker(speakerId), 31 | speakersLoader.getSpeaker(speakerId), 32 | { sessions, speaker -> presentationModelTransformer.transform(speaker, sessions) }), 33 | { 34 | view?.showContent(it) 35 | }, 36 | { 37 | view?.showError(errorMessageDeterminer.getErrorMessageRes(it)) 38 | }) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/SpeakerDetailsScope.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessiondetails 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Scope for SessionDetails 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | @Scope 11 | annotation class SpeakerDetailsScope -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/SpeakerDetailsView.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakerdetails 2 | 3 | import com.openconference.speakerdetails.presentationmodel.SpeakerDetail 4 | import com.openconference.util.lce.LceView 5 | 6 | /** 7 | * A view that dispalys information about a speaker 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface SpeakerDetailsView : LceView -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/SpeakerDetailsViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakerdetails 2 | 3 | import android.support.annotation.DrawableRes 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.View 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import butterknife.bindView 9 | import com.openconference.R 10 | 11 | /** 12 | * ViewHolder for icon text combination 13 | * 14 | * @author Hannes Dorfmann 15 | */ 16 | open class SpeakerDetailsViewHolder(v: View) : RecyclerView.ViewHolder(v) { 17 | 18 | val icon by bindView(R.id.icon) 19 | val text by bindView(R.id.text) 20 | 21 | inline fun bind(@DrawableRes iconRes: Int?, t: String) { 22 | if (iconRes == null) { 23 | icon.visibility = View.INVISIBLE 24 | } else { 25 | icon.setImageDrawable(itemView.resources.getDrawable(iconRes)) 26 | } 27 | 28 | text.text = t 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/presentationmodel/SpeakerDetail.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakerdetails.presentationmodel 2 | 3 | /** 4 | * Presentation Model containig all the data required to dispaly details about a Speaker 5 | * 6 | * @author Hannes Dorfmann 7 | */ 8 | data class SpeakerDetail(val speakerName: String, val profilePic: String?, 9 | val detailsItems: List) 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakerdetails/presentationmodel/SpeakerDetailsItem.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakerdetails.presentationmodel 2 | 3 | import com.openconference.model.Session 4 | 5 | /** 6 | * Common super interface for items that can be dispalyed in the speakers details 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | interface SpeakerDetailsItem 11 | 12 | /** 13 | * Information about the job of the speaker 14 | */ 15 | data class SpeakerJobInfoItem(val company: String?, val jobTitle: String?) : SpeakerDetailsItem 16 | 17 | /** 18 | * A link like personal blog, twitter account etc. of the speaker 19 | */ 20 | data class SpeakerLinkItem(val url: String, val showIcon: Boolean) : SpeakerDetailsItem 21 | 22 | /** 23 | * Containing the bio of the speaker 24 | */ 25 | data class SpeakerBioItem(val bio: String) : SpeakerDetailsItem 26 | 27 | /** 28 | * Represents a session that will be given by the speaker 29 | */ 30 | data class SpeakerSessionItem(val session: Session, val showIcon: Boolean) : SpeakerDetailsItem -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakers/SpeakerAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakers 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.ImageView 8 | import android.widget.TextView 9 | import butterknife.bindView 10 | import com.hannesdorfmann.adapterdelegates2.AbsListItemAdapterDelegate 11 | import com.openconference.R 12 | import com.openconference.model.Speaker 13 | import com.openconference.util.picasso.PicassoScrollListener 14 | import com.squareup.picasso.Picasso 15 | 16 | /** 17 | * Displays an profile pic image and name of a given Speaker 18 | * 19 | * @author Hannes Dorfmann 20 | */ 21 | class SpeakerAdapterDelegate( 22 | private val inflater: LayoutInflater, 23 | private val picasso: Picasso, 24 | private val clickListener: (Speaker) -> Unit) : AbsListItemAdapterDelegate() { 25 | 26 | override fun isForViewType(item: Speaker, items: MutableList?, position: Int): Boolean = 27 | item is Speaker 28 | 29 | override fun onBindViewHolder(item: Speaker, viewHolder: SpeakerViewHolder) = 30 | viewHolder.bind(item) 31 | 32 | override fun onCreateViewHolder(parent: ViewGroup): SpeakerViewHolder = 33 | SpeakerViewHolder(inflater.inflate(R.layout.item_speaker, parent, false), picasso, 34 | clickListener) 35 | 36 | class SpeakerViewHolder(v: View, val picasso: Picasso, clickListener: (Speaker) -> Unit) : RecyclerView.ViewHolder( 37 | v) { 38 | 39 | init { 40 | v.setOnClickListener { clickListener(speaker) } 41 | } 42 | 43 | val image: ImageView by bindView(R.id.image) 44 | val name: TextView by bindView(R.id.name) 45 | lateinit var speaker: Speaker 46 | 47 | inline fun bind(s: Speaker) { 48 | speaker = s 49 | name.text = s.name() 50 | picasso.load(speaker.profilePic()) 51 | .tag(PicassoScrollListener.TAG) 52 | .centerCrop() 53 | .fit() 54 | .placeholder(R.color.speakerslist_placeholder) 55 | .into(image) 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakers/SpeakersComponent.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakers 2 | 3 | import com.openconference.dagger.ApplicationComponent 4 | import com.openconference.sessions.SpeakersFragment 5 | import com.openconference.sessions.SpeakersModule 6 | import com.openconference.sessions.SpeakersPresenter 7 | import dagger.Component 8 | 9 | /** 10 | * 11 | * 12 | * @author Hannes Dorfmann 13 | */ 14 | @Component(modules = arrayOf(SpeakersModule::class), 15 | dependencies = arrayOf(ApplicationComponent::class)) 16 | @SpeakersScope 17 | interface SpeakersComponent { 18 | 19 | fun inject(f: SpeakersFragment) 20 | 21 | fun speakersPresenter(): SpeakersPresenter 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakers/SpeakersModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import android.app.Activity 4 | import com.openconference.Navigator 5 | import com.openconference.PhoneNavigator 6 | import com.openconference.speakers.SpeakersScope 7 | import dagger.Module 8 | import dagger.Provides 9 | 10 | /** 11 | * @author Hannes Dorfmann 12 | */ 13 | @Module 14 | class SpeakersModule(private val activity: Activity) { 15 | 16 | @Provides 17 | @SpeakersScope 18 | fun provideNavigator(): Navigator = PhoneNavigator(activity) 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakers/SpeakersPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.sessions 2 | 3 | import com.openconference.model.SpeakersLoader 4 | import com.openconference.model.errormessage.ErrorMessageDeterminer 5 | import com.openconference.speakers.SpeakersView 6 | import com.openconference.util.RxPresenter 7 | import com.openconference.util.SchedulerTransformer 8 | import javax.inject.Inject 9 | 10 | /** 11 | * Presenter responsible to show display sessions in [SessionsView] 12 | * 13 | * @author Hannes Dorfmann 14 | */ 15 | class SpeakersPresenter @Inject constructor(scheduler: SchedulerTransformer, 16 | private val speakersLoader: SpeakersLoader, 17 | errorMessageDeterminer: ErrorMessageDeterminer) : RxPresenter( 18 | scheduler, errorMessageDeterminer) { 19 | 20 | fun loadSpeakers() { 21 | view?.showLoading() 22 | subscribe(speakersLoader.allSpeakers(), 23 | { 24 | view?.showContent(it) 25 | }, 26 | { 27 | view?.showError(errorMessageDeterminer.getErrorMessageRes(it)) 28 | } 29 | ) 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakers/SpeakersScope.java: -------------------------------------------------------------------------------- 1 | package com.openconference.speakers; 2 | 3 | import javax.inject.Scope; 4 | 5 | /** 6 | * @author Hannes Dorfmann 7 | */ 8 | @Scope public @interface SpeakersScope { 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/speakers/SpeakersView.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.speakers 2 | 3 | import com.openconference.model.Speaker 4 | import com.openconference.util.lce.LceView 5 | 6 | /** 7 | * View that displays a list of all speakers 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface SpeakersView : LceView> -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/splash/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.splash 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import com.hannesdorfmann.mosby.mvp.viewstate.MvpViewStateActivity 6 | import com.hannesdorfmann.mosby.mvp.viewstate.ViewState 7 | import com.openconference.R 8 | import com.openconference.main.ViewPagerMainActivity 9 | import com.openconference.util.applicationComponent 10 | 11 | /** 12 | * 13 | * 14 | * @author Hannes Dorfmann 15 | */ 16 | class SplashActivity : SplashView, MvpViewStateActivity() { 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(R.layout.activity_splash) 21 | retainInstance = true 22 | 23 | } 24 | 25 | override fun finishSplash() { 26 | finish() 27 | startActivity(Intent(this, ViewPagerMainActivity::class.java)) 28 | overridePendingTransition(R.anim.fade_in, R.anim.fade_out) 29 | } 30 | 31 | override fun createPresenter(): SplashPresenter = 32 | DaggerSplashComponent.builder().applicationComponent( 33 | applicationComponent()).build().splashPresenter() 34 | 35 | override fun createViewState(): ViewState = ViewState { view, retained -> } // dummy, does nothing, not needed 36 | 37 | override fun onNewViewStateInstance() = presenter.start() 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/splash/SplashComponent.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.splash 2 | 3 | import com.openconference.dagger.ApplicationComponent 4 | import dagger.Component 5 | 6 | /** 7 | * 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | @Component( 12 | dependencies = arrayOf(ApplicationComponent::class)) 13 | @SplashScope 14 | interface SplashComponent { 15 | 16 | fun splashPresenter(): SplashPresenter 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/splash/SplashPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.splash 2 | 3 | import com.openconference.model.ScheduleDataAwareObservableFactory 4 | import com.openconference.model.errormessage.ErrorMessageDeterminer 5 | import com.openconference.util.RxPresenter 6 | import com.openconference.util.SchedulerTransformer 7 | import rx.Observable 8 | import java.util.concurrent.TimeUnit 9 | import javax.inject.Inject 10 | 11 | /** 12 | * 13 | * 14 | * @author Hannes Dorfmann 15 | */ 16 | class SplashPresenter @Inject constructor(schedulerTransformer: SchedulerTransformer, 17 | errorMessageDeterminer: ErrorMessageDeterminer, private val observableFactory: ScheduleDataAwareObservableFactory) : RxPresenter( 18 | schedulerTransformer, 19 | errorMessageDeterminer) { 20 | 21 | 22 | fun start() { 23 | 24 | // Preload data in background 25 | subscribe(observableFactory.create(Observable.just(1)), 26 | {}, 27 | {}, 28 | {} 29 | ) // no callbacks needed 30 | 31 | // wait for two seconds 32 | val timer = Observable.timer(1500, TimeUnit.MILLISECONDS) 33 | subscribe( 34 | timer, 35 | { // onNext 36 | }, 37 | { 38 | // onError 39 | view?.finishSplash() 40 | }, 41 | { 42 | // onCompleted (timer just fire onCompleted) 43 | view?.finishSplash() 44 | } 45 | ) 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/splash/SplashScope.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.splash 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | @Scope 11 | annotation class SplashScope -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/splash/SplashView.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.splash 2 | 3 | import com.hannesdorfmann.mosby.mvp.MvpView 4 | 5 | /** 6 | * 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | interface SplashView : MvpView { 11 | fun finishSplash() 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/twitter/TwitterTimelinePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.twitter 2 | 3 | import com.hannesdorfmann.mosby.mvp.MvpBasePresenter 4 | import com.openconference.R 5 | import com.twitter.sdk.android.core.Callback 6 | import com.twitter.sdk.android.core.Result 7 | import com.twitter.sdk.android.core.TwitterException 8 | import com.twitter.sdk.android.core.models.Tweet 9 | import com.twitter.sdk.android.tweetui.SearchTimeline 10 | import com.twitter.sdk.android.tweetui.Timeline 11 | import com.twitter.sdk.android.tweetui.TimelineResult 12 | import timber.log.Timber 13 | 14 | class TwitterTimelinePresenter : MvpBasePresenter() { 15 | 16 | private val timeline: Timeline = SearchTimeline.Builder().query("#droidconDE").build() 17 | 18 | override fun attachView(view: TwitterTimelineView) { 19 | super.attachView(view) 20 | view.setTimeline(timeline) 21 | } 22 | 23 | fun loadNextTweets() { 24 | view?.showLoading() 25 | timeline.next(0, object : Callback>() { 26 | 27 | override fun success(result: Result>?) { 28 | view?.showContent(1) // Data will be bound automatically via view.setTimeline 29 | } 30 | 31 | override fun failure(exception: TwitterException?) { 32 | view?.showError(R.string.error_unknown) 33 | Timber.e(exception, "Error while loading Twitter") 34 | } 35 | }) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/twitter/TwitterTimelineView.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.twitter 2 | 3 | import com.openconference.util.lce.LceView 4 | import com.twitter.sdk.android.core.models.Tweet 5 | import com.twitter.sdk.android.tweetui.Timeline 6 | 7 | /** 8 | * Displays a list of item in the timeline 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | interface TwitterTimelineView : LceView { 13 | fun setTimeline(timeline: Timeline) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/ActivityExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util 2 | 3 | import android.app.Activity 4 | import android.support.annotation.IdRes 5 | import android.util.DisplayMetrics 6 | import android.view.View 7 | import com.openconference.OpenConfApp 8 | 9 | 10 | fun Activity.applicationComponent() = OpenConfApp.getApplicationComponent(application) 11 | 12 | fun Activity.dpToPx(dp: Int): Float { 13 | val metrics = resources.displayMetrics; 14 | val px: Float = dp * (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT); 15 | return px; 16 | } 17 | 18 | fun Activity.findView(@IdRes id: Int) = this.findViewById(id) as T -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/ContentValuesExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util 2 | 3 | import android.content.ContentValues 4 | import org.threeten.bp.Instant 5 | 6 | 7 | /** 8 | * Checks if the value is null and either inserts null into content value or the real value 9 | */ 10 | fun ContentValues.putOrNull(key: String, value: String?) = 11 | if (value == null) putNull(key) else put(key, value) 12 | 13 | 14 | fun ContentValues.putOrNull(key: String, value: Instant?) = 15 | if (value == null) putNull(key) else put(key, value.toEpochMilli()) -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/FragmentExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util 2 | 3 | import android.app.Activity 4 | import android.support.annotation.IntegerRes 5 | import android.support.v4.app.Fragment 6 | import android.util.DisplayMetrics 7 | import com.openconference.OpenConfApp 8 | 9 | fun Fragment.applicationComponent() = OpenConfApp.getApplicationComponent(activity) 10 | 11 | fun Fragment.layoutInflater() = activity.layoutInflater 12 | 13 | fun Fragment.getInt(@IntegerRes intRes: Int) = resources.getInteger(intRes) 14 | 15 | fun Fragment.dpToPx(dp: Int): Float { 16 | val metrics = resources.displayMetrics; 17 | val px: Float = dp * (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT); 18 | return px; 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/FragmentScope.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | @Scope 11 | annotation class FragmentScope -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/RxPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util 2 | 3 | import com.hannesdorfmann.mosby.mvp.MvpBasePresenter 4 | import com.hannesdorfmann.mosby.mvp.MvpView 5 | import com.openconference.model.errormessage.ErrorMessageDeterminer 6 | import rx.Observable 7 | import rx.subscriptions.CompositeSubscription 8 | import timber.log.Timber 9 | 10 | /** 11 | * A Presenter that automatically unsubscribes from Observable 12 | * once the view has been permanently detached 13 | * @author Hannes Dorfmann 14 | */ 15 | open class RxPresenter(protected val schedulerTransformer: SchedulerTransformer, 16 | protected val errorMessageDeterminer: ErrorMessageDeterminer) : MvpBasePresenter() { 17 | 18 | protected var subscriptions = CompositeSubscription() 19 | 20 | /** 21 | * subscribes the given observable with the given lambdas. 22 | * Per default the presenters default [SchedulerTransformer] will be applied 23 | */ 24 | protected fun subscribe(observable: Observable, 25 | onNext: (M) -> Unit, 26 | onError: (Throwable) -> Unit, 27 | onCompleted: (() -> Unit)? = null, 28 | scheduler: SchedulerTransformer = schedulerTransformer) { 29 | 30 | 31 | val o = scheduler.schedule(observable) 32 | .doOnError { 33 | Timber.e(it, "Error caught") 34 | } 35 | 36 | if (onCompleted != null) { 37 | subscriptions.add(o.subscribe(onNext, onError, onCompleted)) 38 | } else { 39 | subscriptions.add(o.subscribe(onNext, onError)) 40 | } 41 | } 42 | 43 | private fun unsubscribe(recreateComposite: Boolean) { 44 | if (!subscriptions.isUnsubscribed) { 45 | subscriptions.unsubscribe() 46 | if (recreateComposite) subscriptions = CompositeSubscription() 47 | } 48 | } 49 | 50 | protected fun unsubscribe() { 51 | unsubscribe(true) 52 | } 53 | 54 | override fun detachView(retainInstance: Boolean) { 55 | super.detachView(retainInstance) 56 | if (!retainInstance) { 57 | unsubscribe(false) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/SchedulerTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util 2 | 3 | import rx.Observable 4 | import rx.android.schedulers.AndroidSchedulers 5 | import rx.schedulers.Schedulers 6 | 7 | /** 8 | * 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | interface SchedulerTransformer { 13 | 14 | fun schedule(observable: Observable): Observable 15 | } 16 | 17 | class DefaultSchedulerTransformer : SchedulerTransformer { 18 | 19 | override fun schedule(observable: Observable): Observable = 20 | observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/StableIdListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util 2 | 3 | import com.hannesdorfmann.adapterdelegates2.AdapterDelegatesManager 4 | import com.hannesdorfmann.adapterdelegates2.ListDelegationAdapter 5 | 6 | /** 7 | * 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | class StableIdListAdapter>(delegatesManager: AdapterDelegatesManager, private val idDeterminer: (T, Int) -> Long) : ListDelegationAdapter( 12 | delegatesManager) { 13 | 14 | init { 15 | setHasStableIds(true) 16 | } 17 | 18 | override fun getItemId(position: Int): Long { 19 | return idDeterminer(items, position) 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/ViewExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util 2 | 3 | import android.content.Context 4 | import android.support.annotation.IdRes 5 | import android.view.View 6 | import android.view.inputmethod.InputMethodManager 7 | 8 | 9 | fun View.findView(@IdRes id: Int) = this.findViewById(id) as T 10 | 11 | fun View.hideKeyboard() { 12 | (context.getSystemService( 13 | Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow( 14 | windowToken, 0) 15 | } 16 | 17 | fun View.showKeyboard() { 18 | (context.getSystemService( 19 | Context.INPUT_METHOD_SERVICE) as InputMethodManager).toggleSoftInput( 20 | InputMethodManager.SHOW_FORCED, 0) 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/lce/LceView.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util.lce 2 | 3 | import android.support.annotation.StringRes 4 | import com.hannesdorfmann.mosby.mvp.MvpView 5 | 6 | /** 7 | * LCE (Loading-Content-Error) MVP View 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | interface LceView : MvpView { 12 | 13 | /** 14 | * show the loading indicator 15 | */ 16 | fun showLoading() 17 | 18 | /** 19 | * Show the error indicator 20 | */ 21 | fun showError(@StringRes errorMsgRes: Int) 22 | 23 | /** 24 | * Show the content with the given data 25 | */ 26 | fun showContent(data: M) 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/lce/LceViewState.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util.lce 2 | 3 | import android.support.annotation.StringRes 4 | import com.hannesdorfmann.mosby.mvp.viewstate.ViewState 5 | 6 | /** 7 | * ViewState implementation for [LceView] 8 | * 9 | * @author Hannes Dorfmann 10 | */ 11 | // TODO test 12 | class LceViewState : ViewState> { 13 | 14 | private enum class State { 15 | SHOW_LOADING, SHOW_ERROR, SHOW_CONTENT 16 | } 17 | 18 | private var data: M? = null 19 | private var errorMsg: Int = -1 20 | private var state = State.SHOW_LOADING 21 | 22 | fun showContent(data: M) { 23 | this.data = data 24 | this.state = State.SHOW_CONTENT 25 | this.errorMsg = -1 26 | } 27 | 28 | fun showError(@StringRes errorMsgRes: Int) { 29 | this.errorMsg = errorMsgRes 30 | this.state = State.SHOW_ERROR 31 | this.data = null 32 | } 33 | 34 | fun showLoading() { 35 | this.errorMsg = -1 36 | this.data = null 37 | this.state = State.SHOW_LOADING 38 | } 39 | 40 | override fun apply(view: LceView, retained: Boolean) = 41 | when (state) { 42 | State.SHOW_CONTENT -> view.showContent(data!!) 43 | State.SHOW_LOADING -> view.showLoading() 44 | State.SHOW_ERROR -> view.showError(errorMsg) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/picasso/CircleImageTransformation.java: -------------------------------------------------------------------------------- 1 | package com.openconference.util.picasso; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapShader; 5 | import android.graphics.Canvas; 6 | import android.graphics.Matrix; 7 | import android.graphics.Paint; 8 | import android.graphics.RectF; 9 | import android.graphics.Shader; 10 | import com.squareup.picasso.Transformation; 11 | 12 | /** 13 | * Created by codezjx on 2016/5/4. 14 | */ 15 | public class CircleImageTransformation implements Transformation { 16 | 17 | /** 18 | * A unique key for the transformation, used for caching purposes. 19 | */ 20 | private static final String KEY = "circleImageTransformation"; 21 | 22 | @Override 23 | public Bitmap transform(Bitmap source) { 24 | 25 | int minEdge = Math.min(source.getWidth(), source.getHeight()); 26 | int dx = (source.getWidth() - minEdge) / 2; 27 | int dy = (source.getHeight() - minEdge) / 2; 28 | 29 | // Init shader 30 | Shader shader = new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 31 | Matrix matrix = new Matrix(); 32 | matrix.setTranslate(-dx, -dy); // Move the target area to center of the source bitmap 33 | shader.setLocalMatrix(matrix); 34 | 35 | // Init paint 36 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 37 | paint.setShader(shader); 38 | 39 | // Create and draw circle bitmap 40 | Bitmap output = Bitmap.createBitmap(minEdge, minEdge, source.getConfig()); 41 | Canvas canvas = new Canvas(output); 42 | canvas.drawOval(new RectF(0, 0, minEdge, minEdge), paint); 43 | 44 | // Recycle the source bitmap, because we already generate a new one 45 | source.recycle(); 46 | 47 | return output; 48 | } 49 | 50 | @Override 51 | public String key() { 52 | return KEY; 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/picasso/PicassoScrollListener.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util.picasso 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import com.squareup.picasso.Picasso 5 | 6 | /** 7 | * This is a scroll listener that listens for scroll events on recyclerviews 8 | * to avoid picasso loading images while scrolling (avoid laggy scroll behavior) 9 | */ 10 | class PicassoScrollListener(private val picasso: Picasso) : RecyclerView.OnScrollListener() { 11 | 12 | 13 | companion object { 14 | val TAG = "PicassoScrollTag" 15 | } 16 | 17 | private var previousScrollState = RecyclerView.SCROLL_STATE_IDLE 18 | private var scrollingFirstTime = true 19 | 20 | init { 21 | picasso.resumeTag(TAG) 22 | } 23 | 24 | override fun onScrollStateChanged(view: RecyclerView?, scrollState: Int) { 25 | if (scrollingFirstTime) { 26 | resume() 27 | scrollingFirstTime = false 28 | } 29 | 30 | // TO the picasso staff 31 | if (!isScrolling(scrollState) && isScrolling(previousScrollState)) { 32 | resume() 33 | } 34 | 35 | if (isScrolling(scrollState) && !isScrolling(previousScrollState)) { 36 | pause() 37 | } 38 | 39 | previousScrollState = scrollState 40 | 41 | } 42 | 43 | private inline fun isScrolling(scrollState: Int): Boolean { 44 | return scrollState == RecyclerView.SCROLL_STATE_DRAGGING || scrollState == RecyclerView.SCROLL_STATE_SETTLING 45 | } 46 | 47 | private inline fun resume() { 48 | picasso.resumeTag(TAG) 49 | } 50 | 51 | private inline fun pause() { 52 | picasso.pauseTag(TAG) 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/openconference/util/ui/SquaredImageView.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.util.ui 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.ImageView 6 | 7 | /** 8 | * An image view with square aspect ratio (respecting width). 9 | * @author Hannes Dorfmann 10 | */ 11 | class SquaredImageView : ImageView { 12 | constructor(context: Context) : super(context) { 13 | } 14 | 15 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 16 | } 17 | 18 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 19 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 20 | setMeasuredDimension(measuredWidth, measuredWidth) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/animator/add_to_schedule_line_1_move.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/animator/add_to_schedule_line_1_trim.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/animator/add_to_schedule_line_2_move.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/animator/add_to_schedule_line_2_untrim.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/animator/add_to_schedule_rotate.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/animator/remove_from_schedule_line_1_move.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/animator/remove_from_schedule_line_1_untrim.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/animator/remove_from_schedule_line_2_move.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/animator/remove_from_schedule_line_2_trim.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/animator/remove_from_schedule_rotate.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_notification_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/app/src/main/res/drawable-hdpi/ic_notification_small.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_notification_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/app/src/main/res/drawable-mdpi/ic_notification_small.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_notification_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/app/src/main/res/drawable-xhdpi/ic_notification_small.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_notification_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/app/src/main/res/drawable-xxhdpi/ic_notification_small.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/app/src/main/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_notification_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/app/src/main/res/drawable-xxxhdpi/ic_notification_small.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/avd_add_to_schedule.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/avd_remove_from_schedule.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_to_schedule_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bio.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_link.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_location.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_my_schedule.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_person.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_remove_from_schedule_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search_big.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sessions.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sessions_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_speakers.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_time.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_twitter.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_work.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_profile_pic_placeholder.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/separator_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/viewpager_separator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 15 | 20 | 21 | 22 | 23 | 24 | 27 | 29 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 25 | 26 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_session_details.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_sessions.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 14 | 15 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_twitter_timeline.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 17 | 18 | 19 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_details_icon_link_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 21 | 22 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_details_icon_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 22 | 23 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_search_speaker.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 21 | 22 | 31 | 32 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_session_date_sticky_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 18 | 19 | 20 | 28 | 29 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_session_details_date.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 21 | 22 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_session_details_description.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_session_details_location.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 20 | 21 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_session_details_separator.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_session_details_speaker.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 21 | 22 | 31 | 32 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_speaker.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_speaker_details_jobinfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 22 | 23 | 32 | 33 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/transition/auto.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | OpenConference 4 | 5 | Sessions 6 | Mein Kalender 7 | Referenten 8 | #droidconDE 9 | 10 | Ein unerwarteter Fehler ist aufgetreten. 11 | Ungültige Suchanfrage 12 | 13 | dd.MM 14 | Leider sind noch keine Sessions verfügbar 15 | Keine Sessions in deinem Kalender.\nHier klicken um Sessions hinzuzufügen. 16 | Noch keine Daten für diese Session verfügbar 17 | 18 | Session hinzugefügt. Du bekommst eine Erinnerung 10 Minuten bevor die Session startet. 19 | Ein Fehler ist aufgetreten. Bitte wiederholen. 20 | Ein Fehler ist aufgetreten. Bitte wiederholen. 21 | Leider sind noch keine Daten vorhanden. 22 | 23 | Eine Session startet bald 24 | %1$s startet um %2$s 25 | 26 | 27 | Lizenzen 28 | Suche 29 | 30 | 31 | Suchbegriff ... 32 | Keine Ergebnisse gefunden 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/values-land/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 3 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #222222 4 | #000000 5 | #8ED300 6 | #78B300 7 | 8 | #6a6a6a 9 | #919191 10 | 11 | 12 | #F5F5F5 13 | #B9323232 14 | 15 | 16 | #A8A8A8 17 | #C2C2C2 18 | 19 | 20 | #E0E0E0 21 | #D6D6D6 22 | 23 | 24 | #1E222222 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 7 | 6dp 8 | 9 | 4dp 10 | 10dp 11 | 12 | 72dp 13 | 40dp 14 | 56dp 15 | 16 | 16dp 17 | 128dp 18 | 128dp 19 | 20 | 4dp 21 | 22 | 23 | 32dp 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/fractions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0 4 | 0.96666664 5 | 0.033333335 6 | 0.768 7 | 0.205 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2 5 | @android:integer/config_mediumAnimTime 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | OpenConference 3 | 4 | Sessions 5 | My Schedule 6 | Speakers 7 | #droidconDE 8 | 9 | 10 | An unexpected error has occurred. 11 | The inserted query string is not valid 12 | 13 | MM/dd 14 | 15 | No sessions available yet. Stay tuned. 16 | No session in your schedule yet.\nClick here to browse all available sessions. 17 | No data for this session available yet 18 | Session has been added. You will receive a Notification 10 minutes before Session starts. 19 | An error has occurred. Please retry. 20 | An error has occurred. Please retry. 21 | No data for this speaker available yet 22 | 23 | 24 | A session starts soon 25 | %1$s starts at %2$s 26 | 27 | 28 | License 29 | Search 30 | 31 | Search ... 32 | No results found 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/playground/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/playground/java/com/openconfernce/MockNetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.openconfernce 2 | 3 | import android.content.Context 4 | import com.openconference.dagger.NetworkModule 5 | import com.openconference.model.backend.schedule.BackendScheduleAdapter 6 | import com.openconfernce.mock.MockSchedulerAdapterStub 7 | 8 | /** 9 | * This just mocks some data 10 | * 11 | * @author Hannes Dorfmann 12 | */ 13 | class MockNetworkModule(c: Context) : NetworkModule(c) { 14 | 15 | override fun provideBackendAdapter(): BackendScheduleAdapter = MockSchedulerAdapterStub() 16 | } -------------------------------------------------------------------------------- /app/src/playground/java/com/openconfernce/PlaygroundApp.kt: -------------------------------------------------------------------------------- 1 | package com.openconfernce 2 | 3 | import com.openconference.OpenConfApp 4 | 5 | /** 6 | * Application for demo purpose displaying som mocked data 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | class PlaygroundApp : OpenConfApp() { 11 | 12 | override fun buildApplicationComponent() = 13 | super.buildApplicationComponent().networkModule(MockNetworkModule(this)) 14 | 15 | } -------------------------------------------------------------------------------- /app/src/test/java/com/openconference/TestApplication.kt: -------------------------------------------------------------------------------- 1 | package com.openconference 2 | 3 | import android.app.Application 4 | 5 | /** 6 | * A test application instance without any additional dependencies (like Robolectric) 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | class TestApplication : Application() { 11 | } -------------------------------------------------------------------------------- /app/src/test/java/com/openconference/model/database/InstantParcelableTypeAdapterTest.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.database 2 | 3 | import android.os.Parcel 4 | import org.junit.Test 5 | import org.mockito.Mockito 6 | import org.threeten.bp.Instant 7 | import kotlin.test.assertEquals 8 | 9 | class InstantParcelableTypeAdapterTest { 10 | 11 | @Test 12 | fun writeToParcel() { 13 | val now = Instant.now() 14 | val nowMillis = now.toEpochMilli() 15 | 16 | val typeAdapter = InstantParcelableTypeAdapter() 17 | val parcel = Mockito.mock(Parcel::class.java) 18 | 19 | // Write to parcel 20 | typeAdapter.toParcel(now, parcel) 21 | Mockito.verify(parcel).writeLong(nowMillis) 22 | 23 | // Read from Parcel 24 | Mockito.`when`(parcel.readLong()).thenReturn(nowMillis) 25 | val instantFromParcel = typeAdapter.fromParcel(parcel) 26 | 27 | assertEquals(now, instantFromParcel) 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/test/java/com/openconference/model/notification/NotificationSchedulerCommandTest.kt: -------------------------------------------------------------------------------- 1 | package com.openconference.model.notification 2 | 3 | import com.openconference.model.Session 4 | import org.junit.Test 5 | import org.mockito.Mockito 6 | 7 | /** 8 | * 9 | * 10 | * @author Hannes Dorfmann 11 | */ 12 | class NotificationSchedulerCommandTest { 13 | 14 | @Test 15 | fun removeScheduledNotification() { 16 | val notificationScheduler = Mockito.mock(NotificationScheduler::class.java) 17 | val session = Mockito.mock(Session::class.java) 18 | 19 | val command = RemoveScheduledNotificationCommand(session, notificationScheduler) 20 | command.execute() 21 | 22 | Mockito.verify(notificationScheduler, Mockito.only()).removeNotification(session) 23 | } 24 | 25 | 26 | @Test 27 | fun addOrRescheduleNotification() { 28 | val notificationScheduler = Mockito.mock(NotificationScheduler::class.java) 29 | val session = Mockito.mock(Session::class.java) 30 | 31 | val command = AddOrRescheduleNotificationCommand(session, notificationScheduler) 32 | command.execute() 33 | 34 | Mockito.verify(notificationScheduler, Mockito.only()).addOrRescheduleNotification(session) 35 | } 36 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenConference/OpenConference-android/b429bbf59b284ff25840f8d608d5fa9e270d4bc8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 26 13:48:29 CEST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':stickyheaders' 2 | project(':stickyheaders').projectDir = new File('app/libraries/stickyheaders') --------------------------------------------------------------------------------