From d77dd2c3497697d2d24b9e57fc43249ed05f5af8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 22 Jan 2023 16:48:14 +0100 Subject: [PATCH] Split files --- app/build.gradle | 52 +- app/src/fdroid/res/layout/min_controller.xml | 53 + app/src/main/AndroidManifest.xml | 117 + .../peertube/activities/AccountActivity.java | 299 +++ .../activities/AllLocalPlaylistsActivity.java | 123 + .../activities/AllPlaylistsActivity.java | 431 +++ .../activities/InstancePickerActivity.java | 275 ++ .../activities/LocalPlaylistsActivity.java | 82 + .../peertube/activities/LoginActivity.java | 418 +++ .../peertube/activities/MainActivity.java | 815 ++++++ .../activities/ManageInstancesActivity.java | 134 + .../MastodonWebviewConnectActivity.java | 194 ++ .../activities/MyAccountActivity.java | 362 +++ .../peertube/activities/PeertubeActivity.java | 2319 +++++++++++++++++ .../PeertubeEditUploadActivity.java | 645 +++++ .../activities/PeertubeRegisterActivity.java | 283 ++ .../activities/PeertubeUploadActivity.java | 418 +++ .../activities/PlaylistsActivity.java | 101 + .../peertube/activities/SearchActivity.java | 169 ++ .../activities/SepiaSearchActivity.java | 388 +++ .../peertube/activities/SettingsActivity.java | 45 + .../activities/ShowAccountActivity.java | 313 +++ .../activities/ShowChannelActivity.java | 469 ++++ .../activities/VideosTimelineActivity.java | 178 ++ .../peertube/activities/WebviewActivity.java | 183 ++ .../activities/WebviewConnectActivity.java | 263 ++ .../android/peertube/client/APIResponse.java | 258 ++ .../peertube/client/MenuItemVideo.java | 53 + .../peertube/client/PeertubeService.java | 529 ++++ .../peertube/client/RetrofitPeertubeAPI.java | 2023 ++++++++++++++ .../client/RetrofitSepiaSearchAPI.java | 86 + .../peertube/client/SepiaSearchService.java | 53 + .../peertube/client/data/AccountData.java | 275 ++ .../peertube/client/data/BlockData.java | 81 + .../peertube/client/data/CaptionData.java | 53 + .../peertube/client/data/ChannelData.java | 294 +++ .../peertube/client/data/CommentData.java | 265 ++ .../peertube/client/data/InstanceData.java | 427 +++ .../client/data/NotificationData.java | 167 ++ .../peertube/client/data/PlaylistData.java | 222 ++ .../peertube/client/data/PluginData.java | 125 + .../peertube/client/data/VideoData.java | 885 +++++++ .../client/data/VideoPlaylistData.java | 247 ++ .../client/entities/AcadInstances.java | 71 + .../client/entities/AccountCreation.java | 74 + .../peertube/client/entities/Actor.java | 71 + .../peertube/client/entities/ActorFollow.java | 62 + .../peertube/client/entities/Avatar.java | 92 + .../client/entities/CaptionsParams.java | 42 + .../client/entities/ChannelParams.java | 72 + .../peertube/client/entities/Error.java | 61 + .../peertube/client/entities/File.java | 146 ++ .../client/entities/InstanceParams.java | 78 + .../peertube/client/entities/Item.java | 75 + .../peertube/client/entities/ItemStr.java | 76 + .../client/entities/MenuItemView.java | 92 + .../client/entities/NotificationSettings.java | 163 ++ .../peertube/client/entities/Oauth.java | 44 + .../peertube/client/entities/OauthParams.java | 143 + .../client/entities/OverviewVideo.java | 127 + .../client/entities/PeertubeInformation.java | 77 + .../client/entities/PlaylistExist.java | 63 + .../client/entities/PlaylistParams.java | 63 + .../peertube/client/entities/Rating.java | 42 + .../peertube/client/entities/Report.java | 147 ++ .../peertube/client/entities/SepiaSearch.java | 214 ++ .../client/entities/StreamingPlaylists.java | 161 ++ .../peertube/client/entities/Token.java | 62 + .../peertube/client/entities/UserMe.java | 305 +++ .../client/entities/UserSettings.java | 144 + .../peertube/client/entities/VideoAbuse.java | 79 + .../client/entities/VideoBlacklist.java | 44 + .../peertube/client/entities/VideoParams.java | 176 ++ .../peertube/client/entities/ViewsPerDay.java | 75 + .../client/entities/WellKnownNodeinfo.java | 105 + .../client/mastodon/MastodonAccount.java | 255 ++ .../client/mastodon/MastodonService.java | 104 + .../peertube/client/mastodon/Results.java | 44 + .../client/mastodon/RetrofitMastodonAPI.java | 356 +++ .../peertube/client/mastodon/Status.java | 149 ++ .../peertube/drawer/AboutInstanceAdapter.java | 187 ++ .../drawer/AccountsHorizontalListAdapter.java | 110 + .../peertube/drawer/AccountsListAdapter.java | 162 ++ .../peertube/drawer/ChannelListAdapter.java | 184 ++ .../peertube/drawer/CommentListAdapter.java | 386 +++ .../peertube/drawer/InstanceAdapter.java | 177 ++ .../android/peertube/drawer/MenuAdapter.java | 87 + .../peertube/drawer/MenuItemAdapter.java | 89 + .../peertube/drawer/OwnAccountsAdapter.java | 94 + .../peertube/drawer/PeertubeAdapter.java | 490 ++++ .../PeertubeNotificationsListAdapter.java | 257 ++ .../peertube/drawer/PlaylistAdapter.java | 288 ++ .../fragment/DisplayAccountsFragment.java | 219 ++ .../fragment/DisplayChannelsFragment.java | 357 +++ .../DisplayNotificationsFragment.java | 232 ++ .../fragment/DisplayOverviewFragment.java | 442 ++++ .../fragment/DisplayPlaylistsFragment.java | 367 +++ .../fragment/DisplaySepiaSearchFragment.java | 319 +++ .../fragment/DisplayVideosFragment.java | 559 ++++ .../peertube/fragment/SettingsFragment.java | 480 ++++ .../helper/CacheDataSourceFactory.java | 69 + .../helper/CommentDecorationHelper.java | 46 + .../android/peertube/helper/EmojiHelper.java | 81 + .../android/peertube/helper/Helper.java | 828 ++++++ .../peertube/helper/HelperAcadInstance.java | 107 + .../peertube/helper/HelperInstance.java | 41 + .../peertube/helper/NetworkStateReceiver.java | 84 + .../peertube/helper/NotificationHelper.java | 96 + .../peertube/helper/PlaylistExportHelper.java | 117 + .../helper/RoundedBackgroundSpan.java | 41 + .../peertube/helper/SwitchAccountHelper.java | 84 + .../android/peertube/helper/ThemeHelper.java | 46 + .../services/GlobalUploadObserver.java | 67 + .../services/RetrieveInfoService.java | 150 ++ .../android/peertube/sqlite/AccountDAO.java | 391 +++ .../peertube/sqlite/ManagePlaylistsDAO.java | 377 +++ .../peertube/sqlite/MastodonAccountDAO.java | 219 ++ .../android/peertube/sqlite/Sqlite.java | 214 ++ .../peertube/sqlite/StoredInstanceDAO.java | 217 ++ .../peertube/viewmodel/AccountsVM.java | 90 + .../peertube/viewmodel/CaptionsVM.java | 63 + .../peertube/viewmodel/ChannelsVM.java | 91 + .../android/peertube/viewmodel/CommentVM.java | 96 + .../peertube/viewmodel/InfoInstanceVM.java | 64 + .../peertube/viewmodel/InstancesVM.java | 59 + .../android/peertube/viewmodel/MyVideoVM.java | 62 + .../peertube/viewmodel/NotificationsVM.java | 72 + .../peertube/viewmodel/PlaylistsVM.java | 134 + .../peertube/viewmodel/PostActionsVM.java | 103 + .../peertube/viewmodel/RelationshipVM.java | 62 + .../android/peertube/viewmodel/SearchVM.java | 105 + .../peertube/viewmodel/SepiaSearchVM.java | 57 + .../peertube/viewmodel/TimelineVM.java | 283 ++ .../mastodon/MastodonPostActionsVM.java | 115 + .../peertube/webview/CustomWebview.java | 53 + .../webview/MastalabWebChromeClient.java | 226 ++ .../webview/MastalabWebViewClient.java | 76 + .../android/peertube/webview/ProxyHelper.java | 166 ++ .../peertube/worker/NotificationsWorker.java | 327 +++ .../android/peertube/worker/WorkHelper.java | 53 + .../{ => drawables/mastodon}/drawable/aa.png | Bin .../{ => drawables/mastodon}/drawable/bb.png | Bin .../mastodon}/drawable/bg_compose_panels.xml | 0 .../mastodon}/drawable/blue_border.xml | 0 .../drawable/browser_calls_blocked.xml | 0 .../mastodon}/drawable/default_banner.xml | 0 .../mastodon}/drawable/empty_custom_emoji.xml | 0 .../mastodon}/drawable/fedilab_logo_atom.xml | 0 .../drawable/fedilab_logo_bubbles.xml | 0 .../mastodon}/drawable/fedilab_logo_crash.xml | 0 .../drawable/fedilab_logo_fediverse.xml | 0 .../mastodon}/drawable/fedilab_logo_hero.xml | 0 .../drawable/fedilab_logo_mastalab.xml | 0 .../mastodon}/drawable/green_border.xml | 0 .../mastodon}/drawable/ic_admin.xml | 0 .../drawable/ic_baseline_access_time_24.xml | 0 .../ic_baseline_access_time_filled_24.xml | 0 .../ic_baseline_account_circle_24.xml | 0 .../mastodon}/drawable/ic_baseline_add_24.xml | 0 .../drawable/ic_baseline_add_reaction_24.xml | 0 .../drawable/ic_baseline_android_24.xml | 0 .../ic_baseline_arrow_drop_down_24.xml | 0 .../drawable/ic_baseline_arrow_drop_up_24.xml | 0 .../drawable/ic_baseline_audio_file_24.xml | 0 .../drawable/ic_baseline_audiotrack_24.xml | 0 .../drawable/ic_baseline_block_24.xml | 0 .../drawable/ic_baseline_bookmark_24.xml | 0 .../ic_baseline_bookmark_border_24.xml | 0 .../drawable/ic_baseline_bubble_chart_24.xml | 0 .../drawable/ic_baseline_cached_24.xml | 0 .../drawable/ic_baseline_camera_alt_24.xml | 0 .../drawable/ic_baseline_card_travel_24.xml | 0 .../drawable/ic_baseline_chat_bubble_24.xml | 0 .../ic_baseline_chat_bubble_outline_24.xml | 0 .../drawable/ic_baseline_check_24.xml | 0 .../drawable/ic_baseline_check_circle_24.xml | 0 .../drawable/ic_baseline_chevron_right_24.xml | 0 .../drawable/ic_baseline_close_24.xml | 0 .../drawable/ic_baseline_contact_page_24.xml | 0 .../drawable/ic_baseline_delete_24.xml | 0 .../drawable/ic_baseline_drafts_24.xml | 0 .../drawable/ic_baseline_drag_handle_24.xml | 0 .../drawable/ic_baseline_edit_24.xml | 0 .../drawable/ic_baseline_edit_note_24.xml | 0 .../ic_baseline_emoji_emotions_24.xml | 0 .../drawable/ic_baseline_expand_less_24.xml | 0 .../drawable/ic_baseline_expand_more_24.xml | 0 .../drawable/ic_baseline_extension_24.xml | 0 .../drawable/ic_baseline_filter_asc_24.xml | 0 .../ic_baseline_filter_center_focus_24.xml | 0 .../drawable/ic_baseline_filter_desc_24.xml | 0 .../drawable/ic_baseline_filter_list_24.xml | 0 .../drawable/ic_baseline_first_page_24.xml | 0 .../drawable/ic_baseline_format_quote_24.xml | 0 .../drawable/ic_baseline_format_size_24.xml | 0 .../drawable/ic_baseline_functions_24.xml | 0 .../drawable/ic_baseline_group_add_24.xml | 0 .../drawable/ic_baseline_home_24.xml | 0 .../ic_baseline_hourglass_full_24.xml | 0 .../drawable/ic_baseline_info_24.xml | 0 .../ic_baseline_insert_drive_file_24.xml | 0 .../ic_baseline_insert_emoticon_24.xml | 0 .../ic_baseline_keyboard_arrow_down_24.xml | 0 .../ic_baseline_keyboard_arrow_up_24.xml | 0 ...baseline_keyboard_double_arrow_down_24.xml | 0 ...c_baseline_keyboard_double_arrow_up_24.xml | 0 .../drawable/ic_baseline_label_24.xml | 0 .../drawable/ic_baseline_last_page_24.xml | 0 .../drawable/ic_baseline_local_only_24.xml | 0 .../ic_baseline_location_searching_24.xml | 0 .../drawable/ic_baseline_lock_24.xml | 0 .../drawable/ic_baseline_lock_open_24.xml | 0 .../drawable/ic_baseline_mail_24.xml | 0 .../drawable/ic_baseline_mail_outline_24.xml | 0 .../drawable/ic_baseline_manage_search_24.xml | 0 .../drawable/ic_baseline_message_24.xml | 0 .../mastodon}/drawable/ic_baseline_mic_24.xml | 0 .../drawable/ic_baseline_mode_24.xml | 0 .../drawable/ic_baseline_mode_comment_24.xml | 0 .../drawable/ic_baseline_mode_edit_24.xml | 0 .../ic_baseline_mode_edit_message_24.xml | 0 .../drawable/ic_baseline_more_horiz_24.xml | 0 .../drawable/ic_baseline_more_vert_24.xml | 0 .../drawable/ic_baseline_navigate_next_24.xml | 0 .../drawable/ic_baseline_note_24.xml | 0 .../drawable/ic_baseline_notes_24.xml | 0 .../drawable/ic_baseline_notifications_24.xml | 0 .../ic_baseline_notifications_active_24.xml | 0 .../ic_baseline_notifications_off_24.xml | 0 .../drawable/ic_baseline_open_with_24.xml | 0 .../drawable/ic_baseline_people_alt_24.xml | 0 .../ic_baseline_perm_contact_calendar_24.xml | 0 .../drawable/ic_baseline_perm_media_24.xml | 0 .../drawable/ic_baseline_person_add_24.xml | 0 .../ic_baseline_person_add_alt_1_24.xml | 0 .../drawable/ic_baseline_person_remove_24.xml | 0 .../ic_baseline_person_remove_alt_1_24.xml | 0 .../drawable/ic_baseline_photo_24.xml | 0 .../ic_baseline_play_circle_filled_24.xml | 0 .../drawable/ic_baseline_playlist_add_24.xml | 0 .../drawable/ic_baseline_poll_24.xml | 0 .../drawable/ic_baseline_post_add_24.xml | 0 .../drawable/ic_baseline_public_24.xml | 0 .../drawable/ic_baseline_push_pin_24.xml | 0 .../ic_baseline_remove_red_eye_24.xml | 0 .../drawable/ic_baseline_reorder_24.xml | 0 .../drawable/ic_baseline_repeat_24.xml | 0 .../drawable/ic_baseline_reply_16.xml | 0 .../drawable/ic_baseline_reply_24.xml | 0 .../drawable/ic_baseline_report_24.xml | 0 .../drawable/ic_baseline_save_24.xml | 0 .../drawable/ic_baseline_schedule_24.xml | 0 .../drawable/ic_baseline_schedule_send_24.xml | 0 .../drawable/ic_baseline_search_24.xml | 0 .../drawable/ic_baseline_settings_24.xml | 0 .../drawable/ic_baseline_share_24.xml | 0 .../drawable/ic_baseline_skip_next_24.xml | 0 .../drawable/ic_baseline_skip_previous_24.xml | 0 .../drawable/ic_baseline_star_24.xml | 0 .../drawable/ic_baseline_stop_circle_64.xml | 0 .../drawable/ic_baseline_storage_24.xml | 0 .../ic_baseline_supervised_user_circle_24.xml | 0 .../ic_baseline_supervisor_account_24.xml | 0 .../mastodon}/drawable/ic_baseline_tag_24.xml | 0 .../drawable/ic_baseline_translate_24.xml | 0 .../drawable/ic_baseline_trending_up_24.xml | 0 .../drawable/ic_baseline_verified_24.xml | 0 .../drawable/ic_baseline_view_list_24.xml | 0 .../drawable/ic_baseline_visibility_24.xml | 0 .../ic_baseline_visibility_off_24.xml | 0 .../drawable/ic_baseline_volume_mute_24.xml | 0 .../drawable/ic_baseline_warning_24.xml | 0 .../mastodon}/drawable/ic_block_script.xml | 0 .../mastodon}/drawable/ic_bot.xml | 0 .../mastodon}/drawable/ic_brush.xml | 0 .../mastodon}/drawable/ic_check_redirect.xml | 0 .../mastodon}/drawable/ic_close.xml | 0 .../mastodon}/drawable/ic_compose_attach.xml | 0 .../drawable/ic_compose_attach_audio.xml | 0 .../drawable/ic_compose_attach_image.xml | 0 .../drawable/ic_compose_attach_more.xml | 0 .../drawable/ic_compose_attach_video.xml | 0 .../ic_compose_attachment_order_down.xml | 0 .../ic_compose_attachment_order_up.xml | 0 .../drawable/ic_compose_attachment_play.xml | 0 .../drawable/ic_compose_attachment_remove.xml | 0 .../mastodon}/drawable/ic_compose_emoji.xml | 0 .../mastodon}/drawable/ic_compose_poll.xml | 0 .../ic_compose_poll_option_mark_multiple.xml | 0 .../ic_compose_poll_option_mark_single.xml | 0 .../mastodon}/drawable/ic_compose_post.xml | 0 .../drawable/ic_compose_sensitive.xml | 0 .../drawable/ic_compose_thread_add_status.xml | 0 .../ic_compose_thread_remove_status.xml | 0 .../drawable/ic_compose_visibility_direct.xml | 0 .../ic_compose_visibility_private.xml | 0 .../drawable/ic_compose_visibility_public.xml | 0 .../ic_compose_visibility_unlisted.xml | 0 .../mastodon}/drawable/ic_copy_link.xml | 0 .../mastodon}/drawable/ic_crop.xml | 0 .../mastodon}/drawable/ic_display_less.xml | 0 .../mastodon}/drawable/ic_display_more.xml | 0 .../mastodon}/drawable/ic_eraser.xml | 0 .../drawable/ic_fetch_more_arrow_downward.xml | 0 .../drawable/ic_fetch_more_arrow_upward.xml | 0 .../mastodon}/drawable/ic_full_link.xml | 0 .../mastodon}/drawable/ic_gnu_social.xml | 0 .../mastodon}/drawable/ic_insert_emoticon.xml | 0 .../mastodon}/drawable/ic_language.xml | 0 .../drawable/ic_launcher_atom_foreground.xml | 0 .../ic_launcher_bubbles_foreground.xml | 0 .../drawable/ic_launcher_crash_foreground.xml | 0 .../ic_launcher_fediverse_foreground.xml | 0 .../drawable/ic_launcher_foreground.xml | 0 .../drawable/ic_launcher_hero_foreground.xml | 0 .../ic_launcher_mastalab_foreground.xml | 0 .../mastodon}/drawable/ic_menu_gallery.xml | 0 .../mastodon}/drawable/ic_more.xml | 0 .../mastodon}/drawable/ic_more_horiz.xml | 0 .../mastodon}/drawable/ic_open_external.xml | 0 .../drawable/ic_outline_note_alt_24.xml | 0 .../drawable/ic_outline_remove_red_eye_24.xml | 0 .../mastodon}/drawable/ic_oval.xml | 0 .../mastodon}/drawable/ic_person.xml | 0 .../mastodon}/drawable/ic_photo_filter.xml | 0 .../mastodon}/drawable/ic_redo.xml | 0 .../mastodon}/drawable/ic_repeat.xml | 0 .../mastodon}/drawable/ic_reply.xml | 0 .../drawable/ic_round_bookmark_24.xml | 0 .../drawable/ic_round_bookmark_border_24.xml | 0 .../drawable/ic_round_more_horiz_24.xml | 0 .../mastodon}/drawable/ic_round_repeat_24.xml | 0 .../drawable/ic_round_repeat_active_24.xml | 0 .../mastodon}/drawable/ic_round_reply_24.xml | 0 .../mastodon}/drawable/ic_round_star_24.xml | 0 .../drawable/ic_round_star_border_24.xml | 0 .../mastodon}/drawable/ic_share_link.xml | 0 .../mastodon}/drawable/ic_shield.xml | 0 .../mastodon}/drawable/ic_star_outline.xml | 0 .../mastodon}/drawable/ic_style.xml | 0 .../mastodon}/drawable/ic_text.xml | 0 .../mastodon}/drawable/ic_theming.xml | 0 .../mastodon}/drawable/ic_timeline.xml | 0 .../mastodon}/drawable/ic_tl_list.xml | 0 .../mastodon}/drawable/ic_tl_tag.xml | 0 .../mastodon}/drawable/ic_undo.xml | 0 .../mastodon}/drawable/logo_mastodon.xml | 0 .../mastodon}/drawable/mastohost.png | Bin .../drawable/media_message_border.xml | 0 .../mastodon}/drawable/nitter.xml | 0 .../mastodon}/drawable/reaction_border.xml | 0 .../mastodon}/drawable/reaction_voted.xml | 0 .../mastodon}/drawable/red_border.xml | 0 .../drawable/rounded_border_text_view.xml | 0 .../mastodon}/drawable/shape_counter.xml | 0 .../mastodon}/drawable/tag_follow.xml | 0 .../mastodon}/drawable/tag_muted.xml | 0 .../mastodon}/drawable/tag_pin.xml | 0 .../mastodon}/drawable/tag_pin_off.xml | 0 .../mastodon}/drawable/tag_unfollow.xml | 0 .../mastodon}/drawable/tag_unmuted.xml | 0 .../mastodon}/drawable/translation_border.xml | 0 .../peertube/drawable/account_pp_border.xml | 18 + .../drawable/background_rounded_item.xml | 7 + .../peertube/drawable/circle_red.xml | 8 + .../ic_baseline_add_circle_outline_24.xml | 10 + .../drawable/ic_baseline_attach_money_24.xml | 10 + .../drawable/ic_baseline_blur_on_24.xml | 10 + .../drawable/ic_baseline_cancel_24.xml | 10 + .../peertube/drawable/ic_baseline_cast_24.xml | 10 + .../ic_baseline_cast_connected_24.xml | 10 + .../drawable/ic_baseline_category_24.xml | 16 + .../drawable/ic_baseline_close_white_24.xml | 10 + .../ic_baseline_cloud_download_24.xml | 10 + .../drawable/ic_baseline_cloud_upload_24.xml | 10 + .../drawable/ic_baseline_color_lens_24.xml | 10 + .../drawable/ic_baseline_error_24.xml | 10 + .../ic_baseline_featured_play_list_24.xml | 10 + .../ic_baseline_featured_video_24.xml | 10 + .../drawable/ic_baseline_fiber_new_24.xml | 10 + .../drawable/ic_baseline_forum_24.xml | 10 + .../drawable/ic_baseline_fullscreen_24.xml | 10 + .../ic_baseline_fullscreen_exit_24.xml | 10 + .../drawable/ic_baseline_group_24.xml | 10 + .../drawable/ic_baseline_high_quality_24.xml | 10 + .../drawable/ic_baseline_history_24.xml | 10 + .../ic_baseline_history_toggle_off_24.xml | 10 + .../drawable/ic_baseline_import_export_24.xml | 10 + .../drawable/ic_baseline_language_24.xml | 10 + .../peertube/drawable/ic_baseline_list_24.xml | 10 + .../drawable/ic_baseline_open_in_full_24.xml | 10 + .../drawable/ic_baseline_pause_32.xml | 10 + .../ic_baseline_personal_video_24.xml | 10 + .../drawable/ic_baseline_play_arrow_24.xml | 10 + .../drawable/ic_baseline_play_arrow_32.xml | 10 + .../drawable/ic_baseline_playlist_play_24.xml | 10 + .../ic_baseline_screen_lock_portrait_24.xml | 10 + .../peertube/drawable/ic_baseline_send_24.xml | 10 + .../ic_baseline_slow_motion_video_24.xml | 10 + .../drawable/ic_baseline_speed_24.xml | 10 + .../drawable/ic_baseline_subtitles_24.xml | 10 + .../ic_baseline_thumb_down_alt_24.xml | 10 + .../drawable/ic_baseline_thumb_up_24.xml | 10 + .../drawable/ic_baseline_thumb_up_alt_24.xml | 10 + .../drawable/ic_baseline_track_changes_24.xml | 10 + .../peertube/drawable/ic_home_black_24dp.xml | 9 + .../peertube/drawable/ic_liberapay.xml | 12 + .../drawable/ic_outline_account_circle_24.xml | 10 + .../drawables/peertube/drawable/ic_paypal.xml | 15 + .../drawable/ic_sensitive_content.xml | 10 + .../peertube/drawable/ic_subscription.xml | 61 + .../peertube/drawable/missing_peertube.png | Bin 0 -> 1274 bytes .../peertube/drawable/progress_bar.xml | 24 + .../peertube/drawable/rounded_corner.xml | 15 + .../drawable/rounded_decoration_bottom.xml | 38 + .../drawable/rounded_decoration_top.xml | 24 + .../peertube/drawable/rounded_live.xml | 15 + .../peertube/drawable/tom79_avatar.png | Bin 0 -> 36243 bytes .../mastodon/layout/fragment_login_join.xml | 2 +- .../layout/activity_account_peertube.xml | 175 ++ .../layout/activity_all_playlist_peertube.xml | 89 + .../activity_instance_picker_peertube.xml | 162 ++ .../layout/activity_login_peertube.xml | 181 ++ .../layout/activity_main_peertube.xml | 155 ++ .../activity_manage_instances_peertube.xml | 76 + .../activity_my_account_settings_peertube.xml | 368 +++ .../peertube/layout/activity_peertube.xml | 650 +++++ .../layout/activity_peertube_edit.xml | 289 ++ .../layout/activity_peertube_upload.xml | 190 ++ .../layout/activity_playlists_peertube.xml | 37 + .../layout/activity_register_peertube.xml | 204 ++ .../activity_search_result_peertube.xml | 57 + .../peertube/layout/activity_sepia_search.xml | 382 +++ .../layout/activity_show_account_peertube.xml | 133 + .../peertube/layout/activity_show_channel.xml | 170 ++ .../layout/activity_videos_timeline.xml | 85 + .../layout/activity_webview_connect.xml | 39 + .../layout/activity_webview_peertube.xml | 53 + .../peertube/layout/add_channel_peertube.xml | 102 + .../peertube/layout/add_playlist_peertube.xml | 131 + .../layout/counter_account_icon_peertube.xml | 35 + .../layout/drawer_about_instance_peertube.xml | 78 + .../peertube/layout/drawer_account_owner.xml | 45 + .../layout/drawer_account_peertube.xml | 74 + .../layout/drawer_channel_peertube.xml | 68 + .../layout/drawer_comment_peertube.xml | 147 ++ .../drawer_horizontal_account_peertube.xml | 47 + .../layout/drawer_instance_peertube.xml | 104 + .../peertube/layout/drawer_menu_item.xml | 34 + .../peertube/layout/drawer_menu_peertube.xml | 34 + .../peertube/layout/drawer_peertube.xml | 156 ++ .../layout/drawer_peertube_notification.xml | 80 + .../layout/drawer_playlist_peertube.xml | 88 + .../layout/drawer_status_compact_peertube.xml | 129 + .../exo_player_control_view_peertube.xml | 173 ++ .../layout/fragment_overview_peertube.xml | 98 + .../layout/fragment_playlists_peertube.xml | 87 + .../layout/fragment_recyclerview_peertube.xml | 83 + .../layout/fragment_video_peertube.xml | 127 + .../peertube/layout/popup_report_choice.xml | 42 + .../peertube/layout/popup_report_peertube.xml | 34 + .../layout/popup_video_info_peertube.xml | 159 ++ .../peertube/layout/save_button_peertube.xml | 9 + .../layout/webview_actionbar_peertube.xml | 40 + .../mastodon}/menu/activity_main_drawer.xml | 0 .../mastodon}/menu/activity_profile.xml | 0 .../mastodon}/menu/bottom_nav_menu.xml | 0 .../res/{ => menus/mastodon}/menu/main.xml | 0 .../{ => menus/mastodon}/menu/main_login.xml | 0 .../mastodon}/menu/main_webview.xml | 0 .../mastodon}/menu/menu_accounts.xml | 0 .../mastodon}/menu/menu_admin_account.xml | 0 .../mastodon}/menu/menu_admin_domain.xml | 0 .../{ => menus/mastodon}/menu/menu_cache.xml | 0 .../mastodon}/menu/menu_compose.xml | 0 .../mastodon}/menu/menu_context.xml | 0 .../mastodon}/menu/menu_directory.xml | 0 .../{ => menus/mastodon}/menu/menu_draft.xml | 0 .../mastodon}/menu/menu_edit_profile.xml | 0 .../mastodon}/menu/menu_followed_tag.xml | 0 .../mastodon}/menu/menu_hashtag.xml | 0 .../{ => menus/mastodon}/menu/menu_list.xml | 0 .../mastodon}/menu/menu_main_followed_tag.xml | 0 .../mastodon}/menu/menu_main_list.xml | 0 .../{ => menus/mastodon}/menu/menu_media.xml | 0 .../mastodon}/menu/menu_reorder.xml | 0 .../{ => menus/mastodon}/menu/menu_search.xml | 0 .../mastodon}/menu/option_bubble_timeline.xml | 0 .../mastodon}/menu/option_filter_toots.xml | 0 .../mastodon}/menu/option_nitter_timeline.xml | 0 .../mastodon}/menu/option_tag_timeline.xml | 0 .../{ => menus/mastodon}/menu/option_toot.xml | 0 .../bottom_nav_menu_connected_peertube.xml | 30 + .../menu/bottom_nav_menu_peertube.xml | 25 + .../peertube/menu/comment_menu_peertube.xml | 24 + .../peertube/menu/instance_menu_peertube.xml | 9 + .../peertube/menu/main_account_peertube.xml | 24 + .../peertube/menu/main_history_peertube.xml | 9 + .../peertube/menu/main_menu_peertube.xml | 75 + .../menu/main_my_account_peertube.xml | 9 + .../peertube/menu/main_profile_peertube.xml | 9 + .../peertube/menu/main_video_peertube.xml | 24 + .../peertube/menu/main_webview_peertube.xml | 9 + .../peertube/menu/playlist_menu_peertube.xml | 20 + .../menu/video_drawer_menu_peertube.xml | 24 + app/src/main/res/values-w1240dp/dimens.xml | 1 + app/src/main/res/values-w600dp/dimens.xml | 1 + app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/peertube_strings.xml | 400 +++ app/src/main/res/xml/main_preferences.xml | 86 + app/src/playstore/AndroidManifest.xml | 18 +- .../playstore/res/layout/min_controller.xml | 20 + 512 files changed, 37630 insertions(+), 5 deletions(-) create mode 100644 app/src/fdroid/res/layout/min_controller.xml create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/AccountActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/AllLocalPlaylistsActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/AllPlaylistsActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/InstancePickerActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/LocalPlaylistsActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/LoginActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/MainActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/ManageInstancesActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/MastodonWebviewConnectActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/MyAccountActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/PeertubeActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/PeertubeEditUploadActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/PeertubeRegisterActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/PeertubeUploadActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/PlaylistsActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/SearchActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/SepiaSearchActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/SettingsActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/ShowAccountActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/ShowChannelActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/VideosTimelineActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/WebviewActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/activities/WebviewConnectActivity.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/APIResponse.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/MenuItemVideo.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/PeertubeService.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/RetrofitPeertubeAPI.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/RetrofitSepiaSearchAPI.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/SepiaSearchService.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/AccountData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/BlockData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/CaptionData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/ChannelData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/CommentData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/InstanceData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/NotificationData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/PlaylistData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/PluginData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/VideoData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/data/VideoPlaylistData.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/AcadInstances.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/AccountCreation.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/Actor.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/ActorFollow.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/Avatar.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/CaptionsParams.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/ChannelParams.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/Error.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/File.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/InstanceParams.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/Item.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/ItemStr.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/MenuItemView.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/NotificationSettings.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/Oauth.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/OauthParams.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/OverviewVideo.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/PeertubeInformation.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/PlaylistExist.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/PlaylistParams.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/Rating.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/Report.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/SepiaSearch.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/StreamingPlaylists.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/Token.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/UserMe.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/UserSettings.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/VideoAbuse.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/VideoBlacklist.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/VideoParams.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/ViewsPerDay.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/entities/WellKnownNodeinfo.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/mastodon/MastodonAccount.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/mastodon/MastodonService.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/mastodon/Results.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/mastodon/RetrofitMastodonAPI.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/client/mastodon/Status.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/AboutInstanceAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/AccountsHorizontalListAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/AccountsListAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/ChannelListAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/CommentListAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/InstanceAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/MenuAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/MenuItemAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/OwnAccountsAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/PeertubeAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/PeertubeNotificationsListAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/drawer/PlaylistAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/fragment/DisplayAccountsFragment.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/fragment/DisplayChannelsFragment.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/fragment/DisplayNotificationsFragment.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/fragment/DisplayOverviewFragment.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/fragment/DisplayPlaylistsFragment.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/fragment/DisplaySepiaSearchFragment.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/fragment/DisplayVideosFragment.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/fragment/SettingsFragment.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/CacheDataSourceFactory.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/CommentDecorationHelper.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/EmojiHelper.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/Helper.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/HelperAcadInstance.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/HelperInstance.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/NetworkStateReceiver.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/NotificationHelper.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/PlaylistExportHelper.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/RoundedBackgroundSpan.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/SwitchAccountHelper.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/helper/ThemeHelper.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/services/GlobalUploadObserver.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/services/RetrieveInfoService.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/sqlite/AccountDAO.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/sqlite/ManagePlaylistsDAO.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/sqlite/MastodonAccountDAO.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/sqlite/Sqlite.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/sqlite/StoredInstanceDAO.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/AccountsVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/CaptionsVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/ChannelsVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/CommentVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/InfoInstanceVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/InstancesVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/MyVideoVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/NotificationsVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/PlaylistsVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/PostActionsVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/RelationshipVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/SearchVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/SepiaSearchVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/TimelineVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/viewmodel/mastodon/MastodonPostActionsVM.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/webview/CustomWebview.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/webview/MastalabWebChromeClient.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/webview/MastalabWebViewClient.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/webview/ProxyHelper.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/worker/NotificationsWorker.java create mode 100644 app/src/main/java/app/fedilab/android/peertube/worker/WorkHelper.java rename app/src/main/res/{ => drawables/mastodon}/drawable/aa.png (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/bb.png (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/bg_compose_panels.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/blue_border.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/browser_calls_blocked.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/default_banner.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/empty_custom_emoji.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/fedilab_logo_atom.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/fedilab_logo_bubbles.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/fedilab_logo_crash.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/fedilab_logo_fediverse.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/fedilab_logo_hero.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/fedilab_logo_mastalab.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/green_border.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_admin.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_access_time_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_access_time_filled_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_account_circle_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_add_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_add_reaction_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_android_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_arrow_drop_down_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_arrow_drop_up_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_audio_file_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_audiotrack_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_block_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_bookmark_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_bookmark_border_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_bubble_chart_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_cached_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_camera_alt_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_card_travel_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_chat_bubble_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_chat_bubble_outline_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_check_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_check_circle_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_chevron_right_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_close_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_contact_page_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_delete_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_drafts_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_drag_handle_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_edit_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_edit_note_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_emoji_emotions_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_expand_less_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_expand_more_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_extension_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_filter_asc_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_filter_center_focus_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_filter_desc_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_filter_list_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_first_page_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_format_quote_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_format_size_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_functions_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_group_add_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_home_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_hourglass_full_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_info_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_insert_drive_file_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_insert_emoticon_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_keyboard_arrow_down_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_keyboard_arrow_up_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_keyboard_double_arrow_down_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_keyboard_double_arrow_up_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_label_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_last_page_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_local_only_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_location_searching_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_lock_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_lock_open_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_mail_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_mail_outline_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_manage_search_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_message_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_mic_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_mode_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_mode_comment_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_mode_edit_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_mode_edit_message_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_more_horiz_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_more_vert_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_navigate_next_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_note_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_notes_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_notifications_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_notifications_active_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_notifications_off_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_open_with_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_people_alt_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_perm_contact_calendar_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_perm_media_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_person_add_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_person_add_alt_1_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_person_remove_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_person_remove_alt_1_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_photo_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_play_circle_filled_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_playlist_add_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_poll_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_post_add_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_public_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_push_pin_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_remove_red_eye_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_reorder_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_repeat_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_reply_16.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_reply_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_report_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_save_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_schedule_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_schedule_send_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_search_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_settings_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_share_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_skip_next_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_skip_previous_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_star_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_stop_circle_64.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_storage_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_supervised_user_circle_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_supervisor_account_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_tag_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_translate_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_trending_up_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_verified_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_view_list_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_visibility_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_visibility_off_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_volume_mute_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_baseline_warning_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_block_script.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_bot.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_brush.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_check_redirect.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_close.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_attach.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_attach_audio.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_attach_image.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_attach_more.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_attach_video.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_attachment_order_down.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_attachment_order_up.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_attachment_play.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_attachment_remove.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_emoji.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_poll.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_poll_option_mark_multiple.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_poll_option_mark_single.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_post.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_sensitive.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_thread_add_status.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_thread_remove_status.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_visibility_direct.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_visibility_private.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_visibility_public.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_compose_visibility_unlisted.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_copy_link.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_crop.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_display_less.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_display_more.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_eraser.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_fetch_more_arrow_downward.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_fetch_more_arrow_upward.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_full_link.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_gnu_social.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_insert_emoticon.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_language.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_launcher_atom_foreground.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_launcher_bubbles_foreground.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_launcher_crash_foreground.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_launcher_fediverse_foreground.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_launcher_foreground.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_launcher_hero_foreground.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_launcher_mastalab_foreground.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_menu_gallery.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_more.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_more_horiz.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_open_external.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_outline_note_alt_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_outline_remove_red_eye_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_oval.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_person.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_photo_filter.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_redo.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_repeat.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_reply.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_round_bookmark_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_round_bookmark_border_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_round_more_horiz_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_round_repeat_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_round_repeat_active_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_round_reply_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_round_star_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_round_star_border_24.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_share_link.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_shield.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_star_outline.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_style.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_text.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_theming.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_timeline.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_tl_list.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_tl_tag.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/ic_undo.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/logo_mastodon.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/mastohost.png (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/media_message_border.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/nitter.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/reaction_border.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/reaction_voted.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/red_border.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/rounded_border_text_view.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/shape_counter.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/tag_follow.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/tag_muted.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/tag_pin.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/tag_pin_off.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/tag_unfollow.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/tag_unmuted.xml (100%) rename app/src/main/res/{ => drawables/mastodon}/drawable/translation_border.xml (100%) create mode 100644 app/src/main/res/drawables/peertube/drawable/account_pp_border.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/background_rounded_item.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/circle_red.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_add_circle_outline_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_attach_money_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_blur_on_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_cancel_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_cast_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_cast_connected_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_category_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_close_white_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_cloud_download_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_cloud_upload_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_color_lens_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_error_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_featured_play_list_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_featured_video_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_fiber_new_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_forum_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_fullscreen_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_fullscreen_exit_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_group_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_high_quality_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_history_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_history_toggle_off_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_import_export_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_language_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_list_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_open_in_full_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_pause_32.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_personal_video_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_play_arrow_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_play_arrow_32.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_playlist_play_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_screen_lock_portrait_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_send_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_slow_motion_video_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_speed_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_subtitles_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_thumb_down_alt_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_thumb_up_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_thumb_up_alt_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_baseline_track_changes_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_home_black_24dp.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_liberapay.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_outline_account_circle_24.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_paypal.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_sensitive_content.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/ic_subscription.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/missing_peertube.png create mode 100644 app/src/main/res/drawables/peertube/drawable/progress_bar.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/rounded_corner.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/rounded_decoration_bottom.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/rounded_decoration_top.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/rounded_live.xml create mode 100644 app/src/main/res/drawables/peertube/drawable/tom79_avatar.png create mode 100644 app/src/main/res/layouts/peertube/layout/activity_account_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_all_playlist_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_instance_picker_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_login_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_main_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_manage_instances_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_my_account_settings_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_peertube_edit.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_peertube_upload.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_playlists_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_register_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_search_result_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_sepia_search.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_show_account_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_show_channel.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_videos_timeline.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_webview_connect.xml create mode 100644 app/src/main/res/layouts/peertube/layout/activity_webview_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/add_channel_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/add_playlist_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/counter_account_icon_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_about_instance_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_account_owner.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_account_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_channel_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_comment_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_horizontal_account_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_instance_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_menu_item.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_menu_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_peertube_notification.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_playlist_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/drawer_status_compact_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/exo_player_control_view_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/fragment_overview_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/fragment_playlists_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/fragment_recyclerview_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/fragment_video_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/popup_report_choice.xml create mode 100644 app/src/main/res/layouts/peertube/layout/popup_report_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/popup_video_info_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/save_button_peertube.xml create mode 100644 app/src/main/res/layouts/peertube/layout/webview_actionbar_peertube.xml rename app/src/main/res/{ => menus/mastodon}/menu/activity_main_drawer.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/activity_profile.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/bottom_nav_menu.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/main.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/main_login.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/main_webview.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_accounts.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_admin_account.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_admin_domain.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_cache.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_compose.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_context.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_directory.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_draft.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_edit_profile.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_followed_tag.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_hashtag.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_list.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_main_followed_tag.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_main_list.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_media.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_reorder.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/menu_search.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/option_bubble_timeline.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/option_filter_toots.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/option_nitter_timeline.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/option_tag_timeline.xml (100%) rename app/src/main/res/{ => menus/mastodon}/menu/option_toot.xml (100%) create mode 100644 app/src/main/res/menus/peertube/menu/bottom_nav_menu_connected_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/bottom_nav_menu_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/comment_menu_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/instance_menu_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/main_account_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/main_history_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/main_menu_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/main_my_account_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/main_profile_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/main_video_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/main_webview_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/playlist_menu_peertube.xml create mode 100644 app/src/main/res/menus/peertube/menu/video_drawer_menu_peertube.xml create mode 100644 app/src/main/res/values/peertube_strings.xml create mode 100644 app/src/main/res/xml/main_preferences.xml create mode 100644 app/src/playstore/res/layout/min_controller.xml diff --git a/app/build.gradle b/app/build.gradle index 8e0b66e40..d5e61613e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,9 +67,14 @@ android { res.srcDirs = [ 'src/main/res/layouts/mastodon', 'src/main/res/layouts/peertube', - ' src/main/res/layouts', + 'src/main/res/layouts', + 'src/main/res/drawables/mastodon', + 'src/main/res/drawables/peertube', + 'src/main/res/drawables', + 'src/main/res/menus/mastodon', + 'src/main/res/menus/peertube', + 'src/main/res/menus', 'src/main/res' - ] } } @@ -154,6 +159,49 @@ dependencies { // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' implementation 'com.r0adkll:slidableactivity:2.1.0' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + + + implementation 'androidx.vectordrawable:vectordrawable:1.1.0' + implementation "androidx.fragment:fragment:1.5.5" + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.browser:browser:1.4.0' + implementation 'androidx.documentfile:documentfile:1.0.1' + implementation 'com.github.amoskorir:avatarimagegenerator:1.5.0' + implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' + implementation 'com.google.android.exoplayer:extension-mediasession:2.18.1' + implementation "com.github.mabbas007:TagsEditText:1.0.5" + implementation "net.gotev:uploadservice:4.5.1" + implementation "net.gotev:uploadservice-okhttp:4.5.1" + implementation 'androidx.media:media:1.6.0' + implementation 'com.github.ybq:Android-SpinKit:1.4.0' + implementation 'com.github.mancj:MaterialSearchBar:0.8.5' + + implementation 'com.github.vkay94:DoubleTapPlayerView:1.0.0' + + + //************ CAST **************/// + + //---> Google libs (google_full) + playstoreImplementation "com.google.android.gms:play-services-cast-tv:19.0.1" + playstoreImplementation "com.google.android.gms:play-services-cast:21.0.1" + playstoreImplementation "androidx.mediarouter:mediarouter:1.3.0" + playstoreImplementation 'com.google.android.gms:play-services-cast-framework:21.0.1' + + playstoreImplementation "com.google.android.gms:play-services-cast-tv:19.0.1" + playstoreImplementation "com.google.android.gms:play-services-cast:21.0.1" + playstoreImplementation "androidx.mediarouter:mediarouter:1.3.0" + playstoreImplementation 'com.google.android.gms:play-services-cast-framework:21.0.1' + + //----> Other flavors + fdroidImplementation 'su.litvak.chromecast:api-v2:0.11.3' + fdroidImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0' + fdroidImplementation 'org.slf4j:slf4j-simple:1.7.30' + fdroidImplementation 'com.github.evozi:Cyanea:1.0.7' + + fdroidImplementation 'su.litvak.chromecast:api-v2:0.11.3' + fdroidImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0' + fdroidImplementation 'org.slf4j:slf4j-simple:1.7.30' + } def getCurrentFlavor() { Gradle gradle = getGradle() diff --git a/app/src/fdroid/res/layout/min_controller.xml b/app/src/fdroid/res/layout/min_controller.xml new file mode 100644 index 000000000..1c226909f --- /dev/null +++ b/app/src/fdroid/res/layout/min_controller.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d1bd68709..02c699d2a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -369,5 +369,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/AccountActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/AccountActivity.java new file mode 100644 index 000000000..1dfec3371 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/AccountActivity.java @@ -0,0 +1,299 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.activities.MainActivity.badgeCount; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.Html; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.UnderlineSpan; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.tabs.TabLayout; + +import org.jetbrains.annotations.NotNull; + +import app.fedilab.android.R; +import app.fedilab.android.databinding.ActivityAccountBinding; +import app.fedilab.android.mastodon.activities.BaseActivity; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.data.AccountData.Account; +import app.fedilab.android.peertube.fragment.DisplayAccountsFragment; +import app.fedilab.android.peertube.fragment.DisplayChannelsFragment; +import app.fedilab.android.peertube.fragment.DisplayNotificationsFragment; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.SwitchAccountHelper; +import app.fedilab.android.peertube.sqlite.AccountDAO; +import app.fedilab.android.peertube.sqlite.Sqlite; + + +public class AccountActivity extends BaseActivity { + + + private ActivityAccountBinding binding; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = ActivityAccountBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + + SpannableString content_create = new SpannableString(getString(R.string.join_peertube)); + content_create.setSpan(new UnderlineSpan(), 0, content_create.length(), 0); + content_create.setSpan(new ForegroundColorSpan(Helper.fetchAccentColor(AccountActivity.this)), 0, content_create.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + + SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + String token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null); + + Account account = new AccountDAO(AccountActivity.this, db).getAccountByToken(token); + if (account == null) { + Helper.logoutCurrentUser(AccountActivity.this, null); + return; + } + + + setTitle(String.format("@%s", account.getUsername())); + + Helper.loadAvatar(AccountActivity.this, account, binding.profilePicture); + binding.username.setText(String.format("@%s", account.getUsername())); + binding.displayname.setText(account.getDisplayName()); + + binding.instance.setText(account.getHost()); + + binding.logoutButton.setOnClickListener(v -> { + AlertDialog.Builder dialogBuilderLogoutAccount = new AlertDialog.Builder(AccountActivity.this); + dialogBuilderLogoutAccount.setMessage(getString(R.string.logout_account_confirmation, account.getUsername(), account.getHost())); + dialogBuilderLogoutAccount.setPositiveButton(R.string.action_logout, (dialog, id) -> { + Helper.logoutCurrentUser(AccountActivity.this, account); + dialog.dismiss(); + }); + dialogBuilderLogoutAccount.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); + AlertDialog alertDialogLogoutAccount = dialogBuilderLogoutAccount.create(); + alertDialogLogoutAccount.show(); + }); + + binding.settings.setOnClickListener(v -> { + Intent intent = new Intent(AccountActivity.this, SettingsActivity.class); + startActivity(intent); + }); + + + TabLayout.Tab notificationTab = binding.accountTabLayout.newTab(); + if (Helper.isLoggedIn(AccountActivity.this)) { + if (badgeCount > 0) { + binding.accountTabLayout.addTab(notificationTab.setText(getString(R.string.title_notifications) + " (" + badgeCount + ")")); + } else { + binding.accountTabLayout.addTab(notificationTab.setText(getString(R.string.title_notifications))); + } + binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.title_muted))); + binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.title_channel))); + + binding.accountViewpager.setOffscreenPageLimit(3); + + + binding.accountViewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + TabLayout.Tab tab = binding.accountTabLayout.getTabAt(position); + if (tab != null) + tab.select(); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + + + binding.accountTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) { + binding.accountViewpager.setCurrentItem(tab.getPosition()); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + Fragment fragment = null; + if (binding.accountViewpager.getAdapter() != null) + fragment = (Fragment) binding.accountViewpager.getAdapter().instantiateItem(binding.accountViewpager, tab.getPosition()); + switch (tab.getPosition()) { + case 0: + if (badgeCount > 0) { + android.app.AlertDialog.Builder builder; + builder = new android.app.AlertDialog.Builder(AccountActivity.this); + builder.setMessage(R.string.mark_all_notifications_as_read_confirm); + builder.setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(R.string.mark_all_as_read, (dialog, which) -> { + new Thread(() -> { + new RetrofitPeertubeAPI(AccountActivity.this).markAllAsRead(); + Handler mainHandler = new Handler(Looper.getMainLooper()); + badgeCount = 0; + Runnable myRunnable = () -> binding.accountTabLayout.getTabAt(0).setText(getString(R.string.title_notifications)); + mainHandler.post(myRunnable); + }).start(); + + dialog.dismiss(); + }) + .setNegativeButton(R.string.no, (dialog, which) -> dialog.dismiss()) + .show(); + } else { + if (fragment != null) { + DisplayNotificationsFragment displayNotificationsFragment = ((DisplayNotificationsFragment) fragment); + displayNotificationsFragment.scrollToTop(); + } + } + break; + case 1: + if (fragment != null) { + DisplayAccountsFragment displayAccountsFragment = ((DisplayAccountsFragment) fragment); + displayAccountsFragment.scrollToTop(); + } + break; + case 2: + if (fragment != null) { + DisplayChannelsFragment displayChannelsFragment = ((DisplayChannelsFragment) fragment); + displayChannelsFragment.scrollToTop(); + } + break; + } + } + }); + + PagerAdapter mPagerAdapter = new AccountsPagerAdapter(getSupportFragmentManager()); + binding.accountViewpager.setAdapter(mPagerAdapter); + } else { + binding.accountTabLayout.setVisibility(View.GONE); + binding.accountViewpager.setVisibility(View.GONE); + binding.remoteAccount.setVisibility(View.VISIBLE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + binding.remoteAccount.setText(Html.fromHtml(getString(R.string.remote_account_from, account.getSoftware()), Html.FROM_HTML_MODE_LEGACY)); + else + binding.remoteAccount.setText(Html.fromHtml(getString(R.string.remote_account_from, account.getSoftware()))); + } + } + + public void updateCounter() { + if (badgeCount > 0) { + binding.accountTabLayout.getTabAt(0).setText(getString(R.string.title_notifications) + " (" + badgeCount + ")"); + } else { + binding.accountTabLayout.getTabAt(0).setText(getString(R.string.title_notifications)); + } + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + public boolean onCreateOptionsMenu(@NotNull Menu menu) { + getMenuInflater().inflate(R.menu.main_profile_peertube, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + overridePendingTransition(R.anim.slide_out_up, R.anim.slide_in_up_down); + return true; + } else if (item.getItemId() == R.id.action_add_account) { + SwitchAccountHelper.switchDialog(AccountActivity.this, true); + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + overridePendingTransition(R.anim.slide_out_up, R.anim.slide_in_up_down); + } + + /** + * Pager adapter for three tabs (notifications, muted, blocked) + */ + private static class AccountsPagerAdapter extends FragmentStatePagerAdapter { + + AccountsPagerAdapter(FragmentManager fm) { + super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); + } + + @NotNull + @Override + public Fragment getItem(int position) { + Bundle bundle = new Bundle(); + switch (position) { + case 1: + DisplayAccountsFragment displayAccountsFragment = new DisplayAccountsFragment(); + bundle.putSerializable("accountFetch", RetrofitPeertubeAPI.DataType.MUTED); + displayAccountsFragment.setArguments(bundle); + return displayAccountsFragment; + case 2: + return new DisplayChannelsFragment(); + default: + return new DisplayNotificationsFragment(); + } + } + + @Override + public int getCount() { + return 3; + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/AllLocalPlaylistsActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/AllLocalPlaylistsActivity.java new file mode 100644 index 000000000..3b58f7a5a --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/AllLocalPlaylistsActivity.java @@ -0,0 +1,123 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + + +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.data.PlaylistData.Playlist; +import app.fedilab.android.peertube.client.data.VideoPlaylistData; +import app.fedilab.android.peertube.drawer.PlaylistAdapter; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.viewmodel.PlaylistsVM; + + +public class AllLocalPlaylistsActivity extends BaseActivity implements PlaylistAdapter.AllPlaylistRemoved { + + + PlaylistAdapter playlistAdapter; + private RelativeLayout mainLoader; + private RelativeLayout textviewNoAction; + private List playlists; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_all_playlist); + + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setTitle(R.string.playlists); + + + textviewNoAction = findViewById(R.id.no_action); + mainLoader = findViewById(R.id.loader); + RelativeLayout nextElementLoader = findViewById(R.id.loading_next_items); + mainLoader.setVisibility(View.VISIBLE); + nextElementLoader.setVisibility(View.GONE); + + PlaylistsVM viewModel = new ViewModelProvider(AllLocalPlaylistsActivity.this).get(PlaylistsVM.class); + viewModel.localePlaylist().observe(AllLocalPlaylistsActivity.this, this::manageVIewPlaylists); + + FloatingActionButton add_new = findViewById(R.id.add_new); + add_new.setVisibility(View.GONE); + + TextView no_action_text = findViewById(R.id.no_action_text); + no_action_text.setText(R.string.no_playlist); + playlists = new ArrayList<>(); + RecyclerView lv_playlist = findViewById(R.id.lv_playlist); + playlistAdapter = new PlaylistAdapter(playlists, true); + playlistAdapter.allPlaylistRemoved = this; + lv_playlist.setAdapter(playlistAdapter); + LinearLayoutManager mLayoutManager = new LinearLayoutManager(AllLocalPlaylistsActivity.this); + lv_playlist.setLayoutManager(mLayoutManager); + + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + + public void manageVIewPlaylists(List videoPlaylistExports) { + mainLoader.setVisibility(View.GONE); + if (videoPlaylistExports == null) { + textviewNoAction.setVisibility(View.VISIBLE); + return; + } + if (videoPlaylistExports.size() > 0) { + for (VideoPlaylistData.VideoPlaylistExport videoPlaylistExport : videoPlaylistExports) { + playlists.add(videoPlaylistExport.getPlaylist()); + } + playlistAdapter.notifyDataSetChanged(); + textviewNoAction.setVisibility(View.GONE); + } else { + textviewNoAction.setVisibility(View.VISIBLE); + } + } + + @Override + public void onAllPlaylistRemoved() { + textviewNoAction.setVisibility(View.VISIBLE); + } + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/AllPlaylistsActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/AllPlaylistsActivity.java new file mode 100644 index 000000000..6cf47348c --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/AllPlaylistsActivity.java @@ -0,0 +1,431 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.activities.PeertubeUploadActivity.MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE; +import static app.fedilab.android.peertube.helper.Helper.peertubeInformation; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.InputFilter; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.APIResponse; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.data.ChannelData; +import app.fedilab.android.peertube.client.data.PlaylistData.Playlist; +import app.fedilab.android.peertube.client.entities.Item; +import app.fedilab.android.peertube.client.entities.PlaylistParams; +import app.fedilab.android.peertube.databinding.ActivityAllPlaylistBinding; +import app.fedilab.android.peertube.databinding.AddPlaylistBinding; +import app.fedilab.android.peertube.drawer.PlaylistAdapter; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.viewmodel.ChannelsVM; +import app.fedilab.android.peertube.viewmodel.PlaylistsVM; +import es.dmoral.toasty.Toasty; + + +public class AllPlaylistsActivity extends BaseActivity implements PlaylistAdapter.AllPlaylistRemoved { + + + private static final int PICK_AVATAR = 467; + PlaylistAdapter playlistAdapter; + private HashMap privacyToSend; + private String idChannel; + private List playlists; + private Playlist playlistToEdit; + private List myChannels; + private ChannelData.Channel selectedChannel; + private AddPlaylistBinding bindingDialog; + private Uri inputData; + private ActivityAllPlaylistBinding binding; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + binding = ActivityAllPlaylistBinding.inflate(getLayoutInflater()); + View viewRoot = binding.getRoot(); + setContentView(viewRoot); + + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setTitle(R.string.playlists); + + + binding.loader.setVisibility(View.VISIBLE); + binding.loadingNextItems.setVisibility(View.GONE); + idChannel = null; + + PlaylistsVM viewModel = new ViewModelProvider(AllPlaylistsActivity.this).get(PlaylistsVM.class); + viewModel.manage(PlaylistsVM.action.GET_PLAYLISTS, null, null).observe(AllPlaylistsActivity.this, apiResponse -> manageVIewPlaylists(PlaylistsVM.action.GET_PLAYLISTS, apiResponse)); + + + LinkedHashMap privaciesInit = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + if (privaciesInit.size() > 0) { + Map.Entry entryInt = privaciesInit.entrySet().iterator().next(); + privacyToSend = new HashMap<>(); + privacyToSend.put(entryInt.getKey(), entryInt.getValue()); + } + + + playlists = new ArrayList<>(); + playlistAdapter = new PlaylistAdapter(playlists, false); + playlistAdapter.allPlaylistRemoved = this; + binding.lvPlaylist.setAdapter(playlistAdapter); + LinearLayoutManager mLayoutManager = new LinearLayoutManager(AllPlaylistsActivity.this); + binding.lvPlaylist.setLayoutManager(mLayoutManager); + binding.addNew.setOnClickListener(view -> manageAlert(null)); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + + public void manageVIewPlaylists(PlaylistsVM.action actionType, APIResponse apiResponse) { + binding.loader.setVisibility(View.GONE); + if (apiResponse.getError() != null) { + Toasty.error(AllPlaylistsActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + return; + } + if (actionType == PlaylistsVM.action.GET_PLAYLISTS) { + if (apiResponse.getPlaylists() != null && apiResponse.getPlaylists().size() > 0) { + playlists.addAll(apiResponse.getPlaylists()); + playlistAdapter.notifyDataSetChanged(); + binding.noAction.setVisibility(View.GONE); + } else { + binding.noAction.setVisibility(View.VISIBLE); + } + } + } + + public void manageAlert(Playlist playlistParam) { + + playlistToEdit = playlistParam; + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(AllPlaylistsActivity.this); + bindingDialog = AddPlaylistBinding.inflate(LayoutInflater.from(AllPlaylistsActivity.this), null, false); + dialogBuilder.setView(bindingDialog.getRoot()); + + dialogBuilder.setView(bindingDialog.getRoot()); + + + ChannelsVM viewModelC = new ViewModelProvider(AllPlaylistsActivity.this).get(ChannelsVM.class); + viewModelC.get(RetrofitPeertubeAPI.DataType.MY_CHANNELS, null).observe(AllPlaylistsActivity.this, this::manageVIewChannels); + + bindingDialog.displayName.setFilters(new InputFilter[]{new InputFilter.LengthFilter(120)}); + bindingDialog.description.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1000)}); + + if (playlistToEdit != null) { + bindingDialog.displayName.setText(playlistToEdit.getDisplayName()); + bindingDialog.description.setText(playlistToEdit.getDescription()); + } + dialogBuilder.setPositiveButton(R.string.validate, null); + dialogBuilder.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); + + AlertDialog alertDialog = dialogBuilder.create(); + + bindingDialog.selectFile.setOnClickListener(v -> { + if (ContextCompat.checkSelfPermission(AllPlaylistsActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != + PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(AllPlaylistsActivity.this, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); + return; + } + + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + String[] mimetypes = {"image/*"}; + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); + startActivityForResult(intent, PICK_AVATAR); + }); + Helper.loadGiF(AllPlaylistsActivity.this, playlistParam != null ? playlistParam.getThumbnailPath() : null, bindingDialog.profilePicture); + alertDialog.setOnShowListener(dialogInterface -> { + + Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + button.setOnClickListener(view -> { + if (bindingDialog.displayName.getText() != null && bindingDialog.displayName.getText().toString().trim().length() > 0) { + PlaylistParams playlistElement = new PlaylistParams(); + playlistElement.setDisplayName(bindingDialog.displayName.getText().toString().trim()); + if (bindingDialog.description.getText() != null && bindingDialog.description.getText().toString().trim().length() > 0) { + playlistElement.setDescription(bindingDialog.description.getText().toString().trim()); + } + playlistElement.setVideoChannelId(idChannel); + String label; + Map.Entry privacyM = privacyToSend.entrySet().iterator().next(); + Item privacyItem = new Item(); + privacyItem.setId(privacyM.getKey()); + privacyItem.setLabel(privacyM.getValue()); + label = privacyM.getValue(); + if ((label.trim().compareTo("Public") == 0 && (playlistElement.getVideoChannelId() == null || playlistElement.getVideoChannelId().trim().compareTo("null") == 0))) { + Toasty.error(AllPlaylistsActivity.this, getString(R.string.error_channel_mandatory), Toast.LENGTH_LONG).show(); + } else { + if (privacyToSend != null) { + playlistElement.setPrivacy(privacyItem.getId()); + } + new Thread(() -> { + String playlistId; + if (playlistToEdit == null) { + APIResponse apiResponse = new RetrofitPeertubeAPI(AllPlaylistsActivity.this).createOrUpdatePlaylist(PlaylistsVM.action.CREATE_PLAYLIST, null, playlistElement, inputData); + playlistId = apiResponse.getActionReturn(); + } else { + playlistId = playlistToEdit.getId(); + new RetrofitPeertubeAPI(AllPlaylistsActivity.this).createOrUpdatePlaylist(PlaylistsVM.action.UPDATE_PLAYLIST, playlistId, playlistElement, inputData); + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + Playlist playlist; + if (playlistToEdit == null) { + playlist = new Playlist(); + } else { + playlist = playlistToEdit; + } + playlist.setId(playlistId); + playlist.setUuid(playlistId); + playlist.setDescription(playlistElement.getDescription()); + playlist.setDisplayName(playlistElement.getDisplayName()); + playlist.setVideoChannel(selectedChannel); + playlist.setPrivacy(privacyItem); + if (playlistToEdit == null) { + playlists.add(playlist); + } + playlistAdapter.notifyDataSetChanged(); + }; + mainHandler.post(myRunnable); + }).start(); + alertDialog.dismiss(); + } + } else { + Toasty.error(AllPlaylistsActivity.this, getString(R.string.error_display_name), Toast.LENGTH_LONG).show(); + } + }); + }); + + if (playlistToEdit == null) { + alertDialog.setTitle(getString(R.string.action_playlist_create)); + } else { + alertDialog.setTitle(getString(R.string.action_playlist_edit)); + } + alertDialog.setOnDismissListener(dialogInterface -> { + //Hide keyboard + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + assert imm != null; + imm.hideSoftInputFromWindow(bindingDialog.displayName.getWindowToken(), 0); + }); + if (alertDialog.getWindow() != null) + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + alertDialog.show(); + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_AVATAR && resultCode == Activity.RESULT_OK) { + if (data == null || data.getData() == null) { + Toasty.error(AllPlaylistsActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); + return; + } + inputData = data.getData(); + Glide.with(AllPlaylistsActivity.this) + .load(inputData) + .thumbnail(0.1f) + .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) + .into(bindingDialog.profilePicture); + + } + } + + public void manageVIewChannels(APIResponse apiResponse) { + if (apiResponse.getError() != null || apiResponse.getChannels() == null || apiResponse.getChannels().size() == 0) { + if (apiResponse.getError() != null && apiResponse.getError().getError() != null) + Toasty.error(AllPlaylistsActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + else + Toasty.error(AllPlaylistsActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + return; + } + + //Populate channels + myChannels = apiResponse.getChannels(); + String[] channelName = new String[myChannels.size() + 1]; + String[] channelId = new String[myChannels.size() + 1]; + int i = 1; + channelName[0] = ""; + channelId[0] = "null"; + + for (ChannelData.Channel channel : myChannels) { + channelName[i] = channel.getName(); + channelId[i] = channel.getId(); + i++; + } + + ArrayAdapter adapterChannel = new ArrayAdapter<>(AllPlaylistsActivity.this, + android.R.layout.simple_spinner_dropdown_item, channelName); + bindingDialog.setUploadChannel.setAdapter(adapterChannel); + + + LinkedHashMap translations = null; + if (peertubeInformation.getTranslations() != null) + translations = new LinkedHashMap<>(peertubeInformation.getTranslations()); + + LinkedHashMap privaciesInit = new LinkedHashMap<>(peertubeInformation.getPlaylistPrivacies()); + Map.Entry entryInt = privaciesInit.entrySet().iterator().next(); + privacyToSend = new HashMap<>(); + privacyToSend.put(entryInt.getKey(), entryInt.getValue()); + LinkedHashMap privacies = new LinkedHashMap<>(peertubeInformation.getPlaylistPrivacies()); + //Populate privacies + String[] privaciesA = new String[privacies.size()]; + Iterator> it = privacies.entrySet().iterator(); + i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (translations == null || translations.size() == 0 || !translations.containsKey(pair.getValue())) + privaciesA[i] = pair.getValue(); + else + privaciesA[i] = translations.get(pair.getValue()); + it.remove(); + i++; + } + + ArrayAdapter adapterPrivacies = new ArrayAdapter<>(AllPlaylistsActivity.this, + android.R.layout.simple_spinner_dropdown_item, privaciesA); + bindingDialog.setUploadPrivacy.setAdapter(adapterPrivacies); + + if (playlistToEdit != null) { + Item privacy = playlistToEdit.getPrivacy(); + if (privacy.getId() > 0) { + bindingDialog.setUploadPrivacy.setSelection(privacy.getId() - 1); + } + } else { + bindingDialog.setUploadPrivacy.setSelection(2); + } + + //Manage privacies + bindingDialog.setUploadPrivacy.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + LinkedHashMap privaciesCheck = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + Iterator> it = privaciesCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position) { + privacyToSend = new HashMap<>(); + privacyToSend.put(pair.getKey(), pair.getValue()); + break; + } + it.remove(); + i++; + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + if (playlistToEdit != null) { + Item privacy = playlistToEdit.getPrivacy(); + + if (privacy.getId() > 0) { + bindingDialog.setUploadPrivacy.setSelection(privacy.getId() - 1); + } + } + + //Manage languages + bindingDialog.setUploadChannel.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + idChannel = channelId[position]; + if (position > 0) { + selectedChannel = myChannels.get(position - 1); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + if (playlistToEdit != null) { + int position = 0; + int k = 1; + for (ChannelData.Channel ac : myChannels) { + if (playlistToEdit.getVideoChannel() != null && playlistToEdit.getVideoChannel().getId() != null && ac.getId().compareTo(playlistToEdit.getVideoChannel().getId()) == 0) { + position = k; + break; + } + k++; + } + bindingDialog.setUploadChannel.setSelection(position); + } + } + + @Override + public void onAllPlaylistRemoved() { + binding.noAction.setVisibility(View.VISIBLE); + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/InstancePickerActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/InstancePickerActivity.java new file mode 100644 index 000000000..26348032f --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/InstancePickerActivity.java @@ -0,0 +1,275 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + + +import static app.fedilab.android.peertube.helper.Helper.peertubeInformation; + +import android.os.Bundle; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.APIResponse; +import app.fedilab.android.peertube.client.data.InstanceData; +import app.fedilab.android.peertube.client.entities.InstanceParams; +import app.fedilab.android.peertube.databinding.ActivityInstancePickerBinding; +import app.fedilab.android.peertube.drawer.InstanceAdapter; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.RoundedBackgroundSpan; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.viewmodel.InstancesVM; +import es.dmoral.toasty.Toasty; + + +public class InstancePickerActivity extends BaseActivity { + + + boolean[] checkedItemsCategory; + int[] itemsKeyCategory; + String[] itemsLabelCategory; + boolean[] checkedItemsLanguage; + String[] itemsKeyLanguage; + String[] itemsLabelLanguage; + InstanceParams instanceParams; + private InstancesVM viewModel; + private ActivityInstancePickerBinding binding; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + binding = ActivityInstancePickerBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + binding.loader.setVisibility(View.VISIBLE); + + + String[] channelSensitive = new String[]{"do_not_list", "blur", "display", "no_opinion"}; + String[] channelSensitivesLabel = new String[]{getString(R.string.do_not_list), getString(R.string.blur), getString(R.string.display), getString(R.string.no_opinion)}; + ArrayAdapter adapterChannel = new ArrayAdapter<>(InstancePickerActivity.this, + android.R.layout.simple_spinner_dropdown_item, channelSensitivesLabel); + binding.sensitive.setAdapter(adapterChannel); + + + viewModel = new ViewModelProvider(InstancePickerActivity.this).get(InstancesVM.class); + + binding.sensitive.setSelection(1, false); + binding.sensitive.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + instanceParams.setNsfwPolicy(channelSensitive[position]); + binding.loader.setVisibility(View.VISIBLE); + viewModel.getInstances(instanceParams).observe(InstancePickerActivity.this, apiResponse -> manageVIewInstance(apiResponse)); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + if (peertubeInformation != null && peertubeInformation.getLanguages() != null) { + LinkedHashMap languages = new LinkedHashMap<>(peertubeInformation.getLanguages()); + checkedItemsLanguage = new boolean[languages.size()]; + itemsLabelLanguage = new String[languages.size()]; + itemsKeyLanguage = new String[languages.size()]; + + binding.pickupLanguages.setOnClickListener(v -> { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(InstancePickerActivity.this); + + int i = 0; + if (languages.size() > 0) { + Iterator> it = languages.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = it.next(); + itemsLabelLanguage[i] = pair.getValue(); + checkedItemsLanguage[i] = false; + itemsKeyLanguage[i] = pair.getKey(); + it.remove(); + i++; + } + } + + dialogBuilder.setMultiChoiceItems(itemsLabelLanguage, checkedItemsLanguage, (dialog, which, isChecked) -> { + // The user checked or unchecked a box + checkedItemsLanguage[which] = isChecked; + }); + + dialogBuilder.setOnDismissListener(dialogInterface -> { + + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); + String between = ""; + stringBuilder.append(between); + List langs = new ArrayList<>(); + int j = 0; + for (boolean itemcheked : checkedItemsLanguage) { + if (itemcheked) { + langs.add(itemsKeyLanguage[j]); + String lang = itemsLabelLanguage[j]; + if (lang != null && lang.trim().toLowerCase().compareTo("null") != 0) { + if (between.length() == 0) between = " "; + String tag = " " + lang + " "; + stringBuilder.append(tag); + stringBuilder.setSpan(new RoundedBackgroundSpan(InstancePickerActivity.this), stringBuilder.length() - tag.length(), stringBuilder.length() - tag.length() + tag.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + stringBuilder.append(" "); + } + } + j++; + } + instanceParams.setLanguagesOr(langs); + binding.languagesView.setText(stringBuilder, TextView.BufferType.SPANNABLE); + if (binding.languagesView.getText().toString().trim().length() > 0) { + binding.languagesView.setVisibility(View.VISIBLE); + } else { + binding.languagesView.setVisibility(View.GONE); + } + binding.loader.setVisibility(View.VISIBLE); + viewModel.getInstances(instanceParams).observe(InstancePickerActivity.this, this::manageVIewInstance); + }); + dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> dialog.dismiss()); + + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.setTitle(getString(R.string.pickup_languages)); + alertDialog.show(); + }); + } + + if (peertubeInformation != null && peertubeInformation.getCategories() != null) { + LinkedHashMap categories = new LinkedHashMap<>(peertubeInformation.getCategories()); + checkedItemsCategory = new boolean[categories.size()]; + itemsLabelCategory = new String[categories.size()]; + itemsKeyCategory = new int[categories.size()]; + + + binding.pickupCategories.setOnClickListener(v -> { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(InstancePickerActivity.this); + int i = 0; + if (categories.size() > 0) { + Iterator> it = categories.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = it.next(); + itemsLabelCategory[i] = pair.getValue(); + itemsKeyCategory[i] = pair.getKey(); + checkedItemsCategory[i] = false; + it.remove(); + i++; + } + } + + dialogBuilder.setMultiChoiceItems(itemsLabelCategory, checkedItemsCategory, (dialog, which, isChecked) -> { + // The user checked or unchecked a box + checkedItemsCategory[which] = isChecked; + }); + + dialogBuilder.setOnDismissListener(dialogInterface -> { + int j = 0; + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); + String between = ""; + stringBuilder.append(between); + List cats = new ArrayList<>(); + for (boolean itemcheked : checkedItemsCategory) { + if (itemcheked) { + cats.add(itemsKeyCategory[j]); + String cat = itemsLabelCategory[j]; + if (cat != null && cat.trim().toLowerCase().compareTo("null") != 0) { + if (between.length() == 0) between = " "; + String tag = " " + cat + " "; + stringBuilder.append(tag); + stringBuilder.setSpan(new RoundedBackgroundSpan(InstancePickerActivity.this), stringBuilder.length() - tag.length(), stringBuilder.length() - tag.length() + tag.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + stringBuilder.append(" "); + } + } + j++; + } + instanceParams.setCategoriesOr(cats); + binding.categoriesView.setText(stringBuilder, TextView.BufferType.SPANNABLE); + if (binding.categoriesView.getText().toString().trim().length() > 0) { + binding.categoriesView.setVisibility(View.VISIBLE); + } else { + binding.categoriesView.setVisibility(View.GONE); + } + binding.loader.setVisibility(View.VISIBLE); + viewModel.getInstances(instanceParams).observe(InstancePickerActivity.this, this::manageVIewInstance); + }); + dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> dialog.dismiss()); + + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.setTitle(getString(R.string.pickup_categories)); + alertDialog.show(); + }); + } + + + binding.loader.setVisibility(View.VISIBLE); + + setTitle(R.string.instances_picker); + + instanceParams = new InstanceParams(); + instanceParams.setNsfwPolicy(channelSensitive[1]); + viewModel.getInstances(instanceParams).observe(InstancePickerActivity.this, this::manageVIewInstance); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + + public void manageVIewInstance(APIResponse apiResponse) { + binding.loader.setVisibility(View.GONE); + if (apiResponse.getError() != null) { + Toasty.error(InstancePickerActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + return; + } + List instances = apiResponse.getInstances(); + RecyclerView lv_instances = findViewById(R.id.lv_instances); + if ((instances == null || instances.size() == 0)) { + binding.noAction.setVisibility(View.VISIBLE); + lv_instances.setVisibility(View.GONE); + } else { + binding.noAction.setVisibility(View.GONE); + lv_instances.setVisibility(View.VISIBLE); + InstanceAdapter instanceAdapter = new InstanceAdapter(instances); + lv_instances.setAdapter(instanceAdapter); + lv_instances.setLayoutManager(new LinearLayoutManager(InstancePickerActivity.this)); + } + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/LocalPlaylistsActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/LocalPlaylistsActivity.java new file mode 100644 index 000000000..864c17035 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/LocalPlaylistsActivity.java @@ -0,0 +1,82 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import android.os.Bundle; +import android.view.MenuItem; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.FragmentTransaction; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.data.PlaylistData; +import app.fedilab.android.peertube.fragment.DisplayVideosFragment; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.viewmodel.TimelineVM; +import es.dmoral.toasty.Toasty; + + +public class LocalPlaylistsActivity extends AppCompatActivity { + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + + setContentView(R.layout.activity_playlists); + + + PlaylistData.Playlist playlist; + Bundle b = getIntent().getExtras(); + if (b != null) { + playlist = b.getParcelable("playlist"); + if (playlist == null) { + return; + } + } else { + Toasty.error(LocalPlaylistsActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + return; + } + setTitle(playlist.getDisplayName()); + if (savedInstanceState == null) { + DisplayVideosFragment displayVideosFragment = new DisplayVideosFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, TimelineVM.TimelineType.VIDEOS_IN_LOCAL_PLAYLIST); + bundle.putSerializable("playlistId", playlist.getUuid()); + displayVideosFragment.setArguments(bundle); + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.add(R.id.nav_host_fragment, displayVideosFragment).commit(); + } + + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/LoginActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/LoginActivity.java new file mode 100644 index 000000000..982cc976c --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/LoginActivity.java @@ -0,0 +1,418 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.updateCredential; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.UnderlineSpan; +import android.util.Patterns; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import android.widget.Toast; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +import app.fedilab.android.peertube.BuildConfig; +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.data.InstanceData; +import app.fedilab.android.peertube.client.data.PluginData; +import app.fedilab.android.peertube.client.entities.AcadInstances; +import app.fedilab.android.peertube.client.entities.Oauth; +import app.fedilab.android.peertube.client.entities.OauthParams; +import app.fedilab.android.peertube.client.entities.Token; +import app.fedilab.android.peertube.client.entities.WellKnownNodeinfo; +import app.fedilab.android.peertube.client.mastodon.RetrofitMastodonAPI; +import app.fedilab.android.peertube.databinding.ActivityLoginBinding; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperAcadInstance; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import es.dmoral.toasty.Toasty; + + +public class LoginActivity extends BaseActivity { + + + private static String client_id; + private static String client_secret; + private ActivityLoginBinding binding; + private String acadInstance; + + + @SuppressLint("SetTextI18n") + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + binding = ActivityLoginBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + + SpannableString content_create; + content_create = new SpannableString(getString(R.string.join_peertube)); + + content_create.setSpan(new UnderlineSpan(), 0, content_create.length(), 0); + content_create.setSpan(new ForegroundColorSpan(Helper.fetchAccentColor(LoginActivity.this)), 0, content_create.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + binding.createAnAccountPeertube.setText(content_create, TextView.BufferType.SPANNABLE); + + binding.createAnAccountPeertube.setOnClickListener(v -> { + Intent mainActivity = new Intent(LoginActivity.this, PeertubeRegisterActivity.class); + Bundle b = new Bundle(); + mainActivity.putExtras(b); + startActivity(mainActivity); + }); + + + if (BuildConfig.full_instances && BuildConfig.instance_switcher) { + binding.loginInstanceContainer.setVisibility(View.VISIBLE); + } + + + if (Helper.isTablet(LoginActivity.this)) { + + ViewGroup.LayoutParams layoutParamsI = binding.loginInstanceContainer.getLayoutParams(); + layoutParamsI.width = (int) Helper.convertDpToPixel(300, LoginActivity.this); + binding.loginInstanceContainer.setLayoutParams(layoutParamsI); + + ViewGroup.LayoutParams layoutParamsU = binding.loginUidContainer.getLayoutParams(); + layoutParamsU.width = (int) Helper.convertDpToPixel(300, LoginActivity.this); + binding.loginUidContainer.setLayoutParams(layoutParamsU); + + ViewGroup.LayoutParams layoutParamsP = binding.loginPasswdContainer.getLayoutParams(); + layoutParamsP.width = (int) Helper.convertDpToPixel(300, LoginActivity.this); + binding.loginPasswdContainer.setLayoutParams(layoutParamsP); + } + + if (!BuildConfig.full_instances) { + + binding.loginUidContainer.setVisibility(View.GONE); + binding.loginPasswdContainer.setVisibility(View.GONE); + binding.loginInstanceContainer.setVisibility(View.GONE); + binding.createAnAccountPeertube.setVisibility(View.GONE); + binding.instancePickerTitle.setVisibility(View.VISIBLE); + binding.instancePicker.setVisibility(View.VISIBLE); + + + List acadInstances = AcadInstances.getInstances(); + String[] academiesKey = new String[acadInstances.size()]; + String[] academiesValue = new String[acadInstances.size()]; + String acad = HelperInstance.getLiveInstance(LoginActivity.this); + int position = 0; + int i = 0; + for (AcadInstances ac : acadInstances) { + academiesKey[i] = ac.getName(); + academiesValue[i] = ac.getUrl(); + if (ac.getUrl().compareTo(acad) == 0) { + position = i; + } + i++; + } + ArrayAdapter adapterChannel = new ArrayAdapter<>(LoginActivity.this, + android.R.layout.simple_spinner_dropdown_item, academiesKey); + binding.instancePicker.setAdapter(adapterChannel); + binding.instancePicker.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + acadInstance = academiesValue[position]; + binding.loginUidContainer.setVisibility(View.GONE); + binding.loginPasswdContainer.setVisibility(View.GONE); + binding.loginInstanceContainer.setVisibility(View.GONE); + binding.createAnAccountPeertube.setVisibility(View.GONE); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + binding.instancePicker.setSelection(position, true); + } + if (BuildConfig.allow_remote_connections) { + binding.loginInstance.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + if (binding.loginInstance.getText() != null) { + new Thread(() -> { + String testInstance = binding.loginInstance.getText().toString().trim(); + if (testInstance.length() == 0) { + return; + } + WellKnownNodeinfo.NodeInfo instanceNodeInfo = null; + if (BuildConfig.allow_remote_connections) { + instanceNodeInfo = new RetrofitPeertubeAPI(LoginActivity.this, testInstance, null).getNodeInfo(); + } + if (instanceNodeInfo != null && + (instanceNodeInfo.getSoftware().getName().toUpperCase().trim().compareTo("MASTODON") == 0 || + instanceNodeInfo.getSoftware().getName().toUpperCase().trim().compareTo("PLEROMA") == 0) + ) { + connectToFediverse(testInstance, instanceNodeInfo); + } + }).start(); + } + } + }); + } + + binding.loginButton.setOnClickListener(v -> { + if (!BuildConfig.full_instances && AcadInstances.isOpenId(acadInstance)) { + new Thread(() -> { + try { + InstanceData.InstanceConfig instanceConfig = new RetrofitPeertubeAPI(LoginActivity.this).getConfigInstance(); + PluginData.Plugin plugin = instanceConfig.getPlugin(); + List pluginInfos = plugin.getRegistered(); + String openIdVersion = "0.0.7"; + for (PluginData.PluginInfo pluginInfo : pluginInfos) { + if (pluginInfo.getName().toLowerCase().contains("openid")) { + openIdVersion = pluginInfo.getVersion(); + } + } + Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, acadInstance, null).oauthClient(null, null, null, null); + if (oauth == null) { + runOnUiThread(() -> { + binding.loginButton.setEnabled(true); + Toasty.error(LoginActivity.this, getString(R.string.client_error), Toast.LENGTH_LONG).show(); + }); + return; + } + client_id = oauth.getClient_id(); + client_secret = oauth.getClient_secret(); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.CLIENT_ID, client_id); + editor.putString(Helper.CLIENT_SECRET, client_secret); + editor.apply(); + Intent intent = new Intent(LoginActivity.this, WebviewConnectActivity.class); + Bundle b = new Bundle(); + b.putString("url", "https://" + acadInstance + "/plugins/auth-openid-connect/" + openIdVersion + "/auth/openid-connect"); + intent.putExtras(b); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + runOnUiThread(() -> { + binding.loginButton.setEnabled(true); + Toasty.error(LoginActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + }); + } + + }).start(); + } else { + if (binding.loginUid.getText() != null && binding.loginUid.getText().toString().contains("@") && !Patterns.EMAIL_ADDRESS.matcher(binding.loginUid.getText().toString().trim()).matches()) { + Toasty.error(LoginActivity.this, getString(R.string.email_error)).show(); + return; + } + binding.loginButton.setEnabled(false); + String instance; + if (!BuildConfig.full_instances) { + String[] emailArray = binding.loginUid.getText().toString().split("@"); + if (emailArray.length > 1 && !Arrays.asList(HelperAcadInstance.valideEmails).contains(emailArray[1])) { + Toasty.error(LoginActivity.this, getString(R.string.email_error_domain, emailArray[1])).show(); + binding.loginButton.setEnabled(true); + return; + } + + instance = HelperInstance.getLiveInstance(LoginActivity.this); + } else { + if (binding.loginInstance.getText() == null || binding.loginInstance.getText().toString().trim().length() == 0) { + Toasty.error(LoginActivity.this, getString(R.string.not_valide_instance)).show(); + binding.loginButton.setEnabled(true); + return; + } + instance = binding.loginInstance.getText().toString().trim().toLowerCase(); + } + + if (instance.startsWith("http")) { + try { + URL url = new URL(instance); + instance = url.getHost(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } else if (instance.endsWith("/")) { + try { + URL url = new URL("https://" + instance); + instance = url.getHost(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + if (!Patterns.WEB_URL.matcher("https://" + instance).matches()) { + Toasty.error(LoginActivity.this, getString(R.string.not_valide_instance)).show(); + binding.loginButton.setEnabled(true); + return; + } + String finalInstance = instance; + new Thread(() -> { + WellKnownNodeinfo.NodeInfo instanceNodeInfo = null; + if (BuildConfig.allow_remote_connections) { + instanceNodeInfo = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).getNodeInfo(); + } + connectToFediverse(finalInstance, instanceNodeInfo); + }).start(); + } + }); + } + + /** + * Oauth process for Peertube + * + * @param finalInstance String + */ + private void connectToFediverse(String finalInstance, WellKnownNodeinfo.NodeInfo instanceNodeInfo) { + Oauth oauth = null; + String software; + if (instanceNodeInfo != null) { + software = instanceNodeInfo.getSoftware().getName().toUpperCase().trim(); + switch (software) { + case "MASTODON": + case "PLEROMA": + oauth = new RetrofitMastodonAPI(LoginActivity.this, finalInstance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.REDIRECT_CONTENT_WEB, Helper.OAUTH_SCOPES_MASTODON, Helper.WEBSITE_VALUE); + break; + + case "FRIENDICA": + + break; + + default: + oauth = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.WEBSITE_VALUE, Helper.OAUTH_SCOPES_PEERTUBE, Helper.WEBSITE_VALUE); + } + } else { + oauth = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.WEBSITE_VALUE, Helper.OAUTH_SCOPES_PEERTUBE, Helper.WEBSITE_VALUE); + software = "PEERTUBE"; + } + if (oauth == null) { + runOnUiThread(() -> { + binding.loginButton.setEnabled(true); + Toasty.error(LoginActivity.this, getString(R.string.client_error), Toast.LENGTH_LONG).show(); + }); + return; + } + client_id = oauth.getClient_id(); + client_secret = oauth.getClient_secret(); + + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.CLIENT_ID, client_id); + editor.putString(Helper.CLIENT_SECRET, client_secret); + editor.apply(); + OauthParams oauthParams = new OauthParams(); + oauthParams.setClient_id(client_id); + oauthParams.setClient_secret(client_secret); + oauthParams.setGrant_type("password"); + final boolean isMastodonAPI = software.compareTo("MASTODON") == 0 || software.compareTo("PLEROMA") == 0; + if (software.compareTo("PEERTUBE") == 0) { + oauthParams.setScope("user"); + } else if (isMastodonAPI) { + oauthParams.setScope("read write follow"); + } + if (binding.loginUid.getText() != null) { + oauthParams.setUsername(binding.loginUid.getText().toString().trim()); + } + if (binding.loginPasswd.getText() != null) { + oauthParams.setPassword(binding.loginPasswd.getText().toString()); + } + try { + Token token = null; + if (software.compareTo("PEERTUBE") == 0) { + token = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams); + } else if (isMastodonAPI) { + Intent i = new Intent(LoginActivity.this, MastodonWebviewConnectActivity.class); + i.putExtra("software", software); + i.putExtra("instance", finalInstance); + i.putExtra("client_id", client_id); + i.putExtra("client_secret", client_secret); + startActivity(i); + return; + } + proceedLogin(token, finalInstance, software.compareTo("PEERTUBE") == 0 ? null : software); + } catch (final Exception | Error e) { + oauthParams.setUsername(binding.loginUid.getText().toString().toLowerCase().trim()); + try { + if (software.compareTo("PEERTUBE") == 0) { + Token token = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams); + proceedLogin(token, finalInstance, software.compareTo("PEERTUBE") == 0 ? null : software); + } + } catch (Error error) { + Error.displayError(LoginActivity.this, error); + error.printStackTrace(); + runOnUiThread(() -> binding.loginButton.setEnabled(true)); + } + } + } + + + @SuppressLint("ApplySharedPref") + private void proceedLogin(Token token, String host, String software) { + runOnUiThread(() -> { + if (token != null) { + boolean remote_account = software != null && software.toUpperCase().trim().compareTo("PEERTUBE") != 0; + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token.getAccess_token()); + editor.putString(Helper.PREF_SOFTWARE, remote_account ? software : null); + editor.putString(Helper.PREF_REMOTE_INSTANCE, remote_account ? host : null); + if (!remote_account) { + editor.putString(Helper.PREF_INSTANCE, host); + } + editor.commit(); + //Update the account with the token; + updateCredential(LoginActivity.this, token.getAccess_token(), client_id, client_secret, token.getRefresh_token(), host, software); + } else { + binding.loginButton.setEnabled(true); + } + }); + } + + @Override + protected void onResume() { + super.onResume(); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/MainActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/MainActivity.java new file mode 100644 index 000000000..81a711510 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/MainActivity.java @@ -0,0 +1,815 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.helper.Helper.peertubeInformation; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.SearchView; +import androidx.appcompat.widget.Toolbar; +import androidx.appcompat.widget.TooltipCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.bottomnavigation.BottomNavigationView; +import com.kobakei.ratethisapp.RateThisApp; + +import org.jetbrains.annotations.NotNull; + +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import app.fedilab.android.R; +import app.fedilab.android.databinding.ActivityMainPeertubeBinding; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.data.AccountData.Account; +import app.fedilab.android.peertube.client.data.InstanceData; +import app.fedilab.android.peertube.client.entities.AcadInstances; +import app.fedilab.android.peertube.client.entities.OauthParams; +import app.fedilab.android.peertube.client.entities.PeertubeInformation; +import app.fedilab.android.peertube.client.entities.Token; +import app.fedilab.android.peertube.client.entities.UserMe; +import app.fedilab.android.peertube.client.entities.UserSettings; +import app.fedilab.android.peertube.client.entities.WellKnownNodeinfo; +import app.fedilab.android.peertube.fragment.DisplayOverviewFragment; +import app.fedilab.android.peertube.fragment.DisplayVideosFragment; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperAcadInstance; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.PlaylistExportHelper; +import app.fedilab.android.peertube.helper.SwitchAccountHelper; +import app.fedilab.android.peertube.services.RetrieveInfoService; +import app.fedilab.android.peertube.sqlite.AccountDAO; +import app.fedilab.android.peertube.sqlite.Sqlite; +import app.fedilab.android.peertube.sqlite.StoredInstanceDAO; +import app.fedilab.android.peertube.viewmodel.TimelineVM; +import es.dmoral.toasty.Toasty; + + +public class MainActivity extends app.fedilab.android.activities.MainActivity { + + + public static int PICK_INSTANCE = 5641; + public static int PICK_INSTANCE_SURF = 5642; + public static UserMe userMe; + public static InstanceData.InstanceConfig instanceConfig; + public static TypeOfConnection typeOfConnection; + public static int badgeCount; + private DisplayVideosFragment recentFragment, locaFragment, trendingFragment, subscriptionFragment, mostLikedFragment; + private DisplayOverviewFragment overviewFragment; + private ActivityMainPeertubeBinding binding; + private final BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener + = item -> { + int itemId = item.getItemId(); + String type = null; + if (itemId == R.id.navigation_discover) { + setTitleCustom(R.string.title_discover); + binding.viewpager.setCurrentItem(3); + type = HelperAcadInstance.DISCOVER; + } else if (itemId == R.id.navigation_subscription) { + binding.viewpager.setCurrentItem(4); + setTitleCustom(R.string.subscriptions); + type = HelperAcadInstance.SUBSCRIPTIONS; + } else if (itemId == R.id.navigation_trending) { + setTitleCustom(R.string.title_trending); + binding.viewpager.setCurrentItem(2); + type = HelperAcadInstance.TRENDING; + } else if (itemId == R.id.navigation_recently_added) { + setTitleCustom(R.string.title_recently_added); + binding.viewpager.setCurrentItem(1); + type = HelperAcadInstance.RECENTLY_ADDED; + } else if (itemId == R.id.navigation_local) { + setTitleCustom(R.string.title_local); + binding.viewpager.setCurrentItem(0); + type = HelperAcadInstance.LOCAL; + } + return true; + }; + + + @SuppressLint("ApplySharedPref") + public static void showRadioButtonDialogFullInstances(Activity activity, boolean storeInDb) { + final SharedPreferences sharedpreferences = activity.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + AlertDialog.Builder alt_bld = new AlertDialog.Builder(activity); + alt_bld.setTitle(R.string.instance_choice); + String instance = HelperInstance.getLiveInstance(activity); + final EditText input = new EditText(activity); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT); + input.setLayoutParams(lp); + alt_bld.setView(input); + input.setText(instance); + alt_bld.setPositiveButton(R.string.validate, + (dialog, which) -> new Thread(() -> { + try { + String newInstance = input.getText().toString().trim(); + if (!newInstance.startsWith("http")) { + newInstance = "http://" + newInstance; + } + URL url = new URL(newInstance); + newInstance = url.getHost(); + + WellKnownNodeinfo.NodeInfo instanceNodeInfo = new RetrofitPeertubeAPI(activity, newInstance, null).getNodeInfo(); + if (instanceNodeInfo.getSoftware() != null && instanceNodeInfo.getSoftware().getName().trim().toLowerCase().compareTo("peertube") == 0) { + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_INSTANCE, newInstance); + editor.commit(); + if (storeInDb) { + newInstance = newInstance.trim().toLowerCase(); + InstanceData.AboutInstance aboutInstance = new RetrofitPeertubeAPI(activity, newInstance, null).getAboutInstance(); + SQLiteDatabase db = Sqlite.getInstance(activity.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + new StoredInstanceDAO(activity, db).insertInstance(aboutInstance, newInstance); + activity.runOnUiThread(() -> { + dialog.dismiss(); + Helper.logoutNoRemoval(activity); + }); + } else { + activity.runOnUiThread(() -> { + dialog.dismiss(); + Intent intent = new Intent(activity, MainActivity.class); + activity.startActivity(intent); + }); + } + } else { + activity.runOnUiThread(() -> Toasty.error(activity, activity.getString(R.string.not_valide_instance), Toast.LENGTH_LONG).show()); + } + } catch (Exception e) { + e.printStackTrace(); + } + + }).start()); + alt_bld.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + alt_bld.setNeutralButton(R.string.help, (dialog, which) -> { + Intent intent = new Intent(activity, InstancePickerActivity.class); + if (storeInDb) { + activity.startActivityForResult(intent, PICK_INSTANCE_SURF); + } else { + activity.startActivityForResult(intent, PICK_INSTANCE); + } + }); + AlertDialog alert = alt_bld.create(); + alert.show(); + } + + private void setTitleCustom(int titleRId) { + Toolbar toolbar = findViewById(R.id.toolbar); + TextView mTitle = toolbar.findViewById(R.id.toolbar_title); + if (mTitle != null) { + mTitle.setText(getString(titleRId)); + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + binding = super.binding; + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + typeOfConnection = TypeOfConnection.UNKNOWN; + badgeCount = 0; + + binding.navView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener); + + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayShowTitleEnabled(false); + } + checkIfConnectedUsers(); + + recentFragment = new DisplayVideosFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, TimelineVM.TimelineType.RECENT); + recentFragment.setArguments(bundle); + + locaFragment = new DisplayVideosFragment(); + bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, TimelineVM.TimelineType.LOCAL); + locaFragment.setArguments(bundle); + + trendingFragment = new DisplayVideosFragment(); + bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, TimelineVM.TimelineType.TRENDING); + trendingFragment.setArguments(bundle); + + subscriptionFragment = new DisplayVideosFragment(); + bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, TimelineVM.TimelineType.SUBSCRIBTIONS); + subscriptionFragment.setArguments(bundle); + + mostLikedFragment = new DisplayVideosFragment(); + bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, TimelineVM.TimelineType.MOST_LIKED); + mostLikedFragment.setArguments(bundle); + + overviewFragment = new DisplayOverviewFragment(); + if (!Helper.isLoggedIn(MainActivity.this)) { + PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); + binding.viewpager.setAdapter(mPagerAdapter); + } else { + new Thread(() -> { + badgeCount = new RetrofitPeertubeAPI(MainActivity.this).unreadNotifications(); + invalidateOptionsMenu(); + }).start(); + } + if (Helper.isLoggedIn(MainActivity.this)) { + binding.viewpager.setOffscreenPageLimit(5); + } else { + binding.viewpager.setOffscreenPageLimit(4); + } + + + binding.viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + MenuItem item = binding.navView.getMenu().getItem(position); + binding.navView.setSelectedItemId(item.getItemId()); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + + + toolbar.setOnClickListener(v -> { + if (binding.viewpager.getAdapter() == null) { + return; + } + if (binding.viewpager.getAdapter().instantiateItem(binding.viewpager, binding.viewpager.getCurrentItem()) instanceof DisplayVideosFragment) { + ((DisplayVideosFragment) binding.viewpager.getAdapter().instantiateItem(binding.viewpager, binding.viewpager.getCurrentItem())).scrollToTop(); + } else if (binding.viewpager.getAdapter().instantiateItem(binding.viewpager, binding.viewpager.getCurrentItem()) instanceof DisplayOverviewFragment) { + ((DisplayOverviewFragment) binding.viewpager.getAdapter().instantiateItem(binding.viewpager, binding.viewpager.getCurrentItem())).scrollToTop(); + } + }); + + setTitleCustom(R.string.title_discover); + + if (Helper.isLoggedIn(MainActivity.this)) { + binding.navView.inflateMenu(R.menu.bottom_nav_menu_connected_peertube); + refreshToken(); + + } else { + binding.navView.inflateMenu(R.menu.bottom_nav_menu); + } + peertubeInformation = new PeertubeInformation(); + peertubeInformation.setCategories(new LinkedHashMap<>()); + peertubeInformation.setLanguages(new LinkedHashMap<>()); + peertubeInformation.setLicences(new LinkedHashMap<>()); + peertubeInformation.setPrivacies(new LinkedHashMap<>()); + peertubeInformation.setPlaylistPrivacies(new LinkedHashMap<>()); + peertubeInformation.setTranslations(new LinkedHashMap<>()); + startInForeground(); + + if (BuildConfig.google_restriction && BuildConfig.full_instances) { + RateThisApp.onCreate(this); + RateThisApp.showRateDialogIfNeeded(this); + } + if (!BuildConfig.full_instances) { + PlaylistExportHelper.manageIntentUrl(MainActivity.this, getIntent()); + } + + + final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + + + int search_cast = sharedpreferences.getInt(getString(R.string.set_cast_choice), BuildConfig.cast_enabled); + if (search_cast == 1) { + super.discoverCast(); + } + //Instance + if (HelperInstance.getLiveInstance(MainActivity.this) == null) { + Intent intent = new Intent(MainActivity.this, InstancePickerActivity.class); + startActivityForResult(intent, PICK_INSTANCE); + } + } + + public DisplayVideosFragment getSubscriptionFragment() { + return subscriptionFragment; + } + + private void startInForeground() { + Intent notificationIntent = new Intent(this, RetrieveInfoService.class); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(notificationIntent); + } else { + startService(notificationIntent); + } + } + + @Override + public void onResume() { + super.onResume(); + invalidateOptionsMenu(); + } + + private void refreshToken() { + new Thread(() -> { + final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String tokenStr = Helper.getToken(MainActivity.this); + String instance = HelperInstance.getLiveInstance(MainActivity.this); + SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + String instanceShar = sharedpreferences.getString(Helper.PREF_INSTANCE, null); + String userIdShar = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + Account account = new AccountDAO(MainActivity.this, db).getAccountByToken(tokenStr); + if (account == null) { + account = new AccountDAO(MainActivity.this, db).getAccountByIdInstance(userIdShar, instanceShar); + } + if (account != null) { + Account finalAccount = account; + OauthParams oauthParams = new OauthParams(); + oauthParams.setGrant_type("refresh_token"); + oauthParams.setClient_id(account.getClient_id()); + oauthParams.setClient_secret(account.getClient_secret()); + oauthParams.setRefresh_token(account.getRefresh_token()); + oauthParams.setAccess_token(account.getToken()); + try { + Token token = new RetrofitPeertubeAPI(MainActivity.this).manageToken(oauthParams); + if (token == null) { + return; + } + runOnUiThread(() -> { + //To avoid a token issue with subscriptions, adding fragment is done when the token is refreshed. + new Handler().post(() -> { + if (Helper.isLoggedIn(MainActivity.this)) { + PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); + binding.viewpager.setAdapter(mPagerAdapter); + } + }); + }); + + userMe = new RetrofitPeertubeAPI(MainActivity.this, instance, token.getAccess_token()).verifyCredentials(); + if (userMe != null && userMe.getAccount() != null) { + new AccountDAO(MainActivity.this, db).updateAccount(userMe.getAccount()); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_KEY_ID, account.getId()); + editor.putString(Helper.PREF_KEY_NAME, account.getUsername()); + editor.putBoolean(getString(R.string.set_autoplay_choice), userMe.isAutoPlayVideo()); + editor.putBoolean(getString(R.string.set_store_in_history), userMe.isVideosHistoryEnabled()); + editor.putBoolean(getString(R.string.set_autoplay_next_video_choice), userMe.isAutoPlayNextVideo()); + editor.putString(getString(R.string.set_video_sensitive_choice), userMe.getNsfwPolicy()); + //Sync languages from server + List videoLanguageServer = userMe.getVideoLanguages(); + if (videoLanguageServer != null) { + Set videoLanguageServerSet = new TreeSet<>(videoLanguageServer); + videoLanguageServerSet.addAll(videoLanguageServer); + Set videoLanguageLocal = sharedpreferences.getStringSet(getString(R.string.set_video_language_choice), null); + if (videoLanguageServerSet.size() > 0 && videoLanguageLocal != null) { + videoLanguageServer.addAll(videoLanguageLocal); + } + editor.putStringSet(getString(R.string.set_video_language_choice), videoLanguageServerSet); + editor.apply(); + } + } + instanceConfig = new RetrofitPeertubeAPI(MainActivity.this).getConfigInstance(); + } catch (Error error) { + runOnUiThread(() -> { + AlertDialog.Builder alt_bld = new AlertDialog.Builder(this); + alt_bld.setTitle(R.string.refresh_token_failed); + alt_bld.setMessage(R.string.refresh_token_failed_message); + alt_bld.setNegativeButton(R.string.action_logout, (dialog, id) -> { + dialog.dismiss(); + Helper.logoutCurrentUser(MainActivity.this, finalAccount); + }); + alt_bld.setPositiveButton(R.string._retry, (dialog, id) -> { + dialog.dismiss(); + refreshToken(); + }); + AlertDialog alert = alt_bld.create(); + alert.show(); + + }); + error.printStackTrace(); + } + } + }).start(); + } + + @Override + public boolean onCreateOptionsMenu(@NotNull Menu menu) { + getMenuInflater().inflate(R.menu.main_menu, menu); + + MenuItem myActionMenuItem = menu.findItem(R.id.action_search); + SearchView searchView = (SearchView) myActionMenuItem.getActionView(); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + Pattern link = Pattern.compile("(https?://[\\da-z.-]+\\.[a-z.]{2,10})/videos/watch/(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12})(\\?start=(\\d+[hH])?(\\d+[mM])?(\\d+[sS])?)?$"); + Matcher matcherLink = link.matcher(query.trim()); + if (matcherLink.find()) { + Intent intent = new Intent(MainActivity.this, PeertubeActivity.class); + intent.setData(Uri.parse(query.trim())); + startActivity(intent); + myActionMenuItem.collapseActionView(); + return false; + } + Intent intent = new Intent(MainActivity.this, SearchActivity.class); + Bundle b = new Bundle(); + String search = query.trim(); + b.putString("search", search); + intent.putExtras(b); + startActivity(intent); + if (!searchView.isIconified()) { + searchView.setIconified(true); + } + myActionMenuItem.collapseActionView(); + return false; + } + + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + + MenuItem uploadItem = menu.findItem(R.id.action_upload); + MenuItem myVideosItem = menu.findItem(R.id.action_myvideos); + MenuItem playslistItem = menu.findItem(R.id.action_playlist); + MenuItem historyItem = menu.findItem(R.id.action_history); + MenuItem mostLikedItem = menu.findItem(R.id.action_most_liked); + MenuItem settingsItem = menu.findItem(R.id.action_settings); + MenuItem sepiaSearchItem = menu.findItem(R.id.action_sepia_search); + MenuItem incognitoItem = menu.findItem(R.id.action_incognito); + MenuItem accountItem = menu.findItem(R.id.action_account); + MenuItem donateItem = menu.findItem(R.id.action_donate); + MenuItem changeInstanceItem = menu.findItem(R.id.action_change_instance); + + FrameLayout rootView = (FrameLayout) accountItem.getActionView(); + + FrameLayout redCircle = rootView.findViewById(R.id.view_alert_red_circle); + TextView countTextView = rootView.findViewById(R.id.view_alert_count_textview); + //change counter for notifications + if (badgeCount > 0) { + countTextView.setText(String.valueOf(badgeCount)); + redCircle.setVisibility(View.VISIBLE); + } else { + redCircle.setVisibility(View.GONE); + } + TooltipCompat.setTooltipText(accountItem.getActionView(), getText(R.string.account)); + if (BuildConfig.FLAVOR.compareTo("google_full") == 0) { + donateItem.setVisible(true); + } + + if (!BuildConfig.instance_switcher) { + changeInstanceItem.setVisible(false); + } + switch (typeOfConnection) { + case UNKNOWN: + accountItem.setVisible(false); + uploadItem.setVisible(false); + myVideosItem.setVisible(false); + playslistItem.setVisible(false); + historyItem.setVisible(false); + settingsItem.setVisible(false); + mostLikedItem.setVisible(false); + incognitoItem.setVisible(false); + break; + case REMOTE_ACCOUNT: + case NORMAL: + accountItem.setVisible(true); + if (Helper.isLoggedIn(MainActivity.this)) { + if (!BuildConfig.full_instances) { + changeInstanceItem.setVisible(false); + } + uploadItem.setVisible(true); + myVideosItem.setVisible(true); + playslistItem.setVisible(true); + historyItem.setVisible(true); + settingsItem.setVisible(false); + mostLikedItem.setVisible(true); + incognitoItem.setVisible(true); + final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + boolean checked = sharedpreferences.getBoolean(getString(R.string.set_store_in_history), true); + incognitoItem.setChecked(checked); + } else { + uploadItem.setVisible(false); + myVideosItem.setVisible(false); + playslistItem.setVisible(!BuildConfig.full_instances); + historyItem.setVisible(false); + settingsItem.setVisible(true); + mostLikedItem.setVisible(true); + incognitoItem.setVisible(false); + } + break; + case SURFING: + accountItem.setVisible(true); + uploadItem.setVisible(false); + myVideosItem.setVisible(false); + playslistItem.setVisible(false); + historyItem.setVisible(false); + settingsItem.setVisible(false); + mostLikedItem.setVisible(false); + incognitoItem.setVisible(false); + break; + } + + + if (!BuildConfig.sepia_search) { + sepiaSearchItem.setVisible(false); + } + return true; + } + + + private void checkIfConnectedUsers() { + new Thread(() -> { + try { + typeOfConnection = TypeOfConnection.NORMAL; + if (!Helper.canMakeAction(MainActivity.this)) { + SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + List accounts = new AccountDAO(MainActivity.this, db).getAllAccount(); + if (accounts != null && accounts.size() > 0) { + //The user is not authenticated and there accounts in db. That means the user is surfing some other instances + typeOfConnection = TypeOfConnection.SURFING; + } + } + runOnUiThread(this::invalidateOptionsMenu); + + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + final MenuItem accountItem = menu.findItem(R.id.action_account); + FrameLayout rootView = (FrameLayout) accountItem.getActionView(); + rootView.setOnClickListener(v -> onOptionsItemSelected(accountItem)); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + String type = null; + String action = "TIMELINE"; + if (item.getItemId() == R.id.action_change_instance) { + if (BuildConfig.full_instances) { + Intent intent = new Intent(MainActivity.this, ManageInstancesActivity.class); + startActivity(intent); + overridePendingTransition(R.anim.slide_in_up, R.anim.slide_out_up); + } else { + showRadioButtonDialog(); + } + action = "CHANGE_INSTANCE"; + type = ""; + } else if (item.getItemId() == R.id.action_settings) { + Intent intent = new Intent(MainActivity.this, SettingsActivity.class); + startActivity(intent); + } else if (item.getItemId() == R.id.action_account) { + Intent intent; + if (typeOfConnection == TypeOfConnection.SURFING) { + SwitchAccountHelper.switchDialog(MainActivity.this, false); + } else { + if (Helper.canMakeAction(MainActivity.this)) { + intent = new Intent(MainActivity.this, AccountActivity.class); + startActivity(intent); + overridePendingTransition(R.anim.slide_in_up, R.anim.slide_out_up); + } else { + intent = new Intent(MainActivity.this, LoginActivity.class); + startActivity(intent); + } + + } + } else if (item.getItemId() == R.id.action_upload) { + Intent intent = new Intent(MainActivity.this, PeertubeUploadActivity.class); + startActivity(intent); + } else if (item.getItemId() == R.id.action_myvideos) { + Intent intent = new Intent(MainActivity.this, VideosTimelineActivity.class); + Bundle bundle = new Bundle(); + bundle.putSerializable("type", TimelineVM.TimelineType.MY_VIDEOS); + intent.putExtras(bundle); + startActivity(intent); + type = HelperAcadInstance.MYVIDEOS; + } else if (item.getItemId() == R.id.action_history) { + Intent intent = new Intent(MainActivity.this, VideosTimelineActivity.class); + Bundle bundle = new Bundle(); + bundle.putSerializable("type", TimelineVM.TimelineType.HISTORY); + intent.putExtras(bundle); + startActivity(intent); + type = HelperAcadInstance.HISTORY; + } else if (item.getItemId() == R.id.action_most_liked) { + Intent intent = new Intent(MainActivity.this, VideosTimelineActivity.class); + Bundle bundle = new Bundle(); + bundle.putSerializable("type", TimelineVM.TimelineType.MOST_LIKED); + intent.putExtras(bundle); + startActivity(intent); + type = HelperAcadInstance.MOSTLIKED; + } else if (item.getItemId() == R.id.action_playlist) { + Intent intent; + if (Helper.isLoggedIn(MainActivity.this)) { + intent = new Intent(MainActivity.this, AllPlaylistsActivity.class); + } else { + intent = new Intent(MainActivity.this, AllLocalPlaylistsActivity.class); + } + startActivity(intent); + } else if (item.getItemId() == R.id.action_sepia_search) { + Intent intent = new Intent(MainActivity.this, SepiaSearchActivity.class); + startActivity(intent); + } else if (item.getItemId() == R.id.action_about) { + Intent intent = new Intent(MainActivity.this, AboutActivity.class); + startActivity(intent); + } else if (item.getItemId() == R.id.action_donate) { + Intent intent = new Intent(MainActivity.this, DonationActivity.class); + startActivity(intent); + } else if (item.getItemId() == R.id.action_incognito) { + item.setChecked(!item.isChecked()); + final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putBoolean(getString(R.string.set_store_in_history), item.isChecked()); + editor.apply(); + new Thread(() -> { + UserSettings userSettings = new UserSettings(); + userSettings.setVideosHistoryEnabled(item.isChecked()); + try { + RetrofitPeertubeAPI api = new RetrofitPeertubeAPI(MainActivity.this); + api.updateUser(userSettings); + } catch (Exception | Error e) { + e.printStackTrace(); + } + }).start(); + return false; + } + if (type != null) { + Matomo.sendScreen(MainActivity.this, action, type); + } + return true; + } + + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (intent == null) + return; + Bundle extras = intent.getExtras(); + if (extras != null && extras.containsKey(Helper.INTENT_ACTION)) { + if (extras.getInt(Helper.INTENT_ACTION) == Helper.ADD_USER_INTENT) { + recreate(); + } + } else if (!BuildConfig.full_instances) { + PlaylistExportHelper.manageIntentUrl(MainActivity.this, intent); + } + } + + + @SuppressLint("ApplySharedPref") + private void showRadioButtonDialog() { + + AlertDialog.Builder alt_bld = new AlertDialog.Builder(this); + alt_bld.setTitle(R.string.instance_choice); + final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String acad = HelperInstance.getLiveInstance(MainActivity.this); + int i = 0; + List acadInstances = AcadInstances.getInstances(); + String[] academiesKey = new String[acadInstances.size()]; + String[] academiesValue = new String[acadInstances.size()]; + int position = 0; + for (AcadInstances ac : acadInstances) { + academiesKey[i] = ac.getName(); + academiesValue[i] = ac.getUrl(); + if (ac.getUrl().compareTo(acad) == 0) { + position = i; + } + i++; + } + + alt_bld.setSingleChoiceItems(academiesKey, position, (dialog, item) -> { + String newInstance = academiesValue[item]; + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_INSTANCE, newInstance); + editor.commit(); + dialog.dismiss(); + recreate(); + }); + alt_bld.setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss()); + AlertDialog alert = alt_bld.create(); + alert.show(); + } + + @SuppressLint("ApplySharedPref") + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_INSTANCE && resultCode == Activity.RESULT_OK) { + if (data != null && data.getData() != null) { + final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_INSTANCE, String.valueOf(data.getData())); + editor.commit(); + recreate(); + } + } + } + + public enum TypeOfConnection { + UNKNOWN, + NORMAL, + SURFING, + REMOTE_ACCOUNT, + } + + private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter { + + ScreenSlidePagerAdapter(FragmentManager fm) { + super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); + } + + @NotNull + @Override + public Fragment getItem(final int position) { + if (Helper.isLoggedIn(MainActivity.this)) { + switch (position) { + case 0: + return locaFragment; + case 1: + return recentFragment; + case 2: + return trendingFragment; + case 3: + return overviewFragment; + case 4: + return subscriptionFragment; + } + } else { + switch (position) { + case 0: + return locaFragment; + case 1: + return recentFragment; + case 2: + return trendingFragment; + case 3: + return overviewFragment; + } + } + return overviewFragment; + } + + @Override + public int getCount() { + if (Helper.isLoggedIn(MainActivity.this)) { + return 5; + } else { + return 4; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/ManageInstancesActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/ManageInstancesActivity.java new file mode 100644 index 000000000..c23c86b99 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/ManageInstancesActivity.java @@ -0,0 +1,134 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.activities.MainActivity.PICK_INSTANCE_SURF; +import static app.fedilab.android.peertube.activities.MainActivity.showRadioButtonDialogFullInstances; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.database.sqlite.SQLiteDatabase; +import android.os.Bundle; +import android.os.Handler; +import android.view.MenuItem; +import android.view.View; + +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; + +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.data.InstanceData; +import app.fedilab.android.peertube.databinding.ActivityManageInstancesBinding; +import app.fedilab.android.peertube.drawer.AboutInstanceAdapter; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.sqlite.Sqlite; +import app.fedilab.android.peertube.sqlite.StoredInstanceDAO; +import app.fedilab.android.peertube.viewmodel.InfoInstanceVM; + + +public class ManageInstancesActivity extends BaseActivity implements AboutInstanceAdapter.AllInstancesRemoved { + + private ActivityManageInstancesBinding binding; + private List aboutInstances; + private AboutInstanceAdapter aboutInstanceAdapter; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + binding = ActivityManageInstancesBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + } + binding.loader.setVisibility(View.VISIBLE); + binding.noAction.setVisibility(View.GONE); + binding.lvInstances.setVisibility(View.GONE); + binding.actionButton.setOnClickListener(v -> showRadioButtonDialogFullInstances(ManageInstancesActivity.this, true)); + aboutInstances = new ArrayList<>(); + aboutInstanceAdapter = new AboutInstanceAdapter(this.aboutInstances); + aboutInstanceAdapter.allInstancesRemoved = this; + binding.lvInstances.setAdapter(aboutInstanceAdapter); + LinearLayoutManager layoutManager + = new LinearLayoutManager(ManageInstancesActivity.this); + binding.lvInstances.setLayoutManager(layoutManager); + InfoInstanceVM viewModelInfoInstance = new ViewModelProvider(ManageInstancesActivity.this).get(InfoInstanceVM.class); + viewModelInfoInstance.getInstances().observe(ManageInstancesActivity.this, this::manageVIewInfoInstance); + } + + private void manageVIewInfoInstance(List aboutInstances) { + binding.loader.setVisibility(View.GONE); + if (aboutInstances == null || aboutInstances.size() == 0) { + binding.noAction.setVisibility(View.VISIBLE); + binding.lvInstances.setVisibility(View.GONE); + return; + } + binding.noAction.setVisibility(View.GONE); + binding.lvInstances.setVisibility(View.VISIBLE); + this.aboutInstances.addAll(aboutInstances); + aboutInstanceAdapter.notifyItemRangeInserted(0, aboutInstances.size()); + + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + overridePendingTransition(R.anim.slide_out_up, R.anim.slide_in_up_down); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + overridePendingTransition(R.anim.slide_out_up, R.anim.slide_in_up_down); + return true; + } + return super.onOptionsItemSelected(item); + } + + + @SuppressLint("ApplySharedPref") + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_INSTANCE_SURF && resultCode == Activity.RESULT_OK) { + if (data != null && data.getData() != null) { + new Thread(() -> { + String newInstance = data.getData().toString().trim().toLowerCase(); + InstanceData.AboutInstance aboutInstance = new RetrofitPeertubeAPI(ManageInstancesActivity.this, newInstance, null).getAboutInstance(); + SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + new StoredInstanceDAO(ManageInstancesActivity.this, db).insertInstance(aboutInstance, newInstance); + runOnUiThread(() -> new Handler().post(() -> Helper.logoutNoRemoval(ManageInstancesActivity.this))); + }).start(); + } + } + } + + @Override + public void onAllInstancesRemoved() { + binding.noAction.setVisibility(View.VISIBLE); + binding.lvInstances.setVisibility(View.GONE); + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/MastodonWebviewConnectActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/MastodonWebviewConnectActivity.java new file mode 100644 index 000000000..1fcfc99e1 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/MastodonWebviewConnectActivity.java @@ -0,0 +1,194 @@ +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +package app.fedilab.android.peertube.activities; + + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.MenuItem; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ProgressBar; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.entities.OauthParams; +import app.fedilab.android.peertube.client.entities.Token; +import app.fedilab.android.peertube.client.mastodon.RetrofitMastodonAPI; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import es.dmoral.toasty.Toasty; + + +public class MastodonWebviewConnectActivity extends BaseActivity { + + + private WebView webView; + private AlertDialog alert; + private String clientId, clientSecret; + private String instance, software; + + @SuppressWarnings("deprecation") + public static void clearCookies(Context context) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + CookieManager.getInstance().removeAllCookies(null); + CookieManager.getInstance().flush(); + } else { + CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(context); + cookieSyncMngr.startSync(); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.removeAllCookie(); + cookieManager.removeSessionCookie(); + cookieSyncMngr.stopSync(); + cookieSyncMngr.sync(); + } + } + + private static String redirectUserToAuthorizeAndLogin(String clientId, String instance) { + String queryString = Helper.CLIENT_ID + "=" + clientId; + queryString += "&" + Helper.REDIRECT_URI + "=" + Uri.encode(Helper.REDIRECT_CONTENT_WEB); + queryString += "&response_type=code"; + queryString += "&scope=read write follow"; + return "https://" + instance + "/oauth/authorize?" + queryString; + } + + @SuppressLint("SetJavaScriptEnabled") + public void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_webview_connect); + Bundle b = getIntent().getExtras(); + if (b != null) { + instance = b.getString("instance"); + clientId = b.getString("client_id"); + clientSecret = b.getString("client_secret"); + software = b.getString("software"); + } + if (instance == null) + finish(); + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setTitle(R.string.login); + webView = findViewById(R.id.webviewConnect); + clearCookies(MastodonWebviewConnectActivity.this); + webView.getSettings().setJavaScriptEnabled(true); + + CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true); + + final ProgressBar pbar = findViewById(R.id.progress_bar); + webView.setWebChromeClient(new WebChromeClient() { + @Override + public void onProgressChanged(WebView view, int progress) { + if (progress < 100 && pbar.getVisibility() == ProgressBar.GONE) { + pbar.setVisibility(ProgressBar.VISIBLE); + } + pbar.setProgress(progress); + if (progress == 100) { + pbar.setVisibility(ProgressBar.GONE); + } + } + }); + + if (instance == null) { + finish(); + } + webView.setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + super.shouldOverrideUrlLoading(view, url); + if (url.contains(Helper.REDIRECT_CONTENT_WEB)) { + String[] val = url.split("code="); + if (val.length < 2) { + Toasty.error(MastodonWebviewConnectActivity.this, getString(R.string.toast_code_error), Toast.LENGTH_LONG).show(); + Intent myIntent = new Intent(MastodonWebviewConnectActivity.this, LoginActivity.class); + startActivity(myIntent); + finish(); + return false; + } + String code = val[1]; + if (code.contains("&")) { + code = code.split("&")[0]; + } + OauthParams oauthParams = new OauthParams(); + oauthParams.setClient_id(clientId); + oauthParams.setClient_secret(clientSecret); + oauthParams.setGrant_type("authorization_code"); + oauthParams.setCode(code); + oauthParams.setRedirect_uri(Helper.REDIRECT_CONTENT_WEB); + + new Thread(() -> { + try { + Token token = new RetrofitMastodonAPI(MastodonWebviewConnectActivity.this, instance, null).manageToken(oauthParams); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token.getAccess_token()); + editor.apply(); + new RetrofitMastodonAPI(MastodonWebviewConnectActivity.this, instance, token.getAccess_token()).updateCredential(MastodonWebviewConnectActivity.this, clientId, clientSecret, token.getRefresh_token(), software); + } catch (Exception | Error ignored) { + } + }).start(); + return true; + } + return false; + } + + }); + webView.loadUrl(redirectUserToAuthorizeAndLogin(clientId, instance)); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onBackPressed() { + if (webView != null && webView.canGoBack()) { + webView.goBack(); + } else { + super.onBackPressed(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (alert != null) { + alert.dismiss(); + alert = null; + } + if (webView != null) { + webView.destroy(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/MyAccountActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/MyAccountActivity.java new file mode 100644 index 000000000..fc7c3f559 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/MyAccountActivity.java @@ -0,0 +1,362 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.activities.PeertubeUploadActivity.MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE; +import static app.fedilab.android.peertube.worker.WorkHelper.NOTIFICATION_WORKER; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Toast; + +import androidx.appcompat.widget.SwitchCompat; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.documentfile.provider.DocumentFile; +import androidx.work.WorkManager; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; + +import org.jetbrains.annotations.NotNull; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.entities.NotificationSettings; +import app.fedilab.android.peertube.client.entities.UserMe; +import app.fedilab.android.peertube.client.entities.UserSettings; +import app.fedilab.android.peertube.databinding.ActivityMyAccountSettingsBinding; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.worker.WorkHelper; +import es.dmoral.toasty.Toasty; + +public class MyAccountActivity extends BaseActivity { + + private static final int PICK_IMAGE = 466; + ActivityMyAccountSettingsBinding binding; + private Uri inputData; + private String fileName; + private NotificationSettings notificationSettings; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + binding = ActivityMyAccountSettingsBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + if (MainActivity.userMe == null) { + finish(); + return; + } + setTitle(String.format("@%s", MainActivity.userMe.getUsername())); + binding.displayname.setText(MainActivity.userMe.getAccount().getDisplayName()); + binding.description.setText(MainActivity.userMe.getAccount().getDescription()); + + notificationSettings = MainActivity.userMe.getNotificationSettings(); + initializeValues(notificationSettings.getAbuseStateChange(), binding.notifAbuseAcceptedApp, binding.notifAbuseAcceptedMail); + initializeValues(notificationSettings.getAbuseNewMessage(), binding.notifAbuseReceivedApp, binding.notifAbuseReceivedMail); + initializeValues(notificationSettings.getCommentMention(), binding.notifVideoMentionApp, binding.notifVideoMentionMail); + initializeValues(notificationSettings.getNewFollow(), binding.notifNewFollowersApp, binding.notifNewFollowersMail); + initializeValues(notificationSettings.getMyVideoImportFinished(), binding.notifVideoImportedApp, binding.notifVideoImportedMail); + initializeValues(notificationSettings.getMyVideoPublished(), binding.notifVideoPublishedApp, binding.notifVideoPublishedMail); + initializeValues(notificationSettings.getBlacklistOnMyVideo(), binding.notifBlockedApp, binding.notifBlockedMail); + initializeValues(notificationSettings.getNewCommentOnMyVideo(), binding.notifNewCommentApp, binding.notifNewCommentMail); + initializeValues(notificationSettings.getNewVideoFromSubscription(), binding.notifNewVideoApp, binding.notifNewVideoMail); + + Helper.loadAvatar(MyAccountActivity.this, MainActivity.userMe.getAccount(), binding.profilePicture); + String[] refresh_array = getResources().getStringArray(R.array.refresh_time); + ArrayAdapter refreshArray = new ArrayAdapter<>(MyAccountActivity.this, + android.R.layout.simple_spinner_dropdown_item, refresh_array); + binding.refreshTime.setAdapter(refreshArray); + + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + int interval = sharedpreferences.getInt(Helper.NOTIFICATION_INTERVAL, 60); + binding.refreshTime.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + SharedPreferences.Editor editor = sharedpreferences.edit(); + int time; + switch (position) { + case 1: + time = 15; + break; + case 2: + time = 30; + break; + case 3: + time = 60; + break; + case 4: + time = 120; + break; + case 5: + time = 360; + break; + case 6: + time = 720; + break; + default: + time = 0; + } + editor.putInt(Helper.NOTIFICATION_INTERVAL, time); + editor.apply(); + WorkManager.getInstance(getApplicationContext()).cancelAllWorkByTag(NOTIFICATION_WORKER); + if (time > 0) { + WorkHelper.fetchNotifications(getApplication(), time); + + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + int position = 0; + switch (interval) { + case 0: + position = 0; + break; + case 15: + position = 1; + break; + case 30: + position = 2; + break; + case 60: + position = 3; + break; + case 120: + position = 4; + break; + case 360: + position = 5; + break; + case 720: + position = 6; + break; + } + binding.refreshTime.setSelection(position, false); + + binding.selectFile.setOnClickListener(v -> { + if (ContextCompat.checkSelfPermission(MyAccountActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != + PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(MyAccountActivity.this, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); + return; + } + + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + String[] mimetypes = {"image/*"}; + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); + startActivityForResult(intent, PICK_IMAGE); + }); + + } + + @Override + public boolean onCreateOptionsMenu(@NotNull Menu menu) { + getMenuInflater().inflate(R.menu.main_my_account, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } else if (item.getItemId() == R.id.action_validate) { + item.setEnabled(false); + new Thread(() -> { + UserSettings userSettings = new UserSettings(); + userSettings.setNotificationSettings(notificationSettings); + if (binding.displayname.getText() != null) { + userSettings.setDisplayName(binding.displayname.getText().toString().trim()); + } + if (binding.description.getText() != null) { + userSettings.setDescription(binding.description.getText().toString().trim()); + } + if (inputData != null) { + userSettings.setAvatarfile(inputData); + userSettings.setFileName(fileName); + } + try { + RetrofitPeertubeAPI api = new RetrofitPeertubeAPI(MyAccountActivity.this); + UserMe.AvatarResponse avatarResponse = api.updateUser(userSettings); + MainActivity.userMe.getAccount().setDisplayName(binding.displayname.getText().toString().trim()); + MainActivity.userMe.getAccount().setDescription(binding.description.getText().toString().trim()); + if (avatarResponse != null && avatarResponse.getAvatar() != null) { + MainActivity.userMe.getAccount().setAvatar(avatarResponse.getAvatar()); + } + + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + Toasty.info(MyAccountActivity.this, getString(R.string.account_updated), Toasty.LENGTH_LONG).show(); + item.setEnabled(true); + }; + mainHandler.post(myRunnable); + } catch (Exception | Error e) { + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + Toasty.error(MyAccountActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); + item.setEnabled(true); + }; + mainHandler.post(myRunnable); + } + }).start(); + } + return super.onOptionsItemSelected(item); + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) { + if (data == null || data.getData() == null) { + Toasty.error(MyAccountActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); + return; + } + inputData = data.getData(); + DocumentFile documentFile = DocumentFile.fromSingleUri(this, inputData); + if (documentFile != null) { + fileName = documentFile.getName(); + } + Glide.with(MyAccountActivity.this) + .load(inputData) + .thumbnail(0.1f) + .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) + .into(binding.profilePicture); + + } + } + + private void initializeValues(int value, SwitchCompat app, SwitchCompat email) { + switch (value) { + case 1: + app.setChecked(true); + email.setChecked(false); + break; + case 2: + app.setChecked(false); + email.setChecked(true); + break; + case 3: + app.setChecked(true); + email.setChecked(true); + break; + default: + app.setChecked(false); + email.setChecked(false); + } + app.setOnCheckedChangeListener((compoundButton, checked) -> { + int id = app.getId(); + if (id == R.id.notif_new_video_app) { + notificationSettings.setNewVideoFromSubscription(getNewAppCheckedValue(checked, email)); + } else if (id == R.id.notif_new_comment_app) { + notificationSettings.setNewCommentOnMyVideo(getNewAppCheckedValue(checked, email)); + } else if (id == R.id.notif_blocked_app) { + notificationSettings.setBlacklistOnMyVideo(getNewAppCheckedValue(checked, email)); + } else if (id == R.id.notif_video_published_app) { + notificationSettings.setMyVideoPublished(getNewAppCheckedValue(checked, email)); + } else if (id == R.id.notif_video_imported_app) { + notificationSettings.setMyVideoImportFinished(getNewAppCheckedValue(checked, email)); + } else if (id == R.id.notif_new_followers_app) { + notificationSettings.setNewFollow(getNewAppCheckedValue(checked, email)); + } else if (id == R.id.notif_video_mention_app) { + notificationSettings.setCommentMention(getNewAppCheckedValue(checked, email)); + } else if (id == R.id.notif_abuse_received_app) { + notificationSettings.setAbuseNewMessage(getNewAppCheckedValue(checked, email)); + } else if (id == R.id.notif_abuse_accepted_app) { + notificationSettings.setAbuseStateChange(getNewAppCheckedValue(checked, email)); + } + }); + email.setOnCheckedChangeListener((compoundButtonMail, checkedMail) -> { + int id = email.getId(); + if (id == R.id.notif_new_video_mail) { + notificationSettings.setNewVideoFromSubscription(getNewMailCheckedValue(checkedMail, app)); + } else if (id == R.id.notif_new_comment_mail) { + notificationSettings.setNewCommentOnMyVideo(getNewMailCheckedValue(checkedMail, app)); + } else if (id == R.id.notif_blocked_mail) { + notificationSettings.setBlacklistOnMyVideo(getNewMailCheckedValue(checkedMail, app)); + } else if (id == R.id.notif_video_published_mail) { + notificationSettings.setMyVideoPublished(getNewMailCheckedValue(checkedMail, app)); + } else if (id == R.id.notif_video_imported_mail) { + notificationSettings.setMyVideoImportFinished(getNewMailCheckedValue(checkedMail, app)); + } else if (id == R.id.notif_new_followers_mail) { + notificationSettings.setNewFollow(getNewMailCheckedValue(checkedMail, app)); + } else if (id == R.id.notif_video_mention_mail) { + notificationSettings.setCommentMention(getNewMailCheckedValue(checkedMail, app)); + } else if (id == R.id.notif_abuse_received_mail) { + notificationSettings.setAbuseNewMessage(getNewMailCheckedValue(checkedMail, app)); + } else if (id == R.id.notif_abuse_accepted_mail) { + notificationSettings.setAbuseStateChange(getNewMailCheckedValue(checkedMail, app)); + } + }); + } + + private int getNewAppCheckedValue(boolean checked, SwitchCompat email) { + int newValue; + if (checked && email.isChecked()) { + newValue = 3; + } else if (!checked && email.isChecked()) { + newValue = 2; + } else if (checked && !email.isChecked()) { + newValue = 1; + } else { + newValue = 0; + } + return newValue; + } + + private int getNewMailCheckedValue(boolean checked, SwitchCompat app) { + int newValue; + if (checked && app.isChecked()) { + newValue = 3; + } else if (!checked && app.isChecked()) { + newValue = 1; + } else if (checked && !app.isChecked()) { + newValue = 2; + } else { + newValue = 0; + } + return newValue; + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeActivity.java new file mode 100644 index 000000000..11daf502f --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeActivity.java @@ -0,0 +1,2319 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.ADD_COMMENT; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.RATEVIDEO; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.REPLY; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.REPORT_ACCOUNT; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.REPORT_VIDEO; +import static app.fedilab.android.peertube.helper.Helper.canMakeAction; +import static app.fedilab.android.peertube.helper.Helper.getAttColor; +import static app.fedilab.android.peertube.helper.Helper.isLoggedIn; +import static app.fedilab.android.peertube.helper.Helper.peertubeInformation; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.PictureInPictureParams; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.support.v4.media.session.MediaSessionCompat; +import android.text.Html; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.PopupMenu; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.github.vkay94.dtpv.youtube.YouTubeOverlay; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; +import com.google.android.exoplayer2.source.MergingMediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; +import com.google.android.exoplayer2.source.SingleSampleMediaSource; +import com.google.android.exoplayer2.source.hls.HlsMediaSource; +import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.ui.DefaultTimeBar; +import com.google.android.exoplayer2.ui.PlayerControlView; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoListener; +import com.google.android.material.snackbar.Snackbar; + +import org.jetbrains.annotations.NotNull; + +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import app.fedilab.android.peertube.BasePeertubeActivity; +import app.fedilab.android.peertube.BuildConfig; +import app.fedilab.android.peertube.Matomo; +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.APIResponse; +import app.fedilab.android.peertube.client.MenuItemVideo; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.data.AccountData.Account; +import app.fedilab.android.peertube.client.data.CaptionData.Caption; +import app.fedilab.android.peertube.client.data.CommentData; +import app.fedilab.android.peertube.client.data.CommentData.Comment; +import app.fedilab.android.peertube.client.data.InstanceData; +import app.fedilab.android.peertube.client.data.PlaylistData; +import app.fedilab.android.peertube.client.data.PluginData; +import app.fedilab.android.peertube.client.data.VideoData; +import app.fedilab.android.peertube.client.entities.File; +import app.fedilab.android.peertube.client.entities.MenuItemView; +import app.fedilab.android.peertube.client.entities.PlaylistExist; +import app.fedilab.android.peertube.client.entities.Report; +import app.fedilab.android.peertube.client.entities.UserSettings; +import app.fedilab.android.peertube.client.mastodon.RetrofitMastodonAPI; +import app.fedilab.android.peertube.databinding.ActivityPeertubeBinding; +import app.fedilab.android.peertube.drawer.CommentListAdapter; +import app.fedilab.android.peertube.drawer.MenuAdapter; +import app.fedilab.android.peertube.drawer.MenuItemAdapter; +import app.fedilab.android.peertube.helper.CacheDataSourceFactory; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.sqlite.AccountDAO; +import app.fedilab.android.peertube.sqlite.Sqlite; +import app.fedilab.android.peertube.viewmodel.CaptionsVM; +import app.fedilab.android.peertube.viewmodel.CommentVM; +import app.fedilab.android.peertube.viewmodel.PlaylistsVM; +import app.fedilab.android.peertube.viewmodel.PostActionsVM; +import app.fedilab.android.peertube.viewmodel.SearchVM; +import app.fedilab.android.peertube.viewmodel.TimelineVM; +import app.fedilab.android.peertube.viewmodel.mastodon.MastodonPostActionsVM; +import app.fedilab.android.peertube.webview.CustomWebview; +import app.fedilab.android.peertube.webview.MastalabWebChromeClient; +import app.fedilab.android.peertube.webview.MastalabWebViewClient; +import es.dmoral.toasty.Toasty; + + +public class PeertubeActivity extends BasePeertubeActivity implements CommentListAdapter.AllCommentRemoved, Player.EventListener, VideoListener, MenuAdapter.ItemClicked, MenuItemAdapter.ItemAction { + + public static String video_id; + public static List playedVideos = new ArrayList<>(); + Uri captionURI; + String captionLang; + private String peertubeInstance, videoUuid; + private ImageView fullScreenIcon; + private boolean fullScreenMode; + private int mode; + private Map> playlists; + private boolean playInMinimized, autoPlay, autoFullscreen; + private boolean onStopCalled; + private List captions; + private String max_id; + private boolean flag_loading; + private boolean isMyVideo; + private List comments; + private CommentListAdapter commentListAdapter; + private CommentListAdapter commentReplyListAdapter; + private boolean sepiaSearch; + private ActivityPeertubeBinding binding; + private List commentsThread; + private BroadcastReceiver mPowerKeyReceiver = null; + private boolean isPlayInMinimized; + private VideoData.Video nextVideo; + private String show_more_content; + private videoOrientation videoOrientationType; + private int initialOrientation; + private String currentResolution; + private String currentCaption; + private boolean isRemote; + private boolean willPlayFromIntent; + private app.fedilab.android.peertube.client.mastodon.Status status; + + public static void hideKeyboard(Activity activity) { + if (activity != null && activity.getWindow() != null) { + activity.getWindow().getDecorView(); + InputMethodManager imm = activity.getSystemService(INPUT_METHOD_SERVICE); + assert imm != null; + imm.hideSoftInputFromWindow(activity.getWindow().getDecorView().getWindowToken(), 0); + } + } + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + if (width < height) { + videoOrientationType = videoOrientation.PORTRAIT; + } else { + videoOrientationType = videoOrientation.LANDSCAPE; + } + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + binding = super.binding; + videoOrientationType = videoOrientation.LANDSCAPE; + max_id = "0"; + SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + String token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null); + if (Helper.canMakeAction(PeertubeActivity.this) && !sepiaSearch) { + Account account = new AccountDAO(PeertubeActivity.this, db).getAccountByToken(token); + Helper.loadAvatar(PeertubeActivity.this, account, binding.myPp); + } + isRemote = false; + + + fullScreenMode = false; + initialOrientation = getResources().getConfiguration().orientation; + if (Helper.isTablet(PeertubeActivity.this)) { + + LinearLayout.LayoutParams param = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + 0, + 2.0f + ); + binding.videoContainer.setLayoutParams(param); + } else { + if (initialOrientation == Configuration.ORIENTATION_LANDSCAPE) { + LinearLayout.LayoutParams param = new LinearLayout.LayoutParams( + ConstraintLayout.LayoutParams.MATCH_PARENT, + 0, + 4.0f + ); + binding.videoContainer.setLayoutParams(param); + } + } + isPlayInMinimized = false; + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + mode = sharedpreferences.getInt(Helper.SET_VIDEO_MODE, Helper.VIDEO_MODE_NORMAL); + + Intent intent = getIntent(); + + Bundle b = intent.getExtras(); + if (b != null) { + peertubeInstance = b.getString("peertube_instance", HelperInstance.getLiveInstance(PeertubeActivity.this)); + videoUuid = b.getString("video_uuid", null); + isMyVideo = b.getBoolean("isMyVideo", false); + sepiaSearch = b.getBoolean("sepia_search", false); + peertube = b.getParcelable("video"); + } + + + willPlayFromIntent = manageIntentUrl(intent); + + if (BuildConfig.allow_remote_connections && Helper.isLoggedInType(PeertubeActivity.this) == MainActivity.TypeOfConnection.REMOTE_ACCOUNT) { + binding.peertubeLikeCount.setVisibility(View.GONE); + binding.peertubeDislikeCount.setVisibility(View.GONE); + binding.peertubePlaylist.setVisibility(View.GONE); + binding.peertubeReblog.setVisibility(View.VISIBLE); + binding.peertubeFavorite.setVisibility(View.VISIBLE); + binding.peertubeBookmark.setVisibility(View.VISIBLE); + } else { + binding.peertubePlaylist.setVisibility(View.VISIBLE); + } + + binding.peertubeDescriptionMore.setOnClickListener(v -> { + if (show_more_content != null && peertube != null) { + if (binding.peertubeDescriptionMore.getText().toString().compareTo(getString(R.string.show_more)) == 0) { + binding.peertubeDescriptionMore.setText(getString(R.string.show_less)); + binding.peertubeDescription.setText(show_more_content); + } else { + binding.peertubeDescriptionMore.setText(getString(R.string.show_more)); + binding.peertubeDescription.setText(peertube.getDescription()); + } + } + }); + if (!Helper.canMakeAction(PeertubeActivity.this) || sepiaSearch) { + binding.writeCommentContainer.setVisibility(View.GONE); + } + playInMinimized = sharedpreferences.getBoolean(getString(R.string.set_video_minimize_choice), true); + autoPlay = sharedpreferences.getBoolean(getString(R.string.set_autoplay_choice), true); + autoFullscreen = sharedpreferences.getBoolean(getString(R.string.set_fullscreen_choice), false); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O + || !getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) { + playInMinimized = false; + } + + if (peertube != null && peertube.isNsfw()) { + binding.videoSensitive.setVisibility(View.VISIBLE); + } else { + binding.videoSensitive.setVisibility(View.INVISIBLE); + } + if (mode == Helper.VIDEO_MODE_WEBVIEW) { + binding.webviewVideo.setVisibility(View.VISIBLE); + binding.mediaVideo.setVisibility(View.GONE); + binding.doubleTapPlayerView.setVisibility(View.GONE); + CustomWebview webview_video = Helper.initializeWebview(PeertubeActivity.this, R.id.webview_video, null); + + MastalabWebChromeClient mastalabWebChromeClient = new MastalabWebChromeClient(PeertubeActivity.this, webview_video, binding.mainMediaFrame, binding.videoLayout); + mastalabWebChromeClient.setOnToggledFullscreen(fullscreen -> { + if (fullscreen) { + binding.videoLayout.setVisibility(View.VISIBLE); + } else { + binding.videoLayout.setVisibility(View.GONE); + } + toogleFullscreen(fullscreen); + }); + binding.webviewVideo.getSettings().setAllowFileAccess(true); + binding.webviewVideo.setWebChromeClient(mastalabWebChromeClient); + binding.webviewVideo.getSettings().setDomStorageEnabled(true); + binding.webviewVideo.getSettings().setAppCacheEnabled(true); + binding.webviewVideo.getSettings().setMediaPlaybackRequiresUserGesture(false); + binding.webviewVideo.setWebViewClient(new MastalabWebViewClient(PeertubeActivity.this)); + binding.webviewVideo.loadUrl("https://" + peertubeInstance + "/videos/embed/" + videoUuid); + } else { + binding.webviewVideo.setVisibility(View.GONE); + binding.loader.setVisibility(View.VISIBLE); + } + + if (mode != Helper.VIDEO_MODE_WEBVIEW) { + binding.doubleTapPlayerView.setControllerShowTimeoutMs(1000); + binding.doubleTapPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); + initControllerButtons(); + + binding.doubleTapPlayerView + .setDoubleTapDelay(500); + binding.doubleTapPlayerView.setDoubleTapEnabled(true); + binding.doubleTapPlayerView.setControllerShowTimeoutMs(0); + binding.mediaVideo.performListener(new YouTubeOverlay.PerformListener() { + @Override + public void onAnimationStart() { + binding.mediaVideo.setVisibility(View.VISIBLE); + binding.doubleTapPlayerView.setUseController(false); + } + + @Override + public void onAnimationEnd() { + binding.mediaVideo.setVisibility(View.GONE); + binding.doubleTapPlayerView.setUseController(true); + } + }).playerView(binding.doubleTapPlayerView).seekSeconds(10); + binding.doubleTapPlayerView.setPlayer(player); + binding.doubleTapPlayerView.controller(binding.mediaVideo); + if (player != null) + binding.mediaVideo.player(player); + } + flag_loading = true; + comments = new ArrayList<>(); + + binding.closeReply.setOnClickListener(v -> closeCommentThread()); + binding.closePost.setOnClickListener(v -> closePostComment()); + + commentListAdapter = new CommentListAdapter(comments, isMyVideo || Helper.isVideoOwner(PeertubeActivity.this, peertube), false, peertubeInstance, sepiaSearch); + commentListAdapter.allCommentRemoved = PeertubeActivity.this; + LinearLayoutManager mLayoutManager = new LinearLayoutManager(PeertubeActivity.this); + binding.peertubeComments.setLayoutManager(mLayoutManager); + binding.peertubeComments.setNestedScrollingEnabled(false); + binding.peertubeComments.setAdapter(commentListAdapter); + binding.peertubeComments.addOnScrollListener(new RecyclerView.OnScrollListener() { + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + if (dy > 0) { + int visibleItemCount = mLayoutManager.getChildCount(); + int totalItemCount = mLayoutManager.getItemCount(); + int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); + if (firstVisibleItem + visibleItemCount == totalItemCount) { + if (!flag_loading) { + CommentVM commentViewModel = new ViewModelProvider(PeertubeActivity.this).get(CommentVM.class); + commentViewModel.getThread(sepiaSearch ? peertubeInstance : null, videoUuid, max_id).observe(PeertubeActivity.this, apiresponse -> manageVIewComment(apiresponse)); + } + } + } + } + }); + if (!willPlayFromIntent && peertube != null && sepiaSearch && peertube.getEmbedUrl() != null && Helper.isLoggedIn(PeertubeActivity.this)) { + SearchVM viewModelSearch = new ViewModelProvider(PeertubeActivity.this).get(SearchVM.class); + viewModelSearch.getVideos("0", peertube.getUuid()).observe(PeertubeActivity.this, this::manageVIewVideos); + } else { + playVideo(); + } + + registBroadcastReceiver(); + if (autoFullscreen && autoPlay) { + openFullscreenDialog(); + } + binding.postCommentButton.setOnClickListener(v -> { + if (canMakeAction(PeertubeActivity.this) && !sepiaSearch) { + openPostComment(null, 0); + } else { + if (sepiaSearch) { + Toasty.info(PeertubeActivity.this, getString(R.string.federation_issue), Toasty.LENGTH_SHORT).show(); + } else { + Toasty.error(PeertubeActivity.this, getString(R.string.not_logged_in), Toast.LENGTH_SHORT).show(); + } + } + }); + + + } + + private void manageVIewVideos(APIResponse apiResponse) { + if (apiResponse == null || apiResponse.getPeertubes() == null || apiResponse.getPeertubes().size() == 0) { + playVideo(); + return; + } + peertube = apiResponse.getPeertubes().get(0); + Matomo.sendScreen(PeertubeActivity.this, "VIDEO_REGARDEE", peertube.getUuid()); + if (peertube.isNsfw()) { + binding.videoSensitive.setVisibility(View.VISIBLE); + } else { + binding.videoSensitive.setVisibility(View.INVISIBLE); + } + if (player != null && peertube.getUserHistory() != null) { + player.seekTo(peertube.getUserHistory().getCurrentTime() * 1000); + } + sepiaSearch = false; + playVideo(); + } + + public void manageVIewComment(APIResponse apiResponse) { + flag_loading = false; + if (apiResponse == null || (apiResponse.getError() != null)) { + if (apiResponse == null) + Toasty.error(PeertubeActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + else + Toasty.error(PeertubeActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + return; + } + int oldSize = comments.size(); + int newComments = 0; + for (Comment comment : apiResponse.getComments()) { + if (comment.getText() != null && comment.getText().trim().length() > 0) { + comments.add(comment); + newComments++; + } + } + if (comments.size() > 0) { + binding.peertubeComments.setVisibility(View.VISIBLE); + commentListAdapter.notifyItemRangeInserted(oldSize, newComments); + } + } + + public void manageVIewCommentReply(Comment comment, APIResponse apiResponse) { + if (apiResponse == null || apiResponse.getError() != null || apiResponse.getCommentThreadData() == null) { + if (apiResponse == null || apiResponse.getError() == null) + Toasty.error(PeertubeActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + else + Toasty.error(PeertubeActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + return; + } + List commentThreadDataList = apiResponse.getCommentThreadData().getChildren(); + commentsThread = generateCommentReply(commentThreadDataList, new ArrayList<>()); + comment.setInReplyToCommentId(null); + comment.setTotalReplies(0); + commentsThread.add(0, comment); + commentReplyListAdapter = new CommentListAdapter(commentsThread, Helper.isVideoOwner(PeertubeActivity.this, peertube), true, peertubeInstance, sepiaSearch); + LinearLayoutManager mLayoutManager = new LinearLayoutManager(PeertubeActivity.this); + binding.peertubeReply.setLayoutManager(mLayoutManager); + binding.peertubeReply.setNestedScrollingEnabled(false); + binding.peertubeReply.setAdapter(commentReplyListAdapter); + binding.peertubeReply.setVisibility(View.VISIBLE); + if (commentsThread.size() > 0) { + commentReplyListAdapter.notifyItemRangeInserted(0, commentsThread.size()); + } + } + + private List generateCommentReply(List commentThreadDataList, List comments) { + for (CommentData.CommentThreadData commentThreadData : commentThreadDataList) { + if (commentThreadData.getComment().getText() != null && commentThreadData.getComment().getText().trim().length() > 0) { + commentThreadData.getComment().setReply(true); + comments.add(commentThreadData.getComment()); + } + if (commentThreadData.getChildren() != null && commentThreadData.getChildren().size() > 0) { + generateCommentReply(commentThreadData.getChildren(), comments); + } + } + return comments; + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + Bundle b = intent.getExtras(); + if (b != null) { + isRemote = false; + peertubeInstance = b.getString("peertube_instance", HelperInstance.getLiveInstance(PeertubeActivity.this)); + videoUuid = b.getString("video_uuid", null); + setRequestedOrientationCustom(initialOrientation); + if (comments != null && comments.size() > 0) { + int number = comments.size(); + comments.clear(); + commentListAdapter.notifyItemRangeRemoved(0, number); + } + playVideo(); + } + willPlayFromIntent = manageIntentUrl(intent); + } + + private boolean manageIntentUrl(Intent intent) { + if (intent.getData() != null) { //Comes from a link + String url = intent.getData().toString(); + Pattern link = Pattern.compile("(https?://[\\da-z.-]+\\.[a-z.]{2,10})/videos/watch/(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12})(\\?start=(\\d+[hH])?(\\d+[mM])?(\\d+[sS])?)?$"); + Matcher matcherLink = link.matcher(url); + if (matcherLink.find()) { + String instance = matcherLink.group(1); + String uuid = matcherLink.group(2); + String hour = matcherLink.group(4); + String min = matcherLink.group(5); + String sec = matcherLink.group(6); + int hourInt, minInt, secInt; + int totalSeconds = 0; + if (hour != null) { + hourInt = Integer.parseInt(hour.replace("h", "")); + totalSeconds += 3600 * hourInt; + } + if (min != null) { + minInt = Integer.parseInt(min.replace("m", "")); + totalSeconds += 60 * minInt; + } + if (sec != null) { + secInt = Integer.parseInt(sec.replace("s", "")); + totalSeconds += secInt; + } + captionURI = null; + captionLang = null; + if (instance != null && uuid != null) { + peertubeInstance = instance.replace("https://", "").replace("http://", ""); + sepiaSearch = true; // Sepia search flag is used because, at this time we don't know if the video is federated. + videoUuid = uuid; + peertube = new VideoData.Video(); + peertube.setUuid(uuid); + peertube.setEmbedUrl(url); + if (totalSeconds > 0) { + VideoData.UserHistory userHistory = new VideoData.UserHistory(); + userHistory.setCurrentTime(totalSeconds); + peertube.setUserHistory(userHistory); + } + TimelineVM viewModelTimeline = new ViewModelProvider(PeertubeActivity.this).get(TimelineVM.class); + viewModelTimeline.getVideo(peertubeInstance, peertube.getUuid(), false).observe(PeertubeActivity.this, this::manageVIewVideo); + if (player != null) { + player.release(); + } + if (comments != null && comments.size() > 0) { + int number = comments.size(); + comments.clear(); + commentListAdapter.notifyItemRangeRemoved(0, number); + } + fetchComments(); + isRemote = true; + return true; + } else { + Helper.forwardToAnotherApp(PeertubeActivity.this, intent); + finish(); + } + } else { + Helper.forwardToAnotherApp(PeertubeActivity.this, intent); + finish(); + } + } + return false; + } + + private void playVideo() { + if (player != null) { + player.release(); + player = new SimpleExoPlayer.Builder(PeertubeActivity.this).build(); + binding.mediaVideo.player(player); + binding.doubleTapPlayerView.setPlayer(player); + binding.loader.setVisibility(View.GONE); + player.setPlayWhenReady(autoPlay); + if (autoPlay) { + binding.doubleTapPlayerView.hideController(); + } + captions = null; + } + currentResolution = null; + show_more_content = null; + currentCaption = "null"; + binding.peertubeDescriptionMore.setVisibility(View.GONE); + + if (autoFullscreen && autoPlay) { + openFullscreenDialog(); + } + + TimelineVM feedsViewModel = new ViewModelProvider(PeertubeActivity.this).get(TimelineVM.class); + if (!isRemote) { + feedsViewModel.getVideo(sepiaSearch ? peertubeInstance : null, videoUuid, isMyVideo).observe(PeertubeActivity.this, this::manageVIewVideo); + } + CaptionsVM captionsViewModel = new ViewModelProvider(PeertubeActivity.this).get(CaptionsVM.class); + captionsViewModel.getCaptions(sepiaSearch ? peertubeInstance : null, videoUuid).observe(PeertubeActivity.this, this::manageCaptions); + + //Post view count + new Thread(() -> { + String videoId = peertube != null ? peertube.getUuid() : videoUuid; + new RetrofitPeertubeAPI(PeertubeActivity.this).postView(videoId); + }).start(); + //manage plugin + new Thread(() -> { + String videoInstance = peertubeInstance != null ? peertubeInstance : peertube.getAccount().getHost(); + InstanceData.InstanceConfig instanceConfig = new RetrofitPeertubeAPI(PeertubeActivity.this, videoInstance, null).getConfigInstance(); + if (instanceConfig != null && instanceConfig.getPlugin() != null && instanceConfig.getPlugin().getRegistered() != null) { + for (PluginData.PluginInfo pluginInfo : instanceConfig.getPlugin().getRegistered()) { + if (pluginInfo.getName().compareTo("player-watermark") == 0) { + PluginData.WaterMark getWaterMark = new RetrofitPeertubeAPI(PeertubeActivity.this, videoInstance, null).getWaterMark(); + if (getWaterMark != null && getWaterMark.getDescription() != null && getWaterMark.getDescription().getWatermarkImageUrl() != null) { + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + Glide.with(binding.watermark.getContext()) + .asDrawable() + .load(getWaterMark.getDescription().getWatermarkImageUrl()) + .into(binding.watermark); + binding.watermark.setVisibility(View.VISIBLE); + }; + mainHandler.post(myRunnable); + } + } + } + } + }).start(); + new Thread(() -> { + try { + RetrofitPeertubeAPI api; + if (peertubeInstance != null) { + api = new RetrofitPeertubeAPI(PeertubeActivity.this, peertubeInstance, null); + } else { + api = new RetrofitPeertubeAPI(PeertubeActivity.this); + } + VideoData.Description description = api.getVideoDescription(videoUuid); + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + if (!isFinishing()) { + if (description == null) { + binding.peertubeDescriptionMore.setVisibility(View.GONE); + show_more_content = null; + } else { + if (!PeertubeActivity.this.isFinishing()) { + if (peertube != null && ((peertube.getDescription() == null && description.getDescription() != null && description.getDescription().trim().length() > 0) || (peertube.getDescription() != null && description.getDescription() != null + && description.getDescription().compareTo(peertube.getDescription()) > 0))) { + binding.peertubeDescriptionMore.setVisibility(View.VISIBLE); + show_more_content = description.getDescription(); + } else { + binding.peertubeDescriptionMore.setVisibility(View.GONE); + show_more_content = null; + } + } + } + } + }; + mainHandler.post(myRunnable); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + if (playInMinimized && player != null) { + finishAndRemoveTask(); + } + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void reportAlert(RetrofitPeertubeAPI.ActionType type, AlertDialog alertDialog) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(PeertubeActivity.this); + LayoutInflater inflater1 = getLayoutInflater(); + View dialogView = inflater1.inflate(R.layout.popup_report, new LinearLayout(PeertubeActivity.this), false); + dialogBuilder.setView(dialogView); + dialogBuilder.setNeutralButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); + EditText report_content = dialogView.findViewById(R.id.report_content); + dialogBuilder.setPositiveButton(R.string.report, (dialog, id) -> { + if (report_content.getText().toString().trim().length() == 0) { + Toasty.info(PeertubeActivity.this, getString(R.string.report_comment_size), Toasty.LENGTH_LONG).show(); + } else { + PostActionsVM viewModel = new ViewModelProvider(PeertubeActivity.this).get(PostActionsVM.class); + if (type == RetrofitPeertubeAPI.ActionType.REPORT_VIDEO) { + Report report = new Report(); + Report.VideoReport videoReport = new Report.VideoReport(); + videoReport.setId(peertube.getId()); + report.setVideo(videoReport); + report.setReason(report_content.getText().toString()); + viewModel.report(report).observe(PeertubeActivity.this, apiResponse -> manageVIewPostActions(RetrofitPeertubeAPI.ActionType.REPORT_VIDEO, 0, apiResponse)); + alertDialog.dismiss(); + dialog.dismiss(); + } else if (type == RetrofitPeertubeAPI.ActionType.REPORT_ACCOUNT) { + Report report = new Report(); + Report.AccountReport accountReport = new Report.AccountReport(); + accountReport.setId(peertube.getAccount().getId()); + report.setAccount(accountReport); + report.setReason(report_content.getText().toString()); + viewModel.report(report).observe(PeertubeActivity.this, apiResponse -> manageVIewPostActions(RetrofitPeertubeAPI.ActionType.REPORT_ACCOUNT, 0, apiResponse)); + alertDialog.dismiss(); + dialog.dismiss(); + } + } + }); + AlertDialog alertDialog2 = dialogBuilder.create(); + alertDialog2.show(); + } + + + public void manageCaptions(APIResponse apiResponse) { + if (apiResponse == null || (apiResponse.getError() != null) || apiResponse.getCaptions() == null || apiResponse.getCaptions().size() == 0) { + return; + } + captions = apiResponse.getCaptions(); + } + + public void manageNextVideos(APIResponse apiResponse) { + if (apiResponse == null || apiResponse.getError() != null || apiResponse.getPeertubes() == null || apiResponse.getPeertubes().size() == 0) { + return; + } + List suggestedVideos = apiResponse.getPeertubes(); + for (VideoData.Video video : suggestedVideos) { + if (!playedVideos.contains(video.getId())) { + TimelineVM feedsViewModel = new ViewModelProvider(PeertubeActivity.this).get(TimelineVM.class); + feedsViewModel.getVideo(null, suggestedVideos.get(0).getUuid(), false).observe(PeertubeActivity.this, this::nextVideoDetails); + return; + } + } + } + + @SuppressLint("ClickableViewAccessibility") + public void nextVideoDetails(APIResponse apiResponse) { + if (apiResponse == null || (apiResponse.getError() != null) || apiResponse.getPeertubes() == null || apiResponse.getPeertubes().size() == 0) { + return; + } + int i = 0; + while (i < (apiResponse.getPeertubes().size() - 1) && playedVideos.contains(apiResponse.getPeertubes().get(i).getId())) { + i++; + } + nextVideo = apiResponse.getPeertubes().get(i); + if (!playedVideos.contains(nextVideo.getId()) && player != null && nextVideo.getFileUrl(null, PeertubeActivity.this) != null) { + MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.parse(nextVideo.getFileUrl(null, PeertubeActivity.this))).build(); + player.addMediaItem(mediaItem); + } + } + + + public void manageVIewVideo(APIResponse apiResponse) { + if (!isRemote && apiResponse != null && apiResponse.getPeertubes() != null && apiResponse.getPeertubes().get(0).getErrorCode() == 1 && apiResponse.getPeertubes().get(0).getOriginUrl() != null) { + String url = apiResponse.getPeertubes().get(0).getOriginUrl(); + Pattern link = Pattern.compile("(https?://[\\da-z.-]+\\.[a-z.]{2,10})/videos/watch/(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12})(\\?start=(\\d+[hH])?(\\d+[mM])?(\\d+[sS])?)?$"); + Matcher matcherLink = link.matcher(url); + if (matcherLink.find()) { + String instance = matcherLink.group(1); + String uuid = matcherLink.group(2); + String hour = matcherLink.group(4); + String min = matcherLink.group(5); + String sec = matcherLink.group(6); + int hourInt, minInt, secInt; + int totalSeconds = 0; + if (hour != null) { + hourInt = Integer.parseInt(hour.replace("h", "")); + totalSeconds += 3600 * hourInt; + } + if (min != null) { + minInt = Integer.parseInt(min.replace("m", "")); + totalSeconds += 60 * minInt; + } + if (sec != null) { + secInt = Integer.parseInt(sec.replace("strue", "")); + totalSeconds += secInt; + } + captionURI = null; + captionLang = null; + if (instance != null && uuid != null) { + peertubeInstance = instance.replace("https://", "").replace("http://", ""); + sepiaSearch = true; // Sepia search flag is used because, at this time we don't know if the video is federated. + videoUuid = uuid; + peertube = new VideoData.Video(); + peertube.setUuid(uuid); + peertube.setEmbedUrl(url); + isRemote = true; + if (totalSeconds > 0) { + VideoData.UserHistory userHistory = new VideoData.UserHistory(); + userHistory.setCurrentTime(totalSeconds); + peertube.setUserHistory(userHistory); + } + TimelineVM viewModelTimeline = new ViewModelProvider(PeertubeActivity.this).get(TimelineVM.class); + viewModelTimeline.getVideo(peertubeInstance, peertube.getUuid(), false).observe(PeertubeActivity.this, this::manageVIewVideo); + } + } + return; + } + + if (apiResponse != null && apiResponse.getPeertubes() != null && apiResponse.getPeertubes().size() > 0 && apiResponse.getPeertubes().get(0).getErrorMessage() != null) { + Toasty.error(PeertubeActivity.this, apiResponse.getPeertubes().get(0).getErrorMessage(), Toast.LENGTH_LONG).show(); + binding.loader.setVisibility(View.GONE); + return; + } + if (apiResponse == null || (apiResponse.getError() != null) || apiResponse.getPeertubes() == null || apiResponse.getPeertubes().size() == 0) { + Toasty.error(PeertubeActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + binding.loader.setVisibility(View.GONE); + return; + } + if (apiResponse.getPeertubes() == null || apiResponse.getPeertubes().get(0) == null || (!apiResponse.getPeertubes().get(0).isLive() && apiResponse.getPeertubes().get(0).getFileUrl(null, PeertubeActivity.this) == null)) { + Toasty.error(PeertubeActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + binding.loader.setVisibility(View.GONE); + return; + } + long position = -1; + + long previousPositionHistory = 0; + if (peertube != null && peertube.getUserHistory() != null) { + previousPositionHistory = peertube.getUserHistory().getCurrentTime(); + } + peertube = apiResponse.getPeertubes().get(0); + VideoData.UserHistory userHistory = new VideoData.UserHistory(); + userHistory.setCurrentTime(previousPositionHistory); + peertube.setUserHistory(userHistory); + + + PlayerControlView controlView = binding.doubleTapPlayerView.findViewById(R.id.exo_controller); + DefaultTimeBar exo_progress = controlView.findViewById(R.id.exo_progress); + TextView exo_duration = controlView.findViewById(R.id.exo_duration); + TextView exo_position = controlView.findViewById(R.id.exo_position); + TextView exo_live_badge = controlView.findViewById(R.id.exo_live_badge); + if (peertube.isLive()) { + exo_progress.setVisibility(View.INVISIBLE); + exo_duration.setVisibility(View.GONE); + exo_live_badge.setVisibility(View.VISIBLE); + exo_live_badge.setText(R.string.live); + exo_live_badge.setBackgroundResource(R.drawable.rounded_live); + exo_position.setVisibility(View.GONE); + } else { + exo_progress.setVisibility(View.VISIBLE); + exo_live_badge.setVisibility(View.GONE); + exo_position.setVisibility(View.VISIBLE); + exo_duration.setBackground(null); + } + + if (peertube.getUserHistory() != null) { + position = peertube.getUserHistory().getCurrentTime() * 1000; + } + if (peertube.getTags() != null && peertube.getTags().size() > 0) { + SearchVM searchViewModel = new ViewModelProvider(PeertubeActivity.this).get(SearchVM.class); + searchViewModel.searchNextVideos(peertube.getTags()).observe(PeertubeActivity.this, this::manageNextVideos); + } + if (sepiaSearch) { + peertubeInstance = peertube.getAccount().getHost(); + } + List videoIds = new ArrayList<>(); + videoIds.add(peertube.getId()); + PlaylistsVM viewModel = new ViewModelProvider(this).get(PlaylistsVM.class); + viewModel.videoExists(videoIds).observe(this, this::manageVIewPlaylist); + + if (!Helper.canMakeAction(PeertubeActivity.this) || sepiaSearch) { + binding.writeCommentContainer.setVisibility(View.GONE); + } + + + if (peertube.isNsfw()) { + binding.videoSensitive.setVisibility(View.VISIBLE); + } else { + binding.videoSensitive.setVisibility(View.INVISIBLE); + } + + binding.peertubePlaylist.setOnClickListener(v -> { + PlaylistsVM viewModelOwnerPlaylist = new ViewModelProvider(PeertubeActivity.this).get(PlaylistsVM.class); + viewModelOwnerPlaylist.manage(PlaylistsVM.action.GET_PLAYLISTS, null, null).observe(PeertubeActivity.this, this::manageVIewPlaylists); + }); + + binding.videoInformation.setOnClickListener(v -> { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(PeertubeActivity.this); + LayoutInflater inflater = getLayoutInflater(); + View dialogView = inflater.inflate(R.layout.popup_video_info, new LinearLayout(PeertubeActivity.this), false); + TextView info_privacy = dialogView.findViewById(R.id.info_privacy); + TextView info_published_at = dialogView.findViewById(R.id.info_published_at); + TextView info_category = dialogView.findViewById(R.id.info_category); + TextView info_license = dialogView.findViewById(R.id.info_license); + TextView info_language = dialogView.findViewById(R.id.info_language); + TextView info_duration = dialogView.findViewById(R.id.info_duration); + TextView info_tags = dialogView.findViewById(R.id.info_tags); + + LinkedHashMap privaciesInit = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + info_privacy.setText(privaciesInit.get(peertube.getPrivacy().getId())); + LinkedHashMap licenseInit = new LinkedHashMap<>(peertubeInformation.getLicences()); + info_license.setText(licenseInit.get(peertube.getLicence().getId())); + LinkedHashMap languageStr = new LinkedHashMap<>(peertubeInformation.getLanguages()); + info_language.setText(languageStr.get(peertube.getLanguage().getId())); + LinkedHashMap categoryInit = new LinkedHashMap<>(peertubeInformation.getCategories()); + info_category.setText(categoryInit.get(peertube.getCategory().getId())); + + if (peertube.isLive()) { + info_duration.setText(R.string.live); + info_duration.setBackgroundResource(R.drawable.rounded_live); + info_duration.setBackgroundResource(R.drawable.rounded_live); + } else { + info_duration.setText(Helper.secondsToString(peertube.getDuration())); + } + + String format = DateFormat.getDateInstance(DateFormat.LONG).format(peertube.getPublishedAt()); + info_published_at.setText(format); + List tags = peertube.getTags(); + StringBuilder sb = new StringBuilder(); + for (String tag : tags) { + sb.append("#").append(tag).append(" "); + } + + SpannableString spannableString = new SpannableString(sb.toString()); + for (String tag : tags) { + String target = "#" + tag; + if (spannableString.toString().contains(target)) { + for (int startPosition = -1; (startPosition = spannableString.toString().indexOf(target, startPosition + 1)) != -1; startPosition++) { + final int endPosition = startPosition + target.length(); + if (endPosition <= spannableString.toString().length() && endPosition >= startPosition) { + spannableString.setSpan(new ClickableSpan() { + @Override + public void onClick(@NonNull View textView) { + Intent intent = new Intent(PeertubeActivity.this, SearchActivity.class); + Bundle b = new Bundle(); + String search = tag.trim(); + b.putString("search", search); + intent.putExtras(b); + startActivity(intent); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + super.updateDrawState(ds); + ds.setUnderlineText(false); + ds.setColor(getResources().getColor(R.color.colorAccent)); + } + }, + startPosition, endPosition, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + } + } + } + info_tags.setText(spannableString, TextView.BufferType.SPANNABLE); + info_tags.setMovementMethod(LinkMovementMethod.getInstance()); + dialogBuilder.setView(dialogView); + dialogBuilder.setNeutralButton(R.string.close, (dialog, id) -> dialog.dismiss()); + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.show(); + }); + + fetchComments(); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + setTitle(peertube.getName()); + + binding.peertubeDescription.setText(peertube.getDescription()); + + + binding.peertubeTitle.setText(peertube.getName()); + binding.peertubeDislikeCount.setText(Helper.withSuffix(peertube.getDislikes())); + binding.peertubeLikeCount.setText(Helper.withSuffix(peertube.getLikes())); + binding.peertubeViewCount.setText(Helper.withSuffix(peertube.getViews())); + loadAvatar(PeertubeActivity.this, peertube.getChannel(), binding.ppChannel); + binding.ppChannel.setOnClickListener(v -> { + Intent intent = new Intent(PeertubeActivity.this, ShowChannelActivity.class); + Bundle b = new Bundle(); + b.putParcelable("channel", peertube.getChannel()); + b.putBoolean("sepia_search", sepiaSearch); + if (sepiaSearch) { + b.putString("peertube_instance", peertube.getAccount().getHost()); + } + intent.putExtras(b); + startActivity(intent); + }); + + video_id = peertube.getId(); + + changeColor(); + initResolution(); + + binding.peertubeReblog.setOnClickListener(v -> { + if (status != null) { + MastodonPostActionsVM mastodonPostActionsVM = new ViewModelProvider(PeertubeActivity.this).get(MastodonPostActionsVM.class); + RetrofitMastodonAPI.actionType type = status.isReblogged() ? RetrofitMastodonAPI.actionType.UNBOOST : RetrofitMastodonAPI.actionType.BOOST; + mastodonPostActionsVM.post(type, status).observe(PeertubeActivity.this, this::manageVIewPostActionsMastodon); + } else { + Toasty.error(PeertubeActivity.this, getString(R.string.federation_issue), Toasty.LENGTH_LONG).show(); + } + }); + + binding.peertubeFavorite.setOnClickListener(v -> { + if (status != null) { + MastodonPostActionsVM mastodonPostActionsVM = new ViewModelProvider(PeertubeActivity.this).get(MastodonPostActionsVM.class); + RetrofitMastodonAPI.actionType type = status.isFavourited() ? RetrofitMastodonAPI.actionType.UNFAVOURITE : RetrofitMastodonAPI.actionType.FAVOURITE; + mastodonPostActionsVM.post(type, status).observe(PeertubeActivity.this, this::manageVIewPostActionsMastodon); + } else { + Toasty.error(PeertubeActivity.this, getString(R.string.federation_issue), Toasty.LENGTH_LONG).show(); + } + }); + + binding.peertubeBookmark.setOnClickListener(v -> { + if (status != null) { + MastodonPostActionsVM mastodonPostActionsVM = new ViewModelProvider(PeertubeActivity.this).get(MastodonPostActionsVM.class); + RetrofitMastodonAPI.actionType type = status.isBookmarked() ? RetrofitMastodonAPI.actionType.UNBOOKMARK : RetrofitMastodonAPI.actionType.BOOKMARK; + mastodonPostActionsVM.post(type, status).observe(PeertubeActivity.this, this::manageVIewPostActionsMastodon); + } else { + Toasty.error(PeertubeActivity.this, getString(R.string.federation_issue), Toasty.LENGTH_LONG).show(); + } + }); + + binding.peertubeLikeCount.setOnClickListener(v -> { + if (isLoggedIn(PeertubeActivity.this) && !sepiaSearch) { + String newState = peertube.getMyRating().equals("like") ? "none" : "like"; + PostActionsVM viewModelLike = new ViewModelProvider(PeertubeActivity.this).get(PostActionsVM.class); + viewModelLike.post(RATEVIDEO, peertube.getId(), newState).observe(PeertubeActivity.this, apiResponse1 -> manageVIewPostActions(RATEVIDEO, 0, apiResponse1)); + peertube.setMyRating(newState); + int count = Integer.parseInt(binding.peertubeLikeCount.getText().toString()); + if (newState.compareTo("none") == 0) { + count--; + if (count - 1 < 0) { + count = 0; + } + } else { + count++; + } + binding.peertubeLikeCount.setText(String.valueOf(count)); + changeColor(); + } else { + if (sepiaSearch) { + Toasty.info(PeertubeActivity.this, getString(R.string.federation_issue), Toasty.LENGTH_SHORT).show(); + } else { + Toasty.error(PeertubeActivity.this, getString(R.string.not_logged_in), Toast.LENGTH_SHORT).show(); + } + } + }); + binding.peertubeDislikeCount.setOnClickListener(v -> { + if (isLoggedIn(PeertubeActivity.this) && !sepiaSearch) { + String newState = peertube.getMyRating().equals("dislike") ? "none" : "dislike"; + PostActionsVM viewModelLike = new ViewModelProvider(PeertubeActivity.this).get(PostActionsVM.class); + viewModelLike.post(RATEVIDEO, peertube.getId(), newState).observe(PeertubeActivity.this, apiResponse1 -> manageVIewPostActions(RATEVIDEO, 0, apiResponse1)); + peertube.setMyRating(newState); + int count = Integer.parseInt(binding.peertubeDislikeCount.getText().toString()); + if (newState.compareTo("none") == 0) { + count--; + if (count - 1 < 0) { + count = 0; + } + } else { + count++; + } + binding.peertubeDislikeCount.setText(String.valueOf(count)); + changeColor(); + } else { + if (sepiaSearch) { + Toasty.info(PeertubeActivity.this, getString(R.string.federation_issue), Toasty.LENGTH_SHORT).show(); + } else { + Toasty.error(PeertubeActivity.this, getString(R.string.not_logged_in), Toast.LENGTH_SHORT).show(); + } + } + }); + + if (BuildConfig.allow_remote_connections && Helper.isLoggedInType(PeertubeActivity.this) == MainActivity.TypeOfConnection.REMOTE_ACCOUNT) { + String url = "https://" + peertube.getChannel().getHost() + "/videos/watch/" + peertube.getUuid(); + MastodonPostActionsVM postActionsVM = new ViewModelProvider(PeertubeActivity.this).get(MastodonPostActionsVM.class); + postActionsVM.searchRemoteStatus(url).observe(PeertubeActivity.this, this::retrieveRemoteStatus); + } + + if (mode != Helper.VIDEO_MODE_WEBVIEW) { + + player = new SimpleExoPlayer.Builder(PeertubeActivity.this).build(); + player.addVideoListener(PeertubeActivity.this); + player.addListener(this); + binding.mediaVideo.player(player); + binding.doubleTapPlayerView.setPlayer(player); + binding.loader.setVisibility(View.GONE); + startStream( + apiResponse.getPeertubes().get(0), + null, + autoPlay, position, null, null, true); + player.prepare(); + player.setPlayWhenReady(autoPlay); + if (autoPlay) { + binding.doubleTapPlayerView.hideController(); + } + } + + + binding.moreActions.setOnClickListener(view -> { + PopupMenu popup = new PopupMenu(PeertubeActivity.this, binding.moreActions); + popup.getMenuInflater() + .inflate(R.menu.main_video, popup.getMenu()); + + if (!isMyVideo) { + popup.getMenu().findItem(R.id.action_edit).setVisible(false); + } + + MenuItem itemDownload = popup.getMenu().findItem(R.id.action_download); + itemDownload.setEnabled(peertube != null && peertube.isDownloadEnabled()); + popup.setOnMenuItemClickListener(item -> { + int itemId = item.getItemId(); + if (itemId == R.id.action_download) { + if (Build.VERSION.SDK_INT >= 23) { + if (ContextCompat.checkSelfPermission(PeertubeActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(PeertubeActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(PeertubeActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Helper.EXTERNAL_STORAGE_REQUEST_CODE); + } else { + Helper.manageDownloads(PeertubeActivity.this, peertube.getFileDownloadUrl(null)); + } + } else { + Helper.manageDownloads(PeertubeActivity.this, peertube.getFileDownloadUrl(null)); + } + } else if (itemId == R.id.action_share) { + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.shared_via)); + boolean share_details = sharedpreferences.getBoolean(Helper.SET_SHARE_DETAILS, true); + String extra_text; + if (share_details) { + extra_text = "@" + peertube.getAccount().getAcct(); + extra_text += "\r\n\r\n" + peertube.getName(); + extra_text += "\n\n\uD83D\uDD17 https://" + peertube.getChannel().getHost() + "/videos/watch/" + peertube.getUuid() + "\r\n-\n"; + final String contentToot; + if (peertube.getDescription() != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + contentToot = Html.fromHtml(peertube.getDescription(), Html.FROM_HTML_MODE_LEGACY).toString(); + else + contentToot = Html.fromHtml(peertube.getDescription()).toString(); + } else { + contentToot = ""; + } + extra_text += contentToot; + } else { + extra_text = "https://" + peertube.getChannel().getHost() + "/videos/watch/" + peertube.getUuid(); + } + sendIntent.putExtra(Intent.EXTRA_TEXT, extra_text); + sendIntent.setType("text/plain"); + try { + startActivity(Intent.createChooser(sendIntent, getString(R.string.share_with))); + } catch (Exception e) { + Toasty.error(PeertubeActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); + } + } else if (itemId == R.id.action_edit) { + Intent intent = new Intent(PeertubeActivity.this, PeertubeEditUploadActivity.class); + Bundle b = new Bundle(); + b.putString("video_id", peertube.getUuid()); + intent.putExtras(b); + startActivity(intent); + } else if (itemId == R.id.action_report) { + AlertDialog alertDialog; + AlertDialog.Builder dialogBuilder; + dialogBuilder = new AlertDialog.Builder(PeertubeActivity.this); + LayoutInflater inflater1 = getLayoutInflater(); + View dialogView = inflater1.inflate(R.layout.popup_report_choice, new LinearLayout(PeertubeActivity.this), false); + dialogBuilder.setView(dialogView); + + dialogBuilder.setNeutralButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); + alertDialog = dialogBuilder.create(); + alertDialog.show(); + dialogView.findViewById(R.id.report_video).setOnClickListener(v -> reportAlert(REPORT_VIDEO, alertDialog)); + dialogView.findViewById(R.id.report_account).setOnClickListener(v -> reportAlert(REPORT_ACCOUNT, alertDialog)); + } + return true; + }); + popup.show(); + }); + } + + + /** + * Manage video to play with different factors + * + * @param video VideoData.Video + * @param resolution String the current resolution asked + * @param autoPlay boolean + * @param position int current position + * @param subtitles Uri uri for subtitles + * @param lang String ("en","fr", etc.) + */ + private void stream(VideoData.Video video, String resolution, boolean autoPlay, long position, Uri subtitles, String lang) { + videoURL = video.getFileUrl(resolution, PeertubeActivity.this); + if (subtitles != null) { + subtitlesStr = subtitles.toString(); + } + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + int video_cache = sharedpreferences.getInt(Helper.SET_VIDEO_CACHE, Helper.DEFAULT_VIDEO_CACHE_MB); + ProgressiveMediaSource videoSource = null; + HlsMediaSource hlsMediaSource = null; + SingleSampleMediaSource subtitleSource = null; + DataSource.Factory dataSourceFactory; + if (video_cache == 0 || video.isLive()) { + dataSourceFactory = new DefaultDataSourceFactory(PeertubeActivity.this, + Util.getUserAgent(PeertubeActivity.this, null), null); + + if (subtitles != null) { + MediaItem.Subtitle mediaSubtitle = new MediaItem.Subtitle(subtitles, MimeTypes.TEXT_VTT, lang); + subtitleSource = new SingleSampleMediaSource.Factory(dataSourceFactory).createMediaSource(mediaSubtitle, C.TIME_UNSET); + } + MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.parse(videoURL)).build(); + if (videoURL != null && !videoURL.endsWith("m3u8")) { + videoSource = new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem); + } else { + hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory) + .createMediaSource(mediaItem); + } + } else { + CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(PeertubeActivity.this); + MediaItem mediaItem = new MediaItem.Builder().setUri(videoURL).build(); + if (subtitles != null) { + MediaItem.Subtitle mediaSubtitle = new MediaItem.Subtitle(subtitles, MimeTypes.TEXT_VTT, lang, Format.NO_VALUE); + subtitleSource = new SingleSampleMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaSubtitle, C.TIME_UNSET); + } + if (videoURL != null && !videoURL.endsWith("m3u8")) { + videoSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaItem); + } else { + hlsMediaSource = new HlsMediaSource.Factory(cacheDataSourceFactory) + .createMediaSource(mediaItem); + } + } + if (lang != null && subtitleSource != null && videoSource != null) { + MergingMediaSource mergedSource = + new MergingMediaSource(videoSource, subtitleSource); + player.setMediaSource(mergedSource); + } else if (lang != null && subtitleSource != null) { + MergingMediaSource mergedSource = + new MergingMediaSource(hlsMediaSource, subtitleSource); + player.setMediaSource(mergedSource); + } else if (videoSource != null) { + player.setMediaSource(videoSource); + } else { + player.setMediaSource(hlsMediaSource); + } + player.prepare(); + if (position > 0) { + player.seekTo(0, position); + } + player.setPlayWhenReady(autoPlay); + if (autoPlay) { + binding.doubleTapPlayerView.hideController(); + } + // loadCast(video, videoURL, subtitles!=null?subtitles.toString():null); + } + + private void fetchComments() { + if (peertube.isCommentsEnabled()) { + if (Helper.canMakeAction(PeertubeActivity.this)) { + binding.postCommentButton.setVisibility(View.VISIBLE); + } else { + binding.postCommentButton.setVisibility(View.GONE); + } + CommentVM commentViewModel = new ViewModelProvider(PeertubeActivity.this).get(CommentVM.class); + commentViewModel.getThread(sepiaSearch ? peertubeInstance : null, videoUuid, max_id).observe(PeertubeActivity.this, this::manageVIewComment); + if (Helper.canMakeAction(PeertubeActivity.this) && !sepiaSearch) { + binding.writeCommentContainer.setVisibility(View.VISIBLE); + } + binding.peertubeComments.setVisibility(View.VISIBLE); + binding.noAction.setVisibility(View.GONE); + } else { + binding.postCommentButton.setVisibility(View.GONE); + binding.peertubeComments.setVisibility(View.GONE); + binding.writeCommentContainer.setVisibility(View.GONE); + binding.noActionText.setText(getString(R.string.comment_no_allowed_peertube)); + binding.noAction.setVisibility(View.VISIBLE); + binding.writeCommentContainer.setVisibility(View.GONE); + } + } + + private void startStream(VideoData.Video video, String resolution, boolean autoPlay, long position, Uri subtitles, String lang, boolean promptNSFW) { + + String videoURL = peertube.getFileUrl(resolution, PeertubeActivity.this); + if (peertube != null && peertube.isLive() && videoURL == null) { + View parentLayout = findViewById(android.R.id.content); + Snackbar snackbar = Snackbar.make(parentLayout, R.string.live_not_started, Snackbar.LENGTH_INDEFINITE); + snackbar.setAction(R.string.close, view -> finish()); + snackbar.show(); + return; + } + + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + String nsfwAction = sharedpreferences.getString(getString(R.string.set_video_sensitive_choice), Helper.BLUR); + if (promptNSFW && peertube != null && peertube.isNsfw() && (nsfwAction.compareTo(Helper.BLUR) == 0 || nsfwAction.compareTo(Helper.DO_NOT_LIST) == 0)) { + AlertDialog alertDialog; + AlertDialog.Builder dialogBuilder; + dialogBuilder = new AlertDialog.Builder(PeertubeActivity.this); + dialogBuilder.setTitle(R.string.nsfw_title_warning); + dialogBuilder.setCancelable(false); + dialogBuilder.setMessage(R.string.nsfw_message_warning); + dialogBuilder.setNegativeButton(R.string.no, (dialog, id) -> { + dialog.dismiss(); + finish(); + }); + dialogBuilder.setPositiveButton(R.string.play, (dialog, id) -> { + stream(video, resolution, autoPlay, position, subtitles, lang); + dialog.dismiss(); + }); + alertDialog = dialogBuilder.create(); + alertDialog.show(); + } else { + stream(video, resolution, autoPlay, position, subtitles, lang); + } + + + } + + @Override + public void onConfigurationChanged(@NotNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (binding.minController.castMiniController.getVisibility() == View.VISIBLE) { + return; + } + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + if (mode != Helper.VIDEO_MODE_WEBVIEW) { + openFullscreenDialog(); + } + if (initialOrientation == Configuration.ORIENTATION_LANDSCAPE) { + LinearLayout.LayoutParams param = new LinearLayout.LayoutParams( + ConstraintLayout.LayoutParams.MATCH_PARENT, + 0, + 4.0f + ); + binding.videoContainer.setLayoutParams(param); + } + } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { + if (mode != Helper.VIDEO_MODE_WEBVIEW) { + closeFullscreenDialog(); + } + if (initialOrientation == Configuration.ORIENTATION_LANDSCAPE) { + LinearLayout.LayoutParams param = new LinearLayout.LayoutParams( + ConstraintLayout.LayoutParams.MATCH_PARENT, + 0, + 1.0f + ); + binding.videoContainer.setLayoutParams(param); + } + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + if (player != null) { + player.release(); + } + unregisterReceiver(); + } + + @Override + public void onStop() { + super.onStop(); + onStopCalled = true; + } + + @Override + public void onResume() { + super.onResume(); + onStopCalled = false; + if (player != null && !player.isPlaying() && binding.minController.castMiniController.getVisibility() != View.VISIBLE) { + player.setPlayWhenReady(autoPlay); + if (autoPlay) { + binding.doubleTapPlayerView.hideController(); + } + } + } + + @Override + protected void onPause() { + super.onPause(); + if (player != null) { + updateHistory(player.getCurrentPosition() / 1000); + } + if (player != null && (!isPlayInMinimized || !playInMinimized)) { + player.setPlayWhenReady(false); + } else if (playInMinimized && binding.minController.castMiniController.getVisibility() != View.VISIBLE) { + enterVideoMode(); + } + } + + private void registBroadcastReceiver() { + final IntentFilter theFilter = new IntentFilter(); + theFilter.addAction(Intent.ACTION_SCREEN_ON); + theFilter.addAction(Intent.ACTION_SCREEN_OFF); + final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + mPowerKeyReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String strAction = intent.getAction(); + if (strAction.equals(Intent.ACTION_SCREEN_OFF)) { + if (player != null) { + if (!sharedpreferences.getBoolean(getString(R.string.set_play_screen_lock_choice), false)) { + player.setPlayWhenReady(false); + } else { + player.setWakeMode(C.WAKE_MODE_NETWORK); + } + } + } + } + }; + getApplicationContext().registerReceiver(mPowerKeyReceiver, theFilter); + } + + private void unregisterReceiver() { + if (mPowerKeyReceiver != null) { + getApplicationContext().unregisterReceiver(mPowerKeyReceiver); + mPowerKeyReceiver = null; + } + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @Override + public void onUserLeaveHint() { + enterVideoMode(); + } + + private void enterVideoMode() { + if (playInMinimized && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && player != null) { + isPlayInMinimized = true; + setRequestedOrientationCustom(initialOrientation); + MediaSessionCompat mediaSession = new MediaSessionCompat(this, getPackageName()); + MediaSessionConnector mediaSessionConnector = new MediaSessionConnector(mediaSession); + mediaSessionConnector.setPlayer(player); + PlayerControlView controlView = binding.doubleTapPlayerView.findViewById(R.id.exo_controller); + controlView.hide(); + binding.doubleTapPlayerView.setControllerAutoShow(false); + mediaSession.setActive(true); + PictureInPictureParams params = new PictureInPictureParams.Builder().build(); + enterPictureInPictureMode(params); + } + } + + @Override + public void onBackPressed() { + + if (binding.videoParamsSubmenu.getVisibility() == View.VISIBLE) { + closeSubMenuMenuOptions(); + return; + } + if (binding.videoParams.getVisibility() == View.VISIBLE) { + closeMainMenuOptions(); + return; + } + if (binding.postComment.getVisibility() == View.VISIBLE) { + closePostComment(); + return; + } + if (binding.replyThread.getVisibility() == View.VISIBLE) { + closeCommentThread(); + return; + } + + if (fullScreenMode && player != null && player.isPlaying()) { + player.setPlayWhenReady(false); + return; + } + + if (playInMinimized && player != null) { + enterVideoMode(); + } else { + super.onBackPressed(); + } + } + + @Override + public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { + if (!isInPictureInPictureMode) { + if (onStopCalled) { + isPlayInMinimized = false; + finishAndRemoveTask(); + } + } + } + + + private void toogleFullscreen(boolean fullscreen) { + + if (fullscreen) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + Objects.requireNonNull(getSupportActionBar()).hide(); + binding.bottomVideo.setVisibility(View.GONE); + Objects.requireNonNull(getSupportActionBar()).hide(); + if (videoOrientationType == videoOrientation.LANDSCAPE) { + if (getResources().getConfiguration().orientation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { + setRequestedOrientationCustom(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } + } + } else { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + Objects.requireNonNull(getSupportActionBar()).show(); + binding.bottomVideo.setVisibility(View.VISIBLE); + Objects.requireNonNull(getSupportActionBar()).show(); + } + } + + private void openFullscreenDialog() { + fullScreenIcon.setImageDrawable(ContextCompat.getDrawable(PeertubeActivity.this, R.drawable.ic_baseline_fullscreen_exit_24)); + fullScreenMode = true; + toogleFullscreen(true); + } + + private void closeFullscreenDialog() { + fullScreenMode = false; + fullScreenIcon.setImageDrawable(ContextCompat.getDrawable(PeertubeActivity.this, R.drawable.ic_baseline_fullscreen_24)); + toogleFullscreen(false); + } + + public void openCommentThread(Comment comment) { + + CommentVM commentViewModel = new ViewModelProvider(PeertubeActivity.this).get(CommentVM.class); + binding.peertubeReply.setVisibility(View.GONE); + commentViewModel.getRepliesComment(sepiaSearch ? peertubeInstance : null, videoUuid, comment.getId()).observe(PeertubeActivity.this, apiResponse -> manageVIewCommentReply(comment, apiResponse)); + + binding.replyThread.setVisibility(View.VISIBLE); + TranslateAnimation animate = new TranslateAnimation( + binding.peertubeInformationContainer.getWidth(), + 0, + 0, + 0); + animate.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + binding.peertubeInformationContainer.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + animate.setDuration(500); + binding.replyThread.startAnimation(animate); + } + + + public void openMainMenuOptions() { + binding.videoParams.setVisibility(View.VISIBLE); + DisplayMetrics displayMetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + int height = displayMetrics.heightPixels; + binding.doubleTapPlayerView.hideController(); + List menuItemVideos = new ArrayList<>(); + if (peertube.getAllFile(PeertubeActivity.this) != null && peertube.getAllFile(PeertubeActivity.this).size() > 0) { + MenuItemVideo resolutionItem = new MenuItemVideo(); + resolutionItem.setIcon(R.drawable.ic_baseline_high_quality_24); + resolutionItem.setTitle(getString(R.string.pickup_resolution)); + resolutionItem.setAction(MenuItemVideo.actionType.RESOLUTION); + menuItemVideos.add(resolutionItem); + + } + MenuItemVideo speedItem = new MenuItemVideo(); + speedItem.setIcon(R.drawable.ic_baseline_speed_24); + speedItem.setTitle(getString(R.string.playback_speed)); + speedItem.setAction(MenuItemVideo.actionType.SPEED); + menuItemVideos.add(speedItem); + + if (captions != null) { + MenuItemVideo captionItem = new MenuItemVideo(); + captionItem.setIcon(R.drawable.ic_baseline_subtitles_24); + captionItem.setTitle(getString(R.string.captions)); + captionItem.setAction(MenuItemVideo.actionType.CAPTION); + menuItemVideos.add(captionItem); + } + + MenuItemVideo autoNextItem = new MenuItemVideo(); + autoNextItem.setIcon(R.drawable.ic_baseline_play_arrow_24); + autoNextItem.setTitle(getString(R.string.set_autoplay_next_video_settings)); + autoNextItem.setAction(MenuItemVideo.actionType.AUTONEXT); + menuItemVideos.add(autoNextItem); + + MenuAdapter menuAdapter = new MenuAdapter(menuItemVideos); + binding.mainOptionsVideo.setAdapter(menuAdapter); + menuAdapter.itemClicked = this; + binding.mainOptionsVideo.setLayoutManager(new LinearLayoutManager(PeertubeActivity.this)); + + TranslateAnimation animate = new TranslateAnimation( + 0, + 0, + height, + 0); + animate.setDuration(500); + binding.videoParams.startAnimation(animate); + } + + @Override + public void onItemClicked(MenuItemVideo.actionType action) { + binding.videoParamsSubmenu.setVisibility(View.VISIBLE); + List items = new ArrayList<>(); + switch (action) { + case RESOLUTION: + binding.subMenuTitle.setText(R.string.pickup_resolution); + int position = 0; + for (File file : peertube.getFiles()) { + if (file.getResolutions() != null) { + if (file.getResolutions().getLabel().compareTo("0p") != 0) { + MenuItemView item = new MenuItemView(); + item.setId(position); + item.setLabel(file.getResolutions().getLabel()); + if (file.getResolutions().getLabel().compareTo(currentResolution) == 0) { + item.setSelected(true); + } + items.add(item); + position++; + } + } + } + break; + case SPEED: + binding.subMenuTitle.setText(R.string.playback_speed); + items = new ArrayList<>(); + items.add(new MenuItemView(25, "0.25x", player.getPlaybackParameters().speed == 0.25)); + items.add(new MenuItemView(50, "0.5x", player.getPlaybackParameters().speed == 0.5)); + items.add(new MenuItemView(75, "0.75x", player.getPlaybackParameters().speed == 0.75)); + items.add(new MenuItemView(100, getString(R.string.normal), player.getPlaybackParameters().speed == 1)); + items.add(new MenuItemView(125, "1.25x", player.getPlaybackParameters().speed == 1.25)); + items.add(new MenuItemView(150, "1.5x", player.getPlaybackParameters().speed == 1.5)); + items.add(new MenuItemView(175, "1.75x", player.getPlaybackParameters().speed == 1.75)); + items.add(new MenuItemView(200, "2x", player.getPlaybackParameters().speed == 2.0)); + break; + case CAPTION: + binding.subMenuTitle.setText(R.string.pickup_captions); + items = new ArrayList<>(); + items.add(new MenuItemView(-1, "null", getString(R.string.none), currentCaption.compareTo("null") == 0)); + int i = 0; + for (Caption caption : captions) { + items.add(new MenuItemView(i, caption.getLanguage().getId(), caption.getLanguage().getLabel(), currentCaption.compareTo(caption.getLanguage().getId()) == 0)); + } + break; + case AUTONEXT: + binding.subMenuTitle.setText(R.string.set_autoplay_next_video_settings); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + boolean autoplayNextVideo = sharedpreferences.getBoolean(getString(R.string.set_autoplay_next_video_choice), true); + items.add(new MenuItemView(0, getString(R.string.no), !autoplayNextVideo)); + items.add(new MenuItemView(1, getString(R.string.yes), autoplayNextVideo)); + break; + } + MenuItemAdapter menuItemAdapter = new MenuItemAdapter(action, items); + binding.subMenuRecycler.setAdapter(menuItemAdapter); + menuItemAdapter.itemAction = this; + binding.subMenuRecycler.setLayoutManager(new LinearLayoutManager(PeertubeActivity.this)); + + DisplayMetrics displayMetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + int height = displayMetrics.heightPixels; + TranslateAnimation animate = new TranslateAnimation( + 0, + 0, + height, + 0); + animate.setDuration(500); + binding.videoParamsSubmenu.startAnimation(animate); + } + + @Override + public void which(MenuItemVideo.actionType action, MenuItemView item) { + closeMainMenuOptions(); + switch (action) { + case RESOLUTION: + String res = item.getLabel(); + binding.loader.setVisibility(View.VISIBLE); + long position = player.getCurrentPosition(); + PlayerControlView controlView = binding.doubleTapPlayerView.findViewById(R.id.exo_controller); + TextView resolution = controlView.findViewById(R.id.resolution); + currentResolution = res; + resolution.setText(String.format("%s", res)); + if (mode == Helper.VIDEO_MODE_NORMAL) { + if (player != null) + player.release(); + player = new SimpleExoPlayer.Builder(PeertubeActivity.this).build(); + binding.mediaVideo.player(player); + binding.doubleTapPlayerView.setPlayer(player); + binding.loader.setVisibility(View.GONE); + + startStream( + peertube, + res, + true, position, captionURI, captionLang, false); + } + break; + case SPEED: + int speed = item.getId(); + float ratio = (float) speed / 100; + PlaybackParameters param = new PlaybackParameters(ratio); + if (player != null) { + player.setPlaybackParameters(param); + } + break; + case CAPTION: + Caption captionToUse = null; + for (Caption caption : captions) { + if (caption.getLanguage().getId().compareTo(item.getStrId()) == 0) { + captionToUse = caption; + break; + } + } + if (captionToUse != null) { + if (!sepiaSearch) { + captionURI = Uri.parse("https://" + HelperInstance.getLiveInstance(PeertubeActivity.this) + captionToUse.getCaptionPath()); + } else { + captionURI = Uri.parse("https://" + peertubeInstance + captionToUse.getCaptionPath()); + } + } else { + captionURI = null; + } + currentCaption = item.getStrId(); + long newPosition = player.getCurrentPosition(); + + if (player != null) + player.release(); + + TrackSelector trackSelector = new DefaultTrackSelector(PeertubeActivity.this, new AdaptiveTrackSelection.Factory()); + player = new SimpleExoPlayer.Builder(PeertubeActivity.this).setTrackSelector(trackSelector).build(); + binding.mediaVideo.player(player); + binding.doubleTapPlayerView.setPlayer(player); + captionLang = item.getStrId(); + startStream( + peertube, + null, + true, + newPosition, + captionURI, + captionLang, + false + ); + break; + case AUTONEXT: + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putBoolean(getString(R.string.set_autoplay_next_video_choice), item.getId() == 1); + editor.apply(); + if (Helper.isLoggedIn(PeertubeActivity.this)) { + new Thread(() -> { + UserSettings userSettings = new UserSettings(); + userSettings.setAutoPlayNextVideo(item.getId() == 1); + try { + RetrofitPeertubeAPI api = new RetrofitPeertubeAPI(PeertubeActivity.this); + api.updateUser(userSettings); + } catch (Exception | Error e) { + e.printStackTrace(); + } + }).start(); + } + break; + } + closeSubMenuMenuOptions(); + } + + public void closeMainMenuOptions() { + DisplayMetrics displayMetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + int height = displayMetrics.heightPixels; + TranslateAnimation animate = new TranslateAnimation( + 0, + 0, + 0, + height); + animate.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + binding.videoParams.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + animate.setDuration(500); + binding.videoParams.startAnimation(animate); + } + + + public void closeSubMenuMenuOptions() { + DisplayMetrics displayMetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + int height = displayMetrics.heightPixels; + TranslateAnimation animate = new TranslateAnimation( + 0, + 0, + 0, + height); + animate.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + binding.videoParamsSubmenu.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + animate.setDuration(500); + binding.videoParamsSubmenu.startAnimation(animate); + } + + + private void sendComment(Comment comment, int position) { + if (canMakeAction(PeertubeActivity.this) && !sepiaSearch) { + if (comment == null) { + String commentStr = binding.addCommentWrite.getText() != null ? binding.addCommentWrite.getText().toString() : ""; + if (commentStr.trim().length() > 0) { + if (Helper.isLoggedIn(PeertubeActivity.this)) { + PostActionsVM viewModelComment = new ViewModelProvider(PeertubeActivity.this).get(PostActionsVM.class); + viewModelComment.comment(ADD_COMMENT, peertube.getId(), null, commentStr).observe(PeertubeActivity.this, apiResponse1 -> manageVIewPostActions(ADD_COMMENT, 0, apiResponse1)); + } else {//Remote account is posting a message + String url = "https://" + peertube.getChannel().getHost() + "/videos/watch/" + peertube.getUuid(); + MastodonPostActionsVM viewModelCommentMastodon = new ViewModelProvider(PeertubeActivity.this).get(MastodonPostActionsVM.class); + viewModelCommentMastodon.comment(url, commentStr).observe(PeertubeActivity.this, status -> manageVIewPostActionsMastodon(ADD_COMMENT, position, status)); + } + binding.addCommentWrite.setText(""); + } + } else { + String commentView = binding.addCommentWrite.getText() != null ? binding.addCommentWrite.getText().toString() : ""; + if (commentView.trim().length() > 0) { + if (Helper.isLoggedIn(PeertubeActivity.this)) { + PostActionsVM viewModelComment = new ViewModelProvider(PeertubeActivity.this).get(PostActionsVM.class); + viewModelComment.comment(REPLY, peertube.getId(), comment.getId(), commentView).observe(PeertubeActivity.this, apiResponse1 -> manageVIewPostActions(REPLY, position, apiResponse1)); + } else {//Remote account is posting a message + MastodonPostActionsVM viewModelCommentMastodon = new ViewModelProvider(PeertubeActivity.this).get(MastodonPostActionsVM.class); + viewModelCommentMastodon.comment(comment.getUrl(), commentView).observe(PeertubeActivity.this, status -> manageVIewPostActionsMastodon(REPLY, position, status)); + } + binding.addCommentWrite.setText(""); + } + } + closePostComment(); + } else { + if (sepiaSearch) { + Toasty.info(PeertubeActivity.this, getString(R.string.federation_issue), Toasty.LENGTH_SHORT).show(); + } else { + Toasty.error(PeertubeActivity.this, getString(R.string.not_logged_in), Toast.LENGTH_SHORT).show(); + } + } + } + + private void closeCommentThread() { + binding.peertubeInformationContainer.setVisibility(View.VISIBLE); + hideKeyboard(this); + TranslateAnimation animate = new TranslateAnimation( + 0, + binding.replyThread.getWidth(), + 0, + 0); + animate.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + binding.replyThread.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + animate.setDuration(500); + binding.replyThread.startAnimation(animate); + } + + + public void openPostComment(Comment comment, int position) { + if (comment != null) { + binding.replyContent.setVisibility(View.VISIBLE); + Account account = comment.getAccount(); + Helper.loadAvatar(PeertubeActivity.this, account, binding.commentAccountProfile); + binding.commentAccountDisplayname.setText(account.getDisplayName()); + binding.commentAccountUsername.setText(account.getAcct()); + Spanned commentSpan; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + commentSpan = Html.fromHtml(comment.getText(), Html.FROM_HTML_MODE_COMPACT); + else + commentSpan = Html.fromHtml(comment.getText()); + binding.commentContent.setText(commentSpan); + binding.commentDate.setText(Helper.dateDiff(PeertubeActivity.this, comment.getCreatedAt())); + } else { + binding.replyContent.setVisibility(View.GONE); + } + binding.postComment.setVisibility(View.VISIBLE); + DisplayMetrics displayMetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + int height = displayMetrics.heightPixels; + TranslateAnimation animateComment = new TranslateAnimation( + 0, + 0, + height, + 0); + animateComment.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + binding.peertubeInformationContainer.setVisibility(View.GONE); + InputMethodManager inputMethodManager = + (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.toggleSoftInputFromWindow( + binding.addCommentWrite.getApplicationWindowToken(), + InputMethodManager.SHOW_FORCED, 0); + binding.addCommentWrite.requestFocus(); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + animateComment.setDuration(500); + binding.postComment.startAnimation(animateComment); + if (comment != null) { + binding.addCommentWrite.setText(String.format("@%s ", comment.getAccount().getAcct())); + } else { + binding.addCommentWrite.setText(String.format("@%s ", peertube.getAccount().getAcct())); + } + binding.addCommentWrite.setSelection(binding.addCommentWrite.getText() != null ? binding.addCommentWrite.getText().length() : 0); + + binding.send.setOnClickListener(null); + binding.send.setOnClickListener(v -> sendComment(comment, position)); + } + + private void closePostComment() { + DisplayMetrics displayMetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + int height = displayMetrics.heightPixels; + binding.peertubeInformationContainer.setVisibility(View.VISIBLE); + hideKeyboard(this); + TranslateAnimation animateComment = new TranslateAnimation( + 0, + 0, + 0, + height); + animateComment.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + binding.postComment.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + animateComment.setDuration(500); + binding.postComment.startAnimation(animateComment); + } + + public void manageVIewPostActionsMastodon(app.fedilab.android.peertube.client.mastodon.Status status) { + if (status != null) { + this.status = status; + changeColorMastodon(); + binding.peertubeFavorite.setText(String.valueOf(status.getFavouriteCount())); + binding.peertubeReblog.setText(String.valueOf(status.getReblogsCount())); + } else { + Toasty.error(PeertubeActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); + } + } + + public void manageVIewPostActionsMastodon(RetrofitPeertubeAPI.ActionType statusAction, int position, app.fedilab.android.peertube.client.mastodon.Status status) { + if (peertube.isCommentsEnabled() && statusAction == ADD_COMMENT) { + if (status != null) { + Comment comment = app.fedilab.android.peertube.client.mastodon.Status.convertStatusToComment(status); + comments.add(0, comment); + commentListAdapter.notifyItemInserted(0); + } + } else if (peertube.isCommentsEnabled() && statusAction == REPLY) { + if (status != null) { + Comment comment = app.fedilab.android.peertube.client.mastodon.Status.convertStatusToComment(status); + if (commentsThread == null) { + commentsThread = new ArrayList<>(); + } + commentsThread.add(position + 1, comment); + commentReplyListAdapter.notifyItemInserted(position + 1); + } + } + } + + public void retrieveRemoteStatus(app.fedilab.android.peertube.client.mastodon.Status status) { + this.status = status; + if (status != null) { + changeColorMastodon(); + binding.peertubeFavorite.setText(String.valueOf(status.getFavouriteCount())); + binding.peertubeReblog.setText(String.valueOf(status.getReblogsCount())); + } + } + + @SuppressWarnings({"unused", "RedundantSuppression"}) + public void manageVIewPostActions(RetrofitPeertubeAPI.ActionType statusAction, int position, APIResponse apiResponse) { + + if (peertube.isCommentsEnabled() && statusAction == ADD_COMMENT) { + if (apiResponse.getComments() != null && apiResponse.getComments().size() > 0) { + comments.add(0, apiResponse.getComments().get(0)); + commentListAdapter.notifyItemInserted(0); + } + } else if (peertube.isCommentsEnabled() && statusAction == REPLY) { + if (apiResponse.getComments() != null && apiResponse.getComments().size() > 0) { + commentsThread.add(position + 1, apiResponse.getComments().get(0)); + commentReplyListAdapter.notifyItemInserted(position + 1); + } + } else if (statusAction == RetrofitPeertubeAPI.ActionType.REPORT_ACCOUNT) { + Toasty.success(PeertubeActivity.this, getString(R.string.successful_report), Toasty.LENGTH_LONG).show(); + } else if (statusAction == RetrofitPeertubeAPI.ActionType.REPORT_VIDEO) { + Toasty.success(PeertubeActivity.this, getString(R.string.successful_video_report), Toasty.LENGTH_LONG).show(); + } + } + + + private void initControllerButtons() { + + PlayerControlView controlView = binding.doubleTapPlayerView.findViewById(R.id.exo_controller); + fullScreenIcon = controlView.findViewById(R.id.exo_fullscreen_icon); + View fullScreenButton = controlView.findViewById(R.id.exo_fullscreen_button); + fullScreenButton.setOnClickListener(v -> { + if (!fullScreenMode) { + openFullscreenDialog(); + } else { + closeFullscreenDialog(); + setRequestedOrientationCustom(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + }); + + ImageButton playButton = controlView.findViewById(R.id.exo_play); + playButton.setOnClickListener(v -> { + if (autoFullscreen && !fullScreenMode) { + openFullscreenDialog(); + } + player.setPlayWhenReady(true); + }); + View exo_next = controlView.findViewById(R.id.exo_next); + exo_next.setOnClickListener(v -> playNextVideo()); + + View exoSettings = controlView.findViewById(R.id.exo_settings); + exoSettings.setOnClickListener(v -> { + if (binding.videoParams.getVisibility() == View.VISIBLE) { + closeMainMenuOptions(); + } else { + openMainMenuOptions(); + } + }); + + } + + private void setRequestedOrientationCustom(int orientationCustom) { + setRequestedOrientation(orientationCustom); + Handler handler = new Handler(); + handler.postDelayed(() -> setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR), 2000); + } + + private void initResolution() { + PlayerControlView controlView = binding.doubleTapPlayerView.findViewById(R.id.exo_controller); + TextView resolution = controlView.findViewById(R.id.resolution); + if (Helper.defaultFile(PeertubeActivity.this, peertube.getFiles()) != null) { + currentResolution = Helper.defaultFile(PeertubeActivity.this, peertube.getFiles()).getResolutions().getLabel(); + if (peertube.getFiles() != null && peertube.getFiles().size() > 0) { + resolution.setText(String.format("%s", currentResolution)); + } else { + resolution.setVisibility(View.GONE); + } + } else { + resolution.setVisibility(View.GONE); + } + } + + + private void changeColorMastodon() { + Drawable reblog = ContextCompat.getDrawable(PeertubeActivity.this, R.drawable.ic_baseline_repeat_24); + Drawable favorite = ContextCompat.getDrawable(PeertubeActivity.this, R.drawable.ic_baseline_star_24); + Drawable bookmark = ContextCompat.getDrawable(PeertubeActivity.this, R.drawable.ic_baseline_bookmark_24); + + int color = getAttColor(this, android.R.attr.colorControlNormal); + + if (reblog != null) { + reblog.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + DrawableCompat.setTint(reblog, color); + } + if (favorite != null) { + favorite.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + DrawableCompat.setTint(favorite, color); + } + + if (bookmark != null) { + bookmark.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + DrawableCompat.setTint(bookmark, color); + } + + if (reblog != null && status.isReblogged()) { + reblog.setColorFilter(getResources().getColor(R.color.positive_thumbs), PorterDuff.Mode.SRC_ATOP); + DrawableCompat.setTint(reblog, getResources().getColor(R.color.positive_thumbs)); + } + if (favorite != null && status.isFavourited()) { + favorite.setColorFilter(getResources().getColor(R.color.favorite), PorterDuff.Mode.SRC_ATOP); + DrawableCompat.setTint(favorite, getResources().getColor(R.color.favorite)); + } + + if (bookmark != null && status.isBookmarked()) { + bookmark.setColorFilter(getResources().getColor(R.color.bookmark), PorterDuff.Mode.SRC_ATOP); + DrawableCompat.setTint(bookmark, getResources().getColor(R.color.bookmark)); + } + + binding.peertubeReblog.setCompoundDrawablesWithIntrinsicBounds(null, reblog, null, null); + binding.peertubeFavorite.setCompoundDrawablesWithIntrinsicBounds(null, favorite, null, null); + binding.peertubeBookmark.setCompoundDrawablesWithIntrinsicBounds(null, bookmark, null, null); + } + + private void changeColor() { + + Drawable thumbUp = ContextCompat.getDrawable(PeertubeActivity.this, R.drawable.ic_baseline_thumb_up_alt_24); + Drawable thumbDown = ContextCompat.getDrawable(PeertubeActivity.this, R.drawable.ic_baseline_thumb_down_alt_24); + int color = getAttColor(this, android.R.attr.colorControlNormal); + + if (thumbUp != null) { + thumbUp.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + DrawableCompat.setTint(thumbUp, color); + } + if (thumbDown != null) { + thumbDown.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + DrawableCompat.setTint(thumbDown, color); + } + if (peertube.getMyRating() != null && peertube.getMyRating().compareTo("like") == 0) { + if (thumbUp != null) { + thumbUp.setColorFilter(getResources().getColor(R.color.positive_thumbs), PorterDuff.Mode.SRC_ATOP); + DrawableCompat.setTint(thumbUp, getResources().getColor(R.color.positive_thumbs)); + } + } else if (peertube.getMyRating() != null && peertube.getMyRating().compareTo("dislike") == 0) { + if (thumbDown != null) { + thumbDown.setColorFilter(getResources().getColor(R.color.negative_thumbs), PorterDuff.Mode.SRC_ATOP); + DrawableCompat.setTint(thumbDown, getResources().getColor(R.color.negative_thumbs)); + } + } + binding.peertubeLikeCount.setCompoundDrawablesWithIntrinsicBounds(null, thumbUp, null, null); + binding.peertubeDislikeCount.setCompoundDrawablesWithIntrinsicBounds(null, thumbDown, null, null); + } + + public void manageVIewPlaylists(APIResponse apiResponse) { + if (apiResponse == null || apiResponse.getError() != null || playlists == null || peertube == null) { + return; + } + if (apiResponse.getPlaylists() != null && apiResponse.getPlaylists().size() > 0) { + AlertDialog.Builder builder = new AlertDialog.Builder(PeertubeActivity.this); + builder.setTitle(R.string.modify_playlists); + + List ownerPlaylists = apiResponse.getPlaylists(); + if (ownerPlaylists == null) { + return; + } + String[] label = new String[ownerPlaylists.size()]; + boolean[] checked = new boolean[ownerPlaylists.size()]; + int i = 0; + List playlistsForVideo = playlists.get(peertube.getId()); + + + for (PlaylistData.Playlist playlist : ownerPlaylists) { + checked[i] = false; + if (playlistsForVideo != null) { + for (PlaylistExist playlistExist : playlistsForVideo) { + if (playlistExist != null && playlistExist.getPlaylistId().compareTo(playlist.getId()) == 0) { + checked[i] = true; + break; + } + } + } + label[i] = playlist.getDisplayName(); + i++; + } + + builder.setMultiChoiceItems(label, checked, (dialog, which, isChecked) -> { + PlaylistsVM playlistsViewModel = new ViewModelProvider(PeertubeActivity.this).get(PlaylistsVM.class); + if (isChecked) { //Add to playlist + playlistsViewModel.manage(PlaylistsVM.action.ADD_VIDEOS, ownerPlaylists.get(which), peertube.getUuid()).observe(PeertubeActivity.this, apiResponse3 -> addElement(ownerPlaylists.get(which).getId(), peertube.getId(), apiResponse3)); + } else { //Remove from playlist + String elementInPlaylistId = null; + for (PlaylistExist playlistExist : peertube.getPlaylistExists()) { + if (playlistExist.getPlaylistId().compareTo(ownerPlaylists.get(which).getId()) == 0) { + elementInPlaylistId = playlistExist.getPlaylistElementId(); + } + } + playlistsViewModel.manage(PlaylistsVM.action.DELETE_VIDEOS, ownerPlaylists.get(which), elementInPlaylistId); + playlists.remove(peertube.getId()); + } + }); + builder.setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss()); + AlertDialog dialog = builder.create(); + dialog.show(); + } + } + + public void manageVIewPlaylist(APIResponse apiResponse) { + if (apiResponse.getError() != null || apiResponse.getVideoExistPlaylist() == null) { + return; + } + if (playlists == null) { + playlists = new HashMap<>(); + } + playlists.putAll(apiResponse.getVideoExistPlaylist()); + peertube.setPlaylistExists(playlists.get(peertube.getId())); + + } + + public void addElement(String playlistId, String videoId, APIResponse apiResponse) { + if (apiResponse != null && apiResponse.getActionReturn() != null) { + + PlaylistExist playlistExist = new PlaylistExist(); + playlistExist.setPlaylistId(playlistId); + playlistExist.setPlaylistElementId(apiResponse.getActionReturn()); + List playlistExistList = playlists.get(videoId); + if (playlistExistList == null) { + playlistExistList = new ArrayList<>(); + } + playlistExistList.add(playlistExist); + playlists.put(videoId, playlistExistList); + } + } + + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + + if (MotionEvent.ACTION_UP == event.getAction()) { + Rect viewRectParams = new Rect(); + binding.videoParams.getGlobalVisibleRect(viewRectParams); + if (binding.videoParams.getVisibility() == View.VISIBLE && !viewRectParams.contains((int) event.getRawX(), (int) event.getRawY())) { + closeMainMenuOptions(); + if (binding.videoParamsSubmenu.getVisibility() == View.VISIBLE) { + closeSubMenuMenuOptions(); + } + } + Rect viewRectParamsSub = new Rect(); + binding.videoParamsSubmenu.getGlobalVisibleRect(viewRectParamsSub); + if (binding.videoParamsSubmenu.getVisibility() == View.VISIBLE && !viewRectParamsSub.contains((int) event.getRawX(), (int) event.getRawY())) { + closeSubMenuMenuOptions(); + if (binding.videoParams.getVisibility() == View.VISIBLE) { + closeMainMenuOptions(); + } + } + } + return super.dispatchTouchEvent(event); + } + + private void updateHistory(long position) { + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + boolean storeInHistory = sharedpreferences.getBoolean(getString(R.string.set_store_in_history), true); + if (Helper.isLoggedIn(PeertubeActivity.this) && peertube != null && storeInHistory) { + new Thread(() -> { + try { + RetrofitPeertubeAPI api = new RetrofitPeertubeAPI(PeertubeActivity.this); + api.updateHistory(peertube.getUuid(), position); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + } + + @Override + public void onAllCommentRemoved() { + binding.noActionText.setVisibility(View.VISIBLE); + } + + private void playNextVideo() { + if (nextVideo != null) { + Intent intent = new Intent(PeertubeActivity.this, PeertubeActivity.class); + Bundle b = new Bundle(); + b.putParcelable("video", nextVideo); + b.putString("video_id", nextVideo.getId()); + b.putString("video_uuid", nextVideo.getUuid()); + playedVideos.add(nextVideo.getId()); + b.putBoolean("sepia_search", sepiaSearch); + intent.putExtras(b); + startActivity(intent); + } + } + + @Override + public void onMediaItemTransition(MediaItem mediaItem, int reason) { + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + boolean autoplayNextVideo = sharedpreferences.getBoolean(getString(R.string.set_autoplay_next_video_choice), true); + if (reason == MEDIA_ITEM_TRANSITION_REASON_AUTO) { + player.removeMediaItems(0, player.getMediaItemCount()); + if (!sepiaSearch && autoplayNextVideo) { + playNextVideo(); + } + } + } + + @Override + public void onPlayerError(ExoPlaybackException error) { + + } + + + enum videoOrientation { + LANDSCAPE, + PORTRAIT + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeEditUploadActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeEditUploadActivity.java new file mode 100644 index 000000000..53b5aaa56 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeEditUploadActivity.java @@ -0,0 +1,645 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.PEERTUBEDELETEVIDEO; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.DataType.MY_CHANNELS; +import static app.fedilab.android.peertube.helper.Helper.peertubeInformation; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.APIResponse; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.data.ChannelData.Channel; +import app.fedilab.android.peertube.client.data.VideoData; +import app.fedilab.android.peertube.client.data.VideoData.Video; +import app.fedilab.android.peertube.client.entities.Item; +import app.fedilab.android.peertube.client.entities.ItemStr; +import app.fedilab.android.peertube.client.entities.VideoParams; +import app.fedilab.android.peertube.databinding.ActivityPeertubeEditBinding; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.viewmodel.ChannelsVM; +import app.fedilab.android.peertube.viewmodel.MyVideoVM; +import app.fedilab.android.peertube.viewmodel.PostActionsVM; +import app.fedilab.android.peertube.viewmodel.TimelineVM; +import es.dmoral.toasty.Toasty; + + +public class PeertubeEditUploadActivity extends BaseActivity { + + + private final int PICK_IMAGE = 50378; + private final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 724; + Item licenseToSend, privacyToSend, categoryToSend; + ItemStr languageToSend; + private LinkedHashMap channels; + private String videoId; + private Channel channel; + private VideoParams videoParams; + private Video video; + private String channelToSendId; + private ActivityPeertubeEditBinding binding; + private Uri inputData; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + binding = ActivityPeertubeEditBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + + Bundle b = getIntent().getExtras(); + + if (b != null) { + videoId = b.getString("video_id", null); + } + if (videoId == null) { + videoId = sharedpreferences.getString(Helper.VIDEO_ID, null); + sharedpreferences.edit().remove(Helper.VIDEO_ID).apply(); + } + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + binding.setUploadDelete.setOnClickListener(v -> { + AlertDialog.Builder builderInner; + builderInner = new AlertDialog.Builder(PeertubeEditUploadActivity.this); + builderInner.setMessage(getString(R.string.delete_video_confirmation)); + builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + builderInner.setPositiveButton(R.string.yes, (dialog, which) -> { + PostActionsVM viewModel = new ViewModelProvider(PeertubeEditUploadActivity.this).get(PostActionsVM.class); + viewModel.post(PEERTUBEDELETEVIDEO, videoId, null).observe(PeertubeEditUploadActivity.this, apiResponse -> manageVIewPostActions(PEERTUBEDELETEVIDEO, apiResponse)); + dialog.dismiss(); + }); + builderInner.show(); + }); + //Get params from the API + LinkedHashMap categories = new LinkedHashMap<>(peertubeInformation.getCategories()); + LinkedHashMap licences = new LinkedHashMap<>(peertubeInformation.getLicences()); + LinkedHashMap privacies = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + LinkedHashMap languages = new LinkedHashMap<>(peertubeInformation.getLanguages()); + LinkedHashMap translations = null; + + if (peertubeInformation.getTranslations() != null) + translations = new LinkedHashMap<>(peertubeInformation.getTranslations()); + //Populate catgories + String[] categoriesA = new String[categories.size()]; + Iterator> it = categories.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (translations == null || translations.size() == 0 || !translations.containsKey(pair.getValue())) + categoriesA[i] = pair.getValue(); + else + categoriesA[i] = translations.get(pair.getValue()); + it.remove(); + i++; + } + ArrayAdapter adapterCatgories = new ArrayAdapter<>(PeertubeEditUploadActivity.this, + android.R.layout.simple_spinner_dropdown_item, categoriesA); + binding.setUploadCategories.setAdapter(adapterCatgories); + + + //Populate licenses + String[] licensesA = new String[licences.size()]; + it = licences.entrySet().iterator(); + i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (translations == null || translations.size() == 0 || !translations.containsKey(pair.getValue())) + licensesA[i] = pair.getValue(); + else + licensesA[i] = translations.get(pair.getValue()); + it.remove(); + i++; + } + ArrayAdapter adapterLicenses = new ArrayAdapter<>(PeertubeEditUploadActivity.this, + android.R.layout.simple_spinner_dropdown_item, licensesA); + binding.setUploadLicenses.setAdapter(adapterLicenses); + + + //Populate languages + String[] languagesA = new String[languages.size()]; + Iterator> itl = languages.entrySet().iterator(); + i = 0; + while (itl.hasNext()) { + Map.Entry pair = itl.next(); + if (translations == null || translations.size() == 0 || !translations.containsKey(pair.getValue())) + languagesA[i] = pair.getValue(); + else + languagesA[i] = translations.get(pair.getValue()); + itl.remove(); + i++; + } + ArrayAdapter adapterLanguages = new ArrayAdapter<>(PeertubeEditUploadActivity.this, + android.R.layout.simple_spinner_dropdown_item, languagesA); + binding.setUploadLanguages.setAdapter(adapterLanguages); + + + //Populate languages + String[] privaciesA = new String[privacies.size()]; + it = privacies.entrySet().iterator(); + i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (translations == null || translations.size() == 0 || !translations.containsKey(pair.getValue())) + privaciesA[i] = pair.getValue(); + else + privaciesA[i] = translations.get(pair.getValue()); + it.remove(); + i++; + } + + ArrayAdapter adapterPrivacies = new ArrayAdapter<>(PeertubeEditUploadActivity.this, + android.R.layout.simple_spinner_dropdown_item, privaciesA); + binding.setUploadPrivacy.setAdapter(adapterPrivacies); + + + TimelineVM feedsViewModel = new ViewModelProvider(PeertubeEditUploadActivity.this).get(TimelineVM.class); + feedsViewModel.getMyVideo(null, videoId).observe(PeertubeEditUploadActivity.this, this::manageVIewVideo); + + channels = new LinkedHashMap<>(); + + setTitle(R.string.edit_video); + } + + public void manageUpdate(APIResponse apiResponse) { + binding.setUploadSubmit.setEnabled(true); + if (apiResponse.getError() != null) { + if (apiResponse.getError() != null && apiResponse.getError().getError() != null) + Toasty.error(PeertubeEditUploadActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + else + Toasty.error(PeertubeEditUploadActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + } + Toasty.info(PeertubeEditUploadActivity.this, getString(R.string.toast_peertube_video_updated), Toast.LENGTH_LONG).show(); + } + + public void manageVIewVideo(APIResponse apiResponse) { + if (apiResponse.getError() != null || apiResponse.getPeertubes() == null || apiResponse.getPeertubes().size() == 0) { + if (apiResponse.getError() != null && apiResponse.getError().getError() != null) + Toasty.error(PeertubeEditUploadActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + else + Toasty.error(PeertubeEditUploadActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + binding.setUploadSubmit.setEnabled(true); + return; + } + + //Peertube video + video = apiResponse.getPeertubes().get(0); + + ChannelsVM viewModelC = new ViewModelProvider(PeertubeEditUploadActivity.this).get(ChannelsVM.class); + viewModelC.get(MY_CHANNELS, null).observe(PeertubeEditUploadActivity.this, this::manageVIewChannels); + + languageToSend = video.getLanguage(); + licenseToSend = video.getLicence(); + privacyToSend = video.getPrivacy(); + categoryToSend = video.getCategory(); + + if (video.getThumbnailPath() != null) { + Helper.loadGiF(PeertubeEditUploadActivity.this, video.getThumbnailPath(), binding.pVideoPreview); + } + + + binding.setPreview.setOnClickListener(v -> { + if (ContextCompat.checkSelfPermission(PeertubeEditUploadActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != + PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(PeertubeEditUploadActivity.this, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); + return; + } + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("image/jpg"); + String[] mimetypes = {"image/jpg", "image/jpeg"}; + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); + startActivityForResult(intent, PICK_IMAGE); + + }); + if (languageToSend == null) { + languageToSend = new ItemStr(); + LinkedHashMap languages = new LinkedHashMap<>(peertubeInformation.getLanguages()); + Map.Entry entryString = languages.entrySet().iterator().next(); + languageToSend.setId(entryString.getKey()); + languageToSend.setLabel(entryString.getValue()); + } + + if (licenseToSend == null) { + licenseToSend = new Item(); + LinkedHashMap licences = new LinkedHashMap<>(peertubeInformation.getLicences()); + Map.Entry entryInt = licences.entrySet().iterator().next(); + licenseToSend.setId(entryInt.getKey()); + licenseToSend.setLabel(entryInt.getValue()); + } + + if (categoryToSend == null) { + categoryToSend = new Item(); + LinkedHashMap categories = new LinkedHashMap<>(peertubeInformation.getCategories()); + Map.Entry entryInt = categories.entrySet().iterator().next(); + categoryToSend.setId(entryInt.getKey()); + categoryToSend.setLabel(entryInt.getValue()); + } + if (privacyToSend == null) { + privacyToSend = new Item(); + LinkedHashMap privacies = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + Map.Entry entryInt = privacies.entrySet().iterator().next(); + privacyToSend.setId(entryInt.getKey()); + privacyToSend.setLabel(entryInt.getValue()); + } + + String language = languageToSend.getId(); + int license = licenseToSend.getId(); + int privacy = privacyToSend.getId(); + int category = categoryToSend.getId(); + + channel = video.getChannel(); + String title = video.getName(); + boolean commentEnabled = video.isCommentsEnabled(); + boolean isNSFW = video.isNsfw(); + + binding.setUploadEnableComments.setChecked(commentEnabled); + binding.setUploadNsfw.setChecked(isNSFW); + + binding.pVideoTitle.setText(title); + binding.pVideoDescription.setText(video.getDescription()); + + new Thread(() -> { + try { + RetrofitPeertubeAPI api; + api = new RetrofitPeertubeAPI(PeertubeEditUploadActivity.this); + VideoData.Description description = api.getVideoDescription(video.getUuid()); + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + if (description != null) { + binding.pVideoDescription.setText(description.getDescription()); + } + }; + mainHandler.post(myRunnable); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + + LinkedHashMap categories = new LinkedHashMap<>(peertubeInformation.getCategories()); + LinkedHashMap licences = new LinkedHashMap<>(peertubeInformation.getLicences()); + LinkedHashMap privacies = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + LinkedHashMap languages = new LinkedHashMap<>(peertubeInformation.getLanguages()); + + + int languagePosition = 0; + + if (languages.containsKey(language)) { + Iterator> itstr = languages.entrySet().iterator(); + while (itstr.hasNext()) { + Map.Entry pair = itstr.next(); + if (pair.getKey().compareTo(language) == 0) + break; + itstr.remove(); + languagePosition++; + } + } + int privacyPosition = 0; + if (privacies.containsKey(privacy)) { + Iterator> it = privacies.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (pair.getKey() == privacy) + break; + it.remove(); + privacyPosition++; + } + } + int licensePosition = 0; + if (licences.containsKey(license)) { + Iterator> it = licences.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (pair.getKey() == license) + break; + it.remove(); + licensePosition++; + } + } + int categoryPosition = 0; + if (categories.containsKey(category)) { + Iterator> it = categories.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (pair.getKey() == category) + break; + it.remove(); + categoryPosition++; + } + } + //Manage privacies + binding.setUploadPrivacy.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + updatePrivacyPosition(position); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + binding.setUploadLicenses.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + updateLicensePosition(position); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + //Manage categories + binding.setUploadCategories.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + updateCategoryPosition(position); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + //Manage languages + binding.setUploadLanguages.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + updateLanguagesPosition(position); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + //Manage languages + binding.setUploadChannel.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + + Item finalCategoryToSend = categoryToSend; + Item finalLicenseToSend = licenseToSend; + ItemStr finalLanguageToSend = languageToSend; + Item finalPrivacyToSend = privacyToSend; + binding.setUploadSubmit.setOnClickListener(v -> { + String title1 = binding.pVideoTitle.getText() != null ? binding.pVideoTitle.getText().toString().trim() : ""; + String description = binding.pVideoDescription.getText() != null ? binding.pVideoDescription.getText().toString().trim() : ""; + boolean isNSFW1 = binding.setUploadNsfw.isChecked(); + boolean commentEnabled1 = binding.setUploadEnableComments.isChecked(); + videoParams = new VideoParams(); + videoParams.setName(title1); + videoParams.setDescription(description); + videoParams.setNsfw(isNSFW1); + videoParams.setCommentsEnabled(commentEnabled1); + videoParams.setCategory(finalCategoryToSend.getId()); + videoParams.setLicence(String.valueOf(finalLicenseToSend.getId())); + videoParams.setLanguage(finalLanguageToSend.getId()); + videoParams.setChannelId(channelToSendId); + videoParams.setPrivacy(finalPrivacyToSend.getId()); + List tags = binding.pVideoTags.getTags(); + if (tags.size() > 5) { + Toasty.error(PeertubeEditUploadActivity.this, getString(R.string.max_tag_size), Toast.LENGTH_LONG).show(); + return; + } + videoParams.setTags(tags); + binding.setUploadSubmit.setEnabled(false); + MyVideoVM myVideoVM = new ViewModelProvider(PeertubeEditUploadActivity.this).get(MyVideoVM.class); + myVideoVM.updateVideo(videoId, videoParams, inputData, inputData).observe(PeertubeEditUploadActivity.this, this::manageUpdate); + }); + + binding.setUploadPrivacy.setSelection(privacyPosition, false); + updatePrivacyPosition(privacyPosition); + binding.setUploadLanguages.setSelection(languagePosition, false); + updateLanguagesPosition(languagePosition); + binding.setUploadLicenses.setSelection(licensePosition, false); + updateLicensePosition(licensePosition); + binding.setUploadCategories.setSelection(categoryPosition, false); + updateCategoryPosition(categoryPosition); + + List tags = video.getTags(); + if (tags != null && tags.size() > 0) { + binding.pVideoTags.setTags(tags.toArray(new String[0])); + } + } + + private void updateUploadChannel(int position) { + LinkedHashMap channelsCheck = new LinkedHashMap<>(channels); + Iterator> it = channelsCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position) { + channelToSendId = pair.getValue(); + break; + } + it.remove(); + i++; + } + } + + private void updateLanguagesPosition(int position) { + LinkedHashMap languagesCheck = new LinkedHashMap<>(peertubeInformation.getLanguages()); + Iterator> it = languagesCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position) { + languageToSend.setId(pair.getKey()); + languageToSend.setLabel(pair.getValue()); + break; + } + it.remove(); + i++; + } + } + + private void updateCategoryPosition(int position) { + LinkedHashMap categoriesCheck = new LinkedHashMap<>(peertubeInformation.getCategories()); + Iterator> it = categoriesCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position) { + categoryToSend.setId(pair.getKey()); + categoryToSend.setLabel(pair.getValue()); + break; + } + it.remove(); + i++; + } + } + + private void updateLicensePosition(int position) { + LinkedHashMap licensesCheck = new LinkedHashMap<>(peertubeInformation.getLicences()); + Iterator> it = licensesCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position) { + licenseToSend.setId(pair.getKey()); + licenseToSend.setLabel(pair.getValue()); + break; + } + it.remove(); + i++; + } + } + + private void updatePrivacyPosition(int position) { + LinkedHashMap privaciesCheck = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + Iterator> it = privaciesCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position) { + privacyToSend.setId(pair.getKey()); + privacyToSend.setLabel(pair.getValue()); + break; + } + it.remove(); + i++; + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) { + if (data == null || data.getData() == null) { + Toasty.error(PeertubeEditUploadActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); + return; + } + inputData = data.getData(); + Glide.with(PeertubeEditUploadActivity.this) + .load(data.getData()) + .thumbnail(0.1f) + .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) + .into(binding.pVideoPreview); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + + public void manageVIewChannels(APIResponse apiResponse) { + if (apiResponse.getError() != null || apiResponse.getChannels() == null || apiResponse.getChannels().size() == 0) { + if (apiResponse.getError() != null && apiResponse.getError().getError() != null) + Toasty.error(PeertubeEditUploadActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + else + Toasty.error(PeertubeEditUploadActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + return; + } + //Populate channels + List channelsReply = apiResponse.getChannels(); + String[] channelName = new String[channelsReply.size()]; + int i = 0; + for (Channel channel : channelsReply) { + channels.put(channel.getName(), channel.getId()); + channelName[i] = channel.getName(); + i++; + } + ArrayAdapter adapterChannel = new ArrayAdapter<>(PeertubeEditUploadActivity.this, + android.R.layout.simple_spinner_dropdown_item, channelName); + binding.setUploadChannel.setAdapter(adapterChannel); + int channelPosition = 0; + if (channels.containsKey(channel.getName())) { + LinkedHashMap channelsIterator = new LinkedHashMap<>(channels); + Iterator> it = channelsIterator.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (pair.getKey().equals(channel.getName())) { + channelToSendId = pair.getKey(); + break; + } + it.remove(); + channelPosition++; + } + } + binding.setUploadChannel.setSelection(channelPosition, false); + updateUploadChannel(channelPosition); + binding.setUploadSubmit.setEnabled(true); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + finish(); + } + + @SuppressWarnings({"unused", "RedundantSuppression"}) + public void manageVIewPostActions(RetrofitPeertubeAPI.ActionType statusAction, APIResponse apiResponse) { + Intent intent = new Intent(PeertubeEditUploadActivity.this, MainActivity.class); + intent.putExtra(Helper.INTENT_ACTION, Helper.RELOAD_MYVIDEOS); + startActivity(intent); + finish(); + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeRegisterActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeRegisterActivity.java new file mode 100644 index 000000000..fdc8f9343 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeRegisterActivity.java @@ -0,0 +1,283 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.activities.MainActivity.PICK_INSTANCE; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.util.Patterns; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import app.fedilab.android.peertube.BuildConfig; +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.APIResponse; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.entities.AccountCreation; +import app.fedilab.android.peertube.databinding.ActivityRegisterPeertubeBinding; +import app.fedilab.android.peertube.helper.HelperAcadInstance; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import es.dmoral.toasty.Toasty; + +public class PeertubeRegisterActivity extends BaseActivity { + + + private String instance; + private ActivityRegisterPeertubeBinding binding; + + + @SuppressLint("SetTextI18n") + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + binding = ActivityRegisterPeertubeBinding.inflate(getLayoutInflater()); + View mainView = binding.getRoot(); + setContentView(mainView); + + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + + if (BuildConfig.full_instances && BuildConfig.instance_switcher) { + binding.loginInstanceContainer.setVisibility(View.VISIBLE); + binding.titleLoginInstance.setVisibility(View.VISIBLE); + } else { + binding.loginInstanceContainer.setVisibility(View.GONE); + binding.titleLoginInstance.setVisibility(View.GONE); + } + + + binding.username.setOnFocusChangeListener((view, focused) -> { + if (!focused && binding.username.getText() != null) { + Pattern patternUsername = Pattern.compile("^[a-z0-9._]{1,50}$"); + Matcher matcherMaxId = patternUsername.matcher(binding.username.getText().toString()); + if (!matcherMaxId.matches()) { + binding.username.setError(getString(R.string.username_error)); + } + } + }); + + + binding.instanceHelp.setOnClickListener(v -> { + Intent intent = new Intent(PeertubeRegisterActivity.this, InstancePickerActivity.class); + startActivityForResult(intent, PICK_INSTANCE); + }); + + binding.email.setOnFocusChangeListener((view, focused) -> { + if (!focused && binding.email.getText() != null) { + Pattern patternUsername = Patterns.EMAIL_ADDRESS; + Matcher matcherMaxId = patternUsername.matcher(binding.email.getText().toString()); + if (!matcherMaxId.matches()) { + binding.email.setError(getString(R.string.email_error)); + } + } + }); + + binding.password.setOnFocusChangeListener((view, focused) -> { + if (!focused && binding.password.getText() != null) { + if (binding.password.getText().length() < 6) { + binding.password.setError(getString(R.string.password_length_error)); + } + } + }); + + binding.passwordConfirm.setOnFocusChangeListener((view, focused) -> { + if (!focused && binding.passwordConfirm.getText() != null && binding.password.getText() != null) { + if (binding.passwordConfirm.getText().toString().compareTo(binding.password.getText().toString()) != 0) { + binding.passwordConfirm.setError(getString(R.string.password)); + } + } + }); + setTextAgreement(); + binding.signup.setOnClickListener(view -> { + binding.errorMessage.setVisibility(View.GONE); + if (binding.username.getText() == null || binding.email.getText() == null || binding.password.getText() == null || binding.passwordConfirm.getText() == null || binding.username.getText().toString().trim().length() == 0 || binding.email.getText().toString().trim().length() == 0 || + binding.password.getText().toString().trim().length() == 0 || binding.passwordConfirm.getText().toString().trim().length() == 0 || !binding.agreement.isChecked()) { + Toasty.error(PeertubeRegisterActivity.this, getString(R.string.all_field_filled)).show(); + return; + } + + if (!binding.password.getText().toString().trim().equals(binding.passwordConfirm.getText().toString().trim())) { + Toasty.error(PeertubeRegisterActivity.this, getString(R.string.password_error)).show(); + return; + } + if (!Patterns.EMAIL_ADDRESS.matcher(binding.email.getText().toString().trim()).matches()) { + Toasty.error(PeertubeRegisterActivity.this, getString(R.string.email_error)).show(); + return; + } + String[] emailArray = binding.email.getText().toString().split("@"); + if (!BuildConfig.full_instances) { + if (emailArray.length > 1 && !Arrays.asList(HelperAcadInstance.valideEmails).contains(emailArray[1])) { + Toasty.error(PeertubeRegisterActivity.this, getString(R.string.email_error_domain, emailArray[1])).show(); + return; + } + } + + if (binding.password.getText().toString().trim().length() < 8) { + Toasty.error(PeertubeRegisterActivity.this, getString(R.string.password_too_short)).show(); + return; + } + if (binding.username.getText().toString().matches("[a-z0-9_]")) { + Toasty.error(PeertubeRegisterActivity.this, getString(R.string.username_error)).show(); + return; + } + binding.signup.setEnabled(false); + + if (BuildConfig.full_instances) { + if (binding.loginInstance.getText() != null) { + instance = binding.loginInstance.getText().toString(); + } else { + instance = ""; + } + binding.loginInstance.setOnFocusChangeListener((view1, focus) -> { + if (!focus) { + setTextAgreement(); + } + }); + } else { + instance = HelperInstance.getLiveInstance(PeertubeRegisterActivity.this); + } + if (instance != null) { + instance = instance.toLowerCase().trim(); + } + + AccountCreation accountCreation = new AccountCreation(); + accountCreation.setEmail(binding.email.getText().toString().trim()); + accountCreation.setPassword(binding.password.getText().toString().trim()); + accountCreation.setPasswordConfirm(binding.passwordConfirm.getText().toString().trim()); + accountCreation.setUsername(binding.username.getText().toString().trim()); + accountCreation.setInstance(instance); + + new Thread(() -> { + try { + APIResponse apiResponse = new RetrofitPeertubeAPI(PeertubeRegisterActivity.this, instance, null).createAccount(accountCreation); + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + if (apiResponse.getError() != null) { + String errorMessage; + if (apiResponse.getError().getError() != null) { + try { + String[] resp = apiResponse.getError().getError().split(":"); + if (resp.length == 2) + errorMessage = apiResponse.getError().getError().split(":")[1]; + else if (resp.length == 3) + errorMessage = apiResponse.getError().getError().split(":")[2]; + else + errorMessage = getString(R.string.toast_error); + } catch (Exception e) { + errorMessage = getString(R.string.toast_error); + } + } else { + errorMessage = getString(R.string.toast_error); + } + binding.errorMessage.setText(errorMessage); + binding.errorMessage.setVisibility(View.VISIBLE); + binding.signup.setEnabled(true); + return; + } + + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(PeertubeRegisterActivity.this); + dialogBuilder.setCancelable(false); + dialogBuilder.setPositiveButton(R.string.validate, (dialog, which) -> { + dialog.dismiss(); + finish(); + }); + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.setTitle(getString(R.string.account_created)); + alertDialog.setMessage(getString(R.string.account_created_message, apiResponse.getStringData())); + alertDialog.show(); + }; + mainHandler.post(myRunnable); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + + }); + + setTitle(R.string.create_an_account); + } + + @Override + protected void onResume() { + super.onResume(); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @SuppressLint("ApplySharedPref") + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_INSTANCE && resultCode == Activity.RESULT_OK) { + if (data != null && data.getData() != null) { + String instance = String.valueOf(data.getData()); + binding.loginInstance.setText(instance); + binding.loginInstance.setSelection(instance.length()); + setTextAgreement(); + } + } + } + + private void setTextAgreement() { + TextView agreement_text = findViewById(R.id.agreement_text); + String tos = getString(R.string.tos); + String serverrules = getString(R.string.server_rules); + String content_agreement = null; + agreement_text.setMovementMethod(null); + agreement_text.setText(null); + if (BuildConfig.full_instances) { + if (binding.loginInstance.getText() != null) { + content_agreement = getString(R.string.agreement_check_peertube, + "" + tos + "" + ); + } + } else { + content_agreement = getString(R.string.agreement_check, + "" + serverrules + "", + "" + tos + "" + ); + } + agreement_text.setMovementMethod(LinkMovementMethod.getInstance()); + if (content_agreement != null) { + agreement_text.setText(Html.fromHtml(content_agreement)); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeUploadActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeUploadActivity.java new file mode 100644 index 000000000..c658a77c3 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeUploadActivity.java @@ -0,0 +1,418 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.activities.MainActivity.userMe; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.DataType.MY_CHANNELS; +import static app.fedilab.android.peertube.helper.Helper.peertubeInformation; + +import android.Manifest; +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Toast; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.documentfile.provider.DocumentFile; +import androidx.lifecycle.ViewModelProvider; + +import net.gotev.uploadservice.data.UploadNotificationAction; +import net.gotev.uploadservice.data.UploadNotificationConfig; +import net.gotev.uploadservice.data.UploadNotificationStatusConfig; +import net.gotev.uploadservice.extensions.ContextExtensionsKt; +import net.gotev.uploadservice.protocols.multipart.MultipartUploadRequest; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import app.fedilab.android.peertube.BaseFedilabTube; +import app.fedilab.android.peertube.BuildConfig; +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.APIResponse; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.data.ChannelData; +import app.fedilab.android.peertube.client.entities.UserMe; +import app.fedilab.android.peertube.databinding.ActivityPeertubeUploadBinding; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.viewmodel.ChannelsVM; +import es.dmoral.toasty.Toasty; + + +public class PeertubeUploadActivity extends BaseActivity { + + + public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 724; + private final int PICK_IVDEO = 52378; + private HashMap channels; + private Uri uri; + private String filename; + private HashMap privacyToSend; + private HashMap channelToSend; + private ActivityPeertubeUploadBinding binding; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + binding = ActivityPeertubeUploadBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + + + new Thread(() -> { + UserMe.VideoQuota videoQuotaReply = new RetrofitPeertubeAPI(PeertubeUploadActivity.this).getVideoQuota(); + runOnUiThread(() -> { + if (videoQuotaReply != null) { + long videoQuota = videoQuotaReply.getVideoQuotaUsed(); + long dailyQuota = videoQuotaReply.getVideoQuotaUsedDaily(); + long instanceVideoQuota = userMe.getVideoQuota(); + long instanceDailyQuota = userMe.getVideoQuotaDaily(); + + if (instanceVideoQuota != -1 && instanceVideoQuota != 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + binding.totalQuota.setProgress((int) (videoQuota * 100 / instanceVideoQuota), true); + } else { + binding.totalQuota.setProgress((int) (videoQuota * 100 / instanceVideoQuota)); + } + } else { + int progress = videoQuota > 0 ? 30 : 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + binding.totalQuota.setProgress(progress, true); + } else { + binding.totalQuota.setProgress(progress); + } + } + if (instanceDailyQuota != -1 && instanceDailyQuota != 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + binding.dailyQuota.setProgress((int) (dailyQuota * 100 / instanceDailyQuota), true); + } else { + binding.dailyQuota.setProgress((int) (dailyQuota * 100 / instanceDailyQuota)); + } + } else { + int progress = dailyQuota > 0 ? 30 : 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + binding.dailyQuota.setProgress(progress, true); + } else { + binding.dailyQuota.setProgress(progress); + } + } + binding.totalQuotaValue.setText( + String.format(Locale.getDefault(), "%s/%s", + Helper.returnRoundedSize(PeertubeUploadActivity.this, videoQuota), + Helper.returnRoundedSize(PeertubeUploadActivity.this, instanceVideoQuota))); + binding.dailyQuotaValue.setText( + String.format(Locale.getDefault(), "%s/%s", + Helper.returnRoundedSize(PeertubeUploadActivity.this, dailyQuota), + Helper.returnRoundedSize(PeertubeUploadActivity.this, instanceDailyQuota))); + } + }); + }).start(); + + + ChannelsVM viewModelC = new ViewModelProvider(PeertubeUploadActivity.this).get(ChannelsVM.class); + viewModelC.get(MY_CHANNELS, null).observe(PeertubeUploadActivity.this, this::manageVIewChannels); + channels = new HashMap<>(); + setTitle(R.string.upload_video); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IVDEO && resultCode == Activity.RESULT_OK) { + if (data == null || data.getData() == null) { + Toasty.error(PeertubeUploadActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); + return; + } + binding.setUploadSubmit.setEnabled(true); + uri = data.getData(); + filename = null; + DocumentFile documentFile = DocumentFile.fromSingleUri(this, uri); + if (documentFile != null) { + filename = documentFile.getName(); + } + if (filename == null) { + filename = new Date().toString(); + } + binding.setUploadFileName.setVisibility(View.VISIBLE); + binding.setUploadFileName.setText(filename); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + public void manageVIewChannels(APIResponse apiResponse) { + if (apiResponse.getError() != null || apiResponse.getChannels() == null || apiResponse.getChannels().size() == 0) { + if (apiResponse.getError() != null && apiResponse.getError().getError() != null) + Toasty.error(PeertubeUploadActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + else + Toasty.error(PeertubeUploadActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + return; + } + + //Populate channels + List channelsForUser = apiResponse.getChannels(); + String[] channelName = new String[channelsForUser.size()]; + String[] channelId = new String[channelsForUser.size()]; + int i = 0; + for (ChannelData.Channel channel : channelsForUser) { + channels.put(channel.getName(), channel.getId()); + channelName[i] = channel.getName(); + channelId[i] = channel.getId(); + i++; + } + + channelToSend = new HashMap<>(); + channelToSend.put(channelName[0], channelId[0]); + ArrayAdapter adapterChannel = new ArrayAdapter<>(PeertubeUploadActivity.this, + android.R.layout.simple_spinner_dropdown_item, channelName); + binding.setUploadChannel.setAdapter(adapterChannel); + + if (peertubeInformation == null) { + return; + } + LinkedHashMap translations = null; + if (peertubeInformation.getTranslations() != null) + translations = new LinkedHashMap<>(peertubeInformation.getTranslations()); + + LinkedHashMap privaciesInit = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + Map.Entry entryInt = privaciesInit.entrySet().iterator().next(); + privacyToSend = new HashMap<>(); + privacyToSend.put(entryInt.getKey(), entryInt.getValue()); + LinkedHashMap privacies = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + //Populate privacies + String[] privaciesA = new String[privacies.size()]; + Iterator> it = privacies.entrySet().iterator(); + i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (translations == null || translations.size() == 0 || !translations.containsKey(pair.getValue())) + privaciesA[i] = pair.getValue(); + else + privaciesA[i] = translations.get(pair.getValue()); + it.remove(); + i++; + } + + ArrayAdapter adapterPrivacies = new ArrayAdapter<>(PeertubeUploadActivity.this, + android.R.layout.simple_spinner_dropdown_item, privaciesA); + binding.setUploadPrivacy.setAdapter(adapterPrivacies); + + //Manage privacies + binding.setUploadPrivacy.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + LinkedHashMap privaciesCheck = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + Iterator> it = privaciesCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position) { + privacyToSend = new HashMap<>(); + privacyToSend.put(pair.getKey(), pair.getValue()); + break; + } + it.remove(); + i++; + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + binding.setUploadFile.setEnabled(true); + + binding.setUploadFile.setOnClickListener(v -> { + if (ContextCompat.checkSelfPermission(PeertubeUploadActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != + PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(PeertubeUploadActivity.this, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); + return; + } + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + String[] mimetypes = {"video/*"}; + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); + startActivityForResult(intent, PICK_IVDEO); + + }); + + //Manage languages + binding.setUploadChannel.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + LinkedHashMap channelsCheck = new LinkedHashMap<>(channels); + Iterator> it = channelsCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position) { + channelToSend = new HashMap<>(); + channelToSend.put(pair.getKey(), pair.getValue()); + + break; + } + it.remove(); + i++; + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + binding.setUploadSubmit.setOnClickListener(v -> { + if (uri != null) { + Map.Entry channelM = channelToSend.entrySet().iterator().next(); + String idChannel = channelM.getValue(); + Map.Entry privacyM = privacyToSend.entrySet().iterator().next(); + Integer idPrivacy = privacyM.getKey(); + if (binding.videoTitle.getText() != null && binding.videoTitle.getText().toString().trim().length() > 0) { + filename = binding.videoTitle.getText().toString().trim(); + } + try { + String token = Helper.getToken(PeertubeUploadActivity.this); + new MultipartUploadRequest(PeertubeUploadActivity.this, "https://" + HelperInstance.getLiveInstance(PeertubeUploadActivity.this) + "/api/v1/videos/upload") + .setMethod("POST") + .setBearerAuth(token) + .addHeader("User-Agent", getString(R.string.app_name) + "/" + BuildConfig.VERSION_NAME) + .addParameter("privacy", String.valueOf(idPrivacy)) + .addParameter("nsfw", "false") + .addParameter("name", filename) + .addParameter("commentsEnabled", "true") + .addParameter("downloadEnabled", "true") + .addParameter("waitTranscoding", "true") + .addParameter("channelId", idChannel) + .addFileToUpload(uri.toString(), "videofile") + .setNotificationConfig((context, uploadId) -> getNotificationConfig(uploadId)) + .setMaxRetries(2) + .startUpload(); + finish(); + } catch (Exception exc) { + exc.printStackTrace(); + } + } + }); + } + + UploadNotificationConfig getNotificationConfig(String uploadId) { + PendingIntent clickIntent = PendingIntent.getActivity( + PeertubeUploadActivity.this, 1, new Intent(this, PeertubeEditUploadActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); + + final boolean autoClear = false; + final boolean clearOnAction = true; + final boolean ringToneEnabled = true; + final ArrayList noActions = new ArrayList<>(1); + + final UploadNotificationAction cancelAction = new UploadNotificationAction( + R.drawable.ic_baseline_cancel_24, + getString(R.string.cancel), + ContextExtensionsKt.getCancelUploadIntent(this, uploadId) + ); + + + final ArrayList progressActions = new ArrayList<>(1); + progressActions.add(cancelAction); + + UploadNotificationStatusConfig progress = new UploadNotificationStatusConfig( + getString(R.string.app_name), + getString(R.string.uploading), + R.drawable.ic_baseline_cloud_upload_24, + Color.BLUE, + null, + clickIntent, + progressActions, + clearOnAction, + autoClear + ); + + UploadNotificationStatusConfig success = new UploadNotificationStatusConfig( + getString(R.string.app_name), + getString(R.string.upload_video_success), + R.drawable.ic_baseline_check_24, + Color.GREEN, + null, + clickIntent, + noActions, + clearOnAction, + autoClear + ); + + + UploadNotificationStatusConfig error = new UploadNotificationStatusConfig( + getString(R.string.app_name), + getString(R.string.toast_error), + R.drawable.ic_baseline_error_24, + Color.RED, + null, + clickIntent, + noActions, + clearOnAction, + autoClear + ); + + UploadNotificationStatusConfig cancelled = new UploadNotificationStatusConfig( + getString(R.string.app_name), + getString(R.string.toast_cancelled), + R.drawable.ic_baseline_cancel_24, + Color.YELLOW, + null, + clickIntent, + noActions, + clearOnAction + ); + + return new UploadNotificationConfig(BaseFedilabTube.UPLOAD_CHANNEL_ID, ringToneEnabled, progress, success, error, cancelled); + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/PlaylistsActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/PlaylistsActivity.java new file mode 100644 index 000000000..10d2cb8ea --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/PlaylistsActivity.java @@ -0,0 +1,101 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; +import android.widget.Toast; + +import androidx.fragment.app.FragmentTransaction; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.data.PlaylistData; +import app.fedilab.android.peertube.fragment.DisplayVideosFragment; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.PlaylistExportHelper; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.viewmodel.TimelineVM; +import es.dmoral.toasty.Toasty; + + +public class PlaylistsActivity extends BaseActivity { + + private final int PICK_IMPORT = 5556; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + + setContentView(R.layout.activity_playlists); + + + PlaylistData.Playlist playlist; + Bundle b = getIntent().getExtras(); + if (b != null) { + playlist = b.getParcelable("playlist"); + if (playlist == null) { + return; + } + } else { + Toasty.error(PlaylistsActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + return; + } + setTitle(playlist.getDisplayName()); + if (savedInstanceState == null) { + DisplayVideosFragment displayVideosFragment = new DisplayVideosFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, TimelineVM.TimelineType.VIDEOS_IN_PLAYLIST); + bundle.putSerializable("playlistId", playlist.getUuid()); + displayVideosFragment.setArguments(bundle); + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.add(R.id.nav_host_fragment, displayVideosFragment).commit(); + } + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) { + if (data == null || data.getData() == null) { + Toasty.error(PlaylistsActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + return; + } + PlaylistExportHelper.manageIntentUrl(PlaylistsActivity.this, data); + + } else if (requestCode == PICK_IMPORT) { + Toasty.error(PlaylistsActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + } + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/SearchActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/SearchActivity.java new file mode 100644 index 000000000..293ad4830 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/SearchActivity.java @@ -0,0 +1,169 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.tabs.TabLayout; + +import org.jetbrains.annotations.NotNull; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.databinding.ActivitySearchResultBinding; +import app.fedilab.android.peertube.fragment.DisplayChannelsFragment; +import app.fedilab.android.peertube.fragment.DisplayVideosFragment; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import es.dmoral.toasty.Toasty; + + +public class SearchActivity extends BaseActivity { + + + private String search; + private ActivitySearchResultBinding binding; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + binding = ActivitySearchResultBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + + Bundle b = getIntent().getExtras(); + if (b != null) { + search = b.getString("search"); + } else { + Toasty.error(SearchActivity.this, getString(R.string.toast_error_search), Toast.LENGTH_LONG).show(); + } + + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setTitle(search); + + binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.videos))); + binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.channels))); + binding.searchPager.setOffscreenPageLimit(2); + + PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); + binding.searchPager.setAdapter(mPagerAdapter); + binding.searchPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + TabLayout.Tab tab = binding.searchTabLayout.getTabAt(position); + if (tab != null) + tab.select(); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + + binding.searchTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) { + binding.searchPager.setCurrentItem(tab.getPosition()); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + Fragment fragment = null; + if (binding.searchPager.getAdapter() != null) + fragment = (Fragment) binding.searchPager.getAdapter().instantiateItem(binding.searchPager, tab.getPosition()); + switch (tab.getPosition()) { + case 0: + if (fragment != null) { + DisplayVideosFragment displayVideosFragment = ((DisplayVideosFragment) fragment); + displayVideosFragment.scrollToTop(); + } + break; + case 1: + if (fragment != null) { + DisplayChannelsFragment displayChannelsFragment = ((DisplayChannelsFragment) fragment); + displayChannelsFragment.scrollToTop(); + } + break; + } + } + }); + + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + + /** + * Pager adapter for the 2 fragments + */ + private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter { + + ScreenSlidePagerAdapter(FragmentManager fm) { + super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); + } + + @NotNull + @Override + public Fragment getItem(int position) { + Bundle bundle = new Bundle(); + if (position == 0) { + DisplayVideosFragment displayVideosFragment = new DisplayVideosFragment(); + bundle.putString("search_peertube", search); + displayVideosFragment.setArguments(bundle); + return displayVideosFragment; + } + DisplayChannelsFragment displayChannelsFragment = new DisplayChannelsFragment(); + bundle.putString("search_peertube", search); + displayChannelsFragment.setArguments(bundle); + return displayChannelsFragment; + } + + + @Override + public int getCount() { + return 2; + } + } + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/SepiaSearchActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/SepiaSearchActivity.java new file mode 100644 index 000000000..0a1be8b12 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/SepiaSearchActivity.java @@ -0,0 +1,388 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.activities.PeertubeActivity.hideKeyboard; +import static app.fedilab.android.peertube.helper.Helper.peertubeInformation; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; + +import com.mancj.materialsearchbar.MaterialSearchBar; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.entities.SepiaSearch; +import app.fedilab.android.peertube.databinding.ActivitySepiaSearchBinding; +import app.fedilab.android.peertube.fragment.DisplaySepiaSearchFragment; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; + + +public class SepiaSearchActivity extends BaseActivity { + + + private SepiaSearch sepiaSearchVideo, sepiaSearchChannel; + + private ActivitySepiaSearchBinding binding; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + binding = ActivitySepiaSearchBinding.inflate(getLayoutInflater()); + View rootView = binding.getRoot(); + setContentView(rootView); + + + sepiaSearchVideo = new SepiaSearch(); + sepiaSearchChannel = new SepiaSearch(); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + sepiaSearchVideo.setCount(String.valueOf(sharedpreferences.getInt(Helper.SET_VIDEOS_PER_PAGE, Helper.VIDEOS_PER_PAGE))); + sepiaSearchVideo.setDurationMin(0); + sepiaSearchVideo.setDurationMax(9999999); + sepiaSearchVideo.setStart("0"); + sepiaSearchVideo.setSort("-match"); + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + + binding.filter.setOnClickListener(view -> { + if (binding.filterElements.getVisibility() == View.VISIBLE) { + binding.filterElements.setVisibility(View.GONE); + } else { + binding.filterElements.setVisibility(View.VISIBLE); + } + }); + + + binding.sepiaElementNsfw.setOnCheckedChangeListener((group, checkedId) -> sepiaSearchVideo.setNsfw(checkedId != R.id.sepia_element_nsfw_no)); + + binding.radioDate.setOnCheckedChangeListener((group, checkedId) -> { + if (checkedId == R.id.sepia_element_published_date_today) { + Calendar cal = GregorianCalendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + sepiaSearchVideo.setStartDate(cal.getTime()); + } else if (checkedId == R.id.sepia_element_published_date_last_7_days) { + Calendar cal; + cal = GregorianCalendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.DAY_OF_YEAR, -7); + sepiaSearchVideo.setStartDate(cal.getTime()); + } else if (checkedId == R.id.sepia_element_published_date_last_30_days) { + Calendar cal; + cal = GregorianCalendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.DAY_OF_YEAR, -30); + sepiaSearchVideo.setStartDate(cal.getTime()); + } else if (checkedId == R.id.sepia_element_published_date_last_365_days) { + Calendar cal; + cal = GregorianCalendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.DAY_OF_YEAR, -365); + sepiaSearchVideo.setStartDate(cal.getTime()); + } else { + sepiaSearchVideo.setStartDate(null); + } + }); + + + binding.duration.setOnCheckedChangeListener((group, checkedId) -> { + if (checkedId == R.id.sepia_element_duration_short) { + sepiaSearchVideo.setDurationMin(0); + sepiaSearchVideo.setDurationMax(240); + } else if (checkedId == R.id.sepia_element_duration_medium) { + sepiaSearchVideo.setDurationMin(240); + sepiaSearchVideo.setDurationMax(600); + } else if (checkedId == R.id.sepia_element_duration_long) { + sepiaSearchVideo.setDurationMin(600); + sepiaSearchVideo.setDurationMax(999999999); + } else { + sepiaSearchVideo.setDurationMin(0); + sepiaSearchVideo.setDurationMax(999999999); + } + }); + + + ArrayAdapter adapterSortBy = new ArrayAdapter<>(SepiaSearchActivity.this, + android.R.layout.simple_spinner_dropdown_item, getResources().getStringArray(R.array.sort_by_array)); + binding.sortBy.setAdapter(adapterSortBy); + binding.sortBy.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String orderby, channelOrderBy; + switch (position) { + case 1: + orderby = "-publishedAt"; + channelOrderBy = "-createdAt"; + break; + case 2: + orderby = "publishedAt"; + channelOrderBy = "createdAt"; + break; + default: + orderby = "-match"; + channelOrderBy = null; + } + sepiaSearchVideo.setSort(orderby); + sepiaSearchChannel.setSort(channelOrderBy); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + + LinkedHashMap categories = new LinkedHashMap<>(peertubeInformation.getCategories()); + LinkedHashMap licences = new LinkedHashMap<>(peertubeInformation.getLicences()); + LinkedHashMap languages = new LinkedHashMap<>(peertubeInformation.getLanguages()); + LinkedHashMap translations = null; + + if (peertubeInformation.getTranslations() != null) { + translations = new LinkedHashMap<>(peertubeInformation.getTranslations()); + } + + //Populate catgories + String[] categoriesA = new String[categories.size() + 1]; + categoriesA[0] = getString(R.string.display_all_categories); + Iterator> it = categories.entrySet().iterator(); + int i = 1; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (translations == null || translations.size() == 0 || !translations.containsKey(pair.getValue())) + categoriesA[i] = pair.getValue(); + else + categoriesA[i] = translations.get(pair.getValue()); + it.remove(); + i++; + } + ArrayAdapter adapterCatgories = new ArrayAdapter<>(SepiaSearchActivity.this, + android.R.layout.simple_spinner_dropdown_item, categoriesA); + binding.sepiaElementCategory.setAdapter(adapterCatgories); + + + //Populate licenses + String[] licensesA = new String[licences.size() + 1]; + licensesA[0] = getString(R.string.display_all_licenses); + it = licences.entrySet().iterator(); + i = 1; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (translations == null || translations.size() == 0 || !translations.containsKey(pair.getValue())) + licensesA[i] = pair.getValue(); + else + licensesA[i] = translations.get(pair.getValue()); + it.remove(); + i++; + } + ArrayAdapter adapterLicenses = new ArrayAdapter<>(SepiaSearchActivity.this, + android.R.layout.simple_spinner_dropdown_item, licensesA); + binding.sepiaElementLicense.setAdapter(adapterLicenses); + + //Populate languages + String[] languagesA = new String[languages.size() + 1]; + languagesA[0] = getString(R.string.display_all_languages); + Iterator> itl = languages.entrySet().iterator(); + i = 1; + while (itl.hasNext()) { + Map.Entry pair = itl.next(); + if (translations == null || translations.size() == 0 || !translations.containsKey(pair.getValue())) + languagesA[i] = pair.getValue(); + else + languagesA[i] = translations.get(pair.getValue()); + itl.remove(); + i++; + } + ArrayAdapter adapterLanguages = new ArrayAdapter<>(SepiaSearchActivity.this, + android.R.layout.simple_spinner_dropdown_item, languagesA); + binding.sepiaElementLanguage.setAdapter(adapterLanguages); + + + binding.sepiaElementLicense.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + updateLicensePosition(position); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + //Manage categories + binding.sepiaElementCategory.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + updateCategoryPosition(position); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + //Manage languages + binding.sepiaElementLanguage.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + updateLanguagesPosition(position); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + + binding.searchBar.setOnSearchActionListener(new MaterialSearchBar.OnSearchActionListener() { + @Override + public void onSearchStateChanged(boolean enabled) { + + } + + @Override + public void onSearchConfirmed(CharSequence text) { + makeSearch(); + } + + @Override + public void onButtonClicked(int buttonCode) { + makeSearch(); + } + }); + binding.applyFilter.setOnClickListener(v -> makeSearch()); + + binding.searchBar.openSearch(); + } + + private void makeSearch() { + hideKeyboard(SepiaSearchActivity.this); + sepiaSearchVideo.setStart("0"); + if (binding.sepiaElementOneOfTags.getTags().size() > 0) { + sepiaSearchVideo.setTagsOneOf(binding.sepiaElementOneOfTags.getTags()); + } else { + sepiaSearchVideo.setTagsOneOf(null); + } + if (binding.sepiaElementAllOfTags.getTags().size() > 0) { + sepiaSearchVideo.setTagsAllOf(binding.sepiaElementAllOfTags.getTags()); + } else { + sepiaSearchVideo.setTagsAllOf(null); + } + + Fragment fragment = getSupportFragmentManager().findFragmentByTag("SEPIA_SEARCH"); + if (fragment != null) + getSupportFragmentManager().beginTransaction().remove(fragment).commit(); + binding.filterElements.setVisibility(View.GONE); + sepiaSearchVideo.setSearch(binding.searchBar.getText()); + DisplaySepiaSearchFragment displaySepiaSearchFragment = new DisplaySepiaSearchFragment(); + Bundle bundle = new Bundle(); + bundle.putParcelable("sepiaSearchVideo", sepiaSearchVideo); + displaySepiaSearchFragment.setArguments(bundle); + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.add(R.id.container, displaySepiaSearchFragment, "SEPIA_SEARCH").commit(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void updateLanguagesPosition(int position) { + LinkedHashMap languagesCheck = new LinkedHashMap<>(peertubeInformation.getLanguages()); + Iterator> it = languagesCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position && position > 0) { + List languages = new ArrayList<>(); + languages.add(pair.getKey()); + sepiaSearchVideo.setBoostLanguages(languages); + break; + } else { + sepiaSearchVideo.setBoostLanguages(null); + } + it.remove(); + i++; + } + } + + private void updateCategoryPosition(int position) { + LinkedHashMap categoriesCheck = new LinkedHashMap<>(peertubeInformation.getCategories()); + Iterator> it = categoriesCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position && position > 0) { + List categories = new ArrayList<>(); + categories.add(pair.getKey()); + sepiaSearchVideo.setCategoryOneOf(categories); + break; + } else { + sepiaSearchVideo.setCategoryOneOf(null); + } + it.remove(); + i++; + } + } + + private void updateLicensePosition(int position) { + LinkedHashMap licensesCheck = new LinkedHashMap<>(peertubeInformation.getLicences()); + Iterator> it = licensesCheck.entrySet().iterator(); + int i = 0; + while (it.hasNext()) { + Map.Entry pair = it.next(); + if (i == position && position > 0) { + List licenses = new ArrayList<>(); + licenses.add(pair.getKey()); + sepiaSearchVideo.setLicenceOneOf(licenses); + break; + } else { + sepiaSearchVideo.setLicenceOneOf(null); + } + it.remove(); + i++; + } + } + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/SettingsActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/SettingsActivity.java new file mode 100644 index 000000000..7e0f78450 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/SettingsActivity.java @@ -0,0 +1,45 @@ +package app.fedilab.android.peertube.activities; + +import android.os.Bundle; +import android.view.MenuItem; + +import app.fedilab.android.peertube.fragment.SettingsFragment; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; + +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +public class SettingsActivity extends BaseActivity { + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/ShowAccountActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/ShowAccountActivity.java new file mode 100644 index 000000000..6542e329b --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/ShowAccountActivity.java @@ -0,0 +1,313 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.MUTE; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.REPORT_ACCOUNT; + +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.text.Html; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.lifecycle.ViewModelProvider; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.tabs.TabLayout; + +import org.jetbrains.annotations.NotNull; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.APIResponse; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.data.AccountData; +import app.fedilab.android.peertube.fragment.DisplayChannelsFragment; +import app.fedilab.android.peertube.fragment.DisplayVideosFragment; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.viewmodel.AccountsVM; +import app.fedilab.android.peertube.viewmodel.PostActionsVM; +import app.fedilab.android.peertube.viewmodel.TimelineVM; +import es.dmoral.toasty.Toasty; + + +public class ShowAccountActivity extends BaseActivity { + + + private ViewPager mPager; + private TabLayout tabLayout; + private TextView account_note, subscriber_count; + private ImageView account_pp; + private TextView account_dn; + private AccountData.Account account; + private String accountAcct; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_show_account); + setTitle(""); + Bundle b = getIntent().getExtras(); + subscriber_count = findViewById(R.id.subscriber_count); + account_pp = findViewById(R.id.account_pp); + account_dn = findViewById(R.id.account_dn); + account_pp.setBackgroundResource(R.drawable.account_pp_border); + if (b != null) { + account = b.getParcelable("account"); + accountAcct = b.getString("accountAcct"); + } else { + Toasty.error(ShowAccountActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show(); + } + + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + tabLayout = findViewById(R.id.account_tabLayout); + account_note = findViewById(R.id.account_note); + + manageAccount(); + AccountsVM viewModel = new ViewModelProvider(ShowAccountActivity.this).get(AccountsVM.class); + viewModel.getAccount(accountAcct == null ? account.getUsername() + "@" + account.getHost() : accountAcct).observe(ShowAccountActivity.this, this::manageViewAccounts); + } + + @Override + public boolean onCreateOptionsMenu(@NotNull Menu menu) { + getMenuInflater().inflate(R.menu.main_account, menu); + if (!Helper.isLoggedIn(ShowAccountActivity.this)) { + menu.findItem(R.id.action_mute).setVisible(false); + } + menu.findItem(R.id.action_display_account).setVisible(false); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } else if (item.getItemId() == R.id.action_mute) { + PostActionsVM viewModel = new ViewModelProvider(ShowAccountActivity.this).get(PostActionsVM.class); + viewModel.post(MUTE, accountAcct == null ? account.getUsername() + "@" + account.getHost() : accountAcct, null).observe(ShowAccountActivity.this, apiResponse -> manageVIewPostActions(MUTE, apiResponse)); + } else if (item.getItemId() == R.id.action_report) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(ShowAccountActivity.this); + LayoutInflater inflater1 = getLayoutInflater(); + View dialogView = inflater1.inflate(R.layout.popup_report, new LinearLayout(ShowAccountActivity.this), false); + dialogBuilder.setView(dialogView); + EditText report_content = dialogView.findViewById(R.id.report_content); + dialogBuilder.setNeutralButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); + dialogBuilder.setPositiveButton(R.string.report, (dialog, id) -> { + if (report_content.getText().toString().trim().length() == 0) { + Toasty.info(ShowAccountActivity.this, getString(R.string.report_comment_size), Toasty.LENGTH_LONG).show(); + } else { + PostActionsVM viewModel = new ViewModelProvider(ShowAccountActivity.this).get(PostActionsVM.class); + viewModel.post(REPORT_ACCOUNT, account.getId(), report_content.getText().toString()).observe(ShowAccountActivity.this, apiResponse -> manageVIewPostActions(REPORT_ACCOUNT, apiResponse)); + dialog.dismiss(); + } + }); + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.show(); + } else if (item.getItemId() == R.id.action_share && account != null) { + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.shared_via)); + String extra_text = account.getUrl(); + sendIntent.putExtra(Intent.EXTRA_TEXT, extra_text); + sendIntent.setType("text/plain"); + try { + startActivity(Intent.createChooser(sendIntent, getString(R.string.share_with))); + } catch (Exception e) { + Toasty.error(ShowAccountActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); + } + } + return super.onOptionsItemSelected(item); + } + + private void manageAccount() { + + + setTitle(account.getAcct()); + + mPager = findViewById(R.id.account_viewpager); + tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.channels))); + tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.videos))); + mPager.setOffscreenPageLimit(2); + + PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); + mPager.setAdapter(mPagerAdapter); + mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + TabLayout.Tab tab = tabLayout.getTabAt(position); + if (tab != null) + tab.select(); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + + tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) { + mPager.setCurrentItem(tab.getPosition()); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + Fragment fragment = null; + if (mPager.getAdapter() != null) + fragment = (Fragment) mPager.getAdapter().instantiateItem(mPager, tab.getPosition()); + switch (tab.getPosition()) { + case 0: + if (fragment != null) { + DisplayChannelsFragment displayChannelsFragment = ((DisplayChannelsFragment) fragment); + displayChannelsFragment.scrollToTop(); + } + break; + case 1: + if (fragment != null) { + DisplayVideosFragment displayVideosFragment = ((DisplayVideosFragment) fragment); + displayVideosFragment.scrollToTop(); + } + break; + } + } + }); + + account_dn.setText(account.getDisplayName()); + + manageNotes(account); + Helper.loadAvatar(ShowAccountActivity.this, account, account_pp); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onStop() { + super.onStop(); + } + + + public void manageVIewPostActions(RetrofitPeertubeAPI.ActionType statusAction, APIResponse apiResponse) { + + if (apiResponse.getError() != null) { + Toasty.error(ShowAccountActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + return; + } + if (statusAction == RetrofitPeertubeAPI.ActionType.MUTE) { + Toasty.info(ShowAccountActivity.this, getString(R.string.muted_done), Toast.LENGTH_LONG).show(); + } + } + + public void manageViewAccounts(APIResponse apiResponse) { + if (apiResponse.getAccounts() != null && apiResponse.getAccounts().size() == 1) { + AccountData.Account account = apiResponse.getAccounts().get(0); + if (this.account == null) { + this.account = account; + manageAccount(); + } + subscriber_count.setText(getString(R.string.followers_count, Helper.withSuffix(account.getFollowersCount()))); + subscriber_count.setVisibility(View.VISIBLE); + manageNotes(account); + } + } + + private void manageNotes(AccountData.Account account) { + if (account.getDescription() != null && account.getDescription().compareTo("null") != 0 && account.getDescription().trim().length() > 0) { + SpannableString spannableString; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + spannableString = new SpannableString(Html.fromHtml(account.getDescription(), FROM_HTML_MODE_LEGACY)); + else + spannableString = new SpannableString(Html.fromHtml(account.getDescription())); + + account_note.setText(spannableString, TextView.BufferType.SPANNABLE); + account_note.setMovementMethod(LinkMovementMethod.getInstance()); + account_note.setVisibility(View.VISIBLE); + } else { + account_note.setVisibility(View.GONE); + } + } + + + /** + * Pager adapter for the 2 fragments + */ + private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter { + + ScreenSlidePagerAdapter(FragmentManager fm) { + super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); + } + + @NotNull + @Override + public Fragment getItem(int position) { + Bundle bundle = new Bundle(); + if (position == 0) { + DisplayChannelsFragment displayChannelsFragment = new DisplayChannelsFragment(); + bundle.putString("name", account.getAcct()); + displayChannelsFragment.setArguments(bundle); + return displayChannelsFragment; + } + DisplayVideosFragment displayVideosFragment = new DisplayVideosFragment(); + bundle.putSerializable(Helper.TIMELINE_TYPE, TimelineVM.TimelineType.ACCOUNT_VIDEOS); + bundle.putParcelable("account", account); + bundle.putString("peertube_instance", account.getHost()); + displayVideosFragment.setArguments(bundle); + return displayVideosFragment; + } + + + @Override + public int getCount() { + return 2; + } + } + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/ShowChannelActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/ShowChannelActivity.java new file mode 100644 index 000000000..b743eb04d --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/ShowChannelActivity.java @@ -0,0 +1,469 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY; +import static app.fedilab.android.peertube.activities.MainActivity.TypeOfConnection.SURFING; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.FOLLOW; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.MUTE; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.REPORT_ACCOUNT; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.ActionType.UNFOLLOW; +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.DataType.CHANNEL; +import static app.fedilab.android.peertube.helper.Helper.isLoggedIn; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.ColorStateList; +import android.database.sqlite.SQLiteDatabase; +import android.os.Build; +import android.os.Bundle; +import android.text.Html; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.lifecycle.ViewModelProvider; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.tabs.TabLayout; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.APIResponse; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.data.AccountData; +import app.fedilab.android.peertube.client.data.ChannelData.Channel; +import app.fedilab.android.peertube.databinding.ActivityShowChannelBinding; +import app.fedilab.android.peertube.drawer.OwnAccountsAdapter; +import app.fedilab.android.peertube.fragment.DisplayAccountsFragment; +import app.fedilab.android.peertube.fragment.DisplayVideosFragment; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.sqlite.AccountDAO; +import app.fedilab.android.peertube.sqlite.Sqlite; +import app.fedilab.android.peertube.viewmodel.ChannelsVM; +import app.fedilab.android.peertube.viewmodel.PostActionsVM; +import app.fedilab.android.peertube.viewmodel.RelationshipVM; +import app.fedilab.android.peertube.viewmodel.TimelineVM; +import es.dmoral.toasty.Toasty; + + +public class ShowChannelActivity extends BaseActivity { + + + private Map relationship; + private Channel channel; + private action doAction; + private String channelAcct; + private boolean sepiaSearch; + private String peertubeInstance; + private ActivityShowChannelBinding binding; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + binding = ActivityShowChannelBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + setTitle(""); + Bundle b = getIntent().getExtras(); + binding.accountFollow.setEnabled(false); + binding.accountPp.setBackgroundResource(R.drawable.account_pp_border); + if (b != null) { + channel = b.getParcelable("channel"); + channelAcct = b.getString("channelId"); + sepiaSearch = b.getBoolean("sepia_search", false); + peertubeInstance = b.getString("peertube_instance", null); + + } else { + Toasty.error(ShowChannelActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show(); + } + + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + ChannelsVM viewModel = new ViewModelProvider(ShowChannelActivity.this).get(ChannelsVM.class); + viewModel.get(sepiaSearch ? peertubeInstance : null, CHANNEL, channelAcct == null ? channel.getAcct() : channelAcct).observe(ShowChannelActivity.this, this::manageViewAccounts); + manageChannel(); + + if (MainActivity.typeOfConnection == MainActivity.TypeOfConnection.SURFING) { + binding.accountFollow.setText(getString(R.string.action_follow)); + binding.accountFollow.setEnabled(true); + new Thread(() -> { + try { + SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + List accounts = new AccountDAO(ShowChannelActivity.this, db).getAllAccount(); + runOnUiThread(() -> { + binding.accountFollow.setVisibility(View.VISIBLE); + binding.accountFollow.setOnClickListener(v -> { + AlertDialog.Builder builderSingle = new AlertDialog.Builder(ShowChannelActivity.this); + builderSingle.setTitle(getString(R.string.list_of_accounts)); + if (accounts != null && accounts.size() > 0) { + if (accounts.size() > 1) { + final OwnAccountsAdapter accountsListAdapter = new OwnAccountsAdapter(ShowChannelActivity.this, accounts); + builderSingle.setAdapter(accountsListAdapter, (dialog, which) -> new Thread(() -> { + try { + RetrofitPeertubeAPI peertubeAPI = new RetrofitPeertubeAPI(ShowChannelActivity.this, accounts.get(which).getHost(), accounts.get(which).getToken()); + peertubeAPI.post(FOLLOW, channel.getAcct(), null); + } catch (Exception e) { + e.printStackTrace(); + } + }).start()); + } else { + RetrofitPeertubeAPI peertubeAPI = new RetrofitPeertubeAPI(ShowChannelActivity.this, accounts.get(0).getHost(), accounts.get(0).getToken()); + peertubeAPI.post(FOLLOW, channel.getAcct(), null); + } + } + builderSingle.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + builderSingle.show(); + }); + }); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + } + + @Override + public boolean onCreateOptionsMenu(@NotNull Menu menu) { + getMenuInflater().inflate(R.menu.main_account, menu); + if (!Helper.isLoggedIn(ShowChannelActivity.this) || sepiaSearch) { + menu.findItem(R.id.action_mute).setVisible(false); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } else if (item.getItemId() == R.id.action_mute) { + PostActionsVM viewModel = new ViewModelProvider(ShowChannelActivity.this).get(PostActionsVM.class); + viewModel.post(MUTE, channel.getOwnerAccount().getAcct(), null).observe(ShowChannelActivity.this, apiResponse -> manageVIewPostActions(MUTE, apiResponse)); + } else if (item.getItemId() == R.id.action_report) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(ShowChannelActivity.this); + LayoutInflater inflater1 = getLayoutInflater(); + View dialogView = inflater1.inflate(R.layout.popup_report, new LinearLayout(ShowChannelActivity.this), false); + dialogBuilder.setView(dialogView); + EditText report_content = dialogView.findViewById(R.id.report_content); + dialogBuilder.setNeutralButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); + dialogBuilder.setPositiveButton(R.string.report, (dialog, id) -> { + if (report_content.getText().toString().trim().length() == 0) { + Toasty.info(ShowChannelActivity.this, getString(R.string.report_comment_size), Toasty.LENGTH_LONG).show(); + } else { + PostActionsVM viewModel = new ViewModelProvider(ShowChannelActivity.this).get(PostActionsVM.class); + viewModel.post(REPORT_ACCOUNT, channel.getId(), report_content.getText().toString()).observe(ShowChannelActivity.this, apiResponse -> manageVIewPostActions(REPORT_ACCOUNT, apiResponse)); + dialog.dismiss(); + } + }); + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.show(); + } else if (item.getItemId() == R.id.action_share && channel != null) { + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.shared_via)); + String extra_text = channel.getUrl(); + sendIntent.putExtra(Intent.EXTRA_TEXT, extra_text); + sendIntent.setType("text/plain"); + try { + startActivity(Intent.createChooser(sendIntent, getString(R.string.share_with))); + } catch (Exception e) { + Toasty.error(ShowChannelActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); + } + } else if (item.getItemId() == R.id.action_display_account) { + Bundle b = new Bundle(); + Intent intent = new Intent(ShowChannelActivity.this, ShowAccountActivity.class); + b.putParcelable("account", channel.getOwnerAccount()); + b.putString("accountAcct", channel.getOwnerAccount().getAcct()); + intent.putExtras(b); + startActivity(intent); + } + return super.onOptionsItemSelected(item); + } + + private void manageChannel() { + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + + String accountIdRelation = channel.getAcct(); + if (isLoggedIn(ShowChannelActivity.this)) { + RelationshipVM viewModel = new ViewModelProvider(ShowChannelActivity.this).get(RelationshipVM.class); + List uids = new ArrayList<>(); + uids.add(accountIdRelation); + viewModel.get(uids).observe(ShowChannelActivity.this, this::manageVIewRelationship); + } + + setTitle(channel.getAcct()); + + binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.videos))); + binding.accountViewpager.setOffscreenPageLimit(1); + + + PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); + binding.accountViewpager.setAdapter(mPagerAdapter); + ViewGroup.LayoutParams params = binding.accountTabLayout.getLayoutParams(); + params.height = 0; + binding.accountTabLayout.setLayoutParams(params); + binding.accountViewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + TabLayout.Tab tab = binding.accountTabLayout.getTabAt(position); + if (tab != null) + tab.select(); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + + binding.accountTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) { + binding.accountViewpager.setCurrentItem(tab.getPosition()); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + Fragment fragment = null; + if (binding.accountViewpager.getAdapter() != null) + fragment = (Fragment) binding.accountViewpager.getAdapter().instantiateItem(binding.accountViewpager, tab.getPosition()); + switch (tab.getPosition()) { + case 0: + if (fragment != null) { + DisplayVideosFragment displayVideosFragment = ((DisplayVideosFragment) fragment); + displayVideosFragment.scrollToTop(); + } + break; + case 1: + if (fragment != null) { + DisplayAccountsFragment displayAccountsFragment = ((DisplayAccountsFragment) fragment); + displayAccountsFragment.scrollToTop(); + } + break; + } + } + }); + + binding.accountDn.setText(channel.getDisplayName()); + + + manageNotes(channel); + Helper.loadAvatar(ShowChannelActivity.this, channel, binding.accountPp); + //Follow button + String target = channel.getAcct(); + + binding.accountFollow.setOnClickListener(v -> { + if (doAction == action.NOTHING) { + Toasty.info(ShowChannelActivity.this, getString(R.string.nothing_to_do), Toast.LENGTH_LONG).show(); + } else if (doAction == action.FOLLOW) { + binding.accountFollow.setEnabled(false); + PostActionsVM viewModel = new ViewModelProvider(ShowChannelActivity.this).get(PostActionsVM.class); + viewModel.post(FOLLOW, target, null).observe(ShowChannelActivity.this, apiResponse -> manageVIewPostActions(FOLLOW, apiResponse)); + } else if (doAction == action.UNFOLLOW) { + boolean confirm_unfollow = sharedpreferences.getBoolean(Helper.SET_UNFOLLOW_VALIDATION, true); + if (confirm_unfollow) { + AlertDialog.Builder unfollowConfirm = new AlertDialog.Builder(ShowChannelActivity.this); + unfollowConfirm.setTitle(getString(R.string.unfollow_confirm)); + unfollowConfirm.setMessage(channel.getAcct()); + unfollowConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + unfollowConfirm.setPositiveButton(R.string.yes, (dialog, which) -> { + binding.accountFollow.setEnabled(false); + PostActionsVM viewModel = new ViewModelProvider(ShowChannelActivity.this).get(PostActionsVM.class); + viewModel.post(UNFOLLOW, target, null).observe(ShowChannelActivity.this, apiResponse -> manageVIewPostActions(UNFOLLOW, apiResponse)); + dialog.dismiss(); + }); + unfollowConfirm.show(); + } else { + binding.accountFollow.setEnabled(false); + PostActionsVM viewModel = new ViewModelProvider(ShowChannelActivity.this).get(PostActionsVM.class); + viewModel.post(UNFOLLOW, target, null).observe(ShowChannelActivity.this, apiResponse -> manageVIewPostActions(UNFOLLOW, apiResponse)); + } + + } + }); + } + + + public void manageVIewRelationship(APIResponse apiResponse) { + + if (apiResponse.getError() != null) { + if (apiResponse.getError().getError().length() > 500) { + Toasty.info(ShowChannelActivity.this, getString(R.string.remote_account), Toast.LENGTH_LONG).show(); + } else { + Toasty.error(ShowChannelActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + } + return; + } + this.relationship = apiResponse.getRelationships(); + manageButtonVisibility(); + + invalidateOptionsMenu(); + + } + + //Manages the visibility of the button + private void manageButtonVisibility() { + if (relationship == null || MainActivity.typeOfConnection == SURFING || channel == null) + return; + binding.accountFollow.setEnabled(true); + Boolean isFollowing = relationship.get(channel.getAcct()); + if (isFollowing != null && isFollowing) { + binding.accountFollow.setText(R.string.action_unfollow); + binding.accountFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(ShowChannelActivity.this, R.color.red_1))); + doAction = action.UNFOLLOW; + } else { + binding.accountFollow.setText(R.string.action_follow); + doAction = action.FOLLOW; + } + binding.accountFollow.setVisibility(View.VISIBLE); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onStop() { + super.onStop(); + } + + + public void manageVIewPostActions(RetrofitPeertubeAPI.ActionType statusAction, APIResponse apiResponse) { + + if (apiResponse.getError() != null) { + Toasty.error(ShowChannelActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + return; + } + String target = channel.getAcct(); + //IF action is unfollow or mute, sends an intent to remove statuses + if (statusAction == RetrofitPeertubeAPI.ActionType.UNFOLLOW) { + Bundle b = new Bundle(); + b.putString("receive_action", apiResponse.getTargetedId()); + Intent intentBC = new Intent(Helper.RECEIVE_ACTION); + intentBC.putExtras(b); + } + if (statusAction == RetrofitPeertubeAPI.ActionType.UNFOLLOW || statusAction == RetrofitPeertubeAPI.ActionType.FOLLOW) { + RelationshipVM viewModel = new ViewModelProvider(ShowChannelActivity.this).get(RelationshipVM.class); + List uris = new ArrayList<>(); + uris.add(target); + viewModel.get(uris).observe(ShowChannelActivity.this, this::manageVIewRelationship); + } else if (statusAction == RetrofitPeertubeAPI.ActionType.MUTE) { + Toasty.info(ShowChannelActivity.this, getString(R.string.muted_done), Toast.LENGTH_LONG).show(); + } + } + + public void manageViewAccounts(APIResponse apiResponse) { + if (apiResponse.getChannels() != null && apiResponse.getChannels().size() == 1) { + Channel channel = apiResponse.getChannels().get(0); + if (this.channel == null) { + this.channel = channel; + manageChannel(); + } + if (channel.getOwnerAccount() != null) { + this.channel.setOwnerAccount(channel.getOwnerAccount()); + } + binding.subscriberCount.setText(getString(R.string.followers_count, Helper.withSuffix(channel.getFollowersCount()))); + binding.subscriberCount.setVisibility(View.VISIBLE); + manageNotes(channel); + } + } + + private void manageNotes(Channel channel) { + if (channel.getDescription() != null && channel.getDescription().compareTo("null") != 0 && channel.getDescription().trim().length() > 0) { + SpannableString spannableString; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + spannableString = new SpannableString(Html.fromHtml(channel.getDescription(), FROM_HTML_MODE_LEGACY)); + else + spannableString = new SpannableString(Html.fromHtml(channel.getDescription())); + + binding.accountNote.setText(spannableString, TextView.BufferType.SPANNABLE); + binding.accountNote.setMovementMethod(LinkMovementMethod.getInstance()); + binding.accountNote.setVisibility(View.VISIBLE); + } else { + binding.accountNote.setVisibility(View.GONE); + } + } + + public enum action { + FOLLOW, + UNFOLLOW, + NOTHING + } + + /** + * Pager adapter for the 2 fragments + */ + private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter { + + ScreenSlidePagerAdapter(FragmentManager fm) { + super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); + } + + @NotNull + @Override + public Fragment getItem(int position) { + DisplayVideosFragment displayVideosFragment = new DisplayVideosFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, TimelineVM.TimelineType.CHANNEL_VIDEOS); + bundle.putParcelable("channel", channel); + bundle.putString("peertube_instance", channel.getHost()); + bundle.putBoolean("sepia_search", sepiaSearch); + displayVideosFragment.setArguments(bundle); + return displayVideosFragment; + } + + + @Override + public int getCount() { + return 1; + } + } + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/VideosTimelineActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/VideosTimelineActivity.java new file mode 100644 index 000000000..d5edb88e7 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/VideosTimelineActivity.java @@ -0,0 +1,178 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.viewmodel.TimelineVM.TimelineType.HISTORY; + +import android.app.AlertDialog; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; + +import org.jetbrains.annotations.NotNull; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.APIResponse; +import app.fedilab.android.peertube.databinding.ActivityVideosTimelineBinding; +import app.fedilab.android.peertube.fragment.DisplayVideosFragment; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.viewmodel.TimelineVM; + + +public class VideosTimelineActivity extends BaseActivity { + + private TimelineVM.TimelineType type; + private DisplayVideosFragment displayVideosFragment; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + ActivityVideosTimelineBinding binding = ActivityVideosTimelineBinding.inflate(getLayoutInflater()); + View mainView = binding.getRoot(); + setContentView(mainView); + + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + Bundle b = getIntent().getExtras(); + if (b != null) + type = (TimelineVM.TimelineType) b.get("type"); + displayVideosFragment = null; + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + if (savedInstanceState == null) { + displayVideosFragment = new DisplayVideosFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, type); + displayVideosFragment.setArguments(bundle); + ft.add(R.id.container, displayVideosFragment).addToBackStack(null).commit(); + } + + if (type == TimelineVM.TimelineType.MY_VIDEOS) { + setTitle(R.string.my_videos); + } else if (type == HISTORY) { + setTitle(R.string.my_history); + //TODO: uncomment when available + // binding.historyFilter.setVisibility(View.VISIBLE); + binding.historyFilterAll.setOnClickListener(v -> historyFilter(null)); + binding.historyFilterToday.setOnClickListener(v -> { + Calendar cal = GregorianCalendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.getTime(); + historyFilter(cal.getTime()); + }); + binding.historyFilterLast7Days.setOnClickListener(v -> { + Calendar cal = GregorianCalendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.DAY_OF_YEAR, -7); + cal.getTime(); + historyFilter(cal.getTime()); + }); + + } else if (type == TimelineVM.TimelineType.MOST_LIKED) { + setTitle(R.string.title_most_liked); + } + + + } + + private void historyFilter(Date date) { + String startDate = null; + if (date != null) { + SimpleDateFormat fmtOut = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH); + startDate = fmtOut.format(date); + } + if (displayVideosFragment != null) { + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + displayVideosFragment = new DisplayVideosFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, HISTORY); + bundle.putSerializable("startDate", startDate); + displayVideosFragment.setArguments(bundle); + ft.replace(R.id.container, displayVideosFragment); + ft.addToBackStack(null); + ft.commit(); + } + } + + @Override + public boolean onCreateOptionsMenu(@NotNull Menu menu) { + if (type == HISTORY) { + getMenuInflater().inflate(R.menu.main_history, menu); + return true; + } else { + return super.onCreateOptionsMenu(menu); + } + + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + finish(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } else if (item.getItemId() == R.id.action_delete) { + AlertDialog.Builder builder = new AlertDialog.Builder(VideosTimelineActivity.this); + builder.setTitle(R.string.delete_history); + builder.setMessage(R.string.delete_history_confirm); + builder.setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(R.string.delete, (dialog, which) -> { + TimelineVM viewModelFeeds = new ViewModelProvider(VideosTimelineActivity.this).get(TimelineVM.class); + viewModelFeeds.deleterHistory().observe(VideosTimelineActivity.this, this::manageVIewVideos); + + dialog.dismiss(); + }) + .setNegativeButton(R.string.no, (dialog, which) -> dialog.dismiss()) + .show(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void manageVIewVideos(APIResponse apiResponse) { + if (type == HISTORY) { + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + displayVideosFragment = new DisplayVideosFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.TIMELINE_TYPE, HISTORY); + displayVideosFragment.setArguments(bundle); + ft.replace(R.id.container, displayVideosFragment); + ft.addToBackStack(null); + ft.commit(); + } + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/WebviewActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/WebviewActivity.java new file mode 100644 index 000000000..761bc104a --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/WebviewActivity.java @@ -0,0 +1,183 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.Toast; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import org.jetbrains.annotations.NotNull; + +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; +import app.fedilab.android.peertube.webview.CustomWebview; +import app.fedilab.android.peertube.webview.MastalabWebChromeClient; +import app.fedilab.android.peertube.webview.MastalabWebViewClient; +import es.dmoral.toasty.Toasty; + + +public class WebviewActivity extends BaseActivity { + + private String url; + private boolean peertubeLink; + private CustomWebview webView; + + + @SuppressLint("SetJavaScriptEnabled") + @Override + protected void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_webview); + Bundle b = getIntent().getExtras(); + if (b != null) { + url = b.getString("url", null); + peertubeLink = b.getBoolean("peertubeLink", false); + } + if (url == null) + finish(); + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + webView = Helper.initializeWebview(WebviewActivity.this, R.id.webview, null); + setTitle(""); + FrameLayout webview_container = findViewById(R.id.webview_container); + final ViewGroup videoLayout = findViewById(R.id.videoLayout); // Your own view, read class comments + webView.getSettings().setJavaScriptEnabled(true); + + + MastalabWebChromeClient mastalabWebChromeClient = new MastalabWebChromeClient(WebviewActivity.this, webView, webview_container, videoLayout); + mastalabWebChromeClient.setOnToggledFullscreen(fullscreen -> { + + if (fullscreen) { + videoLayout.setVisibility(View.VISIBLE); + WindowManager.LayoutParams attrs = getWindow().getAttributes(); + attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + attrs.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + getWindow().setAttributes(attrs); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); + } else { + WindowManager.LayoutParams attrs = getWindow().getAttributes(); + attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; + attrs.flags &= ~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + getWindow().setAttributes(attrs); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + videoLayout.setVisibility(View.GONE); + } + }); + webView.setWebChromeClient(mastalabWebChromeClient); + MastalabWebViewClient mastalabWebViewClient = new MastalabWebViewClient(WebviewActivity.this); + webView.setWebViewClient(mastalabWebViewClient); + webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> { + + if (Build.VERSION.SDK_INT >= 23) { + if (ContextCompat.checkSelfPermission(WebviewActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(WebviewActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(WebviewActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, Helper.EXTERNAL_STORAGE_REQUEST_CODE); + } else { + Helper.manageDownloads(WebviewActivity.this, url); + } + } else { + Helper.manageDownloads(WebviewActivity.this, url); + } + }); + if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://")) + url = "http://" + url; + webView.loadUrl(url); + } + + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onCreateOptionsMenu(@NotNull Menu menu) { + getMenuInflater().inflate(R.menu.main_webview, menu); + if (peertubeLink) { + menu.findItem(R.id.action_go).setVisible(false); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + finish(); + return true; + } else if (itemId == R.id.action_go) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + try { + startActivity(browserIntent); + } catch (Exception e) { + Toasty.error(WebviewActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + } + return true; + } + return super.onOptionsItemSelected(item); + } + + public void setUrl(String newUrl) { + this.url = newUrl; + } + + @Override + public void onPause() { + super.onPause(); + if (webView != null) + webView.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + if (webView != null) + webView.onResume(); + } + + @Override + public void onBackPressed() { + if (webView.canGoBack()) { + webView.goBack(); + } else { + super.onBackPressed(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (webView != null) + webView.destroy(); + } + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/WebviewConnectActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/WebviewConnectActivity.java new file mode 100644 index 000000000..03a602f93 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/activities/WebviewConnectActivity.java @@ -0,0 +1,263 @@ +package app.fedilab.android.peertube.activities; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import static app.fedilab.android.peertube.client.RetrofitPeertubeAPI.updateCredential; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.os.Build; +import android.os.Bundle; +import android.view.MenuItem; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; +import android.webkit.WebChromeClient; +import android.webkit.WebResourceRequest; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ProgressBar; + +import androidx.appcompat.app.AlertDialog; + +import java.net.URL; +import java.util.regex.Matcher; + +import app.fedilab.android.peertube.BuildConfig; +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.client.RetrofitPeertubeAPI; +import app.fedilab.android.peertube.client.entities.OauthParams; +import app.fedilab.android.peertube.client.entities.Token; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.helper.Theme; + + +public class WebviewConnectActivity extends BaseActivity { + + + private WebView webView; + private AlertDialog alert; + private String clientId, clientSecret; + private String url; + + @SuppressWarnings("deprecation") + public static void clearCookies(Context context) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + CookieManager.getInstance().removeAllCookies(null); + CookieManager.getInstance().flush(); + } else { + CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(context); + cookieSyncMngr.startSync(); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.removeAllCookie(); + cookieManager.removeSessionCookie(); + cookieSyncMngr.stopSync(); + cookieSyncMngr.sync(); + } + } + + + @SuppressLint("SetJavaScriptEnabled") + public void onCreate(Bundle savedInstanceState) { + Theme.setTheme(this, HelperInstance.getLiveInstance(this), false); + super.onCreate(savedInstanceState); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + WebView.setWebContentsDebuggingEnabled(true); + setContentView(R.layout.activity_webview_connect); + Bundle b = getIntent().getExtras(); + if (b != null) { + url = b.getString("url"); + } + if (url == null) + finish(); + + clientId = sharedpreferences.getString(Helper.CLIENT_ID, null); + clientSecret = sharedpreferences.getString(Helper.CLIENT_SECRET, null); + + webView = findViewById(R.id.webviewConnect); + clearCookies(WebviewConnectActivity.this); + webView.getSettings().setJavaScriptEnabled(true); + CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true); + webView.getSettings().setUseWideViewPort(true); + webView.getSettings().setLoadWithOverviewMode(true); + webView.getSettings().setSupportZoom(true); + webView.getSettings().setDisplayZoomControls(false); + webView.getSettings().setBuiltInZoomControls(true); + webView.getSettings().setAllowContentAccess(true); + webView.getSettings().setLoadsImagesAutomatically(true); + webView.getSettings().setSupportMultipleWindows(false); + webView.getSettings().setAppCacheEnabled(true); + webView.getSettings().setDatabaseEnabled(true); + webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); + webView.getSettings().setMediaPlaybackRequiresUserGesture(true); + if (getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setTitle(R.string.login); + + final ProgressBar pbar = findViewById(R.id.progress_bar); + webView.setWebChromeClient(new WebChromeClient() { + @Override + public void onProgressChanged(WebView view, int progress) { + if (progress < 100 && pbar.getVisibility() == ProgressBar.GONE) { + pbar.setVisibility(ProgressBar.VISIBLE); + } + pbar.setProgress(progress); + if (progress == 100) { + pbar.setVisibility(ProgressBar.GONE); + } + } + }); + + webView.setWebViewClient(new WebViewClient() { + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + //Avoid to load first page for academic instances & openid + if (!BuildConfig.full_instances && url.contains("/client")) { + view.stopLoading(); + } + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + if (request.getUrl() != null) { + String url = request.getUrl().toString(); + Matcher matcher = Helper.redirectPattern.matcher(url); + if (matcher.find()) { + String externalAuthToken = matcher.group(1); + String username = matcher.group(2); + new Thread(() -> { + try { + OauthParams oauthParams = new OauthParams(); + oauthParams.setClient_id(sharedpreferences.getString(Helper.CLIENT_ID, null)); + oauthParams.setClient_secret(sharedpreferences.getString(Helper.CLIENT_SECRET, null)); + oauthParams.setGrant_type("password"); + oauthParams.setScope("upload"); + oauthParams.setResponse_type("code"); + oauthParams.setUsername(username); + oauthParams.setExternalAuthToken(externalAuthToken); + oauthParams.setPassword(externalAuthToken); + String instance = new URL(url).getHost(); + Token token = null; + try { + token = new RetrofitPeertubeAPI(WebviewConnectActivity.this, instance, null).manageToken(oauthParams); + } catch (Error error) { + error.printStackTrace(); + Error.displayError(WebviewConnectActivity.this, error); + } + if (token != null) { + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token.getAccess_token()); + editor.putString(Helper.PREF_SOFTWARE, null); + editor.putString(Helper.PREF_REMOTE_INSTANCE, null); + editor.putString(Helper.PREF_INSTANCE, instance); + editor.apply(); + updateCredential(WebviewConnectActivity.this, token.getAccess_token(), clientId, clientSecret, token.getRefresh_token(), new URL(url).getHost(), null); + finish(); + } + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + return true; + } + } + return super.shouldOverrideUrlLoading(view, request); + } + + /* @Override + public void onPageFinished(WebView view, String url) { + Matcher matcher = Helper.redirectPattern.matcher(url); + if (matcher.find()) { + String externalAuthToken = matcher.group(1); + String username = matcher.group(2); + new Thread(() -> { + try { + OauthParams oauthParams = new OauthParams(); + oauthParams.setClient_id(sharedpreferences.getString(Helper.CLIENT_ID, null)); + oauthParams.setClient_secret(sharedpreferences.getString(Helper.CLIENT_SECRET, null)); + oauthParams.setGrant_type("password"); + oauthParams.setScope("upload"); + oauthParams.setResponse_type("code"); + oauthParams.setUsername(username); + oauthParams.setExternalAuthToken(externalAuthToken); + oauthParams.setPassword(externalAuthToken); + String instance = new URL(url).getHost(); + Token token = null; + try { + token = new RetrofitPeertubeAPI(WebviewConnectActivity.this, instance, null).manageToken(oauthParams); + } catch (Error error) { + error.printStackTrace(); + Error.displayError(WebviewConnectActivity.this, error); + } + if (token != null) { + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token.getAccess_token()); + editor.putString(Helper.PREF_SOFTWARE, null); + editor.putString(Helper.PREF_REMOTE_INSTANCE, null); + editor.putString(Helper.PREF_INSTANCE, instance); + editor.apply(); + updateCredential(WebviewConnectActivity.this, token.getAccess_token(), clientId, clientSecret, token.getRefresh_token(), new URL(url).getHost(), null); + finish(); + } + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + super.onPageFinished(view, url); + }*/ + }); + webView.loadUrl(url); + + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + + @Override + public void onBackPressed() { + if (webView != null && webView.canGoBack()) { + webView.goBack(); + } else { + super.onBackPressed(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (alert != null) { + alert.dismiss(); + alert = null; + } + if (webView != null) { + webView.destroy(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/peertube/client/APIResponse.java b/app/src/main/java/app/fedilab/android/peertube/client/APIResponse.java new file mode 100644 index 000000000..ca46e5c97 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/APIResponse.java @@ -0,0 +1,258 @@ +package app.fedilab.android.peertube.client; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import java.util.List; +import java.util.Map; + +import app.fedilab.android.peertube.client.data.AccountData; +import app.fedilab.android.peertube.client.data.BlockData.Block; +import app.fedilab.android.peertube.client.data.CaptionData; +import app.fedilab.android.peertube.client.data.ChannelData; +import app.fedilab.android.peertube.client.data.CommentData; +import app.fedilab.android.peertube.client.data.InstanceData; +import app.fedilab.android.peertube.client.data.NotificationData; +import app.fedilab.android.peertube.client.data.PlaylistData; +import app.fedilab.android.peertube.client.data.VideoData; +import app.fedilab.android.peertube.client.data.VideoPlaylistData.VideoPlaylist; +import app.fedilab.android.peertube.client.entities.OverviewVideo; +import app.fedilab.android.peertube.client.entities.PlaylistExist; +import app.fedilab.android.peertube.client.entities.Rating; + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class APIResponse { + + private List accounts = null; + private List channels = null; + private String targetedId = null; + private String actionReturn = null; + private Rating rating; + private OverviewVideo overviewVideo = null; + private Map> videoExistPlaylist = null; + private List peertubes = null; + private List comments = null; + private List muted; + private List videoPlaylist; + private CommentData.CommentThreadData commentThreadData; + private List peertubeNotifications = null; + private List playlists = null; + private List domains = null; + private Map relationships = null; + private List captions = null; + private Error error = null; + private String since_id, max_id; + private List instances; + private String stringData; + private int statusCode; + private String captionText; + + public List getAccounts() { + return accounts; + } + + public void setAccounts(List accounts) { + this.accounts = accounts; + } + + public Error getError() { + return error; + } + + public void setError(Error error) { + this.error = error; + } + + public String getMax_id() { + return max_id; + } + + public void setMax_id(String max_id) { + this.max_id = max_id; + } + + public String getSince_id() { + return since_id; + } + + public void setSince_id(String since_id) { + this.since_id = since_id; + } + + + public List getDomains() { + return domains; + } + + public void setDomains(List domains) { + this.domains = domains; + } + + + public List getPeertubes() { + return peertubes; + } + + public void setPeertubes(List peertubes) { + this.peertubes = peertubes; + } + + + public List getPeertubeNotifications() { + return peertubeNotifications; + } + + public void setPeertubeNotifications(List peertubeNotifications) { + this.peertubeNotifications = peertubeNotifications; + } + + + public List getPlaylists() { + return playlists; + } + + public void setPlaylists(List playlists) { + this.playlists = playlists; + } + + + public String getTargetedId() { + return targetedId; + } + + public void setTargetedId(String targetedId) { + this.targetedId = targetedId; + } + + + public String getStringData() { + return stringData; + } + + public void setStringData(String stringData) { + this.stringData = stringData; + } + + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public Map getRelationships() { + return relationships; + } + + public void setRelationships(Map relationships) { + this.relationships = relationships; + } + + public List getInstances() { + return instances; + } + + public void setInstances(List instances) { + this.instances = instances; + } + + public List getCaptions() { + return captions; + } + + public void setCaptions(List captions) { + this.captions = captions; + } + + public String getCaptionText() { + return captionText; + } + + public void setCaptionText(String captionText) { + this.captionText = captionText; + } + + public List getChannels() { + return channels; + } + + public void setChannels(List channels) { + this.channels = channels; + } + + public List getComments() { + return comments; + } + + public void setComments(List comments) { + this.comments = comments; + } + + public Rating getRating() { + return rating; + } + + public void setRating(Rating rating) { + this.rating = rating; + } + + public OverviewVideo getOverviewVideo() { + return overviewVideo; + } + + public void setOverviewVideo(OverviewVideo overviewVideo) { + this.overviewVideo = overviewVideo; + } + + public String getActionReturn() { + return actionReturn; + } + + public void setActionReturn(String actionReturn) { + this.actionReturn = actionReturn; + } + + public List getMuted() { + return muted; + } + + public void setMuted(List muted) { + this.muted = muted; + } + + public List getVideoPlaylist() { + return videoPlaylist; + } + + public void setVideoPlaylist(List videoPlaylist) { + this.videoPlaylist = videoPlaylist; + } + + public Map> getVideoExistPlaylist() { + return videoExistPlaylist; + } + + public void setVideoExistPlaylist(Map> videoExistPlaylist) { + this.videoExistPlaylist = videoExistPlaylist; + } + + public CommentData.CommentThreadData getCommentThreadData() { + return commentThreadData; + } + + public void setCommentThreadData(CommentData.CommentThreadData commentThreadData) { + this.commentThreadData = commentThreadData; + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/MenuItemVideo.java b/app/src/main/java/app/fedilab/android/peertube/client/MenuItemVideo.java new file mode 100644 index 000000000..e433c4a62 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/MenuItemVideo.java @@ -0,0 +1,53 @@ +package app.fedilab.android.peertube.client; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +public class MenuItemVideo { + + int icon; + String title; + actionType action; + + public int getIcon() { + return icon; + } + + public void setIcon(int icon) { + this.icon = icon; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public actionType getAction() { + return action; + } + + public void setAction(actionType action) { + this.action = action; + } + + public enum actionType { + RESOLUTION, + SPEED, + CAPTION, + AUTONEXT + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/PeertubeService.java b/app/src/main/java/app/fedilab/android/peertube/client/PeertubeService.java new file mode 100644 index 000000000..b71c01468 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/PeertubeService.java @@ -0,0 +1,529 @@ +package app.fedilab.android.peertube.client; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import java.util.List; +import java.util.Map; + +import app.fedilab.android.peertube.client.data.AccountData; +import app.fedilab.android.peertube.client.data.BlockData; +import app.fedilab.android.peertube.client.data.CaptionData; +import app.fedilab.android.peertube.client.data.ChannelData; +import app.fedilab.android.peertube.client.data.CommentData; +import app.fedilab.android.peertube.client.data.InstanceData; +import app.fedilab.android.peertube.client.data.NotificationData; +import app.fedilab.android.peertube.client.data.PlaylistData; +import app.fedilab.android.peertube.client.data.PluginData; +import app.fedilab.android.peertube.client.data.VideoData; +import app.fedilab.android.peertube.client.data.VideoPlaylistData; +import app.fedilab.android.peertube.client.entities.CaptionsParams; +import app.fedilab.android.peertube.client.entities.ChannelParams; +import app.fedilab.android.peertube.client.entities.NotificationSettings; +import app.fedilab.android.peertube.client.entities.Oauth; +import app.fedilab.android.peertube.client.entities.OverviewVideo; +import app.fedilab.android.peertube.client.entities.PlaylistExist; +import app.fedilab.android.peertube.client.entities.Rating; +import app.fedilab.android.peertube.client.entities.Report; +import app.fedilab.android.peertube.client.entities.Token; +import app.fedilab.android.peertube.client.entities.UserMe; +import app.fedilab.android.peertube.client.entities.WellKnownNodeinfo; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.Header; +import retrofit2.http.Headers; +import retrofit2.http.Multipart; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Part; +import retrofit2.http.Path; +import retrofit2.http.Query; +import retrofit2.http.QueryMap; + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public interface PeertubeService { + + @GET("instances") + Call getInstances( + @QueryMap Map params, + @Query("nsfwPolicy[]") String nsfwPolicy, + @Query("categoriesOr[]") List categories, + @Query("languagesOr[]") List languages); + + //Server settings + @GET(".well-known/nodeinfo") + Call getWellKnownNodeinfo(); + + @GET("plugins/peertube-plugin-player-watermark/public-settings") + Call waterMark(); + + //Instance info + @GET("config/about") + Call configAbout(); + + //Instance config + @GET("config") + Call config(); + + @GET("{nodeInfoPath}") + Call getNodeinfo(@Path(value = "nodeInfoPath", encoded = true) String nodeInfoPath); + + @GET("{captionContent}") + Call getCaptionContent(@Path("captionContent") String captionContent); + + @GET("videos/categories") + Call> getCategories(); + + @GET("videos/languages") + Call> getLanguages(); + + @GET("videos/privacies") + Call> getPrivacies(); + + @GET("video-playlists/privacies") + Call> getPlaylistsPrivacies(); + + @GET("videos/licences") + Call> getLicences(); + + //This one doesn't use api/v1 path + @GET("client/locales/{local}/server.json") + Call> getTranslations(@Path("local") String local); + + + //TOKEN + //Refresh + @FormUrlEncoded + @POST("users/token") + Call createOpenIdToken( + @Field("client_id") String client_id, + @Field("client_secret") String client_secret, + @Field("response_type") String response_type, + @Field("grant_type") String grant_type, + @Field("scope") String scope, + @Field("username") String username, + @Field("password") String password, + @Field("externalAuthToken") String externalAuthToken); + + //TOKEN + //Refresh + @FormUrlEncoded + @POST("users/token") + Call createToken( + @Field("client_id") String client_id, + @Field("client_secret") String client_secret, + @Field("grant_type") String grant_type, + @Field("username") String username, + @Field("password") String password); + + //TOKEN + //Refresh + @FormUrlEncoded + @POST("users/token") + Call refreshToken( + @Field("client_id") String client_id, + @Field("client_secret") String client_secret, + @Field("refresh_token") String refresh_token, + @Field("grant_type") String grant_type); + + @GET("users/me") + Call verifyCredentials(@Header("Authorization") String credentials); + + + @GET("users/me/video-quota-used") + Call getVideoQuota(@Header("Authorization") String credentials); + + @FormUrlEncoded + @PUT("videos/{id}/watching") + Call addToHistory( + @Header("Authorization") String credentials, + @Path("id") String id, + @Field("currentTime") long currentTime); + + + @FormUrlEncoded + @PUT("users/me") + Call updateUser( + @Header("Authorization") String credentials, + @Field("videosHistoryEnabled") Boolean videosHistoryEnabled, + @Field("autoPlayVideo") Boolean autoPlayVideo, + @Field("autoPlayNextVideo") Boolean autoPlayNextVideo, + @Field("webTorrentEnabled") Boolean webTorrentEnabled, + @Field("videoLanguages") List videoLanguages, + @Field("description") String description, + @Field("displayName") String displayName, + @Field("nsfwPolicy") String nsfwPolicy + ); + + @Multipart + @POST("video-channels/{channelHandle}/avatar/pick") + Call updateChannelProfilePicture( + @Header("Authorization") String credentials, + @Path("channelHandle") String channelHandle, + @Part MultipartBody.Part avatarfile); + + @Multipart + @POST("users/me/avatar/pick") + Call updateProfilePicture( + @Header("Authorization") String credentials, + @Part MultipartBody.Part avatarfile); + + //Timelines Authenticated + //Subscriber timeline + @GET("users/me/subscriptions/videos?sort=-publishedAt") + Call getSubscriptionVideos( + @Header("Authorization") String credentials, + @Query("start") String maxId, + @Query("count") String coun, + @Query("languageOneOf") List languageOneOf); + + //Overview videos + @GET("overviews/videos") + Call getOverviewVideos( + @Header("Authorization") String credentials, + @Query("page") String page, + @Query("nsfw") String nsfw, + @Query("languageOneOf") List languageOneOf); + + //Most liked videos + @GET("videos?sort=-likes") + Call getMostLikedVideos( + @Header("Authorization") String credentials, + @Query("start") String maxId, + @Query("count") String count, + @Query("nsfw") String nsfw, + @Query("languageOneOf") List languageOneOf); + + //Trending videos + @GET("videos?sort=-trending") + Call getTrendingVideos( + @Header("Authorization") String credentials, + @Query("start") String maxId, + @Query("count") String count, + @Query("nsfw") String nsfw, + @Query("languageOneOf") List languageOneOf); + + //Recently added videos + @GET("videos?sort=-publishedAt") + Call getRecentlyAddedVideos( + @Header("Authorization") String credentials, + @Query("start") String maxId, + @Query("count") String count, + @Query("nsfw") String nsfw, + @Query("languageOneOf") List languageOneOf); + + //Local videos + @GET("videos?sort=-publishedAt&filter=local") + Call getLocalVideos( + @Header("Authorization") String credentials, + @Query("start") String maxId, + @Query("count") String count, + @Query("nsfw") String nsfw, + @Query("languageOneOf") List languageOneOf); + + //History + @GET("users/me/history/videos") + Call getHistory( + @Header("Authorization") String credentials, + @Query("start") String maxId, + @Query("count") String count, + @Query("nsfw") String nsfw, + @Query("startDate") String startDate, + @Query("endDate") String endDate + ); + + @POST("users/me/history/videos/remove") + Call deleteHistory( + @Header("Authorization") String credentials); + + //Search videos + @GET("search/videos") + Call searchVideos( + @Header("Authorization") String credentials, + @Query("search") String search, + @Query("start") String maxId, + @Query("count") String count, + @Query("nsfw") String nsfw); + + //Search channels + @GET("search/video-channels") + Call searchChannels( + @Header("Authorization") String credentials, + @Query("search") String search, + @Query("searcharget") String searchTarget, + @Query("start") String maxId, + @Query("count") String count); + + //Search + @GET("search/videos") + Call searchNextVideo( + @Header("Authorization") String credentials, + @Query("tagsOneOf") List tagsOneOf, + @Query("start") String maxId, + @Query("count") String count, + @Query("nsfw") String nsfw); + + //Get notifications + @GET("users/me/notifications") + Call getNotifications(@Header("Authorization") String credentials, @Query("start") String maxId, @Query("count") String count, @Query("since_id") String sinceId); + + @GET("users/me/notifications?start=0&count=0&unread=true") + Call countNotifications(@Header("Authorization") String credentials); + + @POST("users/me/notifications/read-all") + Call markAllAsRead(@Header("Authorization") String credentials); + + @FormUrlEncoded + @POST("users/me/notifications/read") + Call markAsRead(@Header("Authorization") String credentials, @Field("ids[]") List ids); + + //Update Notification settings + @PUT("users/me/notification-settings") + Call updateNotifications(@Header("Authorization") String credentials, @Body NotificationSettings notificationSettings); + + //Get/Post/Update/Delete video + //Get a video + @GET("videos/{id}") + Call getVideo(@Path("id") String id); + + //Get a video description + @GET("videos/{uuid}/description") + Call getVideoDescription(@Path("uuid") String uuid); + + @GET("videos/{id}") + Call getMyVideo(@Header("Authorization") String credentials, @Path("id") String id); + + //Get my video + @GET("users/me/videos?sort=-publishedAt") + Call getMyVideos(@Header("Authorization") String credentials, @Query("start") String maxId, @Query("count") String count); + + //Get user videos + @GET("accounts/{name}/videos?sort=-publishedAt") + Call getVideosForAccount( + @Path("name") String name, + @Query("start") String maxId, + @Query("count") String count, + @Query("nsfw") String nsfw + ); + + @POST("videos/{id}/views") + Call postView(@Path("id") String id); + + @Multipart + @PUT("videos/{id}") + Call updateVideo( + @Header("Authorization") String credentials, + @Path("id") String videoId, + @Part("channelId") RequestBody channelId, + @Part("name") RequestBody name, + @Part("category") int category, + @Part("commentsEnabled") boolean commentsEnabled, + @Part("description") RequestBody description, + @Part("downloadEnabled") boolean downloadEnabled, + @Part("language") RequestBody language, + @Part("licence") RequestBody licence, + @Part("nsfw") boolean nsfw, + @Part("privacy") int privacy, + @Part("support") RequestBody support, + @Part("tags[]") List tags, + @Part("waitTranscoding") boolean waitTranscoding, + @Part MultipartBody.Part thumbnailfile, + @Part MultipartBody.Part previewfile); + + @DELETE("videos/{id}") + Call deleteVideo(@Header("Authorization") String credentials, @Path("id") String videoId); + + + @GET("oauth-clients/local") + Call getOauthAcad(); + + @GET("oauth-clients/local") + Call getOauth(@Query("client_name") String client_name, @Query("redirect_uris") String redirect_uris, @Query("scopes") String scopes, @Query("website") String website); + + + //Post/Update/Delete channel + //Channels for account + @GET("accounts/{accountId}/video-channels") + Call getChannelsForAccount(@Path("accountId") String accountId); + + //Get a channel + @GET("video-channels/{name}") + Call getChannel(@Path("name") String name); + + @GET("video-channels") + Call getAllChannels(); + + @GET("video-channels/{channelHandle}/videos") + Call getChannelVideos( + @Path("channelHandle") String channelHandle, + @Query("start") String maxId, + @Query("count") String count, + @Query("nsfw") String nsfw); + + @POST("video-channels") + Call addChannel(@Header("Authorization") String credentials, @Body ChannelParams channelParams); + + @PUT("video-channels/{channelHandle}") + Call updateChannel(@Header("Authorization") String credentials, @Path("channelHandle") String channelHandle, @Body ChannelParams channelParams); + + @DELETE("video-channels/{channelHandle}") + Call deleteChannel(@Header("Authorization") String credentials, @Path("channelHandle") String channelHandle); + + + //Get/Post/Update/Delete playlist + @GET("video-playlists") + Call getPlaylists(); + + + //Get a single account + @GET("accounts/{accountHandle}") + Call getAccount(@Path("accountHandle") String accountHandle); + + //Get/Post/Update/Delete playlist + @GET("accounts/{accountHandle}/video-playlists") + Call getPlaylistsForAccount(@Header("Authorization") String credentials, @Path("accountHandle") String accountHandle); + + @GET("video-playlists/{id}") + Call getPlaylist(@Path("id") String id); + + @GET("video-playlists/{id}/videos") + Call getVideosPlayList(@Header("Authorization") String credentials, @Path("id") String id, @Query("start") String maxId, @Query("count") String count); + + @GET("users/me/video-playlists/videos-exist") + Call>> getVideoExistsInPlaylist(@Header("Authorization") String credentials, @Query("videoIds") List videoIds); + + @Multipart + @POST("video-playlists") + Call addPlaylist( + @Header("Authorization") String credentials, + @Part("displayName") RequestBody displayName, + @Part("description") RequestBody description, + @Part("privacy") int privacy, + @Part("videoChannelId") RequestBody videoChannelId, + @Part MultipartBody.Part thumbnailfile); + + @Multipart + @PUT("video-playlists/{id}") + Call updatePlaylist( + @Header("Authorization") String credentials, + @Path("id") String videoId, + @Part("displayName") RequestBody displayName, + @Part("description") RequestBody description, + @Part("privacy") int privacy, + @Part("videoChannelId") RequestBody videoChannelId, + @Part MultipartBody.Part thumbnailfil); + + + @FormUrlEncoded + @POST("video-playlists/{id}/videos") + Call addVideoInPlaylist(@Header("Authorization") String credentials, @Path("id") String id, @Field("videoId") String videoId); + + + @DELETE("video-playlists/{id}") + Call deletePlaylist(@Header("Authorization") String credentials, @Path("id") String playlistId); + + @DELETE("video-playlists/{id}/videos/{playlistElementId}") + Call deleteVideoInPlaylist(@Header("Authorization") String credentials, @Path("id") String videoId, @Path("playlistElementId") String playlistElementId); + + //Get/Update/Delete captions + @GET("videos/{id}/captions") + Call getCaptions(@Path("id") String videoId); + + @PUT("videos/{id}/captions/{captionLanguage}") + Call updateCaptions(@Header("Authorization") String credentials, @Path("id") String videoId, @Path("captionLanguage") String captionLanguage, @Body CaptionsParams captionsParams, @Part MultipartBody.Part captionfile); + + @DELETE("videos/{id}/captions/{captionLanguage}") + Call deleteCaptions(@Header("Authorization") String credentials, @Path("id") String videoId, @Path("captionLanguage") String captionLanguage); + + + //Subscribe/Unsubscribe + //subscribers + @GET("users/me/subscriptions") + Call getSubscription(@Header("Authorization") String credentials, @Query("start") String maxId, @Query("count") String count); + + @GET("users/me/subscriptions/exist") + Call> getSubscriptionsExist(@Header("Authorization") String credentials, @Query("uris") List uris); + + @FormUrlEncoded + @POST("users/me/subscriptions") + Call follow(@Header("Authorization") String credentials, @Field("uri") String uri); + + @DELETE("users/me/subscriptions/{subscriptionHandle}") + Call unfollow(@Header("Authorization") String credentials, @Path("subscriptionHandle") String subscriptionHandle); + + //Mute/Unmute + //Muted accounts + @GET("users/me/blocklist/accounts") + Call getMuted(@Header("Authorization") String credentials, @Query("start") String maxId, @Query("count") String count); + + @FormUrlEncoded + @POST("users/me/blocklist/accounts") + Call mute(@Header("Authorization") String credentials, @Field("accountName") String accountName); + + @DELETE("users/me/blocklist/accounts/{accountName}") + Call unmute(@Header("Authorization") String credentials, @Path("accountName") String accountName); + + + //Get video rating + @GET("users/me/videos/{id}/rating") + Call getRating(@Header("Authorization") String credentials, @Path("id") String id); + + //Like/unlike + @FormUrlEncoded + @PUT("videos/{id}/rate") + Call rate(@Header("Authorization") String credentials, @Path("id") String id, @Field("rating") String rating); + + + //Comment + @GET("videos/{id}/comment-threads") + Call getComments(@Path("id") String id, @Query("start") String maxId, @Query("count") String count); + + @GET("videos/{id}/comment-threads/{threadId}") + Call getReplies(@Path("id") String id, @Path("threadId") String threadId); + + @FormUrlEncoded + @POST("videos/{id}/comment-threads") + Call postComment(@Header("Authorization") String credentials, @Path("id") String id, @Field("text") String text); + + @FormUrlEncoded + @POST("videos/{id}/comments/{commentId}") + Call postReply(@Header("Authorization") String credentials, @Path("id") String id, @Path("commentId") String commentId, @Field("text") String text); + + @DELETE("videos/{id}/comments/{commentId}") + Call deleteComment(@Header("Authorization") String credentials, @Path("id") String id, @Path("commentId") String commentId); + + @POST("bulk/remove-comments-of") + Call deleteAllCommentForAccount(@Header("Authorization") String credentials, @Field("accountName") String accountName, @Field("scope") String scope); + + @Headers({"Content-Type: application/json", "Cache-Control: max-age=640000"}) + @POST("abuses") + Call report( + @Header("Authorization") String credentials, + @Body Report report); + + @FormUrlEncoded + @POST("users/register") + Call register( + @Field("email") String email, + @Field("password") String password, + @Field("username") String username, + @Field("displayName") String displayName + ); +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/RetrofitPeertubeAPI.java b/app/src/main/java/app/fedilab/android/peertube/client/RetrofitPeertubeAPI.java new file mode 100644 index 000000000..96d4ab30f --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/RetrofitPeertubeAPI.java @@ -0,0 +1,2023 @@ +package app.fedilab.android.peertube.client; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.webkit.MimeTypeMap; +import android.webkit.URLUtil; + +import androidx.documentfile.provider.DocumentFile; + +import org.jetbrains.annotations.NotNull; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import app.fedilab.android.peertube.BuildConfig; +import app.fedilab.android.peertube.R; +import app.fedilab.android.peertube.activities.MainActivity; +import app.fedilab.android.peertube.client.data.AccountData; +import app.fedilab.android.peertube.client.data.BlockData; +import app.fedilab.android.peertube.client.data.CaptionData; +import app.fedilab.android.peertube.client.data.ChannelData; +import app.fedilab.android.peertube.client.data.CommentData; +import app.fedilab.android.peertube.client.data.InstanceData; +import app.fedilab.android.peertube.client.data.NotificationData; +import app.fedilab.android.peertube.client.data.PlaylistData; +import app.fedilab.android.peertube.client.data.PluginData; +import app.fedilab.android.peertube.client.data.VideoData; +import app.fedilab.android.peertube.client.data.VideoPlaylistData; +import app.fedilab.android.peertube.client.entities.AccountCreation; +import app.fedilab.android.peertube.client.entities.ChannelParams; +import app.fedilab.android.peertube.client.entities.InstanceParams; +import app.fedilab.android.peertube.client.entities.Oauth; +import app.fedilab.android.peertube.client.entities.OauthParams; +import app.fedilab.android.peertube.client.entities.OverviewVideo; +import app.fedilab.android.peertube.client.entities.PeertubeInformation; +import app.fedilab.android.peertube.client.entities.PlaylistExist; +import app.fedilab.android.peertube.client.entities.PlaylistParams; +import app.fedilab.android.peertube.client.entities.Rating; +import app.fedilab.android.peertube.client.entities.Report; +import app.fedilab.android.peertube.client.entities.Token; +import app.fedilab.android.peertube.client.entities.UserMe; +import app.fedilab.android.peertube.client.entities.UserSettings; +import app.fedilab.android.peertube.client.entities.VideoParams; +import app.fedilab.android.peertube.client.entities.WellKnownNodeinfo; +import app.fedilab.android.peertube.helper.Helper; +import app.fedilab.android.peertube.helper.HelperInstance; +import app.fedilab.android.peertube.sqlite.AccountDAO; +import app.fedilab.android.peertube.sqlite.Sqlite; +import app.fedilab.android.peertube.viewmodel.ChannelsVM; +import app.fedilab.android.peertube.viewmodel.CommentVM; +import app.fedilab.android.peertube.viewmodel.PlaylistsVM; +import app.fedilab.android.peertube.viewmodel.TimelineVM; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +@SuppressWarnings({"unused", "RedundantSuppression", "ConstantConditions"}) +public class RetrofitPeertubeAPI { + + final OkHttpClient okHttpClient = new OkHttpClient.Builder() + .readTimeout(60, TimeUnit.SECONDS) + .connectTimeout(60, TimeUnit.SECONDS) + .build(); + private final String finalUrl; + private final Context _context; + private final String instance; + private final String count; + private final String showNSFWVideos; + private String token; + private Set selection; + + + public RetrofitPeertubeAPI(Context context) { + _context = context; + instance = HelperInstance.getLiveInstance(context); + finalUrl = "https://" + HelperInstance.getLiveInstance(context) + "/api/v1/"; + SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + count = String.valueOf(sharedpreferences.getInt(Helper.SET_VIDEOS_PER_PAGE, Helper.VIDEOS_PER_PAGE)); + String currentSensitive = sharedpreferences.getString(_context.getString(R.string.set_video_sensitive_choice), Helper.BLUR); + if (currentSensitive.compareTo(Helper.DO_NOT_LIST) == 0) { + showNSFWVideos = "false"; + } else if (currentSensitive.compareTo(Helper.BLUR) == 0) { + showNSFWVideos = "both"; + } else { + showNSFWVideos = "true"; + } + } + + public RetrofitPeertubeAPI(Context context, String instance, String token) { + _context = context; + this.instance = instance; + this.token = token; + finalUrl = "https://" + instance + "/api/v1/"; + SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + count = String.valueOf(sharedpreferences.getInt(Helper.SET_VIDEOS_PER_PAGE, Helper.VIDEOS_PER_PAGE)); + String currentSensitive = sharedpreferences.getString(_context.getString(R.string.set_video_sensitive_choice), Helper.BLUR); + if (currentSensitive.compareTo(Helper.DO_NOT_LIST) == 0) { + showNSFWVideos = "false"; + } else if (currentSensitive.compareTo(Helper.BLUR) == 0) { + showNSFWVideos = "both"; + } else { + showNSFWVideos = "true"; + } + } + + + public static void updateCredential(Activity activity, String token, String client_id, String client_secret, String refresh_token, String host, String software) { + new Thread(() -> { + AccountData.Account account; + String instance = host; + try { + UserMe userMe = new RetrofitPeertubeAPI(activity, instance, token).verifyCredentials(); + account = userMe.getAccount(); + } catch (Error error) { + Error.displayError(activity, error); + error.printStackTrace(); + return; + } + try { + //At the state the instance can be encoded + instance = URLDecoder.decode(instance, "utf-8"); + } catch (UnsupportedEncodingException ignored) { + } + SharedPreferences sharedpreferences = activity.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + account.setToken(token); + account.setClient_id(client_id); + account.setClient_secret(client_secret); + account.setRefresh_token(refresh_token); + account.setHost(instance); + SQLiteDatabase db = Sqlite.getInstance(activity.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + boolean userExists = new AccountDAO(activity, db).userExist(account); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_KEY_ID, account.getId()); + editor.putString(Helper.PREF_KEY_NAME, account.getUsername()); + boolean remote_account = software != null && software.toUpperCase().trim().compareTo("PEERTUBE") != 0; + if (!remote_account) { + editor.putString(Helper.PREF_INSTANCE, host); + } + editor.apply(); + if (userExists) + new AccountDAO(activity, db).updateAccountCredential(account); + else { + if (account.getUsername() != null && account.getCreatedAt() != null) + new AccountDAO(activity, db).insertAccount(account, software); + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + Intent mainActivity = new Intent(activity, MainActivity.class); + mainActivity.putExtra(Helper.INTENT_ACTION, Helper.ADD_USER_INTENT); + activity.startActivity(mainActivity); + activity.finish(); + }; + mainHandler.post(myRunnable); + }).start(); + } + + private String getToken() { + if (token != null) { + return "Bearer " + token; + } else { + return null; + } + } + + private PeertubeService init() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(finalUrl) + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClient) + .build(); + SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + if (token == null) { + token = Helper.getToken(_context); + } + selection = sharedpreferences.getStringSet(_context.getString(R.string.set_video_language_choice), null); + return retrofit.create(PeertubeService.class); + } + + private PeertubeService initTranslation() { + if (!URLUtil.isValidUrl("https://" + instance)) { + return null; + } + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + instance) + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClient) + .build(); + return retrofit.create(PeertubeService.class); + } + + /*** + * Verifiy credential of the authenticated user *synchronously* + * @return Account + */ + public Token manageToken(OauthParams oauthParams) throws Error { + PeertubeService peertubeService = init(); + Call refreshTokenCall = null; + if (oauthParams.getExternalAuthToken() != null) { + refreshTokenCall = peertubeService.createOpenIdToken(oauthParams.getClient_id(), oauthParams.getClient_secret(), oauthParams.getResponse_type(), oauthParams.getGrant_type(), oauthParams.getScope(), oauthParams.getUsername(), oauthParams.getPassword(), oauthParams.getExternalAuthToken()); + } else if (oauthParams.getGrant_type().compareTo("password") == 0) { + refreshTokenCall = peertubeService.createToken(oauthParams.getClient_id(), oauthParams.getClient_secret(), oauthParams.getGrant_type(), oauthParams.getUsername(), oauthParams.getPassword()); + } else if (oauthParams.getGrant_type().compareTo("refresh_token") == 0) { + refreshTokenCall = peertubeService.refreshToken(oauthParams.getClient_id(), oauthParams.getClient_secret(), oauthParams.getRefresh_token(), oauthParams.getGrant_type()); + } + if (refreshTokenCall != null) { + try { + Response response = refreshTokenCall.execute(); + if (response.isSuccessful()) { + Token tokenReply = response.body(); + if (oauthParams.getGrant_type().compareTo("refresh_token") == 0 && tokenReply != null) { + SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, tokenReply.getAccess_token()); + editor.putString(Helper.PREF_SOFTWARE, null); + editor.putString(Helper.PREF_REMOTE_INSTANCE, null); + editor.apply(); + SQLiteDatabase db = Sqlite.getInstance(_context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + new AccountDAO(_context, db).updateAccountToken(tokenReply); + } + return tokenReply; + } else { + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + throw error; + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + + /** + * POST a view count for a video + */ + public void postView(String videoUuid) { + if (videoUuid == null) { + return; + } + PeertubeService peertubeService = init(); + Call postViewCall = peertubeService.postView(videoUuid); + try { + Response dd = postViewCall.execute(); + } catch (IOException ignored) { + } + } + + + /** + * Retrieve notifications + * + * @return APIResponse + */ + public int unreadNotifications() { + PeertubeService peertubeService = init(); + Call notificationsCall = peertubeService.countNotifications("Bearer " + token); + try { + Response response = notificationsCall.execute(); + if (response.isSuccessful() && response.body() != null) { + return response.body().total; + } + } catch (IOException ignored) { + } + return 0; + } + + /** + * Mark all notifications as read + */ + public void markAllAsRead() { + PeertubeService peertubeService = init(); + Call notificationsCall = peertubeService.markAllAsRead("Bearer " + token); + try { + Response response = notificationsCall.execute(); + } catch (IOException ignored) { + } + } + + /** + * Mark a notification as read + */ + public void markAsRead(String id) { + PeertubeService peertubeService = init(); + ArrayList ids = new ArrayList<>(); + ids.add(id); + Call notificationsCall = peertubeService.markAsRead("Bearer " + token, ids); + try { + Response response = notificationsCall.execute(); + } catch (IOException ignored) { + } + } + + /** + * Retrieve notifications + * + * @return APIResponse + */ + public APIResponse getNotifications() { + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + + Call notificationsCall = peertubeService.getNotifications("Bearer " + token, "0", "40", null); + try { + Response response = notificationsCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setPeertubeNotifications(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + /** + * Retrieve notifications + * + * @param max_id String pagination + * @param since_id String pagination + * @return APIResponse + */ + public APIResponse getNotifications(String max_id, String since_id) { + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + + Call notificationsCall = peertubeService.getNotifications("Bearer " + token, max_id, "20", since_id); + try { + Response response = notificationsCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setPeertubeNotifications(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + /** + * Get caption content + * + * @param path String path to caption + * @return APIResponse + */ + public APIResponse getCaptionContent(String path) { + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + Call captionContentCall = peertubeService.getCaptionContent(path); + try { + Response response = captionContentCall.execute(); + if (response.isSuccessful()) { + apiResponse.setCaptionText(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + /** + * Get videos in a channel + * + * @param channelId String id of the channel + * @param max_id String pagination + * @return APIResponse + */ + public APIResponse getVideosForChannel(String channelId, String max_id) { + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + Call videoCall = peertubeService.getChannelVideos(channelId, max_id, count, showNSFWVideos); + if (videoCall != null) { + try { + Response response = videoCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setPeertubes(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + } + return apiResponse; + } + + + public APIResponse deleteHistory() { + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + Call stringCall = peertubeService.deleteHistory(getToken()); + if (stringCall != null) { + try { + Response response = stringCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setActionReturn(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + } + return apiResponse; + } + + public APIResponse getHistory(String max_id, String startDate, String endDate) { + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + Call videoCall = peertubeService.getHistory(getToken(), max_id, count, showNSFWVideos, startDate, endDate); + if (videoCall != null) { + try { + Response response = videoCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setPeertubes(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + } + return apiResponse; + } + + public APIResponse getTL(TimelineVM.TimelineType timelineType, String max_id, String forAccount) { + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + Call videoCall = null; + ArrayList filter = selection != null ? new ArrayList<>(selection) : null; + switch (timelineType) { + case MY_VIDEOS: + videoCall = peertubeService.getMyVideos(getToken(), max_id, count); + break; + case ACCOUNT_VIDEOS: + videoCall = peertubeService.getVideosForAccount(forAccount, max_id, count, showNSFWVideos); + break; + case SUBSCRIBTIONS: + if (forAccount == null) { + videoCall = peertubeService.getSubscriptionVideos(getToken(), max_id, count, filter); + } else { + videoCall = peertubeService.getChannelVideos(forAccount, max_id, count, showNSFWVideos); + } + break; + case MOST_LIKED: + videoCall = peertubeService.getMostLikedVideos(getToken(), max_id, count, showNSFWVideos, filter); + break; + case LOCAL: + videoCall = peertubeService.getLocalVideos(getToken(), max_id, count, showNSFWVideos, filter); + break; + case TRENDING: + videoCall = peertubeService.getTrendingVideos(getToken(), max_id, count, showNSFWVideos, filter); + break; + case HISTORY: + videoCall = peertubeService.getHistory(getToken(), max_id, count, showNSFWVideos, null, null); + break; + case RECENT: + videoCall = peertubeService.getRecentlyAddedVideos(getToken(), max_id, count, showNSFWVideos, filter); + break; + } + if (videoCall != null) { + try { + Response response = videoCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setPeertubes(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + } + return apiResponse; + } + + /** + * Retrieves overview videos *synchronously* + * + * @param page String id pagination + * @return APIResponse + */ + public APIResponse getOverviewVideo(String page) { + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + ArrayList filter = selection != null ? new ArrayList<>(selection) : null; + Call overviewVideoCall = peertubeService.getOverviewVideos(getToken(), page, showNSFWVideos, filter); + try { + Response response = overviewVideoCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setOverviewVideo(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + + /** + * Retrieves playlists for a video *synchronously* + * + * @param videoIds List ids of videos + * @return APIResponse + */ + public APIResponse getVideosExist(List videoIds) { + PeertubeService peertubeService = init(); + APIResponse apiResponse = new APIResponse(); + try { + Call>> videoExistsInPlaylist = peertubeService.getVideoExistsInPlaylist(getToken(), videoIds); + Response>> response = videoExistsInPlaylist.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setVideoExistPlaylist(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return apiResponse; + } + + /** + * Update history + * + * @param videoId String + * @param currentTime int + */ + public void updateHistory(String videoId, long currentTime) { + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + Call updateUser = peertubeService.addToHistory(getToken(), + videoId, + currentTime + ); + try { + Response response = updateUser.execute(); + if (response.isSuccessful()) { + apiResponse.setActionReturn(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Update account information + * + * @param userSettings UserSettings + */ + public UserMe.AvatarResponse updateUser(UserSettings userSettings) throws IOException, Error { + APIResponse apiResponse = new APIResponse(); + UserMe.AvatarResponse avatarResponse = null; + PeertubeService peertubeService = init(); + + Call updateNotifications = peertubeService.updateNotifications(getToken(), userSettings.getNotificationSettings()); + Response responseNotif = updateNotifications.execute(); + Call updateUser = peertubeService.updateUser(getToken(), + userSettings.isVideosHistoryEnabled(), + userSettings.isAutoPlayVideo(), + userSettings.isAutoPlayNextVideo(), + userSettings.isWebTorrentEnabled(), + userSettings.getVideoLanguages(), + userSettings.getDescription(), + userSettings.getDisplayName(), + userSettings.getNsfwPolicy() + ); + Response response = updateUser.execute(); + if (response.isSuccessful()) { + apiResponse.setActionReturn(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + + error.setError(_context.getString(R.string.toast_error)); + } + throw error; + } + if (userSettings.getAvatarfile() != null) { + MultipartBody.Part bodyThumbnail = createFile("avatarfile", userSettings.getAvatarfile(), userSettings.getFileName()); + Call updateProfilePicture = peertubeService.updateProfilePicture(getToken(), bodyThumbnail); + Response responseAvatar = updateProfilePicture.execute(); + if (response.isSuccessful()) { + avatarResponse = responseAvatar.body(); + } else { + setError(apiResponse, response.code(), response.errorBody()); + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + throw error; + } + } + return avatarResponse; + } + + private MultipartBody.Part createFile(@NotNull String paramName, @NotNull Uri uri, String filename) throws IOException { + + InputStream inputStream = _context.getContentResolver().openInputStream(uri); + ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); + int bufferSize = 1024; + byte[] buffer = new byte[bufferSize]; + + int len; + while ((len = inputStream.read(buffer)) != -1) { + byteBuffer.write(buffer, 0, len); + } + byte[] imageBytes = byteBuffer.toByteArray(); + String mime = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); + if (mime == null || mime.trim().length() == 0) { + mime = "png"; + } + if (filename == null) { + filename = "my_image." + mime; + } + RequestBody requestFile = RequestBody.create(imageBytes, MediaType.parse("image/" + mime)); + return MultipartBody.Part.createFormData(paramName, filename, requestFile); + } + + /** + * Check if users via their uris are following the authenticated user + * + * @param uris List + * @return APIResponse + */ + public APIResponse areFollowing(List uris) { + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + Call> followingCall = peertubeService.getSubscriptionsExist(getToken(), uris); + try { + Response> response = followingCall.execute(); + if (response.isSuccessful()) { + apiResponse.setRelationships(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return apiResponse; + } + + /** + * Find captions for a video + * + * @param videoId String id of the video + * @return APIResponse + */ + public APIResponse getCaptions(String videoId) { + + APIResponse apiResponse = new APIResponse(); + PeertubeService peertubeService = init(); + Call captions = peertubeService.getCaptions(videoId); + try { + Response response = captions.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setCaptions(response.body().data); + + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return apiResponse; + } + + /** + * About the instance + * + * @return AboutInstance + */ + public InstanceData.AboutInstance getAboutInstance() { + + PeertubeService peertubeService = init(); + Call about = peertubeService.configAbout(); + try { + Response response = about.execute(); + if (response.isSuccessful() && response.body() != null) { + return response.body().getInstance(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Config of the instance + * + * @return InstanceConfig + */ + public InstanceData.InstanceConfig getConfigInstance() { + + PeertubeService peertubeService = init(); + Call config = peertubeService.config(); + try { + Response response = config.execute(); + if (response.isSuccessful() && response.body() != null) { + return response.body(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + + /** + * Get watermark info + * + * @return PluginData.WaterMark + */ + public PluginData.WaterMark getWaterMark() { + + PeertubeService peertubeService = init(); + Call waterMarkCall = peertubeService.waterMark(); + try { + Response response = waterMarkCall.execute(); + if (response.isSuccessful() && response.body() != null) { + return response.body(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Get video quota + * + * @return UserMe.VideoQuota + */ + public UserMe.VideoQuota getVideoQuota() { + + PeertubeService peertubeService = init(); + Call videoQuotaCall = peertubeService.getVideoQuota(getToken()); + try { + Response response = videoQuotaCall.execute(); + if (response.isSuccessful() && response.body() != null) { + return response.body(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Returns informations about Peertube such privacies, licenses, etc. + * + * @return PeertubeInformation information about peertube + */ + public PeertubeInformation getPeertubeInformation() { + PeertubeInformation peertubeInformation = new PeertubeInformation(); + PeertubeService peertubeService = init(); + Call> categories = peertubeService.getCategories(); + try { + Response> response = categories.execute(); + if (response.isSuccessful()) { + peertubeInformation.setCategories(response.body()); + } else { + String categoriesStr = Helper.readFileFromAssets(_context, "categories.json"); + try { + JSONObject obj = new JSONObject(categoriesStr); + Iterator iter = obj.keys(); + LinkedHashMap data = new LinkedHashMap<>(); + while (iter.hasNext()) { + String key = iter.next(); + try { + String value = (String) obj.get(key); + data.put(Integer.valueOf(key), value); + } catch (JSONException ignored) { + } + } + peertubeInformation.setCategories(data); + } catch (JSONException e) { + e.printStackTrace(); + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + } + + } + } catch (IOException e) { + String categoriesStr = Helper.readFileFromAssets(_context, "categories.json"); + try { + JSONObject obj = new JSONObject(categoriesStr); + Iterator iter = obj.keys(); + LinkedHashMap data = new LinkedHashMap<>(); + while (iter.hasNext()) { + String key = iter.next(); + try { + String value = (String) obj.get(key); + data.put(Integer.valueOf(key), value); + } catch (JSONException ignored) { + } + } + peertubeInformation.setCategories(data); + } catch (JSONException e2) { + e2.printStackTrace(); + } + } + Call> languages = peertubeService.getLanguages(); + try { + Response> response = languages.execute(); + if (response.isSuccessful()) { + peertubeInformation.setLanguages(response.body()); + } else { + String languageSrt = Helper.readFileFromAssets(_context, "languages.json"); + try { + JSONObject obj = new JSONObject(languageSrt); + Iterator iter = obj.keys(); + LinkedHashMap data = new LinkedHashMap<>(); + while (iter.hasNext()) { + String key = iter.next(); + try { + String value = (String) obj.get(key); + data.put(key, value); + } catch (JSONException ignored) { + } + } + peertubeInformation.setLanguages(data); + } catch (JSONException e) { + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + } + + } + } catch (IOException e) { + String languageSrt = Helper.readFileFromAssets(_context, "languages.json"); + try { + JSONObject obj = new JSONObject(languageSrt); + Iterator iter = obj.keys(); + LinkedHashMap data = new LinkedHashMap<>(); + while (iter.hasNext()) { + String key = iter.next(); + try { + String value = (String) obj.get(key); + data.put(key, value); + } catch (JSONException ignored) { + } + } + peertubeInformation.setLanguages(data); + } catch (JSONException e2) { + } + } + Call> privacies = peertubeService.getPrivacies(); + try { + Response> response = privacies.execute(); + if (response.isSuccessful()) { + peertubeInformation.setPrivacies(response.body()); + } else { + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + Call> playlistsPrivacies = peertubeService.getPlaylistsPrivacies(); + try { + Response> response = playlistsPrivacies.execute(); + if (response.isSuccessful()) { + peertubeInformation.setPlaylistPrivacies(response.body()); + } else { + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + Call> licenses = peertubeService.getLicences(); + try { + Response> response = licenses.execute(); + if (response.isSuccessful()) { + peertubeInformation.setLicences(response.body()); + } else { + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + String lang = Locale.getDefault().getLanguage(); + if (lang.contains("-")) { + if (!lang.split("-")[0].trim().toLowerCase().startsWith("zh")) { + lang = lang.split("-")[0]; + } else { + lang = lang.split("-")[0] + "-" + lang.split("-")[1].toUpperCase(); + } + } + if (lang == null || lang.trim().length() == 0) { + lang = "en"; + } + Call> translations = initTranslation().getTranslations(lang); + try { + Response> response = translations.execute(); + if (response.isSuccessful()) { + peertubeInformation.setTranslations(response.body()); + } else { + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + translations = initTranslation().getTranslations("en"); + try { + response = translations.execute(); + if (response.isSuccessful()) { + peertubeInformation.setTranslations(response.body()); + } else { + error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } else { + error.setError(_context.getString(R.string.toast_error)); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return peertubeInformation; + } + + /** + * Get instances + * + * @param instanceParams InstanceParams + * @return APIResponse + */ + public APIResponse getInstances(InstanceParams instanceParams) { + PeertubeService peertubeService = init(); + LinkedHashMap params = new LinkedHashMap<>(); + params.put("start", "0"); + params.put("count", "250"); + params.put("healthy", "true"); + params.put("signup", "true"); + params.put("sort", "-totalUsers"); + Call instancesCall = peertubeService.getInstances(params, instanceParams.getNsfwPolicy(), instanceParams.getCategoriesOr(), instanceParams.getLanguagesOr()); + APIResponse apiResponse = new APIResponse(); + try { + Response response = instancesCall.execute(); + if (!response.isSuccessful()) { + setError(apiResponse, response.code(), response.errorBody()); + } else { + InstanceData instanceData = response.body(); + if (instanceData != null) { + apiResponse.setInstances(instanceData.data); + } + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + + /** + * Retrieves next peertube videos *synchronously* + * + * @param tags List search + * @return APIResponse + */ + public APIResponse searchNextVideos(List tags) { + PeertubeService peertubeService = init(); + Call searchVideosCall = peertubeService.searchNextVideo(getToken(), tags, "0", "20", showNSFWVideos); + APIResponse apiResponse = new APIResponse(); + try { + Response response = searchVideosCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setPeertubes(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + /** + * Retrieves peertube search *synchronously* + * + * @param query String search + * @return APIResponse + */ + public APIResponse searchPeertube(String query, String max_id) { + PeertubeService peertubeService = init(); + Call searchVideosCall = peertubeService.searchVideos(getToken(), query, max_id, count, showNSFWVideos); + APIResponse apiResponse = new APIResponse(); + try { + Response response = searchVideosCall.execute(); + + if (response.isSuccessful() && response.body() != null) { + apiResponse.setPeertubes(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + + /** + * Retrieves channels search *synchronously* + * + * @param query String search + * @return APIResponse + */ + public APIResponse searchChannels(String query, String max_id) { + PeertubeService peertubeService = init(); + Call searchChannelsCall = peertubeService.searchChannels(getToken(), query, "local", max_id, count); + APIResponse apiResponse = new APIResponse(); + try { + Response response = searchChannelsCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setChannels(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + + /*** + * Verifiy credential of the authenticated user *synchronously* + * @return Account + */ + public UserMe verifyCredentials() throws Error { + PeertubeService peertubeService = init(); + Call accountCall = peertubeService.verifyCredentials("Bearer " + token); + APIResponse apiResponse = new APIResponse(); + try { + Response response = accountCall.execute(); + if (response.isSuccessful() && response.body() != null) { + return response.body(); + } else { + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + throw error; + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return null; + } + + public APIResponse report(Report report) { + PeertubeService peertubeService = init(); + Call report1 = peertubeService.report(getToken(), report); + APIResponse apiResponse = new APIResponse(); + try { + Response response = report1.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setActionReturn(response.body().getItemStr().getLabel()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + /*** + * Update a video + * @param videoId String id of the video + * @param videoParams VideoParams params for the video + * @param thumbnail File thumbnail + * @param previewfile File preview + * @return APIResponse + */ + public APIResponse updateVideo(String videoId, VideoParams videoParams, Uri thumbnail, Uri previewfile) { + PeertubeService peertubeService = init(); + + MultipartBody.Part bodyThumbnail = null; + MultipartBody.Part bodyPreviewfile = null; + try { + if (thumbnail != null) { + DocumentFile documentFile = DocumentFile.fromSingleUri(_context, thumbnail); + String thumbnailName = null; + if (documentFile != null) { + thumbnailName = documentFile.getName(); + } + bodyThumbnail = createFile("avatarfile", thumbnail, thumbnailName); + } + if (previewfile != null && thumbnail != null) { + DocumentFile documentFile = DocumentFile.fromSingleUri(_context, thumbnail); + String previewfileName = null; + if (documentFile != null) { + previewfileName = documentFile.getName(); + } + bodyPreviewfile = createFile("image", previewfile, previewfileName); + } + } catch (IOException e) { + e.printStackTrace(); + } + RequestBody channelId = RequestBody.create(videoParams.getChannelId(), MediaType.parse("text/plain")); + RequestBody description = RequestBody.create(videoParams.getDescription(), MediaType.parse("text/plain")); + RequestBody language = RequestBody.create(videoParams.getLanguage(), MediaType.parse("text/plain")); + RequestBody license = RequestBody.create(videoParams.getLicence(), MediaType.parse("text/plain")); + RequestBody name = RequestBody.create(videoParams.getName(), MediaType.parse("text/plain")); + + List tags = null; + if (videoParams.getTags() != null && videoParams.getTags().size() > 0) { + tags = new ArrayList<>(); + for (String tag : videoParams.getTags()) { + tags.add(RequestBody.create(tag, MediaType.parse("text/plain"))); + } + } + RequestBody support = null; + if (videoParams.getSupport() != null) { + support = RequestBody.create(videoParams.getSupport(), MediaType.parse("text/plain")); + } + + + Call upload = peertubeService.updateVideo(getToken(), videoId, + channelId, name, videoParams.getCategory(), videoParams.isCommentsEnabled(), description, videoParams.isDownloadEnabled(), language, license, videoParams.isNsfw(), + videoParams.getPrivacy(), support, tags, videoParams.isWaitTranscoding() + , bodyThumbnail, bodyPreviewfile); + APIResponse apiResponse = new APIResponse(); + try { + Response response = upload.execute(); + if (response.isSuccessful()) { + apiResponse.setActionReturn("ok"); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + public APIResponse createAccount(AccountCreation accountCreation) { + PeertubeService peertubeService = init(); + Call report1 = peertubeService.register(accountCreation.getEmail(), accountCreation.getPassword(), accountCreation.getUsername(), accountCreation.getDisplayName()); + APIResponse apiResponse = new APIResponse(); + try { + Response response = report1.execute(); + if (response.isSuccessful()) { + apiResponse.setActionReturn(accountCreation.getEmail()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + public APIResponse post(ActionType actionType, String id, String element) { + PeertubeService peertubeService = init(); + Call postCall = null; + APIResponse apiResponse = new APIResponse(); + switch (actionType) { + case FOLLOW: + postCall = peertubeService.follow(getToken(), id); + break; + case UNFOLLOW: + postCall = peertubeService.unfollow(getToken(), id); + break; + case MUTE: + postCall = peertubeService.mute(getToken(), id); + break; + case UNMUTE: + postCall = peertubeService.unmute(getToken(), id); + break; + case RATEVIDEO: + postCall = peertubeService.rate(getToken(), id, element); + break; + case PEERTUBEDELETEVIDEO: + postCall = peertubeService.deleteVideo(getToken(), id); + break; + case PEERTUBEDELETECOMMENT: + postCall = peertubeService.deleteComment(getToken(), id, element); + break; + case PEERTUBE_DELETE_ALL_COMMENT_FOR_ACCOUNT: + postCall = peertubeService.deleteAllCommentForAccount(getToken(), id, element); + break; + case DELETE_CHANNEL: + postCall = peertubeService.deleteChannel(getToken(), id); + break; + } + if (postCall != null) { + try { + Response response = postCall.execute(); + if (response.isSuccessful()) { + apiResponse.setActionReturn(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + } + return apiResponse; + } + + + /** + * Get single account by its handle + * + * @param accountHandle String + * @return APIResponse + */ + public APIResponse getAccount(String accountHandle) { + PeertubeService peertubeService = init(); + Call accountDataCall = peertubeService.getAccount(accountHandle); + APIResponse apiResponse = new APIResponse(); + if (accountDataCall != null) { + try { + Response response = accountDataCall.execute(); + if (response.isSuccessful() && response.body() != null) { + List accountList = new ArrayList<>(); + accountList.add(response.body()); + apiResponse.setAccounts(accountList); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + } + return apiResponse; + } + + + /** + * Get video description + * + * @param uuid String (pagination) + * @return APIResponse + */ + public VideoData.Description getVideoDescription(String uuid) { + PeertubeService peertubeService = init(); + Call videoDescription = peertubeService.getVideoDescription(uuid); + try { + Response response = videoDescription.execute(); + if (response.isSuccessful() && response.body() != null) { + return response.body(); + } + } catch (IOException ignored) { + } + return null; + } + + /** + * Get muted accounts + * + * @param maxId String (pagination) + * @return APIResponse + */ + public APIResponse getMuted(String maxId) { + PeertubeService peertubeService = init(); + Call accountDataCall = peertubeService.getMuted("Bearer " + token, maxId, count); + APIResponse apiResponse = new APIResponse(); + if (accountDataCall != null) { + try { + Response response = accountDataCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setMuted(response.body().getData()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + } + return apiResponse; + } + + /** + * Get subscriptions data + * + * @param maxId String (pagination) + * @return APIResponse + */ + public APIResponse getSubscribtions(String maxId) { + PeertubeService peertubeService = init(); + Call channelDataCall = peertubeService.getSubscription("Bearer " + token, maxId, count); + APIResponse apiResponse = new APIResponse(); + if (channelDataCall != null) { + try { + Response response = channelDataCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setChannels(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + } + return apiResponse; + } + + /** + * Create or update a channel + * + * @param apiAction ChannelsVM.action + * @param channelId String + * @param channelParams PlaylistParams + * @return APIResponse + */ + public APIResponse createOrUpdateChannel(ChannelsVM.action apiAction, String channelId, ChannelParams channelParams, Uri avatar) { + PeertubeService peertubeService = init(); + + APIResponse apiResponse = new APIResponse(); + try { + if (apiAction == ChannelsVM.action.CREATE_CHANNEL) { + Call stringCall = peertubeService.addChannel(getToken(), channelParams); + Response response = stringCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setActionReturn(response.body().getVideoChannel().getId()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + + } else if (apiAction == ChannelsVM.action.UPDATE_CHANNEL) { + Call stringCall = peertubeService.updateChannel(getToken(), channelId, channelParams); + Response response = stringCall.execute(); + if (response.isSuccessful()) { + apiResponse.setActionReturn(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } + if (avatar != null) { + DocumentFile documentFile = DocumentFile.fromSingleUri(_context, avatar); + String avatarfileName = null; + if (documentFile != null) { + avatarfileName = documentFile.getName(); + } + MultipartBody.Part bodyThumbnail = createFile("avatarfile", avatar, avatarfileName); + Call updateProfilePicture = peertubeService.updateChannelProfilePicture(getToken(), channelId, bodyThumbnail); + Response responseAvatar = updateProfilePicture.execute(); + if (responseAvatar.isSuccessful()) { + UserMe.AvatarResponse avatarResponse = responseAvatar.body(); + } else { + setError(apiResponse, responseAvatar.code(), responseAvatar.errorBody()); + } + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + /** + * Get Oauth + * + * @return APIResponse + */ + public Oauth oauthClient(String client_name, String redirect_uris, String scopes, String website) { + PeertubeService peertubeService = init(); + try { + Call oauth; + if (BuildConfig.full_instances) { + oauth = peertubeService.getOauth(client_name, redirect_uris, scopes, website); + } else { + oauth = peertubeService.getOauthAcad(); + } + Response response = oauth.execute(); + if (response.isSuccessful() && response.body() != null) { + return response.body(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Get NodeInfo + * + * @return APIResponse + */ + public WellKnownNodeinfo.NodeInfo getNodeInfo() { + PeertubeService peertubeService = initTranslation(); + try { + Call wellKnownNodeinfoCall = peertubeService.getWellKnownNodeinfo(); + Response response = wellKnownNodeinfoCall.execute(); + if (response.isSuccessful() && response.body() != null) { + int size = response.body().getLinks().size(); + String url = response.body().getLinks().get(size - 1).getHref(); + if (size > 0 && url != null) { + peertubeService = initTranslation(); + String path = new URL(url).getPath(); + path = path.replaceFirst("/", "").trim(); + Call nodeinfo = peertubeService.getNodeinfo(path); + Response responseNodeInfo = nodeinfo.execute(); + return responseNodeInfo.body(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Get channel data + * + * @param accountDataType AccountDataType (type of requested data) + * @param element String (pagination or name for the channel) + * @return APIResponse + */ + public APIResponse getChannelData(DataType accountDataType, String element) { + PeertubeService peertubeService = init(); + + APIResponse apiResponse = new APIResponse(); + switch (accountDataType) { + case MY_CHANNELS: + case CHANNELS_FOR_ACCOUNT: + Call channelDataCall = peertubeService.getChannelsForAccount(element); + try { + Response response = channelDataCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setChannels(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + break; + case CHANNEL: + Call channelCall = peertubeService.getChannel(element); + try { + Response response = channelCall.execute(); + if (!response.isSuccessful()) { + setError(apiResponse, response.code(), response.errorBody()); + } else { + ChannelData.Channel channelData = response.body(); + if (channelData != null) { + List channelList = new ArrayList<>(); + channelList.add(channelData); + apiResponse.setChannels(channelList); + } + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + break; + } + return apiResponse; + } + + /** + * Create or update a playlist + * + * @param apiAction PlaylistsVM.action + * @param playlistId String + * @param playlistParams PlaylistParams + * @return APIResponse + */ + public APIResponse createOrUpdatePlaylist(PlaylistsVM.action apiAction, String playlistId, PlaylistParams playlistParams, Uri thumbnail) { + PeertubeService peertubeService = init(); + + APIResponse apiResponse = new APIResponse(); + MultipartBody.Part body = null; + + MultipartBody.Part bodyThumbnail = null; + if (thumbnail != null) { + DocumentFile documentFile = DocumentFile.fromSingleUri(_context, thumbnail); + String avatarfileName = null; + if (documentFile != null) { + avatarfileName = documentFile.getName(); + } + try { + bodyThumbnail = createFile("thumbnailfile", thumbnail, avatarfileName); + } catch (IOException e) { + e.printStackTrace(); + } + } + try { + RequestBody displayName = RequestBody.create(playlistParams.getDisplayName(), MediaType.parse("text/plain")); + RequestBody description = null; + if (playlistParams.getDescription() != null) { + description = RequestBody.create(playlistParams.getDescription(), MediaType.parse("text/plain")); + } + RequestBody channelId = RequestBody.create(playlistParams.getVideoChannelId(), MediaType.parse("text/plain")); + if (apiAction == PlaylistsVM.action.CREATE_PLAYLIST) { + Call stringCall = peertubeService.addPlaylist(getToken(), displayName, description, playlistParams.getPrivacy(), channelId, bodyThumbnail); + Response response = stringCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setActionReturn(response.body().getVideoPlaylist().getId()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + + } else if (apiAction == PlaylistsVM.action.UPDATE_PLAYLIST) { + Call stringCall = peertubeService.updatePlaylist(getToken(), playlistId, displayName, description, playlistParams.getPrivacy(), channelId, bodyThumbnail); + Response response = stringCall.execute(); + if (response.isSuccessful()) { + apiResponse.setActionReturn(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + + /** + * Retrieves playlist *synchronously* + * + * @param type PlaylistsVM.action + * @param playlistId String id of the playlist + * @param videoId String id of the video + * @return APIResponse + */ + public APIResponse playlistAction(PlaylistsVM.action type, String playlistId, String videoId, String acct, String max_id) { + PeertubeService peertubeService = init(); + + APIResponse apiResponse = new APIResponse(); + try { + if (type == PlaylistsVM.action.GET_PLAYLIST_INFO) { + Call playlistCall = peertubeService.getPlaylist(playlistId); + Response response = playlistCall.execute(); + if (response.isSuccessful()) { + List playlists = new ArrayList<>(); + playlists.add(response.body()); + apiResponse.setPlaylists(playlists); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + + } else if (type == PlaylistsVM.action.GET_PLAYLISTS) { + Call playlistsCall = peertubeService.getPlaylistsForAccount(getToken(), acct); + Response response = playlistsCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setPlaylists(response.body().data); + } else { + + setError(apiResponse, response.code(), response.errorBody()); + } + } else if (type == PlaylistsVM.action.GET_LIST_VIDEOS) { + Call videosPlayList = peertubeService.getVideosPlayList(getToken(), playlistId, max_id, count); + Response response = videosPlayList.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setVideoPlaylist(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } else if (type == PlaylistsVM.action.DELETE_PLAYLIST) { + Call stringCall = peertubeService.deletePlaylist(getToken(), playlistId); + Response response = stringCall.execute(); + if (response.isSuccessful()) { + apiResponse.setActionReturn(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } else if (type == PlaylistsVM.action.ADD_VIDEOS) { + Call stringCall = peertubeService.addVideoInPlaylist(getToken(), playlistId, videoId); + Response response = stringCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setActionReturn(response.body().getVideoPlaylistElement().getId()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } else if (type == PlaylistsVM.action.DELETE_VIDEOS) { + Call stringCall = peertubeService.deleteVideoInPlaylist(getToken(), playlistId, videoId); + Response response = stringCall.execute(); + if (response.isSuccessful()) { + apiResponse.setActionReturn(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } + + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + private void setError(APIResponse apiResponse, int responseCode, ResponseBody errorBody) { + Error error; + if (errorBody != null) { + try { + error = generateError(responseCode, errorBody.string()); + } catch (IOException e) { + error = new Error(); + error.setStatusCode(responseCode); + error.setError(_context.getString(R.string.toast_error)); + } + } else { + error = new Error(); + error.setStatusCode(responseCode); + error.setError(_context.getString(R.string.toast_error)); + } + if (responseCode == 404 || responseCode == 502) { + error.setError(_context.getString(R.string.instance_not_availabe)); + } + apiResponse.setError(error); + } + + public APIResponse getComments(CommentVM.action type, String videoId, String forCommentId, String max_id) { + PeertubeService peertubeService = init(); + + APIResponse apiResponse = new APIResponse(); + try { + if (type == CommentVM.action.GET_THREAD) { + Call commentsCall = peertubeService.getComments(videoId, max_id, count); + Response response = commentsCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setComments(response.body().data); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } else if (type == CommentVM.action.GET_REPLIES) { + Call commentsCall = peertubeService.getReplies(videoId, forCommentId); + Response response = commentsCall.execute(); + if (response.isSuccessful() && response.body() != null) { + apiResponse.setCommentThreadData(response.body()); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + /** + * Manage comments *synchronously* + * + * @param type (CommentVM.action + * @param videoId String id of the video + * @param toCommentId String id of the comment for replies + * @param text String text + * @return APIResponse + */ + public APIResponse commentAction(ActionType type, String videoId, String toCommentId, String text) { + PeertubeService peertubeService = init(); + + APIResponse apiResponse = new APIResponse(); + try { + if (type == ActionType.ADD_COMMENT) { + Call commentPostedCall = peertubeService.postComment(getToken(), videoId, text); + Response response = commentPostedCall.execute(); + if (response.isSuccessful() && response.body() != null) { + List comments = new ArrayList<>(); + comments.add(response.body().getComment()); + apiResponse.setComments(comments); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } else if (type == ActionType.REPLY) { + Call commentPostedCall = peertubeService.postReply(getToken(), videoId, toCommentId, text); + Response response = commentPostedCall.execute(); + if (response.isSuccessful() && response.body() != null) { + List comments = new ArrayList<>(); + comments.add(response.body().getComment()); + apiResponse.setComments(comments); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + /** + * Retrieves playlist *synchronously* + * + * @param id String id + * @return APIResponse + */ + public APIResponse getPlayist(String id) { + PeertubeService peertubeService = init(); + Call playlistCall; + playlistCall = peertubeService.getPlaylist(id); + APIResponse apiResponse = new APIResponse(); + try { + Response response = playlistCall.execute(); + if (response.isSuccessful()) { + List playlists = new ArrayList<>(); + playlists.add(response.body()); + apiResponse.setPlaylists(playlists); + } else { + setError(apiResponse, response.code(), response.errorBody()); + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + private Error generateError(int responseCode, String message) { + Error error = new Error(); + error.setStatusCode(responseCode); + if (message != null) { + error.setError(message); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + return error; + } + + /** + * Retrieves rating of user on a video *synchronously* + * + * @param id String id + * @return APIResponse + */ + public APIResponse getRating(String id) { + PeertubeService peertubeService = init(); + Call rating = peertubeService.getRating(getToken(), id); + APIResponse apiResponse = new APIResponse(); + try { + Response response = rating.execute(); + if (response.isSuccessful()) { + apiResponse.setRating(response.body()); + } else { + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + } + + /** + * Retrieves videos *synchronously* + * + * @param id String id + * @return APIResponse + */ + public APIResponse getVideos(String id, boolean myVideo, boolean canUseToken) { + PeertubeService peertubeService = init(); + Call video; + if (myVideo || canUseToken) { + video = peertubeService.getMyVideo(getToken(), id); + } else { + video = peertubeService.getVideo(id); + } + APIResponse apiResponse = new APIResponse(); + try { + Response response = video.execute(); + if (response.isSuccessful()) { + List videos = new ArrayList<>(); + videos.add(response.body()); + apiResponse.setPeertubes(videos); + } else { + + if (response.errorBody() != null) { + + String error = response.errorBody().string(); + if (error.contains("originUrl")) { + try { + JSONObject jsonObject = new JSONObject(error); + List videos = new ArrayList<>(); + VideoData.Video videoRedirect = new VideoData.Video(); + videoRedirect.setErrorCode(jsonObject.getInt("errorCode")); + videoRedirect.setOriginUrl(jsonObject.getString("originUrl")); + videos.add(videoRedirect); + apiResponse.setPeertubes(videos); + } catch (JSONException e) { + e.printStackTrace(); + } + } else if (error.contains("error")) { + try { + JSONObject jsonObject = new JSONObject(error); + List videos = new ArrayList<>(); + VideoData.Video videoErrorMessage = new VideoData.Video(); + videoErrorMessage.setErrorMessage(jsonObject.getString("error")); + videos.add(videoErrorMessage); + apiResponse.setPeertubes(videos); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } else { + Error error = new Error(); + error.setStatusCode(response.code()); + if (response.errorBody() != null) { + error.setError(response.errorBody().string()); + } else { + error.setError(_context.getString(R.string.toast_error)); + } + } + + } + } catch (IOException e) { + Error error = new Error(); + error.setError(_context.getString(R.string.toast_error)); + apiResponse.setError(error); + e.printStackTrace(); + } + return apiResponse; + + } + + + public enum DataType { + SUBSCRIBER, + MUTED, + CHANNELS_FOR_ACCOUNT, + CHANNEL, + MY_CHANNELS + } + + + public enum ActionType { + FOLLOW, + UNFOLLOW, + MUTE, + UNMUTE, + RATEVIDEO, + PEERTUBEDELETECOMMENT, + PEERTUBE_DELETE_ALL_COMMENT_FOR_ACCOUNT, + PEERTUBEDELETEVIDEO, + REPORT_VIDEO, + REPORT_ACCOUNT, + REPORT_COMMENT, + DELETE_CHANNEL, + ADD_COMMENT, + REPLY, + } + + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/RetrofitSepiaSearchAPI.java b/app/src/main/java/app/fedilab/android/peertube/client/RetrofitSepiaSearchAPI.java new file mode 100644 index 000000000..483db3521 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/RetrofitSepiaSearchAPI.java @@ -0,0 +1,86 @@ +package app.fedilab.android.peertube.client; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Locale; + +import app.fedilab.android.peertube.client.data.VideoData; +import app.fedilab.android.peertube.client.entities.SepiaSearch; +import retrofit2.Call; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class RetrofitSepiaSearchAPI { + + + private final String finalUrl; + + + public RetrofitSepiaSearchAPI() { + finalUrl = "https://search.joinpeertube.org/api/v1/"; + } + + private SepiaSearchService init() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(finalUrl) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + return retrofit.create(SepiaSearchService.class); + } + + /** + * Return videos for a sepia search + * + * @param sepiaSearch SepiaSearch + * @return VideoData + */ + public VideoData getVideos(SepiaSearch sepiaSearch) { + SepiaSearchService sepiaSearchService = init(); + SimpleDateFormat fmtOut = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH); + String startDate = null; + if (sepiaSearch.getStartDate() != null) { + startDate = fmtOut.format(sepiaSearch.getStartDate()); + } + Call videoDataCall = sepiaSearchService.getVideos( + sepiaSearch.getStart(), + sepiaSearch.getCount(), + sepiaSearch.getSearch(), + sepiaSearch.getDurationMin(), + sepiaSearch.getDurationMax(), + startDate, + sepiaSearch.getBoostLanguages(), + sepiaSearch.getCategoryOneOf(), + sepiaSearch.getLicenceOneOf(), + sepiaSearch.getTagsOneOf(), + sepiaSearch.getTagsAllOf(), + sepiaSearch.isNsfw(), + sepiaSearch.getSort()); + + try { + Response response = videoDataCall.execute(); + if (response.isSuccessful() && response.body() != null) { + return response.body(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/SepiaSearchService.java b/app/src/main/java/app/fedilab/android/peertube/client/SepiaSearchService.java new file mode 100644 index 000000000..460241f11 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/SepiaSearchService.java @@ -0,0 +1,53 @@ +package app.fedilab.android.peertube.client; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import java.util.List; + +import app.fedilab.android.peertube.client.data.ChannelData; +import app.fedilab.android.peertube.client.data.VideoData; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; + +interface SepiaSearchService { + + @GET("search/videos") + Call getVideos( + @Query("start") String maxId, + @Query("count") String count, + @Query("search") String search, + @Query("durationMin") int durationMin, + @Query("durationMax") int durationMax, + @Query("startDate") String startDate, + @Query("boostLanguages") List languageOneOf, + @Query("categoryOneOf") List categoryOneOf, + @Query("licenceOneOf") List licenceOneOf, + @Query("tagsOneOf") List tagsOneOf, + @Query("tagsAllOf") List tagsAllOf, + @Query("nsfw") boolean nsfw, + @Query("sort") String sort + + + ); + + @GET("search/channels") + Call getChannels( + @Query("search") String search, + @Query("start") String maxId, + @Query("count") String count + ); + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/data/AccountData.java b/app/src/main/java/app/fedilab/android/peertube/client/data/AccountData.java new file mode 100644 index 000000000..914ad9675 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/data/AccountData.java @@ -0,0 +1,275 @@ +package app.fedilab.android.peertube.client.data; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.SerializedName; + +import java.util.Date; +import java.util.List; + +import app.fedilab.android.peertube.client.entities.Avatar; + + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class AccountData { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List data; + + public static class Account implements Parcelable { + public static final Creator CREATOR = new Creator() { + @Override + public Account createFromParcel(Parcel source) { + return new Account(source); + } + + @Override + public Account[] newArray(int size) { + return new Account[size]; + } + }; + @SerializedName("avatar") + private Avatar avatar; + @SerializedName("createdAt") + private Date createdAt; + @SerializedName("description") + private String description; + @SerializedName("displayName") + private String displayName; + @SerializedName("followersCount") + private int followersCount; + @SerializedName("followingCount") + private int followingCount; + @SerializedName("host") + private String host; + @SerializedName("hostRedundancyAllowed") + private boolean hostRedundancyAllowed; + @SerializedName("id") + private String id; + @SerializedName("name") + private String name; + @SerializedName("username") + private String username; + @SerializedName("updatedAt") + private Date updatedAt; + @SerializedName("url") + private String url; + @SerializedName("userId") + private String userId; + private String token; + private String client_id; + private String client_secret; + private String refresh_token; + private String software; + + public Account() { + } + + protected Account(Parcel in) { + this.avatar = in.readParcelable(Avatar.class.getClassLoader()); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + this.description = in.readString(); + this.displayName = in.readString(); + this.followersCount = in.readInt(); + this.followingCount = in.readInt(); + this.host = in.readString(); + this.hostRedundancyAllowed = in.readByte() != 0; + this.id = in.readString(); + this.name = in.readString(); + this.username = in.readString(); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.url = in.readString(); + this.userId = in.readString(); + } + + public Avatar getAvatar() { + return avatar; + } + + public void setAvatar(Avatar avatar) { + this.avatar = avatar; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public int getFollowersCount() { + return followersCount; + } + + public void setFollowersCount(int followersCount) { + this.followersCount = followersCount; + } + + public int getFollowingCount() { + return followingCount; + } + + public void setFollowingCount(int followingCount) { + this.followingCount = followingCount; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public boolean isHostRedundancyAllowed() { + return hostRedundancyAllowed; + } + + public void setHostRedundancyAllowed(boolean hostRedundancyAllowed) { + this.hostRedundancyAllowed = hostRedundancyAllowed; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUsername() { + return name != null ? name : username; + } + + public void setUsername(String name) { + this.name = name; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getAcct() { + return name + "@" + host; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getClient_id() { + return client_id; + } + + public void setClient_id(String client_id) { + this.client_id = client_id; + } + + public String getClient_secret() { + return client_secret; + } + + public void setClient_secret(String client_secret) { + this.client_secret = client_secret; + } + + public String getRefresh_token() { + return refresh_token; + } + + public void setRefresh_token(String refresh_token) { + this.refresh_token = refresh_token; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getSoftware() { + return software; + } + + public void setSoftware(String software) { + this.software = software; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(this.avatar, flags); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeString(this.description); + dest.writeString(this.displayName); + dest.writeInt(this.followersCount); + dest.writeInt(this.followingCount); + dest.writeString(this.host); + dest.writeByte(this.hostRedundancyAllowed ? (byte) 1 : (byte) 0); + dest.writeString(this.id); + dest.writeString(this.name); + dest.writeString(this.username); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeString(this.url); + dest.writeString(this.userId); + } + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/data/BlockData.java b/app/src/main/java/app/fedilab/android/peertube/client/data/BlockData.java new file mode 100644 index 000000000..44ce3826e --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/data/BlockData.java @@ -0,0 +1,81 @@ +package app.fedilab.android.peertube.client.data; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import com.google.gson.annotations.SerializedName; + +import java.util.Date; +import java.util.List; + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class BlockData { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List data; + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + @SuppressWarnings("unused") + public static class Block { + @SerializedName("blockedAccount") + private AccountData.Account blockedAccount; + @SerializedName("byAccount") + private AccountData.Account byAccount; + @SerializedName("createdAt") + private Date createdAt; + + public AccountData.Account getBlockedAccount() { + return blockedAccount; + } + + public void setBlockedAccount(AccountData.Account blockedAccount) { + this.blockedAccount = blockedAccount; + } + + public AccountData.Account getByAccount() { + return byAccount; + } + + public void setByAccount(AccountData.Account byAccount) { + this.byAccount = byAccount; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + } + + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/data/CaptionData.java b/app/src/main/java/app/fedilab/android/peertube/client/data/CaptionData.java new file mode 100644 index 000000000..8786c88f6 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/data/CaptionData.java @@ -0,0 +1,53 @@ +package app.fedilab.android.peertube.client.data; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +import app.fedilab.android.peertube.client.entities.ItemStr; + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class CaptionData { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List data; + + public static class Caption { + @SerializedName("captionPath") + private String captionPath; + @SerializedName("language") + private ItemStr language; + + public String getCaptionPath() { + return captionPath; + } + + public void setCaptionPath(String captionPath) { + this.captionPath = captionPath; + } + + public ItemStr getLanguage() { + return language; + } + + public void setLanguage(ItemStr language) { + this.language = language; + } + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/data/ChannelData.java b/app/src/main/java/app/fedilab/android/peertube/client/data/ChannelData.java new file mode 100644 index 000000000..fa39829d3 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/data/ChannelData.java @@ -0,0 +1,294 @@ +package app.fedilab.android.peertube.client.data; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import app.fedilab.android.peertube.client.entities.Avatar; +import app.fedilab.android.peertube.client.entities.ItemStr; +import app.fedilab.android.peertube.client.entities.ViewsPerDay; + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class ChannelData { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List data; + + public static class Channel implements Parcelable { + public static final Creator CREATOR = new Creator() { + @Override + public Channel createFromParcel(Parcel source) { + return new Channel(source); + } + + @Override + public Channel[] newArray(int size) { + return new Channel[size]; + } + }; + @SerializedName("avatar") + private Avatar avatar; + @SerializedName("createdAt") + private Date createdAt; + @SerializedName("description") + private String description; + @SerializedName("displayName") + private String displayName; + @SerializedName("followersCount") + private int followersCount; + @SerializedName("followingCount") + private int followingCount; + @SerializedName("host") + private String host; + @SerializedName("hostRedundancyAllowed") + private boolean hostRedundancyAllowed; + @SerializedName("id") + private String id; + @SerializedName("isLocal") + private boolean isLocal; + @SerializedName("name") + private String name; + @SerializedName("ownerAccount") + private AccountData.Account ownerAccount; + @SerializedName("support") + private String support; + @SerializedName("updatedAt") + private Date updatedAt; + @SerializedName("url") + private String url; + @SerializedName("viewsPerDay") + private List viewsPerDays; + private String acct; + private boolean selected; + + public Channel() { + } + + protected Channel(Parcel in) { + this.avatar = in.readParcelable(Avatar.class.getClassLoader()); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + this.description = in.readString(); + this.displayName = in.readString(); + this.followersCount = in.readInt(); + this.followingCount = in.readInt(); + this.host = in.readString(); + this.hostRedundancyAllowed = in.readByte() != 0; + this.id = in.readString(); + this.isLocal = in.readByte() != 0; + this.name = in.readString(); + this.ownerAccount = in.readParcelable(AccountData.Account.class.getClassLoader()); + this.support = in.readString(); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.url = in.readString(); + this.viewsPerDays = new ArrayList<>(); + in.readList(this.viewsPerDays, ViewsPerDay.class.getClassLoader()); + this.acct = in.readString(); + } + + public Avatar getAvatar() { + return avatar; + } + + public void setAvatar(Avatar avatar) { + this.avatar = avatar; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public int getFollowersCount() { + return followersCount; + } + + public void setFollowersCount(int followersCount) { + this.followersCount = followersCount; + } + + public int getFollowingCount() { + return followingCount; + } + + public void setFollowingCount(int followingCount) { + this.followingCount = followingCount; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public boolean isHostRedundancyAllowed() { + return hostRedundancyAllowed; + } + + public void setHostRedundancyAllowed(boolean hostRedundancyAllowed) { + this.hostRedundancyAllowed = hostRedundancyAllowed; + } + + public String getAcct() { + return name + "@" + host; + } + + public void setAcct(String acct) { + this.acct = acct; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public boolean isLocal() { + return isLocal; + } + + public void setLocal(boolean local) { + isLocal = local; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public AccountData.Account getOwnerAccount() { + return ownerAccount; + } + + public void setOwnerAccount(AccountData.Account ownerAccount) { + this.ownerAccount = ownerAccount; + } + + public String getSupport() { + return support; + } + + public void setSupport(String support) { + this.support = support; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public List getViewsPerDays() { + return viewsPerDays; + } + + public void setViewsPerDays(List viewsPerDays) { + this.viewsPerDays = viewsPerDays; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(this.avatar, flags); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeString(this.description); + dest.writeString(this.displayName); + dest.writeInt(this.followersCount); + dest.writeInt(this.followingCount); + dest.writeString(this.host); + dest.writeByte(this.hostRedundancyAllowed ? (byte) 1 : (byte) 0); + dest.writeString(this.id); + dest.writeByte(this.isLocal ? (byte) 1 : (byte) 0); + dest.writeString(this.name); + dest.writeParcelable(this.ownerAccount, flags); + dest.writeString(this.support); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeString(this.url); + dest.writeList(this.viewsPerDays); + dest.writeString(this.acct); + } + } + + public static class ChannelCreation { + @SerializedName("videoChannel") + private ItemStr videoChannel; + + public ItemStr getVideoChannel() { + return videoChannel; + } + + public void setVideoChannel(ItemStr videoChannel) { + this.videoChannel = videoChannel; + } + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/data/CommentData.java b/app/src/main/java/app/fedilab/android/peertube/client/data/CommentData.java new file mode 100644 index 000000000..3aabf3caa --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/data/CommentData.java @@ -0,0 +1,265 @@ +package app.fedilab.android.peertube.client.data; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import com.google.gson.annotations.SerializedName; + +import java.util.Date; +import java.util.List; + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class CommentData { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List data; + + + public static class Comment { + + @SerializedName("account") + private AccountData.Account account; + @SerializedName("createdAt") + private Date createdAt; + @SerializedName("deletedAt") + private Date deletedAt; + @SerializedName("id") + private String id; + @SerializedName("inReplyToCommentId") + private String inReplyToCommentId; + @SerializedName("isDeleted") + private boolean isDeleted; + @SerializedName("text") + private String text; + @SerializedName("threadId") + private String threadId; + @SerializedName("totalReplies") + private int totalReplies; + @SerializedName("totalRepliesFromVideoAuthor") + private int totalRepliesFromVideoAuthor; + @SerializedName("updatedAt") + private String updatedAt; + @SerializedName("url") + private String url; + @SerializedName("videoId") + private String videoId; + private boolean isReply = false; + private boolean isReplyViewOpen = false; + + + public AccountData.Account getAccount() { + return account; + } + + public void setAccount(AccountData.Account account) { + this.account = account; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getDeletedAt() { + return deletedAt; + } + + public void setDeletedAt(Date deletedAt) { + this.deletedAt = deletedAt; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getInReplyToCommentId() { + return inReplyToCommentId; + } + + public void setInReplyToCommentId(String inReplyToCommentId) { + this.inReplyToCommentId = inReplyToCommentId; + } + + public boolean isDeleted() { + return isDeleted; + } + + public void setDeleted(boolean deleted) { + isDeleted = deleted; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getThreadId() { + return threadId; + } + + public void setThreadId(String threadId) { + this.threadId = threadId; + } + + public int getTotalReplies() { + return totalReplies; + } + + public void setTotalReplies(int totalReplies) { + this.totalReplies = totalReplies; + } + + public int getTotalRepliesFromVideoAuthor() { + return totalRepliesFromVideoAuthor; + } + + public void setTotalRepliesFromVideoAuthor(int totalRepliesFromVideoAuthor) { + this.totalRepliesFromVideoAuthor = totalRepliesFromVideoAuthor; + } + + public String getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(String updatedAt) { + this.updatedAt = updatedAt; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getVideoId() { + return videoId; + } + + public void setVideoId(String videoId) { + this.videoId = videoId; + } + + public boolean isReply() { + return isReply; + } + + public void setReply(boolean reply) { + isReply = reply; + } + + public boolean isReplyViewOpen() { + return isReplyViewOpen; + } + + public void setReplyViewOpen(boolean replyViewOpen) { + isReplyViewOpen = replyViewOpen; + } + } + + + public static class CommentThreadData { + + @SerializedName("comment") + public Comment comment; + @SerializedName("children") + public List children; + + public Comment getComment() { + return comment; + } + + public void setComment(Comment comment) { + this.comment = comment; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + } + + public static class CommentPosted { + @SerializedName("comment") + private Comment comment; + + public Comment getComment() { + return comment; + } + + public void setComment(Comment comment) { + this.comment = comment; + } + } + + + public static class NotificationComment { + @SerializedName("id") + private String id; + @SerializedName("threadId") + private String threadId; + @SerializedName("video") + private VideoData.Video video; + @SerializedName("account") + private AccountData.Account account; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getThreadId() { + return threadId; + } + + public void setThreadId(String threadId) { + this.threadId = threadId; + } + + public VideoData.Video getVideo() { + return video; + } + + public void setVideo(VideoData.Video video) { + this.video = video; + } + + public AccountData.Account getAccount() { + return account; + } + + public void setAccount(AccountData.Account account) { + this.account = account; + } + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/data/InstanceData.java b/app/src/main/java/app/fedilab/android/peertube/client/data/InstanceData.java new file mode 100644 index 000000000..98e10a9a9 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/data/InstanceData.java @@ -0,0 +1,427 @@ +package app.fedilab.android.peertube.client.data; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.SpannableStringBuilder; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class InstanceData { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List data; + + + public static class Instance { + + @SerializedName("autoBlacklistUserVideosEnabled") + private boolean autoBlacklistUserVideosEnabled; + @SerializedName("categories") + private List categories; + @SerializedName("country") + private String country; + @SerializedName("createdAt") + private Date createdAt; + @SerializedName("defaultNSFWPolicy") + private String defaultNSFWPolicy; + @SerializedName("health") + private int health; + @SerializedName("host") + private String host; + @SerializedName("id") + private String id; + @SerializedName("languages") + private List languages; + @SerializedName("name") + private String name; + @SerializedName("shortDescription") + private String shortDescription; + @SerializedName("signupAllowed") + private boolean signupAllowed; + @SerializedName("supportsIPv6") + private boolean supportsIPv6; + @SerializedName("totalInstanceFollowers") + private int totalInstanceFollowers; + @SerializedName("totalInstanceFollowing") + private int totalInstanceFollowing; + @SerializedName("totalLocalVideos") + private int totalLocalVideos; + @SerializedName("totalUsers") + private int totalUsers; + @SerializedName("totalVideos") + private int totalVideos; + @SerializedName("userVideoQuota") + private String userVideoQuota; + @SerializedName("version") + private String version; + @SerializedName("isNSFW") + private boolean isNSFW; + private SpannableStringBuilder spannableStringBuilder; + private boolean truncatedDescription = true; + + public boolean isAutoBlacklistUserVideosEnabled() { + return autoBlacklistUserVideosEnabled; + } + + public void setAutoBlacklistUserVideosEnabled(boolean autoBlacklistUserVideosEnabled) { + this.autoBlacklistUserVideosEnabled = autoBlacklistUserVideosEnabled; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public String getDefaultNSFWPolicy() { + return defaultNSFWPolicy; + } + + public void setDefaultNSFWPolicy(String defaultNSFWPolicy) { + this.defaultNSFWPolicy = defaultNSFWPolicy; + } + + public int getHealth() { + return health; + } + + public void setHealth(int health) { + this.health = health; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getShortDescription() { + return shortDescription; + } + + public void setShortDescription(String shortDescription) { + this.shortDescription = shortDescription; + } + + public boolean isSignupAllowed() { + return signupAllowed; + } + + public void setSignupAllowed(boolean signupAllowed) { + this.signupAllowed = signupAllowed; + } + + public boolean isSupportsIPv6() { + return supportsIPv6; + } + + public void setSupportsIPv6(boolean supportsIPv6) { + this.supportsIPv6 = supportsIPv6; + } + + public int getTotalInstanceFollowers() { + return totalInstanceFollowers; + } + + public void setTotalInstanceFollowers(int totalInstanceFollowers) { + this.totalInstanceFollowers = totalInstanceFollowers; + } + + public int getTotalInstanceFollowing() { + return totalInstanceFollowing; + } + + public void setTotalInstanceFollowing(int totalInstanceFollowing) { + this.totalInstanceFollowing = totalInstanceFollowing; + } + + public int getTotalLocalVideos() { + return totalLocalVideos; + } + + public void setTotalLocalVideos(int totalLocalVideos) { + this.totalLocalVideos = totalLocalVideos; + } + + public int getTotalUsers() { + return totalUsers; + } + + public void setTotalUsers(int totalUsers) { + this.totalUsers = totalUsers; + } + + public int getTotalVideos() { + return totalVideos; + } + + public void setTotalVideos(int totalVideos) { + this.totalVideos = totalVideos; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public boolean isNSFW() { + return isNSFW; + } + + public void setNSFW(boolean NSFW) { + isNSFW = NSFW; + } + + public String getUserVideoQuota() { + return userVideoQuota; + } + + public void setUserVideoQuota(String userVideoQuota) { + this.userVideoQuota = userVideoQuota; + } + + public SpannableStringBuilder getSpannableStringBuilder() { + return spannableStringBuilder; + } + + public void setSpannableStringBuilder(SpannableStringBuilder spannableStringBuilder) { + this.spannableStringBuilder = spannableStringBuilder; + } + + public List getLanguages() { + return languages; + } + + public void setLanguages(List languages) { + this.languages = languages; + } + + public List getCategories() { + return categories; + } + + public void setCategories(List categories) { + this.categories = categories; + } + + public boolean isTruncatedDescription() { + return truncatedDescription; + } + + public void setTruncatedDescription(boolean truncatedDescription) { + this.truncatedDescription = truncatedDescription; + } + } + + public static class InstanceInfo { + @SerializedName("instance") + private AboutInstance instance; + + public AboutInstance getInstance() { + return instance; + } + + public void setInstance(AboutInstance instance) { + this.instance = instance; + } + } + + public static class AboutInstance implements Parcelable, Serializable { + + public static final Creator CREATOR = new Creator() { + @Override + public AboutInstance createFromParcel(Parcel in) { + return new AboutInstance(in); + } + + @Override + public AboutInstance[] newArray(int size) { + return new AboutInstance[size]; + } + }; + @SerializedName("name") + private String name; + @SerializedName("shortDescription") + private String shortDescription; + @SerializedName("description") + private String description; + @SerializedName("terms") + private String terms; + private String host; + private boolean truncatedDescription = true; + + public AboutInstance() { + } + + protected AboutInstance(Parcel in) { + name = in.readString(); + shortDescription = in.readString(); + description = in.readString(); + terms = in.readString(); + host = in.readString(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getShortDescription() { + return shortDescription; + } + + public void setShortDescription(String shortDescription) { + this.shortDescription = shortDescription; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getTerms() { + return terms; + } + + public void setTerms(String terms) { + this.terms = terms; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public boolean isTruncatedDescription() { + return truncatedDescription; + } + + public void setTruncatedDescription(boolean truncatedDescription) { + this.truncatedDescription = truncatedDescription; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(name); + parcel.writeString(shortDescription); + parcel.writeString(description); + parcel.writeString(terms); + parcel.writeString(host); + } + } + + + public static class InstanceConfig { + @SerializedName("user") + private User user; + @SerializedName("plugin") + private PluginData.Plugin plugin; + + public PluginData.Plugin getPlugin() { + return plugin; + } + + public void setPlugin(PluginData.Plugin plugin) { + this.plugin = plugin; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + } + + + public static class User { + @SerializedName("videoQuota") + private long videoQuota; + @SerializedName("videoQuotaDaily") + private long videoQuotaDaily; + + public long getVideoQuota() { + return videoQuota; + } + + public void setVideoQuota(long videoQuota) { + this.videoQuota = videoQuota; + } + + public long getVideoQuotaDaily() { + return videoQuotaDaily; + } + + public void setVideoQuotaDaily(long videoQuotaDaily) { + this.videoQuotaDaily = videoQuotaDaily; + } + } +} + diff --git a/app/src/main/java/app/fedilab/android/peertube/client/data/NotificationData.java b/app/src/main/java/app/fedilab/android/peertube/client/data/NotificationData.java new file mode 100644 index 000000000..7517008b8 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/data/NotificationData.java @@ -0,0 +1,167 @@ +package app.fedilab.android.peertube.client.data; + +import com.google.gson.annotations.SerializedName; + +import java.util.Date; +import java.util.List; + +import app.fedilab.android.peertube.client.entities.ActorFollow; +import app.fedilab.android.peertube.client.entities.VideoAbuse; +import app.fedilab.android.peertube.client.entities.VideoBlacklist; + +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class NotificationData { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List data; + + public static class Notification { + @SerializedName("id") + private String id; + @SerializedName("type") + private int type; + @SerializedName("read") + private boolean read; + @SerializedName("video") + private VideoData.Video video; + @SerializedName("videoImport") + private VideoData.VideoImport videoImport; + @SerializedName("comment") + private CommentData.NotificationComment comment; + @SerializedName("videoAbuse") + private VideoAbuse videoAbuse; + @SerializedName("abuse") + private VideoAbuse.Abuse abuse; + @SerializedName("videoBlacklist") + private VideoBlacklist videoBlacklist; + @SerializedName("account") + private AccountData.Account account; + @SerializedName("actorFollow") + private ActorFollow actorFollow; + @SerializedName("createdAt") + private Date createdAt; + @SerializedName("updatedAt") + private Date updatedAt; + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public boolean isRead() { + return read; + } + + public void setRead(boolean read) { + this.read = read; + } + + public VideoData.Video getVideo() { + return video; + } + + public void setVideo(VideoData.Video video) { + this.video = video; + } + + public VideoData.VideoImport getVideoImport() { + return videoImport; + } + + public void setVideoImport(VideoData.VideoImport videoImport) { + this.videoImport = videoImport; + } + + public CommentData.NotificationComment getComment() { + return comment; + } + + public void setComment(CommentData.NotificationComment comment) { + this.comment = comment; + } + + public VideoAbuse getVideoAbuse() { + return videoAbuse; + } + + public void setVideoAbuse(VideoAbuse videoAbuse) { + this.videoAbuse = videoAbuse; + } + + public VideoBlacklist getVideoBlacklist() { + return videoBlacklist; + } + + public void setVideoBlacklist(VideoBlacklist videoBlacklist) { + this.videoBlacklist = videoBlacklist; + } + + public AccountData.Account getAccount() { + return account; + } + + public void setAccount(AccountData.Account account) { + this.account = account; + } + + public ActorFollow getActorFollow() { + return actorFollow; + } + + public void setActorFollow(ActorFollow actorFollow) { + this.actorFollow = actorFollow; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } + + public VideoAbuse.Abuse getAbuse() { + return abuse; + } + + public void setAbuse(VideoAbuse.Abuse abuse) { + this.abuse = abuse; + } + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/data/PlaylistData.java b/app/src/main/java/app/fedilab/android/peertube/client/data/PlaylistData.java new file mode 100644 index 000000000..cb4d38281 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/data/PlaylistData.java @@ -0,0 +1,222 @@ +package app.fedilab.android.peertube.client.data; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.SerializedName; + +import java.util.Date; +import java.util.List; + +import app.fedilab.android.peertube.client.entities.Item; + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class PlaylistData { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List data; + + public static class Playlist implements Parcelable { + public static final Creator CREATOR = new Creator() { + @Override + public Playlist createFromParcel(Parcel source) { + return new Playlist(source); + } + + @Override + public Playlist[] newArray(int size) { + return new Playlist[size]; + } + }; + @SerializedName("id") + private String id; + @SerializedName("createdAt") + private Date createdAt; + @SerializedName("updatedAt") + private Date updatedAt; + @SerializedName("description") + private String description; + @SerializedName("uuid") + private String uuid; + @SerializedName("displayName") + private String displayName; + @SerializedName("isLocal") + private boolean isLocal; + @SerializedName("videoLength") + private long videoLength; + @SerializedName("thumbnailPath") + private String thumbnailPath; + @SerializedName("privacy") + private Item privacy; + @SerializedName("type") + private Item type; + @SerializedName("ownerAccount") + private AccountData.Account ownerAccount; + @SerializedName("videoChannel") + private ChannelData.Channel videoChannel; + + public Playlist() { + } + + protected Playlist(Parcel in) { + this.id = in.readString(); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.description = in.readString(); + this.uuid = in.readString(); + this.displayName = in.readString(); + this.isLocal = in.readByte() != 0; + this.videoLength = in.readLong(); + this.thumbnailPath = in.readString(); + this.privacy = in.readParcelable(Item.class.getClassLoader()); + this.type = in.readParcelable(Item.class.getClassLoader()); + this.ownerAccount = in.readParcelable(AccountData.Account.class.getClassLoader()); + this.videoChannel = in.readParcelable(ChannelData.Channel.class.getClassLoader()); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public boolean isLocal() { + return isLocal; + } + + public void setLocal(boolean local) { + isLocal = local; + } + + public long getVideoLength() { + return videoLength; + } + + public void setVideoLength(long videoLength) { + this.videoLength = videoLength; + } + + public String getThumbnailPath() { + return thumbnailPath; + } + + public void setThumbnailPath(String thumbnailPath) { + this.thumbnailPath = thumbnailPath; + } + + public Item getPrivacy() { + return privacy; + } + + public void setPrivacy(Item privacy) { + this.privacy = privacy; + } + + public Item getType() { + return type; + } + + public void setType(Item type) { + this.type = type; + } + + public AccountData.Account getOwnerAccount() { + return ownerAccount; + } + + public void setOwnerAccount(AccountData.Account ownerAccount) { + this.ownerAccount = ownerAccount; + } + + public ChannelData.Channel getVideoChannel() { + return videoChannel; + } + + public void setVideoChannel(ChannelData.Channel videoChannel) { + this.videoChannel = videoChannel; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.id); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeString(this.description); + dest.writeString(this.uuid); + dest.writeString(this.displayName); + dest.writeByte(this.isLocal ? (byte) 1 : (byte) 0); + dest.writeLong(this.videoLength); + dest.writeString(this.thumbnailPath); + dest.writeParcelable(this.privacy, flags); + dest.writeParcelable(this.type, flags); + dest.writeParcelable(this.ownerAccount, flags); + dest.writeParcelable(this.videoChannel, flags); + } + } + +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/data/PluginData.java b/app/src/main/java/app/fedilab/android/peertube/client/data/PluginData.java new file mode 100644 index 000000000..cb2ef361e --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/data/PluginData.java @@ -0,0 +1,125 @@ +package app.fedilab.android.peertube.client.data; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ +@SuppressWarnings("ALL") +public class PluginData { + + public static class Plugin { + @SerializedName("registered") + private List registered; + @SerializedName("registeredExternalAuths") + private List registeredExternalAuths; + @SerializedName("registeredIdAndPassAuths") + private List registeredIdAndPassAuths; + + public List getRegistered() { + return registered; + } + + public void setRegistered(List registered) { + this.registered = registered; + } + + public List getRegisteredExternalAuths() { + return registeredExternalAuths; + } + + public void setRegisteredExternalAuths(List registeredExternalAuths) { + this.registeredExternalAuths = registeredExternalAuths; + } + + public List getRegisteredIdAndPassAuths() { + return registeredIdAndPassAuths; + } + + public void setRegisteredIdAndPassAuths(List registeredIdAndPassAuths) { + this.registeredIdAndPassAuths = registeredIdAndPassAuths; + } + } + + public static class WaterMark { + @SerializedName("publicSettings") + private PublicSettings description; + + public PublicSettings getDescription() { + return description; + } + + public void setDescription(PublicSettings description) { + this.description = description; + } + } + + public static class PublicSettings { + @SerializedName("watermark-image-url") + private String watermarkImageUrl; + @SerializedName("watermark-target-url") + private String watermarkTargetUrl; + + public String getWatermarkImageUrl() { + return watermarkImageUrl; + } + + public void setWatermarkImageUrl(String watermarkImageUrl) { + this.watermarkImageUrl = watermarkImageUrl; + } + + public String getWatermarkTargetUrl() { + return watermarkTargetUrl; + } + + public void setWatermarkTargetUrl(String watermarkTargetUrl) { + this.watermarkTargetUrl = watermarkTargetUrl; + } + } + + public static class PluginInfo { + @SerializedName("description") + private String description; + @SerializedName("name") + private String name; + @SerializedName("version") + private String version; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/app/fedilab/android/peertube/client/data/VideoData.java b/app/src/main/java/app/fedilab/android/peertube/client/data/VideoData.java new file mode 100644 index 000000000..f600cf992 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/peertube/client/data/VideoData.java @@ -0,0 +1,885 @@ +package app.fedilab.android.peertube.client.data; +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import app.fedilab.android.peertube.client.data.AccountData.Account; +import app.fedilab.android.peertube.client.entities.File; +import app.fedilab.android.peertube.client.entities.Item; +import app.fedilab.android.peertube.client.entities.ItemStr; +import app.fedilab.android.peertube.client.entities.PlaylistExist; +import app.fedilab.android.peertube.client.entities.StreamingPlaylists; +import app.fedilab.android.peertube.helper.Helper; + + +@SuppressWarnings({"unused", "RedundantSuppression"}) +public class VideoData { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List