├── app2 ├── .gitignore └── src │ ├── main │ ├── ic_launcher-playstore.png │ ├── res │ │ ├── drawable-xhdpi │ │ │ ├── logo.png │ │ │ ├── ic_notification.png │ │ │ └── ic_shortcut_editor.png │ │ ├── drawable-xxhdpi │ │ │ ├── logo.png │ │ │ ├── ic_app.png │ │ │ ├── ic_notification.png │ │ │ └── ic_shortcut_editor.png │ │ ├── drawable-xxxhdpi │ │ │ ├── logo.png │ │ │ ├── ic_notification.png │ │ │ └── ic_shortcut_editor.png │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── values │ │ │ ├── ids.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── dimens.xml │ │ │ └── colors.xml │ │ ├── layout │ │ │ ├── activity_compose.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_launcher.xml │ │ │ ├── fragment_splash.xml │ │ │ ├── item_1line_text.xml │ │ │ ├── item_subheader.xml │ │ │ └── item_2line_text.xml │ │ ├── drawable │ │ │ ├── bg_splash.xml │ │ │ ├── ic_send_white.xml │ │ │ ├── ic_send_black_24dp.xml │ │ │ ├── ic_send_white_24dp.xml │ │ │ ├── ic_send_white_transparent_24dp.xml │ │ │ ├── ic_view_headline_black_24dp.xml │ │ │ ├── ic_close_black_24dp.xml │ │ │ ├── ic_close_white_24dp.xml │ │ │ ├── ic_baseline_account_circle_24.xml │ │ │ └── ic_settings_black_24dp.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── color │ │ │ ├── text_color_accent_selector.xml │ │ │ └── text_color_primary_selector.xml │ │ ├── animator │ │ │ └── appbar_elevation.xml │ │ └── navigation │ │ │ └── nav_main.xml │ └── java │ │ └── net │ │ └── yslibrary │ │ └── monotweety │ │ ├── base │ │ ├── CoroutineDispatchers.kt │ │ └── LazyInstanceHolder.kt │ │ ├── ui │ │ ├── base │ │ │ ├── FragmentExtensions.kt │ │ │ ├── groupie │ │ │ │ └── GroupieViewHolder.kt │ │ │ ├── ViewExtensions.kt │ │ │ ├── ViewModelExtensions.kt │ │ │ ├── NavigationExtensions.kt │ │ │ ├── ContextExtensions.kt │ │ │ └── Debounce.kt │ │ ├── di │ │ │ ├── HasComponent.kt │ │ │ └── ViewModelFactory.kt │ │ ├── UserUiSubcomponentModule.kt │ │ ├── AppUiSubcomponentModule.kt │ │ ├── loginform │ │ │ └── LoginFormFragmentComponent.kt │ │ ├── settings │ │ │ ├── SettingsFragmentComponent.kt │ │ │ └── widget │ │ │ │ ├── SubHeaderItem.kt │ │ │ │ ├── OneLineTextItem.kt │ │ │ │ ├── TwoLineTextItem.kt │ │ │ │ └── DividerItemDecoration.kt │ │ ├── footereditor │ │ │ └── FooterEditorFragmentComponent.kt │ │ ├── login │ │ │ └── LoginFragmentComponent.kt │ │ ├── splash │ │ │ └── SplashFragmentComponent.kt │ │ ├── compose │ │ │ ├── ComposeTweetDialogFragmentComponent.kt │ │ │ └── ComposeActivityComponent.kt │ │ ├── main │ │ │ └── MainActivityComponent.kt │ │ └── launcher │ │ │ ├── LauncherActivityComponent.kt │ │ │ └── LauncherActivity.kt │ │ ├── domain │ │ ├── user │ │ │ ├── FetchUser.kt │ │ │ └── ObserveUser.kt │ │ ├── setting │ │ │ ├── UpdateFooterSettings.kt │ │ │ ├── UpdateNotificationEnabled.kt │ │ │ ├── ObserveSettings.kt │ │ │ ├── UpdateTimelineAppSetting.kt │ │ │ ├── GetTwitterAppByPackageName.kt │ │ │ └── GetTimelineTwitterApp.kt │ │ ├── session │ │ │ ├── Logout.kt │ │ │ └── ObserveSession.kt │ │ ├── twitterapp │ │ │ └── GetInstalledTwitterApps.kt │ │ ├── status │ │ │ └── UpdateStatus.kt │ │ └── UserScopeDomainModule.kt │ │ ├── notification │ │ ├── NotificationServiceComponent.kt │ │ ├── BroadcastReceiverExtensions.kt │ │ ├── BootCompletedReceiver.kt │ │ └── PackageReplacedReceiver.kt │ │ ├── Config.kt │ │ ├── AppInitializer.kt │ │ ├── analytics │ │ ├── Analytics.kt │ │ └── CrashReportingTree.kt │ │ ├── AppModule.kt │ │ ├── AppComponent.kt │ │ └── StringExtensions.kt │ ├── release │ ├── res │ │ └── values │ │ │ └── strings.xml │ └── java │ │ └── net │ │ └── yslibrary │ │ └── monotweety │ │ └── AppInitializerProvider.kt │ └── debug │ ├── res │ └── values │ │ └── strings.xml │ └── java │ └── net │ └── yslibrary │ └── monotweety │ ├── AppInitializerProvider.kt │ └── DebugAppInitializer.kt ├── data ├── .gitignore ├── consumer-rules.pro ├── src │ └── main │ │ ├── java │ │ └── net │ │ │ └── yslibrary │ │ │ └── monotweety │ │ │ └── data │ │ │ ├── twitterapp │ │ │ ├── AppInfo.kt │ │ │ └── TwitterAppDataModule.kt │ │ │ ├── user │ │ │ ├── User.kt │ │ │ ├── remote │ │ │ │ └── UserRemoteGateway.kt │ │ │ └── UserRepository.kt │ │ │ ├── settings │ │ │ ├── Settings.kt │ │ │ └── SettingsDataModule.kt │ │ │ ├── session │ │ │ ├── Session.kt │ │ │ └── SessionDataModule.kt │ │ │ ├── auth │ │ │ ├── AuthDataModule.kt │ │ │ └── AuthFlow.kt │ │ │ ├── status │ │ │ ├── StatusDataModule.kt │ │ │ └── StatusRepository.kt │ │ │ └── UserScopeDataModule.kt │ │ └── proto │ │ ├── session_preferences.proto │ │ ├── user_preferences.proto │ │ └── settings_preferences.proto └── proguard-rules.pro ├── .wakatime-project ├── app ├── .gitignore └── src │ ├── test │ ├── resources │ │ └── net │ │ │ └── yslibrary │ │ │ └── monotweety │ │ │ └── robolectric.properties │ └── java │ │ └── net │ │ └── yslibrary │ │ └── monotweety │ │ ├── TestApp.kt │ │ ├── TestExtensions.kt │ │ ├── appdata │ │ ├── license │ │ │ └── LicenseRepositoryImplTest.kt │ │ └── session │ │ │ └── SessionRepositoryImplTest.kt │ │ └── TestData.kt │ ├── main │ ├── res │ │ ├── drawable-xhdpi │ │ │ ├── logo.png │ │ │ ├── ic_notification.png │ │ │ └── ic_shortcut_editor.png │ │ ├── drawable-xxhdpi │ │ │ ├── logo.png │ │ │ ├── ic_app.png │ │ │ ├── ic_notification.png │ │ │ └── ic_shortcut_editor.png │ │ ├── drawable-xxxhdpi │ │ │ ├── logo.png │ │ │ ├── ic_notification.png │ │ │ └── ic_shortcut_editor.png │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ ├── values │ │ │ ├── ids.xml │ │ │ ├── dimens.xml │ │ │ └── colors.xml │ │ ├── values-land │ │ │ └── dimens.xml │ │ ├── layout │ │ │ ├── activity_launcher.xml │ │ │ ├── vh_1line_text.xml │ │ │ ├── vh_subheader.xml │ │ │ ├── controller_license.xml │ │ │ ├── vh_switch.xml │ │ │ ├── controller_changelog.xml │ │ │ ├── controller_progress.xml │ │ │ ├── vh_2line_text.xml │ │ │ ├── controller_compose_status.xml │ │ │ ├── controller_splash.xml │ │ │ ├── controller_setting.xml │ │ │ ├── vh_previous_status.xml │ │ │ ├── dialog_footer_editor.xml │ │ │ ├── vh_2line_switch.xml │ │ │ ├── vh_editor.xml │ │ │ ├── activity_main.xml │ │ │ └── controller_login.xml │ │ ├── drawable │ │ │ ├── ic_send_white.xml │ │ │ ├── ic_send_black_24dp.xml │ │ │ ├── ic_send_white_24dp.xml │ │ │ ├── ic_send_white_transparent_24dp.xml │ │ │ ├── ic_view_headline_black_24dp.xml │ │ │ ├── ic_close_black_24dp.xml │ │ │ ├── ic_close_white_24dp.xml │ │ │ └── ic_settings_black_24dp.xml │ │ ├── menu │ │ │ └── menu_compose_status.xml │ │ └── values-w820dp │ │ │ └── dimens.xml │ └── java │ │ └── net │ │ └── yslibrary │ │ └── monotweety │ │ ├── presentation │ │ └── login │ │ │ └── LoginFragment.kt │ │ ├── base │ │ ├── Clock.kt │ │ ├── HasComponent.kt │ │ ├── di │ │ │ ├── ActivityScope.kt │ │ │ ├── ServiceScope.kt │ │ │ ├── ControllerScope.kt │ │ │ └── Names.kt │ │ ├── ClockImpl.kt │ │ ├── ServiceExtensions.kt │ │ ├── ProgressController.kt │ │ ├── EventBus.kt │ │ ├── RxExtensions.kt │ │ └── ObjectWatcherDelegate.kt │ │ ├── activity │ │ ├── ActionBarProvider.kt │ │ ├── compose │ │ │ └── ComposeActivityComponent.kt │ │ ├── ActivityModule.kt │ │ ├── main │ │ │ └── MainActivityComponent.kt │ │ └── shortcut │ │ │ ├── ShortcutActivity.kt │ │ │ └── CreateShortcutActivity.kt │ │ ├── event │ │ ├── NewIntent.kt │ │ └── ActivityResult.kt │ │ ├── appdata │ │ ├── status │ │ │ ├── OverlongStatusException.kt │ │ │ ├── StatusRepository.kt │ │ │ ├── remote │ │ │ │ ├── StatusRemoteRepository.kt │ │ │ │ ├── TwitterApiClient.kt │ │ │ │ └── TestStatusRemoteRepositoryImpl.kt │ │ │ ├── Tweet.kt │ │ │ ├── StatusModule.kt │ │ │ └── StatusRepositoryImpl.kt │ │ ├── license │ │ │ ├── LicenseRepository.kt │ │ │ └── LicenseModule.kt │ │ ├── user │ │ │ ├── User.kt │ │ │ ├── remote │ │ │ │ └── UserRemoteRepository.kt │ │ │ ├── local │ │ │ │ ├── resolver │ │ │ │ │ ├── UserSQLiteTypeMapping.kt │ │ │ │ │ ├── UserDeleteResolver.kt │ │ │ │ │ ├── UserGetResolver.kt │ │ │ │ │ └── UserPutResolver.kt │ │ │ │ └── UserLocalRepository.kt │ │ │ ├── UserRepository.kt │ │ │ └── UserModule.kt │ │ ├── appinfo │ │ │ ├── AppInfo.kt │ │ │ ├── AppInfoManager.kt │ │ │ └── AppInfoModule.kt │ │ ├── session │ │ │ ├── SessionRepository.kt │ │ │ ├── SessionModule.kt │ │ │ └── SessionRepositoryImpl.kt │ │ ├── local │ │ │ ├── CursorExtensions.kt │ │ │ ├── StorIOExtensions.kt │ │ │ ├── DbOpenHelper.kt │ │ │ └── LocalModule.kt │ │ ├── setting │ │ │ ├── SettingDataManager.kt │ │ │ └── SettingModule.kt │ │ └── DataModule.kt │ │ ├── AppGlideModule.kt │ │ ├── license │ │ ├── LicenseViewModel.kt │ │ ├── LicenseComponent.kt │ │ ├── domain │ │ │ └── GetLicenses.kt │ │ └── LicenseViewModule.kt │ │ ├── logout │ │ ├── LogoutComponent.kt │ │ └── LogoutService.kt │ │ ├── setting │ │ ├── SettingComponent.kt │ │ ├── domain │ │ │ ├── NotificationEnabledManager.kt │ │ │ ├── GetInstalledSupportedApps.kt │ │ │ ├── SelectedTimelineAppInfoManager.kt │ │ │ └── FooterStateManager.kt │ │ └── adapter │ │ │ └── SubHeaderDividerDecoration.kt │ │ ├── changelog │ │ ├── ChangelogComponent.kt │ │ └── ChangelogViewModule.kt │ │ ├── notification │ │ ├── NotificationComponent.kt │ │ ├── BootCompletedReceiver.kt │ │ ├── PackageReplacedReceiver.kt │ │ └── NotificationServiceModule.kt │ │ ├── login │ │ ├── LoginComponent.kt │ │ ├── domain │ │ │ ├── IsLoggedIn.kt │ │ │ └── DoLogout.kt │ │ ├── LoginViewModule.kt │ │ └── LoginViewModel.kt │ │ ├── splash │ │ ├── SplashComponent.kt │ │ ├── SplashViewModule.kt │ │ └── SplashViewModel.kt │ │ ├── status │ │ ├── ComposeStatusComponent.kt │ │ ├── domain │ │ │ ├── UpdateStatus.kt │ │ │ └── CheckStatusLength.kt │ │ └── ComposeStatusViewModule.kt │ │ ├── Config.kt │ │ ├── analytics │ │ └── CrashReportingTree.kt │ │ ├── util │ │ └── StringExtensions.kt │ │ └── AppComponent.kt │ ├── release │ ├── res │ │ ├── values │ │ │ └── strings.xml │ │ └── xml │ │ │ └── shortcuts.xml │ └── java │ │ └── net │ │ └── yslibrary │ │ └── monotweety │ │ └── Modules.kt │ └── debug │ ├── res │ ├── values │ │ └── strings.xml │ └── xml │ │ └── shortcuts.xml │ └── java │ └── net │ └── yslibrary │ └── monotweety │ ├── Modules.kt │ ├── DebugAppModule.kt │ └── DebugAppLifecycleCallbacks.kt ├── di-common ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── net │ │ └── yslibrary │ │ └── monotweety │ │ └── di │ │ ├── UserScope.kt │ │ ├── ActivityScope.kt │ │ ├── FragmentScope.kt │ │ └── ServiceScope.kt └── build.gradle.kts ├── includedBuild ├── build-helper │ ├── .gitignore │ ├── settings.gradle.kts │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── net │ │ │ └── yslibrary │ │ │ └── monotweety │ │ │ └── buildhelper │ │ │ └── secrets.kt │ └── build.gradle.kts └── dependencies │ ├── .gitignore │ ├── settings.gradle.kts │ ├── src │ └── main │ │ └── java │ │ └── net │ │ └── yslibrary │ │ └── monotweety │ │ └── dependencies │ │ └── DependenciesPlugin.kt │ └── build.gradle.kts ├── debug.keystore ├── assets ├── header.png ├── icon_big.png ├── screens.png ├── screenshots │ ├── screenshot_editor_1.png │ ├── screenshot_editor_2.png │ ├── screenshot_setting.png │ ├── screenshot_splash.png │ ├── screenshot_quicksettings.png │ ├── screenshot_notification_1.png │ └── screenshot_notification_2.png ├── description_ja.txt └── description_en.txt ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── lint.xml ├── .idea └── codeStyles │ └── codeStyleConfig.xml ├── codecov.yml ├── code_quality └── findbugs_filter.xml ├── secret.properties.template ├── .gitignore ├── .github └── FUNDING.yml ├── _wercker.yml ├── gradle.properties └── settings.gradle.kts /app2/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.wakatime-project: -------------------------------------------------------------------------------- 1 | monotweety -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /di-common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /includedBuild/build-helper/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /includedBuild/dependencies/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /includedBuild/build-helper/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | // empty 2 | -------------------------------------------------------------------------------- /includedBuild/dependencies/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | // empty 2 | -------------------------------------------------------------------------------- /app/src/test/resources/net/yslibrary/monotweety/robolectric.properties: -------------------------------------------------------------------------------- 1 | sdk=23 2 | -------------------------------------------------------------------------------- /debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/debug.keystore -------------------------------------------------------------------------------- /assets/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/assets/header.png -------------------------------------------------------------------------------- /assets/icon_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/assets/icon_big.png -------------------------------------------------------------------------------- /assets/screens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/assets/screens.png -------------------------------------------------------------------------------- /data/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class * extends com.google.protobuf.GeneratedMessageLite { *; } 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app2/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/drawable-xhdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/drawable-xxhdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/drawable-xxxhdpi/logo.png -------------------------------------------------------------------------------- /app2/src/main/res/drawable-xhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/drawable-xhdpi/logo.png -------------------------------------------------------------------------------- /app2/src/main/res/drawable-xxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/drawable-xxhdpi/logo.png -------------------------------------------------------------------------------- /assets/screenshots/screenshot_editor_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/assets/screenshots/screenshot_editor_1.png -------------------------------------------------------------------------------- /assets/screenshots/screenshot_editor_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/assets/screenshots/screenshot_editor_2.png -------------------------------------------------------------------------------- /assets/screenshots/screenshot_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/assets/screenshots/screenshot_setting.png -------------------------------------------------------------------------------- /assets/screenshots/screenshot_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/assets/screenshots/screenshot_splash.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/drawable-xxhdpi/ic_app.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app2/src/main/res/drawable-xxhdpi/ic_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/drawable-xxhdpi/ic_app.png -------------------------------------------------------------------------------- /app2/src/main/res/drawable-xxxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/drawable-xxxhdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /assets/screenshots/screenshot_quicksettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/assets/screenshots/screenshot_quicksettings.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /assets/screenshots/screenshot_notification_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/assets/screenshots/screenshot_notification_1.png -------------------------------------------------------------------------------- /assets/screenshots/screenshot_notification_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/assets/screenshots/screenshot_notification_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app2/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_shortcut_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/drawable-xhdpi/ic_shortcut_editor.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/drawable-xxxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app2/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app2/src/main/res/drawable-xxxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/drawable-xxxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app2/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/presentation/login/LoginFragment.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.presentation.login 2 | 3 | class LoginFragment { 4 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_shortcut_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/drawable-xxhdpi/ic_shortcut_editor.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_shortcut_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app/src/main/res/drawable-xxxhdpi/ic_shortcut_editor.png -------------------------------------------------------------------------------- /app/src/test/java/net/yslibrary/monotweety/TestApp.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import android.app.Application 4 | 5 | class TestApp : Application() -------------------------------------------------------------------------------- /app2/src/main/res/drawable-xhdpi/ic_shortcut_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/drawable-xhdpi/ic_shortcut_editor.png -------------------------------------------------------------------------------- /app2/src/main/res/drawable-xxhdpi/ic_shortcut_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/drawable-xxhdpi/ic_shortcut_editor.png -------------------------------------------------------------------------------- /app2/src/main/res/drawable-xxxhdpi/ic_shortcut_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/drawable-xxxhdpi/ic_shortcut_editor.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/Clock.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base 2 | 3 | interface Clock { 4 | fun currentTimeMillis(): Long 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 410dp 4 | 5 | -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yshrsmz/monotweety/HEAD/app2/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/HasComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base 2 | 3 | interface HasComponent { 4 | val component: C 5 | } 6 | -------------------------------------------------------------------------------- /app2/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #535353 4 | -------------------------------------------------------------------------------- /app/src/release/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Monotweety 4 | 5 | -------------------------------------------------------------------------------- /app2/src/release/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Monotweety 4 | 5 | -------------------------------------------------------------------------------- /app/src/debug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | (dev)Monotweety 4 | 5 | -------------------------------------------------------------------------------- /app2/src/debug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | (dev)Monotweety 4 | 5 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /app/src/debug/java/net/yslibrary/monotweety/Modules.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | class Modules { 4 | companion object { 5 | fun appModule() = DebugAppModule() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app2/src/debug/java/net/yslibrary/monotweety/AppInitializerProvider.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | object AppInitializerProvider { 4 | fun get(): AppInitializer = DebugAppInitializer() 5 | } 6 | -------------------------------------------------------------------------------- /app2/src/release/java/net/yslibrary/monotweety/AppInitializerProvider.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | object AppInitializerProvider { 4 | fun get(): AppInitializer = AppInitializerImpl() 5 | } 6 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/twitterapp/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.twitterapp 2 | 3 | data class AppInfo( 4 | val name: String, 5 | val packageName: TwitterApp, 6 | ) 7 | -------------------------------------------------------------------------------- /di-common/src/main/java/net/yslibrary/monotweety/di/UserScope.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.di 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class UserScope 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/di/ActivityScope.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base.di 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class ActivityScope -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/di/ServiceScope.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base.di 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class ServiceScope -------------------------------------------------------------------------------- /di-common/src/main/java/net/yslibrary/monotweety/di/ActivityScope.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.di 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class ActivityScope 8 | -------------------------------------------------------------------------------- /di-common/src/main/java/net/yslibrary/monotweety/di/FragmentScope.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.di 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class FragmentScope 8 | -------------------------------------------------------------------------------- /di-common/src/main/java/net/yslibrary/monotweety/di/ServiceScope.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.di 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class ServiceScope 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/di/ControllerScope.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base.di 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class ControllerScope -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/ClockImpl.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base 2 | 3 | class ClockImpl : Clock { 4 | override fun currentTimeMillis(): Long { 5 | return System.currentTimeMillis() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/activity/ActionBarProvider.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.activity 2 | 3 | import androidx.appcompat.app.ActionBar 4 | 5 | interface ActionBarProvider { 6 | fun getSupportActionBar(): ActionBar? 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/event/NewIntent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.event 2 | 3 | import android.content.Intent 4 | import net.yslibrary.monotweety.base.EventBus 5 | 6 | data class NewIntent(val intent: Intent) : EventBus.Event -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-rc-1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /app/src/release/java/net/yslibrary/monotweety/Modules.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | /** 4 | * Created by yshrsmz on 2016/09/24. 5 | */ 6 | class Modules { 7 | companion object { 8 | fun appModule() = AppModule() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app2/src/main/res/layout/activity_compose.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | notify: 3 | slack: 4 | default: 5 | url: "secret:/XJnx2BO6H2CfikgHG3ZEFxxH55Wk7XaxIzBzBbygCJTVkFIXpME9rAuxL+U1MR/XdSMnO6eB2ADxtwutkeu6ZBy4dD2GJ/K137ASWPwASr1dmdDr7NNX58kA4c5yP3UDCB3gmwTlRZ+YgnleBEGAiaWL2SKlpdrC1CvvOxxxFg=" 6 | -------------------------------------------------------------------------------- /data/src/main/proto/session_preferences.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "net.yslibrary.monotweety.data.session"; 4 | option java_multiple_files = true; 5 | 6 | message SessionPreferences { 7 | string authToken = 1; 8 | string authTokenSecret = 2; 9 | } -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/status/OverlongStatusException.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.status 2 | 3 | class OverlongStatusException( 4 | val status: String, 5 | val length: Int 6 | ) : Exception("Message is too long to tweet: $length") 7 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/base/CoroutineDispatchers.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base 2 | 3 | import kotlin.coroutines.CoroutineContext 4 | 5 | interface CoroutineDispatchers { 6 | val main: CoroutineContext 7 | val background: CoroutineContext 8 | } 9 | -------------------------------------------------------------------------------- /app/src/test/java/net/yslibrary/monotweety/TestExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import android.app.Application 4 | import androidx.test.core.app.ApplicationProvider 5 | 6 | val targetApplication: Application 7 | get() = ApplicationProvider.getApplicationContext() 8 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/user/User.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.user 2 | 3 | data class User( 4 | val id: String, 5 | val name: String, 6 | val screenName: String, 7 | val profileImageUrl: String, 8 | val updatedAt: Long, 9 | ) 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/license/LicenseRepository.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.license 2 | 3 | import io.reactivex.Single 4 | import net.yslibrary.licenseadapter.Library 5 | 6 | interface LicenseRepository { 7 | fun get(): Single> 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/status/StatusRepository.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.status 2 | 3 | import io.reactivex.Completable 4 | 5 | interface StatusRepository { 6 | fun updateStatus(status: String, inReplyToStatusId: Long? = null): Completable 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/user/User.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.user 2 | 3 | data class User( 4 | val id: Long, 5 | val name: String, 6 | val screenName: String, 7 | val profileImageUrl: String, 8 | val _updatedAt: Long 9 | ) 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/user/remote/UserRemoteRepository.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.user.remote 2 | 3 | import io.reactivex.Single 4 | import net.yslibrary.monotweety.appdata.user.User 5 | 6 | interface UserRemoteRepository { 7 | fun get(): Single 8 | } 9 | -------------------------------------------------------------------------------- /code_quality/findbugs_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /gradle/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/base/FragmentExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.base 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import androidx.fragment.app.Fragment 5 | 6 | fun Fragment.requireAppCompatActivity(): AppCompatActivity = requireActivity() as AppCompatActivity 7 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/AppGlideModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import com.bumptech.glide.annotation.GlideModule 4 | 5 | 6 | /** 7 | * Created by yshrsmz on 2017/10/09. 8 | */ 9 | @GlideModule 10 | class AppGlideModule : com.bumptech.glide.module.AppGlideModule() { 11 | 12 | } -------------------------------------------------------------------------------- /app2/src/main/res/drawable/bg_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app2/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app2/src/main/res/color/text_color_accent_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app2/src/main/res/color/text_color_primary_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/event/ActivityResult.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.event 2 | 3 | import android.content.Intent 4 | import net.yslibrary.monotweety.base.EventBus 5 | 6 | data class ActivityResult( 7 | val requestCode: Int, 8 | val resultCode: Int, 9 | val data: Intent? 10 | ) : EventBus.Event 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/ServiceExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | 6 | fun Service.closeNotificationDrawer() { 7 | val intent = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) 8 | applicationContext.sendBroadcast(intent) 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/base/groupie/GroupieViewHolder.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.base.groupie 2 | 3 | import androidx.viewbinding.ViewBinding 4 | 5 | class GroupieViewHolder( 6 | @Suppress("MemberVisibilityCanBePrivate") val binding: T, 7 | ) : com.xwray.groupie.GroupieViewHolder(binding.root) 8 | -------------------------------------------------------------------------------- /data/src/main/proto/user_preferences.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "net.yslibrary.monotweety.data.user"; 4 | option java_multiple_files = true; 5 | 6 | message UserPreferences { 7 | string id = 1; 8 | string name = 2; 9 | string screenName = 3; 10 | string profileImageUrl = 4; 11 | int64 _updatedAt = 5; 12 | } -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/appinfo/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.appinfo 2 | 3 | data class AppInfo( 4 | val name: String, 5 | val packageName: String, 6 | val installed: Boolean 7 | ) { 8 | companion object { 9 | fun empty(): AppInfo = AppInfo("", "", false) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app2/src/main/res/drawable/ic_send_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/settings/Settings.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.settings 2 | 3 | data class Settings( 4 | val notificationEnabled: Boolean, 5 | val footerEnabled: Boolean, 6 | val footerText: String, 7 | val timelineAppEnabled: Boolean, 8 | val timelineAppPackageName: String, 9 | ) 10 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/di/HasComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.di 2 | 3 | import android.app.Activity 4 | 5 | interface HasComponent { 6 | val component: C 7 | } 8 | 9 | @Suppress("UNCHECKED_CAST") 10 | fun

