From 6b23bf1daa838a419e8a187a41be240fb1e9c3b6 Mon Sep 17 00:00:00 2001 From: mehul4795 Date: Sat, 27 Mar 2021 10:55:34 +0530 Subject: [PATCH] Initial commit --- .gitignore | 14 + .idea/.gitignore | 3 + .idea/compiler.xml | 6 + .idea/gradle.xml | 22 + .idea/jarRepositories.xml | 30 + .idea/misc.xml | 9 + app/.gitignore | 1 + app/build.gradle | 141 ++++ app/proguard-rules.pro | 56 ++ app/release/output.json | 1 + app/src/main/AndroidManifest.xml | 58 ++ app/src/main/ic_launcher-web.png | Bin 0 -> 16616 bytes .../java/aculix/channelify/app/Channelify.kt | 89 +++ .../channelify/app/activity/MainActivity.kt | 61 ++ .../channelify/app/activity/SplashActivity.kt | 16 + .../app/activity/VideoPlayerActivity.kt | 132 ++++ .../channelify/app/api/ChannelInfoService.kt | 17 + .../channelify/app/api/ChannelsService.kt | 16 + .../app/api/CommentRepliesService.kt | 22 + .../channelify/app/api/CommentService.kt | 25 + .../app/api/PlaylistItemsService.kt | 20 + .../channelify/app/api/PlaylistsService.kt | 19 + .../channelify/app/api/SearchVideoService.kt | 20 + .../channelify/app/api/VideosService.kt | 17 + .../channelify/app/db/ChannelifyDatabase.kt | 11 + .../channelify/app/db/FavoriteVideoDao.kt | 33 + .../aculix/channelify/app/di/aboutModule.kt | 22 + .../aculix/channelify/app/di/appModule.kt | 120 ++++ .../channelify/app/di/commentRepliesModule.kt | 24 + .../channelify/app/di/commentsModule.kt | 21 + .../channelify/app/di/favoritesModule.kt | 15 + .../aculix/channelify/app/di/homeModule.kt | 28 + .../channelify/app/di/playlistVideosModule.kt | 17 + .../channelify/app/di/playlistsModule.kt | 23 + .../aculix/channelify/app/di/searchModule.kt | 22 + .../channelify/app/di/videoDetailsModule.kt | 29 + .../channelify/app/di/videoPlayerModule.kt | 13 + .../app/fastadapteritems/CommentItem.kt | 90 +++ .../app/fastadapteritems/CommentReplyItem.kt | 77 +++ .../app/fastadapteritems/FavoriteItem.kt | 57 ++ .../app/fastadapteritems/HomeItem.kt | 46 ++ .../app/fastadapteritems/PlaylistItem.kt | 47 ++ .../app/fastadapteritems/PlaylistVideoItem.kt | 52 ++ .../fastadapteritems/ProgressIndicatorItem.kt | 31 + .../app/fastadapteritems/SearchItem.kt | 48 ++ .../channelify/app/fragment/AboutFragment.kt | 224 +++++++ .../app/fragment/AppInfoFragment.kt | 118 ++++ .../app/fragment/CommentRepliesFragment.kt | 206 ++++++ .../app/fragment/CommentsFragment.kt | 272 ++++++++ .../app/fragment/FavoritesFragment.kt | 159 +++++ .../channelify/app/fragment/HomeFragment.kt | 247 +++++++ .../app/fragment/PlaylistVideosFragment.kt | 249 +++++++ .../app/fragment/PlaylistsFragment.kt | 221 +++++++ .../channelify/app/fragment/SearchFragment.kt | 244 +++++++ .../app/fragment/VideoDetailsFragment.kt | 213 ++++++ .../channelify/app/model/ChannelInfo.kt | 58 ++ .../app/model/ChannelUploadsPlaylistInfo.kt | 26 + .../aculix/channelify/app/model/Comment.kt | 32 + .../channelify/app/model/CommentReply.kt | 23 + .../channelify/app/model/FavoriteVideo.kt | 11 + .../aculix/channelify/app/model/Playlist.kt | 57 ++ .../channelify/app/model/PlaylistItemInfo.kt | 63 ++ .../channelify/app/model/SearchedVideo.kt | 59 ++ .../java/aculix/channelify/app/model/Video.kt | 67 ++ .../channelify/app/paging/NetworkState.kt | 20 + .../datasource/CommentRepliesDataSource.kt | 87 +++ .../paging/datasource/CommentsDataSource.kt | 87 +++ .../app/paging/datasource/HomeDataSource.kt | 87 +++ .../datasource/PlaylistVideosDataSource.kt | 87 +++ .../paging/datasource/PlaylistsDataSource.kt | 87 +++ .../app/paging/datasource/SearchDataSource.kt | 90 +++ .../CommentRepliesDataSourceFactory.kt | 33 + .../CommentsDataSourceFactory.kt | 40 ++ .../HomeDataSourceFactory.kt | 33 + .../PlaylistVideosDataSourceFactory.kt | 33 + .../PlaylistsDataSourceFactory.kt | 33 + .../SearchDataSourceFactory.kt | 42 ++ .../app/repository/AboutRepository.kt | 9 + .../repository/CommentRepliesRepository.kt | 13 + .../app/repository/CommentsRepository.kt | 11 + .../app/repository/FavoritesRepository.kt | 26 + .../app/repository/HomeRepository.kt | 20 + .../repository/PlaylistVideosRepository.kt | 9 + .../app/repository/PlaylistsRepository.kt | 9 + .../app/repository/SearchRepository.kt | 21 + .../app/repository/VideoDetailsRepository.kt | 35 + .../app/repository/VideoPlayerRepository.kt | 8 + .../channelify/app/sharedpref/AppPref.kt | 7 + .../app/utils/ActivityExtensions.kt | 25 + .../aculix/channelify/app/utils/Constants.kt | 8 + .../channelify/app/utils/DateTimeUtils.kt | 35 + .../app/utils/DividerItemDecorator.kt | 26 + .../app/utils/FragmentActivityExtensions.kt | 26 + .../channelify/app/utils/FullScreenHelper.kt | 60 ++ .../app/viewmodel/AboutViewModel.kt | 38 ++ .../app/viewmodel/CommentRepliesViewModel.kt | 53 ++ .../app/viewmodel/CommentsViewModel.kt | 81 +++ .../app/viewmodel/FavoritesViewModel.kt | 29 + .../channelify/app/viewmodel/HomeViewModel.kt | 73 ++ .../app/viewmodel/PlaylistVideosViewModel.kt | 55 ++ .../app/viewmodel/PlaylistsViewModel.kt | 72 ++ .../app/viewmodel/SearchViewModel.kt | 80 +++ .../app/viewmodel/VideoDetailsViewModel.kt | 55 ++ .../app/viewmodel/VideoPlayerViewModel.kt | 7 + app/src/main/res/anim/slide_in_bottom.xml | 8 + app/src/main/res/anim/slide_out_bottom.xml | 8 + .../drawable-hdpi/ic_stat_notification.png | Bin 0 -> 769 bytes .../drawable-mdpi/ic_stat_notification.png | Bin 0 -> 515 bytes .../res/drawable-night/ic_empty_comments.xml | 127 ++++ .../res/drawable-night/ic_empty_favorites.xml | 116 ++++ .../res/drawable-night/ic_empty_playlists.xml | 624 ++++++++++++++++++ app/src/main/res/drawable-night/ic_error.xml | 105 +++ .../drawable-night/ic_error_video_details.xml | 245 +++++++ .../drawable-xhdpi/ic_stat_notification.png | Bin 0 -> 1008 bytes .../drawable-xxhdpi/ic_stat_notification.png | Bin 0 -> 1527 bytes .../drawable-xxxhdpi/ic_stat_notification.png | Bin 0 -> 2078 bytes app/src/main/res/drawable/bg_rounded_rect.xml | 15 + app/src/main/res/drawable/bg_splash.xml | 15 + app/src/main/res/drawable/ic_about.xml | 9 + app/src/main/res/drawable/ic_arrow_back.xml | 9 + app/src/main/res/drawable/ic_close.xml | 10 + app/src/main/res/drawable/ic_comment.xml | 10 + app/src/main/res/drawable/ic_dislike.xml | 10 + app/src/main/res/drawable/ic_email.xml | 9 + .../main/res/drawable/ic_empty_comments.xml | 127 ++++ .../main/res/drawable/ic_empty_favorites.xml | 116 ++++ .../main/res/drawable/ic_empty_playlists.xml | 624 ++++++++++++++++++ app/src/main/res/drawable/ic_error.xml | 105 +++ app/src/main/res/drawable/ic_error_about.xml | 180 +++++ .../res/drawable/ic_error_video_details.xml | 245 +++++++ .../main/res/drawable/ic_favorite_border.xml | 10 + .../main/res/drawable/ic_favorite_filled.xml | 11 + .../drawable/ic_favorite_filled_border.xml | 13 + app/src/main/res/drawable/ic_forward.xml | 10 + app/src/main/res/drawable/ic_google_play.xml | 18 + app/src/main/res/drawable/ic_home.xml | 9 + app/src/main/res/drawable/ic_info.xml | 10 + app/src/main/res/drawable/ic_instagram.xml | 15 + app/src/main/res/drawable/ic_like.xml | 10 + app/src/main/res/drawable/ic_playlists.xml | 9 + .../main/res/drawable/ic_privacy_policy.xml | 9 + app/src/main/res/drawable/ic_retry.xml | 10 + app/src/main/res/drawable/ic_rewind.xml | 10 + app/src/main/res/drawable/ic_search.xml | 10 + app/src/main/res/drawable/ic_share.xml | 10 + app/src/main/res/drawable/ic_sort.xml | 10 + app/src/main/res/drawable/ic_star.xml | 9 + app/src/main/res/drawable/ic_store.xml | 10 + app/src/main/res/drawable/ic_theme_dark.xml | 10 + app/src/main/res/drawable/ic_theme_light.xml | 10 + app/src/main/res/drawable/ic_tos.xml | 9 + app/src/main/res/drawable/ic_unfold_more.xml | 10 + app/src/main/res/drawable/ic_view_count.xml | 10 + app/src/main/res/drawable/ic_website.xml | 9 + app/src/main/res/drawable/logo_splash.png | Bin 0 -> 4576 bytes .../drawable/view_divider_item_decorator.xml | 8 + app/src/main/res/font/roboto.xml | 7 + app/src/main/res/layout/activity_main.xml | 38 ++ .../main/res/layout/activity_video_player.xml | 30 + app/src/main/res/layout/fragment_about.xml | 287 ++++++++ app/src/main/res/layout/fragment_app_info.xml | 151 +++++ .../res/layout/fragment_comment_replies.xml | 54 ++ app/src/main/res/layout/fragment_comments.xml | 99 +++ .../main/res/layout/fragment_favorites.xml | 63 ++ app/src/main/res/layout/fragment_home.xml | 88 +++ .../res/layout/fragment_playlist_details.xml | 70 ++ .../res/layout/fragment_playlist_videos.xml | 31 + .../main/res/layout/fragment_playlists.xml | 77 +++ app/src/main/res/layout/fragment_search.xml | 115 ++++ .../res/layout/fragment_video_details.xml | 219 ++++++ app/src/main/res/layout/item_comment.xml | 79 +++ .../main/res/layout/item_comment_reply.xml | 57 ++ app/src/main/res/layout/item_favorite.xml | 52 ++ app/src/main/res/layout/item_home.xml | 53 ++ app/src/main/res/layout/item_playlist.xml | 58 ++ .../main/res/layout/item_playlist_video.xml | 53 ++ .../res/layout/item_progress_indicator.xml | 13 + app/src/main/res/layout/item_search.xml | 53 ++ app/src/main/res/layout/widget_toolbar.xml | 40 ++ app/src/main/res/menu/app_info_menu.xml | 33 + .../main/res/menu/bab_menu_video_details.xml | 16 + .../main/res/menu/bottom_navigation_menu.xml | 27 + .../menu/comments_sort_menu_video_details.xml | 13 + app/src/main/res/menu/main_menu.xml | 14 + app/src/main/res/menu/toolbar_menu_about.xml | 23 + .../res/menu/toolbar_menu_playlist_videos.xml | 16 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3993 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 2390 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3993 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2475 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 1565 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2475 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 5678 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 3231 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5678 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 8872 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 5065 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 8872 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 12715 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 7086 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 12715 bytes app/src/main/res/navigation/nav_graph.xml | 80 +++ .../res/navigation/video_player_graph.xml | 50 ++ app/src/main/res/values-night/colors.xml | 11 + app/src/main/res/values-night/config.xml | 4 + app/src/main/res/values-night/styles.xml | 5 + app/src/main/res/values/colors.xml | 15 + app/src/main/res/values/config.xml | 8 + app/src/main/res/values/dimens.xml | 15 + app/src/main/res/values/font_certs.xml | 17 + .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/ids.xml | 11 + app/src/main/res/values/preloaded_fonts.xml | 6 + app/src/main/res/values/strings.xml | 97 +++ app/src/main/res/values/styles.xml | 42 ++ build.gradle | 51 ++ core/.gitignore | 1 + core/build.gradle | 48 ++ core/proguard-rules.pro | 24 + core/src/main/AndroidManifest.xml | 6 + .../java/aculix/core/base/BaseFragment.kt | 17 + .../base/RoundedBottomSheetDialogFragment.kt | 20 + .../core/extensions/ButtonExtensions.kt | 37 ++ .../aculix/core/extensions/ChipExtensions.kt | 11 + .../core/extensions/ContextExtensions.kt | 137 ++++ .../core/extensions/EditTextExtensions.kt | 25 + .../core/extensions/FragmentExtensions.kt | 71 ++ .../core/extensions/ImageViewExtensions.kt | 2 + .../aculix/core/extensions/IntExtensions.kt | 32 + .../aculix/core/extensions/LongExtensions.kt | 19 + .../core/extensions/StringExtensions.kt | 15 + .../aculix/core/extensions/ViewExtensions.kt | 76 +++ .../core/extensions/ViewgroupExtensions.kt | 10 + .../core/helper/BaseViewModelFactory.kt | 14 + .../java/aculix/core/helper/ResultWrapper.kt | 14 + .../helper/SimpleDividerItemDecoration.kt | 34 + .../drawable-v24/ic_launcher_background.xml | 24 + .../src/main/res/drawable/bg_bottom_sheet.xml | 9 + .../src/main/res/drawable/bg_line_divider.xml | 10 + .../res/drawable/ic_launcher_foreground.xml | 33 + .../src/main/res/drawable/ic_place_holder.xml | 9 + .../res/drawable/ic_place_holder_wrapper.xml | 8 + core/src/main/res/values/colors.xml | 4 + core/src/main/res/values/dimens.xml | 8 + core/src/main/res/values/strings.xml | 4 + core/src/main/res/values/styles.xml | 18 + gradle.properties | 23 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++ gradlew.bat | 84 +++ settings.gradle | 2 + 254 files changed, 12178 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/release/output.json create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_launcher-web.png create mode 100644 app/src/main/java/aculix/channelify/app/Channelify.kt create mode 100644 app/src/main/java/aculix/channelify/app/activity/MainActivity.kt create mode 100644 app/src/main/java/aculix/channelify/app/activity/SplashActivity.kt create mode 100644 app/src/main/java/aculix/channelify/app/activity/VideoPlayerActivity.kt create mode 100644 app/src/main/java/aculix/channelify/app/api/ChannelInfoService.kt create mode 100644 app/src/main/java/aculix/channelify/app/api/ChannelsService.kt create mode 100644 app/src/main/java/aculix/channelify/app/api/CommentRepliesService.kt create mode 100644 app/src/main/java/aculix/channelify/app/api/CommentService.kt create mode 100644 app/src/main/java/aculix/channelify/app/api/PlaylistItemsService.kt create mode 100644 app/src/main/java/aculix/channelify/app/api/PlaylistsService.kt create mode 100644 app/src/main/java/aculix/channelify/app/api/SearchVideoService.kt create mode 100644 app/src/main/java/aculix/channelify/app/api/VideosService.kt create mode 100644 app/src/main/java/aculix/channelify/app/db/ChannelifyDatabase.kt create mode 100644 app/src/main/java/aculix/channelify/app/db/FavoriteVideoDao.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/aboutModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/appModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/commentRepliesModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/commentsModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/favoritesModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/homeModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/playlistVideosModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/playlistsModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/searchModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/videoDetailsModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/di/videoPlayerModule.kt create mode 100644 app/src/main/java/aculix/channelify/app/fastadapteritems/CommentItem.kt create mode 100644 app/src/main/java/aculix/channelify/app/fastadapteritems/CommentReplyItem.kt create mode 100644 app/src/main/java/aculix/channelify/app/fastadapteritems/FavoriteItem.kt create mode 100644 app/src/main/java/aculix/channelify/app/fastadapteritems/HomeItem.kt create mode 100644 app/src/main/java/aculix/channelify/app/fastadapteritems/PlaylistItem.kt create mode 100644 app/src/main/java/aculix/channelify/app/fastadapteritems/PlaylistVideoItem.kt create mode 100644 app/src/main/java/aculix/channelify/app/fastadapteritems/ProgressIndicatorItem.kt create mode 100644 app/src/main/java/aculix/channelify/app/fastadapteritems/SearchItem.kt create mode 100644 app/src/main/java/aculix/channelify/app/fragment/AboutFragment.kt create mode 100644 app/src/main/java/aculix/channelify/app/fragment/AppInfoFragment.kt create mode 100644 app/src/main/java/aculix/channelify/app/fragment/CommentRepliesFragment.kt create mode 100644 app/src/main/java/aculix/channelify/app/fragment/CommentsFragment.kt create mode 100644 app/src/main/java/aculix/channelify/app/fragment/FavoritesFragment.kt create mode 100644 app/src/main/java/aculix/channelify/app/fragment/HomeFragment.kt create mode 100644 app/src/main/java/aculix/channelify/app/fragment/PlaylistVideosFragment.kt create mode 100644 app/src/main/java/aculix/channelify/app/fragment/PlaylistsFragment.kt create mode 100644 app/src/main/java/aculix/channelify/app/fragment/SearchFragment.kt create mode 100644 app/src/main/java/aculix/channelify/app/fragment/VideoDetailsFragment.kt create mode 100644 app/src/main/java/aculix/channelify/app/model/ChannelInfo.kt create mode 100644 app/src/main/java/aculix/channelify/app/model/ChannelUploadsPlaylistInfo.kt create mode 100644 app/src/main/java/aculix/channelify/app/model/Comment.kt create mode 100644 app/src/main/java/aculix/channelify/app/model/CommentReply.kt create mode 100644 app/src/main/java/aculix/channelify/app/model/FavoriteVideo.kt create mode 100644 app/src/main/java/aculix/channelify/app/model/Playlist.kt create mode 100644 app/src/main/java/aculix/channelify/app/model/PlaylistItemInfo.kt create mode 100644 app/src/main/java/aculix/channelify/app/model/SearchedVideo.kt create mode 100644 app/src/main/java/aculix/channelify/app/model/Video.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/NetworkState.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasource/CommentRepliesDataSource.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasource/CommentsDataSource.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasource/HomeDataSource.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasource/PlaylistVideosDataSource.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasource/PlaylistsDataSource.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasource/SearchDataSource.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasourcefactory/CommentRepliesDataSourceFactory.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasourcefactory/CommentsDataSourceFactory.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasourcefactory/HomeDataSourceFactory.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasourcefactory/PlaylistVideosDataSourceFactory.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasourcefactory/PlaylistsDataSourceFactory.kt create mode 100644 app/src/main/java/aculix/channelify/app/paging/datasourcefactory/SearchDataSourceFactory.kt create mode 100644 app/src/main/java/aculix/channelify/app/repository/AboutRepository.kt create mode 100644 app/src/main/java/aculix/channelify/app/repository/CommentRepliesRepository.kt create mode 100644 app/src/main/java/aculix/channelify/app/repository/CommentsRepository.kt create mode 100644 app/src/main/java/aculix/channelify/app/repository/FavoritesRepository.kt create mode 100644 app/src/main/java/aculix/channelify/app/repository/HomeRepository.kt create mode 100644 app/src/main/java/aculix/channelify/app/repository/PlaylistVideosRepository.kt create mode 100644 app/src/main/java/aculix/channelify/app/repository/PlaylistsRepository.kt create mode 100644 app/src/main/java/aculix/channelify/app/repository/SearchRepository.kt create mode 100644 app/src/main/java/aculix/channelify/app/repository/VideoDetailsRepository.kt create mode 100644 app/src/main/java/aculix/channelify/app/repository/VideoPlayerRepository.kt create mode 100644 app/src/main/java/aculix/channelify/app/sharedpref/AppPref.kt create mode 100644 app/src/main/java/aculix/channelify/app/utils/ActivityExtensions.kt create mode 100644 app/src/main/java/aculix/channelify/app/utils/Constants.kt create mode 100644 app/src/main/java/aculix/channelify/app/utils/DateTimeUtils.kt create mode 100644 app/src/main/java/aculix/channelify/app/utils/DividerItemDecorator.kt create mode 100644 app/src/main/java/aculix/channelify/app/utils/FragmentActivityExtensions.kt create mode 100644 app/src/main/java/aculix/channelify/app/utils/FullScreenHelper.kt create mode 100644 app/src/main/java/aculix/channelify/app/viewmodel/AboutViewModel.kt create mode 100644 app/src/main/java/aculix/channelify/app/viewmodel/CommentRepliesViewModel.kt create mode 100644 app/src/main/java/aculix/channelify/app/viewmodel/CommentsViewModel.kt create mode 100644 app/src/main/java/aculix/channelify/app/viewmodel/FavoritesViewModel.kt create mode 100644 app/src/main/java/aculix/channelify/app/viewmodel/HomeViewModel.kt create mode 100644 app/src/main/java/aculix/channelify/app/viewmodel/PlaylistVideosViewModel.kt create mode 100644 app/src/main/java/aculix/channelify/app/viewmodel/PlaylistsViewModel.kt create mode 100644 app/src/main/java/aculix/channelify/app/viewmodel/SearchViewModel.kt create mode 100644 app/src/main/java/aculix/channelify/app/viewmodel/VideoDetailsViewModel.kt create mode 100644 app/src/main/java/aculix/channelify/app/viewmodel/VideoPlayerViewModel.kt create mode 100644 app/src/main/res/anim/slide_in_bottom.xml create mode 100644 app/src/main/res/anim/slide_out_bottom.xml create mode 100644 app/src/main/res/drawable-hdpi/ic_stat_notification.png create mode 100644 app/src/main/res/drawable-mdpi/ic_stat_notification.png create mode 100644 app/src/main/res/drawable-night/ic_empty_comments.xml create mode 100644 app/src/main/res/drawable-night/ic_empty_favorites.xml create mode 100644 app/src/main/res/drawable-night/ic_empty_playlists.xml create mode 100644 app/src/main/res/drawable-night/ic_error.xml create mode 100644 app/src/main/res/drawable-night/ic_error_video_details.xml create mode 100644 app/src/main/res/drawable-xhdpi/ic_stat_notification.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_stat_notification.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_stat_notification.png create mode 100644 app/src/main/res/drawable/bg_rounded_rect.xml create mode 100644 app/src/main/res/drawable/bg_splash.xml create mode 100644 app/src/main/res/drawable/ic_about.xml create mode 100644 app/src/main/res/drawable/ic_arrow_back.xml create mode 100644 app/src/main/res/drawable/ic_close.xml create mode 100644 app/src/main/res/drawable/ic_comment.xml create mode 100644 app/src/main/res/drawable/ic_dislike.xml create mode 100644 app/src/main/res/drawable/ic_email.xml create mode 100644 app/src/main/res/drawable/ic_empty_comments.xml create mode 100644 app/src/main/res/drawable/ic_empty_favorites.xml create mode 100644 app/src/main/res/drawable/ic_empty_playlists.xml create mode 100644 app/src/main/res/drawable/ic_error.xml create mode 100644 app/src/main/res/drawable/ic_error_about.xml create mode 100644 app/src/main/res/drawable/ic_error_video_details.xml create mode 100644 app/src/main/res/drawable/ic_favorite_border.xml create mode 100644 app/src/main/res/drawable/ic_favorite_filled.xml create mode 100644 app/src/main/res/drawable/ic_favorite_filled_border.xml create mode 100644 app/src/main/res/drawable/ic_forward.xml create mode 100644 app/src/main/res/drawable/ic_google_play.xml create mode 100644 app/src/main/res/drawable/ic_home.xml create mode 100644 app/src/main/res/drawable/ic_info.xml create mode 100644 app/src/main/res/drawable/ic_instagram.xml create mode 100644 app/src/main/res/drawable/ic_like.xml create mode 100644 app/src/main/res/drawable/ic_playlists.xml create mode 100644 app/src/main/res/drawable/ic_privacy_policy.xml create mode 100644 app/src/main/res/drawable/ic_retry.xml create mode 100644 app/src/main/res/drawable/ic_rewind.xml create mode 100644 app/src/main/res/drawable/ic_search.xml create mode 100644 app/src/main/res/drawable/ic_share.xml create mode 100644 app/src/main/res/drawable/ic_sort.xml create mode 100644 app/src/main/res/drawable/ic_star.xml create mode 100644 app/src/main/res/drawable/ic_store.xml create mode 100644 app/src/main/res/drawable/ic_theme_dark.xml create mode 100644 app/src/main/res/drawable/ic_theme_light.xml create mode 100644 app/src/main/res/drawable/ic_tos.xml create mode 100644 app/src/main/res/drawable/ic_unfold_more.xml create mode 100644 app/src/main/res/drawable/ic_view_count.xml create mode 100644 app/src/main/res/drawable/ic_website.xml create mode 100644 app/src/main/res/drawable/logo_splash.png create mode 100644 app/src/main/res/drawable/view_divider_item_decorator.xml create mode 100644 app/src/main/res/font/roboto.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_video_player.xml create mode 100644 app/src/main/res/layout/fragment_about.xml create mode 100644 app/src/main/res/layout/fragment_app_info.xml create mode 100644 app/src/main/res/layout/fragment_comment_replies.xml create mode 100644 app/src/main/res/layout/fragment_comments.xml create mode 100644 app/src/main/res/layout/fragment_favorites.xml create mode 100644 app/src/main/res/layout/fragment_home.xml create mode 100644 app/src/main/res/layout/fragment_playlist_details.xml create mode 100644 app/src/main/res/layout/fragment_playlist_videos.xml create mode 100644 app/src/main/res/layout/fragment_playlists.xml create mode 100644 app/src/main/res/layout/fragment_search.xml create mode 100644 app/src/main/res/layout/fragment_video_details.xml create mode 100644 app/src/main/res/layout/item_comment.xml create mode 100644 app/src/main/res/layout/item_comment_reply.xml create mode 100644 app/src/main/res/layout/item_favorite.xml create mode 100644 app/src/main/res/layout/item_home.xml create mode 100644 app/src/main/res/layout/item_playlist.xml create mode 100644 app/src/main/res/layout/item_playlist_video.xml create mode 100644 app/src/main/res/layout/item_progress_indicator.xml create mode 100644 app/src/main/res/layout/item_search.xml create mode 100644 app/src/main/res/layout/widget_toolbar.xml create mode 100644 app/src/main/res/menu/app_info_menu.xml create mode 100644 app/src/main/res/menu/bab_menu_video_details.xml create mode 100644 app/src/main/res/menu/bottom_navigation_menu.xml create mode 100644 app/src/main/res/menu/comments_sort_menu_video_details.xml create mode 100644 app/src/main/res/menu/main_menu.xml create mode 100644 app/src/main/res/menu/toolbar_menu_about.xml create mode 100644 app/src/main/res/menu/toolbar_menu_playlist_videos.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/navigation/nav_graph.xml create mode 100644 app/src/main/res/navigation/video_player_graph.xml create mode 100644 app/src/main/res/values-night/colors.xml create mode 100644 app/src/main/res/values-night/config.xml create mode 100644 app/src/main/res/values-night/styles.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/config.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/font_certs.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/ids.xml create mode 100644 app/src/main/res/values/preloaded_fonts.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 build.gradle create mode 100644 core/.gitignore create mode 100644 core/build.gradle create mode 100644 core/proguard-rules.pro create mode 100644 core/src/main/AndroidManifest.xml create mode 100644 core/src/main/java/aculix/core/base/BaseFragment.kt create mode 100644 core/src/main/java/aculix/core/base/RoundedBottomSheetDialogFragment.kt create mode 100644 core/src/main/java/aculix/core/extensions/ButtonExtensions.kt create mode 100644 core/src/main/java/aculix/core/extensions/ChipExtensions.kt create mode 100644 core/src/main/java/aculix/core/extensions/ContextExtensions.kt create mode 100644 core/src/main/java/aculix/core/extensions/EditTextExtensions.kt create mode 100644 core/src/main/java/aculix/core/extensions/FragmentExtensions.kt create mode 100644 core/src/main/java/aculix/core/extensions/ImageViewExtensions.kt create mode 100644 core/src/main/java/aculix/core/extensions/IntExtensions.kt create mode 100644 core/src/main/java/aculix/core/extensions/LongExtensions.kt create mode 100644 core/src/main/java/aculix/core/extensions/StringExtensions.kt create mode 100644 core/src/main/java/aculix/core/extensions/ViewExtensions.kt create mode 100644 core/src/main/java/aculix/core/extensions/ViewgroupExtensions.kt create mode 100644 core/src/main/java/aculix/core/helper/BaseViewModelFactory.kt create mode 100644 core/src/main/java/aculix/core/helper/ResultWrapper.kt create mode 100644 core/src/main/java/aculix/core/helper/SimpleDividerItemDecoration.kt create mode 100644 core/src/main/res/drawable-v24/ic_launcher_background.xml create mode 100644 core/src/main/res/drawable/bg_bottom_sheet.xml create mode 100644 core/src/main/res/drawable/bg_line_divider.xml create mode 100644 core/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 core/src/main/res/drawable/ic_place_holder.xml create mode 100644 core/src/main/res/drawable/ic_place_holder_wrapper.xml create mode 100644 core/src/main/res/values/colors.xml create mode 100644 core/src/main/res/values/dimens.xml create mode 100644 core/src/main/res/values/strings.xml create mode 100644 core/src/main/res/values/styles.xml create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..61a9130 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..dc73612 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..e34606c --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d5d35ec --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..c996390 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,141 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' +apply plugin: 'androidx.navigation.safeargs.kotlin' +apply plugin: 'com.google.gms.google-services' + +android { + compileSdkVersion COMPILE_SDK_VERSION + + defaultConfig { + applicationId "aculix.channelify.app" + minSdkVersion 21 + targetSdkVersion TARGET_SDK_VERSION + versionCode 7 + versionName "1.32" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + debug { + resValue "string", "youtube_api_key", YT_API_KEY + } + + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + + resValue "string", "youtube_api_key", YT_API_KEY + } + } + + kotlinOptions { + jvmTarget = "1.8" + } + + viewBinding { + enabled = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + packagingOptions { + exclude 'META-INF/core_release.kotlin_module' + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + // Kotlin + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + + // Kotlin Coroutines + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutinesVersion" + + // Koin + implementation "org.koin:koin-android:$koinVersion" + implementation "org.koin:koin-androidx-viewmodel:$koinVersion" + + // AndroidX + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.core:core-ktx:1.3.0-rc01' + implementation "androidx.constraintlayout:constraintlayout:$clVersion" + implementation "androidx.recyclerview:recyclerview:1.1.0" + + // Architecture Components + implementation "androidx.lifecycle:lifecycle-extensions:$archVersion" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$archVersion" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$archVersion" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$archVersion" + + // Room + implementation "androidx.room:room-runtime:$roomVersion" + implementation "androidx.room:room-ktx:$roomVersion" + kapt "androidx.room:room-compiler:$roomVersion" + + // Material theme + implementation "com.google.android.material:material:$materialThemeVersion" + + // Navigation + implementation "androidx.navigation:navigation-fragment-ktx:$navVersion" + implementation "androidx.navigation:navigation-ui-ktx:$navVersion" + + // Retrofit, OkHttp & Gson + implementation "com.squareup.retrofit2:retrofit:2.8.1" + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2' + + // Timber + implementation "com.jakewharton.timber:timber:$timberVersion" + + // Fast Adapter + implementation "com.mikepenz:fastadapter:$fastAdapterVersion" + implementation "com.mikepenz:fastadapter-extensions-diff:$fastAdapterVersion" + implementation "com.mikepenz:fastadapter-extensions-utils:$fastAdapterVersion" + implementation "com.mikepenz:fastadapter-extensions-ui:$fastAdapterVersion" + implementation "com.mikepenz:fastadapter-extensions-paged:$fastAdapterVersion" + implementation "com.mikepenz:itemanimators:1.1.0" + + // Coil + implementation "io.coil-kt:coil:0.10.0" + + // Paging + implementation "androidx.paging:paging-runtime:2.1.2" + + // TimeAgo + implementation 'com.github.marlonlom:timeago:4.0.1' + + // YouTube Player + implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:core:10.0.5' + + // Circle ImageView + implementation 'de.hdodenhof:circleimageview:3.1.0' + + // Material Dialog + implementation "com.afollestad.material-dialogs:core:$materialDialogVersion" + implementation "com.afollestad.material-dialogs:bottomsheets:$materialDialogVersion" + + // Kotpref + implementation 'com.chibatching.kotpref:kotpref:2.10.0' + + // Firebase + implementation 'com.google.firebase:firebase-analytics:17.4.0' + implementation 'com.google.firebase:firebase-messaging:20.1.6' + implementation 'com.google.android.gms:play-services-ads:19.1.0' + + // Custom Tabs + implementation "saschpe.android:customtabs:$saschpeCustomTabs" + implementation "androidx.browser:browser:$browserVersion" + + // Modules + implementation project(':core') +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..25f7b25 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,56 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +##-------------------------- Model classes -------------------------- +-keep class aculix.channelify.app.model.** { *; } + + +#-------------------------- Coroutines --------------------------# + +# ServiceLoader support +-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} +-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} +-keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {} +-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {} + +# Most of volatile fields are updated with AFU and should not be mangled +-keepclassmembernames class kotlinx.** { + volatile ; +} + + +#-------------------------- OkHttp --------------------------# + +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt dependency is available. +-dontwarn okhttp3.internal.platform.ConscryptPlatform + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* diff --git a/app/release/output.json b/app/release/output.json new file mode 100644 index 0000000..0194400 --- /dev/null +++ b/app/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":7,"versionName":"1.32","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release","dirName":""},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e71c9e9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000000000000000000000000000000000000..9e188238851c398376033baeeaa02a5e057a13c7 GIT binary patch literal 16616 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 rkz|11`2Uk9{A6AKZwU>~g(Z)2; literal 0 HcmV?d00001 diff --git a/app/src/main/java/aculix/channelify/app/Channelify.kt b/app/src/main/java/aculix/channelify/app/Channelify.kt new file mode 100644 index 0000000..77a3224 --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/Channelify.kt @@ -0,0 +1,89 @@ +package aculix.channelify.app + +import aculix.channelify.app.di.* +import aculix.channelify.app.sharedpref.AppPref +import android.app.Application +import androidx.appcompat.app.AppCompatDelegate +import com.chibatching.kotpref.Kotpref +import com.google.android.gms.ads.MobileAds +import com.google.android.gms.ads.RequestConfiguration +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin +import saschpe.android.customtabs.CustomTabsActivityLifecycleCallbacks +import timber.log.Timber +import java.util.* + +class Channelify : Application() { + + companion object { + var isAdEnabled = true + } + + override fun onCreate() { + super.onCreate() + isAdEnabled = resources.getBoolean(R.bool.enable_ads) + + initializeKotpref() + setThemeMode() + initializeTimber() + initializeKoin() + initializeCustomTabs() + + if (resources.getBoolean(R.bool.enable_ads)) initializeAdmob() + } + + private fun setThemeMode() { + if (AppPref.isLightThemeEnabled) { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + } else { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + } + + } + + private fun initializeTimber() { + if (BuildConfig.DEBUG) { + Timber.plant(Timber.DebugTree()) + } + } + + private fun initializeKotpref() { + Kotpref.init(this) + } + + private fun initializeKoin() { + startKoin { + androidLogger() + androidContext(this@Channelify) + modules( + listOf( + appModule, + homeModule, + videoPlayerModule, + commentsModule, + videoDetailsModule, + commentRepliesModule, + playlistsModule, + playlistVideosModule, + favoritesModule, + searchModule, + aboutModule + ) + ) + } + } + + private fun initializeAdmob() { + MobileAds.initialize(this) { + val testDeviceIds = listOf("7BD04413716C0B3DD5C73F814E02D21A") + val configuration = + RequestConfiguration.Builder().setTestDeviceIds(testDeviceIds).build() + MobileAds.setRequestConfiguration(configuration) + } + } + + private fun initializeCustomTabs() { + registerActivityLifecycleCallbacks(CustomTabsActivityLifecycleCallbacks()) + } +} \ No newline at end of file diff --git a/app/src/main/java/aculix/channelify/app/activity/MainActivity.kt b/app/src/main/java/aculix/channelify/app/activity/MainActivity.kt new file mode 100644 index 0000000..71e1b3e --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/activity/MainActivity.kt @@ -0,0 +1,61 @@ +package aculix.channelify.app.activity + +import aculix.channelify.app.Channelify +import aculix.channelify.app.R +import aculix.channelify.app.utils.getAdaptiveBannerAdSize +import aculix.core.extensions.makeGone +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.util.DisplayMetrics +import androidx.navigation.findNavController +import androidx.navigation.ui.setupWithNavController +import com.google.android.gms.ads.* +import kotlinx.android.synthetic.main.activity_main.* +import java.util.* + +class MainActivity : AppCompatActivity() { + + private lateinit var adView: AdView + private var initialLayoutComplete = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + + val navController = findNavController(R.id.navHostFragment) + bottomNavView.setupWithNavController(navController) + + if (Channelify.isAdEnabled) setupAd() else adViewContainerMain.makeGone() + + } + + override fun onPause() { + if (Channelify.isAdEnabled) adView.pause() + super.onPause() + } + + override fun onResume() { + if (Channelify.isAdEnabled) adView.resume() + super.onResume() + } + + override fun onDestroy() { + if (Channelify.isAdEnabled) adView.destroy() + super.onDestroy() + } + + private fun setupAd() { + adView = AdView(this) + adViewContainerMain.addView(adView) + adViewContainerMain.viewTreeObserver.addOnGlobalLayoutListener { + if (!initialLayoutComplete) { + initialLayoutComplete = true + + adView.adUnitId = getString(R.string.main_banner_ad_id) + adView.adSize = getAdaptiveBannerAdSize(adViewContainerMain) + adView.loadAd(AdRequest.Builder().build()) + } + } + } +} diff --git a/app/src/main/java/aculix/channelify/app/activity/SplashActivity.kt b/app/src/main/java/aculix/channelify/app/activity/SplashActivity.kt new file mode 100644 index 0000000..bb3f9b1 --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/activity/SplashActivity.kt @@ -0,0 +1,16 @@ +package aculix.channelify.app.activity + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle + +class SplashActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + finish() + } +} diff --git a/app/src/main/java/aculix/channelify/app/activity/VideoPlayerActivity.kt b/app/src/main/java/aculix/channelify/app/activity/VideoPlayerActivity.kt new file mode 100644 index 0000000..ca2a78e --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/activity/VideoPlayerActivity.kt @@ -0,0 +1,132 @@ +package aculix.channelify.app.activity + +import aculix.channelify.app.R +import aculix.channelify.app.fragment.VideoDetailsFragment +import aculix.channelify.app.utils.FullScreenHelper +import aculix.channelify.app.viewmodel.VideoPlayerViewModel +import aculix.core.extensions.toast +import android.annotation.SuppressLint +import android.app.PictureInPictureParams +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.os.Build +import android.os.Bundle +import android.util.Rational +import android.view.ContextThemeWrapper +import android.view.View +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.AppCompatToggleButton +import androidx.core.content.ContextCompat +import androidx.core.os.bundleOf +import androidx.navigation.findNavController +import com.google.android.gms.ads.AdListener +import com.google.android.gms.ads.AdRequest +import com.google.android.gms.ads.InterstitialAd +import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer +import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener +import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.YouTubePlayerFullScreenListener +import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.utils.loadOrCueVideo +import kotlinx.android.synthetic.main.activity_video_player.* +import org.koin.androidx.viewmodel.ext.android.viewModel + + +class VideoPlayerActivity : AppCompatActivity(R.layout.activity_video_player) { + + companion object { + const val VIDEO_ID = "video_id" + + fun startActivity(context: Context?, videoId: String) { + val intent = Intent(context, VideoPlayerActivity::class.java).apply { + putExtra(VIDEO_ID, videoId) + } + context?.startActivity(intent) + } + } + + private val viewModel by viewModel() // Lazy inject ViewModel + + lateinit var fullScreenHelper: FullScreenHelper + lateinit var videoId: String + private var videoElapsedTimeInSeconds = 0f + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + fullScreenHelper = FullScreenHelper(this) + videoId = intent.getStringExtra(VIDEO_ID)!! + + // Passing the videoId as argument to the start destination + findNavController(R.id.navHostVideoPlayer).setGraph( + R.navigation.video_player_graph, + bundleOf(VideoDetailsFragment.VIDEO_ID to videoId) + ) + + initYouTubePlayer() + } + + override fun onBackPressed() { + if (ytVideoPlayerView.isFullScreen()) ytVideoPlayerView.exitFullScreen() else super.onBackPressed() + } + + private fun initYouTubePlayer() { + lifecycle.addObserver(ytVideoPlayerView) + + ytVideoPlayerView.addYouTubePlayerListener(object : AbstractYouTubePlayerListener() { + override fun onReady(youTubePlayer: YouTubePlayer) { + youTubePlayer.loadOrCueVideo(lifecycle, videoId, 0f) + addFullScreenListenerToPlayer() + setupCustomActions(youTubePlayer) + } + + override fun onCurrentSecond(youTubePlayer: YouTubePlayer, second: Float) { + videoElapsedTimeInSeconds = second + } + }) + + + } + + /** + * Adds the forward and rewind action button to the Player + */ + private fun setupCustomActions(youTubePlayer: YouTubePlayer) { + ytVideoPlayerView.getPlayerUiController() + .setCustomAction1( + ContextCompat.getDrawable(this, R.drawable.ic_rewind)!!, + View.OnClickListener { + videoElapsedTimeInSeconds -= 10 + youTubePlayer.seekTo(videoElapsedTimeInSeconds) + }) + .setCustomAction2( + ContextCompat.getDrawable(this, R.drawable.ic_forward)!!, + View.OnClickListener { + videoElapsedTimeInSeconds += 10 + youTubePlayer.seekTo(videoElapsedTimeInSeconds) + }) + } + + /** + * Changes the orientation of the activity based on the + * change of the player state (Full screen or not) + */ + @SuppressLint("SourceLockedOrientationActivity") + private fun addFullScreenListenerToPlayer() { + ytVideoPlayerView.addFullScreenListener(object : YouTubePlayerFullScreenListener { + override fun onYouTubePlayerEnterFullScreen() { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + fullScreenHelper.enterFullScreen() + } + + override fun onYouTubePlayerExitFullScreen() { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + fullScreenHelper.exitFullScreen() + } + }) + } +} diff --git a/app/src/main/java/aculix/channelify/app/api/ChannelInfoService.kt b/app/src/main/java/aculix/channelify/app/api/ChannelInfoService.kt new file mode 100644 index 0000000..0db8337 --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/api/ChannelInfoService.kt @@ -0,0 +1,17 @@ +package aculix.channelify.app.api + +import aculix.channelify.app.model.ChannelInfo +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface ChannelInfoService { + + @GET("channels") + suspend fun getChannelInfo( + @Query("id") channelId: String, + @Query("part") part: String = "snippet, statistics, brandingSettings", + @Query("fields") fields: String = "items(snippet(title, description, publishedAt, thumbnails), statistics(viewCount, subscriberCount, videoCount), brandingSettings(image(bannerMobileHdImageUrl, bannerMobileMediumHdImageUrl)))", + @Query("maxResults") maxResults: Int = 1 + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/aculix/channelify/app/api/ChannelsService.kt b/app/src/main/java/aculix/channelify/app/api/ChannelsService.kt new file mode 100644 index 0000000..68a7ba7 --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/api/ChannelsService.kt @@ -0,0 +1,16 @@ +package aculix.channelify.app.api + +import aculix.channelify.app.model.ChannelUploadsPlaylistInfo +import aculix.channelify.app.model.SearchedVideo +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface ChannelsService { + + @GET("channels") + suspend fun getChannelUploadsPlaylistInfo( + @Query("id") channelId: String, + @Query("part") part: String = "contentDetails" + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/aculix/channelify/app/api/CommentRepliesService.kt b/app/src/main/java/aculix/channelify/app/api/CommentRepliesService.kt new file mode 100644 index 0000000..e7cfae6 --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/api/CommentRepliesService.kt @@ -0,0 +1,22 @@ +package aculix.channelify.app.api + +import aculix.channelify.app.model.CommentReply +import aculix.channelify.app.utils.Constants +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface CommentRepliesService { + + /** + * Gets the list of replies for a particular comment + */ + @GET("comments") + suspend fun getCommentReplies( + @Query("parentId") commentId: String, + @Query("pageToken") pageToken: String?, + @Query("part") part: String = "snippet", + @Query("fields") fields: String = "nextPageToken, items(snippet(authorDisplayName, authorProfileImageUrl, textOriginal, likeCount, publishedAt, updatedAt))", + @Query("maxResults") maxResults: Int = Constants.YT_API_MAX_RESULTS + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/aculix/channelify/app/api/CommentService.kt b/app/src/main/java/aculix/channelify/app/api/CommentService.kt new file mode 100644 index 0000000..c774487 --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/api/CommentService.kt @@ -0,0 +1,25 @@ +package aculix.channelify.app.api + +import aculix.channelify.app.model.Comment +import aculix.channelify.app.model.CommentReply +import aculix.channelify.app.model.PlaylistItemInfo +import aculix.channelify.app.utils.Constants +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface CommentService { + + /** + * Gets the list of comments of a particular video + */ + @GET("commentThreads") + suspend fun getVideoComments( + @Query("videoId") videoId: String, + @Query("pageToken") pageToken: String?, + @Query("order") sortOrder: String, + @Query("part") part: String = "snippet", + @Query("fields") fields: String = "nextPageToken, items(snippet(topLevelComment(id, snippet(authorDisplayName, authorProfileImageUrl, textOriginal, likeCount, publishedAt, updatedAt)), totalReplyCount))", + @Query("maxResults") maxResults: Int = Constants.YT_API_MAX_RESULTS + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/aculix/channelify/app/api/PlaylistItemsService.kt b/app/src/main/java/aculix/channelify/app/api/PlaylistItemsService.kt new file mode 100644 index 0000000..d6aa130 --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/api/PlaylistItemsService.kt @@ -0,0 +1,20 @@ +package aculix.channelify.app.api + +import aculix.channelify.app.model.ChannelUploadsPlaylistInfo +import aculix.channelify.app.model.PlaylistItemInfo +import aculix.channelify.app.utils.Constants +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface PlaylistItemsService { + + @GET("playlistItems") + suspend fun getPlaylistVideos( + @Query("playlistId") playlistId: String, + @Query("pageToken") pageToken: String?, + @Query("part") part: String = "snippet,contentDetails", + @Query("fields") fields: String = "nextPageToken, prevPageToken, items(snippet(title, thumbnails), contentDetails(videoId, videoPublishedAt))", + @Query("maxResults") maxResults: Int = Constants.YT_API_MAX_RESULTS + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/aculix/channelify/app/api/PlaylistsService.kt b/app/src/main/java/aculix/channelify/app/api/PlaylistsService.kt new file mode 100644 index 0000000..d9b4da1 --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/api/PlaylistsService.kt @@ -0,0 +1,19 @@ +package aculix.channelify.app.api + +import aculix.channelify.app.model.Playlist +import aculix.channelify.app.utils.Constants +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface PlaylistsService { + + @GET("playlists") + suspend fun getPlaylists( + @Query("channelId") channelId: String, + @Query("pageToken") pageToken: String?, + @Query("part") part: String = "snippet,contentDetails", + @Query("fields") fields: String = "nextPageToken, items(id, snippet(publishedAt, title, description, thumbnails), contentDetails)", + @Query("maxResults") maxResults: Int = Constants.YT_API_MAX_RESULTS + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/aculix/channelify/app/api/SearchVideoService.kt b/app/src/main/java/aculix/channelify/app/api/SearchVideoService.kt new file mode 100644 index 0000000..8db928a --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/api/SearchVideoService.kt @@ -0,0 +1,20 @@ +package aculix.channelify.app.api + +import aculix.channelify.app.model.SearchedVideo +import aculix.channelify.app.utils.Constants +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query +import retrofit2.http.QueryMap + +interface SearchVideoService { + + @GET("search") + suspend fun searchVideos( + @Query("q") searchQuery: String, + @Query("channelId") channelId: String, + @Query("pageToken") pageToken: String?, + @QueryMap defaultQueryMap: HashMap, + @Query("maxResults") maxResults: Int = Constants.YT_API_MAX_RESULTS + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/aculix/channelify/app/api/VideosService.kt b/app/src/main/java/aculix/channelify/app/api/VideosService.kt new file mode 100644 index 0000000..e146a24 --- /dev/null +++ b/app/src/main/java/aculix/channelify/app/api/VideosService.kt @@ -0,0 +1,17 @@ +package aculix.channelify.app.api + +import aculix.channelify.app.model.Video +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface VideosService { + + @GET("videos") + suspend fun getVideoInfo( + @Query("id") videoId: String, + @Query("part") part: String = "snippet, statistics", + @Query("fields") fields: String = "items(snippet(publishedAt, title, description, thumbnails), statistics)", + @Query("maxResults") maxResults: Int = 1 + ): Response