Activity.getComponentProvider(): P { 11 | return (this as HasComponent

).component 12 | } 13 | -------------------------------------------------------------------------------- /includedBuild/dependencies/src/main/java/net/yslibrary/monotweety/dependencies/DependenciesPlugin.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.dependencies 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | 6 | class DependenciesPlugin : Plugin { 7 | override fun apply(target: Project) { 8 | // no-op 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/twitterapp/TwitterAppDataModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.twitterapp 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | 6 | @Module 7 | internal interface TwitterAppDataModule { 8 | @Binds 9 | fun bindTwitterAppRepository(impl: TwitterAppRepositoryImpl): TwitterAppRepository 10 | } 11 | -------------------------------------------------------------------------------- /secret.properties.template: -------------------------------------------------------------------------------- 1 | # 2 | # fabric config 3 | fabric.api.key=YOUR_FABRIC_API_KEY 4 | # 5 | # twitter config 6 | twitter.api.key=YOUR_TWITTER_API_KEY 7 | twitter.api.secret=YOUR_TWITTER_API_SECRET 8 | # 9 | # keystore 10 | keystoreFile=YOUR_KEYSTORE_FILENAME 11 | keystorePassword=YOUR_KEYSTORE_PASSWORD 12 | keyAlias=YOUR_KEY_ALIAS 13 | keyPassword=YOUR_KEY_PASSWORD -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/session/SessionRepository.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.session 2 | 3 | import com.gojuno.koptional.Optional 4 | import com.twitter.sdk.android.core.TwitterSession 5 | import io.reactivex.Single 6 | 7 | interface SessionRepository { 8 | fun getActiveSession(): Single> 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/status/remote/StatusRemoteRepository.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.status.remote 2 | 3 | 4 | import io.reactivex.Single 5 | import net.yslibrary.monotweety.appdata.status.Tweet 6 | 7 | interface StatusRemoteRepository { 8 | fun update(status: String, inReplyToStatusId: Long? = null): Single 9 | } 10 | -------------------------------------------------------------------------------- /app2/src/debug/java/net/yslibrary/monotweety/DebugAppInitializer.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import net.yslibrary.monotweety.analytics.CrashReportingTree 4 | import timber.log.Timber 5 | 6 | class DebugAppInitializer : AppInitializerImpl() { 7 | override fun initLogger() { 8 | Timber.plant(CrashReportingTree(), Timber.DebugTree()) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/appinfo/AppInfoManager.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.appinfo 2 | 3 | import io.reactivex.Single 4 | 5 | interface AppInfoManager { 6 | 7 | fun isInstalled(packageName: String): Single 8 | 9 | fun installedApps(): Single> 10 | 11 | fun appInfo(packageName: String): Single 12 | } 13 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/session/Session.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.session 2 | 3 | import com.codingfeline.twitter4kt.core.model.oauth1a.AccessToken 4 | 5 | data class Session( 6 | val authToken: String, 7 | val authTokenSecret: String, 8 | ) 9 | 10 | fun Session.toAccessToken(): AccessToken = AccessToken(authToken, authTokenSecret, "", "") 11 | -------------------------------------------------------------------------------- /data/src/main/proto/settings_preferences.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "net.yslibrary.monotweety.data.setting"; 4 | option java_multiple_files = true; 5 | 6 | message SettingsPreferences { 7 | bool notificationEnabled = 1; 8 | bool footerEnabled = 2; 9 | string footerText = 3; 10 | bool timelineAppEnabled = 4; 11 | string timelineAppPackageName = 5; 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/appinfo/AppInfoModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.appinfo 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import javax.inject.Singleton 6 | 7 | @Module 8 | abstract class AppInfoModule { 9 | 10 | @Singleton 11 | @Binds 12 | abstract fun provideAppInfoManager(appInfoManagerImpl: AppInfoManagerImpl): AppInfoManager 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/license/LicenseModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.license 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import javax.inject.Singleton 6 | 7 | @Module 8 | abstract class LicenseModule { 9 | 10 | @Singleton 11 | @Binds 12 | abstract fun provideLicenseRepository(repository: LicenseRepositoryImpl): LicenseRepository 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/license/LicenseViewModel.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.license 2 | 3 | import io.reactivex.Single 4 | import net.yslibrary.licenseadapter.Library 5 | import net.yslibrary.monotweety.license.domain.GetLicenses 6 | 7 | class LicenseViewModel(private val getLicenses: GetLicenses) { 8 | val licenses: Single> 9 | get() = getLicenses.execute() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/logout/LogoutComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.logout 2 | 3 | import dagger.Component 4 | import net.yslibrary.monotweety.UserComponent 5 | import net.yslibrary.monotweety.base.di.ServiceScope 6 | 7 | @ServiceScope 8 | @Component( 9 | dependencies = [UserComponent::class] 10 | ) 11 | interface LogoutComponent { 12 | fun inject(service: LogoutService) 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/user/local/resolver/UserSQLiteTypeMapping.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.user.local.resolver 2 | 3 | import com.pushtorefresh.storio3.sqlite.SQLiteTypeMapping 4 | import net.yslibrary.monotweety.appdata.user.User 5 | 6 | class UserSQLiteTypeMapping : SQLiteTypeMapping( 7 | UserPutResolver(), 8 | UserGetResolver(), 9 | UserDeleteResolver() 10 | ) 11 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/UserUiSubcomponentModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui 2 | 3 | import dagger.Module 4 | import net.yslibrary.monotweety.ui.main.MainActivityComponent 5 | 6 | @Module( 7 | subcomponents = [ 8 | MainActivityComponent::class, 9 | ] 10 | ) 11 | interface UserUiSubcomponentModule { 12 | interface ComponentProviders : MainActivityComponent.ComponentProvider 13 | } 14 | -------------------------------------------------------------------------------- /app2/src/main/res/animator/appbar_elevation.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/local/CursorExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.local 2 | 3 | import android.database.Cursor 4 | 5 | fun Cursor.getStringByName(name: String): String? = this.getString(this.getColumnIndex(name)) 6 | 7 | fun Cursor.getLongByName(name: String): Long = this.getLong(this.getColumnIndex(name)) 8 | 9 | fun Cursor.getIntByName(name: String): Int = this.getInt(this.getColumnIndex(name)) -------------------------------------------------------------------------------- /app/src/main/res/layout/vh_1line_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_compose_status.xml: -------------------------------------------------------------------------------- 1 | 2 |

5 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/di/Names.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base.di 2 | 3 | class Names { 4 | companion object { 5 | const val FOR_APP = "for_app" 6 | const val FOR_ACTIVITY = "for_activity" 7 | const val FOR_CONTROLLER = "for_controller" 8 | 9 | const val FOR_SETTING = "for_setting" 10 | const val FOR_CONFIG = "for_config" 11 | const val FOR_LOGIN = "for_login" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/AppUiSubcomponentModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui 2 | 3 | import dagger.Module 4 | import net.yslibrary.monotweety.ui.launcher.LauncherActivityComponent 5 | 6 | @Module( 7 | subcomponents = [ 8 | LauncherActivityComponent::class, 9 | ] 10 | ) 11 | interface AppUiSubcomponentModule { 12 | interface ComponentProviders : LauncherActivityComponent.ComponentProvider { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/status/remote/TwitterApiClient.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.status.remote 2 | 3 | import com.twitter.sdk.android.core.TwitterApiClient 4 | import com.twitter.sdk.android.core.TwitterSession 5 | 6 | class TwitterApiClient(session: TwitterSession) : TwitterApiClient(session) { 7 | 8 | val updateStatusService: UpdateStatusService 9 | get() = getService(UpdateStatusService::class.java) 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/vh_subheader.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app2/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4dp 4 | 8dp 5 | 12dp 6 | 16dp 7 | 20dp 8 | 24dp 9 | 32dp 10 | 40dp 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | 410dp 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/* 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | yslibrary.jks 10 | 11 | crashlytics.properties 12 | com_crashlytics_export_strings.xml 13 | crashlytics-build.properties 14 | 15 | fabric.properties 16 | secret.properties 17 | 18 | google-services.json 19 | 20 | *.jks 21 | 22 | # not ignore 23 | !.idea/codeStyleSettings.xml 24 | !.idea/icon.png 25 | !.idea/icon_dark.png 26 | !.idea/codeStyles/ 27 | -------------------------------------------------------------------------------- /app2/src/main/res/drawable/ic_send_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app2/src/main/res/drawable/ic_send_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/license/LicenseComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.license 2 | 3 | import dagger.Component 4 | import net.yslibrary.monotweety.UserComponent 5 | import net.yslibrary.monotweety.base.di.ControllerScope 6 | 7 | @ControllerScope 8 | @Component( 9 | dependencies = [UserComponent::class], 10 | modules = [LicenseViewModule::class] 11 | ) 12 | interface LicenseComponent { 13 | fun inject(controller: LicenseController) 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/setting/SettingComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.setting 2 | 3 | import dagger.Component 4 | import net.yslibrary.monotweety.UserComponent 5 | import net.yslibrary.monotweety.base.di.ControllerScope 6 | 7 | @ControllerScope 8 | @Component( 9 | dependencies = [UserComponent::class], 10 | modules = [SettingViewModule::class] 11 | ) 12 | interface SettingComponent { 13 | fun inject(controller: SettingController) 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_white_transparent_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/base/LazyInstanceHolder.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base 2 | 3 | class LazyInstanceHolder(val function: (P) -> T) { 4 | private var instance: T? = null 5 | fun get(param: P): T { 6 | return instance ?: function(param).also { instance = it } 7 | } 8 | } 9 | 10 | fun lazyWithParam(function: (P) -> T): Lazy> { 11 | return lazy { LazyInstanceHolder(function) } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/controller_license.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/debug/java/net/yslibrary/monotweety/DebugAppModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import android.app.NotificationManager 4 | import android.content.Context 5 | 6 | class DebugAppModule : AppModule() { 7 | 8 | override fun provideAppLifecycleCallbacks( 9 | context: Context, 10 | notificationManager: NotificationManager 11 | ): App.LifecycleCallbacks { 12 | return DebugAppLifecycleCallbacks(context, notificationManager) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/user/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.user 2 | 3 | 4 | import com.gojuno.koptional.Optional 5 | import io.reactivex.Completable 6 | import io.reactivex.Flowable 7 | 8 | interface UserRepository { 9 | fun get(id: Long): Flowable> 10 | fun fetch(): Completable 11 | fun set(user: User): Completable 12 | fun delete(id: Long): Completable 13 | fun isValid(user: User?): Boolean 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/user/local/UserLocalRepository.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.user.local 2 | 3 | import com.gojuno.koptional.Optional 4 | import io.reactivex.Completable 5 | import io.reactivex.Flowable 6 | import net.yslibrary.monotweety.appdata.user.User 7 | 8 | interface UserLocalRepository { 9 | fun getById(id: Long): Flowable> 10 | fun set(entity: User): Completable 11 | fun delete(id: Long): Completable 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/vh_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app2/src/main/res/drawable/ic_send_white_transparent_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/changelog/ChangelogComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.changelog 2 | 3 | import dagger.Component 4 | import net.yslibrary.monotweety.UserComponent 5 | import net.yslibrary.monotweety.base.di.ControllerScope 6 | 7 | @ControllerScope 8 | @Component( 9 | dependencies = [UserComponent::class], 10 | modules = [ChangelogViewModule::class] 11 | ) 12 | interface ChangelogComponent { 13 | fun inject(controller: ChangelogController) 14 | } 15 | -------------------------------------------------------------------------------- /app/src/debug/java/net/yslibrary/monotweety/DebugAppLifecycleCallbacks.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import android.app.NotificationManager 4 | import android.content.Context 5 | import timber.log.Timber 6 | 7 | class DebugAppLifecycleCallbacks( 8 | context: Context, 9 | notificationManager: NotificationManager 10 | ) : AppLifecycleCallbacks(context, notificationManager) { 11 | 12 | override fun initTimber() { 13 | Timber.plant(Timber.DebugTree()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/notification/NotificationComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.notification 2 | 3 | import dagger.Component 4 | import net.yslibrary.monotweety.UserComponent 5 | import net.yslibrary.monotweety.base.di.ServiceScope 6 | 7 | @ServiceScope 8 | @Component( 9 | dependencies = [UserComponent::class], 10 | modules = [NotificationServiceModule::class] 11 | ) 12 | interface NotificationComponent { 13 | fun inject(service: NotificationService) 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/login/LoginComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.login 2 | 3 | import dagger.Subcomponent 4 | import net.yslibrary.monotweety.base.di.ControllerScope 5 | 6 | @ControllerScope 7 | @Subcomponent( 8 | modules = [LoginViewModule::class] 9 | ) 10 | interface LoginComponent { 11 | fun inject(controller: LoginController) 12 | 13 | interface ComponentProvider { 14 | fun loginComponent(module: LoginViewModule): LoginComponent 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/controller_changelog.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/user/FetchUser.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.user 2 | 3 | import net.yslibrary.monotweety.data.user.UserRepository 4 | import javax.inject.Inject 5 | 6 | interface FetchUser { 7 | suspend operator fun invoke() 8 | } 9 | 10 | internal class FetchUserImpl @Inject constructor( 11 | private val userRepository: UserRepository, 12 | ) : FetchUser { 13 | override suspend fun invoke() { 14 | userRepository.fetch() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/splash/SplashComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.splash 2 | 3 | import dagger.Subcomponent 4 | import net.yslibrary.monotweety.base.di.ControllerScope 5 | 6 | @ControllerScope 7 | @Subcomponent( 8 | modules = [SplashViewModule::class] 9 | ) 10 | interface SplashComponent { 11 | fun inject(controller: SplashController) 12 | 13 | interface ComponentProvider { 14 | fun splashComponent(module: SplashViewModule): SplashComponent 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_view_headline_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/splash/SplashViewModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.splash 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import net.yslibrary.monotweety.base.di.ControllerScope 6 | import net.yslibrary.monotweety.login.domain.IsLoggedIn 7 | 8 | @Module 9 | class SplashViewModule { 10 | 11 | @ControllerScope 12 | @Provides 13 | fun provideSplashViewModel(isLoggedIn: IsLoggedIn): SplashViewModel { 14 | return SplashViewModel(isLoggedIn) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/loginform/LoginFormFragmentComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.loginform 2 | 3 | import dagger.Subcomponent 4 | 5 | @Subcomponent 6 | interface LoginFormFragmentComponent { 7 | fun inject(fragment: LoginFormFragment) 8 | 9 | @Subcomponent.Factory 10 | interface Factory { 11 | fun build(): LoginFormFragmentComponent 12 | } 13 | 14 | interface ComponentProvider { 15 | fun loginFormFragmentComponent(): Factory 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/settings/SettingsFragmentComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.settings 2 | 3 | import dagger.Subcomponent 4 | 5 | @Subcomponent 6 | interface SettingsFragmentComponent { 7 | fun inject(fragment: SettingsFragment) 8 | 9 | @Subcomponent.Factory 10 | interface Factory { 11 | fun build(): SettingsFragmentComponent 12 | } 13 | 14 | interface ComponentProvider { 15 | fun settingsFragmentComponent(): Factory 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /app2/src/main/res/drawable/ic_view_headline_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app2/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /di-common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | id("java-library") 5 | kotlin("jvm") 6 | } 7 | 8 | dependencies { 9 | implementation(Deps.Kotlin.Stdlib.jdk8) 10 | implementation(Deps.Dagger.core) 11 | } 12 | 13 | tasks.withType(KotlinCompile::class.java).all { 14 | kotlinOptions { 15 | jvmTarget = "1.8" 16 | } 17 | } 18 | 19 | java { 20 | sourceCompatibility = JavaVersion.VERSION_1_8 21 | targetCompatibility = JavaVersion.VERSION_1_8 22 | } 23 | -------------------------------------------------------------------------------- /app2/src/main/res/drawable/ic_close_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app2/src/main/res/drawable/ic_close_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app2/src/main/res/layout/activity_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /app2/src/main/res/layout/fragment_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/license/domain/GetLicenses.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.license.domain 2 | 3 | import io.reactivex.Single 4 | import net.yslibrary.licenseadapter.Library 5 | import net.yslibrary.monotweety.appdata.license.LicenseRepository 6 | import net.yslibrary.monotweety.di.UserScope 7 | import javax.inject.Inject 8 | 9 | @UserScope 10 | class GetLicenses @Inject constructor(private val licenseRepository: LicenseRepository) { 11 | fun execute(): Single> { 12 | return licenseRepository.get() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/status/ComposeStatusComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.status 2 | 3 | import dagger.Subcomponent 4 | import net.yslibrary.monotweety.base.di.ControllerScope 5 | 6 | @ControllerScope 7 | @Subcomponent( 8 | modules = [ComposeStatusViewModule::class] 9 | ) 10 | interface ComposeStatusComponent { 11 | 12 | fun inject(controller: ComposeStatusController) 13 | 14 | interface ComponentProvider { 15 | fun composeStatusComponent(module: ComposeStatusViewModule): ComposeStatusComponent 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/footereditor/FooterEditorFragmentComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.footereditor 2 | 3 | import dagger.Subcomponent 4 | 5 | @Subcomponent 6 | interface FooterEditorFragmentComponent { 7 | fun inject(fragment: FooterEditorFragment) 8 | 9 | @Subcomponent.Factory 10 | interface Factory { 11 | fun build(): FooterEditorFragmentComponent 12 | } 13 | 14 | interface ComponentProvider { 15 | fun footerEditorFragmentComponent(): FooterEditorFragmentComponent.Factory 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/login/domain/IsLoggedIn.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.login.domain 2 | 3 | import io.reactivex.Single 4 | import net.yslibrary.monotweety.appdata.session.SessionRepository 5 | import javax.inject.Inject 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | class IsLoggedIn @Inject constructor(private val sessionRepository: SessionRepository) { 10 | 11 | fun execute(): Single { 12 | return sessionRepository.getActiveSession() 13 | .map { token -> token.toNullable() != null } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/di/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.di 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import javax.inject.Inject 6 | import javax.inject.Provider 7 | 8 | class ViewModelFactory @Inject constructor( 9 | private val provider: Provider, 10 | ) : ViewModelProvider.Factory { 11 | @Suppress("UNCHECKED_CAST") 12 | override fun create(modelClass: Class): T { 13 | return provider.get() as T 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/login/LoginFragmentComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.login 2 | 3 | import dagger.Subcomponent 4 | import net.yslibrary.monotweety.di.FragmentScope 5 | 6 | @FragmentScope 7 | @Subcomponent 8 | interface LoginFragmentComponent { 9 | fun inject(fragment: LoginFragment) 10 | 11 | @Subcomponent.Factory 12 | interface Factory { 13 | fun build(): LoginFragmentComponent 14 | } 15 | 16 | interface ComponentProvider { 17 | fun loginFragmentComponent(): Factory 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/splash/SplashFragmentComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.splash 2 | 3 | import dagger.Subcomponent 4 | import net.yslibrary.monotweety.di.FragmentScope 5 | 6 | 7 | @FragmentScope 8 | @Subcomponent 9 | interface SplashFragmentComponent { 10 | fun inject(fragment: SplashFragment) 11 | 12 | @Subcomponent.Factory 13 | interface Factory { 14 | fun build(): SplashFragmentComponent 15 | } 16 | 17 | interface ComponentProvider { 18 | fun splashFragmentComponent(): Factory 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/session/SessionModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.session 2 | 3 | import com.twitter.sdk.android.core.SessionManager 4 | import com.twitter.sdk.android.core.TwitterSession 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Singleton 8 | 9 | @Module 10 | class SessionModule { 11 | 12 | @Singleton 13 | @Provides 14 | fun provideSessionRepository(sessionManager: SessionManager): SessionRepository { 15 | return SessionRepositoryImpl(sessionManager) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/status/domain/UpdateStatus.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.status.domain 2 | 3 | import io.reactivex.Completable 4 | import net.yslibrary.monotweety.appdata.status.StatusRepository 5 | import net.yslibrary.monotweety.di.UserScope 6 | import javax.inject.Inject 7 | 8 | @UserScope 9 | class UpdateStatus @Inject constructor(private val statusRepository: StatusRepository) { 10 | 11 | fun execute(status: String, inReplyToStatusId: Long? = null): Completable { 12 | return statusRepository.updateStatus(status, inReplyToStatusId) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/notification/NotificationServiceComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.notification 2 | 3 | import dagger.Subcomponent 4 | import net.yslibrary.monotweety.di.ServiceScope 5 | 6 | @ServiceScope 7 | @Subcomponent 8 | interface NotificationServiceComponent { 9 | 10 | fun inject(service: NotificationService) 11 | 12 | @Subcomponent.Factory 13 | interface Factory { 14 | fun build(): NotificationServiceComponent 15 | } 16 | 17 | interface ComponentProvider { 18 | fun notificationServiceComponent(): Factory 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/setting/domain/NotificationEnabledManager.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.setting.domain 2 | 3 | import io.reactivex.Observable 4 | import net.yslibrary.monotweety.appdata.setting.SettingDataManager 5 | import javax.inject.Inject 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | class NotificationEnabledManager @Inject constructor(private val settingDataManager: SettingDataManager) { 10 | 11 | fun get(): Observable = settingDataManager.notificationEnabled() 12 | 13 | fun set(enabled: Boolean) = settingDataManager.notificationEnabled(enabled) 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/user/local/resolver/UserDeleteResolver.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.user.local.resolver 2 | 3 | import com.pushtorefresh.storio3.sqlite.operations.delete.DefaultDeleteResolver 4 | import com.pushtorefresh.storio3.sqlite.queries.DeleteQuery 5 | import net.yslibrary.monotweety.appdata.user.User 6 | import net.yslibrary.monotweety.appdata.user.local.UserTable 7 | 8 | class UserDeleteResolver : DefaultDeleteResolver() { 9 | override fun mapToDeleteQuery(entity: User): DeleteQuery { 10 | return UserTable.deleteById(entity.id) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/user/ObserveUser.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.user 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import net.yslibrary.monotweety.data.user.User 5 | import net.yslibrary.monotweety.data.user.UserRepository 6 | import javax.inject.Inject 7 | 8 | interface ObserveUser { 9 | operator fun invoke(): Flow 10 | } 11 | 12 | internal class ObserveUserImpl @Inject constructor( 13 | private val userRepository: UserRepository, 14 | ) : ObserveUser { 15 | override fun invoke(): Flow { 16 | return userRepository.userFlow 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/compose/ComposeTweetDialogFragmentComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.compose 2 | 3 | import dagger.Subcomponent 4 | import net.yslibrary.monotweety.di.FragmentScope 5 | 6 | @FragmentScope 7 | @Subcomponent 8 | interface ComposeTweetDialogFragmentComponent { 9 | fun inject(fragment: ComposeTweetDialogFragment) 10 | 11 | @Subcomponent.Factory 12 | interface Factory { 13 | fun build(): ComposeTweetDialogFragmentComponent 14 | } 15 | 16 | interface ComponentProvider { 17 | fun composeTweetDialogFragmentComponent(): Factory 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/controller_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/base/ViewExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.base 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.inputmethod.InputMethodManager 6 | 7 | fun View.showKeyboard() { 8 | requestFocus() 9 | val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 10 | imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) 11 | } 12 | 13 | fun View.hideKeyboard() { 14 | val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 15 | imm.hideSoftInputFromWindow(windowToken, 0) 16 | } 17 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/setting/UpdateFooterSettings.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.setting 2 | 3 | import net.yslibrary.monotweety.data.settings.SettingsRepository 4 | import javax.inject.Inject 5 | 6 | interface UpdateFooterSettings { 7 | suspend operator fun invoke(enabled: Boolean, text: String) 8 | } 9 | 10 | internal class UpdateFooterSettingsImpl @Inject constructor( 11 | private val settingsRepository: SettingsRepository, 12 | ) : UpdateFooterSettings { 13 | override suspend fun invoke(enabled: Boolean, text: String) { 14 | settingsRepository.updateFooter(enabled, text) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/setting/UpdateNotificationEnabled.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.setting 2 | 3 | import net.yslibrary.monotweety.data.settings.SettingsRepository 4 | import javax.inject.Inject 5 | 6 | interface UpdateNotificationEnabled { 7 | suspend operator fun invoke(enabled: Boolean) 8 | } 9 | 10 | internal class UpdateNotificationEnabledImpl @Inject constructor( 11 | private val settingsRepository: SettingsRepository, 12 | ) : UpdateNotificationEnabled { 13 | override suspend fun invoke(enabled: Boolean) { 14 | settingsRepository.updateNotificationEnabled(enabled) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app2/src/main/res/drawable/ic_baseline_account_circle_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/session/SessionRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.session 2 | 3 | import com.gojuno.koptional.Optional 4 | import com.gojuno.koptional.toOptional 5 | import com.twitter.sdk.android.core.SessionManager 6 | import com.twitter.sdk.android.core.TwitterSession 7 | import io.reactivex.Single 8 | 9 | class SessionRepositoryImpl(private val sessionManager: SessionManager) : 10 | SessionRepository { 11 | 12 | override fun getActiveSession(): Single> { 13 | return Single.fromCallable { sessionManager.activeSession.toOptional() } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/session/Logout.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.session 2 | 3 | import net.yslibrary.monotweety.data.session.SessionRepository 4 | import net.yslibrary.monotweety.data.user.UserRepository 5 | import javax.inject.Inject 6 | 7 | interface Logout { 8 | suspend operator fun invoke() 9 | } 10 | 11 | internal class LogoutImpl @Inject constructor( 12 | private val sessionRepository: SessionRepository, 13 | private val userRepository: UserRepository, 14 | ) : Logout { 15 | override suspend fun invoke() { 16 | sessionRepository.delete() 17 | userRepository.delete() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/status/Tweet.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.status 2 | 3 | data class Tweet( 4 | val id: Long, 5 | val inReplyToStatusId: Long, 6 | val text: String, 7 | val createdAt: String 8 | ) { 9 | companion object { 10 | fun from(twitterTweet: com.twitter.sdk.android.core.models.Tweet): Tweet { 11 | return Tweet( 12 | id = twitterTweet.id, 13 | inReplyToStatusId = twitterTweet.inReplyToStatusId, 14 | text = twitterTweet.text, 15 | createdAt = twitterTweet.createdAt 16 | ) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/vh_2line_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/session/ObserveSession.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.session 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import net.yslibrary.monotweety.data.session.Session 5 | import net.yslibrary.monotweety.data.session.SessionRepository 6 | import javax.inject.Inject 7 | 8 | interface ObserveSession { 9 | operator fun invoke(): Flow 10 | } 11 | 12 | internal class ObserveSessionImpl @Inject constructor( 13 | private val sessionRepository: SessionRepository, 14 | ) : ObserveSession { 15 | override fun invoke(): Flow { 16 | return sessionRepository.sessionFlow 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/setting/ObserveSettings.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.setting 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import net.yslibrary.monotweety.data.settings.Settings 5 | import net.yslibrary.monotweety.data.settings.SettingsRepository 6 | import javax.inject.Inject 7 | 8 | interface ObserveSettings { 9 | operator fun invoke(): Flow 10 | } 11 | 12 | internal class ObserveSettingsImpl @Inject constructor( 13 | private val settingsRepository: SettingsRepository, 14 | ) : ObserveSettings { 15 | override fun invoke(): Flow { 16 | return settingsRepository.settingsFlow 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app2/src/main/res/layout/item_1line_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/ProgressController.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import net.yslibrary.monotweety.R 8 | 9 | /** 10 | * Controller to show ProgressBar 11 | * Created by yshrsmz on 2016/10/05. 12 | */ 13 | class ProgressController : BaseController() { 14 | override fun onCreateView( 15 | inflater: LayoutInflater, 16 | container: ViewGroup, 17 | savedViewState: Bundle? 18 | ): View { 19 | return inflater.inflate(R.layout.controller_progress, container, false) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/twitterapp/GetInstalledTwitterApps.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.twitterapp 2 | 3 | import net.yslibrary.monotweety.data.twitterapp.AppInfo 4 | import net.yslibrary.monotweety.data.twitterapp.TwitterAppRepository 5 | import javax.inject.Inject 6 | 7 | interface GetInstalledTwitterApps { 8 | suspend operator fun invoke(): List 9 | } 10 | 11 | internal class GetInstalledTwitterAppsImpl @Inject constructor( 12 | private val repository: TwitterAppRepository, 13 | ) : GetInstalledTwitterApps { 14 | override suspend fun invoke(): List { 15 | return repository.getInstalledApps() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/setting/UpdateTimelineAppSetting.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.setting 2 | 3 | import net.yslibrary.monotweety.data.settings.SettingsRepository 4 | import javax.inject.Inject 5 | 6 | interface UpdateTimelineAppSetting { 7 | suspend operator fun invoke(enabled: Boolean, packageName: String) 8 | } 9 | 10 | internal class UpdateTimelineAppSettingImpl @Inject constructor( 11 | private val settingsRepository: SettingsRepository, 12 | ) : UpdateTimelineAppSetting { 13 | override suspend fun invoke(enabled: Boolean, packageName: String) { 14 | settingsRepository.updateTimelineApp(enabled, packageName) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/login/LoginViewModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.login 2 | 3 | import com.twitter.sdk.android.core.TwitterSession 4 | import dagger.Module 5 | import dagger.Provides 6 | import io.reactivex.subjects.PublishSubject 7 | import net.yslibrary.monotweety.base.di.ControllerScope 8 | import net.yslibrary.monotweety.base.di.Names 9 | import javax.inject.Named 10 | 11 | @Module 12 | class LoginViewModule { 13 | 14 | @ControllerScope 15 | @Provides 16 | fun provideLoginViewModel(@Named(Names.FOR_LOGIN) loginCompletedSubject: PublishSubject): LoginViewModel { 17 | return LoginViewModel(loginCompletedSubject) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/activity/compose/ComposeActivityComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.activity.compose 2 | 3 | import dagger.Component 4 | import net.yslibrary.monotweety.UserComponent 5 | import net.yslibrary.monotweety.activity.ActivityModule 6 | import net.yslibrary.monotweety.base.di.ActivityScope 7 | import net.yslibrary.monotweety.status.ComposeStatusComponent 8 | 9 | @ActivityScope 10 | @Component( 11 | dependencies = [UserComponent::class], 12 | modules = [ActivityModule::class] 13 | ) 14 | interface ComposeActivityComponent : ActivityModule.Provider, 15 | ComposeStatusComponent.ComponentProvider { 16 | fun inject(activity: ComposeActivity) 17 | } 18 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/notification/BroadcastReceiverExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.notification 2 | 3 | import android.content.Context 4 | import kotlinx.coroutines.flow.first 5 | import kotlinx.coroutines.runBlocking 6 | import net.yslibrary.monotweety.App 7 | 8 | fun Context.shouldStartService(): Boolean { 9 | val observeSession = App.appComponent(this).observeSession() 10 | val observeSettings = App.appComponent(this).observeSettings() 11 | 12 | return runBlocking { 13 | val session = observeSession().first() 14 | val settings = observeSettings().first() 15 | 16 | session != null && settings.notificationEnabled 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/auth/AuthDataModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.auth 2 | 3 | import com.codingfeline.twitter4kt.core.Twitter 4 | import com.codingfeline.twitter4kt.core.launchOAuth1aFlow 5 | import com.codingfeline.twitter4kt.core.oauth1a.OAuth1aFlow 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.Provides 9 | 10 | @Module 11 | internal interface AuthDataModule { 12 | 13 | @Binds 14 | fun bindAuthFlow(impl: AuthFlowImpl): AuthFlow 15 | 16 | companion object { 17 | @Provides 18 | fun provideOAuthFlow(twitter: Twitter): OAuth1aFlow { 19 | return twitter.launchOAuth1aFlow() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/EventBus.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.subjects.PublishSubject 5 | import kotlin.reflect.KClass 6 | 7 | class EventBus { 8 | 9 | private val bus = PublishSubject.create().toSerialized() 10 | 11 | fun emit(event: Event): Unit = bus.onNext(event) 12 | 13 | fun on(kClass: KClass): Observable = bus.ofType(kClass.java) 14 | 15 | fun on(kClass: KClass, initialValue: R): Observable = 16 | bus.ofType(kClass.java).startWith(initialValue) 17 | 18 | fun hasObservers() = bus.hasObservers() 19 | 20 | interface Event 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/controller_compose_status.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 13 | 14 | 20 | 21 | -------------------------------------------------------------------------------- /app2/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @color/gray 4 | @color/darkGray 5 | @color/orange 6 | 7 | #757575 8 | #424242 9 | #F5F5F5 10 | #FF9800 11 | 12 | #1e000000 13 | #88000000 14 | 15 | #f44336 16 | #ffffff 17 | #dfdfdf 18 | 19 | -------------------------------------------------------------------------------- /assets/description_ja.txt: -------------------------------------------------------------------------------- 1 | # app name 2 | 3 | Monotweety - 通知エリアからツイート 4 | 5 | # short description 6 | 7 | 端末の通知エリアからツイートすることに特化したTwitterクライアント 8 | 9 | # description 10 | 11 | Monotweety(モノツイーティ)は通知エリアからツイートすることに特化したシンプルなTwitterクライアントです。 12 | 13 | お使いの端末がAndroid 7.0以上であれば、Android 7.0からの新機能を利用して、文字通り「通知エリアから」ツイートすることができます。 14 | それ以前の端末では、通知をタップすると編集ダイアログが立ち上がります。 15 | 16 | また、編集ダイアログからはツイートを前のツイートへのリプライとして繋げる「連ツイ」をワンタップで有効にできます。 17 | 18 | 機能一覧 19 | - 通知エリアからツイート 20 | - 連ツイ(複数のツイートをリプライとして紐付け) 21 | - 通知から任意のTwitterアプリを起動する 22 | - クイック設定タイル(Android 7.0以降) 23 | 24 | Monotweetyはオープンソースです。 25 | ソースコードは https://github.com/yshrsmz/monotweety から確認できます。 26 | 27 | お問い合わせや要望、不具合報告等は上記GitHubレポジトリ、あるいはTwitterアカウント @yslibnet までお願いします。 -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/status/UpdateStatus.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.status 2 | 3 | import com.codingfeline.twitter4kt.core.ApiResult 4 | import com.codingfeline.twitter4kt.v1.model.status.Tweet 5 | import net.yslibrary.monotweety.data.status.StatusRepository 6 | import javax.inject.Inject 7 | 8 | interface UpdateStatus { 9 | suspend operator fun invoke(status: String): ApiResult 10 | } 11 | 12 | internal class UpdateStatusImpl @Inject constructor( 13 | private val statusRepository: StatusRepository, 14 | ) : UpdateStatus { 15 | override suspend fun invoke(status: String): ApiResult { 16 | return statusRepository.update(status) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/changelog/ChangelogViewModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.changelog 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import net.yslibrary.monotweety.base.EventBus 6 | import net.yslibrary.monotweety.base.di.ControllerScope 7 | import net.yslibrary.monotweety.base.di.Names 8 | import javax.inject.Named 9 | 10 | @Module 11 | class ChangelogViewModule(private val activityBus: EventBus) { 12 | 13 | @ControllerScope 14 | @Provides 15 | @Named(Names.FOR_ACTIVITY) 16 | fun provideActivityBus(): EventBus = activityBus 17 | 18 | interface DependencyProvider { 19 | @Named(Names.FOR_ACTIVITY) 20 | fun activityBus(): EventBus 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/status/StatusModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.status 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import net.yslibrary.monotweety.appdata.status.remote.StatusRemoteRepository 6 | import net.yslibrary.monotweety.appdata.status.remote.StatusRemoteRepositoryImpl 7 | import net.yslibrary.monotweety.di.UserScope 8 | 9 | @Module 10 | abstract class StatusModule { 11 | 12 | @UserScope 13 | @Binds 14 | abstract fun provideStatusRemoteRepository(repository: StatusRemoteRepositoryImpl): StatusRemoteRepository 15 | 16 | @UserScope 17 | @Binds 18 | abstract fun provideStatusRepository(repository: StatusRepositoryImpl): StatusRepository 19 | } 20 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/notification/BootCompletedReceiver.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.notification 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import androidx.core.content.ContextCompat 7 | import timber.log.Timber 8 | 9 | class BootCompletedReceiver : BroadcastReceiver() { 10 | override fun onReceive(context: Context, intent: Intent) { 11 | Timber.d("boot completed ---") 12 | 13 | if (context.shouldStartService()) { 14 | ContextCompat.startForegroundService( 15 | context, 16 | NotificationService.callingIntent(context) 17 | ) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/notification/PackageReplacedReceiver.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.notification 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import androidx.core.content.ContextCompat 7 | import timber.log.Timber 8 | 9 | class PackageReplacedReceiver : BroadcastReceiver() { 10 | override fun onReceive(context: Context, intent: Intent) { 11 | Timber.d("package replaced ---") 12 | if (context.shouldStartService()) { 13 | ContextCompat.startForegroundService( 14 | context, 15 | NotificationService.callingIntent(context) 16 | ) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/splash/SplashViewModel.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.splash 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.rxkotlin.subscribeBy 5 | import io.reactivex.schedulers.Schedulers 6 | import io.reactivex.subjects.BehaviorSubject 7 | import net.yslibrary.monotweety.login.domain.IsLoggedIn 8 | 9 | class SplashViewModel(private val isLoggedIn: IsLoggedIn) { 10 | 11 | private val loggedInSubject = BehaviorSubject.create() 12 | 13 | val loggedIn: Observable 14 | get() = loggedInSubject 15 | 16 | init { 17 | isLoggedIn.execute() 18 | .subscribeOn(Schedulers.io()) 19 | .subscribeBy { loggedInSubject.onNext(it) } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: yshrsmz # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/local/StorIOExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.local 2 | 3 | import com.pushtorefresh.storio3.sqlite.operations.delete.PreparedDelete 4 | import com.pushtorefresh.storio3.sqlite.operations.get.PreparedGet 5 | import com.pushtorefresh.storio3.sqlite.operations.put.PreparedPut 6 | 7 | fun PreparedPut.Builder.withObject(entity: T) = `object`(entity) 8 | 9 | fun PreparedPut.Builder.withObjects(entities: Collection) = objects(entities) 10 | 11 | fun PreparedGet.Builder.singleObject(entityClass: Class) = `object`(entityClass) 12 | 13 | fun PreparedDelete.Builder.withObject(entity: T) = `object`(entity) 14 | 15 | fun PreparedDelete.Builder.withObjects(entities: Collection) = objects(entities) -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/status/StatusDataModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.status 2 | 3 | import com.codingfeline.twitter4kt.core.session.ApiClient 4 | import com.codingfeline.twitter4kt.v1.api.statuses.StatusesApi 5 | import com.codingfeline.twitter4kt.v1.api.statuses.statuses 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.Provides 9 | import net.yslibrary.monotweety.di.UserScope 10 | 11 | @Module 12 | internal interface StatusDataModule { 13 | @Binds 14 | fun bindStatusRepository(impl: StatusRepositoryImpl): StatusRepository 15 | 16 | companion object { 17 | @UserScope 18 | @Provides 19 | fun provideStatusesApi(apiClient: ApiClient): StatusesApi = apiClient.statuses 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/local/DbOpenHelper.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.local 2 | 3 | import android.content.Context 4 | import android.database.sqlite.SQLiteDatabase 5 | import android.database.sqlite.SQLiteOpenHelper 6 | import net.yslibrary.monotweety.appdata.user.local.UserTable 7 | 8 | class DbOpenHelper(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { 9 | 10 | override fun onCreate(db: SQLiteDatabase) { 11 | db.execSQL(UserTable.CREATE_TABLE) 12 | } 13 | 14 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { 15 | 16 | } 17 | 18 | companion object { 19 | const val DB_NAME = "monotweety_db" 20 | const val DB_VERSION = 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/Config.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | data class Config( 4 | val developerUrl: String, 5 | val googlePlayUrl: String, 6 | val githubUrl: String, 7 | val privacyPolicyUrl: String, 8 | val statusMaxLength: Int = 140 9 | ) { 10 | companion object { 11 | fun init(): Config { 12 | return Config( 13 | developerUrl = "https://twitter.com/yslibnet", 14 | googlePlayUrl = "https://play.google.com/store/apps/details?id=net.yslibrary.monotweety", 15 | githubUrl = "https://github.com/yshrsmz/monotweety", 16 | privacyPolicyUrl = "https://www.yslibrary.net/products/monotweety/privacy-policy/" 17 | ) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/setting/SettingDataManager.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.setting 2 | 3 | import io.reactivex.Completable 4 | import io.reactivex.Observable 5 | 6 | interface SettingDataManager { 7 | fun notificationEnabled(): Observable 8 | fun notificationEnabled(enabled: Boolean) 9 | 10 | fun footerEnabled(): Observable 11 | fun footerEnabled(enabled: Boolean) 12 | 13 | fun footerText(): Observable 14 | fun footerText(text: String) 15 | 16 | 17 | fun timelineAppEnabled(): Observable 18 | fun timelineAppEnabled(enabled: Boolean) 19 | 20 | fun timelineAppPackageName(): Observable 21 | fun timelineAppPackageName(packageName: String) 22 | 23 | fun clear(): Completable 24 | } 25 | -------------------------------------------------------------------------------- /app/src/test/java/net/yslibrary/monotweety/appdata/license/LicenseRepositoryImplTest.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.license 2 | 3 | import org.junit.Before 4 | import org.junit.Test 5 | import org.junit.runner.RunWith 6 | import org.robolectric.RobolectricTestRunner 7 | import kotlin.properties.Delegates 8 | 9 | @RunWith(RobolectricTestRunner::class) 10 | class LicenseRepositoryImplTest { 11 | 12 | var repository: LicenseRepositoryImpl by Delegates.notNull() 13 | 14 | @Before 15 | fun setup() { 16 | repository = LicenseRepositoryImpl() 17 | } 18 | 19 | @Test 20 | fun get() { 21 | repository.get().test() 22 | .apply { 23 | assertValueCount(1) 24 | assertComplete() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/Config.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | data class Config( 4 | val twitterUrl: String, 5 | val googlePlayUrl: String, 6 | val githubUrl: String, 7 | val privacyPolicyUrl: String, 8 | val statusMaxLength: Int, 9 | ) { 10 | companion object { 11 | fun init(): Config { 12 | return Config( 13 | twitterUrl = "https://twitter.com/yslibnet", 14 | googlePlayUrl = "https://play.google.com/store/apps/details?id=net.yslibrary.monotweety", 15 | githubUrl = "https://github.com/yshrsmz/monotweety", 16 | privacyPolicyUrl = "https://www.yslibrary.net/products/monotweety/privacy-policy/", 17 | statusMaxLength = 280, 18 | ) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app2/src/main/res/layout/item_subheader.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/status/StatusRepository.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.status 2 | 3 | import com.codingfeline.twitter4kt.core.ApiResult 4 | import com.codingfeline.twitter4kt.v1.api.statuses.StatusesApi 5 | import com.codingfeline.twitter4kt.v1.api.statuses.update 6 | import com.codingfeline.twitter4kt.v1.model.status.Tweet 7 | import net.yslibrary.monotweety.di.UserScope 8 | import javax.inject.Inject 9 | 10 | interface StatusRepository { 11 | suspend fun update(status: String): ApiResult 12 | } 13 | 14 | @UserScope 15 | internal class StatusRepositoryImpl @Inject constructor( 16 | private val statusesApi: StatusesApi, 17 | ) : StatusRepository { 18 | override suspend fun update(status: String): ApiResult { 19 | return statusesApi.update(status) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/status/StatusRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.status 2 | 3 | import io.reactivex.Completable 4 | import net.yslibrary.monotweety.appdata.status.remote.StatusRemoteRepository 5 | import net.yslibrary.monotweety.di.UserScope 6 | import timber.log.Timber 7 | import javax.inject.Inject 8 | 9 | @UserScope 10 | class StatusRepositoryImpl @Inject constructor( 11 | private val remoteRepository: StatusRemoteRepository 12 | ) : StatusRepository { 13 | 14 | override fun updateStatus(status: String, inReplyToStatusId: Long?): Completable { 15 | return remoteRepository.update(status, inReplyToStatusId) 16 | .doOnSuccess { 17 | Timber.d("status updated: ${it.text}") 18 | } 19 | .ignoreElement() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/controller_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/AppInitializer.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import android.content.Context 4 | import androidx.core.app.NotificationManagerCompat 5 | import net.yslibrary.monotweety.analytics.CrashReportingTree 6 | import net.yslibrary.monotweety.notification.createNotificationChannel 7 | import timber.log.Timber 8 | 9 | interface AppInitializer { 10 | fun init(app: App) 11 | } 12 | 13 | open class AppInitializerImpl : AppInitializer { 14 | override fun init(app: App) { 15 | initLogger() 16 | initNotificationChannel(app) 17 | } 18 | 19 | open fun initLogger() { 20 | Timber.plant(CrashReportingTree()) 21 | } 22 | 23 | private fun initNotificationChannel(context: Context) { 24 | createNotificationChannel(context, NotificationManagerCompat.from(context)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/login/LoginViewModel.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.login 2 | 3 | import com.twitter.sdk.android.core.TwitterSession 4 | import io.reactivex.Observable 5 | import io.reactivex.subjects.PublishSubject 6 | 7 | class LoginViewModel(private val loginCompletedSubject: PublishSubject) { 8 | 9 | private val loginFailedSubject = PublishSubject.create() 10 | 11 | val loginCompleted: Observable 12 | get() = loginCompletedSubject 13 | 14 | val loginFailed: Observable 15 | get() = loginFailedSubject 16 | 17 | fun onLoginCompleted(activeSession: TwitterSession) { 18 | loginCompletedSubject.onNext(activeSession) 19 | } 20 | 21 | fun onLoginFailed(exception: Exception) { 22 | loginFailedSubject.onNext(exception) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -1px 7 | -3px 8 | 9 | 48dp 10 | 72dp 11 | 88dp 12 | 16dp 13 | 16dp 14 | 20dp 15 | 16 | 40dp 17 | 18 | 8dp 19 | 20 | @dimen/match_parent 21 | 22 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/session/SessionDataModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.session 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.createDataStore 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.Provides 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | internal interface SessionDataModule { 13 | @Binds 14 | fun bindSessionRepository(impl: SessionRepositoryImpl): SessionRepository 15 | 16 | companion object { 17 | @Singleton 18 | @Provides 19 | fun provideSessionDataStore(context: Context): DataStore { 20 | return context.createDataStore( 21 | fileName = "session_prefs.pb", 22 | serializer = SessionPreferencesSerializer, 23 | ) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #757575 6 | #424242 7 | #F5F5F5 8 | #FF9800 9 | #F57C00 10 | 11 | #212121 12 | #212121 13 | #757575 14 | #BDBDBD 15 | 16 | 17 | #1e000000 18 | #88000000 19 | 20 | #f44336 21 | #ffffff 22 | #dfdfdf 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/setting/domain/GetInstalledSupportedApps.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.setting.domain 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.Single 5 | import net.yslibrary.monotweety.appdata.appinfo.AppInfo 6 | import net.yslibrary.monotweety.appdata.appinfo.AppInfoManager 7 | import net.yslibrary.monotweety.appdata.appinfo.TwitterApp 8 | import javax.inject.Inject 9 | import javax.inject.Singleton 10 | 11 | @Singleton 12 | class GetInstalledSupportedApps @Inject constructor(private val appInfoManager: AppInfoManager) { 13 | 14 | private val twitterApps = TwitterApp.packages() 15 | 16 | fun execute(): Single> { 17 | return appInfoManager.installedApps() 18 | .flatMapObservable { Observable.fromIterable(it) } 19 | .filter { twitterApps.contains(it.packageName) } 20 | .toList() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/UserScopeDataModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data 2 | 3 | import com.codingfeline.twitter4kt.core.Twitter 4 | import com.codingfeline.twitter4kt.core.model.oauth1a.AccessToken 5 | import com.codingfeline.twitter4kt.core.session.ApiClient 6 | import com.codingfeline.twitter4kt.core.startSession 7 | import dagger.Module 8 | import dagger.Provides 9 | import net.yslibrary.monotweety.data.status.StatusDataModule 10 | import net.yslibrary.monotweety.data.user.UserDataModule 11 | import net.yslibrary.monotweety.di.UserScope 12 | 13 | @Module( 14 | includes = [ 15 | StatusDataModule::class, 16 | UserDataModule::class, 17 | ] 18 | ) 19 | object UserScopeDataModule { 20 | 21 | @UserScope 22 | @Provides 23 | fun provideApiClient(twitter: Twitter, token: AccessToken): ApiClient { 24 | return twitter.startSession(token) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/user/remote/UserRemoteGateway.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.user.remote 2 | 3 | import com.codingfeline.twitter4kt.core.ApiResult 4 | import com.codingfeline.twitter4kt.v1.api.account.AccountApi 5 | import com.codingfeline.twitter4kt.v1.api.account.verifyCredentials 6 | import com.codingfeline.twitter4kt.v1.model.account.Account 7 | import javax.inject.Inject 8 | 9 | interface UserRemoteGateway { 10 | suspend fun verifyCredentials(): ApiResult 11 | } 12 | 13 | internal class UserRemoteGatewayImpl @Inject constructor( 14 | private val accountApi: AccountApi, 15 | ) : UserRemoteGateway { 16 | override suspend fun verifyCredentials(): ApiResult { 17 | return accountApi.verifyCredentials( 18 | includeEntities = false, 19 | skipStatus = true, 20 | includeEmail = false 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /includedBuild/dependencies/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | `java-gradle-plugin` 4 | } 5 | 6 | group = "net.yslibrary.monotweety.dependencies" 7 | version = "SNAPSHOT" 8 | 9 | repositories { 10 | mavenCentral() 11 | jcenter() 12 | } 13 | 14 | dependencies { 15 | compileOnly(gradleApi()) 16 | compileOnly(gradleKotlinDsl()) 17 | } 18 | 19 | java { 20 | sourceCompatibility = org.gradle.api.JavaVersion.VERSION_1_8 21 | targetCompatibility = org.gradle.api.JavaVersion.VERSION_1_8 22 | } 23 | 24 | kotlin { 25 | target { 26 | compilations.all { 27 | kotlinOptions.jvmTarget = "1.8" 28 | } 29 | } 30 | } 31 | 32 | gradlePlugin { 33 | plugins { 34 | create("dependencies") { 35 | id = "dependencies" 36 | implementationClass = "net.yslibrary.monotweety.dependencies.DependenciesPlugin" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app2/src/main/res/layout/item_2line_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /data/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class * extends com.google.protobuf.GeneratedMessageLite { *; } 24 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/setting/GetTwitterAppByPackageName.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.setting 2 | 3 | import net.yslibrary.monotweety.data.twitterapp.AppInfo 4 | import net.yslibrary.monotweety.data.twitterapp.TwitterApp 5 | import net.yslibrary.monotweety.data.twitterapp.TwitterAppRepository 6 | import javax.inject.Inject 7 | 8 | interface GetTwitterAppByPackageName { 9 | suspend operator fun invoke(packageName: String): AppInfo? 10 | } 11 | 12 | internal class GetTwitterAppByPackageNameImpl @Inject constructor( 13 | private val twitterAppRepository: TwitterAppRepository, 14 | ) : GetTwitterAppByPackageName { 15 | override suspend fun invoke(packageName: String): AppInfo? { 16 | val twitterApp = TwitterApp.fromPackageName(packageName) 17 | if (twitterApp == TwitterApp.NONE) return null 18 | return twitterAppRepository.getByPackageName(twitterApp) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app2/src/main/res/navigation/nav_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 18 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/status/remote/TestStatusRemoteRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.status.remote 2 | 3 | import io.reactivex.Single 4 | import net.yslibrary.monotweety.appdata.status.Tweet 5 | import net.yslibrary.monotweety.di.UserScope 6 | import javax.inject.Inject 7 | 8 | /** 9 | * This is a test implementation of StatusRemoteRepository. 10 | * you can switch to this implementation to test editor locally. 11 | */ 12 | @UserScope 13 | class TestStatusRemoteRepositoryImpl @Inject constructor() : StatusRemoteRepository { 14 | override fun update(status: String, inReplyToStatusId: Long?): Single { 15 | val tweet = Tweet( 16 | id = 0, 17 | inReplyToStatusId = inReplyToStatusId ?: 0, 18 | text = status, 19 | createdAt = System.currentTimeMillis().toString() 20 | ) 21 | 22 | return Single.just(tweet) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/setting/domain/SelectedTimelineAppInfoManager.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.setting.domain 2 | 3 | import io.reactivex.Observable 4 | import net.yslibrary.monotweety.appdata.appinfo.AppInfo 5 | import net.yslibrary.monotweety.appdata.appinfo.AppInfoManager 6 | import net.yslibrary.monotweety.appdata.setting.SettingDataManager 7 | import javax.inject.Inject 8 | import javax.inject.Singleton 9 | 10 | @Singleton 11 | class SelectedTimelineAppInfoManager @Inject constructor( 12 | private val appInfoManager: AppInfoManager, 13 | private val settingDataManager: SettingDataManager 14 | ) { 15 | 16 | fun get(): Observable { 17 | return settingDataManager.timelineAppPackageName() 18 | .flatMap { appInfoManager.appInfo(it).toObservable() } 19 | } 20 | 21 | fun set(selectedApp: AppInfo) { 22 | settingDataManager.timelineAppPackageName(selectedApp.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/license/LicenseViewModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.license 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import net.yslibrary.monotweety.base.EventBus 6 | import net.yslibrary.monotweety.base.di.ControllerScope 7 | import net.yslibrary.monotweety.base.di.Names 8 | import net.yslibrary.monotweety.license.domain.GetLicenses 9 | import javax.inject.Named 10 | 11 | @Module 12 | class LicenseViewModule(private val activityBus: EventBus) { 13 | 14 | @ControllerScope 15 | @Provides 16 | @Named(Names.FOR_ACTIVITY) 17 | fun provideActivityBus(): EventBus = activityBus 18 | 19 | @ControllerScope 20 | @Provides 21 | fun provideLicenseViewModel(getLicenses: GetLicenses): LicenseViewModel { 22 | return LicenseViewModel(getLicenses) 23 | } 24 | 25 | interface DependencyProvider { 26 | @Named(Names.FOR_ACTIVITY) 27 | fun activityBus(): EventBus 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/settings/SettingsDataModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.settings 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.createDataStore 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.Provides 9 | import net.yslibrary.monotweety.data.setting.SettingsPreferences 10 | import javax.inject.Singleton 11 | 12 | @Module 13 | internal interface SettingsDataModule { 14 | @Binds 15 | fun bindSettingsRepository(impl: SettingsRepositoryImpl): SettingsRepository 16 | 17 | companion object { 18 | @Singleton 19 | @Provides 20 | fun provideSettingsDataStore(context: Context): DataStore { 21 | return context.createDataStore( 22 | fileName = "setting_prefs.pb", 23 | serializer = SettingPreferencesSerializer, 24 | ) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/activity/ActivityModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.activity 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import net.yslibrary.monotweety.Navigator 6 | import net.yslibrary.monotweety.base.BaseActivity 7 | import net.yslibrary.monotweety.base.EventBus 8 | import net.yslibrary.monotweety.base.di.ActivityScope 9 | import net.yslibrary.monotweety.base.di.Names 10 | import javax.inject.Named 11 | 12 | @Module 13 | class ActivityModule(private val activity: BaseActivity) { 14 | 15 | @Provides 16 | @Named(Names.FOR_ACTIVITY) 17 | @ActivityScope 18 | fun provideActivityBus(): EventBus = EventBus() 19 | 20 | @Provides 21 | @ActivityScope 22 | fun provideNavigator(): Navigator { 23 | return Navigator(activity) 24 | } 25 | 26 | interface Provider { 27 | @Named(Names.FOR_ACTIVITY) 28 | fun activityBus(): EventBus 29 | 30 | fun navigator(): Navigator 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/settings/widget/SubHeaderItem.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.settings.widget 2 | 3 | import com.xwray.groupie.Item 4 | import net.yslibrary.monotweety.R 5 | import net.yslibrary.monotweety.databinding.ItemSubheaderBinding 6 | import net.yslibrary.monotweety.ui.base.groupie.BindableItem 7 | 8 | class SubHeaderItem( 9 | val title: String, 10 | ) : BindableItem( 11 | R.layout.item_subheader, 12 | ItemSubheaderBinding::bind, 13 | ) { 14 | override fun bind(viewBinding: ItemSubheaderBinding, position: Int) { 15 | viewBinding.title.text = title 16 | } 17 | 18 | override fun hasSameContentAs(other: Item<*>): Boolean { 19 | if (other !is SubHeaderItem) return false 20 | return other.title == title 21 | } 22 | 23 | override fun isSameAs(other: Item<*>): Boolean { 24 | if (other !is SubHeaderItem) return false 25 | return other.title == title 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /assets/description_en.txt: -------------------------------------------------------------------------------- 1 | # app name 2 | 3 | Montweety - just for tweeting 4 | 5 | # short description 6 | 7 | Open-source Twitter client just for tweeting from notification 8 | 9 | # description 10 | 11 | Monotweety is an open-source Twitter client just for tweeting from device's notification area. 12 | 13 | If you are using Android 7.0 or later, you can literally tweet from notification area. Other wise when you tap the notification, Editor dialog will be launched. 14 | 15 | Also, with Monotweety you can chain tweets as a thread with one single tap(from editor dialog). 16 | 17 | Features 18 | - tweet from notification 19 | - chain tweets as a thread 20 | - Open selected twitter app from notification 21 | - Quick Settings' tile to opening editor dialog(Android 7.0 and up). 22 | 23 | 24 | Monotweety is an open-source project. 25 | If you are interested in contributing, reporting bugs or requesting new feature, visit https://github.com/yshrsmz/monotweety. 26 | Also, you can contact me via twitter, @yslibnet 27 | -------------------------------------------------------------------------------- /_wercker.yml: -------------------------------------------------------------------------------- 1 | build: 2 | box: 3 | id: yshrsmz/android 4 | tag: latest 5 | steps: 6 | - script: 7 | name: obtain secrets 8 | code: echo $SECRETS | base64 -d > ./secret.properties 9 | - script: 10 | name: obtain release jks 11 | code: echo $RELEASE_JKS | base64 -d > ./monotweety.jks 12 | - script: 13 | name: obtain google-services.json 14 | code: echo $GOOGLE_SERVICES_JSON | base64 -d > ./app/google-services.json 15 | - script: 16 | name: run gradle assembleDebug 17 | code: | 18 | ./gradlew --project-cache-dir=$WERCKER_CACHE_DIR assembleDebug testDebugUnitTest createDebugUnitTestCoverageReport -PdisablePreDex 19 | 20 | after-steps: 21 | - script: 22 | name: codecov integration 23 | code: bash <(curl -s https://codecov.io/bash) -t $CODECOV_TOKEN -s "app/build/reports/jacoco/DebugUnitTest" 24 | - slack-notifier: 25 | url: $SLACK_URL 26 | channel: general 27 | username: Wercker build 28 | -------------------------------------------------------------------------------- /app/src/release/res/xml/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 14 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/debug/res/xml/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 14 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/base/ViewModelExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.base 2 | 3 | import androidx.lifecycle.LifecycleCoroutineScope 4 | import kotlinx.coroutines.flow.collect 5 | import kotlinx.coroutines.flow.onEach 6 | import net.yslibrary.monotweety.ui.arch.Effect 7 | import net.yslibrary.monotweety.ui.arch.MviViewModel 8 | import net.yslibrary.monotweety.ui.arch.State 9 | 10 | inline fun > VM.consumeEffects( 11 | scope: LifecycleCoroutineScope, 12 | crossinline effectHandler: (effect: E) -> Unit, 13 | ) { 14 | scope.launchWhenStarted { 15 | effects.onEach { effectHandler(it) } 16 | .collect() 17 | } 18 | } 19 | 20 | inline fun > VM.consumeStates( 21 | scope: LifecycleCoroutineScope, 22 | crossinline stateHandler: (state: S) -> Unit, 23 | ) { 24 | scope.launchWhenStarted { 25 | states.onEach { stateHandler(it) } 26 | .collect() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/RxExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.Single 5 | import io.reactivex.disposables.Disposable 6 | import io.reactivex.disposables.SerialDisposable 7 | import io.reactivex.functions.Consumer 8 | 9 | class SkipUntilCompletedConsumer(val doOnSubscribe: (T, () -> Unit) -> Unit) : Consumer { 10 | private var loading: Boolean = false 11 | override fun accept(t: T) { 12 | if (loading) { 13 | return 14 | } 15 | loading = true 16 | doOnSubscribe(t) { loading = false } 17 | } 18 | } 19 | 20 | fun Observable.subscribeWhenCompleted(doOnSubscribe: (t: T, completed: () -> Unit) -> Unit): Disposable { 21 | return subscribe(SkipUntilCompletedConsumer(doOnSubscribe)) 22 | } 23 | 24 | fun Disposable.setTo(disposable: SerialDisposable): Disposable { 25 | disposable.set(this) 26 | return this 27 | } 28 | 29 | fun T.toSingle(): Single = Single.just(this) 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/controller_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/base/ObjectWatcherDelegate.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.base 2 | 3 | import com.bluelinelabs.conductor.ControllerChangeType 4 | import leakcanary.ObjectWatcher 5 | import javax.inject.Inject 6 | 7 | interface ObjectWatcherDelegate { 8 | fun handleOnDestroy() 9 | fun handleOnChangeEnded(isDestroyed: Boolean, changeType: ControllerChangeType) 10 | } 11 | 12 | class ObjectWatcherDelegateImpl @Inject constructor( 13 | private val objectWatcher: ObjectWatcher 14 | ) : ObjectWatcherDelegate { 15 | 16 | private var hasExisted: Boolean = false 17 | 18 | override fun handleOnDestroy() { 19 | if (hasExisted) { 20 | objectWatcher.watch(this, "check Controller leak") 21 | } 22 | } 23 | 24 | override fun handleOnChangeEnded(isDestroyed: Boolean, changeType: ControllerChangeType) { 25 | 26 | hasExisted = !changeType.isEnter 27 | if (isDestroyed) { 28 | objectWatcher.watch(this, "check Controller leak") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/DataModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata 2 | 3 | import com.twitter.sdk.android.core.SessionManager 4 | import com.twitter.sdk.android.core.TwitterCore 5 | import com.twitter.sdk.android.core.TwitterSession 6 | import dagger.Module 7 | import dagger.Provides 8 | import net.yslibrary.monotweety.appdata.appinfo.AppInfoModule 9 | import net.yslibrary.monotweety.appdata.license.LicenseModule 10 | import net.yslibrary.monotweety.appdata.local.LocalModule 11 | import net.yslibrary.monotweety.appdata.session.SessionModule 12 | import net.yslibrary.monotweety.appdata.setting.SettingModule 13 | 14 | @Module( 15 | includes = [ 16 | AppInfoModule::class, 17 | LicenseModule::class, 18 | LocalModule::class, 19 | SessionModule::class, 20 | SettingModule::class, 21 | ] 22 | ) 23 | class DataModule { 24 | 25 | @Provides 26 | fun provideSessionManager(): SessionManager { 27 | return TwitterCore.getInstance().sessionManager 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/settings/widget/OneLineTextItem.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.settings.widget 2 | 3 | import androidx.core.view.doOnAttach 4 | import net.yslibrary.monotweety.R 5 | import net.yslibrary.monotweety.databinding.Item1lineTextBinding 6 | import net.yslibrary.monotweety.ui.base.groupie.BindableItem 7 | import net.yslibrary.monotweety.ui.base.setDebounceClickListener 8 | 9 | class OneLineTextItem( 10 | private val item: Item, 11 | private val onClick: (item: Item) -> Unit, 12 | ) : BindableItem(R.layout.item_1line_text, Item1lineTextBinding::bind) { 13 | 14 | override fun bind(viewBinding: Item1lineTextBinding, position: Int) { 15 | viewBinding.apply { 16 | title.text = item.title 17 | root.isEnabled = item.enabled 18 | root.doOnAttach { 19 | it.setDebounceClickListener { onClick(item) } 20 | } 21 | } 22 | } 23 | 24 | data class Item( 25 | val title: String, 26 | val enabled: Boolean, 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/user/UserModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.user 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import net.yslibrary.monotweety.appdata.user.local.UserLocalRepository 6 | import net.yslibrary.monotweety.appdata.user.local.UserLocalRepositoryImpl 7 | import net.yslibrary.monotweety.appdata.user.remote.UserRemoteRepository 8 | import net.yslibrary.monotweety.appdata.user.remote.UserRemoteRepositoryImpl 9 | import net.yslibrary.monotweety.di.UserScope 10 | 11 | @Module 12 | abstract class UserModule { 13 | 14 | @UserScope 15 | @Binds 16 | abstract fun bindUserRemoteRepository(repository: UserRemoteRepositoryImpl): UserRemoteRepository 17 | 18 | @UserScope 19 | @Binds 20 | abstract fun bindUserLocalRepository(repository: UserLocalRepositoryImpl): UserLocalRepository 21 | 22 | @UserScope 23 | @Binds 24 | abstract fun bindUserRepository(repository: UserRepositoryImpl): UserRepository 25 | 26 | interface Provider { 27 | fun userRepository(): UserRepository 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/test/java/net/yslibrary/monotweety/TestData.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import android.content.pm.ApplicationInfo 4 | import android.content.pm.PackageInfo 5 | import java.io.BufferedReader 6 | import java.io.FileInputStream 7 | import java.io.InputStreamReader 8 | 9 | fun readJsonFromAssets(filename: String): String { 10 | val ASSET_BASE_PATH = "../app/src/test/assets/" 11 | 12 | val br = BufferedReader(InputStreamReader(FileInputStream("$ASSET_BASE_PATH$filename"))) 13 | 14 | val sb = StringBuilder() 15 | var line = br.readLine() 16 | while (line != null) { 17 | sb.append(line) 18 | line = br.readLine() 19 | } 20 | 21 | br.close() 22 | return sb.toString() 23 | } 24 | 25 | fun newPackageInfo(appName: String, pacakgeName: String): PackageInfo { 26 | return PackageInfo().apply { 27 | packageName = pacakgeName 28 | applicationInfo = ApplicationInfo() 29 | 30 | applicationInfo.apply { 31 | packageName = pacakgeName 32 | name = appName 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /includedBuild/build-helper/src/main/java/net/yslibrary/monotweety/buildhelper/secrets.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.buildhelper 2 | 3 | import org.gradle.api.Project 4 | import java.util.Properties 5 | 6 | private fun Project.loadProperties(path: String): Properties { 7 | val propertiesFile = rootProject.rootDir.resolve(path) 8 | if (!propertiesFile.exists()) { 9 | throw IllegalStateException("'$path' does not exist") 10 | } 11 | val props = Properties() 12 | props.load(propertiesFile.inputStream()) 13 | return props 14 | } 15 | 16 | internal fun Project.loadSecrets(): Properties = loadProperties("secret.properties") 17 | 18 | fun splitAlternately(source: String): Pair { 19 | var result1 = "" 20 | var result2 = "" 21 | 22 | var i = 0 23 | val len = source.length 24 | while (i < len) { 25 | if (i % 2 == 0) { 26 | // even 27 | result1 += source[i] 28 | } else { 29 | result2 += source[i] 30 | } 31 | i++ 32 | } 33 | return result1 to result2 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/activity/main/MainActivityComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.activity.main 2 | 3 | import dagger.Component 4 | import net.yslibrary.monotweety.AppComponent 5 | import net.yslibrary.monotweety.activity.ActivityModule 6 | import net.yslibrary.monotweety.base.di.ActivityScope 7 | import net.yslibrary.monotweety.changelog.ChangelogViewModule 8 | import net.yslibrary.monotweety.license.LicenseViewModule 9 | import net.yslibrary.monotweety.login.LoginComponent 10 | import net.yslibrary.monotweety.setting.SettingViewModule 11 | import net.yslibrary.monotweety.splash.SplashComponent 12 | 13 | @ActivityScope 14 | @Component( 15 | dependencies = [AppComponent::class], 16 | modules = [ActivityModule::class] 17 | ) 18 | interface MainActivityComponent : ActivityModule.Provider, 19 | SplashComponent.ComponentProvider, 20 | LoginComponent.ComponentProvider, 21 | SettingViewModule.DependencyProvider, 22 | LicenseViewModule.DependencyProvider, 23 | ChangelogViewModule.DependencyProvider { 24 | fun inject(activity: MainActivity) 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/local/LocalModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.local 2 | 3 | import android.content.Context 4 | import android.database.sqlite.SQLiteOpenHelper 5 | import com.pushtorefresh.storio3.sqlite.StorIOSQLite 6 | import com.pushtorefresh.storio3.sqlite.impl.DefaultStorIOSQLite 7 | import dagger.Module 8 | import dagger.Provides 9 | import net.yslibrary.monotweety.appdata.user.User 10 | import net.yslibrary.monotweety.appdata.user.local.resolver.UserSQLiteTypeMapping 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | class LocalModule { 15 | 16 | @Singleton 17 | @Provides 18 | fun provideDbOpenHelper(context: Context): SQLiteOpenHelper { 19 | return DbOpenHelper(context) 20 | } 21 | 22 | @Singleton 23 | @Provides 24 | fun provideStorIOSQLite(sqLiteOpenHelper: SQLiteOpenHelper): StorIOSQLite { 25 | return DefaultStorIOSQLite.builder() 26 | .sqliteOpenHelper(sqLiteOpenHelper) 27 | .addTypeMapping(User::class.java, UserSQLiteTypeMapping()) 28 | .build() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/status/ComposeStatusViewModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.status 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import net.yslibrary.monotweety.Config 6 | import net.yslibrary.monotweety.base.di.ControllerScope 7 | import net.yslibrary.monotweety.setting.domain.FooterStateManager 8 | import net.yslibrary.monotweety.status.domain.CheckStatusLength 9 | import net.yslibrary.monotweety.status.domain.UpdateStatus 10 | 11 | @Module 12 | class ComposeStatusViewModule(private val status: String?) { 13 | 14 | @ControllerScope 15 | @Provides 16 | fun provideComposeStatusViewModel( 17 | config: Config, 18 | checkStatusLength: CheckStatusLength, 19 | updateStatus: UpdateStatus, 20 | footerStateManager: FooterStateManager 21 | ): ComposeStatusViewModel { 22 | return ComposeStatusViewModel( 23 | if (status.isNullOrBlank()) "" else status, 24 | config, 25 | checkStatusLength, 26 | updateStatus, 27 | footerStateManager 28 | ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/status/domain/CheckStatusLength.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.status.domain 2 | 3 | import com.twitter.twittertext.TwitterTextParser 4 | import io.reactivex.Single 5 | import net.yslibrary.monotweety.di.UserScope 6 | import javax.inject.Inject 7 | 8 | @UserScope 9 | class CheckStatusLength @Inject constructor() { 10 | 11 | fun execute(status: String): Single { 12 | return Single.fromCallable { 13 | val result = TwitterTextParser.parseTweet( 14 | status, 15 | TwitterTextParser.TWITTER_TEXT_DEFAULT_CONFIG 16 | ) 17 | Result( 18 | status = status, 19 | length = result.weightedLength, 20 | valid = result.isValid, 21 | maxLength = TwitterTextParser.TWITTER_TEXT_DEFAULT_CONFIG.maxWeightedTweetLength 22 | ) 23 | } 24 | } 25 | 26 | data class Result( 27 | val status: String, 28 | val length: Int, 29 | val valid: Boolean, 30 | val maxLength: Int 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/compose/ComposeActivityComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.compose 2 | 3 | import android.app.Activity 4 | import dagger.BindsInstance 5 | import dagger.Module 6 | import dagger.Subcomponent 7 | import net.yslibrary.monotweety.di.ActivityScope 8 | 9 | @ActivityScope 10 | @Subcomponent( 11 | modules = [ 12 | ComposeActivitySubcomponentModule::class, 13 | ] 14 | ) 15 | interface ComposeActivityComponent : ComposeActivitySubcomponentModule.ComponentProviders { 16 | fun inject(activity: ComposeActivity) 17 | 18 | @Subcomponent.Factory 19 | interface Factory { 20 | fun build(@BindsInstance activity: Activity): ComposeActivityComponent 21 | } 22 | 23 | interface ComponentProvider { 24 | fun composeActivityComponent(): Factory 25 | } 26 | } 27 | 28 | @Module( 29 | subcomponents = [ 30 | ComposeTweetDialogFragmentComponent::class, 31 | ] 32 | ) 33 | interface ComposeActivitySubcomponentModule { 34 | interface ComponentProviders : ComposeTweetDialogFragmentComponent.ComponentProvider 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/setting/SettingModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.setting 2 | 3 | import android.content.Context 4 | import com.f2prateek.rx.preferences2.RxSharedPreferences 5 | import dagger.Module 6 | import dagger.Provides 7 | import net.yslibrary.monotweety.base.di.Names 8 | import javax.inject.Named 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | class SettingModule { 13 | 14 | @Named(Names.FOR_SETTING) 15 | @Singleton 16 | @Provides 17 | fun provideSettingPreferences(context: Context): RxSharedPreferences { 18 | val prefs = context.getSharedPreferences( 19 | "net.yslibrary.monotweety.prefs.settings", 20 | Context.MODE_PRIVATE 21 | ) 22 | return RxSharedPreferences.create(prefs) 23 | } 24 | 25 | @Singleton 26 | @Provides 27 | fun provideSettingDataManager(@Named(Names.FOR_SETTING) prefs: RxSharedPreferences): SettingDataManager { 28 | return SettingDataManagerImpl(prefs) 29 | } 30 | 31 | interface Provider { 32 | fun settingDataManager(): SettingDataManager 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /includedBuild/build-helper/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | `java-gradle-plugin` 4 | id("dependencies") 5 | } 6 | 7 | group = "net.yslibrary.monotweety.buildhelper" 8 | version = "SNAPSHOT" 9 | 10 | repositories { 11 | mavenCentral() 12 | google() 13 | jcenter() 14 | } 15 | 16 | dependencies { 17 | compileOnly(gradleApi()) 18 | compileOnly(gradleKotlinDsl()) 19 | 20 | implementation("net.yslibrary.monotweety.dependencies:dependencies:SNAPSHOT") 21 | 22 | implementation(Plugins.kotlin) 23 | implementation(Plugins.android) 24 | } 25 | 26 | java { 27 | sourceCompatibility = org.gradle.api.JavaVersion.VERSION_1_8 28 | targetCompatibility = org.gradle.api.JavaVersion.VERSION_1_8 29 | } 30 | 31 | kotlin { 32 | target { 33 | compilations.all { 34 | kotlinOptions.jvmTarget = "1.8" 35 | } 36 | } 37 | } 38 | 39 | gradlePlugin { 40 | plugins { 41 | create("build-helper") { 42 | id = "build-helper" 43 | implementationClass = "net.yslibrary.monotweety.buildhelper.BuildHelperPlugin" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/analytics/Analytics.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.analytics 2 | 3 | import androidx.core.os.bundleOf 4 | import com.google.firebase.analytics.FirebaseAnalytics 5 | import javax.inject.Inject 6 | import kotlin.reflect.KClass 7 | 8 | class Analytics @Inject constructor( 9 | private val analytics: FirebaseAnalytics, 10 | ) { 11 | fun screenView(screen: Screen, clazz: KClass<*>) { 12 | analytics.logEvent( 13 | FirebaseAnalytics.Event.SCREEN_VIEW, 14 | bundleOf( 15 | FirebaseAnalytics.Param.SCREEN_NAME to screen.name, 16 | FirebaseAnalytics.Param.SCREEN_CLASS to clazz.simpleName, 17 | ) 18 | ) 19 | } 20 | 21 | fun logEvent() { 22 | 23 | } 24 | 25 | sealed class Screen(val name: String) { 26 | object Splash : Screen("splash") 27 | object Login : Screen("login") 28 | object Settings : Screen("setting") 29 | object License : Screen("compose_tweet") 30 | object Changelog : Screen("license") 31 | object Compose : Screen("changelog") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/user/local/resolver/UserGetResolver.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.user.local.resolver 2 | 3 | import android.database.Cursor 4 | import com.pushtorefresh.storio3.sqlite.StorIOSQLite 5 | import com.pushtorefresh.storio3.sqlite.operations.get.DefaultGetResolver 6 | import net.yslibrary.monotweety.appdata.local.getLongByName 7 | import net.yslibrary.monotweety.appdata.local.getStringByName 8 | import net.yslibrary.monotweety.appdata.user.User 9 | import net.yslibrary.monotweety.appdata.user.local.UserTable 10 | 11 | class UserGetResolver : DefaultGetResolver() { 12 | override fun mapFromCursor(storIOSQLite: StorIOSQLite, cursor: Cursor): User { 13 | return User( 14 | id = cursor.getLongByName(UserTable.COLUMN_ID), 15 | name = cursor.getStringByName(UserTable.COLUMN_NAME)!!, 16 | screenName = cursor.getStringByName(UserTable.COLUMN_SCREEN_NAME)!!, 17 | profileImageUrl = cursor.getStringByName(UserTable.COLUMN_PROFILE_IMAGE_URL)!!, 18 | _updatedAt = cursor.getLongByName(UserTable.COLUMN__UPDATED_AT) 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/vh_previous_status.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 23 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/activity/shortcut/ShortcutActivity.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.activity.shortcut 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import android.widget.Toast 8 | import net.yslibrary.monotweety.App 9 | import net.yslibrary.monotweety.R 10 | import net.yslibrary.monotweety.activity.compose.ComposeActivity 11 | 12 | class ShortcutActivity : Activity() { 13 | 14 | companion object { 15 | fun callingIntent(context: Context): Intent { 16 | return Intent(context, ShortcutActivity::class.java) 17 | } 18 | } 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | 23 | val loggedIn = App.appComponent(this).isLoggedIn().execute().blockingGet() 24 | 25 | if (!loggedIn) { 26 | Toast.makeText(this, R.string.error_not_authorized, Toast.LENGTH_SHORT).show() 27 | finish() 28 | return 29 | } 30 | 31 | startActivity(ComposeActivity.callingIntent(this)) 32 | finish() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/setting/GetTimelineTwitterApp.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain.setting 2 | 3 | import kotlinx.coroutines.flow.firstOrNull 4 | import net.yslibrary.monotweety.data.settings.SettingsRepository 5 | import net.yslibrary.monotweety.data.twitterapp.AppInfo 6 | import net.yslibrary.monotweety.data.twitterapp.TwitterApp 7 | import net.yslibrary.monotweety.data.twitterapp.TwitterAppRepository 8 | import javax.inject.Inject 9 | 10 | interface GetTimelineTwitterApp { 11 | suspend operator fun invoke(): AppInfo? 12 | } 13 | 14 | internal class GetTimelineTwitterAppImpl @Inject constructor( 15 | private val twitterAppRepository: TwitterAppRepository, 16 | private val settingsRepository: SettingsRepository, 17 | ) : GetTimelineTwitterApp { 18 | 19 | override suspend fun invoke(): AppInfo? { 20 | val settings = settingsRepository.settingsFlow.firstOrNull() ?: return null 21 | val twitterApp = TwitterApp.fromPackageName(settings.timelineAppPackageName) 22 | .takeUnless { it == TwitterApp.NONE } ?: return null 23 | 24 | return twitterAppRepository.getByPackageName(twitterApp) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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 | org.gradle.jvmargs=-Xmx1536m 13 | # 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | android.enableBuildCache=true 19 | org.gradle.daemon=true 20 | org.gradle.configureondemand=true 21 | org.gradle.caching=true 22 | # 23 | android.enableD8=true 24 | android.useAndroidX=true 25 | android.enableJetifier=true 26 | android.defaults.buildfeatures.buildconfig=false 27 | # 28 | # Kotlin code style for this project: "official" or "obsolete": 29 | kotlin.code.style=official 30 | 31 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/AppModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import android.content.Context 4 | import com.google.firebase.analytics.FirebaseAnalytics 5 | import dagger.Module 6 | import dagger.Provides 7 | import kotlinx.coroutines.Dispatchers 8 | import net.yslibrary.monotweety.base.CoroutineDispatchers 9 | import javax.inject.Singleton 10 | import kotlin.coroutines.CoroutineContext 11 | 12 | @Module 13 | interface AppModule { 14 | 15 | companion object { 16 | @Provides 17 | fun provideFirebaseAnalytics(context: Context): FirebaseAnalytics = 18 | FirebaseAnalytics.getInstance(context) 19 | 20 | @Singleton 21 | @Provides 22 | fun provideCoroutineDispatchers(): CoroutineDispatchers = newCoroutineDispatchers() 23 | 24 | @Singleton 25 | @Provides 26 | fun provideConfig(): Config = Config.init() 27 | } 28 | } 29 | 30 | private fun newCoroutineDispatchers(): CoroutineDispatchers { 31 | return object : CoroutineDispatchers { 32 | override val main: CoroutineContext = Dispatchers.Main 33 | override val background: CoroutineContext = Dispatchers.Default 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/base/NavigationExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.base 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import android.webkit.URLUtil 6 | import androidx.browser.customtabs.CustomTabsIntent 7 | import androidx.navigation.NavController 8 | import androidx.navigation.NavDirections 9 | 10 | fun Context.navigateToBrowser(url: String) { 11 | if (!url.isValidUrl()) return 12 | 13 | CustomTabsIntent.Builder() 14 | .build() 15 | .launchUrl(this, Uri.parse(url)) 16 | } 17 | 18 | /** 19 | * Check if the current destination can handle the provided [direction]. 20 | * 21 | * https://stackoverflow.com/questions/51060762/java-lang-illegalargumentexception-navigation-destination-xxx-is-unknown-to-thi 22 | */ 23 | fun NavController.navigateSafe(direction: NavDirections) { 24 | val action = 25 | currentDestination?.getAction(direction.actionId) ?: graph.getAction(direction.actionId) 26 | if (action != null && currentDestination?.id != action.destinationId) { 27 | navigate(direction) 28 | } 29 | } 30 | 31 | fun String.isValidUrl(): Boolean = URLUtil.isHttpUrl(this) || URLUtil.isHttpsUrl(this) 32 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/base/ContextExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.base 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.util.TypedValue 7 | import androidx.annotation.AttrRes 8 | 9 | fun Context.openExternalAppWithUrl(url: String) { 10 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) 11 | startActivity(intent) 12 | } 13 | 14 | fun Context.openExternalAppWithShareIntent(text: String) { 15 | val intent = Intent(Intent.ACTION_SEND) 16 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 17 | .putExtra(Intent.EXTRA_TEXT, text) 18 | .setType("text/plain") 19 | 20 | startActivity(intent) 21 | } 22 | 23 | fun Context.getIntValueFromAttribute(@AttrRes attrId: Int): Int { 24 | val tv = TypedValue() 25 | theme.resolveAttribute(attrId, tv, true) 26 | return tv.data 27 | } 28 | 29 | fun Context.getColorFromAttribute(@AttrRes attrId: Int): Int { 30 | val tv = TypedValue() 31 | theme.resolveAttribute(attrId, tv, true) 32 | val arr = obtainStyledAttributes(tv.data, intArrayOf(attrId)) 33 | return try { 34 | arr.getColor(0, -1) 35 | } finally { 36 | arr.recycle() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app2/src/main/res/drawable/ic_settings_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/appdata/user/local/resolver/UserPutResolver.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.user.local.resolver 2 | 3 | import android.content.ContentValues 4 | import com.pushtorefresh.storio3.sqlite.operations.put.DefaultPutResolver 5 | import com.pushtorefresh.storio3.sqlite.queries.InsertQuery 6 | import com.pushtorefresh.storio3.sqlite.queries.UpdateQuery 7 | import net.yslibrary.monotweety.appdata.user.User 8 | import net.yslibrary.monotweety.appdata.user.local.UserTable 9 | 10 | class UserPutResolver : DefaultPutResolver() { 11 | override fun mapToContentValues(entity: User): ContentValues { 12 | val cv = ContentValues(5) 13 | 14 | cv.put(UserTable.COLUMN_ID, entity.id) 15 | cv.put(UserTable.COLUMN_NAME, entity.name) 16 | cv.put(UserTable.COLUMN_SCREEN_NAME, entity.screenName) 17 | cv.put(UserTable.COLUMN_PROFILE_IMAGE_URL, entity.profileImageUrl) 18 | cv.put(UserTable.COLUMN__UPDATED_AT, entity._updatedAt) 19 | 20 | return cv 21 | } 22 | 23 | override fun mapToInsertQuery(entity: User): InsertQuery { 24 | return UserTable.insertQuery() 25 | } 26 | 27 | override fun mapToUpdateQuery(entity: User): UpdateQuery { 28 | return UserTable.updateById(entity.id) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/main/MainActivityComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.main 2 | 3 | import android.app.Activity 4 | import dagger.BindsInstance 5 | import dagger.Module 6 | import dagger.Subcomponent 7 | import net.yslibrary.monotweety.di.ActivityScope 8 | import net.yslibrary.monotweety.ui.footereditor.FooterEditorFragmentComponent 9 | import net.yslibrary.monotweety.ui.settings.SettingsFragmentComponent 10 | 11 | @ActivityScope 12 | @Subcomponent( 13 | modules = [ 14 | MainActivitySubcomponentModule::class, 15 | ] 16 | ) 17 | interface MainActivityComponent : MainActivitySubcomponentModule.ComponentProviders { 18 | fun inject(activity: MainActivity) 19 | 20 | @Subcomponent.Factory 21 | interface Factory { 22 | fun build(@BindsInstance activity: Activity): MainActivityComponent 23 | } 24 | 25 | interface ComponentProvider { 26 | fun mainActivityComponent(): Factory 27 | } 28 | } 29 | 30 | @Module( 31 | subcomponents = [ 32 | SettingsFragmentComponent::class, 33 | FooterEditorFragmentComponent::class, 34 | ] 35 | ) 36 | interface MainActivitySubcomponentModule { 37 | interface ComponentProviders : SettingsFragmentComponent.ComponentProvider, 38 | FooterEditorFragmentComponent.ComponentProvider 39 | } 40 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/domain/UserScopeDomainModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.domain 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import net.yslibrary.monotweety.domain.session.Logout 6 | import net.yslibrary.monotweety.domain.session.LogoutImpl 7 | import net.yslibrary.monotweety.domain.status.BuildAndValidateStatusString 8 | import net.yslibrary.monotweety.domain.status.BuildAndValidateStatusStringImpl 9 | import net.yslibrary.monotweety.domain.status.UpdateStatus 10 | import net.yslibrary.monotweety.domain.status.UpdateStatusImpl 11 | import net.yslibrary.monotweety.domain.user.FetchUser 12 | import net.yslibrary.monotweety.domain.user.FetchUserImpl 13 | import net.yslibrary.monotweety.domain.user.ObserveUser 14 | import net.yslibrary.monotweety.domain.user.ObserveUserImpl 15 | 16 | @Module 17 | internal interface UserScopeDomainModule { 18 | @Binds 19 | fun bindUpdateStatus(impl: UpdateStatusImpl): UpdateStatus 20 | 21 | @Binds 22 | fun bindBuildAndValidateStatusString(impl: BuildAndValidateStatusStringImpl): BuildAndValidateStatusString 23 | 24 | @Binds 25 | fun bindFetchUser(impl: FetchUserImpl): FetchUser 26 | 27 | @Binds 28 | fun bindObserveUser(impl: ObserveUserImpl): ObserveUser 29 | 30 | @Binds 31 | fun bindLogout(impl: LogoutImpl): Logout 32 | } 33 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/base/Debounce.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.base 2 | 3 | import android.view.View 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Job 6 | import kotlinx.coroutines.delay 7 | import kotlinx.coroutines.launch 8 | 9 | /** 10 | * https://medium.com/swlh/android-click-debounce-80b3f2e638f3 11 | */ 12 | fun debounce( 13 | delayMillis: Long = 800, 14 | scope: CoroutineScope, 15 | action: (T) -> Unit, 16 | ): (T) -> Unit { 17 | var debounceJob: Job? = null 18 | return { param: T -> 19 | if (debounceJob == null) { 20 | debounceJob = scope.launch { 21 | action(param) 22 | delay(delayMillis) 23 | debounceJob = null 24 | } 25 | } 26 | } 27 | } 28 | 29 | inline fun View.setDebounceClickListener( 30 | timeoutMillis: Long = 800, 31 | crossinline listener: (View) -> Unit, 32 | ) { 33 | setOnClickListener(object : View.OnClickListener { 34 | private var throttling = false 35 | 36 | override fun onClick(view: View) { 37 | if (throttling) return 38 | 39 | throttling = true 40 | view.postDelayed({ throttling = false }, timeoutMillis) 41 | listener(view) 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/notification/BootCompletedReceiver.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.notification 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import androidx.core.content.ContextCompat 7 | import io.reactivex.Observable 8 | import io.reactivex.functions.BiFunction 9 | import io.reactivex.rxkotlin.subscribeBy 10 | import net.yslibrary.monotweety.App 11 | import timber.log.Timber 12 | 13 | class BootCompletedReceiver : BroadcastReceiver() { 14 | override fun onReceive(context: Context, intent: Intent) { 15 | Timber.d("boot completed ---") 16 | 17 | Observable.zip( 18 | App.appComponent(context).notificationEnabledManager().get(), 19 | App.appComponent(context).isLoggedIn().execute().toObservable(), 20 | BiFunction { enabled: Boolean, loggedIn: Boolean -> enabled && loggedIn }) 21 | .firstOrError() 22 | .subscribeBy { 23 | Timber.d("is logged in and notification enabled: $it") 24 | if (it) { 25 | ContextCompat.startForegroundService( 26 | context, 27 | NotificationService.callingIntent(context) 28 | ) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_footer_editor.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 24 | 25 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/activity/shortcut/CreateShortcutActivity.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.activity.shortcut 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import androidx.core.content.pm.ShortcutInfoCompat 7 | import androidx.core.content.pm.ShortcutManagerCompat 8 | import androidx.core.graphics.drawable.IconCompat 9 | import net.yslibrary.monotweety.R 10 | 11 | class CreateShortcutActivity : Activity() { 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | 16 | val shortcut = ShortcutActivity.callingIntent(applicationContext) 17 | shortcut.action = Intent.ACTION_EDIT 18 | 19 | val shortcutInfo = ShortcutInfoCompat.Builder(applicationContext, "open_editor") 20 | .setIcon(IconCompat.createWithResource(applicationContext, R.drawable.ic_app)) 21 | .setLongLabel(getString(R.string.title_shortcut_editor)) 22 | .setShortLabel(getString(R.string.title_shortcut_editor)) 23 | .setIntent(shortcut) 24 | .build() 25 | 26 | val resultIntent = ShortcutManagerCompat 27 | .createShortcutResultIntent(applicationContext, shortcutInfo) 28 | 29 | setResult(RESULT_OK, resultIntent) 30 | finish() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/analytics/CrashReportingTree.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.analytics 2 | 3 | import android.util.Log 4 | import com.google.firebase.crashlytics.FirebaseCrashlytics 5 | import timber.log.Timber 6 | 7 | class CrashReportingTree : Timber.Tree() { 8 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { 9 | val logLevel = LogLevel.fromPriority(priority) 10 | if (t != null) { 11 | FirebaseCrashlytics.getInstance().recordException(t) 12 | } 13 | 14 | when (logLevel) { 15 | LogLevel.ERROR, 16 | LogLevel.WARN, 17 | LogLevel.INFO -> { 18 | FirebaseCrashlytics.getInstance().log("${logLevel.name}/$tag: $message") 19 | } 20 | else -> Unit // no-op 21 | } 22 | } 23 | 24 | @Suppress("unused") 25 | private enum class LogLevel(val priority: Int) { 26 | VERBOSE(Log.VERBOSE), 27 | DEBUG(Log.DEBUG), 28 | INFO(Log.INFO), 29 | WARN(Log.WARN), 30 | ERROR(Log.ERROR), 31 | ASSERT(Log.ASSERT); 32 | 33 | companion object { 34 | fun fromPriority(priority: Int): LogLevel { 35 | return values().firstOrNull { it.priority == priority } ?: DEBUG 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/notification/PackageReplacedReceiver.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.notification 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import androidx.core.content.ContextCompat 7 | import io.reactivex.Observable 8 | import io.reactivex.functions.BiFunction 9 | import io.reactivex.rxkotlin.subscribeBy 10 | import net.yslibrary.monotweety.App 11 | import timber.log.Timber 12 | 13 | class PackageReplacedReceiver : BroadcastReceiver() { 14 | 15 | override fun onReceive(context: Context, intent: Intent) { 16 | Timber.d("package replaced") 17 | 18 | Observable.zip( 19 | App.appComponent(context).notificationEnabledManager().get(), 20 | App.appComponent(context).isLoggedIn().execute().toObservable(), 21 | BiFunction { enabled: Boolean, loggedIn: Boolean -> enabled && loggedIn }) 22 | .firstOrError() 23 | .subscribeBy { 24 | Timber.d("is logged in and notification enabled: $it") 25 | if (it) { 26 | ContextCompat.startForegroundService( 27 | context, 28 | NotificationService.callingIntent(context) 29 | ) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/util/StringExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.util 2 | 3 | /** 4 | * concat provided strings alternately 5 | * 6 | * @param firstSource even index of the result string will be from this string. 7 | * @param secondSource uneven index of result string will be from this string. 8 | * @return String 9 | */ 10 | fun concatAlternately(firstSource: String, secondSource: String): String { 11 | var firstSource1 = firstSource 12 | var secondSource1 = secondSource 13 | var concat = "" 14 | var i = 0 15 | val len = firstSource1.length + secondSource1.length 16 | while (i < len) { 17 | var target: String 18 | if (i % 2 == 0) { 19 | // even 20 | target = firstSource1.substring(0, 1) 21 | firstSource1 = firstSource1.substring(1) 22 | 23 | if (firstSource1.length == 0) { 24 | target += secondSource1 25 | i = len 26 | } 27 | } else { 28 | target = secondSource1.substring(0, 1) 29 | secondSource1 = secondSource1.substring(1) 30 | 31 | if (secondSource1.isEmpty()) { 32 | target += firstSource1 33 | i = len 34 | } 35 | } 36 | concat += target 37 | i++ 38 | } 39 | 40 | return concat 41 | } -------------------------------------------------------------------------------- /app/src/test/java/net/yslibrary/monotweety/appdata/session/SessionRepositoryImplTest.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.appdata.session 2 | 3 | import com.twitter.sdk.android.core.SessionManager 4 | import com.twitter.sdk.android.core.TwitterSession 5 | import io.mockk.MockKAnnotations 6 | import io.mockk.confirmVerified 7 | import io.mockk.every 8 | import io.mockk.impl.annotations.MockK 9 | import io.mockk.verify 10 | import org.junit.Before 11 | import org.junit.Test 12 | 13 | class SessionRepositoryImplTest { 14 | 15 | @MockK 16 | lateinit var mockSessionManager: SessionManager 17 | lateinit var repository: SessionRepositoryImpl 18 | 19 | @Suppress("UNCHECKED_CAST") 20 | @Before 21 | fun setup() { 22 | MockKAnnotations.init(this, relaxUnitFun = true) 23 | repository = SessionRepositoryImpl(mockSessionManager) 24 | } 25 | 26 | @Test 27 | fun getActiveSession() { 28 | every { mockSessionManager.activeSession } returns null 29 | 30 | repository.getActiveSession().test() 31 | .apply { 32 | assertValueCount(1) 33 | assertComplete() 34 | 35 | verify { 36 | mockSessionManager.activeSession 37 | } 38 | 39 | confirmVerified(mockSessionManager) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/analytics/CrashReportingTree.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.analytics 2 | 3 | import android.util.Log 4 | import com.google.firebase.crashlytics.FirebaseCrashlytics 5 | import timber.log.Timber 6 | 7 | class CrashReportingTree : Timber.Tree() { 8 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { 9 | val logLevel = LogLevel.fromPriority(priority) 10 | if (t != null) { 11 | FirebaseCrashlytics.getInstance().recordException(t) 12 | } 13 | 14 | when (logLevel) { 15 | LogLevel.ERROR, 16 | LogLevel.WARN, 17 | LogLevel.INFO, 18 | -> { 19 | FirebaseCrashlytics.getInstance().log("${logLevel.name}/$tag: $message") 20 | } 21 | else -> Unit // no-op 22 | } 23 | } 24 | 25 | @Suppress("unused") 26 | private enum class LogLevel(val priority: Int) { 27 | VERBOSE(Log.VERBOSE), 28 | DEBUG(Log.DEBUG), 29 | INFO(Log.INFO), 30 | WARN(Log.WARN), 31 | ERROR(Log.ERROR), 32 | ASSERT(Log.ASSERT); 33 | 34 | companion object { 35 | fun fromPriority(priority: Int): LogLevel { 36 | return values().firstOrNull { it.priority == priority } ?: DEBUG 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/setting/domain/FooterStateManager.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.setting.domain 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.functions.BiFunction 5 | import io.reactivex.subjects.PublishSubject 6 | import net.yslibrary.monotweety.appdata.setting.SettingDataManager 7 | import javax.inject.Inject 8 | import javax.inject.Singleton 9 | 10 | @Singleton 11 | class FooterStateManager @Inject constructor( 12 | private val settingDataManager: SettingDataManager 13 | ) { 14 | 15 | private val subject: PublishSubject = PublishSubject.create() 16 | 17 | fun get(): Observable { 18 | return subject 19 | .startWith(Unit) 20 | .switchMapSingle { 21 | Observable.zip( 22 | settingDataManager.footerEnabled(), 23 | settingDataManager.footerText(), 24 | BiFunction { enabled: Boolean, footer: String -> State(enabled, footer) } 25 | ).firstOrError() 26 | } 27 | } 28 | 29 | fun set(state: State) { 30 | settingDataManager.footerEnabled(state.enabled) 31 | settingDataManager.footerText(state.text.trim()) 32 | subject.onNext(Unit) 33 | } 34 | 35 | data class State( 36 | val enabled: Boolean, 37 | val text: String 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/vh_2line_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 29 | 30 | 31 | 32 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/notification/NotificationServiceModule.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.notification 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import net.yslibrary.monotweety.base.di.ServiceScope 6 | import net.yslibrary.monotweety.setting.domain.FooterStateManager 7 | import net.yslibrary.monotweety.setting.domain.NotificationEnabledManager 8 | import net.yslibrary.monotweety.setting.domain.SelectedTimelineAppInfoManager 9 | import net.yslibrary.monotweety.status.domain.CheckStatusLength 10 | import net.yslibrary.monotweety.status.domain.UpdateStatus 11 | 12 | @Module 13 | class NotificationServiceModule(private val service: NotificationService) { 14 | 15 | @ServiceScope 16 | @Provides 17 | fun provideNotificationServiceViewModel( 18 | notificationEnabledManager: NotificationEnabledManager, 19 | checkStatusLength: CheckStatusLength, 20 | updateStatus: UpdateStatus, 21 | footerStateManager: FooterStateManager, 22 | selectedTimelineAppInfoManager: SelectedTimelineAppInfoManager 23 | ): NotificationServiceViewModel { 24 | return NotificationServiceViewModel( 25 | notificationEnabledManager, 26 | checkStatusLength, 27 | updateStatus, 28 | footerStateManager, 29 | selectedTimelineAppInfoManager 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/login/domain/DoLogout.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.login.domain 2 | 3 | import com.twitter.sdk.android.core.SessionManager 4 | import com.twitter.sdk.android.core.TwitterSession 5 | import io.reactivex.Completable 6 | import io.reactivex.Single 7 | import net.yslibrary.monotweety.appdata.setting.SettingDataManager 8 | import net.yslibrary.monotweety.appdata.user.UserRepository 9 | import net.yslibrary.monotweety.di.UserScope 10 | import javax.inject.Inject 11 | 12 | 13 | /** 14 | * Clear all user data 15 | * this usecase does not remove UserComponent, and does not stop NotificationService. 16 | * You should manually remove/stop these after this usecase completes. 17 | * And then you need to navigate to SplashController or finish the Application. 18 | * 19 | * Created by yshrsmz on 2016/10/09. 20 | */ 21 | @UserScope 22 | class DoLogout @Inject constructor( 23 | private val settingDataManager: SettingDataManager, 24 | private val userRepository: UserRepository, 25 | private val sessionManager: SessionManager 26 | ) { 27 | 28 | fun execute(): Completable { 29 | return Single.fromCallable { sessionManager.activeSession?.id } 30 | .flatMapCompletable { userRepository.delete(it) } 31 | .andThen(settingDataManager.clear()) 32 | .andThen(Completable.fromAction { sessionManager.clearActiveSession() }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import android.content.Context 4 | import com.codingfeline.twitter4kt.core.ConsumerKeys 5 | import dagger.BindsInstance 6 | import dagger.Component 7 | import kotlinx.datetime.Clock 8 | import net.yslibrary.monotweety.data.SingletonDataModule 9 | import net.yslibrary.monotweety.domain.SingletonDomainModule 10 | import net.yslibrary.monotweety.domain.session.ObserveSession 11 | import net.yslibrary.monotweety.domain.setting.ObserveSettings 12 | import net.yslibrary.monotweety.ui.AppUiSubcomponentModule 13 | import javax.inject.Singleton 14 | 15 | @Singleton 16 | @Component( 17 | modules = [ 18 | AppModule::class, 19 | SingletonDataModule::class, 20 | SingletonDomainModule::class, 21 | UserComponentModule::class, 22 | AppUiSubcomponentModule::class, 23 | ], 24 | ) 25 | interface AppComponent : UserComponent.ComponentProvider, 26 | AppUiSubcomponentModule.ComponentProviders { 27 | 28 | fun observeSession(): ObserveSession 29 | 30 | fun observeSettings(): ObserveSettings 31 | 32 | fun inject(app: App) 33 | 34 | @Component.Factory 35 | interface Factory { 36 | fun create( 37 | @BindsInstance context: Context, 38 | @BindsInstance consumerKeys: ConsumerKeys, 39 | @BindsInstance clock: Clock, 40 | ): AppComponent 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/auth/AuthFlow.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.auth 2 | 3 | import com.codingfeline.twitter4kt.core.getOrThrow 4 | import com.codingfeline.twitter4kt.core.model.oauth1a.AccessToken 5 | import com.codingfeline.twitter4kt.core.model.oauth1a.RequestToken 6 | import com.codingfeline.twitter4kt.core.oauth1a.OAuth1aFlow 7 | import com.codingfeline.twitter4kt.core.onSuccess 8 | import net.yslibrary.monotweety.data.session.Session 9 | import net.yslibrary.monotweety.data.session.SessionRepository 10 | import javax.inject.Inject 11 | 12 | interface AuthFlow { 13 | suspend fun getRequestToken(): RequestToken 14 | suspend fun getAccessToken(requestToken: RequestToken, verifierCode: String): AccessToken 15 | } 16 | 17 | internal class AuthFlowImpl @Inject constructor( 18 | private val oAuth1aFlow: OAuth1aFlow, 19 | private val sessionRepository: SessionRepository, 20 | ) : AuthFlow { 21 | 22 | override suspend fun getRequestToken(): RequestToken { 23 | return oAuth1aFlow.fetchRequestToken().getOrThrow() 24 | } 25 | 26 | override suspend fun getAccessToken( 27 | requestToken: RequestToken, 28 | verifierCode: String, 29 | ): AccessToken { 30 | return oAuth1aFlow.fetchAccessToken(requestToken.token, verifierCode) 31 | .onSuccess { sessionRepository.update(Session(it.token, it.secret)) } 32 | .getOrThrow() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/vh_editor.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 22 | 23 | 24 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/launcher/LauncherActivityComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.launcher 2 | 3 | import android.app.Activity 4 | import dagger.BindsInstance 5 | import dagger.Module 6 | import dagger.Subcomponent 7 | import net.yslibrary.monotweety.di.ActivityScope 8 | import net.yslibrary.monotweety.ui.login.LoginFragmentComponent 9 | import net.yslibrary.monotweety.ui.loginform.LoginFormFragmentComponent 10 | import net.yslibrary.monotweety.ui.splash.SplashFragmentComponent 11 | 12 | @ActivityScope 13 | @Subcomponent( 14 | modules = [ 15 | LauncherActivitySubcomponentModule::class 16 | ] 17 | ) 18 | interface LauncherActivityComponent : LauncherActivitySubcomponentModule.ComponentProviders { 19 | fun inject(activity: LauncherActivity) 20 | 21 | @Subcomponent.Factory 22 | interface Factory { 23 | fun build(@BindsInstance activity: Activity): LauncherActivityComponent 24 | } 25 | 26 | interface ComponentProvider { 27 | fun launcherActivityComponent(): Factory 28 | } 29 | } 30 | 31 | @Module( 32 | subcomponents = [ 33 | SplashFragmentComponent::class, 34 | LoginFragmentComponent::class, 35 | ] 36 | ) 37 | interface LauncherActivitySubcomponentModule { 38 | interface ComponentProviders : SplashFragmentComponent.ComponentProvider, 39 | LoginFragmentComponent.ComponentProvider, 40 | LoginFormFragmentComponent.ComponentProvider 41 | } 42 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/settings/widget/TwoLineTextItem.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.settings.widget 2 | 3 | import net.yslibrary.monotweety.R 4 | import net.yslibrary.monotweety.databinding.Item2lineTextBinding 5 | import net.yslibrary.monotweety.ui.base.groupie.BindableItem 6 | import net.yslibrary.monotweety.ui.base.setDebounceClickListener 7 | 8 | class TwoLineTextItem( 9 | private val item: Item, 10 | private val onClick: (item: Item) -> Unit, 11 | ) : BindableItem( 12 | R.layout.item_2line_text, 13 | Item2lineTextBinding::bind 14 | ) { 15 | override fun bind(viewBinding: Item2lineTextBinding, position: Int) { 16 | viewBinding.apply { 17 | title.text = item.title 18 | subtitle.text = item.subTitle 19 | root.isEnabled = item.enabled 20 | root.setDebounceClickListener { onClick(item) } 21 | } 22 | } 23 | 24 | override fun hasSameContentAs(other: com.xwray.groupie.Item<*>): Boolean { 25 | if (other !is TwoLineTextItem) return false 26 | return other.item == item 27 | } 28 | 29 | override fun isSameAs(other: com.xwray.groupie.Item<*>): Boolean { 30 | if (other !is TwoLineTextItem) return false 31 | return other.item.title == item.title 32 | } 33 | 34 | data class Item( 35 | val title: String, 36 | val subTitle: String, 37 | val enabled: Boolean, 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/StringExtensions.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | /** 4 | * concat provided strings alternately 5 | * 6 | * @param firstSource even index of the result string will be from this string. 7 | * @param secondSource uneven index of result string will be from this string. 8 | * @return String 9 | */ 10 | fun concatAlternately(firstSource: String, secondSource: String): String { 11 | var firstSource1 = firstSource 12 | var secondSource1 = secondSource 13 | var concat = "" 14 | var i = 0 15 | val len = firstSource1.length + secondSource1.length 16 | while (i < len) { 17 | var target: String 18 | if (i % 2 == 0) { 19 | // even 20 | target = firstSource1.substring(0, 1) 21 | firstSource1 = firstSource1.substring(1) 22 | 23 | if (firstSource1.length == 0) { 24 | target += secondSource1 25 | i = len 26 | } 27 | } else { 28 | target = secondSource1.substring(0, 1) 29 | secondSource1 = secondSource1.substring(1) 30 | 31 | if (secondSource1.isEmpty()) { 32 | target += firstSource1 33 | i = len 34 | } 35 | } 36 | concat += target 37 | i++ 38 | } 39 | 40 | return concat 41 | } 42 | 43 | internal fun Pair.concatAlternately(): String = 44 | net.yslibrary.monotweety.concatAlternately(first, second) 45 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/logout/LogoutService.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.logout 2 | 3 | import android.app.IntentService 4 | import android.content.Context 5 | import android.content.Intent 6 | import net.yslibrary.monotweety.App 7 | import net.yslibrary.monotweety.activity.main.MainActivity 8 | import net.yslibrary.monotweety.login.domain.DoLogout 9 | import javax.inject.Inject 10 | import kotlin.properties.Delegates 11 | 12 | class LogoutService : IntentService(NAME) { 13 | 14 | @set:[Inject] 15 | var doLogout by Delegates.notNull() 16 | 17 | val component: LogoutComponent by lazy { 18 | DaggerLogoutComponent.builder() 19 | .userComponent(App.userComponent(this)) 20 | .build() 21 | } 22 | 23 | override fun onCreate() { 24 | super.onCreate() 25 | 26 | component.inject(this) 27 | } 28 | 29 | override fun onHandleIntent(intent: Intent?) { 30 | doLogout.execute().blockingAwait() 31 | App.clearUserComponent(this) 32 | 33 | val activityIntent = MainActivity.callingIntent(this) 34 | activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) 35 | 36 | startActivity(activityIntent) 37 | } 38 | 39 | companion object { 40 | const val NAME = "LogoutService" 41 | 42 | fun callingIntent(context: Context): Intent { 43 | return Intent(context, LogoutService::class.java) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/launcher/LauncherActivity.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.launcher 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import net.yslibrary.monotweety.App 7 | import net.yslibrary.monotweety.R 8 | import net.yslibrary.monotweety.analytics.Analytics 9 | import net.yslibrary.monotweety.databinding.ActivityMainBinding 10 | import net.yslibrary.monotweety.ui.base.ViewBindingAppCompatActivity 11 | import net.yslibrary.monotweety.ui.di.HasComponent 12 | import javax.inject.Inject 13 | 14 | class LauncherActivity : ViewBindingAppCompatActivity( 15 | R.layout.activity_launcher, 16 | ActivityMainBinding::bind 17 | ), HasComponent { 18 | 19 | override val component: LauncherActivityComponent by lazy { 20 | App.appComponent(this) 21 | .launcherActivityComponent() 22 | .build(this) 23 | } 24 | 25 | @Inject 26 | lateinit var analyrics: Analytics 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | component.inject(this) 30 | super.onCreate(savedInstanceState) 31 | setTheme(R.style.AppTheme) 32 | } 33 | 34 | companion object { 35 | fun callingIntent(context: Context): Intent { 36 | return Intent(context.applicationContext, LauncherActivity::class.java) 37 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 25 | 26 | 27 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /data/src/main/java/net/yslibrary/monotweety/data/user/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.data.user 2 | 3 | import com.codingfeline.twitter4kt.core.getOrThrow 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.datetime.Clock 6 | import net.yslibrary.monotweety.data.user.local.UserLocalGateway 7 | import net.yslibrary.monotweety.data.user.remote.UserRemoteGateway 8 | import net.yslibrary.monotweety.di.UserScope 9 | import javax.inject.Inject 10 | 11 | interface UserRepository { 12 | val userFlow: Flow 13 | suspend fun delete() 14 | suspend fun fetch() 15 | } 16 | 17 | @UserScope 18 | internal class UserRepositoryImpl @Inject constructor( 19 | private val remoteGateway: UserRemoteGateway, 20 | private val localGateway: UserLocalGateway, 21 | private val clock: Clock, 22 | ) : UserRepository { 23 | override val userFlow: Flow = localGateway.userFlow 24 | 25 | override suspend fun delete() { 26 | localGateway.delete() 27 | } 28 | 29 | override suspend fun fetch() { 30 | val result = remoteGateway.verifyCredentials() 31 | val account = result.getOrThrow() 32 | localGateway.update( 33 | User( 34 | id = account.idStr, 35 | name = account.name, 36 | screenName = account.screenName, 37 | profileImageUrl = account.profileImageUrlHttps, 38 | updatedAt = clock.now().epochSeconds 39 | ) 40 | ) 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | includeBuild("includedBuild/dependencies") 2 | includeBuild("includedBuild/build-helper") 3 | 4 | pluginManagement { 5 | resolutionStrategy { 6 | eachPlugin { 7 | when (requested.id.id) { 8 | "com.android.application", "com.android.library" -> 9 | useModule("com.android.tools.build:gradle:${requested.version}") 10 | "com.google.gms.google-services" -> 11 | useModule("com.google.gms:google-services:${requested.version}") 12 | "com.google.firebase.crashlytics" -> 13 | useModule("com.google.firebase:firebase-crashlytics-gradle:${requested.version}") 14 | "androidx.navigation.safeargs.kotlin" -> 15 | useModule("androidx.navigation:navigation-safe-args-gradle-plugin:${requested.version}") 16 | "dagger.hilt.android.plugin" -> 17 | useModule("com.google.dagger:hilt-android-gradle-plugin:${requested.version}") 18 | "com.google.android.gms.oss-licenses-plugin" -> 19 | useModule("com.google.android.gms:oss-licenses-plugin:${requested.version}") 20 | } 21 | } 22 | } 23 | repositories { 24 | gradlePluginPortal() 25 | google() 26 | mavenCentral() 27 | maven(url = "https://plugins.gradle.org/m2/") 28 | jcenter() 29 | } 30 | } 31 | 32 | include( 33 | ":di-common", 34 | ":data", 35 | // ":app", 36 | ":app2" 37 | ) 38 | -------------------------------------------------------------------------------- /app2/src/main/java/net/yslibrary/monotweety/ui/settings/widget/DividerItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.ui.settings.widget 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.drawable.Drawable 6 | import androidx.recyclerview.widget.RecyclerView 7 | import net.yslibrary.monotweety.ui.base.groupie.GroupieViewHolder 8 | 9 | private val ATTRS = intArrayOf(android.R.attr.listDivider) 10 | 11 | class DividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() { 12 | 13 | private val divider: Drawable 14 | 15 | init { 16 | val a = context.obtainStyledAttributes(ATTRS) 17 | divider = a.getDrawable(0)!! 18 | a.recycle() 19 | } 20 | 21 | override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { 22 | val left = parent.paddingLeft 23 | val right = parent.width - parent.paddingRight 24 | 25 | (0 until parent.childCount).forEach { index -> 26 | val child = parent.getChildAt(index) 27 | val holder = parent.getChildViewHolder(child) 28 | if (holder is GroupieViewHolder<*> && holder.item is SubHeaderItem) { 29 | val params = child.layoutParams as RecyclerView.LayoutParams 30 | 31 | val bottom = child.top - params.topMargin 32 | val top = bottom - divider.intrinsicHeight 33 | 34 | divider.setBounds(left, top, right, bottom) 35 | divider.draw(c) 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety 2 | 3 | import android.content.Context 4 | import com.twitter.sdk.android.core.TwitterSession 5 | import dagger.BindsInstance 6 | import dagger.Component 7 | import io.reactivex.subjects.PublishSubject 8 | import net.yslibrary.monotweety.analytics.Analytics 9 | import net.yslibrary.monotweety.appdata.DataModule 10 | import net.yslibrary.monotweety.base.ObjectWatcherDelegate 11 | import net.yslibrary.monotweety.base.di.Names 12 | import net.yslibrary.monotweety.login.domain.IsLoggedIn 13 | import net.yslibrary.monotweety.setting.domain.FooterStateManager 14 | import net.yslibrary.monotweety.setting.domain.NotificationEnabledManager 15 | import javax.inject.Named 16 | import javax.inject.Singleton 17 | 18 | @Singleton 19 | @Component( 20 | modules = [AppModule::class, DataModule::class] 21 | ) 22 | interface AppComponent : UserComponent.ComponentProvider { 23 | fun inject(app: App) 24 | 25 | fun isLoggedIn(): IsLoggedIn 26 | 27 | fun notificationEnabledManager(): NotificationEnabledManager 28 | 29 | fun footerStateManager(): FooterStateManager 30 | 31 | fun objectWatcherDelegate(): ObjectWatcherDelegate 32 | 33 | fun analytics(): Analytics 34 | 35 | @Named(Names.FOR_LOGIN) 36 | fun loginCompletedSubject(): PublishSubject 37 | 38 | @Component.Factory 39 | interface Factory { 40 | fun create( 41 | appModule: AppModule, 42 | @BindsInstance context: Context, 43 | ): AppComponent 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/net/yslibrary/monotweety/setting/adapter/SubHeaderDividerDecoration.kt: -------------------------------------------------------------------------------- 1 | package net.yslibrary.monotweety.setting.adapter 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.drawable.Drawable 6 | import androidx.recyclerview.widget.RecyclerView 7 | 8 | class SubHeaderDividerDecoration(context: Context) : RecyclerView.ItemDecoration() { 9 | 10 | private val divider: Drawable 11 | 12 | init { 13 | val a = context.obtainStyledAttributes(ATTRS) 14 | divider = a.getDrawable(0)!! 15 | a.recycle() 16 | } 17 | 18 | override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { 19 | val left = parent.paddingLeft 20 | val right = parent.width - parent.paddingRight 21 | 22 | 0.rangeTo(parent.childCount - 1) 23 | .forEach { 24 | val child = parent.getChildAt(it) 25 | val holder = parent.getChildViewHolder(child) 26 | if (holder is SubHeaderAdapterDelegate.ViewHolder && it != 0) { 27 | val params = child.layoutParams as RecyclerView.LayoutParams 28 | 29 | val bottom = child.top - params.topMargin 30 | val top = bottom - divider.intrinsicHeight 31 | 32 | divider.setBounds(left, top, right, bottom) 33 | divider.draw(c) 34 | } 35 | } 36 | } 37 | 38 | companion object { 39 | private val ATTRS = intArrayOf(android.R.attr.listDivider) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/controller_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 30 | 31 | 39 | 40 | 41 | --------------------------------------------------------------------------------