From 6ef3dc288ee66ee035e53b233e2cefb22815c52d Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 15 Sep 2021 11:44:23 +0200 Subject: [PATCH] New call/conference UI --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 10 +- app/src/main/assets/assistant_default_values | 2 + .../assets/assistant_linphone_default_values | 2 + app/src/main/assets/linphonerc_default | 1 + .../org/linphone/activities/Navigation.kt | 263 ++++++++++ .../{call => }/ProximitySensorActivity.kt | 5 +- .../fragments/PhoneAccountLinkingFragment.kt | 4 +- .../linphone/activities/call/CallActivity.kt | 208 -------- .../activities/call/IncomingCallActivity.kt | 183 ------- .../activities/call/OutgoingCallActivity.kt | 236 --------- .../activities/call/VideoZoomHelper.kt | 142 ----- .../call/data/ConferenceParticipantData.kt | 47 -- .../call/fragments/ControlsFragment.kt | 323 ------------ .../call/fragments/VideoRenderingFragment.kt | 91 ---- .../call/viewmodels/CallViewModel.kt | 166 ------ .../call/viewmodels/CallsViewModel.kt | 165 ------ .../call/viewmodels/ConferenceViewModel.kt | 157 ------ .../viewmodels/ControlsFadingViewModel.kt | 154 ------ .../call/viewmodels/ControlsViewModel.kt | 484 ------------------ .../call/viewmodels/IncomingCallViewModel.kt | 81 --- .../views/AnswerDeclineIncomingCallButtons.kt | 180 ------- .../call/views/ConferenceParticipantView.kt | 71 --- .../activities/call/views/PausedCallView.kt | 67 --- .../chat_bubble/ChatBubbleActivity.kt | 2 +- .../chat/adapters/ChatMessagesListAdapter.kt | 35 +- .../main/chat/data/ChatMessageContentData.kt | 102 +++- .../main/chat/data/ChatMessageData.kt | 4 +- .../main/chat/data/DevicesListChildData.kt | 2 +- .../main/chat/data/DevicesListGroupData.kt | 2 +- .../fragments/ChatRoomCreationFragment.kt | 20 +- .../chat/fragments/DetailChatRoomFragment.kt | 35 +- .../main/chat/fragments/DevicesFragment.kt | 1 - .../main/chat/fragments/EphemeralFragment.kt | 1 - .../main/chat/fragments/GroupInfoFragment.kt | 2 +- .../main/chat/fragments/ImdnFragment.kt | 1 - .../chat/fragments/MasterChatRoomsFragment.kt | 6 +- .../viewmodels/ChatRoomCreationViewModel.kt | 81 +-- .../main/chat/viewmodels/ChatRoomViewModel.kt | 44 +- .../chat/viewmodels/ChatRoomsListViewModel.kt | 6 +- .../chat/viewmodels/GroupInfoViewModel.kt | 8 +- .../adapters/ScheduledConferencesAdapter.kt | 144 ++++++ .../ConferenceSchedulingParticipantData.kt | 32 ++ .../conference/data/Duration.kt} | 18 +- .../data/ScheduledConferenceData.kt | 102 ++++ .../main/conference/data/TimeZoneData.kt | 59 +++ .../fragments/ConferenceSchedulingFragment.kt | 92 ++++ ...renceSchedulingParticipantsListFragment.kt | 121 +++++ .../ConferenceSchedulingSummaryFragment.kt | 76 +++ .../ConferenceWaitingRoomFragment.kt | 169 ++++++ .../fragments/ScheduledConferencesFragment.kt | 137 +++++ .../ConferenceSchedulingViewModel.kt | 277 ++++++++++ .../ConferenceWaitingRoomViewModel.kt | 254 +++++++++ .../ScheduledConferencesViewModel.kt | 82 +++ .../fragments/ContactEditorFragment.kt | 2 +- .../fragments/DetailContactFragment.kt | 3 +- .../fragments/MasterContactsFragment.kt | 4 +- .../contact/viewmodels/ContactViewModel.kt | 8 +- .../main/dialer/fragments/DialerFragment.kt | 16 + .../main/dialer/viewmodels/DialerViewModel.kt | 11 +- .../files/fragments/AudioViewerFragment.kt | 1 - .../files/fragments/GenericViewerFragment.kt | 1 - .../files/fragments/ImageViewerFragment.kt | 1 - .../main/files/fragments/PdfViewerFragment.kt | 1 - .../files/fragments/TextViewerFragment.kt | 1 - .../files/fragments/VideoViewerFragment.kt | 1 - .../history/adapters/CallLogsListAdapter.kt | 2 +- .../main/history/data/CallLogData.kt | 77 --- .../main/history/data/GroupedCallLogData.kt | 8 +- .../fragments/DetailCallLogFragment.kt | 5 +- .../DetailConferenceCallLogFragment.kt | 89 ++++ .../fragments/MasterCallLogsFragment.kt | 53 +- .../history/viewmodels/CallLogViewModel.kt | 106 +++- .../viewmodels/CallLogsListViewModel.kt | 128 +++-- .../fragments/AccountSettingsFragment.kt | 1 - .../fragments/ConferencesSettingsFragment.kt} | 32 +- .../settings/fragments/SettingsFragment.kt | 6 + .../viewmodels/AccountSettingsViewModel.kt | 24 + .../viewmodels/CallSettingsViewModel.kt | 16 +- .../ConferencesSettingsViewModel.kt | 54 ++ .../settings/viewmodels/SettingsViewModel.kt | 3 + .../sidemenu/fragments/SideMenuFragment.kt | 7 +- .../sidemenu/viewmodels/SideMenuViewModel.kt | 3 + .../main/viewmodels/CallOverlayViewModel.kt | 15 +- .../main/viewmodels/LogsUploadViewModel.kt | 3 +- ...ewModel.kt => MessageNotifierViewModel.kt} | 6 +- .../main/viewmodels/TabsViewModel.kt | 1 - .../linphone/activities/voip/CallActivity.kt | 260 ++++++++++ .../linphone/activities/voip/data/CallData.kt | 314 ++++++++++++ .../{call => voip}/data/CallStatisticsData.kt | 12 +- .../voip/data/ConferenceParticipantData.kt | 70 +++ .../data/ConferenceParticipantDeviceData.kt | 161 ++++++ .../{call => voip}/data/StatItemData.kt | 4 +- .../ActiveCallOrConferenceFragment.kt | 338 ++++++++++++ .../voip/fragments/CallsListFragment.kt | 160 ++++++ .../activities/voip/fragments/ChatFragment.kt | 263 ++++++++++ .../ConferenceAddParticipantsFragment.kt | 133 +++++ .../fragments/ConferenceLayoutFragment.kt | 103 ++++ .../ConferenceParticipantsFragment.kt | 80 +++ .../voip/fragments/IncomingCallFragment.kt | 84 +++ .../voip/fragments/OutgoingCallFragment.kt | 83 +++ .../fragments/StatusFragment.kt | 35 +- .../voip/viewmodels/CallsViewModel.kt | 265 ++++++++++ .../ConferenceParticipantsViewModel.kt | 77 +++ .../voip/viewmodels/ConferenceViewModel.kt | 395 ++++++++++++++ .../voip/viewmodels/ControlsViewModel.kt | 456 +++++++++++++++++ .../viewmodels/StatisticsListViewModel.kt | 6 +- .../viewmodels/StatusViewModel.kt | 12 +- .../voip/views/HorizontalScrollDotsView.kt | 190 +++++++ .../voip/views/RoundCornersTextureView.kt | 108 ++++ .../compatibility/Api21Compatibility.kt | 18 + .../compatibility/Api26Compatibility.kt | 4 +- .../compatibility/Api30Compatibility.kt | 17 + .../compatibility/Api31Compatibility.kt | 27 +- .../linphone/compatibility/Compatibility.kt | 17 +- .../compatibility/XiaomiCompatibility.kt | 2 +- .../linphone/contact/BigContactAvatarView.kt | 4 +- .../main/java/org/linphone/contact/Contact.kt | 4 +- .../linphone/contact/ContactDataInterface.kt | 19 +- .../ContactSelectionData.kt} | 6 +- .../org/linphone/contact/ContactsManager.kt | 2 + .../ContactsSelectionAdapter.kt} | 37 +- .../contact/ContactsSelectionViewModel.kt | 113 ++++ .../org/linphone/contact/NativeContact.kt | 2 +- .../java/org/linphone/core/CoreContext.kt | 129 +++-- .../java/org/linphone/core/CorePreferences.kt | 35 +- .../notifications/NotificationsManager.kt | 33 +- .../org/linphone/telecom/NativeCallWrapper.kt | 21 +- .../org/linphone/utils/AudioRouteUtils.kt | 48 +- .../org/linphone/utils/DataBindingUtils.kt | 82 ++- .../java/org/linphone/utils/DialogUtils.kt | 20 + .../main/java/org/linphone/utils/FileUtils.kt | 22 +- .../java/org/linphone/utils/LinphoneUtils.kt | 8 +- .../org/linphone/utils/ShortcutsHelper.kt | 6 +- .../java/org/linphone/utils/TimestampUtils.kt | 54 +- .../res/color/security_switch_track_color.xml | 6 - .../color/voip_extra_button_text_color.xml | 6 + .../main/res/drawable-xhdpi/arrow_accept.png | Bin 446 -> 0 bytes .../main/res/drawable-xhdpi/arrow_hangup.png | Bin 437 -> 0 bytes app/src/main/res/drawable-xhdpi/audio.png | Bin 11876 -> 0 bytes app/src/main/res/drawable-xhdpi/avatar.png | Bin 26655 -> 0 bytes .../res/drawable-xhdpi/camera_default.png | Bin 1218 -> 0 bytes .../drawable-xhdpi/cancel_edit_default.png | Bin 2901 -> 0 bytes .../res/drawable-xhdpi/chat_group_avatar.png | Bin 5357 -> 0 bytes .../res/drawable-xhdpi/checkbox_checked.png | Bin 1792 -> 0 bytes .../res/drawable-xhdpi/checkbox_unchecked.png | Bin 992 -> 0 bytes .../conference_exit_default.png | Bin 4182 -> 0 bytes .../conference_schedule_calendar_default.png | Bin 0 -> 182 bytes ...nference_schedule_participants_default.png | Bin 0 -> 660 bytes .../conference_schedule_time_default.png | Bin 0 -> 442 bytes .../res/drawable-xhdpi/dialer_alt_back.png | Bin 1007 -> 0 bytes .../res/drawable-xhdpi/download_default.png | Bin 1512 -> 0 bytes .../main/res/drawable-xhdpi/micro_default.png | Bin 3561 -> 0 bytes .../res/drawable-xhdpi/more_menu_default.png | Bin 2738 -> 0 bytes .../main/res/drawable-xhdpi/next_default.png | Bin 0 -> 8512 bytes .../options_add_call_default.png | Bin 4495 -> 0 bytes .../res/drawable-xhdpi/options_default.png | Bin 3533 -> 0 bytes .../drawable-xhdpi/options_rec_default.png | Bin 3329 -> 0 bytes .../options_transfer_call_default.png | Bin 4665 -> 0 bytes .../main/res/drawable-xhdpi/pause_default.png | Bin 6705 -> 0 bytes .../drawable-xhdpi/reply_message_default.png | Bin 10985 -> 0 bytes .../route_bluetooth_default.png | Bin 2402 -> 0 bytes .../drawable-xhdpi/route_earpiece_default.png | Bin 3027 -> 0 bytes .../drawable-xhdpi/route_speaker_default.png | Bin 1977 -> 0 bytes .../res/drawable-xhdpi/routes_default.png | Bin 3472 -> 0 bytes .../res/drawable-xhdpi/screenshot_default.png | Bin 10130 -> 0 bytes .../settings_conferences_default.png | Bin 0 -> 12641 bytes .../res/drawable-xhdpi/voip_audio_routes.png | Bin 0 -> 3729 bytes .../res/drawable-xhdpi/voip_bluetooth.png | Bin 0 -> 2557 bytes app/src/main/res/drawable-xhdpi/voip_call.png | Bin 0 -> 3830 bytes .../main/res/drawable-xhdpi/voip_call_add.png | Bin 0 -> 14157 bytes .../res/drawable-xhdpi/voip_call_chat.png | Bin 0 -> 13663 bytes .../res/drawable-xhdpi/voip_call_forward.png | Bin 0 -> 14584 bytes .../voip_call_header_active.png | Bin 0 -> 1787 bytes .../voip_call_header_incoming.png | Bin 0 -> 1298 bytes .../voip_call_header_outgoing.png | Bin 0 -> 1274 bytes .../voip_call_header_paused.png | Bin 0 -> 1119 bytes .../drawable-xhdpi/voip_call_list_menu.png | Bin 0 -> 750 bytes .../res/drawable-xhdpi/voip_call_more.png | Bin 0 -> 10488 bytes .../res/drawable-xhdpi/voip_call_numpad.png | Bin 0 -> 19610 bytes .../drawable-xhdpi/voip_call_participants.png | Bin 0 -> 15075 bytes .../res/drawable-xhdpi/voip_call_record.png | Bin 0 -> 3890 bytes .../res/drawable-xhdpi/voip_call_stats.png | Bin 0 -> 9380 bytes .../res/drawable-xhdpi/voip_calls_list.png | Bin 0 -> 14703 bytes .../res/drawable-xhdpi/voip_camera_off.png | Bin 0 -> 2971 bytes .../res/drawable-xhdpi/voip_camera_on.png | Bin 0 -> 1748 bytes .../main/res/drawable-xhdpi/voip_cancel.png | Bin 0 -> 1609 bytes .../res/drawable-xhdpi/voip_change_camera.png | Bin 0 -> 1831 bytes .../drawable-xhdpi/voip_chat_rooms_list.png | Bin 0 -> 4105 bytes .../voip_conference_active_speaker.png | Bin 0 -> 7818 bytes .../drawable-xhdpi/voip_conference_mosaic.png | Bin 0 -> 8850 bytes .../drawable-xhdpi/voip_conference_new.png | Bin 0 -> 6012 bytes .../voip_conference_paused_big.png | Bin 0 -> 2250 bytes .../voip_conference_play_big.png | Bin 0 -> 15790 bytes app/src/main/res/drawable-xhdpi/voip_copy.png | Bin 0 -> 1930 bytes .../main/res/drawable-xhdpi/voip_delete.png | Bin 0 -> 2223 bytes .../main/res/drawable-xhdpi/voip_dropdown.png | Bin 0 -> 1256 bytes .../main/res/drawable-xhdpi/voip_earpiece.png | Bin 0 -> 3857 bytes app/src/main/res/drawable-xhdpi/voip_edit.png | Bin 0 -> 1834 bytes .../main/res/drawable-xhdpi/voip_export.png | Bin 0 -> 2383 bytes .../main/res/drawable-xhdpi/voip_hangup.png | Bin 0 -> 3166 bytes app/src/main/res/drawable-xhdpi/voip_info.png | Bin 0 -> 13635 bytes .../res/drawable-xhdpi/voip_mandatory.png | Bin 0 -> 7410 bytes .../res/drawable-xhdpi/voip_menu_more.png | Bin 0 -> 1772 bytes .../res/drawable-xhdpi/voip_merge_calls.png | Bin 0 -> 4349 bytes .../res/drawable-xhdpi/voip_micro_off.png | Bin 0 -> 4208 bytes .../main/res/drawable-xhdpi/voip_micro_on.png | Bin 0 -> 3114 bytes .../voip_multiple_contacts_avatar.png | Bin 0 -> 6667 bytes .../main/res/drawable-xhdpi/voip_numpad_0.png | Bin 0 -> 14607 bytes .../main/res/drawable-xhdpi/voip_numpad_1.png | Bin 0 -> 10598 bytes .../main/res/drawable-xhdpi/voip_numpad_2.png | Bin 0 -> 8603 bytes .../main/res/drawable-xhdpi/voip_numpad_3.png | Bin 0 -> 8978 bytes .../main/res/drawable-xhdpi/voip_numpad_4.png | Bin 0 -> 7692 bytes .../main/res/drawable-xhdpi/voip_numpad_5.png | Bin 0 -> 8747 bytes .../main/res/drawable-xhdpi/voip_numpad_6.png | Bin 0 -> 10150 bytes .../main/res/drawable-xhdpi/voip_numpad_7.png | Bin 0 -> 7606 bytes .../main/res/drawable-xhdpi/voip_numpad_8.png | Bin 0 -> 10857 bytes .../main/res/drawable-xhdpi/voip_numpad_9.png | Bin 0 -> 10035 bytes .../res/drawable-xhdpi/voip_numpad_hash.png | Bin 0 -> 12417 bytes .../res/drawable-xhdpi/voip_numpad_star.png | Bin 0 -> 10163 bytes .../main/res/drawable-xhdpi/voip_pause.png | Bin 0 -> 830 bytes .../drawable-xhdpi/voip_remote_recording.png | Bin 0 -> 2673 bytes .../voip_single_contact_avatar.png | Bin 0 -> 4848 bytes .../res/drawable-xhdpi/voip_speaker_off.png | Bin 0 -> 3780 bytes .../res/drawable-xhdpi/voip_speaker_on.png | Bin 0 -> 2599 bytes .../main/res/drawable-xhdpi/voip_spinner.png | Bin 0 -> 2499 bytes .../main/res/drawable-xhdpi/waiting_time.png | Bin 4407 -> 0 bytes .../main/res/drawable/button_background.xml | 9 + .../res/drawable/button_background_dark.xml | 6 +- .../drawable/button_background_reverse.xml | 9 + .../button_call_answer_background.xml | 7 + .../button_call_context_menu_background.xml | 7 + .../button_call_numpad_background.xml | 7 + .../button_call_recording_background.xml | 9 + .../button_call_terminate_background.xml | 7 + .../res/drawable/button_conference_info.xml | 9 + .../res/drawable/button_green_background.xml | 7 + .../res/drawable/button_round_background.xml | 9 + .../res/drawable/button_toggle_background.xml | 11 + .../button_toggle_background_reverse.xml | 11 + .../res/drawable/call_hangup_background.xml | 7 - app/src/main/res/drawable/call_numpad.xml | 10 - app/src/main/res/drawable/call_pause.xml | 20 - app/src/main/res/drawable/call_screenshot.xml | 20 - app/src/main/res/drawable/camera.xml | 19 - ...ecurity.xml => chat_room_menu_devices.xml} | 0 app/src/main/res/drawable/checkbox.xml | 7 - app/src/main/res/drawable/download.xml | 9 - .../res/drawable/field_button_background.xml | 7 - app/src/main/res/drawable/footer_button.xml | 4 +- .../{menu_more.xml => icon_apply.xml} | 6 +- ...rd_stop_dark.xml => icon_audio_routes.xml} | 5 +- app/src/main/res/drawable/icon_bluetooth.xml | 7 + app/src/main/res/drawable/icon_call_add.xml | 7 + .../main/res/drawable/icon_call_answer.xml | 7 + .../res/drawable/icon_call_camera_switch.xml | 7 + app/src/main/res/drawable/icon_call_chat.xml | 15 + .../res/drawable/icon_call_chat_rooms.xml | 7 + .../main/res/drawable/icon_call_forward.xml | 7 + .../main/res/drawable/icon_call_hangup.xml | 7 + app/src/main/res/drawable/icon_call_more.xml | 7 + .../main/res/drawable/icon_call_numpad.xml | 15 + .../res/drawable/icon_call_participants.xml | 15 + .../main/res/drawable/icon_call_record.xml | 15 + app/src/main/res/drawable/icon_call_stats.xml | 15 + app/src/main/res/drawable/icon_calls_list.xml | 15 + app/src/main/res/drawable/icon_cancel.xml | 7 + app/src/main/res/drawable/icon_cancel_alt.xml | 7 + .../main/res/drawable/icon_conf_layout.xml | 15 + .../icon_conference_layout_active_speaker.xml | 7 + .../drawable/icon_conference_layout_grid.xml | 7 + app/src/main/res/drawable/icon_copy.xml | 7 + app/src/main/res/drawable/icon_delete.xml | 7 + app/src/main/res/drawable/icon_earpiece.xml | 7 + app/src/main/res/drawable/icon_edit.xml | 7 + app/src/main/res/drawable/icon_edit_alt.xml | 7 + app/src/main/res/drawable/icon_info.xml | 7 + .../main/res/drawable/icon_info_selected.xml | 7 + ...ptions_add_call.xml => icon_menu_more.xml} | 6 +- .../icon_merge_calls_local_conference.xml | 7 + ...ptions_transfer_call.xml => icon_next.xml} | 6 +- app/src/main/res/drawable/icon_pause.xml | 7 + .../main/res/drawable/icon_schedule_date.xml | 7 + .../drawable/icon_schedule_participants.xml | 7 + ...{conference.xml => icon_schedule_time.xml} | 4 +- ..._remove_participant.xml => icon_share.xml} | 3 +- app/src/main/res/drawable/icon_speaker.xml | 7 + app/src/main/res/drawable/icon_spinner.xml | 7 + .../res/drawable/icon_spinner_background.xml | 24 + .../res/drawable/icon_spinner_rotating.xml | 6 + .../main/res/drawable/icon_toggle_camera.xml | 11 + app/src/main/res/drawable/icon_toggle_mic.xml | 11 + .../main/res/drawable/icon_toggle_speaker.xml | 11 + .../main/res/drawable/icon_video_conf_new.xml | 7 + app/src/main/res/drawable/micro.xml | 19 - app/src/main/res/drawable/options.xml | 19 - app/src/main/res/drawable/options_rec.xml | 19 - .../res/drawable/options_start_conference.xml | 15 - app/src/main/res/drawable/route_bluetooth.xml | 19 - app/src/main/res/drawable/route_earpiece.xml | 19 - app/src/main/res/drawable/route_speaker.xml | 19 - app/src/main/res/drawable/routes.xml | 19 - .../res/drawable/security_switch_track.xml | 10 - .../res/drawable/settings_conferences.xml | 12 + .../shape_audio_routes_background.xml | 5 + .../res/drawable/shape_button_background.xml | 5 + .../shape_button_disabled_background.xml | 5 + .../shape_button_pressed_background.xml | 5 + .../drawable/shape_call_answer_background.xml | 6 + .../shape_call_answer_pressed_background.xml | 6 + ... shape_call_contact_avatar_background.xml} | 4 +- ...pe_call_contact_avatar_background_alt.xml} | 4 +- .../drawable/shape_call_numpad_background.xml | 5 + .../shape_call_numpad_pressed_background.xml | 5 + .../drawable/shape_call_popup_background.xml | 5 + .../shape_call_recording_off_background.xml | 5 + .../shape_call_recording_on_background.xml | 5 + .../shape_call_terminate_background.xml | 6 + ...hape_call_terminate_pressed_background.xml | 6 + ...shape_conference_active_speaker_border.xml | 5 + .../res/drawable/shape_conference_divider.xml | 7 + .../shape_conference_divider_fullscreen.xml | 7 + .../shape_conference_invite_background.xml | 5 + .../shape_conference_selected_border.xml | 5 + .../res/drawable/shape_dialog_background.xml | 5 + .../drawable/shape_edittext_background.xml | 6 + .../shape_extra_buttons_background.xml | 5 + .../drawable/shape_form_field_background.xml | 5 + .../main/res/drawable/shape_orange_circle.xml | 6 + ...h_thumb.xml => shape_rect_gray_button.xml} | 6 +- .../res/drawable/shape_rect_green_button.xml | 6 + .../res/drawable/shape_rect_orange_button.xml | 6 + .../res/drawable/shape_remote_background.xml | 5 + .../shape_remote_paused_background.xml | 5 + .../shape_remote_recording_background.xml | 5 + ....xml => shape_remote_video_background.xml} | 4 +- .../main/res/drawable/shape_round_button.xml | 5 + .../drawable/shape_round_button_disabled.xml | 5 + .../drawable/shape_round_button_pressed.xml | 5 + .../drawable/shape_round_gray_background.xml | 5 + ...und_gray_background_with_orange_border.xml | 6 + .../shape_toggle_disabled_background.xml | 5 + .../drawable/shape_toggle_off_background.xml | 5 + .../drawable/shape_toggle_on_background.xml | 5 + .../shape_toggle_pressed_background.xml | 5 + app/src/main/res/drawable/speaker.xml | 19 - .../main/res/layout-land/about_fragment.xml | 2 +- .../layout-land/call_controls_fragment.xml | 211 -------- .../res/layout-land/call_statistics_cell.xml | 102 ---- .../main/res/layout-land/dialer_fragment.xml | 15 + .../res/layout-land/main_activity_content.xml | 3 +- .../res/layout-land/voip_conference_grid.xml | 126 +++++ .../layout-sw533dp-land/dialer_fragment.xml | 15 + .../res/layout-sw600dp/dialer_fragment.xml | 15 + app/src/main/res/layout/about_fragment.xml | 2 +- app/src/main/res/layout/call_activity.xml | 88 ---- app/src/main/res/layout/call_conference.xml | 89 ---- .../main/res/layout/call_conference_cell.xml | 16 - .../layout/call_conference_participant.xml | 80 --- .../res/layout/call_controls_fragment.xml | 205 -------- .../res/layout/call_incoming_activity.xml | 134 ----- .../call_incoming_answer_decline_buttons.xml | 128 ----- .../call_incoming_notification_heads_up.xml | 2 +- .../res/layout/call_outgoing_activity.xml | 154 ------ app/src/main/res/layout/call_paused.xml | 64 --- app/src/main/res/layout/call_paused_cell.xml | 16 - .../main/res/layout/call_primary_buttons.xml | 72 --- .../res/layout/call_secondary_buttons.xml | 228 --------- .../layout/call_statistics_cell_header.xml | 65 --- .../res/layout/call_statistics_fragment.xml | 28 - .../main/res/layout/call_video_fragment.xml | 42 -- ...age_conference_invitation_content_cell.xml | 137 +++++ .../res/layout/chat_message_content_cell.xml | 7 + .../res/layout/chat_message_list_cell.xml | 2 +- .../layout/chat_message_long_press_menu.xml | 35 +- .../chat_message_reply_content_cell.xml | 2 +- ...hat_message_reply_preview_content_cell.xml | 2 +- .../layout/chat_room_creation_fragment.xml | 6 +- .../res/layout/chat_room_detail_fragment.xml | 14 +- .../layout/chat_room_group_info_fragment.xml | 27 +- .../chat_room_group_info_participant_cell.xml | 20 +- .../main/res/layout/chat_room_list_cell.xml | 4 +- app/src/main/res/layout/chat_room_menu.xml | 19 +- app/src/main/res/layout/conference_paused.xml | 53 -- .../res/layout/conference_schedule_cell.xml | 285 +++++++++++ .../conference_schedule_list_header.xml | 26 + .../layout/conference_scheduling_fragment.xml | 374 ++++++++++++++ ...conference_scheduling_participant_cell.xml | 69 +++ ..._scheduling_participants_list_fragment.xml | 120 +++++ ...conference_scheduling_summary_fragment.xml | 324 ++++++++++++ .../conference_waiting_room_fragment.xml | 229 +++++++++ .../layout/conferences_scheduled_fragment.xml | 81 +++ app/src/main/res/layout/contact_avatar.xml | 4 +- .../main/res/layout/contact_avatar_big.xml | 2 +- ...act_cell.xml => contact_selected_cell.xml} | 2 +- ...ct_cell.xml => contact_selection_cell.xml} | 2 +- app/src/main/res/layout/dialer_fragment.xml | 15 + .../layout/history_conf_detail_fragment.xml | 172 +++++++ .../main/res/layout/history_detail_cell.xml | 2 +- app/src/main/res/layout/history_list_cell.xml | 4 +- .../res/layout/history_master_fragment.xml | 65 ++- .../res/layout/list_edit_top_bar_fragment.xml | 2 +- .../main/res/layout/main_activity_content.xml | 3 +- .../res/layout/settings_account_fragment.xml | 14 + .../res/layout/settings_call_fragment.xml | 13 +- .../layout/settings_conferences_fragment.xml | 85 +++ app/src/main/res/layout/settings_fragment.xml | 7 + .../main/res/layout/side_menu_fragment.xml | 39 +- ...oip_active_call_or_conference_fragment.xml | 187 +++++++ app/src/main/res/layout/voip_activity.xml | 40 ++ app/src/main/res/layout/voip_buttons.xml | 154 ++++++ .../res/layout/voip_buttons_audio_routes.xml | 54 ++ .../main/res/layout/voip_buttons_extra.xml | 179 +++++++ .../main/res/layout/voip_buttons_incoming.xml | 61 +++ .../main/res/layout/voip_buttons_outgoing.xml | 98 ++++ app/src/main/res/layout/voip_call.xml | 235 +++++++++ .../res/layout/voip_call_context_menu.xml | 91 ++++ .../layout/voip_call_incoming_fragment.xml | 121 +++++ .../layout/voip_call_outgoing_fragment.xml | 126 +++++ app/src/main/res/layout/voip_call_paused.xml | 57 +++ .../res/layout/voip_call_paused_by_remote.xml | 41 ++ ...istic_cell.xml => voip_call_stat_cell.xml} | 9 +- app/src/main/res/layout/voip_call_stats.xml | 67 +++ ...tics_cell.xml => voip_call_stats_cell.xml} | 27 +- app/src/main/res/layout/voip_calls_cell.xml | 140 +++++ .../res/layout/voip_calls_list_fragment.xml | 115 +++++ .../main/res/layout/voip_chat_fragment.xml | 202 ++++++++ .../layout/voip_conference_active_speaker.xml | 185 +++++++ ...onference_creation_pending_wait_layout.xml | 36 ++ .../main/res/layout/voip_conference_grid.xml | 126 +++++ .../voip_conference_layout_fragment.xml | 110 ++++ .../voip_conference_participant_cell.xml | 135 +++++ ...cipant_remote_active_speaker_miniature.xml | 115 +++++ ...oip_conference_participant_remote_grid.xml | 114 +++++ ...p_conference_participants_add_fragment.xml | 120 +++++ .../voip_conference_participants_fragment.xml | 92 ++++ .../res/layout/voip_conference_paused.xml | 57 +++ .../main/res/layout/voip_contact_avatar.xml | 57 +++ .../res/layout/voip_contact_avatar_alt.xml | 61 +++ app/src/main/res/layout/voip_dialog.xml | 93 ++++ app/src/main/res/layout/voip_dialog_info.xml | 59 +++ app/src/main/res/layout/voip_numpad.xml | 159 ++++++ ..._fragment.xml => voip_status_fragment.xml} | 24 +- app/src/main/res/layout/wait_layout.xml | 2 +- .../res/navigation/assistant_nav_graph.xml | 1 - .../main/res/navigation/call_nav_graph.xml | 95 ++++ .../res/navigation/contacts_nav_graph.xml | 1 - .../main/res/navigation/history_nav_graph.xml | 11 +- .../main/res/navigation/main_nav_graph.xml | 54 +- .../res/navigation/settings_nav_graph.xml | 9 +- app/src/main/res/values-de/strings.xml | 7 - app/src/main/res/values-es/strings.xml | 35 -- app/src/main/res/values-fr/strings.xml | 35 -- app/src/main/res/values-hu/strings.xml | 35 -- app/src/main/res/values-night-v27/styles.xml | 9 + app/src/main/res/values-night/styles.xml | 41 +- app/src/main/res/values-nl/strings.xml | 6 - app/src/main/res/values-pt/strings.xml | 34 -- app/src/main/res/values-ru/strings.xml | 36 +- app/src/main/res/values-v27/styles.xml | 9 +- app/src/main/res/values-zh-rCN/strings.xml | 37 +- app/src/main/res/values-zh-rTW/strings.xml | 30 -- app/src/main/res/values/attrs.xml | 34 +- app/src/main/res/values/colors.xml | 25 +- app/src/main/res/values/dimen.xml | 26 +- app/src/main/res/values/strings.xml | 129 +++-- app/src/main/res/values/styles.xml | 243 +++++++-- gradle.properties | 2 +- 468 files changed, 13721 insertions(+), 5940 deletions(-) rename app/src/main/java/org/linphone/activities/{call => }/ProximitySensorActivity.kt (95%) delete mode 100644 app/src/main/java/org/linphone/activities/call/CallActivity.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/IncomingCallActivity.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/VideoZoomHelper.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/data/ConferenceParticipantData.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/fragments/VideoRenderingFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/viewmodels/ConferenceViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/viewmodels/ControlsFadingViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/viewmodels/IncomingCallViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/views/AnswerDeclineIncomingCallButtons.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/views/ConferenceParticipantView.kt delete mode 100644 app/src/main/java/org/linphone/activities/call/views/PausedCallView.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/adapters/ScheduledConferencesAdapter.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/data/ConferenceSchedulingParticipantData.kt rename app/src/main/java/org/linphone/activities/{call/viewmodels/SharedCallViewModel.kt => main/conference/data/Duration.kt} (64%) create mode 100644 app/src/main/java/org/linphone/activities/main/conference/data/ScheduledConferenceData.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/data/TimeZoneData.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingParticipantsListFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingSummaryFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceWaitingRoomFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/fragments/ScheduledConferencesFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceSchedulingViewModel.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceWaitingRoomViewModel.kt create mode 100644 app/src/main/java/org/linphone/activities/main/conference/viewmodels/ScheduledConferencesViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/history/data/CallLogData.kt create mode 100644 app/src/main/java/org/linphone/activities/main/history/fragments/DetailConferenceCallLogFragment.kt rename app/src/main/java/org/linphone/activities/{call/fragments/StatisticsFragment.kt => main/settings/fragments/ConferencesSettingsFragment.kt} (51%) create mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/ConferencesSettingsViewModel.kt rename app/src/main/java/org/linphone/activities/main/viewmodels/{ErrorReportingViewModel.kt => MessageNotifierViewModel.kt} (83%) create mode 100644 app/src/main/java/org/linphone/activities/voip/CallActivity.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/data/CallData.kt rename app/src/main/java/org/linphone/activities/{call => voip}/data/CallStatisticsData.kt (93%) create mode 100644 app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantData.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantDeviceData.kt rename app/src/main/java/org/linphone/activities/{call => voip}/data/StatItemData.kt (97%) create mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/ActiveCallOrConferenceFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/CallsListFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/ChatFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/ConferenceAddParticipantsFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/ConferenceLayoutFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/ConferenceParticipantsFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/IncomingCallFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/OutgoingCallFragment.kt rename app/src/main/java/org/linphone/activities/{call => voip}/fragments/StatusFragment.kt (84%) create mode 100644 app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceParticipantsViewModel.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceViewModel.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt rename app/src/main/java/org/linphone/activities/{call => voip}/viewmodels/StatisticsListViewModel.kt (92%) rename app/src/main/java/org/linphone/activities/{call => voip}/viewmodels/StatusViewModel.kt (95%) create mode 100644 app/src/main/java/org/linphone/activities/voip/views/HorizontalScrollDotsView.kt create mode 100644 app/src/main/java/org/linphone/activities/voip/views/RoundCornersTextureView.kt rename app/src/main/java/org/linphone/{activities/main/chat/data/ChatRoomCreationContactData.kt => contact/ContactSelectionData.kt} (92%) rename app/src/main/java/org/linphone/{activities/main/chat/adapters/ChatRoomCreationContactsAdapter.kt => contact/ContactsSelectionAdapter.kt} (80%) create mode 100644 app/src/main/java/org/linphone/contact/ContactsSelectionViewModel.kt delete mode 100644 app/src/main/res/color/security_switch_track_color.xml create mode 100644 app/src/main/res/color/voip_extra_button_text_color.xml delete mode 100644 app/src/main/res/drawable-xhdpi/arrow_accept.png delete mode 100644 app/src/main/res/drawable-xhdpi/arrow_hangup.png delete mode 100644 app/src/main/res/drawable-xhdpi/audio.png delete mode 100644 app/src/main/res/drawable-xhdpi/avatar.png delete mode 100644 app/src/main/res/drawable-xhdpi/camera_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/cancel_edit_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_group_avatar.png delete mode 100644 app/src/main/res/drawable-xhdpi/checkbox_checked.png delete mode 100644 app/src/main/res/drawable-xhdpi/checkbox_unchecked.png delete mode 100644 app/src/main/res/drawable-xhdpi/conference_exit_default.png create mode 100644 app/src/main/res/drawable-xhdpi/conference_schedule_calendar_default.png create mode 100644 app/src/main/res/drawable-xhdpi/conference_schedule_participants_default.png create mode 100644 app/src/main/res/drawable-xhdpi/conference_schedule_time_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/dialer_alt_back.png delete mode 100644 app/src/main/res/drawable-xhdpi/download_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/micro_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/more_menu_default.png create mode 100644 app/src/main/res/drawable-xhdpi/next_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/options_add_call_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/options_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/options_rec_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/options_transfer_call_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/pause_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/reply_message_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/route_bluetooth_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/route_earpiece_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/route_speaker_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/routes_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/screenshot_default.png create mode 100644 app/src/main/res/drawable-xhdpi/settings_conferences_default.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_audio_routes.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_bluetooth.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_add.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_chat.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_forward.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_header_active.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_header_incoming.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_header_outgoing.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_header_paused.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_list_menu.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_more.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_numpad.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_participants.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_record.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_call_stats.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_calls_list.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_camera_off.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_camera_on.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_cancel.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_change_camera.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_chat_rooms_list.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_conference_active_speaker.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_conference_mosaic.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_conference_new.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_conference_paused_big.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_conference_play_big.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_copy.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_delete.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_dropdown.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_earpiece.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_edit.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_export.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_hangup.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_info.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_mandatory.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_menu_more.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_merge_calls.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_micro_off.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_micro_on.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_multiple_contacts_avatar.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_0.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_1.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_2.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_3.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_4.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_5.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_6.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_7.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_8.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_9.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_hash.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_star.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_pause.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_remote_recording.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_single_contact_avatar.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_speaker_off.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_speaker_on.png create mode 100644 app/src/main/res/drawable-xhdpi/voip_spinner.png delete mode 100644 app/src/main/res/drawable-xhdpi/waiting_time.png create mode 100644 app/src/main/res/drawable/button_background.xml create mode 100644 app/src/main/res/drawable/button_background_reverse.xml create mode 100644 app/src/main/res/drawable/button_call_answer_background.xml create mode 100644 app/src/main/res/drawable/button_call_context_menu_background.xml create mode 100644 app/src/main/res/drawable/button_call_numpad_background.xml create mode 100644 app/src/main/res/drawable/button_call_recording_background.xml create mode 100644 app/src/main/res/drawable/button_call_terminate_background.xml create mode 100644 app/src/main/res/drawable/button_conference_info.xml create mode 100644 app/src/main/res/drawable/button_green_background.xml create mode 100644 app/src/main/res/drawable/button_round_background.xml create mode 100644 app/src/main/res/drawable/button_toggle_background.xml create mode 100644 app/src/main/res/drawable/button_toggle_background_reverse.xml delete mode 100644 app/src/main/res/drawable/call_hangup_background.xml delete mode 100644 app/src/main/res/drawable/call_numpad.xml delete mode 100644 app/src/main/res/drawable/call_pause.xml delete mode 100644 app/src/main/res/drawable/call_screenshot.xml delete mode 100644 app/src/main/res/drawable/camera.xml rename app/src/main/res/drawable/{chat_room_menu_security.xml => chat_room_menu_devices.xml} (100%) delete mode 100644 app/src/main/res/drawable/checkbox.xml delete mode 100644 app/src/main/res/drawable/download.xml delete mode 100644 app/src/main/res/drawable/field_button_background.xml rename app/src/main/res/drawable/{menu_more.xml => icon_apply.xml} (70%) rename app/src/main/res/drawable/{record_stop_dark.xml => icon_audio_routes.xml} (56%) create mode 100644 app/src/main/res/drawable/icon_bluetooth.xml create mode 100644 app/src/main/res/drawable/icon_call_add.xml create mode 100644 app/src/main/res/drawable/icon_call_answer.xml create mode 100644 app/src/main/res/drawable/icon_call_camera_switch.xml create mode 100644 app/src/main/res/drawable/icon_call_chat.xml create mode 100644 app/src/main/res/drawable/icon_call_chat_rooms.xml create mode 100644 app/src/main/res/drawable/icon_call_forward.xml create mode 100644 app/src/main/res/drawable/icon_call_hangup.xml create mode 100644 app/src/main/res/drawable/icon_call_more.xml create mode 100644 app/src/main/res/drawable/icon_call_numpad.xml create mode 100644 app/src/main/res/drawable/icon_call_participants.xml create mode 100644 app/src/main/res/drawable/icon_call_record.xml create mode 100644 app/src/main/res/drawable/icon_call_stats.xml create mode 100644 app/src/main/res/drawable/icon_calls_list.xml create mode 100644 app/src/main/res/drawable/icon_cancel.xml create mode 100644 app/src/main/res/drawable/icon_cancel_alt.xml create mode 100644 app/src/main/res/drawable/icon_conf_layout.xml create mode 100644 app/src/main/res/drawable/icon_conference_layout_active_speaker.xml create mode 100644 app/src/main/res/drawable/icon_conference_layout_grid.xml create mode 100644 app/src/main/res/drawable/icon_copy.xml create mode 100644 app/src/main/res/drawable/icon_delete.xml create mode 100644 app/src/main/res/drawable/icon_earpiece.xml create mode 100644 app/src/main/res/drawable/icon_edit.xml create mode 100644 app/src/main/res/drawable/icon_edit_alt.xml create mode 100644 app/src/main/res/drawable/icon_info.xml create mode 100644 app/src/main/res/drawable/icon_info_selected.xml rename app/src/main/res/drawable/{options_add_call.xml => icon_menu_more.xml} (68%) create mode 100644 app/src/main/res/drawable/icon_merge_calls_local_conference.xml rename app/src/main/res/drawable/{options_transfer_call.xml => icon_next.xml} (66%) create mode 100644 app/src/main/res/drawable/icon_pause.xml create mode 100644 app/src/main/res/drawable/icon_schedule_date.xml create mode 100644 app/src/main/res/drawable/icon_schedule_participants.xml rename app/src/main/res/drawable/{conference.xml => icon_schedule_time.xml} (56%) rename app/src/main/res/drawable/{conference_remove_participant.xml => icon_share.xml} (75%) create mode 100644 app/src/main/res/drawable/icon_speaker.xml create mode 100644 app/src/main/res/drawable/icon_spinner.xml create mode 100644 app/src/main/res/drawable/icon_spinner_background.xml create mode 100644 app/src/main/res/drawable/icon_spinner_rotating.xml create mode 100644 app/src/main/res/drawable/icon_toggle_camera.xml create mode 100644 app/src/main/res/drawable/icon_toggle_mic.xml create mode 100644 app/src/main/res/drawable/icon_toggle_speaker.xml create mode 100644 app/src/main/res/drawable/icon_video_conf_new.xml delete mode 100644 app/src/main/res/drawable/micro.xml delete mode 100644 app/src/main/res/drawable/options.xml delete mode 100644 app/src/main/res/drawable/options_rec.xml delete mode 100644 app/src/main/res/drawable/options_start_conference.xml delete mode 100644 app/src/main/res/drawable/route_bluetooth.xml delete mode 100644 app/src/main/res/drawable/route_earpiece.xml delete mode 100644 app/src/main/res/drawable/route_speaker.xml delete mode 100644 app/src/main/res/drawable/routes.xml delete mode 100644 app/src/main/res/drawable/security_switch_track.xml create mode 100644 app/src/main/res/drawable/settings_conferences.xml create mode 100644 app/src/main/res/drawable/shape_audio_routes_background.xml create mode 100644 app/src/main/res/drawable/shape_button_background.xml create mode 100644 app/src/main/res/drawable/shape_button_disabled_background.xml create mode 100644 app/src/main/res/drawable/shape_button_pressed_background.xml create mode 100644 app/src/main/res/drawable/shape_call_answer_background.xml create mode 100644 app/src/main/res/drawable/shape_call_answer_pressed_background.xml rename app/src/main/res/drawable/{field_button_background_default.xml => shape_call_contact_avatar_background.xml} (55%) rename app/src/main/res/drawable/{field_button_background_over.xml => shape_call_contact_avatar_background_alt.xml} (52%) create mode 100644 app/src/main/res/drawable/shape_call_numpad_background.xml create mode 100644 app/src/main/res/drawable/shape_call_numpad_pressed_background.xml create mode 100644 app/src/main/res/drawable/shape_call_popup_background.xml create mode 100644 app/src/main/res/drawable/shape_call_recording_off_background.xml create mode 100644 app/src/main/res/drawable/shape_call_recording_on_background.xml create mode 100644 app/src/main/res/drawable/shape_call_terminate_background.xml create mode 100644 app/src/main/res/drawable/shape_call_terminate_pressed_background.xml create mode 100644 app/src/main/res/drawable/shape_conference_active_speaker_border.xml create mode 100644 app/src/main/res/drawable/shape_conference_divider.xml create mode 100644 app/src/main/res/drawable/shape_conference_divider_fullscreen.xml create mode 100644 app/src/main/res/drawable/shape_conference_invite_background.xml create mode 100644 app/src/main/res/drawable/shape_conference_selected_border.xml create mode 100644 app/src/main/res/drawable/shape_dialog_background.xml create mode 100644 app/src/main/res/drawable/shape_edittext_background.xml create mode 100644 app/src/main/res/drawable/shape_extra_buttons_background.xml create mode 100644 app/src/main/res/drawable/shape_form_field_background.xml create mode 100644 app/src/main/res/drawable/shape_orange_circle.xml rename app/src/main/res/drawable/{security_switch_thumb.xml => shape_rect_gray_button.xml} (56%) create mode 100644 app/src/main/res/drawable/shape_rect_green_button.xml create mode 100644 app/src/main/res/drawable/shape_rect_orange_button.xml create mode 100644 app/src/main/res/drawable/shape_remote_background.xml create mode 100644 app/src/main/res/drawable/shape_remote_paused_background.xml create mode 100644 app/src/main/res/drawable/shape_remote_recording_background.xml rename app/src/main/res/drawable/{call_video_conference_participant_background.xml => shape_remote_video_background.xml} (79%) create mode 100644 app/src/main/res/drawable/shape_round_button.xml create mode 100644 app/src/main/res/drawable/shape_round_button_disabled.xml create mode 100644 app/src/main/res/drawable/shape_round_button_pressed.xml create mode 100644 app/src/main/res/drawable/shape_round_gray_background.xml create mode 100644 app/src/main/res/drawable/shape_round_gray_background_with_orange_border.xml create mode 100644 app/src/main/res/drawable/shape_toggle_disabled_background.xml create mode 100644 app/src/main/res/drawable/shape_toggle_off_background.xml create mode 100644 app/src/main/res/drawable/shape_toggle_on_background.xml create mode 100644 app/src/main/res/drawable/shape_toggle_pressed_background.xml delete mode 100644 app/src/main/res/drawable/speaker.xml delete mode 100644 app/src/main/res/layout-land/call_controls_fragment.xml delete mode 100644 app/src/main/res/layout-land/call_statistics_cell.xml create mode 100644 app/src/main/res/layout-land/voip_conference_grid.xml delete mode 100644 app/src/main/res/layout/call_activity.xml delete mode 100644 app/src/main/res/layout/call_conference.xml delete mode 100644 app/src/main/res/layout/call_conference_cell.xml delete mode 100644 app/src/main/res/layout/call_conference_participant.xml delete mode 100644 app/src/main/res/layout/call_controls_fragment.xml delete mode 100644 app/src/main/res/layout/call_incoming_activity.xml delete mode 100644 app/src/main/res/layout/call_incoming_answer_decline_buttons.xml delete mode 100644 app/src/main/res/layout/call_outgoing_activity.xml delete mode 100644 app/src/main/res/layout/call_paused.xml delete mode 100644 app/src/main/res/layout/call_paused_cell.xml delete mode 100644 app/src/main/res/layout/call_primary_buttons.xml delete mode 100644 app/src/main/res/layout/call_secondary_buttons.xml delete mode 100644 app/src/main/res/layout/call_statistics_cell_header.xml delete mode 100644 app/src/main/res/layout/call_statistics_fragment.xml delete mode 100644 app/src/main/res/layout/call_video_fragment.xml create mode 100644 app/src/main/res/layout/chat_message_conference_invitation_content_cell.xml delete mode 100644 app/src/main/res/layout/conference_paused.xml create mode 100644 app/src/main/res/layout/conference_schedule_cell.xml create mode 100644 app/src/main/res/layout/conference_schedule_list_header.xml create mode 100644 app/src/main/res/layout/conference_scheduling_fragment.xml create mode 100644 app/src/main/res/layout/conference_scheduling_participant_cell.xml create mode 100644 app/src/main/res/layout/conference_scheduling_participants_list_fragment.xml create mode 100644 app/src/main/res/layout/conference_scheduling_summary_fragment.xml create mode 100644 app/src/main/res/layout/conference_waiting_room_fragment.xml create mode 100644 app/src/main/res/layout/conferences_scheduled_fragment.xml rename app/src/main/res/layout/{chat_room_creation_selected_contact_cell.xml => contact_selected_cell.xml} (93%) rename app/src/main/res/layout/{chat_room_creation_contact_cell.xml => contact_selection_cell.xml} (97%) create mode 100644 app/src/main/res/layout/history_conf_detail_fragment.xml create mode 100644 app/src/main/res/layout/settings_conferences_fragment.xml create mode 100644 app/src/main/res/layout/voip_active_call_or_conference_fragment.xml create mode 100644 app/src/main/res/layout/voip_activity.xml create mode 100644 app/src/main/res/layout/voip_buttons.xml create mode 100644 app/src/main/res/layout/voip_buttons_audio_routes.xml create mode 100644 app/src/main/res/layout/voip_buttons_extra.xml create mode 100644 app/src/main/res/layout/voip_buttons_incoming.xml create mode 100644 app/src/main/res/layout/voip_buttons_outgoing.xml create mode 100644 app/src/main/res/layout/voip_call.xml create mode 100644 app/src/main/res/layout/voip_call_context_menu.xml create mode 100644 app/src/main/res/layout/voip_call_incoming_fragment.xml create mode 100644 app/src/main/res/layout/voip_call_outgoing_fragment.xml create mode 100644 app/src/main/res/layout/voip_call_paused.xml create mode 100644 app/src/main/res/layout/voip_call_paused_by_remote.xml rename app/src/main/res/layout/{call_single_statistic_cell.xml => voip_call_stat_cell.xml} (77%) create mode 100644 app/src/main/res/layout/voip_call_stats.xml rename app/src/main/res/layout/{call_statistics_cell.xml => voip_call_stats_cell.xml} (71%) create mode 100644 app/src/main/res/layout/voip_calls_cell.xml create mode 100644 app/src/main/res/layout/voip_calls_list_fragment.xml create mode 100644 app/src/main/res/layout/voip_chat_fragment.xml create mode 100644 app/src/main/res/layout/voip_conference_active_speaker.xml create mode 100644 app/src/main/res/layout/voip_conference_creation_pending_wait_layout.xml create mode 100644 app/src/main/res/layout/voip_conference_grid.xml create mode 100644 app/src/main/res/layout/voip_conference_layout_fragment.xml create mode 100644 app/src/main/res/layout/voip_conference_participant_cell.xml create mode 100644 app/src/main/res/layout/voip_conference_participant_remote_active_speaker_miniature.xml create mode 100644 app/src/main/res/layout/voip_conference_participant_remote_grid.xml create mode 100644 app/src/main/res/layout/voip_conference_participants_add_fragment.xml create mode 100644 app/src/main/res/layout/voip_conference_participants_fragment.xml create mode 100644 app/src/main/res/layout/voip_conference_paused.xml create mode 100644 app/src/main/res/layout/voip_contact_avatar.xml create mode 100644 app/src/main/res/layout/voip_contact_avatar_alt.xml create mode 100644 app/src/main/res/layout/voip_dialog.xml create mode 100644 app/src/main/res/layout/voip_dialog_info.xml create mode 100644 app/src/main/res/layout/voip_numpad.xml rename app/src/main/res/layout/{call_status_fragment.xml => voip_status_fragment.xml} (82%) create mode 100644 app/src/main/res/navigation/call_nav_graph.xml create mode 100644 app/src/main/res/values-night-v27/styles.xml diff --git a/app/build.gradle b/app/build.gradle index effc1d64e..ac6879953 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -202,7 +202,7 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.4.1' implementation 'androidx.core:core-ktx:1.7.0' - def nav_version = "2.4.0" + def nav_version = "2.4.1" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" @@ -211,7 +211,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "androidx.security:security-crypto-ktx:1.1.0-alpha03" implementation 'androidx.core:core-splashscreen:1.0.0-beta01' @@ -237,7 +237,7 @@ dependencies { implementation 'com.google.firebase:firebase-messaging' } - implementation 'org.linphone:linphone-sdk-android:5.1+' + implementation 'org.linphone:linphone-sdk-android:5.2+' // Only enable leak canary prior to release //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 88547b8ef..571c013c6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -115,19 +115,11 @@ - - - - - + 0 0 + 0
diff --git a/app/src/main/assets/assistant_linphone_default_values b/app/src/main/assets/assistant_linphone_default_values index 9f2badee0..497e98efc 100644 --- a/app/src/main/assets/assistant_linphone_default_values +++ b/app/src/main/assets/assistant_linphone_default_values @@ -15,8 +15,10 @@ nat_policy_default_values sip.linphone.org sip:conference-factory@sip.linphone.org + sip:videoconference-factory2@sip.linphone.org 1 1 + 1
stun.linphone.org diff --git a/app/src/main/assets/linphonerc_default b/app/src/main/assets/linphonerc_default index 48add4247..2e8b8b02e 100644 --- a/app/src/main/assets/linphonerc_default +++ b/app/src/main/assets/linphonerc_default @@ -33,6 +33,7 @@ file_transfer_server_url=https://www.linphone.org:444/lft.php version_check_url_root=https://www.linphone.org/releases max_calls=10 history_max_size=100 +conference_layout=1 [in-app-purchase] server_url=https://subscribe.linphone.org:444/inapp.php diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt index 5d6bd190a..331c2417f 100644 --- a/app/src/main/java/org/linphone/activities/Navigation.kt +++ b/app/src/main/java/org/linphone/activities/Navigation.kt @@ -36,15 +36,22 @@ import org.linphone.activities.main.chat.fragments.ChatRoomCreationFragment import org.linphone.activities.main.chat.fragments.DetailChatRoomFragment import org.linphone.activities.main.chat.fragments.GroupInfoFragment import org.linphone.activities.main.chat.fragments.MasterChatRoomsFragment +import org.linphone.activities.main.conference.fragments.* import org.linphone.activities.main.contact.fragments.ContactEditorFragment import org.linphone.activities.main.contact.fragments.DetailContactFragment import org.linphone.activities.main.contact.fragments.MasterContactsFragment import org.linphone.activities.main.dialer.fragments.DialerFragment import org.linphone.activities.main.fragments.TabsFragment import org.linphone.activities.main.history.fragments.DetailCallLogFragment +import org.linphone.activities.main.history.fragments.DetailConferenceCallLogFragment import org.linphone.activities.main.history.fragments.MasterCallLogsFragment import org.linphone.activities.main.settings.fragments.* import org.linphone.activities.main.sidemenu.fragments.SideMenuFragment +import org.linphone.activities.voip.CallActivity +import org.linphone.activities.voip.fragments.ActiveCallOrConferenceFragment +import org.linphone.activities.voip.fragments.ConferenceParticipantsFragment +import org.linphone.activities.voip.fragments.IncomingCallFragment +import org.linphone.activities.voip.fragments.OutgoingCallFragment import org.linphone.contact.NativeContact import org.linphone.core.Address @@ -173,6 +180,104 @@ internal fun DialerFragment.navigateToConfigFileViewer() { ) } +internal fun DialerFragment.navigateToConferenceScheduling() { + findMasterNavController().navigate( + R.id.action_global_conferenceSchedulingFragment, + null, + popupTo() + ) +} + +/* Conference scheduling related */ + +internal fun ConferenceSchedulingFragment.navigateToParticipantsList() { + if (findNavController().currentDestination?.id == R.id.conferenceSchedulingFragment) { + findNavController().navigate( + R.id.action_conferenceSchedulingFragment_to_conferenceSchedulingParticipantsListFragment, + null, + popupTo(R.id.conferenceSchedulingParticipantsListFragment, true) + ) + } +} + +internal fun ConferenceSchedulingParticipantsListFragment.navigateToSummary() { + if (findNavController().currentDestination?.id == R.id.conferenceSchedulingParticipantsListFragment) { + findNavController().navigate( + R.id.action_conferenceSchedulingParticipantsListFragment_to_conferenceSchedulingSummaryFragment, + null, + popupTo(R.id.conferenceSchedulingSummaryFragment, true) + ) + } +} + +internal fun ConferenceSchedulingSummaryFragment.goToScheduledConferences() { + if (findNavController().currentDestination?.id == R.id.conferenceSchedulingSummaryFragment) { + findNavController().navigate( + R.id.action_global_scheduledConferencesFragment, + null, + popupTo(R.id.dialerFragment, false) + ) + } +} + +internal fun ConferenceSchedulingSummaryFragment.navigateToConferenceWaitingRoom( + address: String, + subject: String? +) { + val bundle = Bundle() + bundle.putString("Address", address) + bundle.putString("Subject", subject) + findMasterNavController().navigate( + R.id.action_global_conferenceWaitingRoomFragment, + bundle, + popupTo(R.id.dialerFragment, false) + ) +} + +internal fun ConferenceWaitingRoomFragment.navigateToDialer() { + findNavController().navigate( + R.id.action_global_dialerFragment, + null, + popupTo(R.id.dialerFragment, true) + ) +} + +internal fun DetailChatRoomFragment.navigateToConferenceWaitingRoom( + address: String, + subject: String? +) { + val bundle = Bundle() + bundle.putString("Address", address) + bundle.putString("Subject", subject) + findMasterNavController().navigate( + R.id.action_global_conferenceWaitingRoomFragment, + bundle, + popupTo(R.id.conferenceWaitingRoomFragment, true) + ) +} + +internal fun ScheduledConferencesFragment.navigateToConferenceWaitingRoom( + address: String, + subject: String? +) { + val bundle = Bundle() + bundle.putString("Address", address) + bundle.putString("Subject", subject) + findMasterNavController().navigate( + R.id.action_global_conferenceWaitingRoomFragment, + bundle, + popupTo(R.id.conferenceWaitingRoomFragment, true) + ) +} + +internal fun ScheduledConferencesFragment.navigateToConferenceScheduling() { + findMasterNavController().navigate( + R.id.action_global_conferenceSchedulingFragment, + null, + popupTo(R.id.conferenceSchedulingFragment, true) + ) +} + /* Chat related */ internal fun MasterChatRoomsFragment.navigateToChatRoom(args: Bundle) { @@ -499,6 +604,19 @@ internal fun MasterCallLogsFragment.navigateToCallHistory(slidingPane: SlidingPa } } +internal fun MasterCallLogsFragment.navigateToConferenceCallHistory(slidingPane: SlidingPaneLayout) { + if (findNavController().currentDestination?.id == R.id.masterCallLogsFragment) { + val navHostFragment = + childFragmentManager.findFragmentById(R.id.history_nav_container) as NavHostFragment + navHostFragment.navController.navigate( + R.id.action_global_detailConferenceCallLogFragment, + null, + popupTo(R.id.detailConferenceCallLogFragment, true) + ) + if (!slidingPane.isOpen) slidingPane.openPane() + } +} + internal fun MasterCallLogsFragment.clearDisplayedCallHistory() { if (findNavController().currentDestination?.id == R.id.masterCallLogsFragment) { val navHostFragment = @@ -519,6 +637,20 @@ internal fun MasterCallLogsFragment.navigateToDialer(args: Bundle?) { ) } +internal fun MasterCallLogsFragment.navigateToConferenceWaitingRoom( + address: String, + subject: String? +) { + val bundle = Bundle() + bundle.putString("Address", address) + bundle.putString("Subject", subject) + findMasterNavController().navigate( + R.id.action_global_conferenceWaitingRoomFragment, + bundle, + popupTo(R.id.dialerFragment, false) + ) +} + internal fun DetailCallLogFragment.navigateToContacts(sipUriToAdd: String) { val deepLink = "linphone-android://contact/new/$sipUriToAdd" findMasterNavController().navigate(Uri.parse(deepLink)) @@ -564,6 +696,16 @@ internal fun DetailCallLogFragment.navigateToEmptyCallHistory() { } } +internal fun DetailConferenceCallLogFragment.navigateToEmptyCallHistory() { + if (findNavController().currentDestination?.id == R.id.detailConferenceCallLogFragment) { + findNavController().navigate( + R.id.action_global_emptyFragment, + null, + popupTo(R.id.emptyCallHistoryFragment, true) + ) + } +} + /* Settings related */ internal fun SettingsFragment.navigateToAccountSettings(identity: String) { @@ -683,6 +825,19 @@ internal fun SettingsFragment.navigateToAdvancedSettings(slidingPane: SlidingPan } } +internal fun SettingsFragment.navigateToConferencesSettings(slidingPane: SlidingPaneLayout) { + if (findNavController().currentDestination?.id == R.id.settingsFragment) { + val navHostFragment = + childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment + navHostFragment.navController.navigate( + R.id.action_global_conferencesSettingsFragment, + null, + popupTo(R.id.conferencesSettingsFragment, true) + ) + if (!slidingPane.isOpen) slidingPane.openPane() + } +} + internal fun AccountSettingsFragment.navigateToPhoneLinking(args: Bundle?) { if (findNavController().currentDestination?.id == R.id.accountSettingsFragment) { findNavController().navigate( @@ -731,6 +886,10 @@ internal fun ChatSettingsFragment.navigateToEmptySetting() { navigateToEmptySetting(findNavController()) } +internal fun ConferencesSettingsFragment.navigateToEmptySetting() { + navigateToEmptySetting(findNavController()) +} + internal fun ContactsSettingsFragment.navigateToEmptySetting() { navigateToEmptySetting(findNavController()) } @@ -778,6 +937,110 @@ internal fun SideMenuFragment.navigateToRecordings() { ) } +internal fun SideMenuFragment.navigateToScheduledConferences() { + findNavController().navigate( + R.id.action_global_scheduledConferencesFragment, + null, + popupTo(R.id.scheduledConferencesFragment, true) + ) +} + +/* Calls related */ + +internal fun CallActivity.navigateToActiveCall() { + if (findNavController(R.id.nav_host_fragment).currentDestination?.id != R.id.activeCallOrConferenceFragment) { + findNavController(R.id.nav_host_fragment).navigate( + R.id.action_global_activeCallOrConferenceFragment, + null, + popupTo(R.id.activeCallOrConferenceFragment, false) + ) + } +} + +internal fun CallActivity.navigateToOutgoingCall() { + findNavController(R.id.nav_host_fragment).navigate( + R.id.action_global_outgoingCallFragment, + null, + popupTo(R.id.activeCallOrConferenceFragment, false) + ) +} + +internal fun CallActivity.navigateToIncomingCall(earlyMediaVideoEnabled: Boolean) { + val args = Bundle() + args.putBoolean("earlyMediaVideo", earlyMediaVideoEnabled) + findNavController(R.id.nav_host_fragment).navigate( + R.id.action_global_incomingCallFragment, + args, + popupTo(R.id.activeCallOrConferenceFragment, false) + ) +} + +internal fun OutgoingCallFragment.navigateToActiveCall() { + findNavController().navigate( + R.id.action_global_activeCallOrConferenceFragment, + null, + popupTo(R.id.activeCallOrConferenceFragment, false) + ) +} + +internal fun IncomingCallFragment.navigateToActiveCall() { + findNavController().navigate( + R.id.action_global_activeCallOrConferenceFragment, + null, + popupTo(R.id.activeCallOrConferenceFragment, false) + ) +} + +internal fun ActiveCallOrConferenceFragment.navigateToCallsList() { + if (findNavController().currentDestination?.id == R.id.activeCallOrConferenceFragment) { + findNavController().navigate( + R.id.action_activeCallOrConferenceFragment_to_callsListFragment, + null, + popupTo() + ) + } +} + +internal fun ActiveCallOrConferenceFragment.navigateToConferenceParticipants() { + if (findNavController().currentDestination?.id == R.id.activeCallOrConferenceFragment) { + findNavController().navigate( + R.id.action_activeCallOrConferenceFragment_to_conferenceParticipantsFragment, + null, + popupTo() + ) + } +} + +internal fun ActiveCallOrConferenceFragment.navigateToChat(args: Bundle) { + if (findNavController().currentDestination?.id == R.id.activeCallOrConferenceFragment) { + findNavController().navigate( + R.id.action_activeCallOrConferenceFragment_to_chatFragment, + args, + popupTo() + ) + } +} + +internal fun ActiveCallOrConferenceFragment.navigateToConferenceLayout() { + if (findNavController().currentDestination?.id == R.id.activeCallOrConferenceFragment) { + findNavController().navigate( + R.id.action_activeCallOrConferenceFragment_to_conferenceLayoutFragment, + null, + popupTo() + ) + } +} + +internal fun ConferenceParticipantsFragment.navigateToAddParticipants() { + if (findNavController().currentDestination?.id == R.id.conferenceParticipantsFragment) { + findNavController().navigate( + R.id.action_conferenceParticipantsFragment_to_conferenceAddParticipantsFragment, + null, + popupTo(R.id.conferenceAddParticipantsFragment, true) + ) + } +} + /* Assistant related */ internal fun WelcomeFragment.navigateToEmailAccountCreation() { diff --git a/app/src/main/java/org/linphone/activities/call/ProximitySensorActivity.kt b/app/src/main/java/org/linphone/activities/ProximitySensorActivity.kt similarity index 95% rename from app/src/main/java/org/linphone/activities/call/ProximitySensorActivity.kt rename to app/src/main/java/org/linphone/activities/ProximitySensorActivity.kt index cc502b4d1..e66befe35 100644 --- a/app/src/main/java/org/linphone/activities/call/ProximitySensorActivity.kt +++ b/app/src/main/java/org/linphone/activities/ProximitySensorActivity.kt @@ -17,14 +17,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.activities.call +package org.linphone.activities import android.content.Context import android.os.Bundle import android.os.PowerManager import android.os.PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.GenericActivity import org.linphone.core.tools.Log abstract class ProximitySensorActivity : GenericActivity() { @@ -49,7 +48,7 @@ abstract class ProximitySensorActivity : GenericActivity() { super.onResume() if (coreContext.core.callsNb > 0) { - val videoEnabled = coreContext.isVideoCallOrConferenceActive() + val videoEnabled = coreContext.core.currentCall?.currentParams?.isVideoEnabled ?: false enableProximitySensor(!videoEnabled) } } diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountLinkingFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountLinkingFragment.kt index 1f7fa1f26..66d1ed98a 100644 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountLinkingFragment.kt +++ b/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountLinkingFragment.kt @@ -22,7 +22,7 @@ package org.linphone.activities.assistant.fragments import android.os.Bundle import android.view.View import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication +import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.activities.assistant.AssistantActivity import org.linphone.activities.assistant.viewmodels.* @@ -87,7 +87,7 @@ class PhoneAccountLinkingFragment : AbstractPhoneFragment. - */ -package org.linphone.activities.call - -import android.content.Intent -import android.content.res.Configuration -import android.content.res.Resources -import android.os.Bundle -import android.view.Gravity -import android.view.View -import androidx.constraintlayout.widget.ConstraintSet -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.ViewModelProvider -import androidx.window.layout.FoldingFeature -import kotlinx.coroutines.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.call.viewmodels.* -import org.linphone.activities.main.MainActivity -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log -import org.linphone.databinding.CallActivityBinding - -class CallActivity : ProximitySensorActivity() { - private lateinit var binding: CallActivityBinding - private lateinit var viewModel: ControlsFadingViewModel - private lateinit var sharedViewModel: SharedCallViewModel - - private var foldingFeature: FoldingFeature? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - Compatibility.setShowWhenLocked(this, true) - Compatibility.setTurnScreenOn(this, true) - - binding = DataBindingUtil.setContentView(this, R.layout.call_activity) - binding.lifecycleOwner = this - - viewModel = ViewModelProvider(this)[ControlsFadingViewModel::class.java] - binding.controlsFadingViewModel = viewModel - - sharedViewModel = ViewModelProvider(this)[SharedCallViewModel::class.java] - - sharedViewModel.toggleDrawerEvent.observe( - this - ) { - it.consume { - if (binding.statsMenu.isDrawerOpen(Gravity.LEFT)) { - binding.statsMenu.closeDrawer(binding.sideMenuContent, true) - } else { - binding.statsMenu.openDrawer(binding.sideMenuContent, true) - } - } - } - - sharedViewModel.resetHiddenInterfaceTimerInVideoCallEvent.observe( - this - ) { - it.consume { - viewModel.showMomentarily() - } - } - - viewModel.proximitySensorEnabled.observe( - this - ) { - enableProximitySensor(it) - } - - viewModel.videoEnabled.observe( - this - ) { - updateConstraintSetDependingOnFoldingState() - } - } - - override fun onLayoutChanges(foldingFeature: FoldingFeature?) { - this.foldingFeature = foldingFeature - updateConstraintSetDependingOnFoldingState() - } - - override fun onResume() { - super.onResume() - - if (coreContext.core.callsNb == 0) { - Log.w("[Call Activity] Resuming but no call found...") - if (isTaskRoot) { - // When resuming app from recent tasks make sure MainActivity will be launched if there is no call - val intent = Intent() - intent.setClass(this, MainActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - startActivity(intent) - } else { - finish() - } - } else { - coreContext.removeCallOverlay() - } - - if (corePreferences.fullScreenCallUI) { - hideSystemUI() - window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> - if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) { - GlobalScope.launch { - delay(2000) - withContext(Dispatchers.Main) { - hideSystemUI() - } - } - } - } - } - } - - override fun onPause() { - val core = coreContext.core - if (core.callsNb > 0) { - coreContext.createCallOverlay() - } - - super.onPause() - } - - override fun onDestroy() { - coreContext.core.nativeVideoWindowId = null - coreContext.core.nativePreviewWindowId = null - - super.onDestroy() - } - - override fun onUserLeaveHint() { - super.onUserLeaveHint() - - if (coreContext.isVideoCallOrConferenceActive()) { - Compatibility.enterPipMode(this) - } - } - - override fun onPictureInPictureModeChanged( - isInPictureInPictureMode: Boolean, - newConfig: Configuration - ) { - if (isInPictureInPictureMode) { - viewModel.areControlsHidden.value = true - } - - if (corePreferences.hideCameraPreviewInPipMode) { - viewModel.isVideoPreviewHidden.value = isInPictureInPictureMode - } else { - viewModel.isVideoPreviewResizedForPip.value = isInPictureInPictureMode - } - } - - override fun getTheme(): Resources.Theme { - val theme = super.getTheme() - if (corePreferences.fullScreenCallUI) { - theme.applyStyle(R.style.FullScreenTheme, true) - } - return theme - } - - private fun hideSystemUI() { - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_IMMERSIVE or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - } - - private fun updateConstraintSetDependingOnFoldingState() { - val feature = foldingFeature ?: return - val constraintLayout = binding.constraintLayout - val set = ConstraintSet() - set.clone(constraintLayout) - - if (feature.state == FoldingFeature.State.HALF_OPENED && viewModel.videoEnabled.value == true) { - set.setGuidelinePercent(R.id.hinge_top, 0.5f) - set.setGuidelinePercent(R.id.hinge_bottom, 0.5f) - viewModel.disable(true) - } else { - set.setGuidelinePercent(R.id.hinge_top, 0f) - set.setGuidelinePercent(R.id.hinge_bottom, 1f) - viewModel.disable(false) - } - - set.applyTo(constraintLayout) - } -} diff --git a/app/src/main/java/org/linphone/activities/call/IncomingCallActivity.kt b/app/src/main/java/org/linphone/activities/call/IncomingCallActivity.kt deleted file mode 100644 index c150de7a0..000000000 --- a/app/src/main/java/org/linphone/activities/call/IncomingCallActivity.kt +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call - -import android.Manifest -import android.annotation.TargetApi -import android.app.KeyguardManager -import android.content.Context -import android.content.Intent -import android.content.pm.ActivityInfo -import android.content.pm.PackageManager -import android.os.Bundle -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericActivity -import org.linphone.activities.call.viewmodels.IncomingCallViewModel -import org.linphone.activities.call.viewmodels.IncomingCallViewModelFactory -import org.linphone.activities.main.MainActivity -import org.linphone.compatibility.Compatibility -import org.linphone.core.Call -import org.linphone.core.tools.Log -import org.linphone.databinding.CallIncomingActivityBinding -import org.linphone.mediastream.Version -import org.linphone.utils.PermissionHelper - -class IncomingCallActivity : GenericActivity() { - private lateinit var binding: CallIncomingActivityBinding - private lateinit var viewModel: IncomingCallViewModel - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - Compatibility.setShowWhenLocked(this, true) - Compatibility.setTurnScreenOn(this, true) - // Leaks on API 27+: https://stackoverflow.com/questions/60477120/keyguardmanager-memory-leak - Compatibility.requestDismissKeyguard(this) - - binding = DataBindingUtil.setContentView(this, R.layout.call_incoming_activity) - binding.lifecycleOwner = this - - val incomingCall: Call? = findIncomingCall() - if (incomingCall == null) { - Log.e("[Incoming Call Activity] Couldn't find call in state Incoming") - if (isTaskRoot) { - // When resuming app from recent tasks make sure MainActivity will be launched if there is no call - val intent = Intent() - intent.setClass(this, MainActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - startActivity(intent) - } else { - finish() - } - return - } - - viewModel = ViewModelProvider( - this, - IncomingCallViewModelFactory(incomingCall) - )[IncomingCallViewModel::class.java] - binding.viewModel = viewModel - - viewModel.callEndedEvent.observe( - this - ) { - it.consume { - Log.i("[Incoming Call Activity] Call ended, finish activity") - finish() - } - } - - viewModel.earlyMediaVideoEnabled.observe( - this - ) { - if (it) { - Log.i("[Incoming Call Activity] Early media video being received, set native window id") - coreContext.core.nativeVideoWindowId = binding.remoteVideoSurface - } - } - - val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - val keyguardLocked = keyguardManager.isKeyguardLocked - viewModel.screenLocked.value = keyguardLocked - if (keyguardLocked) { - // Forbid screen rotation to prevent keyguard to show up above incoming call view - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED - } - - binding.buttons.setViewModel(viewModel) - - if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) { - checkPermissions() - } - } - - override fun onResume() { - super.onResume() - - val incomingCall: Call? = findIncomingCall() - if (incomingCall == null) { - Log.e("[Incoming Call Activity] Couldn't find call in state Incoming") - if (isTaskRoot) { - // When resuming app from recent tasks make sure MainActivity will be launched if there is no call - val intent = Intent() - intent.setClass(this, MainActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - startActivity(intent) - } else { - finish() - } - } - } - - @TargetApi(Version.API23_MARSHMALLOW_60) - private fun checkPermissions() { - val permissionsRequiredList = arrayListOf() - if (!PermissionHelper.get().hasRecordAudioPermission()) { - Log.i("[Incoming Call Activity] Asking for RECORD_AUDIO permission") - permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO) - } - - if (viewModel.call.currentParams.isVideoEnabled && !PermissionHelper.get().hasCameraPermission()) { - Log.i("[Incoming Call Activity] Asking for CAMERA permission") - permissionsRequiredList.add(Manifest.permission.CAMERA) - } - - if (permissionsRequiredList.isNotEmpty()) { - val permissionsRequired = arrayOfNulls(permissionsRequiredList.size) - permissionsRequiredList.toArray(permissionsRequired) - requestPermissions(permissionsRequired, 0) - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - for (i in permissions.indices) { - when (permissions[i]) { - Manifest.permission.RECORD_AUDIO -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Incoming Call Activity] RECORD_AUDIO permission has been granted") - } - Manifest.permission.CAMERA -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Incoming Call Activity] CAMERA permission has been granted") - coreContext.core.reloadVideoDevices() - } - } - } - } - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - - private fun findIncomingCall(): Call? { - for (call in coreContext.core.calls) { - if (call.state == Call.State.IncomingReceived || - call.state == Call.State.IncomingEarlyMedia - ) { - return call - } - } - return null - } -} diff --git a/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt b/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt deleted file mode 100644 index 5dc2a1676..000000000 --- a/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call - -import android.Manifest -import android.animation.ValueAnimator -import android.annotation.TargetApi -import android.content.Intent -import android.content.pm.PackageManager -import android.os.Bundle -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.ViewModelProvider -import com.google.android.flexbox.FlexboxLayout -import org.linphone.LinphoneApplication -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.call.viewmodels.CallViewModel -import org.linphone.activities.call.viewmodels.CallViewModelFactory -import org.linphone.activities.call.viewmodels.ControlsViewModel -import org.linphone.activities.main.MainActivity -import org.linphone.core.Call -import org.linphone.core.tools.Log -import org.linphone.databinding.CallOutgoingActivityBinding -import org.linphone.mediastream.Version -import org.linphone.utils.PermissionHelper - -class OutgoingCallActivity : ProximitySensorActivity() { - private lateinit var binding: CallOutgoingActivityBinding - private lateinit var viewModel: CallViewModel - private lateinit var controlsViewModel: ControlsViewModel - - // We have to use lateinit here because we need to compute the screen width first - private lateinit var numpadAnimator: ValueAnimator - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - binding = DataBindingUtil.setContentView(this, R.layout.call_outgoing_activity) - binding.lifecycleOwner = this - - val outgoingCall: Call? = findOutgoingCall() - if (outgoingCall == null) { - Log.e("[Outgoing Call Activity] Couldn't find call in state Outgoing") - if (isTaskRoot) { - // When resuming app from recent tasks make sure MainActivity will be launched if there is no call - val intent = Intent() - intent.setClass(this, MainActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - startActivity(intent) - } else { - finish() - } - return - } - - viewModel = ViewModelProvider( - this, - CallViewModelFactory(outgoingCall) - )[CallViewModel::class.java] - binding.viewModel = viewModel - - controlsViewModel = ViewModelProvider(this)[ControlsViewModel::class.java] - binding.controlsViewModel = controlsViewModel - - viewModel.callEndedEvent.observe( - this - ) { - it.consume { - Log.i("[Outgoing Call Activity] Call ended, finish activity") - finish() - } - } - - viewModel.callConnectedEvent.observe( - this - ) { - it.consume { - Log.i("[Outgoing Call Activity] Call connected, finish activity") - finish() - } - } - - controlsViewModel.isSpeakerSelected.observe( - this - ) { - enableProximitySensor(!it) - } - - controlsViewModel.askAudioRecordPermissionEvent.observe( - this - ) { - it.consume { permission -> - requestPermissions(arrayOf(permission), 0) - } - } - - controlsViewModel.askCameraPermissionEvent.observe( - this - ) { - it.consume { permission -> - requestPermissions(arrayOf(permission), 0) - } - } - - controlsViewModel.toggleNumpadEvent.observe( - this - ) { - it.consume { open -> - if (this::numpadAnimator.isInitialized) { - if (open) { - numpadAnimator.start() - } else { - numpadAnimator.reverse() - } - } - } - } - - if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) { - checkPermissions() - } - } - - override fun onStart() { - super.onStart() - initNumpadLayout() - } - - override fun onStop() { - numpadAnimator.end() - super.onStop() - } - - override fun onResume() { - super.onResume() - - val outgoingCall: Call? = findOutgoingCall() - if (outgoingCall == null) { - Log.e("[Outgoing Call Activity] Couldn't find call in state Outgoing") - if (isTaskRoot) { - // When resuming app from recent tasks make sure MainActivity will be launched if there is no call - val intent = Intent() - intent.setClass(this, MainActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - startActivity(intent) - } else { - finish() - } - } - } - - @TargetApi(Version.API23_MARSHMALLOW_60) - private fun checkPermissions() { - val permissionsRequiredList = arrayListOf() - if (!PermissionHelper.get().hasRecordAudioPermission()) { - Log.i("[Outgoing Call Activity] Asking for RECORD_AUDIO permission") - permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO) - } - if (viewModel.call.currentParams.isVideoEnabled && !PermissionHelper.get().hasCameraPermission()) { - Log.i("[Outgoing Call Activity] Asking for CAMERA permission") - permissionsRequiredList.add(Manifest.permission.CAMERA) - } - if (permissionsRequiredList.isNotEmpty()) { - val permissionsRequired = arrayOfNulls(permissionsRequiredList.size) - permissionsRequiredList.toArray(permissionsRequired) - requestPermissions(permissionsRequired, 0) - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - for (i in permissions.indices) { - when (permissions[i]) { - Manifest.permission.RECORD_AUDIO -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Outgoing Call Activity] RECORD_AUDIO permission has been granted") - controlsViewModel.updateMuteMicState() - } - Manifest.permission.CAMERA -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Outgoing Call Activity] CAMERA permission has been granted") - coreContext.core.reloadVideoDevices() - } - } - } - } - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - - private fun findOutgoingCall(): Call? { - for (call in coreContext.core.calls) { - if (call.state == Call.State.OutgoingInit || - call.state == Call.State.OutgoingProgress || - call.state == Call.State.OutgoingRinging || - call.state == Call.State.OutgoingEarlyMedia - ) { - return call - } - } - return null - } - - private fun initNumpadLayout() { - val screenWidth = coreContext.screenWidth - numpadAnimator = ValueAnimator.ofFloat(screenWidth, 0f).apply { - addUpdateListener { - val value = it.animatedValue as Float - findViewById(R.id.numpad)?.translationX = -value - duration = if (LinphoneApplication.corePreferences.enableAnimations) 500 else 0 - } - } - // Hide the numpad here as we can't set the translationX property on include tag in layout - if (this::controlsViewModel.isInitialized && controlsViewModel.numpadVisibility.value == false) { - findViewById(R.id.numpad)?.translationX = -screenWidth - } - } -} diff --git a/app/src/main/java/org/linphone/activities/call/VideoZoomHelper.kt b/app/src/main/java/org/linphone/activities/call/VideoZoomHelper.kt deleted file mode 100644 index 820bd3e95..000000000 --- a/app/src/main/java/org/linphone/activities/call/VideoZoomHelper.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call - -import android.content.Context -import android.view.GestureDetector -import android.view.MotionEvent -import android.view.ScaleGestureDetector -import android.view.View -import kotlin.math.max -import kotlin.math.min -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.Call - -class VideoZoomHelper(context: Context, private var videoDisplayView: View) : GestureDetector.SimpleOnGestureListener() { - private var scaleDetector: ScaleGestureDetector - - private var zoomFactor = 1f - private var zoomCenterX = 0f - private var zoomCenterY = 0f - - init { - val gestureDetector = GestureDetector(context, this) - - scaleDetector = ScaleGestureDetector( - context, - object : - ScaleGestureDetector.SimpleOnScaleGestureListener() { - override fun onScale(detector: ScaleGestureDetector): Boolean { - zoomFactor *= detector.scaleFactor - // Don't let the object get too small or too large. - // Zoom to make the video fill the screen vertically - val portraitZoomFactor = videoDisplayView.height.toFloat() / (3 * videoDisplayView.width / 4) - // Zoom to make the video fill the screen horizontally - val landscapeZoomFactor = videoDisplayView.width.toFloat() / (3 * videoDisplayView.height / 4) - zoomFactor = max(0.1f, min(zoomFactor, max(portraitZoomFactor, landscapeZoomFactor))) - - val currentCall: Call? = coreContext.core.currentCall - if (currentCall != null) { - currentCall.zoom(zoomFactor, zoomCenterX, zoomCenterY) - return true - } - - return false - } - } - ) - - videoDisplayView.setOnTouchListener { _, event -> - val currentZoomFactor = zoomFactor - scaleDetector.onTouchEvent(event) - - if (currentZoomFactor != zoomFactor) { - // We did scale, prevent touch event from going further - return@setOnTouchListener true - } - - // If true, gesture detected, prevent touch event from going further - // Otherwise it seems we didn't use event, - // allow it to be dispatched somewhere else - gestureDetector.onTouchEvent(event) - } - } - - override fun onScroll( - e1: MotionEvent, - e2: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - val currentCall: Call? = coreContext.core.currentCall - if (currentCall != null) { - if (zoomFactor > 1) { - // Video is zoomed, slide is used to change center of zoom - if (distanceX > 0 && zoomCenterX < 1) { - zoomCenterX += 0.01f - } else if (distanceX < 0 && zoomCenterX > 0) { - zoomCenterX -= 0.01f - } - - if (distanceY < 0 && zoomCenterY < 1) { - zoomCenterY += 0.01f - } else if (distanceY > 0 && zoomCenterY > 0) { - zoomCenterY -= 0.01f - } - - if (zoomCenterX > 1) zoomCenterX = 1f - if (zoomCenterX < 0) zoomCenterX = 0f - if (zoomCenterY > 1) zoomCenterY = 1f - if (zoomCenterY < 0) zoomCenterY = 0f - - currentCall.zoom(zoomFactor, zoomCenterX, zoomCenterY) - return true - } - } - - return false - } - - override fun onDoubleTap(e: MotionEvent?): Boolean { - val currentCall: Call? = coreContext.core.currentCall - if (currentCall != null) { - if (zoomFactor == 1f) { - // Zoom to make the video fill the screen vertically - val portraitZoomFactor = videoDisplayView.height.toFloat() / (3 * videoDisplayView.width / 4) - // Zoom to make the video fill the screen horizontally - val landscapeZoomFactor = videoDisplayView.width.toFloat() / (3 * videoDisplayView.height / 4) - zoomFactor = max(portraitZoomFactor, landscapeZoomFactor) - } else { - resetZoom() - } - - currentCall.zoom(zoomFactor, zoomCenterX, zoomCenterY) - return true - } - - return false - } - - private fun resetZoom() { - zoomFactor = 1f - zoomCenterY = 0.5f - zoomCenterX = zoomCenterY - } -} diff --git a/app/src/main/java/org/linphone/activities/call/data/ConferenceParticipantData.kt b/app/src/main/java/org/linphone/activities/call/data/ConferenceParticipantData.kt deleted file mode 100644 index 2af5a7a8a..000000000 --- a/app/src/main/java/org/linphone/activities/call/data/ConferenceParticipantData.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.data - -import androidx.lifecycle.MutableLiveData -import org.linphone.contact.GenericContactData -import org.linphone.core.Conference -import org.linphone.core.Participant -import org.linphone.core.tools.Log - -class ConferenceParticipantData( - private val conference: Conference, - val participant: Participant -) : - GenericContactData(participant.address) { - private val isAdmin = MutableLiveData() - val isMeAdmin = MutableLiveData() - - init { - isAdmin.value = participant.isAdmin - isMeAdmin.value = conference.me.isAdmin - Log.i("[Conference Participant VM] Participant ${participant.address.asStringUriOnly()} is ${if (participant.isAdmin) "admin" else "not admin"}") - Log.i("[Conference Participant VM] Me is ${if (conference.me.isAdmin) "admin" else "not admin"} and is ${if (conference.me.isFocus) "focus" else "not focus"}") - } - - fun removeFromConference() { - Log.i("[Conference Participant VM] Removing participant ${participant.address.asStringUriOnly()} from conference $conference") - conference.removeParticipant(participant) - } -} diff --git a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt deleted file mode 100644 index 3765a4183..000000000 --- a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.fragments - -import android.Manifest -import android.animation.ValueAnimator -import android.annotation.TargetApi -import android.app.Dialog -import android.content.Intent -import android.content.pm.PackageManager.PERMISSION_GRANTED -import android.os.Bundle -import android.os.SystemClock -import android.view.View -import androidx.lifecycle.ViewModelProvider -import com.google.android.flexbox.FlexboxLayout -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.call.viewmodels.CallsViewModel -import org.linphone.activities.call.viewmodels.ConferenceViewModel -import org.linphone.activities.call.viewmodels.ControlsViewModel -import org.linphone.activities.call.viewmodels.SharedCallViewModel -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.core.Call -import org.linphone.core.tools.Log -import org.linphone.databinding.CallControlsFragmentBinding -import org.linphone.mediastream.Version -import org.linphone.utils.AppUtils -import org.linphone.utils.DialogUtils -import org.linphone.utils.Event -import org.linphone.utils.PermissionHelper - -class ControlsFragment : GenericFragment() { - private lateinit var callsViewModel: CallsViewModel - private lateinit var controlsViewModel: ControlsViewModel - private lateinit var conferenceViewModel: ConferenceViewModel - private lateinit var sharedViewModel: SharedCallViewModel - - private var dialog: Dialog? = null - - override fun getLayoutId(): Int = R.layout.call_controls_fragment - - // We have to use lateinit here because we need to compute the screen width first - private lateinit var numpadAnimator: ValueAnimator - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - useMaterialSharedAxisXForwardAnimation = false - - sharedViewModel = requireActivity().run { - ViewModelProvider(this)[SharedCallViewModel::class.java] - } - - callsViewModel = requireActivity().run { - ViewModelProvider(this)[CallsViewModel::class.java] - } - binding.viewModel = callsViewModel - - controlsViewModel = requireActivity().run { - ViewModelProvider(this)[ControlsViewModel::class.java] - } - binding.controlsViewModel = controlsViewModel - - conferenceViewModel = requireActivity().run { - ViewModelProvider(this)[ConferenceViewModel::class.java] - } - binding.conferenceViewModel = conferenceViewModel - - callsViewModel.currentCallViewModel.observe( - viewLifecycleOwner - ) { - if (it != null) { - binding.activeCallTimer.base = - SystemClock.elapsedRealtime() - (1000 * it.call.duration) // Linphone timestamps are in seconds - binding.activeCallTimer.start() - } - } - - callsViewModel.noMoreCallEvent.observe( - viewLifecycleOwner - ) { - it.consume { - requireActivity().finish() - } - } - - callsViewModel.askWriteExternalStoragePermissionEvent.observe( - viewLifecycleOwner - ) { - it.consume { - if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { - Log.i("[Controls Fragment] Asking for WRITE_EXTERNAL_STORAGE permission") - requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 2) - } - } - } - - callsViewModel.callUpdateEvent.observe( - viewLifecycleOwner - ) { - it.consume { call -> - if (call.state == Call.State.StreamsRunning) { - dialog?.dismiss() - } else if (call.state == Call.State.UpdatedByRemote) { - if (coreContext.core.isVideoCaptureEnabled || coreContext.core.isVideoDisplayEnabled) { - if (call.currentParams.isVideoEnabled != call.remoteParams?.isVideoEnabled) { - showCallVideoUpdateDialog(call) - } - } else { - Log.w("[Controls Fragment] Video display & capture are disabled, don't show video dialog") - } - } - } - } - - controlsViewModel.chatClickedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val intent = Intent() - intent.setClass(requireContext(), MainActivity::class.java) - intent.putExtra("Chat", true) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } - } - - controlsViewModel.addCallClickedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val intent = Intent() - intent.setClass(requireContext(), MainActivity::class.java) - intent.putExtra("Dialer", true) - intent.putExtra("Transfer", false) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } - } - - controlsViewModel.transferCallClickedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val intent = Intent() - intent.setClass(requireContext(), MainActivity::class.java) - intent.putExtra("Dialer", true) - intent.putExtra("Transfer", true) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } - } - - controlsViewModel.askAudioRecordPermissionEvent.observe( - viewLifecycleOwner - ) { - it.consume { permission -> - Log.i("[Controls Fragment] Asking for $permission permission") - requestPermissions(arrayOf(permission), 0) - } - } - - controlsViewModel.askCameraPermissionEvent.observe( - viewLifecycleOwner - ) { - it.consume { permission -> - Log.i("[Controls Fragment] Asking for $permission permission") - requestPermissions(arrayOf(permission), 1) - } - } - - controlsViewModel.toggleNumpadEvent.observe( - viewLifecycleOwner - ) { - it.consume { open -> - if (this::numpadAnimator.isInitialized) { - if (open) { - numpadAnimator.start() - } else { - numpadAnimator.reverse() - } - } - } - } - - controlsViewModel.somethingClickedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - sharedViewModel.resetHiddenInterfaceTimerInVideoCallEvent.value = Event(true) - } - } - - if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) { - checkPermissions() - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - onBackPressedCallback.isEnabled = false - } - - override fun onStart() { - super.onStart() - initNumpadLayout() - } - - override fun onStop() { - numpadAnimator.end() - super.onStop() - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - for (i in permissions.indices) { - when (permissions[i]) { - Manifest.permission.RECORD_AUDIO -> if (grantResults[i] == PERMISSION_GRANTED) { - Log.i("[Controls Fragment] RECORD_AUDIO permission has been granted") - controlsViewModel.updateMuteMicState() - } - Manifest.permission.CAMERA -> if (grantResults[i] == PERMISSION_GRANTED) { - Log.i("[Controls Fragment] CAMERA permission has been granted") - coreContext.core.reloadVideoDevices() - } - } - } - } else if (requestCode == 1) { - if (grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) { - Log.i("[Controls Fragment] CAMERA permission has been granted") - coreContext.core.reloadVideoDevices() - controlsViewModel.toggleVideo() - } - } else if (requestCode == 2 && grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) { - callsViewModel.takeScreenshot() - } - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - - @TargetApi(Version.API23_MARSHMALLOW_60) - private fun checkPermissions() { - val permissionsRequiredList = arrayListOf() - - if (!PermissionHelper.get().hasRecordAudioPermission()) { - Log.i("[Controls Fragment] Asking for RECORD_AUDIO permission") - permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO) - } - - if (coreContext.isVideoCallOrConferenceActive() && !PermissionHelper.get().hasCameraPermission()) { - Log.i("[Controls Fragment] Asking for CAMERA permission") - permissionsRequiredList.add(Manifest.permission.CAMERA) - } - - if (permissionsRequiredList.isNotEmpty()) { - val permissionsRequired = arrayOfNulls(permissionsRequiredList.size) - permissionsRequiredList.toArray(permissionsRequired) - requestPermissions(permissionsRequired, 0) - } - } - - private fun showCallVideoUpdateDialog(call: Call) { - val viewModel = DialogViewModel(AppUtils.getString(R.string.call_video_update_requested_dialog)) - dialog = DialogUtils.getDialog(requireContext(), viewModel) - - viewModel.showCancelButton( - { - callsViewModel.answerCallVideoUpdateRequest(call, false) - dialog?.dismiss() - }, - getString(R.string.dialog_decline) - ) - - viewModel.showOkButton( - { - callsViewModel.answerCallVideoUpdateRequest(call, true) - dialog?.dismiss() - }, - getString(R.string.dialog_accept) - ) - - dialog?.show() - } - - private fun initNumpadLayout() { - val screenWidth = coreContext.screenWidth - numpadAnimator = ValueAnimator.ofFloat(screenWidth, 0f).apply { - addUpdateListener { - val value = it.animatedValue as Float - view?.findViewById(R.id.numpad)?.translationX = -value - duration = if (corePreferences.enableAnimations) 500 else 0 - } - } - // Hide the numpad here as we can't set the translationX property on include tag in layout - if (controlsViewModel.numpadVisibility.value == false) { - view?.findViewById(R.id.numpad)?.translationX = -screenWidth - } - } -} diff --git a/app/src/main/java/org/linphone/activities/call/fragments/VideoRenderingFragment.kt b/app/src/main/java/org/linphone/activities/call/fragments/VideoRenderingFragment.kt deleted file mode 100644 index c7f980c52..000000000 --- a/app/src/main/java/org/linphone/activities/call/fragments/VideoRenderingFragment.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.fragments - -import android.os.Bundle -import android.view.MotionEvent -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.call.VideoZoomHelper -import org.linphone.activities.call.viewmodels.CallsViewModel -import org.linphone.activities.call.viewmodels.ConferenceViewModel -import org.linphone.activities.call.viewmodels.ControlsFadingViewModel -import org.linphone.databinding.CallVideoFragmentBinding - -class VideoRenderingFragment : GenericFragment() { - private lateinit var controlsFadingViewModel: ControlsFadingViewModel - private lateinit var callsViewModel: CallsViewModel - private lateinit var conferenceViewModel: ConferenceViewModel - - private var previewX: Float = 0f - private var previewY: Float = 0f - private lateinit var videoZoomHelper: VideoZoomHelper - - override fun getLayoutId(): Int = R.layout.call_video_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = this - - controlsFadingViewModel = requireActivity().run { - ViewModelProvider(this)[ControlsFadingViewModel::class.java] - } - binding.controlsFadingViewModel = controlsFadingViewModel - - callsViewModel = requireActivity().run { - ViewModelProvider(this)[CallsViewModel::class.java] - } - - conferenceViewModel = requireActivity().run { - ViewModelProvider(this)[ConferenceViewModel::class.java] - } - binding.conferenceViewModel = conferenceViewModel - - coreContext.core.nativeVideoWindowId = binding.remoteVideoSurface - coreContext.core.nativePreviewWindowId = binding.localPreviewVideoSurface - - binding.setPreviewTouchListener { v, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - previewX = v.x - event.rawX - previewY = v.y - event.rawY - } - MotionEvent.ACTION_MOVE -> { - v.animate() - .x(event.rawX + previewX) - .y(event.rawY + previewY) - .setDuration(0) - .start() - } - else -> { - v.performClick() - false - } - } - true - } - - videoZoomHelper = VideoZoomHelper(requireContext(), binding.remoteVideoSurface) - } -} diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt deleted file mode 100644 index 59216ead9..000000000 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import java.util.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.compatibility.Compatibility -import org.linphone.contact.GenericContactViewModel -import org.linphone.core.Call -import org.linphone.core.CallListenerStub -import org.linphone.core.Factory -import org.linphone.core.tools.Log -import org.linphone.utils.Event -import org.linphone.utils.FileUtils -import org.linphone.utils.LinphoneUtils - -class CallViewModelFactory(private val call: Call) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return CallViewModel(call) as T - } -} - -open class CallViewModel(val call: Call) : GenericContactViewModel(call.remoteAddress) { - val address: String by lazy { - LinphoneUtils.getDisplayableAddress(call.remoteAddress) - } - - val isPaused = MutableLiveData() - - val isOutgoingEarlyMedia = MutableLiveData() - - val callEndedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val callConnectedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private var timer: Timer? = null - - private val listener = object : CallListenerStub() { - override fun onStateChanged(call: Call, state: Call.State, message: String) { - if (call != this@CallViewModel.call) return - - isPaused.value = state == Call.State.Paused - isOutgoingEarlyMedia.value = state == Call.State.OutgoingEarlyMedia - - if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) { - timer?.cancel() - callEndedEvent.value = Event(true) - - if (state == Call.State.Error) { - Log.e("[Call View Model] Error state reason is ${call.reason}") - } - } else if (call.state == Call.State.Connected) { - callConnectedEvent.value = Event(true) - } else if (call.state == Call.State.StreamsRunning) { - // Stop call update timer once user has accepted or declined call update - timer?.cancel() - } else if (call.state == Call.State.UpdatedByRemote) { - // User has 30 secs to accept or decline call update - // Dialog to accept or decline is handled by CallsViewModel & ControlsFragment - startTimer(call) - } - } - - override fun onSnapshotTaken(call: Call, filePath: String) { - Log.i("[Call View Model] Snapshot taken, saved at $filePath") - val content = Factory.instance().createContent() - content.filePath = filePath - content.type = "image" - content.subtype = "jpeg" - content.name = filePath.substring(filePath.indexOf("/") + 1) - - viewModelScope.launch { - if (Compatibility.addImageToMediaStore(coreContext.context, content)) { - Log.i("[Call View Model] Adding snapshot ${content.name} to Media Store terminated") - } else { - Log.e("[Call View Model] Something went wrong while copying file to Media Store...") - } - } - } - } - - init { - call.addListener(listener) - - isPaused.value = call.state == Call.State.Paused - isOutgoingEarlyMedia.value = call.state == Call.State.OutgoingEarlyMedia - } - - override fun onCleared() { - destroy() - super.onCleared() - } - - fun destroy() { - call.removeListener(listener) - } - - fun terminateCall() { - coreContext.terminateCall(call) - } - - fun pause() { - call.pause() - } - - fun resume() { - call.resume() - } - - fun takeScreenshot() { - if (call.currentParams.isVideoEnabled) { - val fileName = System.currentTimeMillis().toString() + ".jpeg" - call.takeVideoSnapshot(FileUtils.getFileStoragePath(fileName).absolutePath) - } - } - - private fun startTimer(call: Call) { - timer?.cancel() - - timer = Timer("Call update timeout") - timer?.schedule( - object : TimerTask() { - override fun run() { - // Decline call update - viewModelScope.launch { - withContext(Dispatchers.Main) { - coreContext.answerCallVideoUpdateRequest(call, false) - } - } - } - }, - 30000 - ) - } -} diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt deleted file mode 100644 index e6165e651..000000000 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event -import org.linphone.utils.PermissionHelper - -class CallsViewModel : ViewModel() { - val currentCallViewModel = MutableLiveData() - - val noActiveCall = MutableLiveData() - - val callPausedByRemote = MutableLiveData() - - val pausedCalls = MutableLiveData>() - - val noMoreCallEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val callUpdateEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val askWriteExternalStoragePermissionEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val listener = object : CoreListenerStub() { - override fun onCallStateChanged(core: Core, call: Call, state: Call.State, message: String) { - Log.i("[Calls VM] Call state changed: $state") - callPausedByRemote.value = (state == Call.State.PausedByRemote) and (call.conference == null) - - val currentCall = core.currentCall - noActiveCall.value = currentCall == null - if (currentCall == null) { - currentCallViewModel.value?.destroy() - } else if (currentCallViewModel.value?.call != currentCall) { - val viewModel = CallViewModel(currentCall) - currentCallViewModel.value = viewModel - } - - if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) { - if (core.callsNb == 0) { - noMoreCallEvent.value = Event(true) - } else { - removeCallFromPausedListIfPresent(call) - } - } else if (state == Call.State.Paused) { - addCallToPausedList(call) - } else if (state == Call.State.Resuming) { - removeCallFromPausedListIfPresent(call) - } else if (call.state == Call.State.UpdatedByRemote) { - // If the correspondent asks to turn on video while audio call, - // defer update until user has chosen whether to accept it or not - val remoteVideo = call.remoteParams?.isVideoEnabled ?: false - val localVideo = call.currentParams.isVideoEnabled - val autoAccept = call.core.videoActivationPolicy.automaticallyAccept - if (remoteVideo && !localVideo && !autoAccept) { - if (coreContext.core.isVideoCaptureEnabled || coreContext.core.isVideoDisplayEnabled) { - call.deferUpdate() - callUpdateEvent.value = Event(call) - } else { - coreContext.answerCallVideoUpdateRequest(call, false) - } - } - } else if (state == Call.State.StreamsRunning) { - callUpdateEvent.value = Event(call) - } - } - } - - init { - coreContext.core.addListener(listener) - - val currentCall = coreContext.core.currentCall - noActiveCall.value = currentCall == null - if (currentCall != null) { - currentCallViewModel.value?.destroy() - - val viewModel = CallViewModel(currentCall) - currentCallViewModel.value = viewModel - } - - callPausedByRemote.value = currentCall?.state == Call.State.PausedByRemote - - for (call in coreContext.core.calls) { - if (call.state == Call.State.Paused || call.state == Call.State.Pausing) { - addCallToPausedList(call) - } - } - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - - super.onCleared() - } - - fun answerCallVideoUpdateRequest(call: Call, accept: Boolean) { - coreContext.answerCallVideoUpdateRequest(call, accept) - } - - fun takeScreenshot() { - if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { - askWriteExternalStoragePermissionEvent.value = Event(true) - } else { - currentCallViewModel.value?.takeScreenshot() - } - } - - private fun addCallToPausedList(call: Call) { - if (call.conference != null) return // Conference will be displayed as paused, no need to display the call as well - - val list = arrayListOf() - list.addAll(pausedCalls.value.orEmpty()) - - for (pausedCallViewModel in list) { - if (pausedCallViewModel.call == call) { - return - } - } - - val viewModel = CallViewModel(call) - list.add(viewModel) - pausedCalls.value = list - } - - private fun removeCallFromPausedListIfPresent(call: Call) { - val list = arrayListOf() - list.addAll(pausedCalls.value.orEmpty()) - - for (pausedCallViewModel in list) { - if (pausedCallViewModel.call == call) { - pausedCallViewModel.destroy() - list.remove(pausedCallViewModel) - break - } - } - - pausedCalls.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/ConferenceViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/ConferenceViewModel.kt deleted file mode 100644 index 164a067bb..000000000 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/ConferenceViewModel.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.call.data.ConferenceParticipantData -import org.linphone.core.* -import org.linphone.core.tools.Log - -class ConferenceViewModel : ViewModel() { - val isConferencePaused = MutableLiveData() - - val isMeConferenceFocus = MutableLiveData() - - val conferenceAddress = MutableLiveData
() - - val conferenceParticipants = MutableLiveData>() - - val isInConference = MutableLiveData() - - private val conferenceListener = object : ConferenceListenerStub() { - override fun onParticipantAdded(conference: Conference, participant: Participant) { - if (conference.isMe(participant.address)) { - Log.i("[Conference VM] Entered conference") - isConferencePaused.value = false - } else { - Log.i("[Conference VM] Participant added") - updateParticipantsList(conference) - } - } - - override fun onParticipantRemoved(conference: Conference, participant: Participant) { - if (conference.isMe(participant.address)) { - Log.i("[Conference VM] Left conference") - isConferencePaused.value = true - } else { - Log.i("[Conference VM] Participant removed") - updateParticipantsList(conference) - } - } - - override fun onParticipantAdminStatusChanged( - conference: Conference, - participant: Participant - ) { - Log.i("[Conference VM] Participant admin status changed") - updateParticipantsList(conference) - } - } - - private val listener = object : CoreListenerStub() { - override fun onConferenceStateChanged( - core: Core, - conference: Conference, - state: Conference.State - ) { - Log.i("[Conference VM] Conference state changed: $state") - isConferencePaused.value = !conference.isIn - - if (state == Conference.State.Instantiated) { - conference.addListener(conferenceListener) - } else if (state == Conference.State.Created) { - updateParticipantsList(conference) - isMeConferenceFocus.value = conference.me.isFocus - conferenceAddress.value = conference.conferenceAddress - } else if (state == Conference.State.Terminated || state == Conference.State.TerminationFailed) { - isInConference.value = false - conference.removeListener(conferenceListener) - conferenceParticipants.value = arrayListOf() - } - } - } - - init { - coreContext.core.addListener(listener) - - isConferencePaused.value = coreContext.core.conference?.isIn != true - isMeConferenceFocus.value = false - conferenceParticipants.value = arrayListOf() - isInConference.value = false - - val conference = coreContext.core.conference - if (conference != null) { - conference.addListener(conferenceListener) - isMeConferenceFocus.value = conference.me.isFocus - updateParticipantsList(conference) - } - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - - super.onCleared() - } - - fun pauseConference() { - val defaultProxyConfig = coreContext.core.defaultProxyConfig - val localAddress = defaultProxyConfig?.identityAddress - val participants = arrayOf
() - val remoteConference = coreContext.core.searchConference(null, localAddress, conferenceAddress.value, participants) - val localConference = coreContext.core.searchConference(null, conferenceAddress.value, conferenceAddress.value, participants) - val conference = remoteConference ?: localConference - - if (conference != null) { - Log.i("[Conference VM] Leaving conference with address ${conferenceAddress.value?.asStringUriOnly()} temporarily") - conference.leave() - } else { - Log.w("[Conference VM] Unable to find conference with address ${conferenceAddress.value?.asStringUriOnly()}") - } - } - - fun resumeConference() { - val defaultProxyConfig = coreContext.core.defaultProxyConfig - val localAddress = defaultProxyConfig?.identityAddress - val participants = arrayOf
() - val remoteConference = coreContext.core.searchConference(null, localAddress, conferenceAddress.value, participants) - val localConference = coreContext.core.searchConference(null, conferenceAddress.value, conferenceAddress.value, participants) - val conference = remoteConference ?: localConference - - if (conference != null) { - Log.i("[Conference VM] Entering again conference with address ${conferenceAddress.value?.asStringUriOnly()}") - conference.enter() - } else { - Log.w("[Conference VM] Unable to find conference with address ${conferenceAddress.value?.asStringUriOnly()}") - } - } - - private fun updateParticipantsList(conference: Conference) { - val participants = arrayListOf() - for (participant in conference.participantList) { - Log.i("[Conference VM] Participant found: ${participant.address.asStringUriOnly()}") - val viewModel = ConferenceParticipantData(conference, participant) - participants.add(viewModel) - } - conferenceParticipants.value = participants - isInConference.value = participants.isNotEmpty() - } -} diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsFadingViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsFadingViewModel.kt deleted file mode 100644 index 1d231a8db..000000000 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsFadingViewModel.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.viewmodels - -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import java.util.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.AudioDevice -import org.linphone.core.Call -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.core.tools.Log - -class ControlsFadingViewModel : ViewModel() { - val areControlsHidden = MutableLiveData() - - val isVideoPreviewHidden = MutableLiveData() - val isVideoPreviewResizedForPip = MutableLiveData() - - val videoEnabled = MutableLiveData() - - val proximitySensorEnabled: MediatorLiveData = MediatorLiveData() - - private val nonEarpieceOutputAudioDevice = MutableLiveData() - - private var timer: Timer? = null - - private var disabled: Boolean = false - - private val listener = object : CoreListenerStub() { - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State, - message: String - ) { - if (state == Call.State.StreamsRunning || state == Call.State.Updating || state == Call.State.UpdatedByRemote) { - val isVideoCall = coreContext.isVideoCallOrConferenceActive() - Log.i("[Controls Fading] Call is in state $state, video is ${if (isVideoCall) "enabled" else "disabled"}") - if (isVideoCall) { - videoEnabled.value = true - startTimer() - } else { - videoEnabled.value = false - stopTimer() - } - } - } - - override fun onAudioDeviceChanged(core: Core, audioDevice: AudioDevice) { - if (audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) { - Log.i("[Controls Fading] Output audio device changed to: ${audioDevice.id}") - nonEarpieceOutputAudioDevice.value = audioDevice.type != AudioDevice.Type.Earpiece - } - } - } - - init { - coreContext.core.addListener(listener) - - areControlsHidden.value = false - isVideoPreviewHidden.value = false - isVideoPreviewResizedForPip.value = false - nonEarpieceOutputAudioDevice.value = coreContext.core.outputAudioDevice?.type != AudioDevice.Type.Earpiece - - val isVideoCall = coreContext.isVideoCallOrConferenceActive() - videoEnabled.value = isVideoCall - if (isVideoCall) { - startTimer() - } - - proximitySensorEnabled.value = shouldEnableProximitySensor() - proximitySensorEnabled.addSource(videoEnabled) { - proximitySensorEnabled.value = shouldEnableProximitySensor() - } - proximitySensorEnabled.addSource(nonEarpieceOutputAudioDevice) { - proximitySensorEnabled.value = shouldEnableProximitySensor() - } - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - stopTimer() - - super.onCleared() - } - - fun showMomentarily() { - stopTimer() - startTimer() - } - - fun disable(disable: Boolean) { - disabled = disable - if (disabled) { - stopTimer() - } else { - startTimer() - } - } - - private fun shouldEnableProximitySensor(): Boolean { - return !(videoEnabled.value ?: false) && !(nonEarpieceOutputAudioDevice.value ?: false) - } - - private fun stopTimer() { - timer?.cancel() - - areControlsHidden.value = false - } - - private fun startTimer() { - timer?.cancel() - if (disabled) return - - timer = Timer("Hide UI controls scheduler") - timer?.schedule( - object : TimerTask() { - override fun run() { - viewModelScope.launch { - withContext(Dispatchers.Main) { - val videoEnabled = coreContext.isVideoCallOrConferenceActive() - areControlsHidden.postValue(videoEnabled) - } - } - } - }, - 3000 - ) - } -} diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt deleted file mode 100644 index 094cd64c2..000000000 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.viewmodels - -import android.Manifest -import android.animation.ValueAnimator -import android.content.Context -import android.os.Vibrator -import android.view.animation.LinearInterpolator -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlin.math.max -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.dialer.NumpadDigitListener -import org.linphone.compatibility.Compatibility -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.* -import org.linphone.utils.Event - -class ControlsViewModel : ViewModel() { - val isMicrophoneMuted = MutableLiveData() - - val isMuteMicrophoneEnabled = MutableLiveData() - - val isSpeakerSelected = MutableLiveData() - - val isBluetoothHeadsetSelected = MutableLiveData() - - val isVideoAvailable = MutableLiveData() - - val isVideoEnabled = MutableLiveData() - - val isVideoUpdateInProgress = MutableLiveData() - - val showSwitchCamera = MutableLiveData() - - val isPauseEnabled = MutableLiveData() - - val isRecording = MutableLiveData() - - val isConferencingAvailable = MutableLiveData() - - val unreadMessagesCount = MutableLiveData() - - val numpadVisibility = MutableLiveData() - - val optionsVisibility = MutableLiveData() - - val audioRoutesSelected = MutableLiveData() - - val audioRoutesEnabled = MutableLiveData() - - val takeScreenshotEnabled = MutableLiveData() - - val chatClickedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val addCallClickedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val transferCallClickedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val askAudioRecordPermissionEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val askCameraPermissionEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val somethingClickedEvent = MutableLiveData>() - - val chatAllowed = !corePreferences.disableChat - - private val vibrator = coreContext.context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator - - val chatUnreadCountTranslateY = MutableLiveData() - - val optionsMenuTranslateY = MutableLiveData() - - val audioRoutesMenuTranslateY = MutableLiveData() - - val toggleNumpadEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val bounceAnimator: ValueAnimator by lazy { - ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.tabs_fragment_unread_count_bounce_offset), 0f).apply { - addUpdateListener { - val value = it.animatedValue as Float - chatUnreadCountTranslateY.value = -value - } - interpolator = LinearInterpolator() - duration = 250 - repeatMode = ValueAnimator.REVERSE - repeatCount = ValueAnimator.INFINITE - } - } - - private val optionsMenuAnimator: ValueAnimator by lazy { - ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.call_options_menu_translate_y), 0f).apply { - addUpdateListener { - val value = it.animatedValue as Float - optionsMenuTranslateY.value = value - } - duration = if (corePreferences.enableAnimations) 500 else 0 - } - } - - private val audioRoutesMenuAnimator: ValueAnimator by lazy { - ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.call_audio_routes_menu_translate_y), 0f).apply { - addUpdateListener { - val value = it.animatedValue as Float - audioRoutesMenuTranslateY.value = value - } - duration = if (corePreferences.enableAnimations) 500 else 0 - } - } - - val onKeyClick: NumpadDigitListener = object : NumpadDigitListener { - override fun handleClick(key: Char) { - coreContext.core.playDtmf(key, 1) - somethingClickedEvent.value = Event(true) - coreContext.core.currentCall?.sendDtmf(key) - - if (vibrator.hasVibrator() && corePreferences.dtmfKeypadVibration) { - Compatibility.eventVibration(vibrator) - } - } - - override fun handleLongClick(key: Char): Boolean { - return true - } - } - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onMessageReceived(core: Core, chatRoom: ChatRoom, message: ChatMessage) { - updateUnreadChatCount() - } - - override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) { - updateUnreadChatCount() - } - - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State, - message: String - ) { - if (state == Call.State.StreamsRunning) { - isVideoUpdateInProgress.value = false - } - - if (coreContext.isVideoCallOrConferenceActive() && !PermissionHelper.get().hasCameraPermission()) { - askCameraPermissionEvent.value = Event(Manifest.permission.CAMERA) - } - - updateUI() - } - - override fun onAudioDeviceChanged(core: Core, audioDevice: AudioDevice) { - Log.i("[Call] Audio device changed: ${audioDevice.deviceName}") - updateSpeakerState() - updateBluetoothHeadsetState() - } - - override fun onAudioDevicesListUpdated(core: Core) { - Log.i("[Call] Audio devices list updated") - val wasBluetoothPreviouslyAvailable = audioRoutesEnabled.value == true - updateAudioRoutesState() - - if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) { - AudioRouteUtils.routeAudioToHeadset() - } else if (!wasBluetoothPreviouslyAvailable && corePreferences.routeAudioToBluetoothIfAvailable) { - // Only attempt to route audio to bluetooth automatically when bluetooth device is connected - if (AudioRouteUtils.isBluetoothAudioRouteAvailable()) { - AudioRouteUtils.routeAudioToBluetooth() - } - } - } - } - - init { - coreContext.core.addListener(listener) - val currentCall = coreContext.core.currentCall - - updateMuteMicState() - updateAudioRelated() - updateUnreadChatCount() - - numpadVisibility.value = false - optionsVisibility.value = false - audioRoutesSelected.value = false - - isRecording.value = currentCall?.isRecording - isVideoUpdateInProgress.value = false - showSwitchCamera.value = coreContext.showSwitchCameraButton() - - chatUnreadCountTranslateY.value = 0f - optionsMenuTranslateY.value = AppUtils.getDimension(R.dimen.call_options_menu_translate_y) - audioRoutesMenuTranslateY.value = AppUtils.getDimension(R.dimen.call_audio_routes_menu_translate_y) - - takeScreenshotEnabled.value = corePreferences.showScreenshotButton - - updateUI() - if (corePreferences.enableAnimations) bounceAnimator.start() - } - - override fun onCleared() { - if (corePreferences.enableAnimations) bounceAnimator.end() - optionsMenuAnimator.end() - audioRoutesMenuAnimator.end() - coreContext.core.removeListener(listener) - super.onCleared() - } - - fun updateUnreadChatCount() { - unreadMessagesCount.value = coreContext.core.unreadChatMessageCountFromActiveLocals - } - - fun toggleMuteMicrophone() { - if (!PermissionHelper.get().hasRecordAudioPermission()) { - askAudioRecordPermissionEvent.value = Event(Manifest.permission.RECORD_AUDIO) - return - } - - somethingClickedEvent.value = Event(true) - val micEnabled = coreContext.core.isMicEnabled - coreContext.core.isMicEnabled = !micEnabled - updateMuteMicState() - } - - fun toggleSpeaker() { - somethingClickedEvent.value = Event(true) - if (AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed()) { - forceEarpieceAudioRoute() - } else { - forceSpeakerAudioRoute() - } - } - - fun switchCamera() { - somethingClickedEvent.value = Event(true) - coreContext.switchCamera() - } - - fun terminateCall() { - val core = coreContext.core - when { - core.currentCall != null -> core.currentCall?.terminate() - core.conference?.isIn == true -> core.terminateConference() - else -> core.terminateAllCalls() - } - } - - fun toggleVideo() { - if (!PermissionHelper.get().hasCameraPermission()) { - askCameraPermissionEvent.value = Event(Manifest.permission.CAMERA) - return - } - - val core = coreContext.core - val currentCall = core.currentCall - val conference = core.conference - - if (conference != null && conference.isIn) { - val params = core.createConferenceParams() - val videoEnabled = conference.currentParams.isVideoEnabled - params.isVideoEnabled = !videoEnabled - Log.i("[Controls VM] Conference current param for video is $videoEnabled") - conference.updateParams(params) - } else if (currentCall != null) { - val state = currentCall.state - if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) - return - - isVideoUpdateInProgress.value = true - val params = core.createCallParams(currentCall) - params?.isVideoEnabled = !currentCall.currentParams.isVideoEnabled - currentCall.update(params) - } - } - - fun toggleOptionsMenu() { - somethingClickedEvent.value = Event(true) - optionsVisibility.value = optionsVisibility.value != true - if (optionsVisibility.value == true) { - optionsMenuAnimator.start() - } else { - optionsMenuAnimator.reverse() - } - } - - fun toggleNumpadVisibility() { - somethingClickedEvent.value = Event(true) - numpadVisibility.value = numpadVisibility.value != true - toggleNumpadEvent.value = Event(numpadVisibility.value ?: true) - } - - fun toggleRoutesMenu() { - somethingClickedEvent.value = Event(true) - audioRoutesSelected.value = audioRoutesSelected.value != true - if (audioRoutesSelected.value == true) { - audioRoutesMenuAnimator.start() - } else { - audioRoutesMenuAnimator.reverse() - } - } - - fun toggleRecording(closeMenu: Boolean) { - somethingClickedEvent.value = Event(true) - - val core = coreContext.core - val currentCall = core.currentCall - val conference = core.conference - - when { - currentCall != null -> { - if (currentCall.isRecording) { - currentCall.stopRecording() - } else { - currentCall.startRecording() - } - isRecording.value = currentCall.isRecording - } - conference != null -> { - val path = LinphoneUtils.getRecordingFilePathForConference() - if (conference.isRecording) { - conference.stopRecording() - } else { - conference.startRecording(path) - } - isRecording.value = conference.isRecording - } - else -> { - isRecording.value = false - } - } - - if (closeMenu) toggleOptionsMenu() - } - - fun onChatClicked() { - chatClickedEvent.value = Event(true) - } - - fun onAddCallClicked() { - addCallClickedEvent.value = Event(true) - toggleOptionsMenu() - } - - fun onTransferCallClicked() { - transferCallClickedEvent.value = Event(true) - toggleOptionsMenu() - } - - fun startConference() { - somethingClickedEvent.value = Event(true) - - val core = coreContext.core - val currentCallVideoEnabled = core.currentCall?.currentParams?.isVideoEnabled ?: false - - val params = core.createConferenceParams() - params.isVideoEnabled = currentCallVideoEnabled - Log.i("[Call] Setting videoEnabled to [$currentCallVideoEnabled] in conference params") - - val conference = core.conference ?: core.createConferenceWithParams(params) - conference?.addParticipants(core.calls) - - toggleOptionsMenu() - } - - fun forceEarpieceAudioRoute() { - somethingClickedEvent.value = Event(true) - if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) { - Log.i("[Call] Headset found, route audio to it instead of earpiece") - AudioRouteUtils.routeAudioToHeadset() - } else { - AudioRouteUtils.routeAudioToEarpiece() - } - } - - fun forceSpeakerAudioRoute() { - somethingClickedEvent.value = Event(true) - AudioRouteUtils.routeAudioToSpeaker() - } - - fun forceBluetoothAudioRoute() { - somethingClickedEvent.value = Event(true) - AudioRouteUtils.routeAudioToBluetooth() - } - - fun updateMuteMicState() { - isMicrophoneMuted.value = !PermissionHelper.get().hasRecordAudioPermission() || !coreContext.core.isMicEnabled - isMuteMicrophoneEnabled.value = coreContext.core.currentCall != null || coreContext.core.conference?.isIn == true - } - - private fun updateAudioRelated() { - updateSpeakerState() - updateBluetoothHeadsetState() - updateAudioRoutesState() - } - - private fun updateUI() { - val currentCall = coreContext.core.currentCall - updateVideoAvailable() - updateVideoEnabled() - isPauseEnabled.value = currentCall != null && !currentCall.mediaInProgress() - isMuteMicrophoneEnabled.value = currentCall != null || coreContext.core.conference?.isIn == true - updateConferenceState() - - // Check periodically until mediaInProgress is false - if (currentCall != null && currentCall.mediaInProgress()) { - viewModelScope.launch { - delay(1000) - updateUI() - } - } - } - - private fun updateSpeakerState() { - isSpeakerSelected.value = AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed() - } - - private fun updateAudioRoutesState() { - val bluetoothDeviceAvailable = AudioRouteUtils.isBluetoothAudioRouteAvailable() - audioRoutesEnabled.value = bluetoothDeviceAvailable - if (!bluetoothDeviceAvailable) { - audioRoutesSelected.value = false - } - } - - private fun updateBluetoothHeadsetState() { - isBluetoothHeadsetSelected.value = AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed() - } - - private fun updateVideoAvailable() { - val core = coreContext.core - val currentCall = core.currentCall - isVideoAvailable.value = (core.isVideoCaptureEnabled || core.isVideoPreviewEnabled) && - ( - (currentCall != null && !currentCall.mediaInProgress()) || - core.conference?.isIn == true - ) - } - - private fun updateVideoEnabled() { - val enabled = coreContext.isVideoCallOrConferenceActive() - isVideoEnabled.value = enabled - } - - private fun updateConferenceState() { - val core = coreContext.core - isConferencingAvailable.value = core.callsNb > max(1, core.conference?.participantCount ?: 0) && !core.soundResourcesLocked() - } -} diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/IncomingCallViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/IncomingCallViewModel.kt deleted file mode 100644 index f1dc0bfb6..000000000 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/IncomingCallViewModel.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.core.* -import org.linphone.utils.Event - -class IncomingCallViewModelFactory(private val call: Call) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return IncomingCallViewModel(call) as T - } -} - -class IncomingCallViewModel(call: Call) : CallViewModel(call) { - val screenLocked = MutableLiveData() - - val earlyMediaVideoEnabled = MutableLiveData() - - val inviteWithVideo = MutableLiveData() - - private val listener = object : CoreListenerStub() { - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State, - message: String - ) { - if (core.callsNb == 0) { - callEndedEvent.value = Event(true) - } - } - } - - init { - coreContext.core.addListener(listener) - - screenLocked.value = false - inviteWithVideo.value = call.remoteParams?.isVideoEnabled == true && coreContext.core.videoActivationPolicy.automaticallyAccept - earlyMediaVideoEnabled.value = corePreferences.acceptEarlyMedia && - call.state == Call.State.IncomingEarlyMedia && - call.currentParams.isVideoEnabled - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - super.onCleared() - } - - fun answer(doAction: Boolean) { - if (doAction) coreContext.answerCall(call) - } - - fun decline(doAction: Boolean) { - if (doAction) coreContext.declineCall(call) - } -} diff --git a/app/src/main/java/org/linphone/activities/call/views/AnswerDeclineIncomingCallButtons.kt b/app/src/main/java/org/linphone/activities/call/views/AnswerDeclineIncomingCallButtons.kt deleted file mode 100644 index b030ea7a8..000000000 --- a/app/src/main/java/org/linphone/activities/call/views/AnswerDeclineIncomingCallButtons.kt +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.views - -import android.animation.AnimatorSet -import android.animation.ObjectAnimator -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.View -import android.view.View.OnTouchListener -import android.view.animation.LinearInterpolator -import android.widget.LinearLayout -import androidx.databinding.DataBindingUtil -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.call.viewmodels.IncomingCallViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.CallIncomingAnswerDeclineButtonsBinding - -class AnswerDeclineIncomingCallButtons : LinearLayout { - private lateinit var binding: CallIncomingAnswerDeclineButtonsBinding - private var mBegin = false - private var mDeclineX = 0f - private var mAnswerX = 0f - private var mOldSize = 0f - - private val mAnswerTouchListener = OnTouchListener { view, motionEvent -> - val curX: Float - - when (motionEvent.action) { - MotionEvent.ACTION_DOWN -> { - binding.declineButton.visibility = View.GONE - mAnswerX = motionEvent.x - view.width - mBegin = true - mOldSize = 0f - } - MotionEvent.ACTION_MOVE -> { - curX = motionEvent.x - view.width - view.scrollBy((mAnswerX - curX).toInt(), view.scrollY) - mOldSize -= mAnswerX - curX - mAnswerX = curX - if (mOldSize < -25) mBegin = false - if (curX < (width / 4) - view.width && !mBegin) { - binding.viewModel?.answer(true) - } - } - MotionEvent.ACTION_UP -> { - binding.declineButton.visibility = View.VISIBLE - view.scrollTo(0, view.scrollY) - } - } - true - } - private val mDeclineTouchListener = OnTouchListener { view, motionEvent -> - val curX: Float - - when (motionEvent.action) { - MotionEvent.ACTION_DOWN -> { - binding.answerButton.visibility = View.GONE - mDeclineX = motionEvent.x - } - MotionEvent.ACTION_MOVE -> { - curX = motionEvent.x - view.scrollBy((mDeclineX - curX).toInt(), view.scrollY) - mDeclineX = curX - if (curX > 3 * width / 4) { - binding.viewModel?.decline(true) - } - } - MotionEvent.ACTION_UP -> { - binding.answerButton.visibility = View.VISIBLE - view.scrollTo(0, view.scrollY) - } - } - true - } - - constructor(context: Context) : super(context) { - init(context) - } - - constructor(context: Context, attrs: AttributeSet) : super( - context, - attrs - ) { - init(context) - } - - constructor( - context: Context, - attrs: AttributeSet, - defStyleAttr: Int - ) : super(context, attrs, defStyleAttr) { - init(context) - } - - fun setViewModel(viewModel: IncomingCallViewModel) { - binding.viewModel = viewModel - - updateSlideMode() - } - - private fun init(context: Context) { - binding = DataBindingUtil.inflate( - LayoutInflater.from(context), R.layout.call_incoming_answer_decline_buttons, this, true - ) - - updateSlideMode() - configureAnimation() - } - - private fun updateSlideMode() { - val slideMode = binding.viewModel?.screenLocked?.value == true - Log.i("[Call Incoming Decline Button] Slide mode is $slideMode") - if (slideMode) { - binding.answerButton.setOnTouchListener(mAnswerTouchListener) - binding.declineButton.setOnTouchListener(mDeclineTouchListener) - } - } - - private fun configureAnimation() { - if (!corePreferences.enableAnimations) return - - val accept1 = ObjectAnimator.ofFloat(binding.arrowAccept1, "alpha", 1f, 0.6f, 0.4f, 1f).apply { - repeatCount = ObjectAnimator.INFINITE - repeatMode = ObjectAnimator.RESTART - } - - val accept2 = ObjectAnimator.ofFloat(binding.arrowAccept2, "alpha", 0.6f, 1f, 0.4f, 0.6f).apply { - repeatCount = ObjectAnimator.INFINITE - repeatMode = ObjectAnimator.RESTART - } - - val accept3 = ObjectAnimator.ofFloat(binding.arrowAccept3, "alpha", 0.4f, 0.6f, 1f, 0.4f).apply { - repeatCount = ObjectAnimator.INFINITE - repeatMode = ObjectAnimator.RESTART - } - - val hangup1 = ObjectAnimator.ofFloat(binding.arrowHangup1, "alpha", 1f, 0.6f, 0.4f, 1f).apply { - repeatCount = ObjectAnimator.INFINITE - repeatMode = ObjectAnimator.RESTART - } - - val hangup2 = ObjectAnimator.ofFloat(binding.arrowHangup2, "alpha", 0.6f, 1f, 0.4f, 0.6f).apply { - repeatCount = ObjectAnimator.INFINITE - repeatMode = ObjectAnimator.RESTART - } - - val hangup3 = ObjectAnimator.ofFloat(binding.arrowHangup3, "alpha", 0.4f, 0.6f, 1f, 0.4f).apply { - repeatCount = ObjectAnimator.INFINITE - repeatMode = ObjectAnimator.RESTART - } - - AnimatorSet().apply { - duration = 2000 - interpolator = LinearInterpolator() - playTogether(accept1, accept2, accept3, hangup1, hangup2, hangup3) - start() - } - } -} diff --git a/app/src/main/java/org/linphone/activities/call/views/ConferenceParticipantView.kt b/app/src/main/java/org/linphone/activities/call/views/ConferenceParticipantView.kt deleted file mode 100644 index 16499951f..000000000 --- a/app/src/main/java/org/linphone/activities/call/views/ConferenceParticipantView.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.views - -import android.content.Context -import android.os.SystemClock -import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.LinearLayout -import androidx.databinding.DataBindingUtil -import org.linphone.R -import org.linphone.activities.call.data.ConferenceParticipantData -import org.linphone.core.tools.Log -import org.linphone.databinding.CallConferenceParticipantBinding - -class ConferenceParticipantView : LinearLayout { - private lateinit var binding: CallConferenceParticipantBinding - - constructor(context: Context) : super(context) { - init(context) - } - - constructor(context: Context, attrs: AttributeSet) : super( - context, - attrs - ) { - init(context) - } - - constructor( - context: Context, - attrs: AttributeSet, - defStyleAttr: Int - ) : super(context, attrs, defStyleAttr) { - init(context) - } - - fun init(context: Context) { - binding = DataBindingUtil.inflate( - LayoutInflater.from(context), R.layout.call_conference_participant, this, true - ) - } - - fun setData(data: ConferenceParticipantData) { - binding.data = data - - val currentTimeSecs = System.currentTimeMillis() - val participantTime = data.participant.creationTime * 1000 // Linphone timestamps are in seconds - val diff = currentTimeSecs - participantTime - Log.i("[Conference Participant] Participant joined conference at $participantTime == ${diff / 1000} seconds ago.") - binding.callTimer.base = SystemClock.elapsedRealtime() - diff - binding.callTimer.start() - } -} diff --git a/app/src/main/java/org/linphone/activities/call/views/PausedCallView.kt b/app/src/main/java/org/linphone/activities/call/views/PausedCallView.kt deleted file mode 100644 index a5c9ffce4..000000000 --- a/app/src/main/java/org/linphone/activities/call/views/PausedCallView.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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. - * - * This program 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 this program. If not, see . - */ -package org.linphone.activities.call.views - -import android.content.Context -import android.os.SystemClock -import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.LinearLayout -import androidx.databinding.DataBindingUtil -import org.linphone.R -import org.linphone.activities.call.viewmodels.CallViewModel -import org.linphone.databinding.CallPausedBinding - -class PausedCallView : LinearLayout { - private lateinit var binding: CallPausedBinding - - constructor(context: Context) : super(context) { - init(context) - } - - constructor(context: Context, attrs: AttributeSet) : super( - context, - attrs - ) { - init(context) - } - - constructor( - context: Context, - attrs: AttributeSet, - defStyleAttr: Int - ) : super(context, attrs, defStyleAttr) { - init(context) - } - - fun init(context: Context) { - binding = DataBindingUtil.inflate( - LayoutInflater.from(context), R.layout.call_paused, this, true - ) - } - - fun setViewModel(viewModel: CallViewModel) { - binding.viewModel = viewModel - - binding.callTimer.base = - SystemClock.elapsedRealtime() - (1000 * viewModel.call.duration) // Linphone timestamps are in seconds - binding.callTimer.start() - } -} diff --git a/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt b/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt index 08d3d8d1e..9dfa3387c 100644 --- a/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt +++ b/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt @@ -119,7 +119,7 @@ class ChatBubbleActivity : GenericActivity() { adapter.registerAdapterDataObserver(observer) // Disable context menu on each message - adapter.disableContextMenu() + adapter.disableAdvancedContextMenuOptions() adapter.openContentEvent.observe( this diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt index 12a69892f..83ebbe442 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt @@ -40,11 +40,11 @@ import org.linphone.activities.main.chat.data.EventData import org.linphone.activities.main.chat.data.EventLogData import org.linphone.activities.main.chat.data.OnContentClickedListener import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.core.ChatMessage -import org.linphone.core.ChatRoomCapabilities -import org.linphone.core.Content -import org.linphone.core.EventLog -import org.linphone.databinding.* +import org.linphone.core.* +import org.linphone.databinding.ChatEventListCellBinding +import org.linphone.databinding.ChatMessageListCellBinding +import org.linphone.databinding.ChatMessageLongPressMenuBindingImpl +import org.linphone.databinding.ChatUnreadMessagesListHeaderBinding import org.linphone.utils.AppUtils import org.linphone.utils.Event import org.linphone.utils.HeaderAdapter @@ -90,6 +90,10 @@ class ChatMessagesListAdapter( MutableLiveData>() } + val callConferenceEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + val scrollToChatMessageEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -102,9 +106,13 @@ class ChatMessagesListAdapter( override fun onSipAddressClicked(sipUri: String) { sipUriClickedEvent.value = Event(sipUri) } + + override fun onCallConference(address: String, subject: String?) { + callConferenceEvent.value = Event(Pair(address, subject)) + } } - private var contextMenuDisabled: Boolean = false + private var advancedContextMenuOptionsDisabled: Boolean = false private var unreadMessagesCount: Int = 0 private var firstUnreadMessagePosition: Int = -1 @@ -170,8 +178,8 @@ class ChatMessagesListAdapter( return binding.root } - fun disableContextMenu() { - contextMenuDisabled = true + fun disableAdvancedContextMenuOptions() { + advancedContextMenuOptionsDisabled = true } fun setUnreadMessageCount(count: Int, forceUpdate: Boolean) { @@ -269,8 +277,6 @@ class ChatMessagesListAdapter( executePendingBindings() - if (contextMenuDisabled) return - setContextMenuClickListener { val popupView: ChatMessageLongPressMenuBindingImpl = DataBindingUtil.inflate( LayoutInflater.from(root.context), @@ -292,7 +298,10 @@ class ChatMessagesListAdapter( popupView.copyTextHidden = true totalSize -= itemSize } - if (chatMessage.isOutgoing || chatMessageViewModel.contact.value != null) { + if (chatMessage.isOutgoing || + chatMessageViewModel.contact.value != null || + advancedContextMenuOptionsDisabled + ) { popupView.addToContactsHidden = true totalSize -= itemSize } @@ -300,6 +309,10 @@ class ChatMessagesListAdapter( popupView.replyHidden = true totalSize -= itemSize } + if (advancedContextMenuOptionsDisabled) { + popupView.forwardHidden = true + totalSize -= itemSize + } // When using WRAP_CONTENT instead of real size, fails to place the // popup window above if not enough space is available below diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt index 8c81a62cf..df0e86c83 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt @@ -27,8 +27,12 @@ import android.text.style.UnderlineSpan import android.widget.Toast import androidx.lifecycle.MutableLiveData import androidx.media.AudioFocusRequestCompat +import java.io.BufferedReader +import java.io.FileReader +import java.lang.StringBuilder import java.text.SimpleDateFormat import java.util.* +import java.util.concurrent.TimeUnit import kotlinx.coroutines.* import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn @@ -40,6 +44,7 @@ import org.linphone.core.tools.Log import org.linphone.utils.AppUtils import org.linphone.utils.FileUtils import org.linphone.utils.ImageUtils +import org.linphone.utils.TimestampUtils class ChatMessageContentData( private val chatMessage: ChatMessage, @@ -56,6 +61,7 @@ class ChatMessageContentData( val isPdf = MutableLiveData() val isGenericFile = MutableLiveData() val isVoiceRecording = MutableLiveData() + val isConferenceSchedule = MutableLiveData() val fileName = MutableLiveData() val filePath = MutableLiveData() @@ -71,6 +77,15 @@ class ChatMessageContentData( val voiceRecordPlayingPosition = MutableLiveData() val isVoiceRecordPlaying = MutableLiveData() + val conferenceSubject = MutableLiveData() + val conferenceDescription = MutableLiveData() + val conferenceParticipantCount = MutableLiveData() + val conferenceDate = MutableLiveData() + val conferenceTime = MutableLiveData() + val conferenceDuration = MutableLiveData() + var conferenceAddress = MutableLiveData() + val showDuration = MutableLiveData() + val isAlone: Boolean get() { var count = 0 @@ -203,6 +218,13 @@ class ChatMessageContentData( spannable.setSpan(UnderlineSpan(), 0, spannable.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) downloadLabel.value = spannable + isImage.value = false + isVideo.value = false + isAudio.value = false + isPdf.value = false + isVoiceRecording.value = false + isConferenceSchedule.value = false + if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) { val path = if (isFileEncrypted) { Log.i("[Content] Content is encrypted, requesting plain file path") @@ -212,21 +234,27 @@ class ChatMessageContentData( } downloadable.value = content.filePath.orEmpty().isEmpty() + val isVoiceRecord = content.isVoiceRecording + isVoiceRecording.value = isVoiceRecord + + val isConferenceIcs = content.isIcalendar + isConferenceSchedule.value = isConferenceIcs + if (path.isNotEmpty()) { Log.i("[Content] Found displayable content: $path") - val isVoiceRecord = content.isVoiceRecording filePath.value = path isImage.value = FileUtils.isExtensionImage(path) isVideo.value = FileUtils.isExtensionVideo(path) && !isVoiceRecord isAudio.value = FileUtils.isExtensionAudio(path) && !isVoiceRecord isPdf.value = FileUtils.isExtensionPdf(path) - isVoiceRecording.value = isVoiceRecord if (isVoiceRecord) { val duration = content.fileDuration // duration is in ms voiceRecordDuration.value = duration formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration) - Log.i("[Voice Recording] Duration is ${voiceRecordDuration.value} ($duration)") + Log.i("[Content] Voice recording duration is ${voiceRecordDuration.value} ($duration)") + } else if (isConferenceIcs) { + parseConferenceInvite(content) } if (isVideo.value == true) { @@ -234,6 +262,9 @@ class ChatMessageContentData( videoPreview.postValue(ImageUtils.getVideoPreview(path)) } } + } else if (isConferenceIcs) { + Log.i("[Content] Found content with icalendar file") + parseConferenceInvite(content) } else { Log.w("[Content] Found ${if (content.isFile) "file" else "file transfer"} content with empty path...") isImage.value = false @@ -241,22 +272,81 @@ class ChatMessageContentData( isAudio.value = false isPdf.value = false isVoiceRecording.value = false + isConferenceSchedule.value = false } - } else { + } else if (content.isFileTransfer) { downloadable.value = true isImage.value = FileUtils.isExtensionImage(fileName.value!!) isVideo.value = FileUtils.isExtensionVideo(fileName.value!!) isAudio.value = FileUtils.isExtensionAudio(fileName.value!!) isPdf.value = FileUtils.isExtensionPdf(fileName.value!!) isVoiceRecording.value = false + isConferenceSchedule.value = false + } else if (content.isIcalendar) { + Log.i("[Content] Found content with icalendar body") + isConferenceSchedule.value = true + parseConferenceInvite(content) + } else { + Log.w("[Content] Found content that's neither a file or a file transfer") } - isGenericFile.value = !isPdf.value!! && !isAudio.value!! && !isVideo.value!! && !isImage.value!! && !isVoiceRecording.value!! + isGenericFile.value = !isPdf.value!! && !isAudio.value!! && !isVideo.value!! && !isImage.value!! && !isVoiceRecording.value!! && !isConferenceSchedule.value!! downloadEnabled.value = !chatMessage.isFileTransferInProgress downloadProgressInt.value = 0 downloadProgressString.value = "0%" } + private fun parseConferenceInvite(content: Content) { + val conferenceInfo = Factory.instance().createConferenceInfoFromIcalendarContent(content) + val conferenceUri = conferenceInfo?.uri?.asStringUriOnly() + if (conferenceInfo != null && conferenceUri != null) { + conferenceAddress.value = conferenceUri!! + Log.i("[Content] Created conference info from ICS with address ${conferenceAddress.value}") + conferenceSubject.value = conferenceInfo.subject + conferenceDescription.value = conferenceInfo.description + + conferenceDate.value = TimestampUtils.dateToString(conferenceInfo.dateTime) + conferenceTime.value = TimestampUtils.timeToString(conferenceInfo.dateTime) + + val minutes = conferenceInfo.duration + val hours = TimeUnit.MINUTES.toHours(minutes.toLong()) + val remainMinutes = minutes - TimeUnit.HOURS.toMinutes(hours).toInt() + conferenceDuration.value = TimestampUtils.durationToString(hours.toInt(), remainMinutes) + showDuration.value = minutes > 0 + + conferenceParticipantCount.value = String.format(AppUtils.getString(R.string.conference_invite_participants_count), conferenceInfo.participants.size + 1) // +1 for organizer + } else if (conferenceInfo == null) { + if (content.filePath != null) { + try { + val br = BufferedReader(FileReader(content.filePath)) + var line: String? + val textBuilder = StringBuilder() + while (br.readLine().also { line = it } != null) { + textBuilder.append(line) + textBuilder.append('\n') + } + br.close() + Log.e("[Content] Failed to create conference info from ICS file [${content.filePath}]: $textBuilder") + } catch (e: Exception) { + Log.e("[Content] Failed to read content of ICS file [${content.filePath}]: $e") + } + } else { + Log.e("[Content] Failed to create conference info from ICS: ${content.utf8Text}") + } + } else if (conferenceInfo.uri == null) { + Log.e("[Content] Failed to find the conference URI in conference info [$conferenceInfo]") + } + } + + fun callConferenceAddress() { + val address = conferenceAddress.value + if (address == null) { + Log.e("[Content] Can't call null conference address!") + return + } + listener?.onCallConference(address, conferenceSubject.value) + } + /** Voice recording specifics */ fun playVoiceRecording() { @@ -359,4 +449,6 @@ interface OnContentClickedListener { fun onContentClicked(content: Content) fun onSipAddressClicked(sipUri: String) + + fun onCallConference(address: String, subject: String?) } diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt index 3f588d008..993a74c99 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt @@ -177,7 +177,7 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes val contentsList = chatMessage.contents for (index in contentsList.indices) { val content = contentsList[index] - if (content.isFileTransfer || content.isFile) { + if (content.isFileTransfer || content.isFile || content.isIcalendar) { val data = ChatMessageContentData(chatMessage, index) data.listener = contentListener list.add(data) @@ -194,6 +194,8 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes } } ).build(spannable) + } else { + Log.e("[Chat Message Data] Unexpected content with type: ${content.type}/${content.subtype}") } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListChildData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListChildData.kt index d4a51b937..3052871d9 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListChildData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListChildData.kt @@ -44,6 +44,6 @@ class DevicesListChildData(private val device: ParticipantDevice) { } fun onClick() { - coreContext.startCall(device.address, true) + coreContext.startCall(device.address, forceZRTP = true) } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListGroupData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListGroupData.kt index 3d1e624be..bc3141943 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListGroupData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListGroupData.kt @@ -68,6 +68,6 @@ class DevicesListGroupData(private val participant: Participant) : GenericContac } fun onClick() { - if (device?.address != null) coreContext.startCall(device.address, true) + if (device?.address != null) coreContext.startCall(device.address, forceZRTP = true) } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/ChatRoomCreationFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/ChatRoomCreationFragment.kt index 95b08e3bf..069d90a24 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/ChatRoomCreationFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/ChatRoomCreationFragment.kt @@ -25,16 +25,16 @@ import android.view.View import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager -import org.linphone.LinphoneApplication +import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.chat.adapters.ChatRoomCreationContactsAdapter import org.linphone.activities.main.chat.viewmodels.ChatRoomCreationViewModel import org.linphone.activities.main.fragments.SecureFragment import org.linphone.activities.main.viewmodels.SharedMainViewModel import org.linphone.activities.navigateToChatRoom import org.linphone.activities.navigateToEmptyChatRoom import org.linphone.activities.navigateToGroupInfo +import org.linphone.contact.ContactsSelectionAdapter import org.linphone.core.tools.Log import org.linphone.databinding.ChatRoomCreationFragmentBinding import org.linphone.utils.AppUtils @@ -44,7 +44,7 @@ import org.linphone.utils.PermissionHelper class ChatRoomCreationFragment : SecureFragment() { private lateinit var viewModel: ChatRoomCreationViewModel private lateinit var sharedViewModel: SharedMainViewModel - private lateinit var adapter: ChatRoomCreationContactsAdapter + private lateinit var adapter: ContactsSelectionAdapter override fun getLayoutId(): Int = R.layout.chat_room_creation_fragment @@ -68,9 +68,9 @@ class ChatRoomCreationFragment : SecureFragment binding.viewModel = viewModel - adapter = ChatRoomCreationContactsAdapter(viewLifecycleOwner) - adapter.groupChatEnabled = viewModel.createGroupChat.value == true - adapter.updateSecurity(viewModel.isEncrypted.value == true) + adapter = ContactsSelectionAdapter(viewLifecycleOwner) + adapter.setGroupChatCapabilityRequired(viewModel.createGroupChat.value == true) + adapter.setLimeCapabilityRequired(viewModel.isEncrypted.value == true) binding.contactsList.adapter = adapter val layoutManager = LinearLayoutManager(activity) @@ -101,7 +101,7 @@ class ChatRoomCreationFragment : SecureFragment viewModel.isEncrypted.observe( viewLifecycleOwner ) { - adapter.updateSecurity(it) + adapter.setLimeCapabilityRequired(it) } viewModel.sipContactsSelected.observe( @@ -152,7 +152,7 @@ class ChatRoomCreationFragment : SecureFragment navigateToGroupInfo() } - viewModel.onErrorEvent.observe( + viewModel.onMessageToNotifyEvent.observe( viewLifecycleOwner ) { it.consume { messageResourceId -> @@ -185,8 +185,8 @@ class ChatRoomCreationFragment : SecureFragment val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED if (granted) { Log.i("[Chat Room Creation] READ_CONTACTS permission granted") - LinphoneApplication.coreContext.contactsManager.onReadContactsPermissionGranted() - LinphoneApplication.coreContext.contactsManager.fetchContactsAsync() + coreContext.contactsManager.onReadContactsPermissionGranted() + coreContext.contactsManager.fetchContactsAsync() } else { Log.w("[Chat Room Creation] READ_CONTACTS permission denied") } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index f8c2e012e..a2041beb4 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -161,7 +161,6 @@ class DetailChatRoomFragment : MasterFragment - val path = content.filePath.orEmpty() + var path = content.filePath.orEmpty() if (!File(path).exists()) { (requireActivity() as MainActivity).showSnackBar(R.string.chat_room_file_not_found) } else { + if (path.isEmpty()) { + val name = content.name + if (name != null && name.isNotEmpty()) { + val file = FileUtils.getFileStoragePath(name) + FileUtils.writeIntoFile(content.buffer, file) + path = file.absolutePath + content.filePath = path + Log.i("[Chat Message] Content file path was empty, created file from buffer at $path") + } else if (content.isIcalendar) { + val name = "conference.ics" + val file = FileUtils.getFileStoragePath(name) + FileUtils.writeIntoFile(content.buffer, file) + path = file.absolutePath + content.filePath = path + Log.i("[Chat Message] Content file path was empty, created conference.ics from buffer at $path") + } + } + Log.i("[Chat Message] Opening file: $path") sharedViewModel.contentToOpen.value = content @@ -470,6 +487,14 @@ class DetailChatRoomFragment : MasterFragment + navigateToConferenceWaitingRoom(pair.first, pair.second) + } + } + adapter.scrollToChatMessageEvent.observe( viewLifecycleOwner ) { @@ -542,7 +567,7 @@ class DetailChatRoomFragment : MasterFragment() { val chatRoom = sharedViewModel.selectedChatRoom.value if (chatRoom == null) { Log.e("[Devices] Chat room is null, aborting!") - // (activity as MainActivity).showSnackBar(R.string.error) findNavController().navigateUp() return } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/EphemeralFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/EphemeralFragment.kt index 3eed8654b..310ca4724 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/EphemeralFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/EphemeralFragment.kt @@ -52,7 +52,6 @@ class EphemeralFragment : SecureFragment() { val chatRoom = sharedViewModel.selectedChatRoom.value if (chatRoom == null) { Log.e("[Ephemeral] Chat room is null, aborting!") - // (activity as MainActivity).showSnackBar(R.string.error) findNavController().navigateUp() return } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt index 4baa685a3..9e5ea8c16 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt @@ -175,7 +175,7 @@ class GroupInfoFragment : SecureFragment() { dialog.show() } - viewModel.onErrorEvent.observe( + viewModel.onMessageToNotifyEvent.observe( viewLifecycleOwner ) { it.consume { messageResourceId -> diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt index e50e5d0a1..7b422a491 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt @@ -56,7 +56,6 @@ class ImdnFragment : SecureFragment() { val chatRoom = sharedViewModel.selectedChatRoom.value if (chatRoom == null) { Log.e("[IMDN] Chat room is null, aborting!") - // (activity as MainActivity).showSnackBar(R.string.error) findNavController().navigateUp() return } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt index eff7aedc1..d2626aa41 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt @@ -312,8 +312,6 @@ class MasterChatRoomsFragment : MasterFragment diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt index bb1cc356e..24ced613a 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt @@ -23,43 +23,26 @@ import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R -import org.linphone.activities.main.viewmodels.ErrorReportingViewModel -import org.linphone.contact.ContactsUpdatedListenerStub +import org.linphone.contact.ContactsSelectionViewModel import org.linphone.core.* import org.linphone.core.tools.Log import org.linphone.utils.AppUtils import org.linphone.utils.Event import org.linphone.utils.LinphoneUtils -class ChatRoomCreationViewModel : ErrorReportingViewModel() { +class ChatRoomCreationViewModel : ContactsSelectionViewModel() { val chatRoomCreatedEvent: MutableLiveData> by lazy { MutableLiveData>() } val createGroupChat = MutableLiveData() - val sipContactsSelected = MutableLiveData() - val isEncrypted = MutableLiveData() - val contactsList = MutableLiveData>() - val waitForChatRoomCreation = MutableLiveData() - val selectedAddresses = MutableLiveData>() - - val filter = MutableLiveData() - private var previousFilter = "" - val limeAvailable: Boolean = LinphoneUtils.isLimeAvailable() - private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() { - override fun onContactsUpdated() { - Log.i("[Chat Room Creation] Contacts have changed") - updateContactsList() - } - } - private val listener = object : ChatRoomListenerStub() { override fun onStateChanged(room: ChatRoom, state: ChatRoom.State) { if (state == ChatRoom.State.Created) { @@ -69,25 +52,18 @@ class ChatRoomCreationViewModel : ErrorReportingViewModel() { } else if (state == ChatRoom.State.CreationFailed) { Log.e("[Chat Room Creation] Group chat room creation has failed !") waitForChatRoomCreation.value = false - onErrorEvent.value = Event(R.string.chat_room_creation_failed_snack) + onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) } } } init { createGroupChat.value = false - sipContactsSelected.value = coreContext.contactsManager.shouldDisplaySipContactsList() isEncrypted.value = false - - selectedAddresses.value = arrayListOf() - - coreContext.contactsManager.addListener(contactsUpdatedListener) waitForChatRoomCreation.value = false } override fun onCleared() { - coreContext.contactsManager.removeListener(contactsUpdatedListener) - super.onCleared() } @@ -95,55 +71,6 @@ class ChatRoomCreationViewModel : ErrorReportingViewModel() { isEncrypted.value = encrypted } - fun applyFilter() { - val filterValue = filter.value.orEmpty() - if (previousFilter == filterValue) return - - if (previousFilter.isNotEmpty() && previousFilter.length > filterValue.length) { - coreContext.contactsManager.magicSearch.resetSearchCache() - } - previousFilter = filterValue - - updateContactsList() - } - - fun updateContactsList() { - val domain = if (sipContactsSelected.value == true) coreContext.core.defaultAccount?.params?.domain ?: "" else "" - val results = coreContext.contactsManager.magicSearch.getContactListFromFilter(filter.value.orEmpty(), domain) - - val list = arrayListOf() - for (result in results) { - list.add(result) - } - contactsList.value = list - } - - fun toggleSelectionForSearchResult(searchResult: SearchResult) { - val address = searchResult.address - if (address != null) { - toggleSelectionForAddress(address) - } - } - - fun toggleSelectionForAddress(address: Address) { - val list = arrayListOf
() - list.addAll(selectedAddresses.value.orEmpty()) - - val found = list.find { - it.weakEqual(address) - } - - if (found != null) { - list.remove(found) - } else { - val contact = coreContext.contactsManager.findContactByAddress(address) - if (contact != null) address.displayName = contact.fullName - list.add(address) - } - - selectedAddresses.value = list - } - fun createOneToOneChat(searchResult: SearchResult) { waitForChatRoomCreation.value = true val defaultAccount = coreContext.core.defaultAccount @@ -152,7 +79,7 @@ class ChatRoomCreationViewModel : ErrorReportingViewModel() { val address = searchResult.address ?: coreContext.core.interpretUrl(searchResult.phoneNumber ?: "") if (address == null) { Log.e("[Chat Room Creation] Can't get a valid address from search result $searchResult") - onErrorEvent.value = Event(R.string.chat_room_creation_failed_snack) + onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) waitForChatRoomCreation.value = false return } diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt index b84b82090..3a06cc41f 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt @@ -20,6 +20,9 @@ package org.linphone.activities.main.chat.viewmodels import android.animation.ValueAnimator +import android.graphics.Typeface +import android.text.SpannableStringBuilder +import android.text.style.StyleSpan import android.view.animation.LinearInterpolator import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -60,7 +63,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf val lastUpdate = MutableLiveData() - val lastMessageText = MutableLiveData() + val lastMessageText = MutableLiveData() val callInProgress = MutableLiveData() @@ -113,7 +116,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf override fun onContactsUpdated() { Log.i("[Chat Room] Contacts have changed") contactLookup() - updateLastMessageToDisplay() + formatLastMessage(chatRoom.lastMessageInHistory) } } @@ -150,11 +153,11 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf override fun onChatMessageReceived(chatRoom: ChatRoom, eventLog: EventLog) { unreadMessagesCount.value = chatRoom.unreadMessagesCount - lastMessageText.value = formatLastMessage(eventLog.chatMessage) + formatLastMessage(eventLog.chatMessage) } override fun onChatMessageSent(chatRoom: ChatRoom, eventLog: EventLog) { - lastMessageText.value = formatLastMessage(eventLog.chatMessage) + formatLastMessage(eventLog.chatMessage) } override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) { @@ -199,7 +202,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf override fun onEphemeralMessageDeleted(chatRoom: ChatRoom, eventLog: EventLog) { Log.i("[Chat Room] Ephemeral message deleted, updated last message displayed") - updateLastMessageToDisplay() + formatLastMessage(chatRoom.lastMessageInHistory) } override fun onEphemeralEvent(chatRoom: ChatRoom, eventLog: EventLog) { @@ -216,6 +219,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf chatRoom.addListener(chatRoomListener) coreContext.contactsManager.addListener(contactsUpdatedListener) + formatLastMessage(chatRoom.lastMessageInHistory) unreadMessagesCount.value = chatRoom.unreadMessagesCount lastUpdate.value = TimestampUtils.toString(chatRoom.lastUpdateTime, true) @@ -226,7 +230,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf contactLookup() updateParticipants() - updateLastMessageToDisplay() + formatLastMessage(chatRoom.lastMessageInHistory) callInProgress.value = chatRoom.core.callsNb > 0 updateRemotesComposing() @@ -277,22 +281,36 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf } fun updateLastMessageToDisplay() { - lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory) + formatLastMessage(chatRoom.lastMessageInHistory) } - private fun formatLastMessage(msg: ChatMessage?): String { - if (msg == null) return "" + private fun formatLastMessage(msg: ChatMessage?) { + val builder = SpannableStringBuilder() + if (msg == null) { + lastMessageText.value = builder + return + } val sender: String = coreContext.contactsManager.findContactByAddress(msg.fromAddress)?.fullName ?: LinphoneUtils.getDisplayName(msg.fromAddress) - var body = "" + builder.append(sender) + builder.append(": ") + for (content in msg.contents) { - if (content.isFile || content.isFileTransfer) body += content.name + " " - else if (content.isText) body += content.utf8Text + " " + if (content.isIcalendar) { + val body = AppUtils.getString(R.string.conference_invitation) + builder.append(body) + builder.setSpan(StyleSpan(Typeface.ITALIC), builder.length - body.length, builder.length, 0) + } else if (content.isFile || content.isFileTransfer) { + builder.append(content.name + " ") + } else if (content.isText) { + builder.append(content.utf8Text + " ") + } } - return "$sender: $body" + builder.trim() + lastMessageText.value = builder } private fun searchMatchingContact() { diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt index 1e9982c5f..7982d9fcf 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt @@ -22,7 +22,7 @@ package org.linphone.activities.main.chat.viewmodels import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R -import org.linphone.activities.main.viewmodels.ErrorReportingViewModel +import org.linphone.activities.main.viewmodels.MessageNotifierViewModel import org.linphone.compatibility.Compatibility import org.linphone.contact.ContactsUpdatedListenerStub import org.linphone.core.* @@ -30,7 +30,7 @@ import org.linphone.core.tools.Log import org.linphone.utils.Event import org.linphone.utils.LinphoneUtils -class ChatRoomsListViewModel : ErrorReportingViewModel() { +class ChatRoomsListViewModel : MessageNotifierViewModel() { val chatRooms = MutableLiveData>() val contactsUpdatedEvent: MutableLiveData> by lazy { @@ -60,7 +60,7 @@ class ChatRoomsListViewModel : ErrorReportingViewModel() { } } else if (state == ChatRoom.State.TerminationFailed) { Log.e("[Chat Rooms] Group chat room removal for address ${chatRoom.peerAddress.asStringUriOnly()} has failed !") - onErrorEvent.value = Event(R.string.chat_room_removal_failed_snack) + onMessageToNotifyEvent.value = Event(R.string.chat_room_removal_failed_snack) } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt index 7bfd94e48..807698f62 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt @@ -27,7 +27,7 @@ import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.activities.main.chat.GroupChatRoomMember import org.linphone.activities.main.chat.data.GroupInfoParticipantData -import org.linphone.activities.main.viewmodels.ErrorReportingViewModel +import org.linphone.activities.main.viewmodels.MessageNotifierViewModel import org.linphone.core.* import org.linphone.core.tools.Log import org.linphone.utils.Event @@ -41,7 +41,7 @@ class GroupInfoViewModelFactory(private val chatRoom: ChatRoom?) : } } -class GroupInfoViewModel(val chatRoom: ChatRoom?) : ErrorReportingViewModel() { +class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() { val createdChatRoomEvent = MutableLiveData>() val updatedChatRoomEvent = MutableLiveData>() @@ -69,7 +69,7 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : ErrorReportingViewModel() { } else if (state == ChatRoom.State.CreationFailed) { Log.e("[Chat Room Group Info] Group chat room creation has failed !") waitForChatRoomCreation.value = false - onErrorEvent.value = Event(R.string.chat_room_creation_failed_snack) + onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) } } @@ -142,7 +142,7 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : ErrorReportingViewModel() { if (chatRoom == null) { Log.e("[Chat Room Group Info] Couldn't create chat room!") waitForChatRoomCreation.value = false - onErrorEvent.value = Event(R.string.chat_room_creation_failed_snack) + onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) } } diff --git a/app/src/main/java/org/linphone/activities/main/conference/adapters/ScheduledConferencesAdapter.kt b/app/src/main/java/org/linphone/activities/main/conference/adapters/ScheduledConferencesAdapter.kt new file mode 100644 index 000000000..a3ce4ac64 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/adapters/ScheduledConferencesAdapter.kt @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.linphone.R +import org.linphone.activities.main.conference.data.ScheduledConferenceData +import org.linphone.core.Address +import org.linphone.databinding.ConferenceScheduleCellBinding +import org.linphone.databinding.ConferenceScheduleListHeaderBinding +import org.linphone.utils.Event +import org.linphone.utils.HeaderAdapter +import org.linphone.utils.TimestampUtils + +class ScheduledConferencesAdapter( + private val viewLifecycleOwner: LifecycleOwner +) : ListAdapter(ConferenceInfoDiffCallback()), + HeaderAdapter { + val copyAddressToClipboardEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val joinConferenceEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + + val deleteConferenceInfoEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScheduledConferencesAdapter.ViewHolder { + val binding: ConferenceScheduleCellBinding = DataBindingUtil.inflate( + LayoutInflater.from(parent.context), + R.layout.conference_schedule_cell, parent, false + ) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + (holder as ScheduledConferencesAdapter.ViewHolder).bind(getItem(position)) + } + + override fun displayHeaderForPosition(position: Int): Boolean { + if (position >= itemCount) return false + val conferenceInfo = getItem(position) + val previousPosition = position - 1 + return if (previousPosition >= 0) { + val previousItem = getItem(previousPosition) + !TimestampUtils.isSameDay(previousItem.conferenceInfo.dateTime, conferenceInfo.conferenceInfo.dateTime) + } else true + } + + override fun getHeaderViewForPosition(context: Context, position: Int): View { + val data = getItem(position) + val binding: ConferenceScheduleListHeaderBinding = DataBindingUtil.inflate( + LayoutInflater.from(context), + R.layout.conference_schedule_list_header, null, false + ) + binding.title = formatDate(context, data.conferenceInfo.dateTime) + binding.executePendingBindings() + return binding.root + } + + private fun formatDate(context: Context, date: Long): String { + if (TimestampUtils.isToday(date)) { + return context.getString(R.string.today) + } + return TimestampUtils.toString(date, onlyDate = true, shortDate = false, hideYear = false) + } + + inner class ViewHolder( + val binding: ConferenceScheduleCellBinding + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(conferenceData: ScheduledConferenceData) { + with(binding) { + data = conferenceData + + lifecycleOwner = viewLifecycleOwner + + setCopyAddressClickListener { + val address = conferenceData.conferenceInfo.uri + if (address != null) { + copyAddressToClipboardEvent.value = Event(address) + } + } + + setJoinConferenceClickListener { + val address = conferenceData.conferenceInfo.uri + if (address != null) { + joinConferenceEvent.value = Event(Pair(address.asStringUriOnly(), conferenceData.conferenceInfo.subject)) + } + } + + setDeleteConferenceClickListener { + deleteConferenceInfoEvent.value = Event(conferenceData) + } + + executePendingBindings() + } + } + } +} + +private class ConferenceInfoDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ScheduledConferenceData, + newItem: ScheduledConferenceData + ): Boolean { + return oldItem.conferenceInfo == newItem.conferenceInfo + } + + override fun areContentsTheSame( + oldItem: ScheduledConferenceData, + newItem: ScheduledConferenceData + ): Boolean { + return false + } +} diff --git a/app/src/main/java/org/linphone/activities/main/conference/data/ConferenceSchedulingParticipantData.kt b/app/src/main/java/org/linphone/activities/main/conference/data/ConferenceSchedulingParticipantData.kt new file mode 100644 index 000000000..71d9f0f87 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/data/ConferenceSchedulingParticipantData.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.data + +import org.linphone.contact.GenericContactData +import org.linphone.core.Address +import org.linphone.utils.LinphoneUtils + +class ConferenceSchedulingParticipantData( + private val sipAddress: Address, + val showLimeBadge: Boolean +) : + GenericContactData(sipAddress) { + val sipUri: String get() = LinphoneUtils.getDisplayableAddress(sipAddress) +} diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/SharedCallViewModel.kt b/app/src/main/java/org/linphone/activities/main/conference/data/Duration.kt similarity index 64% rename from app/src/main/java/org/linphone/activities/call/viewmodels/SharedCallViewModel.kt rename to app/src/main/java/org/linphone/activities/main/conference/data/Duration.kt index a10b96fe3..61a6ee696 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/SharedCallViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/conference/data/Duration.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2020 Belledonne Communications SARL. + * Copyright (c) 2010-2021 Belledonne Communications SARL. * * This file is part of linphone-android * (see https://www.linphone.org). @@ -17,14 +17,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.activities.call.viewmodels +package org.linphone.activities.main.conference.data -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.utils.Event +class Duration(val value: Int, val display: String) : Comparable { + override fun toString(): String { + return display + } -class SharedCallViewModel : ViewModel() { - val toggleDrawerEvent = MutableLiveData>() - - val resetHiddenInterfaceTimerInVideoCallEvent = MutableLiveData>() + override fun compareTo(other: Duration): Int { + return value.compareTo(other.value) + } } diff --git a/app/src/main/java/org/linphone/activities/main/conference/data/ScheduledConferenceData.kt b/app/src/main/java/org/linphone/activities/main/conference/data/ScheduledConferenceData.kt new file mode 100644 index 000000000..feff7598f --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/data/ScheduledConferenceData.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.data + +import androidx.lifecycle.MutableLiveData +import java.util.concurrent.TimeUnit +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.ConferenceInfo +import org.linphone.core.tools.Log +import org.linphone.utils.LinphoneUtils +import org.linphone.utils.TimestampUtils + +class ScheduledConferenceData(val conferenceInfo: ConferenceInfo) { + val expanded = MutableLiveData() + + val address = MutableLiveData() + val subject = MutableLiveData() + val description = MutableLiveData() + val time = MutableLiveData() + val date = MutableLiveData() + val duration = MutableLiveData() + val organizer = MutableLiveData() + val participantsShort = MutableLiveData() + val participantsExpanded = MutableLiveData() + val showDuration = MutableLiveData() + + init { + expanded.value = false + + address.value = conferenceInfo.uri?.asStringUriOnly() + subject.value = conferenceInfo.subject + description.value = conferenceInfo.description + + time.value = TimestampUtils.timeToString(conferenceInfo.dateTime) + date.value = TimestampUtils.toString(conferenceInfo.dateTime, onlyDate = true, shortDate = false, hideYear = false) + + val minutes = conferenceInfo.duration + val hours = TimeUnit.MINUTES.toHours(minutes.toLong()) + val remainMinutes = minutes - TimeUnit.HOURS.toMinutes(hours).toInt() + duration.value = TimestampUtils.durationToString(hours.toInt(), remainMinutes) + showDuration.value = minutes > 0 + + val organizerAddress = conferenceInfo.organizer + if (organizerAddress != null) { + val contact = coreContext.contactsManager.findContactByAddress(organizerAddress) + organizer.value = if (contact != null) + contact.fullName + else + LinphoneUtils.getDisplayName(conferenceInfo.organizer) + } else { + Log.e("[Scheduled Conference] No organizer SIP URI found for: ${conferenceInfo.uri?.asStringUriOnly()}") + } + + computeParticipantsLists() + } + + fun destroy() {} + + fun delete() { + Log.w("[Scheduled Conference] Deleting conference info with URI: ${conferenceInfo.uri?.asStringUriOnly()}") + coreContext.core.deleteConferenceInformation(conferenceInfo) + } + + fun toggleExpand() { + expanded.value = expanded.value == false + } + + private fun computeParticipantsLists() { + var participantsListShort = "" + var participantsListExpanded = "" + + for (participant in conferenceInfo.participants) { + val contact = coreContext.contactsManager.findContactByAddress(participant) + val name = if (contact != null) contact.fullName else LinphoneUtils.getDisplayName(participant) + val address = participant.asStringUriOnly() + participantsListShort += "$name, " + participantsListExpanded += "$name ($address)\n" + } + participantsListShort = participantsListShort.dropLast(2) + participantsListExpanded = participantsListExpanded.dropLast(1) + + participantsShort.value = participantsListShort + participantsExpanded.value = participantsListExpanded + } +} diff --git a/app/src/main/java/org/linphone/activities/main/conference/data/TimeZoneData.kt b/app/src/main/java/org/linphone/activities/main/conference/data/TimeZoneData.kt new file mode 100644 index 000000000..de9007e53 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/data/TimeZoneData.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.data + +import java.util.* +import java.util.concurrent.TimeUnit +import kotlin.math.abs + +class TimeZoneData(timeZone: TimeZone) : Comparable { + val id: String = timeZone.id + private val hours: Long + private val minutes: Long + private val gmt: String + + init { + hours = TimeUnit.MILLISECONDS.toHours(timeZone.rawOffset.toLong()) + minutes = abs( + TimeUnit.MILLISECONDS.toMinutes(timeZone.rawOffset.toLong()) - + TimeUnit.HOURS.toMinutes(hours) + ) + + gmt = if (hours > 0) { + String.format("%s - GMT+%d:%02d", timeZone.id, hours, minutes) + } else { + String.format("%s - GMT%d:%02d", timeZone.id, hours, minutes) + } + } + + override fun toString(): String { + return gmt + } + + override fun compareTo(other: TimeZoneData): Int { + if (hours == other.hours) { + if (minutes == other.minutes) { + return id.compareTo(other.id) + } + return minutes.compareTo(other.minutes) + } + return hours.compareTo(other.hours) + } +} diff --git a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingFragment.kt b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingFragment.kt new file mode 100644 index 000000000..23e1fb610 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingFragment.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.fragments + +import android.os.Bundle +import android.text.format.DateFormat.is24HourFormat +import android.view.View +import androidx.navigation.navGraphViewModels +import com.google.android.material.datepicker.CalendarConstraints +import com.google.android.material.datepicker.DateValidatorPointForward +import com.google.android.material.datepicker.MaterialDatePicker +import com.google.android.material.timepicker.MaterialTimePicker +import com.google.android.material.timepicker.TimeFormat +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.main.conference.viewmodels.ConferenceSchedulingViewModel +import org.linphone.activities.navigateToParticipantsList +import org.linphone.databinding.ConferenceSchedulingFragmentBinding + +class ConferenceSchedulingFragment : GenericFragment() { + private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels(R.id.conference_scheduling_nav_graph) + + override fun getLayoutId(): Int = R.layout.conference_scheduling_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + binding.viewModel = viewModel + + binding.setBackClickListener { + goBack() + } + + binding.setNextClickListener { + navigateToParticipantsList() + } + + binding.setDatePickerClickListener { + val constraintsBuilder = + CalendarConstraints.Builder() + .setValidator(DateValidatorPointForward.now()) + val picker = + MaterialDatePicker.Builder.datePicker() + .setCalendarConstraints(constraintsBuilder.build()) + .setTitleText(R.string.conference_schedule_date) + .setSelection(viewModel.dateTimestamp) + .build() + picker.addOnPositiveButtonClickListener { + val selection = picker.selection + if (selection != null) { + viewModel.setDate(selection) + } + } + picker.show(requireFragmentManager(), "Date picker") + } + + binding.setTimePickerClickListener { + val isSystem24Hour = is24HourFormat(requireContext()) + val clockFormat = if (isSystem24Hour) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H + val picker = + MaterialTimePicker.Builder() + .setTimeFormat(clockFormat) + .setTitleText(R.string.conference_schedule_time) + .setHour(viewModel.hour) + .setMinute(viewModel.minutes) + .build() + picker.addOnPositiveButtonClickListener { + viewModel.setTime(picker.hour, picker.minute) + } + picker.show(requireFragmentManager(), "Time picker") + } + } +} diff --git a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingParticipantsListFragment.kt b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingParticipantsListFragment.kt new file mode 100644 index 000000000..6f565c116 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingParticipantsListFragment.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.fragments + +import android.content.pm.PackageManager +import android.os.Bundle +import android.view.View +import androidx.navigation.navGraphViewModels +import androidx.recyclerview.widget.LinearLayoutManager +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.main.conference.viewmodels.ConferenceSchedulingViewModel +import org.linphone.activities.navigateToSummary +import org.linphone.contact.ContactsSelectionAdapter +import org.linphone.core.tools.Log +import org.linphone.databinding.ConferenceSchedulingParticipantsListFragmentBinding +import org.linphone.utils.AppUtils +import org.linphone.utils.PermissionHelper + +class ConferenceSchedulingParticipantsListFragment : GenericFragment() { + private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels(R.id.conference_scheduling_nav_graph) + private lateinit var adapter: ContactsSelectionAdapter + + override fun getLayoutId(): Int = R.layout.conference_scheduling_participants_list_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + binding.viewModel = viewModel + + adapter = ContactsSelectionAdapter(viewLifecycleOwner) + adapter.setLimeCapabilityRequired(viewModel.isEncrypted.value == true) + binding.contactsList.adapter = adapter + + val layoutManager = LinearLayoutManager(activity) + binding.contactsList.layoutManager = layoutManager + + // Divider between items + binding.contactsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) + + binding.setBackClickListener { + goBack() + } + + binding.setNextClickListener { + navigateToSummary() + } + + viewModel.contactsList.observe( + viewLifecycleOwner + ) { + adapter.submitList(it) + } + viewModel.sipContactsSelected.observe( + viewLifecycleOwner + ) { + viewModel.updateContactsList() + } + + viewModel.selectedAddresses.observe( + viewLifecycleOwner + ) { + adapter.updateSelectedAddresses(it) + } + viewModel.filter.observe( + viewLifecycleOwner + ) { + viewModel.applyFilter() + } + + adapter.selectedContact.observe( + viewLifecycleOwner + ) { + it.consume { searchResult -> + viewModel.toggleSelectionForSearchResult(searchResult) + } + } + + if (!PermissionHelper.get().hasReadContactsPermission()) { + Log.i("[Conference Creation] Asking for READ_CONTACTS permission") + requestPermissions(arrayOf(android.Manifest.permission.READ_CONTACTS), 0) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + if (requestCode == 0) { + val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED + if (granted) { + Log.i("[Conference Creation] READ_CONTACTS permission granted") + coreContext.contactsManager.onReadContactsPermissionGranted() + coreContext.contactsManager.fetchContactsAsync() + } else { + Log.w("[Conference Creation] READ_CONTACTS permission denied") + } + } + } +} diff --git a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingSummaryFragment.kt b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingSummaryFragment.kt new file mode 100644 index 000000000..4d253e0f6 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingSummaryFragment.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.fragments + +import android.os.Bundle +import android.view.View +import androidx.navigation.navGraphViewModels +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.goToScheduledConferences +import org.linphone.activities.main.MainActivity +import org.linphone.activities.main.conference.viewmodels.ConferenceSchedulingViewModel +import org.linphone.activities.navigateToConferenceWaitingRoom +import org.linphone.databinding.ConferenceSchedulingSummaryFragmentBinding + +class ConferenceSchedulingSummaryFragment : GenericFragment() { + private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels(R.id.conference_scheduling_nav_graph) + + override fun getLayoutId(): Int = R.layout.conference_scheduling_summary_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + binding.viewModel = viewModel + + binding.setBackClickListener { + goBack() + } + + binding.setCreateConferenceClickListener { + viewModel.createConference() + } + + viewModel.conferenceCreationCompletedEvent.observe( + viewLifecycleOwner + ) { + it.consume { pair -> + if (viewModel.scheduleForLater.value == true) { + (requireActivity() as MainActivity).showSnackBar(R.string.conference_schedule_info_created) + goToScheduledConferences() + } else { + navigateToConferenceWaitingRoom(pair.first, pair.second) + } + } + } + + viewModel.onMessageToNotifyEvent.observe( + viewLifecycleOwner + ) { + it.consume { messageId -> + (activity as MainActivity).showSnackBar(messageId) + } + } + + viewModel.computeParticipantsData() + } +} diff --git a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceWaitingRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceWaitingRoomFragment.kt new file mode 100644 index 000000000..869b4e95f --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceWaitingRoomFragment.kt @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.fragments + +import android.Manifest +import android.annotation.TargetApi +import android.content.pm.PackageManager +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.main.conference.viewmodels.ConferenceWaitingRoomViewModel +import org.linphone.activities.navigateToDialer +import org.linphone.core.tools.Log +import org.linphone.databinding.ConferenceWaitingRoomFragmentBinding +import org.linphone.mediastream.Version +import org.linphone.utils.PermissionHelper + +class ConferenceWaitingRoomFragment : GenericFragment() { + private lateinit var viewModel: ConferenceWaitingRoomViewModel + + override fun getLayoutId(): Int = R.layout.conference_waiting_room_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + viewModel = ViewModelProvider( + this + )[ConferenceWaitingRoomViewModel::class.java] + binding.viewModel = viewModel + + val conferenceSubject = arguments?.getString("Subject") + viewModel.subject.value = conferenceSubject + + viewModel.cancelConferenceJoiningEvent.observe( + viewLifecycleOwner + ) { + it.consume { + if (viewModel.joinInProgress.value == true) { + val conferenceUri = arguments?.getString("Address") + val callToCancel = coreContext.core.calls.find { call -> + call.remoteAddress.asStringUriOnly() == conferenceUri + } + if (callToCancel != null) { + Log.i("[Conference Waiting Room] Call to conference server with URI [$conferenceUri] was started, terminate it") + callToCancel.terminate() + } else { + Log.w("[Conference Waiting Room] Call to conference server with URI [$conferenceUri] wasn't found!") + } + } + navigateToDialer() + } + } + + viewModel.joinConferenceEvent.observe( + viewLifecycleOwner + ) { + it.consume { callParams -> + val conferenceUri = arguments?.getString("Address") + if (conferenceUri != null) { + val conferenceAddress = coreContext.core.interpretUrl(conferenceUri) + if (conferenceAddress != null) { + Log.i("[Conference Waiting Room] Calling conference SIP URI: ${conferenceAddress.asStringUriOnly()}") + coreContext.startCall(conferenceAddress, callParams) + } else { + Log.e("[Conference Waiting Room] Failed to parse conference SIP URI: $conferenceUri") + } + } else { + Log.e("[Conference Waiting Room] Failed to find conference SIP URI in arguments") + } + } + } + + viewModel.askPermissionEvent.observe( + viewLifecycleOwner + ) { + it.consume { permission -> + Log.i("[Conference Waiting Room] Asking for $permission permission") + requestPermissions(arrayOf(permission), 0) + } + } + + viewModel.leaveWaitingRoomEvent.observe( + viewLifecycleOwner + ) { + it.consume { + goBack() + } + } + + if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) { + checkPermissions() + } + } + + override fun onResume() { + super.onResume() + + coreContext.core.nativePreviewWindowId = binding.localPreviewVideoSurface + } + + override fun onPause() { + coreContext.core.nativePreviewWindowId = null + + super.onPause() + } + + @TargetApi(Version.API23_MARSHMALLOW_60) + private fun checkPermissions() { + val permissionsRequiredList = arrayListOf() + if (!PermissionHelper.get().hasRecordAudioPermission()) { + Log.i("[Conference Waiting Room] Asking for RECORD_AUDIO permission") + permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO) + } + if (!PermissionHelper.get().hasCameraPermission()) { + Log.i("[Conference Waiting Room] Asking for CAMERA permission") + permissionsRequiredList.add(Manifest.permission.CAMERA) + } + if (permissionsRequiredList.isNotEmpty()) { + val permissionsRequired = arrayOfNulls(permissionsRequiredList.size) + permissionsRequiredList.toArray(permissionsRequired) + requestPermissions(permissionsRequired, 0) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + if (requestCode == 0) { + for (i in permissions.indices) { + when (permissions[i]) { + Manifest.permission.RECORD_AUDIO -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { + Log.i("[Conference Waiting Room] RECORD_AUDIO permission has been granted") + viewModel.enableMic() + } + Manifest.permission.CAMERA -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { + Log.i("[Conference Waiting Room] CAMERA permission has been granted") + coreContext.core.reloadVideoDevices() + viewModel.enableVideo() + } + } + } + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + } +} diff --git a/app/src/main/java/org/linphone/activities/main/conference/fragments/ScheduledConferencesFragment.kt b/app/src/main/java/org/linphone/activities/main/conference/fragments/ScheduledConferencesFragment.kt new file mode 100644 index 000000000..1f74142c7 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/fragments/ScheduledConferencesFragment.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.fragments + +import android.app.Dialog +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.main.MainActivity +import org.linphone.activities.main.conference.adapters.ScheduledConferencesAdapter +import org.linphone.activities.main.conference.viewmodels.ScheduledConferencesViewModel +import org.linphone.activities.main.viewmodels.DialogViewModel +import org.linphone.activities.navigateToConferenceScheduling +import org.linphone.activities.navigateToConferenceWaitingRoom +import org.linphone.databinding.ConferencesScheduledFragmentBinding +import org.linphone.utils.AppUtils +import org.linphone.utils.DialogUtils +import org.linphone.utils.RecyclerViewHeaderDecoration + +class ScheduledConferencesFragment : GenericFragment() { + private lateinit var viewModel: ScheduledConferencesViewModel + private lateinit var adapter: ScheduledConferencesAdapter + + override fun getLayoutId(): Int = R.layout.conferences_scheduled_fragment + + private var deleteConferenceInfoDialog: Dialog? = null + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + viewModel = ViewModelProvider( + this + )[ScheduledConferencesViewModel::class.java] + binding.viewModel = viewModel + + adapter = ScheduledConferencesAdapter( + viewLifecycleOwner + ) + binding.conferenceInfoList.adapter = adapter + + val layoutManager = LinearLayoutManager(activity) + binding.conferenceInfoList.layoutManager = layoutManager + + // Displays date header + val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) + binding.conferenceInfoList.addItemDecoration(headerItemDecoration) + + viewModel.conferences.observe( + viewLifecycleOwner + ) { + adapter.submitList(it) + } + + adapter.copyAddressToClipboardEvent.observe( + viewLifecycleOwner + ) { + it.consume { address -> + val clipboard = + requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("Conference address", address.asStringUriOnly()) + clipboard.setPrimaryClip(clip) + + (activity as MainActivity).showSnackBar(R.string.conference_schedule_address_copied_to_clipboard) + } + } + + adapter.joinConferenceEvent.observe( + viewLifecycleOwner + ) { + it.consume { pair -> + navigateToConferenceWaitingRoom(pair.first, pair.second) + } + } + + adapter.deleteConferenceInfoEvent.observe( + viewLifecycleOwner + ) { + it.consume { data -> + val dialogViewModel = + DialogViewModel(AppUtils.getString(R.string.conference_info_confirm_removal)) + deleteConferenceInfoDialog = + DialogUtils.getVoipDialog(requireContext(), dialogViewModel) + + dialogViewModel.showCancelButton( + { + deleteConferenceInfoDialog?.dismiss() + }, + getString(R.string.dialog_cancel) + ) + + dialogViewModel.showDeleteButton( + { + viewModel.deleteConferenceInfo(data) + deleteConferenceInfoDialog?.dismiss() + (requireActivity() as MainActivity).showSnackBar(R.string.conference_info_removed) + }, + getString(R.string.dialog_delete) + ) + + deleteConferenceInfoDialog?.show() + } + } + + binding.setBackClickListener { + goBack() + } + + binding.setNewConferenceClickListener { + navigateToConferenceScheduling() + } + } +} diff --git a/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceSchedulingViewModel.kt b/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceSchedulingViewModel.kt new file mode 100644 index 000000000..2f5ca4a79 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceSchedulingViewModel.kt @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.viewmodels + +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import java.util.* +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R +import org.linphone.activities.main.conference.data.ConferenceSchedulingParticipantData +import org.linphone.activities.main.conference.data.Duration +import org.linphone.activities.main.conference.data.TimeZoneData +import org.linphone.contact.ContactsSelectionViewModel +import org.linphone.core.* +import org.linphone.core.tools.Log +import org.linphone.utils.Event +import org.linphone.utils.TimestampUtils + +class ConferenceSchedulingViewModel : ContactsSelectionViewModel() { + val subject = MutableLiveData() + val description = MutableLiveData() + + val scheduleForLater = MutableLiveData() + + val formattedDate = MutableLiveData() + val formattedTime = MutableLiveData() + + val isEncrypted = MutableLiveData() + + val sendInviteViaChat = MutableLiveData() + val sendInviteViaEmail = MutableLiveData() + + val participantsData = MutableLiveData>() + + val address = MutableLiveData
() + + val conferenceCreationInProgress = MutableLiveData() + + val conferenceCreationCompletedEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + + val continueEnabled: MediatorLiveData = MediatorLiveData() + + var timeZone = MutableLiveData() + val timeZones: List = computeTimeZonesList() + + var duration = MutableLiveData() + val durationList: List = computeDurationList() + + var dateTimestamp: Long = System.currentTimeMillis() + var hour: Int = 0 + var minutes: Int = 0 + + private val conferenceScheduler = coreContext.core.createConferenceScheduler() + + private val chatRoomListener = object : ChatRoomListenerStub() { + override fun onStateChanged(room: ChatRoom, state: ChatRoom.State) { + if (state == ChatRoom.State.Created) { + Log.i("[Conference Creation] Chat room created") + room.removeListener(this) + } else if (state == ChatRoom.State.CreationFailed) { + Log.e("[Conference Creation] Group chat room creation has failed !") + room.removeListener(this) + } + } + } + + private val listener = object : ConferenceSchedulerListenerStub() { + override fun onStateChanged( + conferenceScheduler: ConferenceScheduler, + state: ConferenceSchedulerState + ) { + Log.i("[Conference Creation] Conference scheduler state is $state") + if (state == ConferenceSchedulerState.Ready) { + val conferenceAddress = conferenceScheduler.info?.uri + Log.i("[Conference Creation] Conference info created, address will be ${conferenceAddress?.asStringUriOnly()}") + conferenceAddress ?: return + + address.value = conferenceAddress!! + + if (sendInviteViaChat.value == true) { + // Send conference info even when conf is not scheduled for later + // as the conference server doesn't invite participants automatically + val chatRoomParams = coreContext.core.createDefaultChatRoomParams() + chatRoomParams.backend = ChatRoomBackend.FlexisipChat + chatRoomParams.isGroupEnabled = false + chatRoomParams.isEncryptionEnabled = true + chatRoomParams.subject = subject.value + conferenceScheduler.sendInvitations(chatRoomParams) + } else { + conferenceCreationInProgress.value = false + conferenceCreationCompletedEvent.value = Event(Pair(conferenceAddress.asStringUriOnly(), conferenceScheduler.info?.subject)) + } + } + } + + override fun onInvitationsSent( + conferenceScheduler: ConferenceScheduler, + failedInvitations: Array? + ) { + Log.i("[Conference Creation] Conference information successfully sent to all participants") + conferenceCreationInProgress.value = false + + if (failedInvitations?.isNotEmpty() == true) { + for (address in failedInvitations) { + Log.e("[Conference Creation] Conference information wasn't sent to participant ${address.asStringUriOnly()}") + } + onMessageToNotifyEvent.value = Event(R.string.conference_schedule_info_not_sent_to_participant) + } + + val conferenceAddress = conferenceScheduler.info?.uri + if (conferenceAddress == null) { + Log.e("[Conference Creation] Conference address is null!") + } else { + conferenceCreationCompletedEvent.value = Event(Pair(conferenceAddress.asStringUriOnly(), conferenceScheduler.info?.subject)) + } + } + } + + init { + sipContactsSelected.value = true + + subject.value = "" + scheduleForLater.value = false + isEncrypted.value = false + sendInviteViaChat.value = true + sendInviteViaEmail.value = false + + timeZone.value = timeZones.find { + it.id == TimeZone.getDefault().id + } + duration.value = durationList.find { + it.value == 3600 + } + + continueEnabled.value = false + continueEnabled.addSource(subject) { + continueEnabled.value = allMandatoryFieldsFilled() + } + continueEnabled.addSource(scheduleForLater) { + continueEnabled.value = allMandatoryFieldsFilled() + } + continueEnabled.addSource(formattedDate) { + continueEnabled.value = allMandatoryFieldsFilled() + } + continueEnabled.addSource(formattedTime) { + continueEnabled.value = allMandatoryFieldsFilled() + } + + conferenceScheduler.addListener(listener) + } + + override fun onCleared() { + conferenceScheduler.removeListener(listener) + participantsData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy) + + super.onCleared() + } + + fun toggleSchedule() { + scheduleForLater.value = scheduleForLater.value == false + } + + fun setDate(d: Long) { + dateTimestamp = d + formattedDate.value = TimestampUtils.dateToString(dateTimestamp, false) + } + + fun setTime(h: Int, m: Int) { + hour = h + minutes = m + formattedTime.value = TimestampUtils.timeToString(hour, minutes) + } + + fun updateEncryption(enable: Boolean) { + isEncrypted.value = enable + } + + fun computeParticipantsData() { + participantsData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy) + val list = arrayListOf() + + for (address in selectedAddresses.value.orEmpty()) { + val data = ConferenceSchedulingParticipantData(address, isEncrypted.value == true) + list.add(data) + } + + participantsData.value = list + } + + fun createConference() { + val participantsCount = selectedAddresses.value.orEmpty().size + if (participantsCount == 0) { + Log.e("[Conference Creation] Couldn't create conference without any participant!") + return + } + + conferenceCreationInProgress.value = true + val core = coreContext.core + val participants = arrayOfNulls
(selectedAddresses.value.orEmpty().size) + selectedAddresses.value?.toArray(participants) + val localAddress = core.defaultAccount?.params?.identityAddress + + // TODO: Temporary workaround for chat room, to be removed once we can get matching chat room from conference + /*val chatRoomParams = core.createDefaultChatRoomParams() + chatRoomParams.backend = ChatRoomBackend.FlexisipChat + chatRoomParams.enableGroup(true) + chatRoomParams.subject = subject.value + val chatRoom = core.createChatRoom(chatRoomParams, localAddress, participants) + if (chatRoom == null) { + Log.e("[Conference Creation] Failed to create a chat room with same subject & participants as for conference") + } else { + Log.i("[Conference Creation] Creating chat room with same subject [${subject.value}] & participants as for conference") + chatRoom.addListener(chatRoomListener) + }*/ + // END OF TODO + + val conferenceInfo = Factory.instance().createConferenceInfo() + conferenceInfo.organizer = localAddress + conferenceInfo.subject = subject.value + conferenceInfo.description = description.value + conferenceInfo.setParticipants(participants) + if (scheduleForLater.value == true) { + val startTime = getConferenceStartTimestamp() + conferenceInfo.dateTime = startTime + val duration = duration.value?.value ?: 0 + conferenceInfo.duration = duration + } + conferenceScheduler.info = conferenceInfo // Will trigger the conference creation automatically + } + + private fun computeTimeZonesList(): List { + return TimeZone.getAvailableIDs().map { id -> TimeZoneData(TimeZone.getTimeZone(id)) }.toList().sorted() + } + + private fun computeDurationList(): List { + // Duration value is in minutes as according to conferenceInfo.setDuration() doc + return arrayListOf(Duration(30, "30min"), Duration(60, "1h"), Duration(120, "2h")) + } + + private fun allMandatoryFieldsFilled(): Boolean { + return !subject.value.isNullOrEmpty() && + ( + scheduleForLater.value == false || + ( + !formattedDate.value.isNullOrEmpty() && + !formattedTime.value.isNullOrEmpty() + ) + ) + } + + private fun getConferenceStartTimestamp(): Long { + val calendar = Calendar.getInstance(TimeZone.getTimeZone(timeZone.value?.id ?: TimeZone.getDefault().id)) + calendar.timeInMillis = dateTimestamp + calendar.set(Calendar.HOUR_OF_DAY, hour) + calendar.set(Calendar.MINUTE, minutes) + return calendar.timeInMillis / 1000 // Linphone expects a time_t (so in seconds) + } +} diff --git a/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceWaitingRoomViewModel.kt b/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceWaitingRoomViewModel.kt new file mode 100644 index 000000000..eeed7e054 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceWaitingRoomViewModel.kt @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.viewmodels + +import android.Manifest +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.* +import org.linphone.core.tools.Log +import org.linphone.utils.AudioRouteUtils +import org.linphone.utils.Event +import org.linphone.utils.PermissionHelper + +class ConferenceWaitingRoomViewModel : ViewModel() { + val subject = MutableLiveData() + + val isMicrophoneMuted = MutableLiveData() + + val audioRoutesEnabled = MutableLiveData() + + val audioRoutesSelected = MutableLiveData() + + val isSpeakerSelected = MutableLiveData() + + val isBluetoothHeadsetSelected = MutableLiveData() + + val isVideoAvailable = MutableLiveData() + + val isVideoEnabled = MutableLiveData() + + val isSwitchCameraAvailable = MutableLiveData() + + val joinInProgress = MutableLiveData() + + val askPermissionEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val cancelConferenceJoiningEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val joinConferenceEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val leaveWaitingRoomEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + private val callParams: CallParams = coreContext.core.createCallParams(null)!! + + private val listener: CoreListenerStub = object : CoreListenerStub() { + override fun onAudioDevicesListUpdated(core: Core) { + Log.i("[Conference Waiting Room] Audio devices list updated") + onAudioDevicesListUpdated() + } + + override fun onCallStateChanged( + core: Core, + call: Call, + state: Call.State?, + message: String + ) { + if (state == Call.State.Connected) { + Log.i("[Conference Waiting Room] Call is now connected, leaving waiting room fragment") + leaveWaitingRoomEvent.value = Event(true) + } + } + } + + init { + val core = coreContext.core + core.addListener(listener) + + callParams.isMicEnabled = PermissionHelper.get().hasRecordAudioPermission() + Log.i("[Conference Waiting Room] Microphone will be ${if (callParams.isMicEnabled) "enabled" else "muted"}") + updateMicState() + + isVideoAvailable.value = core.isVideoCaptureEnabled || core.isVideoPreviewEnabled + callParams.isVideoEnabled = core.videoActivationPolicy.automaticallyInitiate + Log.i("[Conference Waiting Room] Video will be ${if (callParams.isVideoEnabled) "enabled" else "disabled"}") + updateVideoState() + + if (AudioRouteUtils.isBluetoothAudioRouteAvailable()) { + setBluetoothAudioRoute() + } else if (isVideoAvailable.value == true && isVideoEnabled.value == true) { + setSpeakerAudioRoute() + } else { + setEarpieceAudioRoute() + } + updateAudioRouteState() + } + + override fun onCleared() { + coreContext.core.removeListener(listener) + + super.onCleared() + } + + fun cancel() { + cancelConferenceJoiningEvent.value = Event(true) + } + + fun start() { + joinInProgress.value = true + joinConferenceEvent.value = Event(callParams) + } + + fun toggleMuteMicrophone() { + if (!PermissionHelper.get().hasRecordAudioPermission()) { + askPermissionEvent.value = Event(Manifest.permission.RECORD_AUDIO) + return + } + + callParams.isMicEnabled = !callParams.isMicEnabled + Log.i("[Conference Waiting Room] Microphone will be ${if (callParams.isMicEnabled) "enabled" else "muted"}") + updateMicState() + } + + fun enableMic() { + Log.i("[Conference Waiting Room] Microphone will be enabled") + callParams.isMicEnabled = true + updateMicState() + } + + fun toggleSpeaker() { + if (isSpeakerSelected.value == true) { + setEarpieceAudioRoute() + } else { + setSpeakerAudioRoute() + } + } + + fun toggleAudioRoutesMenu() { + audioRoutesSelected.value = audioRoutesSelected.value != true + } + + fun setBluetoothAudioRoute() { + Log.i("[Conference Waiting Room] Set default output audio device to Bluetooth") + callParams.outputAudioDevice = coreContext.core.audioDevices.find { + it.type == AudioDevice.Type.Bluetooth && it.hasCapability(AudioDevice.Capabilities.CapabilityPlay) + } + callParams.inputAudioDevice = coreContext.core.audioDevices.find { + it.type == AudioDevice.Type.Bluetooth && it.hasCapability(AudioDevice.Capabilities.CapabilityRecord) + } + updateAudioRouteState() + } + + fun setSpeakerAudioRoute() { + Log.i("[Conference Waiting Room] Set default output audio device to Speaker") + callParams.outputAudioDevice = coreContext.core.audioDevices.find { + it.type == AudioDevice.Type.Speaker && it.hasCapability(AudioDevice.Capabilities.CapabilityPlay) + } + callParams.inputAudioDevice = coreContext.core.audioDevices.find { + it.type == AudioDevice.Type.Microphone && it.hasCapability(AudioDevice.Capabilities.CapabilityRecord) + } + updateAudioRouteState() + } + + fun setEarpieceAudioRoute() { + Log.i("[Conference Waiting Room] Set default output audio device to Earpiece") + callParams.outputAudioDevice = coreContext.core.audioDevices.find { + it.type == AudioDevice.Type.Earpiece && it.hasCapability(AudioDevice.Capabilities.CapabilityPlay) + } + callParams.inputAudioDevice = coreContext.core.audioDevices.find { + it.type == AudioDevice.Type.Microphone && it.hasCapability(AudioDevice.Capabilities.CapabilityRecord) + } + updateAudioRouteState() + } + + fun toggleVideo() { + if (!PermissionHelper.get().hasCameraPermission()) { + askPermissionEvent.value = Event(Manifest.permission.CAMERA) + return + } + callParams.isVideoEnabled = !callParams.isVideoEnabled + Log.i("[Conference Waiting Room] Video will be ${if (callParams.isVideoEnabled) "enabled" else "disabled"}") + updateVideoState() + } + + fun enableVideo() { + Log.i("[Conference Waiting Room] Video will be enabled") + callParams.isVideoEnabled = true + updateVideoState() + } + + fun switchCamera() { + Log.i("[Conference Waiting Room] Switching camera") + coreContext.switchCamera() + } + + private fun updateMicState() { + isMicrophoneMuted.value = !callParams.isMicEnabled + } + + private fun onAudioDevicesListUpdated() { + val bluetoothDeviceAvailable = AudioRouteUtils.isBluetoothAudioRouteAvailable() + audioRoutesEnabled.value = bluetoothDeviceAvailable + + if (!bluetoothDeviceAvailable) { + audioRoutesSelected.value = false + Log.w("[Conference Waiting Room] Bluetooth device no longer available, switching back to default microphone & earpiece/speaker") + if (isBluetoothHeadsetSelected.value == true) { + for (audioDevice in coreContext.core.audioDevices) { + if (isVideoEnabled.value == true) { + if (audioDevice.type == AudioDevice.Type.Speaker) { + callParams.outputAudioDevice = audioDevice + } + } else { + if (audioDevice.type == AudioDevice.Type.Earpiece) { + callParams.outputAudioDevice = audioDevice + } + } + if (audioDevice.type == AudioDevice.Type.Microphone) { + callParams.inputAudioDevice = audioDevice + } + } + } + } + + updateAudioRouteState() + } + + private fun updateAudioRouteState() { + val outputDeviceType = callParams.outputAudioDevice?.type + isSpeakerSelected.value = outputDeviceType == AudioDevice.Type.Speaker + isBluetoothHeadsetSelected.value = outputDeviceType == AudioDevice.Type.Bluetooth + } + + private fun updateVideoState() { + isVideoEnabled.value = callParams.isVideoEnabled + isSwitchCameraAvailable.value = callParams.isVideoEnabled && coreContext.showSwitchCameraButton() + coreContext.core.isVideoPreviewEnabled = callParams.isVideoEnabled + } +} diff --git a/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ScheduledConferencesViewModel.kt b/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ScheduledConferencesViewModel.kt new file mode 100644 index 000000000..5e2355d44 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ScheduledConferencesViewModel.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.conference.viewmodels + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.activities.main.conference.data.ScheduledConferenceData +import org.linphone.core.ConferenceInfo +import org.linphone.core.Core +import org.linphone.core.CoreListenerStub +import org.linphone.core.tools.Log + +class ScheduledConferencesViewModel : ViewModel() { + val conferences = MutableLiveData>() + + private val listener = object : CoreListenerStub() { + override fun onConferenceInfoReceived(core: Core, conferenceInfo: ConferenceInfo) { + Log.i("[Scheduled Conferences] New conference info received") + val conferencesList = arrayListOf() + conferencesList.addAll(conferences.value.orEmpty()) + val data = ScheduledConferenceData(conferenceInfo) + conferencesList.add(data) + conferences.value = conferencesList + } + } + + init { + coreContext.core.addListener(listener) + computeConferenceInfoList() + } + + override fun onCleared() { + coreContext.core.removeListener(listener) + conferences.value.orEmpty().forEach(ScheduledConferenceData::destroy) + super.onCleared() + } + + fun deleteConferenceInfo(data: ScheduledConferenceData) { + val conferenceInfoList = arrayListOf() + + conferenceInfoList.addAll(conferences.value.orEmpty()) + conferenceInfoList.remove(data) + + data.delete() + data.destroy() + conferences.value = conferenceInfoList + } + + private fun computeConferenceInfoList() { + conferences.value.orEmpty().forEach(ScheduledConferenceData::destroy) + + val conferencesList = arrayListOf() + + val now = System.currentTimeMillis() / 1000 // Linphone uses time_t in seconds + val oneHourAgo = now - 3600 // Show all conferences from 1 hour ago and forward + for (conferenceInfo in coreContext.core.getConferenceInformationListAfterTime(oneHourAgo)) { + val data = ScheduledConferenceData(conferenceInfo) + conferencesList.add(data) + } + + conferences.value = conferencesList + Log.i("[Scheduled Conferences] Found ${conferencesList.size} future conferences") + } +} diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt index ac738abd4..552245bf6 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt @@ -138,7 +138,7 @@ class ContactEditorFragment : GenericFragment(), S Log.i("[Contact Editor] WRITE_CONTACTS permission granted") } else { Log.w("[Contact Editor] WRITE_CONTACTS permission denied") - (requireActivity() as MainActivity).showSnackBar(R.string.contact_editor_write_permission_denied) + (activity as MainActivity).showSnackBar(R.string.contact_editor_write_permission_denied) goBack() } } diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/DetailContactFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/DetailContactFragment.kt index cf7384fae..dff171a33 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/DetailContactFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/fragments/DetailContactFragment.kt @@ -71,7 +71,6 @@ class DetailContactFragment : GenericFragment() { val contact = sharedViewModel.selectedContact.value if (contact == null) { Log.e("[Contact] Contact is null, aborting!") - // (activity as MainActivity).showSnackBar(R.string.error) goBack() return } @@ -140,7 +139,7 @@ class DetailContactFragment : GenericFragment() { confirmContactRemoval() } - viewModel.onErrorEvent.observe( + viewModel.onMessageToNotifyEvent.observe( viewLifecycleOwner ) { it.consume { messageResourceId -> diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt index bc419400f..540dad03c 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt @@ -138,7 +138,6 @@ class MasterContactsFragment : MasterFragment = MutableLiveData() override val displayName: MutableLiveData = MutableLiveData() override val securityLevel: MutableLiveData = MutableLiveData() @@ -91,7 +91,7 @@ class ContactViewModel(val contactInternal: Contact) : ErrorReportingViewModel() } else if (state == ChatRoom.State.CreationFailed) { Log.e("[Contact Detail] Group chat room creation has failed !") waitForChatRoomCreation.value = false - onErrorEvent.value = Event(R.string.chat_room_creation_failed_snack) + onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) } } } @@ -115,7 +115,7 @@ class ContactViewModel(val contactInternal: Contact) : ErrorReportingViewModel() } else { waitForChatRoomCreation.value = false Log.e("[Contact Detail] Couldn't create chat room with address $address") - onErrorEvent.value = Event(R.string.chat_room_creation_failed_snack) + onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) } } diff --git a/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt b/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt index 04791ca56..de60f66b9 100644 --- a/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt @@ -31,6 +31,7 @@ import android.content.res.Configuration import android.net.Uri import android.os.Bundle import android.view.View +import android.widget.Toast import androidx.lifecycle.ViewModelProvider import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.transition.MaterialSharedAxis @@ -43,6 +44,7 @@ import org.linphone.activities.main.dialer.viewmodels.DialerViewModel import org.linphone.activities.main.fragments.SecureFragment import org.linphone.activities.main.viewmodels.DialogViewModel import org.linphone.activities.main.viewmodels.SharedMainViewModel +import org.linphone.activities.navigateToConferenceScheduling import org.linphone.activities.navigateToConfigFileViewer import org.linphone.activities.navigateToContacts import org.linphone.compatibility.Compatibility @@ -103,11 +105,17 @@ class DialerFragment : SecureFragment() { navigateToContacts(viewModel.enteredUri.value) } + binding.setNewConferenceClickListener { + sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.conferenceSchedulingFragment) + navigateToConferenceScheduling() + } + binding.setTransferCallClickListener { if (viewModel.transferCall()) { // Transfer has been consumed, otherwise it might have been a "bis" use sharedViewModel.pendingCallTransfer = false viewModel.transferVisibility.value = false + coreContext.onCallStarted() } } @@ -147,6 +155,14 @@ class DialerFragment : SecureFragment() { } } + viewModel.onMessageToNotifyEvent.observe( + viewLifecycleOwner + ) { + it.consume { id -> + Toast.makeText(requireContext(), id, Toast.LENGTH_SHORT).show() + } + } + if (corePreferences.firstStart) { Log.w("[Dialer] First start detected, wait for assistant to be finished to check for update & request permissions") return diff --git a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt b/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt index 964399d6b..45874eb05 100644 --- a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt @@ -47,6 +47,8 @@ class DialerViewModel : LogsUploadViewModel() { val autoInitiateVideoCalls = MutableLiveData() + val scheduleConferenceAvailable = MutableLiveData() + val updateAvailableEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -136,6 +138,7 @@ class DialerViewModel : LogsUploadViewModel() { transferVisibility.value = false showSwitchCamera.value = coreContext.showSwitchCameraButton() + scheduleConferenceAvailable.value = LinphoneUtils.isRemoteConferencingAvailable() } override fun onCleared() { @@ -195,7 +198,13 @@ class DialerViewModel : LogsUploadViewModel() { fun transferCall(): Boolean { val addressToCall = enteredUri.value.orEmpty() return if (addressToCall.isNotEmpty()) { - coreContext.transferCallTo(addressToCall) + onMessageToNotifyEvent.value = Event( + if (coreContext.transferCallTo(addressToCall)) { + org.linphone.R.string.dialer_transfer_succeded + } else { + org.linphone.R.string.dialer_transfer_failed + } + ) eraseAll() true } else { diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt index 1f6877079..8dd25f62d 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt @@ -48,7 +48,6 @@ class AudioViewerFragment : GenericViewerFragment : SecureFragment() val content = sharedViewModel.contentToOpen.value if (content == null) { Log.e("[Generic Viewer] Content is null, aborting!") - // (activity as MainActivity).showSnackBar(R.string.error) findNavController().navigateUp() return } diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt index daae3d5aa..2bab58d08 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt @@ -44,7 +44,6 @@ class ImageViewerFragment : GenericViewerFragment() val content = sharedViewModel.contentToOpen.value if (content == null) { Log.e("[PDF Viewer] Content is null, aborting!") - // (activity as MainActivity).showSnackBar(R.string.error) findNavController().navigateUp() return } diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt index ea81e9124..470ce50c1 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt @@ -42,7 +42,6 @@ class TextViewerFragment : GenericViewerFragment( val content = sharedViewModel.contentToOpen.value if (content == null) { Log.e("[Text Viewer] Content is null, aborting!") - // (activity as MainActivity).showSnackBar(R.string.error) findNavController().navigateUp() return } diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt index f0efad396..f82711429 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt @@ -46,7 +46,6 @@ class VideoViewerFragment : GenericViewerFragment. - */ -package org.linphone.activities.main.history.data - -import java.text.SimpleDateFormat -import java.util.* -import org.linphone.R -import org.linphone.contact.GenericContactData -import org.linphone.core.Call -import org.linphone.core.CallLog -import org.linphone.utils.TimestampUtils - -class CallLogData(callLog: CallLog) : GenericContactData(callLog.remoteAddress) { - val statusIconResource: Int by lazy { - if (callLog.dir == Call.Dir.Incoming) { - if (callLog.status == Call.Status.Missed) { - R.drawable.call_status_missed - } else { - R.drawable.call_status_incoming - } - } else { - R.drawable.call_status_outgoing - } - } - - val iconContentDescription: Int by lazy { - if (callLog.dir == Call.Dir.Incoming) { - if (callLog.status == Call.Status.Missed) { - R.string.content_description_missed_call - } else { - R.string.content_description_incoming_call - } - } else { - R.string.content_description_outgoing_call - } - } - - val directionIconResource: Int by lazy { - if (callLog.dir == Call.Dir.Incoming) { - if (callLog.status == Call.Status.Missed) { - R.drawable.call_missed - } else { - R.drawable.call_incoming - } - } else { - R.drawable.call_outgoing - } - } - - val duration: String by lazy { - val dateFormat = SimpleDateFormat(if (callLog.duration >= 3600) "HH:mm:ss" else "mm:ss", Locale.getDefault()) - val cal = Calendar.getInstance() - cal[0, 0, 0, 0, 0] = callLog.duration - dateFormat.format(cal.time) - } - - val date: String by lazy { - TimestampUtils.toString(callLog.startDate, shortDate = false, hideYear = false) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt b/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt index ea76e8fe8..e00a9c5dd 100644 --- a/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt +++ b/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt @@ -19,14 +19,18 @@ */ package org.linphone.activities.main.history.data +import org.linphone.activities.main.history.viewmodels.CallLogViewModel import org.linphone.core.CallLog class GroupedCallLogData(callLog: CallLog) { var lastCallLog: CallLog = callLog val callLogs = arrayListOf(callLog) - val lastCallLogData = CallLogData(lastCallLog) + val lastCallLogViewModel: CallLogViewModel + get() { + return CallLogViewModel(lastCallLog) + } fun destroy() { - lastCallLogData.destroy() + lastCallLogViewModel.destroy() } } diff --git a/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt b/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt index 825921125..f444e86e2 100644 --- a/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt @@ -57,7 +57,6 @@ class DetailCallLogFragment : GenericFragment() { val callLogGroup = sharedViewModel.selectedCallLogGroup.value if (callLogGroup == null) { Log.e("[History] Call log group is null, aborting!") - // (activity as MainActivity).showSnackBar(R.string.error) findNavController().navigateUp() return } @@ -70,7 +69,7 @@ class DetailCallLogFragment : GenericFragment() { useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false - viewModel.addRelatedCallLogs(callLogGroup.callLogs) + viewModel.relatedCallLogs.value = callLogGroup.callLogs binding.setBackClickListener { goBack() @@ -134,7 +133,7 @@ class DetailCallLogFragment : GenericFragment() { } } - viewModel.onErrorEvent.observe( + viewModel.onMessageToNotifyEvent.observe( viewLifecycleOwner ) { it.consume { messageResourceId -> diff --git a/app/src/main/java/org/linphone/activities/main/history/fragments/DetailConferenceCallLogFragment.kt b/app/src/main/java/org/linphone/activities/main/history/fragments/DetailConferenceCallLogFragment.kt new file mode 100644 index 000000000..efcd9b5fe --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/history/fragments/DetailConferenceCallLogFragment.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.history.fragments + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import org.linphone.R +import org.linphone.activities.* +import org.linphone.activities.main.* +import org.linphone.activities.main.history.viewmodels.CallLogViewModel +import org.linphone.activities.main.history.viewmodels.CallLogViewModelFactory +import org.linphone.activities.main.viewmodels.SharedMainViewModel +import org.linphone.core.tools.Log +import org.linphone.databinding.HistoryConfDetailFragmentBinding +import org.linphone.utils.Event + +class DetailConferenceCallLogFragment : GenericFragment() { + private lateinit var viewModel: CallLogViewModel + private lateinit var sharedViewModel: SharedMainViewModel + + override fun getLayoutId(): Int = R.layout.history_conf_detail_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + sharedViewModel = requireActivity().run { + ViewModelProvider(this)[SharedMainViewModel::class.java] + } + binding.sharedMainViewModel = sharedViewModel + + val callLogGroup = sharedViewModel.selectedCallLogGroup.value + if (callLogGroup == null) { + Log.e("[History] Call log group is null, aborting!") + findNavController().navigateUp() + return + } + + viewModel = ViewModelProvider( + this, + CallLogViewModelFactory(callLogGroup.lastCallLog) + )[CallLogViewModel::class.java] + binding.viewModel = viewModel + + useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false + + viewModel.relatedCallLogs.value = callLogGroup.callLogs + + binding.setBackClickListener { + goBack() + } + + viewModel.onMessageToNotifyEvent.observe( + viewLifecycleOwner + ) { + it.consume { messageResourceId -> + (activity as MainActivity).showSnackBar(messageResourceId) + } + } + } + + override fun goBack() { + if (sharedViewModel.isSlidingPaneSlideable.value == true) { + sharedViewModel.closeSlidingPaneEvent.value = Event(true) + } else { + navigateToEmptyCallHistory() + } + } +} diff --git a/app/src/main/java/org/linphone/activities/main/history/fragments/MasterCallLogsFragment.kt b/app/src/main/java/org/linphone/activities/main/history/fragments/MasterCallLogsFragment.kt index bbd2e0cfa..389a74be9 100644 --- a/app/src/main/java/org/linphone/activities/main/history/fragments/MasterCallLogsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/history/fragments/MasterCallLogsFragment.kt @@ -35,6 +35,7 @@ import com.google.android.material.transition.MaterialSharedAxis import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R +import org.linphone.activities.* import org.linphone.activities.clearDisplayedCallHistory import org.linphone.activities.main.fragments.MasterFragment import org.linphone.activities.main.history.adapters.CallLogsListAdapter @@ -44,6 +45,7 @@ import org.linphone.activities.main.viewmodels.DialogViewModel import org.linphone.activities.main.viewmodels.SharedMainViewModel import org.linphone.activities.main.viewmodels.TabsViewModel import org.linphone.activities.navigateToCallHistory +import org.linphone.activities.navigateToConferenceCallHistory import org.linphone.activities.navigateToDialer import org.linphone.core.tools.Log import org.linphone.databinding.HistoryMasterFragmentBinding @@ -125,7 +127,6 @@ class MasterCallLogsFragment : MasterFragment - if (listViewModel.missedCallLogsSelected.value == false) { - adapter.submitList(callLogs) - } - } - - listViewModel.missedCallLogs.observe( - viewLifecycleOwner - ) { callLogs -> - if (listViewModel.missedCallLogsSelected.value == true) { - adapter.submitList(callLogs) - } - } - - listViewModel.missedCallLogsSelected.observe( - viewLifecycleOwner - ) { - if (it) { - adapter.submitList(listViewModel.missedCallLogs.value) - } else { - adapter.submitList(listViewModel.callLogs.value) - } + adapter.submitList(callLogs) } listViewModel.contactsUpdatedEvent.observe( @@ -230,24 +211,29 @@ class MasterCallLogsFragment : MasterFragment sharedViewModel.selectedCallLogGroup.value = callLog - navigateToCallHistory(binding.slidingPane) + if (callLog.lastCallLog.wasConference()) { + navigateToConferenceCallHistory(binding.slidingPane) + } else { + navigateToCallHistory(binding.slidingPane) + } } } adapter.startCallToEvent.observe( - viewLifecycleOwner + viewLifecycleOwner, ) { it.consume { callLogGroup -> val remoteAddress = callLogGroup.lastCallLog.remoteAddress - if (coreContext.core.callsNb > 0) { + val conferenceInfo = coreContext.core.findConferenceInformationFromUri(remoteAddress) + if (conferenceInfo != null) { + navigateToConferenceWaitingRoom(remoteAddress.asStringUriOnly(), conferenceInfo.subject) + } else if (coreContext.core.callsNb > 0) { Log.i("[History] Starting dialer with pre-filled URI ${remoteAddress.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}") - sharedViewModel.updateDialerAnimationsBasedOnDestination.value = - Event(R.id.masterCallLogsFragment) + sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment) val args = Bundle() args.putString("URI", remoteAddress.asStringUriOnly()) args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer) - // If auto start call setting is enabled, ignore it - args.putBoolean("SkipAutoCallStart", true) + args.putBoolean("SkipAutoCallStart", true) // If auto start call setting is enabled, ignore it navigateToDialer(args) } else { val localAddress = callLogGroup.lastCallLog.localAddress @@ -256,13 +242,6 @@ class MasterCallLogsFragment : MasterFragment= 3600) "HH:mm:ss" else "mm:ss", Locale.getDefault()) + val cal = Calendar.getInstance() + cal[0, 0, 0, 0, 0] = callLog.duration + dateFormat.format(cal.time) + } + + val date: String by lazy { + TimestampUtils.toString(callLog.startDate, shortDate = false, hideYear = false) + } + val startCallEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -61,17 +112,30 @@ class CallLogViewModel(val callLog: CallLog) : GenericContactViewModel(callLog.r val secureChatAllowed = contact.value?.friend?.getPresenceModelForUriOrTel(peerSipUri)?.hasCapability(FriendCapability.LimeX3Dh) ?: false - val relatedCallLogs = MutableLiveData>() + val relatedCallLogs = MutableLiveData>() private val listener = object : CoreListenerStub() { override fun onCallLogUpdated(core: Core, log: CallLog) { if (callLog.remoteAddress.weakEqual(log.remoteAddress) && callLog.localAddress.weakEqual(log.localAddress)) { Log.i("[History Detail] New call log for ${callLog.remoteAddress.asStringUriOnly()} with local address ${callLog.localAddress.asStringUriOnly()}") - addRelatedCallLogs(arrayListOf(log)) + val list = arrayListOf() + list.add(callLog) + list.addAll(relatedCallLogs.value.orEmpty()) + relatedCallLogs.value = list } } } + val isConferenceCallLog = callLog.wasConference() + + val conferenceSubject = callLog.conferenceInfo?.subject + val conferenceParticipantsData = MutableLiveData>() + val conferenceTime = MutableLiveData() + val conferenceDate = MutableLiveData() + + override val showGroupChatAvatar: Boolean + get() = isConferenceCallLog + private val chatRoomListener = object : ChatRoomListenerStub() { override fun onStateChanged(chatRoom: ChatRoom, state: ChatRoom.State) { if (state == ChatRoom.State.Created) { @@ -80,7 +144,7 @@ class CallLogViewModel(val callLog: CallLog) : GenericContactViewModel(callLog.r } else if (state == ChatRoom.State.CreationFailed) { Log.e("[History Detail] Group chat room creation has failed !") waitForChatRoomCreation.value = false - onErrorEvent.value = Event(R.string.chat_room_creation_failed_snack) + onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) } } } @@ -89,17 +153,31 @@ class CallLogViewModel(val callLog: CallLog) : GenericContactViewModel(callLog.r waitForChatRoomCreation.value = false coreContext.core.addListener(listener) + + val conferenceInfo = callLog.conferenceInfo + if (conferenceInfo != null) { + conferenceTime.value = TimestampUtils.timeToString(conferenceInfo.dateTime) + conferenceDate.value = if (TimestampUtils.isToday(conferenceInfo.dateTime)) { + AppUtils.getString(R.string.today) + } else { + TimestampUtils.toString(conferenceInfo.dateTime, onlyDate = true, shortDate = false, hideYear = false) + } + val list = arrayListOf() + for (participant in conferenceInfo.participants) { + list.add(ConferenceSchedulingParticipantData(participant, false)) + } + conferenceParticipantsData.value = list + } } override fun onCleared() { - coreContext.core.removeListener(listener) destroy() - super.onCleared() } fun destroy() { - relatedCallLogs.value.orEmpty().forEach(CallLogData::destroy) + coreContext.core.removeListener(listener) + conferenceParticipantsData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy) } fun startCall() { @@ -119,19 +197,7 @@ class CallLogViewModel(val callLog: CallLog) : GenericContactViewModel(callLog.r } else { waitForChatRoomCreation.value = false Log.e("[History Detail] Couldn't create chat room with address ${callLog.remoteAddress}") - onErrorEvent.value = Event(R.string.chat_room_creation_failed_snack) + onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) } } - - fun addRelatedCallLogs(logs: ArrayList) { - val callsHistory = ArrayList() - - // We assume new logs are more recent than the ones we already have, so we add them first - for (log in logs) { - callsHistory.add(CallLogData(log)) - } - callsHistory.addAll(relatedCallLogs.value.orEmpty()) - - relatedCallLogs.value = callsHistory - } } diff --git a/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogsListViewModel.kt b/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogsListViewModel.kt index a14fcc859..a4c179fff 100644 --- a/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogsListViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogsListViewModel.kt @@ -31,15 +31,20 @@ import org.linphone.utils.LinphoneUtils import org.linphone.utils.TimestampUtils class CallLogsListViewModel : ViewModel() { - val callLogs = MutableLiveData>() - val missedCallLogs = MutableLiveData>() + val displayedCallLogs = MutableLiveData>() - val missedCallLogsSelected = MutableLiveData() + val filter = MutableLiveData() + + val showConferencesFilter = MutableLiveData() val contactsUpdatedEvent: MutableLiveData> by lazy { MutableLiveData>() } + private val callLogs = MutableLiveData>() + private val missedCallLogs = MutableLiveData>() + private val conferenceCallLogs = MutableLiveData>() + private val listener: CoreListenerStub = object : CoreListenerStub() { override fun onCallStateChanged( core: Core, @@ -59,9 +64,11 @@ class CallLogsListViewModel : ViewModel() { } init { - missedCallLogsSelected.value = false + filter.value = CallLogsFilter.ALL updateCallLogs() + showConferencesFilter.value = LinphoneUtils.isRemoteConferencingAvailable() + coreContext.core.addListener(listener) coreContext.contactsManager.addListener(contactsUpdatedListener) } @@ -69,6 +76,8 @@ class CallLogsListViewModel : ViewModel() { override fun onCleared() { callLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) missedCallLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) + conferenceCallLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) + displayedCallLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) coreContext.contactsManager.removeListener(contactsUpdatedListener) coreContext.core.removeListener(listener) @@ -76,6 +85,21 @@ class CallLogsListViewModel : ViewModel() { super.onCleared() } + fun showAllCallLogs() { + filter.value = CallLogsFilter.ALL + applyFilter() + } + + fun showOnlyMissedCallLogs() { + filter.value = CallLogsFilter.MISSED + applyFilter() + } + + fun showOnlyConferenceCallLogs() { + filter.value = CallLogsFilter.CONFERENCE + applyFilter() + } + fun deleteCallLogGroup(callLog: GroupedCallLogData?) { if (callLog != null) { for (log in callLog.callLogs) { @@ -96,61 +120,69 @@ class CallLogsListViewModel : ViewModel() { updateCallLogs() } - private fun updateCallLogs() { - callLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) - missedCallLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) - - val list = arrayListOf() - val missedList = arrayListOf() - + private fun computeCallLogs(callLogs: Array, missed: Boolean, conference: Boolean): ArrayList { var previousCallLogGroup: GroupedCallLogData? = null - var previousMissedCallLogGroup: GroupedCallLogData? = null - for (callLog in coreContext.core.callLogs) { - if (previousCallLogGroup == null) { - previousCallLogGroup = GroupedCallLogData(callLog) - } else if (previousCallLogGroup.lastCallLog.localAddress.weakEqual(callLog.localAddress) && - previousCallLogGroup.lastCallLog.remoteAddress.weakEqual(callLog.remoteAddress) - ) { - if (TimestampUtils.isSameDay(previousCallLogGroup.lastCallLog.startDate, callLog.startDate)) { - previousCallLogGroup.callLogs.add(callLog) - previousCallLogGroup.lastCallLog = callLog + val list = arrayListOf() + + for (callLog in callLogs) { + if ((!missed && !conference) || (missed && LinphoneUtils.isCallLogMissed(callLog)) || (conference && callLog.wasConference())) { + if (previousCallLogGroup == null) { + previousCallLogGroup = GroupedCallLogData(callLog) + } else if (previousCallLogGroup.lastCallLog.localAddress.weakEqual(callLog.localAddress) && + previousCallLogGroup.lastCallLog.remoteAddress.equal(callLog.remoteAddress) + ) { + if (TimestampUtils.isSameDay( + previousCallLogGroup.lastCallLog.startDate, + callLog.startDate + ) + ) { + previousCallLogGroup.callLogs.add(callLog) + previousCallLogGroup.lastCallLog = callLog + } else { + list.add(previousCallLogGroup) + previousCallLogGroup = GroupedCallLogData(callLog) + } } else { list.add(previousCallLogGroup) previousCallLogGroup = GroupedCallLogData(callLog) } - } else { - list.add(previousCallLogGroup) - previousCallLogGroup = GroupedCallLogData(callLog) - } - - if (LinphoneUtils.isCallLogMissed(callLog)) { - if (previousMissedCallLogGroup == null) { - previousMissedCallLogGroup = GroupedCallLogData(callLog) - } else if (previousMissedCallLogGroup.lastCallLog.localAddress.weakEqual(callLog.localAddress) && - previousMissedCallLogGroup.lastCallLog.remoteAddress.weakEqual(callLog.remoteAddress) - ) { - if (TimestampUtils.isSameDay(previousMissedCallLogGroup.lastCallLog.startDate, callLog.startDate)) { - previousMissedCallLogGroup.callLogs.add(callLog) - previousMissedCallLogGroup.lastCallLog = callLog - } else { - missedList.add(previousMissedCallLogGroup) - previousMissedCallLogGroup = GroupedCallLogData(callLog) - } - } else { - missedList.add(previousMissedCallLogGroup) - previousMissedCallLogGroup = GroupedCallLogData(callLog) - } } } - if (previousCallLogGroup != null && !list.contains(previousCallLogGroup)) { list.add(previousCallLogGroup) } - if (previousMissedCallLogGroup != null && !missedList.contains(previousMissedCallLogGroup)) { - missedList.add(previousMissedCallLogGroup) + + return list + } + + private fun updateCallLogs() { + callLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) + missedCallLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) + conferenceCallLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) + + val allCallLogs = coreContext.core.callLogs + callLogs.value = computeCallLogs(allCallLogs, false, false) + missedCallLogs.value = computeCallLogs(allCallLogs, true, false) + conferenceCallLogs.value = computeCallLogs(allCallLogs, false, true) + applyFilter() + } + + private fun applyFilter() { + displayedCallLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) + val displayedList = arrayListOf() + + when (filter.value) { + CallLogsFilter.MISSED -> displayedList.addAll(missedCallLogs.value.orEmpty()) + CallLogsFilter.CONFERENCE -> displayedList.addAll(conferenceCallLogs.value.orEmpty()) + else -> displayedList.addAll(callLogs.value.orEmpty()) } - callLogs.value = list - missedCallLogs.value = missedList + displayedCallLogs.value = displayedList } } + +enum class CallLogsFilter { + ALL, + MISSED, + CONFERENCE +} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt index cb18d3a35..1bd80f1fc 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt @@ -46,7 +46,6 @@ class AccountSettingsFragment : GenericSettingFragment. */ -package org.linphone.activities.call.fragments +package org.linphone.activities.main.settings.fragments import android.os.Bundle import android.view.View import androidx.lifecycle.ViewModelProvider import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.call.viewmodels.StatisticsListViewModel -import org.linphone.databinding.CallStatisticsFragmentBinding +import org.linphone.activities.main.settings.viewmodels.ConferencesSettingsViewModel +import org.linphone.activities.navigateToEmptySetting +import org.linphone.databinding.SettingsConferencesFragmentBinding +import org.linphone.utils.Event -class StatisticsFragment : GenericFragment() { - private lateinit var viewModel: StatisticsListViewModel +class ConferencesSettingsFragment : GenericSettingFragment() { + private lateinit var viewModel: ConferencesSettingsViewModel - override fun getLayoutId(): Int = R.layout.call_statistics_fragment + override fun getLayoutId(): Int = R.layout.settings_conferences_fragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.lifecycleOwner = viewLifecycleOwner - useMaterialSharedAxisXForwardAnimation = false + binding.sharedMainViewModel = sharedViewModel - viewModel = ViewModelProvider(this)[StatisticsListViewModel::class.java] + viewModel = ViewModelProvider(this)[ConferencesSettingsViewModel::class.java] binding.viewModel = viewModel + + binding.setBackClickListener { goBack() } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - onBackPressedCallback.isEnabled = false + override fun goBack() { + if (sharedViewModel.isSlidingPaneSlideable.value == true) { + sharedViewModel.closeSlidingPaneEvent.value = Event(true) + } else { + navigateToEmptySetting() + } } } diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt index 6395507b9..14c4fc816 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt @@ -177,5 +177,11 @@ class SettingsFragment : SecureFragment() { navigateToAdvancedSettings(binding.slidingPane) } } + + viewModel.conferencesSettingsListener = object : SettingListenerStub() { + override fun onClicked() { + navigateToConferencesSettings(binding.slidingPane) + } + } } } diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt index 81760768b..4c5012741 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt @@ -390,6 +390,27 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( } val linkPhoneNumberEvent = MutableLiveData>() + val conferenceFactoryUriListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + val params = account.params.clone() + Log.i("[Account Settings] Forcing conference factory on proxy config ${params.identityAddress?.asString()} to value: $newValue") + params.conferenceFactoryUri = newValue + account.params = params + } + } + val conferenceFactoryUri = MutableLiveData() + + val audioVideoConferenceFactoryUriListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + val params = account.params.clone() + val uri = coreContext.core.interpretUrl(newValue) + Log.i("[Account Settings] Forcing audio/video conference factory on proxy config ${params.identityAddress?.asString()} to value: $newValue") + params.audioVideoConferenceFactoryAddress = uri + account.params = params + } + } + val audioVideoConferenceFactoryUri = MutableLiveData() + init { update() account.addListener(listener) @@ -444,6 +465,9 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( prefix.value = params.internationalPrefix dialPrefix.value = params.useInternationalPrefixForCallsAndChats escapePlus.value = params.isDialEscapePlusEnabled + + conferenceFactoryUri.value = params.conferenceFactoryUri + audioVideoConferenceFactoryUri.value = params.audioVideoConferenceFactoryAddress?.asStringUriOnly() } private fun initTransportList() { diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt index f95ab222e..b52997f90 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt @@ -105,13 +105,6 @@ class CallSettingsViewModel : GenericSettingsViewModel() { } val api26OrHigher = MutableLiveData() - val fullScreenListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.fullScreenCallUI = newValue - } - } - val fullScreen = MutableLiveData() - val overlayListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { prefs.showCallOverlay = newValue @@ -151,6 +144,13 @@ class CallSettingsViewModel : GenericSettingsViewModel() { } val autoStartCallRecording = MutableLiveData() + val remoteCallRecordingListener = object : SettingListenerStub() { + override fun onBoolValueChanged(newValue: Boolean) { + core.isRecordAwareEnabled = newValue + } + } + val remoteCallRecording = MutableLiveData() + val autoStartListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { prefs.callRightAway = newValue @@ -242,12 +242,12 @@ class CallSettingsViewModel : GenericSettingsViewModel() { useTelecomManager.value = prefs.useTelecomManager api26OrHigher.value = Version.sdkAboveOrEqual(Version.API26_O_80) - fullScreen.value = prefs.fullScreenCallUI overlay.value = prefs.showCallOverlay systemWideOverlay.value = prefs.systemWideCallOverlay sipInfoDtmf.value = core.useInfoForDtmf rfc2833Dtmf.value = core.useRfc2833ForDtmf autoStartCallRecording.value = prefs.automaticallyStartCallRecording + remoteCallRecording.value = core.isRecordAwareEnabled autoStart.value = prefs.callRightAway autoAnswer.value = prefs.autoAnswerEnabled autoAnswerDelay.value = prefs.autoAnswerDelay diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ConferencesSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ConferencesSettingsViewModel.kt new file mode 100644 index 000000000..52d1c005c --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ConferencesSettingsViewModel.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2022 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.main.settings.viewmodels + +import androidx.lifecycle.MutableLiveData +import org.linphone.R +import org.linphone.activities.main.settings.SettingListenerStub +import org.linphone.core.ConferenceLayout + +class ConferencesSettingsViewModel : GenericSettingsViewModel() { + val layoutListener = object : SettingListenerStub() { + override fun onListValueChanged(position: Int) { + core.defaultConferenceLayout = ConferenceLayout.fromInt(layoutValues[position]) + layoutIndex.value = position + } + } + val layoutIndex = MutableLiveData() + val layoutLabels = MutableLiveData>() + private val layoutValues = arrayListOf() + + init { + initLayoutsList() + } + + private fun initLayoutsList() { + val labels = arrayListOf() + + labels.add(prefs.getString(R.string.conference_display_mode_active_speaker)) + layoutValues.add(ConferenceLayout.ActiveSpeaker.toInt()) + + labels.add(prefs.getString(R.string.conference_display_mode_mosaic)) + layoutValues.add(ConferenceLayout.Grid.toInt()) + + layoutLabels.value = labels + layoutIndex.value = layoutValues.indexOf(core.defaultConferenceLayout.toInt()) + } +} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/SettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/SettingsViewModel.kt index 258ebed0d..74bf954dd 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/SettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/SettingsViewModel.kt @@ -37,6 +37,7 @@ class SettingsViewModel : ViewModel() { val showNetworkSettings: Boolean = corePreferences.showNetworkSettings val showContactsSettings: Boolean = corePreferences.showContactsSettings val showAdvancedSettings: Boolean = corePreferences.showAdvancedSettings + val showConferencesSettings: Boolean = corePreferences.showConferencesSettings val accounts = MutableLiveData>() @@ -64,6 +65,8 @@ class SettingsViewModel : ViewModel() { lateinit var advancedSettingsListener: SettingListenerStub + lateinit var conferencesSettingsListener: SettingListenerStub + val primaryAccountDisplayNameListener = object : SettingListenerStub() { override fun onTextValueChanged(newValue: String) { val address = coreContext.core.createPrimaryContactParsed() diff --git a/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt b/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt index 346d6d3d7..fc35e9a18 100644 --- a/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt @@ -32,7 +32,7 @@ import java.io.File import kotlinx.coroutines.launch import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R -import org.linphone.activities.GenericFragment +import org.linphone.activities.* import org.linphone.activities.assistant.AssistantActivity import org.linphone.activities.main.settings.SettingListenerStub import org.linphone.activities.main.sidemenu.viewmodels.SideMenuViewModel @@ -108,6 +108,11 @@ class SideMenuFragment : GenericFragment() { navigateToAbout() } + binding.setConferencesClickListener { + sharedViewModel.toggleDrawerEvent.value = Event(true) + navigateToScheduledConferences() + } + binding.setQuitClickListener { Log.i("[Side Menu] Quitting app") requireActivity().finishAndRemoveTask() diff --git a/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt b/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt index 9ed09c824..9fdce5da2 100644 --- a/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt @@ -26,12 +26,15 @@ import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.activities.main.settings.SettingListenerStub import org.linphone.activities.main.settings.viewmodels.AccountSettingsViewModel import org.linphone.core.* +import org.linphone.utils.LinphoneUtils class SideMenuViewModel : ViewModel() { val showAccounts: Boolean = corePreferences.showAccountsInSideMenu val showAssistant: Boolean = corePreferences.showAssistantInSideMenu val showSettings: Boolean = corePreferences.showSettingsInSideMenu val showRecordings: Boolean = corePreferences.showRecordingsInSideMenu + val showScheduledConferences: Boolean = corePreferences.showScheduledConferencesInSideMenu && + LinphoneUtils.isRemoteConferencingAvailable() val showAbout: Boolean = corePreferences.showAboutInSideMenu val showQuit: Boolean = corePreferences.showQuitInSideMenu diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/CallOverlayViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/CallOverlayViewModel.kt index 88807c6fa..79863593f 100644 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/CallOverlayViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/viewmodels/CallOverlayViewModel.kt @@ -26,6 +26,7 @@ import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.core.Call import org.linphone.core.Core import org.linphone.core.CoreListenerStub +import org.linphone.core.tools.Log class CallOverlayViewModel : ViewModel() { val displayCallOverlay = MutableLiveData() @@ -34,17 +35,19 @@ class CallOverlayViewModel : ViewModel() { override fun onCallStateChanged( core: Core, call: Call, - state: Call.State, + state: Call.State?, message: String ) { - if (state == Call.State.IncomingReceived || state == Call.State.OutgoingInit) { + if (core.callsNb == 1 && call.state == Call.State.Connected) { + Log.i("[Call Overlay] First call connected, creating it") createCallOverlay() - } else if (state == Call.State.End || state == Call.State.Error || state == Call.State.Released) { - if (core.callsNb == 0) { - removeCallOverlay() - } } } + + override fun onLastCallEnded(core: Core) { + Log.i("[Call Overlay] Last call ended, removing it") + removeCallOverlay() + } } init { diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/LogsUploadViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/LogsUploadViewModel.kt index 337938289..ca9a083df 100644 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/LogsUploadViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/viewmodels/LogsUploadViewModel.kt @@ -21,13 +21,12 @@ package org.linphone.activities.main.viewmodels import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.core.Core import org.linphone.core.CoreListenerStub import org.linphone.utils.Event -open class LogsUploadViewModel : ViewModel() { +open class LogsUploadViewModel : MessageNotifierViewModel() { val uploadInProgress = MutableLiveData() val resetCompleteEvent: MutableLiveData> by lazy { diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/ErrorReportingViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/MessageNotifierViewModel.kt similarity index 83% rename from app/src/main/java/org/linphone/activities/main/viewmodels/ErrorReportingViewModel.kt rename to app/src/main/java/org/linphone/activities/main/viewmodels/MessageNotifierViewModel.kt index 24c7a3924..6e81ee8db 100644 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/ErrorReportingViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/viewmodels/MessageNotifierViewModel.kt @@ -23,7 +23,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import org.linphone.utils.Event -/* Helper for view models to notify user of an error through a Snackbar */ -abstract class ErrorReportingViewModel : ViewModel() { - val onErrorEvent = MutableLiveData>() +/* Helper for view models to notify user of a massage through a Snackbar */ +abstract class MessageNotifierViewModel : ViewModel() { + val onMessageToNotifyEvent = MutableLiveData>() } diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/TabsViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/TabsViewModel.kt index e7fb0162b..d6578e55d 100644 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/TabsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/viewmodels/TabsViewModel.kt @@ -101,7 +101,6 @@ class TabsViewModel : ViewModel() { } override fun onCleared() { - if (corePreferences.enableAnimations) bounceAnimator.end() coreContext.core.removeListener(listener) super.onCleared() } diff --git a/app/src/main/java/org/linphone/activities/voip/CallActivity.kt b/app/src/main/java/org/linphone/activities/voip/CallActivity.kt new file mode 100644 index 000000000..70a4f8fb2 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/CallActivity.kt @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip + +import android.Manifest +import android.annotation.TargetApi +import android.content.Intent +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.os.Bundle +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.findNavController +import androidx.window.layout.FoldingFeature +import org.linphone.LinphoneApplication +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R +import org.linphone.activities.ProximitySensorActivity +import org.linphone.activities.main.MainActivity +import org.linphone.activities.navigateToActiveCall +import org.linphone.activities.navigateToIncomingCall +import org.linphone.activities.navigateToOutgoingCall +import org.linphone.activities.voip.viewmodels.CallsViewModel +import org.linphone.activities.voip.viewmodels.ControlsViewModel +import org.linphone.compatibility.Compatibility +import org.linphone.core.Call +import org.linphone.core.tools.Log +import org.linphone.databinding.VoipActivityBinding +import org.linphone.mediastream.Version +import org.linphone.utils.PermissionHelper + +class CallActivity : ProximitySensorActivity() { + private lateinit var binding: VoipActivityBinding + private lateinit var controlsViewModel: ControlsViewModel + private lateinit var callsViewModel: CallsViewModel + + private var foldingFeature: FoldingFeature? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + Compatibility.setShowWhenLocked(this, true) + Compatibility.setTurnScreenOn(this, true) + // Leaks on API 27+: https://stackoverflow.com/questions/60477120/keyguardmanager-memory-leak + Compatibility.requestDismissKeyguard(this) + + binding = DataBindingUtil.setContentView(this, R.layout.voip_activity) + binding.lifecycleOwner = this + } + + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) + + // This can't be done in onCreate(), has to be at least in onPostCreate() ! + val navController = binding.navHostFragment.findNavController() + val navControllerStoreOwner = navController.getViewModelStoreOwner(R.id.call_nav_graph) + + controlsViewModel = ViewModelProvider(navControllerStoreOwner)[ControlsViewModel::class.java] + binding.controlsViewModel = controlsViewModel + + callsViewModel = ViewModelProvider(navControllerStoreOwner)[CallsViewModel::class.java] + + callsViewModel.noMoreCallEvent.observe( + this + ) { + it.consume { + finish() + } + } + + controlsViewModel.askPermissionEvent.observe( + this + ) { + it.consume { permission -> + Log.i("[Call] Asking for $permission permission") + requestPermissions(arrayOf(permission), 0) + } + } + + controlsViewModel.fullScreenMode.observe( + this + ) { hide -> + Compatibility.hideAndroidSystemUI(hide, window) + } + + controlsViewModel.proximitySensorEnabled.observe( + this + ) { enabled -> + enableProximitySensor(enabled) + } + + controlsViewModel.isVideoEnabled.observe( + this + ) { enabled -> + Compatibility.enableAutoEnterPiP(this, enabled) + } + + if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) { + checkPermissions() + } + } + + override fun onUserLeaveHint() { + super.onUserLeaveHint() + + if (coreContext.core.currentCall?.currentParams?.isVideoEnabled ?: false) { + Log.i("[Call] Entering PiP mode") + Compatibility.enterPipMode(this) + } + } + + override fun onPictureInPictureModeChanged( + isInPictureInPictureMode: Boolean, + newConfig: Configuration + ) { + Log.i("[Call] Activity is in PiP mode? $isInPictureInPictureMode") + if (::controlsViewModel.isInitialized) { + // To hide UI except for TextureViews + controlsViewModel.pipMode.value = isInPictureInPictureMode + } + } + + override fun onResume() { + super.onResume() + + if (coreContext.core.callsNb == 0) { + Log.w("[Call] Resuming but no call found...") + if (isTaskRoot) { + // When resuming app from recent tasks make sure MainActivity will be launched if there is no call + val intent = Intent() + intent.setClass(this, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + startActivity(intent) + } else { + finish() + } + } else { + coreContext.removeCallOverlay() + + val currentCall = coreContext.core.currentCall + if (currentCall == null) { + Log.e("[Call] No current call found, assume active call") + navigateToActiveCall() + return + } + + when (currentCall.state) { + Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia, Call.State.OutgoingProgress, Call.State.OutgoingRinging -> { + navigateToOutgoingCall() + } + Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> { + val earlyMediaVideoEnabled = LinphoneApplication.corePreferences.acceptEarlyMedia && + currentCall.state == Call.State.IncomingEarlyMedia && + currentCall.currentParams.isVideoEnabled + navigateToIncomingCall(earlyMediaVideoEnabled) + } + else -> navigateToActiveCall() + } + } + } + + override fun onPause() { + val core = coreContext.core + if (core.callsNb > 0) { + coreContext.createCallOverlay() + } + + super.onPause() + } + + override fun onDestroy() { + coreContext.core.nativeVideoWindowId = null + coreContext.core.nativePreviewWindowId = null + + super.onDestroy() + } + + @TargetApi(Version.API23_MARSHMALLOW_60) + private fun checkPermissions() { + val permissionsRequiredList = arrayListOf() + if (!PermissionHelper.get().hasRecordAudioPermission()) { + Log.i("[Call] Asking for RECORD_AUDIO permission") + permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO) + } + if (callsViewModel.currentCallData.value?.call?.currentParams?.isVideoEnabled == true && + !PermissionHelper.get().hasCameraPermission() + ) { + Log.i("[Call] Asking for CAMERA permission") + permissionsRequiredList.add(Manifest.permission.CAMERA) + } + if (permissionsRequiredList.isNotEmpty()) { + val permissionsRequired = arrayOfNulls(permissionsRequiredList.size) + permissionsRequiredList.toArray(permissionsRequired) + requestPermissions(permissionsRequired, 0) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + if (requestCode == 0) { + for (i in permissions.indices) { + when (permissions[i]) { + Manifest.permission.RECORD_AUDIO -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { + Log.i("[Call] RECORD_AUDIO permission has been granted") + controlsViewModel.updateMicState() + } + Manifest.permission.CAMERA -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { + Log.i("[Call] CAMERA permission has been granted") + coreContext.core.reloadVideoDevices() + } + } + } + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + } + + override fun onLayoutChanges(foldingFeature: FoldingFeature?) { + this.foldingFeature = foldingFeature + updateConstraintSetDependingOnFoldingState() + } + + private fun updateConstraintSetDependingOnFoldingState() { + /*val feature = foldingFeature ?: return + val constraintLayout = binding.constraintLayout + val set = ConstraintSet() + set.clone(constraintLayout) + + if (feature.state == FoldingFeature.State.HALF_OPENED && viewModel.videoEnabled.value == true) { + set.setGuidelinePercent(R.id.hinge_top, 0.5f) + set.setGuidelinePercent(R.id.hinge_bottom, 0.5f) + viewModel.disable(true) + } else { + set.setGuidelinePercent(R.id.hinge_top, 0f) + set.setGuidelinePercent(R.id.hinge_bottom, 1f) + viewModel.disable(false) + } + + set.applyTo(constraintLayout)*/ + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/data/CallData.kt b/app/src/main/java/org/linphone/activities/voip/data/CallData.kt new file mode 100644 index 000000000..588438589 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/data/CallData.kt @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.data + +import android.view.View +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import java.util.* +import kotlinx.coroutines.* +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R +import org.linphone.contact.GenericContactData +import org.linphone.core.* +import org.linphone.core.tools.Log +import org.linphone.utils.AppUtils + +open class CallData(val call: Call) : GenericContactData(call.remoteAddress) { + interface CallContextMenuClickListener { + fun onShowContextMenu(anchor: View, callData: CallData) + } + + val address = call.remoteAddress.asStringUriOnly() + + val isPaused = MutableLiveData() + val isRemotelyPaused = MutableLiveData() + val canBePaused = MutableLiveData() + + val isRecording = MutableLiveData() + val isRemotelyRecorded = MutableLiveData() + + val isInRemoteConference = MutableLiveData() + val remoteConferenceSubject = MutableLiveData() + val isActiveAndNotInConference = MediatorLiveData() + + val isOutgoing = MutableLiveData() + val isIncoming = MutableLiveData() + + var chatRoom: ChatRoom? = null + + var contextMenuClickListener: CallContextMenuClickListener? = null + + private var timer: Timer? = null + + private val listener = object : CallListenerStub() { + override fun onStateChanged(call: Call, state: Call.State, message: String) { + if (call != this@CallData.call) return + Log.i("[Call] State changed: $state") + + update() + + if (call.state == Call.State.UpdatedByRemote) { + val remoteVideo = call.remoteParams?.isVideoEnabled ?: false + val localVideo = call.currentParams.isVideoEnabled + if (remoteVideo && !localVideo) { + // User has 30 secs to accept or decline call update + startVideoUpdateAcceptanceTimer() + } + } else if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) { + timer?.cancel() + } else if (state == Call.State.StreamsRunning) { + // Stop call update timer once user has accepted or declined call update + timer?.cancel() + } + } + + override fun onRemoteRecording(call: Call, recording: Boolean) { + Log.i("[Call] Remote recording changed: $recording") + isRemotelyRecorded.value = recording + } + } + + private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) + + init { + call.addListener(listener) + isRemotelyRecorded.value = call.remoteParams?.isRecording + + isActiveAndNotInConference.value = true + isActiveAndNotInConference.addSource(isPaused) { + updateActiveAndNotInConference() + } + isActiveAndNotInConference.addSource(isRemotelyPaused) { + updateActiveAndNotInConference() + } + isActiveAndNotInConference.addSource(isInRemoteConference) { + updateActiveAndNotInConference() + } + + update() + // initChatRoom() + + val conferenceInfo = coreContext.core.findConferenceInformationFromUri(call.remoteAddress) + if (conferenceInfo != null) { + Log.i("[Call] Found matching conference info with subject: ${conferenceInfo.subject}") + remoteConferenceSubject.value = conferenceInfo.subject + } + } + + override fun destroy() { + call.removeListener(listener) + timer?.cancel() + scope.cancel() + + super.destroy() + } + + fun togglePause() { + if (isCallPaused()) { + resume() + } else { + pause() + } + } + + fun pause() { + call.pause() + } + + fun resume() { + call.resume() + } + + fun accept() { + call.accept() + } + + fun terminate() { + call.terminate() + } + + fun toggleRecording() { + if (call.isRecording) { + call.stopRecording() + } else { + call.startRecording() + } + isRecording.value = call.isRecording + } + + fun showContextMenu(anchor: View) { + contextMenuClickListener?.onShowContextMenu(anchor, this) + } + + private fun initChatRoom() { + val core = coreContext.core + val localSipUri = core.defaultAccount?.params?.identityAddress?.asStringUriOnly() + val remoteSipUri = call.remoteAddress.asStringUriOnly() + val conference = call.conference + + if (localSipUri != null) { + val localAddress = Factory.instance().createAddress(localSipUri) + val remoteSipAddress = Factory.instance().createAddress(remoteSipUri) + chatRoom = core.searchChatRoom(null, localAddress, remoteSipAddress, arrayOfNulls(0)) + + if (chatRoom == null) { + Log.w("[Call] Failed to find existing chat room for local address [$localSipUri] and remote address [$remoteSipUri]") + var chatRoomParams: ChatRoomParams? = null + if (conference != null) { + val params = core.createDefaultChatRoomParams() + params.subject = conference.subject + params.backend = ChatRoomBackend.FlexisipChat + params.isGroupEnabled = true + chatRoomParams = params + } + + chatRoom = core.searchChatRoom( + chatRoomParams, + localAddress, + null, + arrayOf(remoteSipAddress) + ) + } + + if (chatRoom == null) { + val chatRoomParams = core.createDefaultChatRoomParams() + + if (conference != null) { + Log.w("[Call] Failed to find existing chat room with same subject & participants, creating it") + chatRoomParams.backend = ChatRoomBackend.FlexisipChat + chatRoomParams.isGroupEnabled = true + chatRoomParams.subject = conference.subject + + val participants = arrayOfNulls
(conference.participantCount) + val addresses = arrayListOf
() + for (participant in conference.participantList) { + addresses.add(participant.address) + } + addresses.toArray(participants) + + Log.i("[Call] Creating chat room with same subject [${chatRoomParams.subject}] & participants as for conference") + chatRoom = core.createChatRoom(chatRoomParams, localAddress, participants) + } else { + Log.w("[Call] Failed to find existing chat room with same participants, creating it") + // TODO: configure chat room params + chatRoom = core.createChatRoom(chatRoomParams, localAddress, arrayOf(remoteSipAddress)) + } + } + + if (chatRoom == null) { + Log.e("[Call] Failed to create a chat room for local address [$localSipUri] and remote address [$remoteSipUri]!") + } + } else { + Log.e("[Call] Failed to get either local [$localSipUri] or remote [$remoteSipUri] SIP address!") + } + } + + private fun isCallPaused(): Boolean { + return when (call.state) { + Call.State.Paused, Call.State.Pausing -> true + else -> false + } + } + + private fun isCallRemotelyPaused(): Boolean { + return when (call.state) { + Call.State.PausedByRemote -> { + val conference = call.conference + if (conference != null && conference.me.isFocus) { + Log.w("[Call] State is paused by remote but we are the focus of the conference, so considering call as active") + false + } else { + true + } + } + else -> false + } + } + + private fun canCallBePaused(): Boolean { + return !call.mediaInProgress() && when (call.state) { + Call.State.StreamsRunning, Call.State.PausedByRemote -> true + else -> false + } + } + + private fun update() { + isPaused.value = isCallPaused() + isRemotelyPaused.value = isCallRemotelyPaused() + canBePaused.value = canCallBePaused() + + updateConferenceInfo() + + isOutgoing.value = when (call.state) { + Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia, Call.State.OutgoingProgress, Call.State.OutgoingRinging -> true + else -> false + } + isIncoming.value = when (call.state) { + Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> true + else -> false + } + + // Check periodically until mediaInProgress is false + if (call.mediaInProgress()) { + scope.launch { + delay(1000) + update() + } + } + } + + private fun updateConferenceInfo() { + val conference = call.conference + isInRemoteConference.value = conference != null + if (conference != null) { + remoteConferenceSubject.value = if (conference.subject.isNullOrEmpty()) { + if (conference.me.isFocus) { + AppUtils.getString(R.string.conference_local_title) + } else { + AppUtils.getString(R.string.conference_default_title) + } + } else { + conference.subject + } + Log.d("[Call] Found conference related to this call with subject [${remoteConferenceSubject.value}]") + } + } + + private fun startVideoUpdateAcceptanceTimer() { + timer?.cancel() + + timer = Timer("Call update timeout") + timer?.schedule( + object : TimerTask() { + override fun run() { + // Decline call update + coreContext.videoUpdateRequestTimedOut(call) + } + }, + 30000 + ) + Log.i("[Call] Starting 30 seconds timer to automatically decline video request") + } + + private fun updateActiveAndNotInConference() { + isActiveAndNotInConference.value = isPaused.value == false && isRemotelyPaused.value == false && isInRemoteConference.value == false + } +} diff --git a/app/src/main/java/org/linphone/activities/call/data/CallStatisticsData.kt b/app/src/main/java/org/linphone/activities/voip/data/CallStatisticsData.kt similarity index 93% rename from app/src/main/java/org/linphone/activities/call/data/CallStatisticsData.kt rename to app/src/main/java/org/linphone/activities/voip/data/CallStatisticsData.kt index bbfe7a887..b742a910d 100644 --- a/app/src/main/java/org/linphone/activities/call/data/CallStatisticsData.kt +++ b/app/src/main/java/org/linphone/activities/voip/data/CallStatisticsData.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2020 Belledonne Communications SARL. + * Copyright (c) 2010-2021 Belledonne Communications SARL. * * This file is part of linphone-android * (see https://www.linphone.org). @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.activities.call.data +package org.linphone.activities.voip.data import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext @@ -31,8 +31,6 @@ class CallStatisticsData(val call: Call) : GenericContactData(call.remoteAddress val isVideoEnabled = MutableLiveData() - val isExpanded = MutableLiveData() - private val listener = object : CoreListenerStub() { override fun onCallStatsUpdated(core: Core, call: Call, stats: CallStats) { if (call == this@CallStatisticsData.call) { @@ -52,8 +50,6 @@ class CallStatisticsData(val call: Call) : GenericContactData(call.remoteAddress val videoEnabled = call.currentParams.isVideoEnabled isVideoEnabled.value = videoEnabled - - isExpanded.value = coreContext.core.currentCall == call } override fun destroy() { @@ -61,10 +57,6 @@ class CallStatisticsData(val call: Call) : GenericContactData(call.remoteAddress super.destroy() } - fun toggleExpanded() { - isExpanded.value = isExpanded.value != true - } - private fun initCallStats() { val audioList = arrayListOf() audioList.add(StatItemData(StatType.CAPTURE)) diff --git a/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantData.kt b/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantData.kt new file mode 100644 index 000000000..ae05689b2 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantData.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.data + +import androidx.lifecycle.MutableLiveData +import org.linphone.contact.GenericContactData +import org.linphone.core.* +import org.linphone.core.tools.Log +import org.linphone.utils.LinphoneUtils + +class ConferenceParticipantData( + val conference: Conference, + val participant: Participant +) : + GenericContactData(participant.address) { + val sipUri: String get() = LinphoneUtils.getDisplayableAddress(participant.address) + + val isAdmin = MutableLiveData() + val isMeAdmin = MutableLiveData() + + init { + isAdmin.value = participant.isAdmin + isMeAdmin.value = conference.me.isAdmin + Log.i("[Conference Participant] Participant ${participant.address.asStringUriOnly()} is ${if (participant.isAdmin) "admin" else "not admin"}") + } + + fun setAdmin() { + if (conference.me.isAdmin) { + Log.i("[Conference Participant] Participant ${participant.address.asStringUriOnly()} will be set as admin") + conference.setParticipantAdminStatus(participant, true) + } else { + Log.e("[Conference Participant] You aren't admin, you can't change participants admin rights") + } + } + + fun unsetAdmin() { + if (conference.me.isAdmin) { + Log.i("[Conference Participant] Participant ${participant.address.asStringUriOnly()} will be unset as admin") + conference.setParticipantAdminStatus(participant, false) + } else { + Log.e("[Conference Participant] You aren't admin, you can't change participants admin rights") + } + } + + fun removeParticipantFromConference() { + if (conference.me.isAdmin) { + Log.i("[Conference Participant] Removing participant ${participant.address.asStringUriOnly()} from conference") + conference.removeParticipant(participant) + } else { + Log.e("[Conference Participant] Can't remove participant ${participant.address.asStringUriOnly()} from conference, you aren't admin") + } + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantDeviceData.kt b/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantDeviceData.kt new file mode 100644 index 000000000..762f58b48 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantDeviceData.kt @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.data + +import android.graphics.SurfaceTexture +import android.view.TextureView +import androidx.lifecycle.MutableLiveData +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.contact.GenericContactData +import org.linphone.core.MediaDirection +import org.linphone.core.ParticipantDevice +import org.linphone.core.ParticipantDeviceListenerStub +import org.linphone.core.StreamType +import org.linphone.core.tools.Log + +class ConferenceParticipantDeviceData( + val participantDevice: ParticipantDevice, + val isMe: Boolean +) : + GenericContactData(participantDevice.address) { + val videoEnabled = MutableLiveData() + + val activeSpeaker = MutableLiveData() + + val isInConference = MutableLiveData() + + private var textureView: TextureView? = null + + private val listener = object : ParticipantDeviceListenerStub() { + override fun onIsSpeakingChanged( + participantDevice: ParticipantDevice, + isSpeaking: Boolean + ) { + Log.i("[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}] is ${if (isSpeaking) "speaking" else "not speaking"}") + activeSpeaker.value = isSpeaking + } + + override fun onConferenceJoined(participantDevice: ParticipantDevice) { + Log.i("[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}] has joined the conference") + isInConference.value = true + updateWindowId(textureView) + } + + override fun onConferenceLeft(participantDevice: ParticipantDevice) { + Log.i("[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}] has left the conference") + isInConference.value = false + updateWindowId(null) + } + + override fun onStreamCapabilityChanged( + participantDevice: ParticipantDevice, + direction: MediaDirection, + streamType: StreamType + ) { + if (streamType == StreamType.Video) { + Log.i("[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}] video capability changed to $direction") + } + } + + override fun onStreamAvailabilityChanged( + participantDevice: ParticipantDevice, + available: Boolean, + streamType: StreamType + ) { + if (streamType == StreamType.Video) { + Log.i("[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}] video availability changed to ${if (available) "available" else "unavailable"}") + videoEnabled.value = available + if (available) { + updateWindowId(textureView) + } + } + } + } + + init { + Log.i("[Conference Participant Device] Created device width Address [${participantDevice.address.asStringUriOnly()}], is it myself? $isMe") + participantDevice.addListener(listener) + + activeSpeaker.value = false + videoEnabled.value = participantDevice.getStreamAvailability(StreamType.Video) + isInConference.value = participantDevice.isInConference + + val videoCapability = participantDevice.getStreamCapability(StreamType.Video) + Log.i("[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}], is in conf? ${isInConference.value}, is video enabled? ${videoEnabled.value} ($videoCapability)") + } + + override fun destroy() { + participantDevice.removeListener(listener) + + super.destroy() + } + + fun switchCamera() { + coreContext.switchCamera() + } + + fun isSwitchCameraAvailable(): Boolean { + return isMe && coreContext.showSwitchCameraButton() + } + + fun setTextureView(tv: TextureView) { + textureView = tv + + if (tv.isAvailable) { + Log.i("[Conference Participant Device] Setting textureView [$textureView] for participant [${participantDevice.address.asStringUriOnly()}]") + updateWindowId(textureView) + } else { + Log.i("[Conference Participant Device] Got textureView [$textureView] for participant [${participantDevice.address.asStringUriOnly()}], but it is not available yet") + tv.surfaceTextureListener = object : TextureView.SurfaceTextureListener { + override fun onSurfaceTextureAvailable( + surface: SurfaceTexture, + width: Int, + height: Int + ) { + Log.i("[Conference Participant Device] Setting textureView [$textureView] for participant [${participantDevice.address.asStringUriOnly()}]") + updateWindowId(textureView) + } + + override fun onSurfaceTextureSizeChanged( + surface: SurfaceTexture, + width: Int, + height: Int + ) { } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { + Log.w("[Conference Participant Device] TextureView [$textureView] for participant [${participantDevice.address.asStringUriOnly()}] has been destroyed") + textureView = null + updateWindowId(null) + return true + } + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { } + } + } + } + + private fun updateWindowId(windowId: Any?) { + if (isMe) { + coreContext.core.nativePreviewWindowId = windowId + } else { + participantDevice.nativeVideoWindowId = windowId + } + } +} diff --git a/app/src/main/java/org/linphone/activities/call/data/StatItemData.kt b/app/src/main/java/org/linphone/activities/voip/data/StatItemData.kt similarity index 97% rename from app/src/main/java/org/linphone/activities/call/data/StatItemData.kt rename to app/src/main/java/org/linphone/activities/voip/data/StatItemData.kt index d37cd9dfd..67e4a18eb 100644 --- a/app/src/main/java/org/linphone/activities/call/data/StatItemData.kt +++ b/app/src/main/java/org/linphone/activities/voip/data/StatItemData.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2020 Belledonne Communications SARL. + * Copyright (c) 2010-2021 Belledonne Communications SARL. * * This file is part of linphone-android * (see https://www.linphone.org). @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.activities.call.data +package org.linphone.activities.voip.data import androidx.lifecycle.MutableLiveData import java.text.DecimalFormat diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ActiveCallOrConferenceFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ActiveCallOrConferenceFragment.kt new file mode 100644 index 000000000..c61f22a40 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/fragments/ActiveCallOrConferenceFragment.kt @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.fragments + +import android.annotation.SuppressLint +import android.app.Dialog +import android.content.Intent +import android.os.Bundle +import android.os.SystemClock +import android.view.View +import android.widget.Chronometer +import android.widget.LinearLayout +import android.widget.RelativeLayout +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.navigation.navGraphViewModels +import com.google.android.material.snackbar.Snackbar +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.LinphoneApplication.Companion.corePreferences +import org.linphone.R +import org.linphone.activities.* +import org.linphone.activities.main.MainActivity +import org.linphone.activities.main.viewmodels.DialogViewModel +import org.linphone.activities.navigateToCallsList +import org.linphone.activities.navigateToConferenceParticipants +import org.linphone.activities.voip.viewmodels.CallsViewModel +import org.linphone.activities.voip.viewmodels.ConferenceViewModel +import org.linphone.activities.voip.viewmodels.ControlsViewModel +import org.linphone.activities.voip.viewmodels.StatisticsListViewModel +import org.linphone.activities.voip.views.RoundCornersTextureView +import org.linphone.core.* +import org.linphone.core.tools.Log +import org.linphone.databinding.VoipActiveCallOrConferenceFragmentBindingImpl +import org.linphone.mediastream.video.capture.CaptureTextureView +import org.linphone.utils.AppUtils +import org.linphone.utils.DialogUtils + +class ActiveCallOrConferenceFragment : GenericFragment() { + private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) + private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph) + private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) + private val statsViewModel: StatisticsListViewModel by navGraphViewModels(R.id.call_nav_graph) + + private var dialog: Dialog? = null + + override fun getLayoutId(): Int = R.layout.voip_active_call_or_conference_fragment + + override fun onStart() { + useMaterialSharedAxisXForwardAnimation = false + + super.onStart() + } + + @SuppressLint("CutPasteId") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + controlsViewModel.hideCallStats() // In case it was toggled on during incoming/outgoing fragment was visible + + binding.lifecycleOwner = viewLifecycleOwner + + binding.controlsViewModel = controlsViewModel + + binding.callsViewModel = callsViewModel + + binding.conferenceViewModel = conferenceViewModel + + binding.statsViewModel = statsViewModel + + conferenceViewModel.conferenceMosaicDisplayMode.observe( + viewLifecycleOwner + ) { + if (it) { + startTimer(R.id.grid_conference_timer) + } + } + + conferenceViewModel.conferenceActiveSpeakerDisplayMode.observe( + viewLifecycleOwner + ) { + if (it) { + startTimer(R.id.active_speaker_conference_timer) + + if (conferenceViewModel.conferenceExists.value == true) { + Log.i("[Call] Local participant is in conference and current layout is active speaker, updating Core's native window id") + val layout = + binding.root.findViewById(R.id.conference_active_speaker_layout) + val window = + layout?.findViewById(R.id.conference_active_speaker_remote_video) + coreContext.core.nativeVideoWindowId = window + } else { + Log.i("[Call] Either not in conference or current layout isn't active speaker, updating Core's native window id") + val layout = binding.root.findViewById(R.id.remote_layout) + val window = + layout?.findViewById(R.id.remote_video_surface) + coreContext.core.nativeVideoWindowId = window + } + } + } + + conferenceViewModel.conferenceParticipantDevices.observe( + viewLifecycleOwner + ) { + if (it.size > conferenceViewModel.maxParticipantsForMosaicLayout) { + showSnackBar(R.string.conference_too_many_participants_for_mosaic_layout) + } + } + + conferenceViewModel.conference.observe( + viewLifecycleOwner + ) { conference -> + if (corePreferences.enableFullScreenWhenJoiningVideoConference) { + if (conference != null && conference.currentParams.isVideoEnabled) { + if (conference.me.devices.find { it.getStreamAvailability(StreamType.Video) } != null) { + Log.i("[Call] Conference is video & our device has video enabled, enabling full screen mode") + controlsViewModel.fullScreenMode.value = true + } + } + } + } + + callsViewModel.currentCallData.observe( + viewLifecycleOwner + ) { + if (it != null) { + val timer = binding.root.findViewById(R.id.active_call_timer) + timer.base = + SystemClock.elapsedRealtime() - (1000 * it.call.duration) // Linphone timestamps are in seconds + timer.start() + } + } + + controlsViewModel.goToConferenceParticipantsListEvent.observe( + viewLifecycleOwner + ) { + it.consume { + navigateToConferenceParticipants() + } + } + + controlsViewModel.goToChatEvent.observe( + viewLifecycleOwner + ) { + it.consume { + goToChat() + } + } + + controlsViewModel.goToCallsListEvent.observe( + viewLifecycleOwner + ) { + it.consume { + navigateToCallsList() + } + } + + controlsViewModel.goToConferenceLayoutSettings.observe( + viewLifecycleOwner + ) { + it.consume { + navigateToConferenceLayout() + } + } + + callsViewModel.callUpdateEvent.observe( + viewLifecycleOwner + ) { + it.consume { call -> + if (call.state == Call.State.StreamsRunning) { + dialog?.dismiss() + } else if (call.state == Call.State.UpdatedByRemote) { + if (coreContext.core.isVideoEnabled) { + val remoteVideo = call.remoteParams?.isVideoEnabled ?: false + val localVideo = call.currentParams.isVideoEnabled + if (remoteVideo && !localVideo) { + showCallVideoUpdateDialog(call) + } + } else { + Log.w("[Call] Video display & capture are disabled, don't show video dialog") + } + } + + val conference = call.conference + if (conference != null && conferenceViewModel.conference.value == null) { + Log.i("[Call] Found conference attached to call and no conference in dedicated view model, init & configure it") + conferenceViewModel.initConference(conference) + conferenceViewModel.configureConference(conference) + } + } + } + + controlsViewModel.goToDialer.observe( + viewLifecycleOwner + ) { + it.consume { isCallTransfer -> + val intent = Intent() + intent.setClass(requireContext(), MainActivity::class.java) + intent.putExtra("Dialer", true) + intent.putExtra("Transfer", isCallTransfer) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } + } + + val remoteLayout = binding.root.findViewById(R.id.remote_layout) + val remoteVideoView = remoteLayout.findViewById(R.id.remote_video_surface) + coreContext.core.nativeVideoWindowId = remoteVideoView + val localVideoView = remoteLayout.findViewById(R.id.local_preview_video_surface) + coreContext.core.nativePreviewWindowId = localVideoView + + binding.stubbedConferenceActiveSpeakerLayout.setOnInflateListener { _, inflated -> + Log.i("[Call] Active speaker conference layout inflated") + val binding = DataBindingUtil.bind(inflated) + binding?.lifecycleOwner = viewLifecycleOwner + startTimer(R.id.active_speaker_conference_timer) + } + + binding.stubbedConferenceGridLayout.setOnInflateListener { _, inflated -> + Log.i("[Call] Mosaic conference layout inflated") + val binding = DataBindingUtil.bind(inflated) + binding?.lifecycleOwner = viewLifecycleOwner + startTimer(R.id.grid_conference_timer) + } + + binding.stubbedAudioRoutes.setOnInflateListener { _, inflated -> + val binding = DataBindingUtil.bind(inflated) + binding?.lifecycleOwner = viewLifecycleOwner + } + + binding.stubbedNumpad.setOnInflateListener { _, inflated -> + val binding = DataBindingUtil.bind(inflated) + binding?.lifecycleOwner = viewLifecycleOwner + } + + binding.stubbedCallStats.setOnInflateListener { _, inflated -> + val binding = DataBindingUtil.bind(inflated) + binding?.lifecycleOwner = viewLifecycleOwner + } + + binding.stubbedPausedCall.setOnInflateListener { _, inflated -> + val binding = DataBindingUtil.bind(inflated) + binding?.lifecycleOwner = viewLifecycleOwner + } + + binding.stubbedRemotelyPausedCall.setOnInflateListener { _, inflated -> + val binding = DataBindingUtil.bind(inflated) + binding?.lifecycleOwner = viewLifecycleOwner + } + + binding.stubbedPausedConference.setOnInflateListener { _, inflated -> + val binding = DataBindingUtil.bind(inflated) + binding?.lifecycleOwner = viewLifecycleOwner + } + } + + override fun onPause() { + super.onPause() + + controlsViewModel.hideExtraButtons(true) + } + + override fun onDestroy() { + super.onDestroy() + + coreContext.core.nativeVideoWindowId = null + coreContext.core.nativePreviewWindowId = null + } + + private fun showCallVideoUpdateDialog(call: Call) { + val viewModel = DialogViewModel(AppUtils.getString(R.string.call_video_update_requested_dialog)) + dialog = DialogUtils.getVoipDialog(requireContext(), viewModel) + + viewModel.showCancelButton( + { + coreContext.answerCallVideoUpdateRequest(call, false) + dialog?.dismiss() + }, + getString(R.string.dialog_decline) + ) + + viewModel.showOkButton( + { + coreContext.answerCallVideoUpdateRequest(call, true) + dialog?.dismiss() + }, + getString(R.string.dialog_accept) + ) + + dialog?.show() + } + + private fun goToChat() { + val intent = Intent() + intent.setClass(requireContext(), MainActivity::class.java) + intent.putExtra("Chat", true) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } + + private fun showSnackBar(resourceId: Int) { + Snackbar.make(binding.coordinator, resourceId, Snackbar.LENGTH_LONG).show() + } + + private fun startTimer(timerId: Int) { + val timer: Chronometer? = binding.root.findViewById(timerId) + if (timer == null) { + Log.w("[Call] Timer not found, maybe view wasn't inflated yet?") + return + } + + val conference = conferenceViewModel.conference.value + if (conference != null) { + val duration = 1000 * conference.duration // Linphone timestamps are in seconds + timer.base = SystemClock.elapsedRealtime() - duration + } else { + Log.e("[Call] Conference not found, timer will have no base") + } + + timer.start() + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/CallsListFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/CallsListFragment.kt new file mode 100644 index 000000000..899263c61 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/fragments/CallsListFragment.kt @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.fragments + +import android.content.Intent +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.PopupWindow +import androidx.databinding.DataBindingUtil +import androidx.navigation.navGraphViewModels +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.main.MainActivity +import org.linphone.activities.voip.data.CallData +import org.linphone.activities.voip.viewmodels.CallsViewModel +import org.linphone.activities.voip.viewmodels.ConferenceViewModel +import org.linphone.databinding.VoipCallContextMenuBindingImpl +import org.linphone.databinding.VoipCallsListFragmentBinding +import org.linphone.utils.AppUtils + +class CallsListFragment : GenericFragment() { + private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph) + private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) + + override fun getLayoutId(): Int = R.layout.voip_calls_list_fragment + + private val callContextMenuClickListener = object : CallData.CallContextMenuClickListener { + override fun onShowContextMenu(anchor: View, callData: CallData) { + showCallMenu(anchor, callData) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + binding.callsViewModel = callsViewModel + binding.conferenceViewModel = conferenceViewModel + + binding.setCancelClickListener { + goBack() + } + + binding.setAddCallClickListener { + val intent = Intent() + intent.setClass(requireContext(), MainActivity::class.java) + intent.putExtra("Dialer", true) + intent.putExtra("Transfer", false) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } + + callsViewModel.callsData.observe( + viewLifecycleOwner + ) { + for (data in it) { + data.contextMenuClickListener = callContextMenuClickListener + } + } + } + + private fun showCallMenu(anchor: View, callData: CallData) { + val popupView: VoipCallContextMenuBindingImpl = DataBindingUtil.inflate( + LayoutInflater.from(requireContext()), + R.layout.voip_call_context_menu, null, false + ) + + val itemSize = AppUtils.getDimension(R.dimen.voip_call_context_menu_item_height).toInt() + var totalSize = itemSize * 5 + + if (callData.isPaused.value == true || + callData.isIncoming.value == true || + callData.isOutgoing.value == true || + callData.isInRemoteConference.value == true + ) { + popupView.hidePause = true + totalSize -= itemSize + } + + if (callData.isIncoming.value == true || + callData.isOutgoing.value == true || + callData.isInRemoteConference.value == true + ) { + popupView.hideResume = true + popupView.hideTransfer = true + totalSize -= itemSize * 2 + } else if (callData.isPaused.value == false) { + popupView.hideResume = true + totalSize -= itemSize + } + + if (callData.isIncoming.value == false) { + popupView.hideAccept = true + totalSize -= itemSize + } + + // When using WRAP_CONTENT instead of real size, fails to place the + // popup window above if not enough space is available below + val popupWindow = PopupWindow( + popupView.root, + AppUtils.getDimension(R.dimen.voip_call_context_menu_width).toInt(), + totalSize, + true + ) + // Elevation is for showing a shadow around the popup + popupWindow.elevation = 20f + + popupView.setResumeClickListener { + callData.resume() + popupWindow.dismiss() + } + + popupView.setPauseClickListener { + callData.pause() + popupWindow.dismiss() + } + + popupView.setTransferClickListener { + val intent = Intent() + intent.setClass(requireContext(), MainActivity::class.java) + intent.putExtra("Dialer", true) + intent.putExtra("Transfer", true) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + popupWindow.dismiss() + } + + popupView.setAnswerClickListener { + callData.accept() + popupWindow.dismiss() + } + + popupView.setHangupClickListener { + callData.terminate() + popupWindow.dismiss() + } + + popupWindow.showAsDropDown(anchor, 0, 0, Gravity.END or Gravity.TOP) + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ChatFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ChatFragment.kt new file mode 100644 index 000000000..0d953d058 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/fragments/ChatFragment.kt @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.fragments + +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.os.Parcelable +import android.provider.MediaStore +import android.view.View +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.launch +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.main.MainActivity +import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter +import org.linphone.activities.main.chat.data.ChatMessageData +import org.linphone.activities.main.chat.viewmodels.* +import org.linphone.activities.main.viewmodels.ListTopBarViewModel +import org.linphone.core.ChatRoom +import org.linphone.core.Factory +import org.linphone.core.tools.Log +import org.linphone.databinding.VoipChatFragmentBinding +import org.linphone.utils.FileUtils +import org.linphone.utils.PermissionHelper + +class ChatFragment : GenericFragment() { + private lateinit var adapter: ChatMessagesListAdapter + private lateinit var viewModel: ChatRoomViewModel + private lateinit var listViewModel: ChatMessagesListViewModel + private lateinit var chatSendingViewModel: ChatMessageSendingViewModel + + override fun getLayoutId(): Int = R.layout.voip_chat_fragment + + private val observer = object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + if (positionStart == adapter.itemCount - itemCount) { + adapter.notifyItemChanged(positionStart - 1) // For grouping purposes + scrollToBottom() + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + binding.setCancelClickListener { + goBack() + } + + binding.setChatRoomsListClickListener { + val intent = Intent() + intent.setClass(requireContext(), MainActivity::class.java) + intent.putExtra("Chat", true) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } + + binding.setAttachFileClickListener { + if (PermissionHelper.get().hasReadExternalStoragePermission() && PermissionHelper.get().hasCameraPermission()) { + pickFile() + } else { + Log.i("[Chat] Asking for READ_EXTERNAL_STORAGE and CAMERA permissions") + requestPermissions( + arrayOf( + android.Manifest.permission.READ_EXTERNAL_STORAGE, + android.Manifest.permission.CAMERA + ), + 0 + ) + } + } + + val localSipUri = arguments?.getString("LocalSipUri") + val remoteSipUri = arguments?.getString("RemoteSipUri") + var chatRoom: ChatRoom? = null + if (localSipUri != null && remoteSipUri != null) { + Log.i("[Chat] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments") + + val localAddress = Factory.instance().createAddress(localSipUri) + val remoteSipAddress = Factory.instance().createAddress(remoteSipUri) + chatRoom = coreContext.core.searchChatRoom(null, localAddress, remoteSipAddress, arrayOfNulls(0)) + } + chatRoom ?: return + + viewModel = requireActivity().run { + ViewModelProvider( + this, + ChatRoomViewModelFactory(chatRoom) + )[ChatRoomViewModel::class.java] + } + binding.viewModel = viewModel + + listViewModel = ViewModelProvider( + this, + ChatMessagesListViewModelFactory(chatRoom) + )[ChatMessagesListViewModel::class.java] + + chatSendingViewModel = ViewModelProvider( + this, + ChatMessageSendingViewModelFactory(chatRoom) + )[ChatMessageSendingViewModel::class.java] + binding.chatSendingViewModel = chatSendingViewModel + + val listSelectionViewModel = ViewModelProvider(this)[ListTopBarViewModel::class.java] + adapter = ChatMessagesListAdapter(listSelectionViewModel, this) + // SubmitList is done on a background thread + // We need this adapter data observer to know when to scroll + binding.chatMessagesList.adapter = adapter + adapter.registerAdapterDataObserver(observer) + + // Disable context menu on each message + adapter.disableAdvancedContextMenuOptions() + + val layoutManager = LinearLayoutManager(requireContext()) + layoutManager.stackFromEnd = true + binding.chatMessagesList.layoutManager = layoutManager + + listViewModel.events.observe( + viewLifecycleOwner + ) { events -> + adapter.submitList(events) + } + + chatSendingViewModel.textToSend.observe( + viewLifecycleOwner + ) { + chatSendingViewModel.onTextToSendChanged(it) + } + + adapter.replyMessageEvent.observe( + viewLifecycleOwner + ) { + it.consume { chatMessage -> + chatSendingViewModel.pendingChatMessageToReplyTo.value?.destroy() + chatSendingViewModel.pendingChatMessageToReplyTo.value = + ChatMessageData(chatMessage) + chatSendingViewModel.isPendingAnswer.value = true + } + } + } + + override fun onResume() { + super.onResume() + + if (this::viewModel.isInitialized) { + // Prevent notifications for this chat room to be displayed + val peerAddress = viewModel.chatRoom.peerAddress.asStringUriOnly() + coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = peerAddress + viewModel.chatRoom.markAsRead() + } else { + Log.e("[Chat] Fragment resuming but viewModel lateinit property isn't initialized!") + } + } + + override fun onPause() { + coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null + + super.onPause() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == Activity.RESULT_OK) { + lifecycleScope.launch { + for ( + fileToUploadPath in FileUtils.getFilesPathFromPickerIntent( + data, + chatSendingViewModel.temporaryFileUploadPath + ) + ) { + chatSendingViewModel.addAttachment(fileToUploadPath) + } + } + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + var atLeastOneGranted = false + for (result in grantResults) { + atLeastOneGranted = atLeastOneGranted || result == PackageManager.PERMISSION_GRANTED + } + + when (requestCode) { + 0 -> { + if (atLeastOneGranted) { + pickFile() + } + } + } + } + + private fun scrollToBottom() { + if (adapter.itemCount > 0) { + binding.chatMessagesList.scrollToPosition(adapter.itemCount - 1) + } + } + + private fun pickFile() { + val intentsList = ArrayList() + + val pickerIntent = Intent(Intent.ACTION_GET_CONTENT) + pickerIntent.type = "*/*" + pickerIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) + + if (PermissionHelper.get().hasCameraPermission()) { + // Allows to capture directly from the camera + val capturePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + val tempFileName = System.currentTimeMillis().toString() + ".jpeg" + val file = FileUtils.getFileStoragePath(tempFileName) + chatSendingViewModel.temporaryFileUploadPath = file + val publicUri = FileProvider.getUriForFile( + requireContext(), + requireContext().getString(R.string.file_provider), + file + ) + capturePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, publicUri) + capturePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + capturePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + intentsList.add(capturePictureIntent) + + val captureVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE) + intentsList.add(captureVideoIntent) + } + + val chooserIntent = + Intent.createChooser(pickerIntent, getString(R.string.chat_message_pick_file_dialog)) + chooserIntent.putExtra( + Intent.EXTRA_INITIAL_INTENTS, + intentsList.toArray(arrayOf()) + ) + + startActivityForResult(chooserIntent, 0) + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceAddParticipantsFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceAddParticipantsFragment.kt new file mode 100644 index 000000000..f3690e09d --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceAddParticipantsFragment.kt @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.fragments + +import android.content.pm.PackageManager +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.navGraphViewModels +import androidx.recyclerview.widget.LinearLayoutManager +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.voip.viewmodels.ConferenceParticipantsViewModel +import org.linphone.activities.voip.viewmodels.ConferenceParticipantsViewModelFactory +import org.linphone.activities.voip.viewmodels.ConferenceViewModel +import org.linphone.contact.ContactsSelectionAdapter +import org.linphone.core.tools.Log +import org.linphone.databinding.VoipConferenceParticipantsAddFragmentBinding +import org.linphone.utils.AppUtils +import org.linphone.utils.PermissionHelper + +class ConferenceAddParticipantsFragment : GenericFragment() { + private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) + private lateinit var viewModel: ConferenceParticipantsViewModel + private lateinit var adapter: ContactsSelectionAdapter + + override fun getLayoutId(): Int = R.layout.voip_conference_participants_add_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + val conference = conferenceViewModel.conference.value + conference ?: return + + viewModel = ViewModelProvider( + this, + ConferenceParticipantsViewModelFactory(conference) + )[ConferenceParticipantsViewModel::class.java] + + binding.viewModel = viewModel + + adapter = ContactsSelectionAdapter(viewLifecycleOwner) + adapter.setLimeCapabilityRequired(false) // TODO: Use right value from conference + binding.contactsList.adapter = adapter + + val layoutManager = LinearLayoutManager(activity) + binding.contactsList.layoutManager = layoutManager + + // Divider between items + binding.contactsList.addItemDecoration(AppUtils.getDividerDecoration(requireContext(), layoutManager)) + + binding.setBackClickListener { + goBack() + } + + binding.setApplyClickListener { + viewModel.applyChanges() + goBack() + } + + viewModel.contactsList.observe( + viewLifecycleOwner + ) { + adapter.submitList(it) + } + viewModel.sipContactsSelected.observe( + viewLifecycleOwner + ) { + viewModel.updateContactsList() + } + + viewModel.selectedAddresses.observe( + viewLifecycleOwner + ) { + adapter.updateSelectedAddresses(it) + } + viewModel.filter.observe( + viewLifecycleOwner + ) { + viewModel.applyFilter() + } + + adapter.selectedContact.observe( + viewLifecycleOwner + ) { + it.consume { searchResult -> + viewModel.toggleSelectionForSearchResult(searchResult) + } + } + + if (!PermissionHelper.get().hasReadContactsPermission()) { + Log.i("[Conference Add Participants] Asking for READ_CONTACTS permission") + requestPermissions(arrayOf(android.Manifest.permission.READ_CONTACTS), 0) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + if (requestCode == 0) { + val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED + if (granted) { + Log.i("[Conference Add Participants] READ_CONTACTS permission granted") + coreContext.contactsManager.onReadContactsPermissionGranted() + coreContext.contactsManager.fetchContactsAsync() + } else { + Log.w("[Conference Add Participants] READ_CONTACTS permission denied") + } + } + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceLayoutFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceLayoutFragment.kt new file mode 100644 index 000000000..ae01dcb17 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceLayoutFragment.kt @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.fragments + +import android.os.Bundle +import android.view.View +import android.widget.LinearLayout +import androidx.navigation.navGraphViewModels +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.voip.viewmodels.ConferenceViewModel +import org.linphone.core.ConferenceLayout +import org.linphone.core.tools.Log +import org.linphone.databinding.VoipConferenceLayoutFragmentBinding + +class ConferenceLayoutFragment : GenericFragment() { + private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) + + override fun getLayoutId(): Int = R.layout.voip_conference_layout_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + binding.conferenceViewModel = conferenceViewModel + + binding.setCancelClickListener { + goBack() + } + + conferenceViewModel.conferenceMosaicDisplayMode.observe( + viewLifecycleOwner + ) { + if (it) { + Log.i("[Conference] Trying to change conference layout to Grid") + val conference = conferenceViewModel.conference.value + if (conference != null) { + conference.layout = ConferenceLayout.Grid + } else { + Log.e("[Conference] Conference is null in ConferenceViewModel") + } + } + } + + conferenceViewModel.conferenceActiveSpeakerDisplayMode.observe( + viewLifecycleOwner + ) { + if (it) { + Log.i("[Conference] Trying to change conference layout to ActiveSpeaker") + val conference = conferenceViewModel.conference.value + if (conference != null) { + conference.layout = ConferenceLayout.ActiveSpeaker + } else { + Log.e("[Conference] Conference is null in ConferenceViewModel") + } + } + } + + conferenceViewModel.conferenceParticipantDevices.observe( + viewLifecycleOwner + ) { + if (it.size > conferenceViewModel.maxParticipantsForMosaicLayout) { + showTooManyParticipantsForMosaicLayoutDialog() + } + } + + binding.setDismissDialogClickListener { + val dialog = binding.root.findViewById(R.id.too_many_participants_dialog) + dialog?.visibility = View.GONE + } + } + + override fun onResume() { + super.onResume() + + if (conferenceViewModel.conferenceParticipantDevices.value.orEmpty().size > conferenceViewModel.maxParticipantsForMosaicLayout) { + showTooManyParticipantsForMosaicLayoutDialog() + } + } + + private fun showTooManyParticipantsForMosaicLayoutDialog() { + val dialog = binding.root.findViewById(R.id.too_many_participants_dialog) + dialog?.visibility = View.VISIBLE + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceParticipantsFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceParticipantsFragment.kt new file mode 100644 index 000000000..d8badadea --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceParticipantsFragment.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.fragments + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.navigation.navGraphViewModels +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.voip.viewmodels.CallsViewModel +import org.linphone.activities.voip.viewmodels.ConferenceViewModel +import org.linphone.core.tools.Log +import org.linphone.databinding.VoipConferenceParticipantsFragmentBinding + +class ConferenceParticipantsFragment : GenericFragment() { + private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph) + private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) + + override fun getLayoutId(): Int = R.layout.voip_conference_participants_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + binding.callsViewModel = callsViewModel + + binding.conferenceViewModel = conferenceViewModel + + conferenceViewModel.conferenceExists.observe( + viewLifecycleOwner + ) { exists -> + if (!exists) { + Log.w("[Conference Participants] Conference no longer exists, going back") + goBack() + } + } + + conferenceViewModel.participantAdminStatusChangedEvent.observe( + viewLifecycleOwner + ) { + it.consume { participantData -> + val participantName = + participantData.contact.value?.fullName ?: participantData.displayName.value + val message = if (participantData.participant.isAdmin) { + getString(R.string.conference_admin_set).format(participantName) + } else { + getString(R.string.conference_admin_unset).format(participantName) + } + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + } + + binding.setCancelClickListener { + goBack() + } + + binding.setEditClickListener { + // TODO: go to conferences view outside of call activity in edition mode + } + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/IncomingCallFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/IncomingCallFragment.kt new file mode 100644 index 000000000..2cfca960b --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/fragments/IncomingCallFragment.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.fragments + +import android.os.Bundle +import android.os.SystemClock +import android.view.View +import android.widget.Chronometer +import androidx.navigation.navGraphViewModels +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.navigateToActiveCall +import org.linphone.activities.voip.viewmodels.CallsViewModel +import org.linphone.activities.voip.viewmodels.ControlsViewModel +import org.linphone.core.tools.Log +import org.linphone.databinding.VoipCallIncomingFragmentBinding + +class IncomingCallFragment : GenericFragment() { + private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) + private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph) + + override fun getLayoutId(): Int = R.layout.voip_call_incoming_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + binding.controlsViewModel = controlsViewModel + + binding.callsViewModel = callsViewModel + + callsViewModel.callConnectedEvent.observe( + viewLifecycleOwner + ) { + it.consume { + navigateToActiveCall() + } + } + + callsViewModel.callEndedEvent.observe( + viewLifecycleOwner + ) { + it.consume { + navigateToActiveCall() + } + } + + callsViewModel.currentCallData.observe( + viewLifecycleOwner + ) { + if (it != null) { + val timer = binding.root.findViewById(R.id.incoming_call_timer) + timer.base = + SystemClock.elapsedRealtime() - (1000 * it.call.duration) // Linphone timestamps are in seconds + timer.start() + } + } + + val earlyMediaVideo = arguments?.getBoolean("earlyMediaVideo") ?: false + if (earlyMediaVideo) { + Log.i("[Incoming Call] Video early media detected, setting native window id") + coreContext.core.nativeVideoWindowId = binding.remoteVideoSurface + } + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/OutgoingCallFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/OutgoingCallFragment.kt new file mode 100644 index 000000000..9f2ab7365 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/fragments/OutgoingCallFragment.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.fragments + +import android.os.Bundle +import android.os.SystemClock +import android.view.View +import android.widget.Chronometer +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.navigation.navGraphViewModels +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.activities.navigateToActiveCall +import org.linphone.activities.voip.viewmodels.CallsViewModel +import org.linphone.activities.voip.viewmodels.ControlsViewModel +import org.linphone.databinding.VoipCallOutgoingFragmentBinding + +class OutgoingCallFragment : GenericFragment() { + private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) + private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph) + + override fun getLayoutId(): Int = R.layout.voip_call_outgoing_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + + binding.controlsViewModel = controlsViewModel + + binding.callsViewModel = callsViewModel + + callsViewModel.callConnectedEvent.observe( + viewLifecycleOwner + ) { + it.consume { + navigateToActiveCall() + } + } + + callsViewModel.callEndedEvent.observe( + viewLifecycleOwner + ) { + it.consume { + navigateToActiveCall() + } + } + + callsViewModel.currentCallData.observe( + viewLifecycleOwner + ) { + if (it != null) { + val timer = binding.root.findViewById(R.id.outgoing_call_timer) + timer.base = + SystemClock.elapsedRealtime() - (1000 * it.call.duration) // Linphone timestamps are in seconds + timer.start() + } + } + + binding.stubNumpad.setOnInflateListener { _, inflated -> + val binding = DataBindingUtil.bind(inflated) + binding?.lifecycleOwner = viewLifecycleOwner + } + } +} diff --git a/app/src/main/java/org/linphone/activities/call/fragments/StatusFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/StatusFragment.kt similarity index 84% rename from app/src/main/java/org/linphone/activities/call/fragments/StatusFragment.kt rename to app/src/main/java/org/linphone/activities/voip/fragments/StatusFragment.kt index 7837b7482..e9f8f748d 100644 --- a/app/src/main/java/org/linphone/activities/call/fragments/StatusFragment.kt +++ b/app/src/main/java/org/linphone/activities/voip/fragments/StatusFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2020 Belledonne Communications SARL. + * Copyright (c) 2010-2021 Belledonne Communications SARL. * * This file is part of linphone-android * (see https://www.linphone.org). @@ -17,30 +17,31 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.activities.call.fragments +package org.linphone.activities.voip.fragments import android.app.Dialog import android.os.Bundle import android.view.View import androidx.lifecycle.ViewModelProvider +import androidx.navigation.navGraphViewModels import java.util.* import org.linphone.R import org.linphone.activities.GenericFragment -import org.linphone.activities.call.viewmodels.SharedCallViewModel -import org.linphone.activities.call.viewmodels.StatusViewModel import org.linphone.activities.main.viewmodels.DialogViewModel +import org.linphone.activities.voip.viewmodels.ControlsViewModel +import org.linphone.activities.voip.viewmodels.StatusViewModel import org.linphone.core.Call import org.linphone.core.tools.Log -import org.linphone.databinding.CallStatusFragmentBinding +import org.linphone.databinding.VoipStatusFragmentBinding import org.linphone.utils.DialogUtils -import org.linphone.utils.Event -class StatusFragment : GenericFragment() { +class StatusFragment : GenericFragment() { private lateinit var viewModel: StatusViewModel - private lateinit var sharedViewModel: SharedCallViewModel + private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) + private var zrtpDialog: Dialog? = null - override fun getLayoutId(): Int = R.layout.call_status_fragment + override fun getLayoutId(): Int = R.layout.voip_status_fragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -51,14 +52,6 @@ class StatusFragment : GenericFragment() { viewModel = ViewModelProvider(this)[StatusViewModel::class.java] binding.viewModel = viewModel - sharedViewModel = requireActivity().run { - ViewModelProvider(this)[SharedCallViewModel::class.java] - } - - binding.setStatsClickListener { - sharedViewModel.toggleDrawerEvent.value = Event(true) - } - binding.setRefreshClickListener { viewModel.refreshRegister() } @@ -72,6 +65,14 @@ class StatusFragment : GenericFragment() { } } } + + viewModel.showCallStatsEvent.observe( + viewLifecycleOwner + ) { + it.consume { + controlsViewModel.showCallStats() + } + } } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt new file mode 100644 index 000000000..ec565c8c8 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.viewmodels + +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R +import org.linphone.activities.voip.data.CallData +import org.linphone.core.* +import org.linphone.core.tools.Log +import org.linphone.utils.AppUtils +import org.linphone.utils.Event + +class CallsViewModel : ViewModel() { + val currentCallData = MutableLiveData() + + val callsData = MutableLiveData>() + + val inactiveCallsCount = MutableLiveData() + + val currentCallUnreadChatMessageCount = MutableLiveData() + + val chatAndCallsCount = MediatorLiveData() + + val callConnectedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val callEndedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val callUpdateEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val noMoreCallEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + private val listener = object : CoreListenerStub() { + override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) { + updateUnreadChatCount() + } + + override fun onMessageReceived(core: Core, chatRoom: ChatRoom, message: ChatMessage) { + updateUnreadChatCount() + } + + override fun onLastCallEnded(core: Core) { + currentCallData.value?.destroy() + noMoreCallEvent.value = Event(true) + } + + override fun onCallStateChanged(core: Core, call: Call, state: Call.State, message: String) { + Log.i("[Calls] Call with ID [${call.callLog.callId}] state changed: $state") + + if (state == Call.State.IncomingEarlyMedia || state == Call.State.IncomingReceived || state == Call.State.OutgoingInit) { + if (!callDataAlreadyExists(call)) { + addCallToList(call) + } + } + + val currentCall = core.currentCall + if (currentCall != null && currentCallData.value?.call != currentCall) { + updateCurrentCallData(currentCall) + } else if (currentCall == null && core.callsNb > 0) { + updateCurrentCallData(currentCall) + } + + if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) { + removeCallFromList(call) + if (core.callsNb > 0) { + callEndedEvent.value = Event(call) + } + } else if (call.state == Call.State.UpdatedByRemote) { + // If the correspondent asks to turn on video while audio call, + // defer update until user has chosen whether to accept it or not + val remoteVideo = call.remoteParams?.isVideoEnabled ?: false + val localVideo = call.currentParams.isVideoEnabled + val autoAccept = call.core.videoActivationPolicy.automaticallyAccept + if (remoteVideo && !localVideo && !autoAccept) { + if (coreContext.core.isVideoCaptureEnabled || coreContext.core.isVideoDisplayEnabled) { + call.deferUpdate() + callUpdateEvent.value = Event(call) + } else { + coreContext.answerCallVideoUpdateRequest(call, false) + } + } + } else if (state == Call.State.Connected) { + callConnectedEvent.value = Event(call) + } else if (state == Call.State.StreamsRunning) { + callUpdateEvent.value = Event(call) + } + + updateInactiveCallsCount() + } + } + + init { + coreContext.core.addListener(listener) + + val currentCall = coreContext.core.currentCall + if (currentCall != null) { + currentCallData.value?.destroy() + + val viewModel = CallData(currentCall) + currentCallData.value = viewModel + } + + chatAndCallsCount.value = 0 + chatAndCallsCount.addSource(inactiveCallsCount) { + chatAndCallsCount.value = updateCallsAndChatCount() + } + chatAndCallsCount.addSource(currentCallUnreadChatMessageCount) { + chatAndCallsCount.value = updateCallsAndChatCount() + } + + initCallList() + updateInactiveCallsCount() + updateUnreadChatCount() + } + + override fun onCleared() { + coreContext.core.removeListener(listener) + + currentCallData.value?.destroy() + callsData.value.orEmpty().forEach(CallData::destroy) + + super.onCleared() + } + + fun mergeCallsIntoConference() { + Log.i("[Calls] Merging all calls into new conference") + val core = coreContext.core + val params = core.createConferenceParams(null) + params.subject = AppUtils.getString(R.string.conference_local_title) + val conference = core.createConferenceWithParams(params) + conference?.addParticipants(core.calls) + } + + private fun initCallList() { + val calls = arrayListOf() + + for (call in coreContext.core.calls) { + val data: CallData = if (currentCallData.value?.call == call) { + currentCallData.value!! + } else { + CallData(call) + } + Log.i("[Calls] Adding call with ID ${call.callLog.callId} to calls list") + calls.add(data) + } + + callsData.value = calls + } + + private fun addCallToList(call: Call) { + Log.i("[Calls] Adding call with ID ${call.callLog.callId} to calls list") + + val calls = arrayListOf() + calls.addAll(callsData.value.orEmpty()) + + val data = CallData(call) + calls.add(data) + + callsData.value = calls + } + + private fun removeCallFromList(call: Call) { + Log.i("[Calls] Removing call with ID ${call.callLog.callId} from calls list") + + val calls = arrayListOf() + calls.addAll(callsData.value.orEmpty()) + + val data = calls.find { it.call == call } + if (data == null) { + Log.w("[Calls] Data for call to remove wasn't found") + } else { + data.destroy() + calls.remove(data) + } + + callsData.value = calls + } + + private fun updateCurrentCallData(currentCall: Call?) { + var callToUse = currentCall + if (currentCall == null) { + Log.w("[Calls] Current call is now null") + + val firstCall = coreContext.core.calls.find { call -> + call.state != Call.State.Error && call.state != Call.State.End && call.state != Call.State.Released + } + if (firstCall != null && currentCallData.value?.call != firstCall) { + Log.i("[Calls] Using [${firstCall.remoteAddress.asStringUriOnly()}] call as \"current\" call") + callToUse = firstCall + } + } + + if (callToUse == null) { + Log.w("[Calls] No call found to be used as \"current\"") + return + } + + var found = false + for (callData in callsData.value.orEmpty()) { + if (callData.call == callToUse) { + Log.i("[Calls] Updating current call to: ${callData.call.remoteAddress.asStringUriOnly()}") + currentCallData.value = callData + found = true + break + } + } + if (!found) { + Log.w("[Calls] Call with ID [${callToUse.callLog.callId}] not found in calls data list, shouldn't happen!") + val viewModel = CallData(callToUse) + currentCallData.value = viewModel + } + + // updateUnreadChatCount() + } + + private fun callDataAlreadyExists(call: Call): Boolean { + for (callData in callsData.value.orEmpty()) { + if (callData.call == call) { + return true + } + } + return false + } + + private fun updateCallsAndChatCount(): Int { + return (inactiveCallsCount.value ?: 0) + (currentCallUnreadChatMessageCount.value ?: 0) + } + + private fun updateUnreadChatCount() { + // For now we don't display in-call chat, so use global unread chat messages count + currentCallUnreadChatMessageCount.value = coreContext.core.unreadChatMessageCountFromActiveLocals + } + + private fun updateInactiveCallsCount() { + // TODO: handle local conference + inactiveCallsCount.value = coreContext.core.callsNb - 1 + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceParticipantsViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceParticipantsViewModel.kt new file mode 100644 index 000000000..69f178007 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceParticipantsViewModel.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import org.linphone.contact.ContactsSelectionViewModel +import org.linphone.core.Address +import org.linphone.core.Conference +import org.linphone.core.tools.Log + +class ConferenceParticipantsViewModelFactory(private val conference: Conference) : + ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return ConferenceParticipantsViewModel(conference) as T + } +} + +class ConferenceParticipantsViewModel(val conference: Conference) : ContactsSelectionViewModel() { + init { + selectCurrentParticipants() + } + + fun applyChanges() { + // Adding new participants first, because if we remove all of them (or all of them except one) + // It will terminate the conference first and we won't be able to add new participants after + for (address in selectedAddresses.value.orEmpty()) { + val participant = conference.participantList.find { participant -> + participant.address.weakEqual(address) + } + if (participant == null) { + Log.i("[Conference Participants] Participant ${address.asStringUriOnly()} will be added to group") + conference.addParticipant(address) + } + } + + // Removing participants + for (participant in conference.participantList) { + val member = selectedAddresses.value.orEmpty().find { address -> + participant.address.weakEqual(address) + } + if (member == null) { + Log.w("[Conference Participants] Participant ${participant.address.asStringUriOnly()} will be removed from conference") + conference.removeParticipant(participant) + } + } + } + + private fun selectCurrentParticipants() { + val list = arrayListOf
() + + for (participant in conference.participantList) { + list.add(participant.address) + } + + selectedAddresses.value = list + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceViewModel.kt new file mode 100644 index 000000000..38fef36ba --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceViewModel.kt @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.viewmodels + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.LinphoneApplication.Companion.corePreferences +import org.linphone.R +import org.linphone.activities.voip.data.ConferenceParticipantData +import org.linphone.activities.voip.data.ConferenceParticipantDeviceData +import org.linphone.core.* +import org.linphone.core.tools.Log +import org.linphone.utils.AppUtils +import org.linphone.utils.Event +import org.linphone.utils.LinphoneUtils + +class ConferenceViewModel : ViewModel() { + val conferenceExists = MutableLiveData() + val subject = MutableLiveData() + val isConferenceLocallyPaused = MutableLiveData() + val isVideoConference = MutableLiveData() + val isMeAdmin = MutableLiveData() + + val conference = MutableLiveData() + val conferenceCreationPending = MutableLiveData() + val conferenceParticipants = MutableLiveData>() + val conferenceParticipantDevices = MutableLiveData>() + val conferenceMosaicDisplayMode = MutableLiveData() + val conferenceActiveSpeakerDisplayMode = MutableLiveData() + + val isRecording = MutableLiveData() + val isRemotelyRecorded = MutableLiveData() + + val participantAdminStatusChangedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val maxParticipantsForMosaicLayout = corePreferences.maxConferenceParticipantsForMosaicLayout + + val speakingParticipant = MutableLiveData() + + private val conferenceListener = object : ConferenceListenerStub() { + override fun onParticipantAdded(conference: Conference, participant: Participant) { + Log.i("[Conference] Participant added: ${participant.address.asStringUriOnly()}") + updateParticipantsList(conference) + + val count = conferenceParticipants.value.orEmpty().size + if (count > maxParticipantsForMosaicLayout) { + Log.w("[Conference] More than $maxParticipantsForMosaicLayout participants ($count), forcing active speaker layout") + conferenceMosaicDisplayMode.value = false + conferenceActiveSpeakerDisplayMode.value = true + } + } + + override fun onParticipantRemoved(conference: Conference, participant: Participant) { + Log.i("[Conference] Participant removed: ${participant.address.asStringUriOnly()}") + updateParticipantsList(conference) + } + + override fun onParticipantDeviceAdded( + conference: Conference, + participantDevice: ParticipantDevice + ) { + Log.i("[Conference] Participant device added: ${participantDevice.address.asStringUriOnly()}") + addParticipantDevice(participantDevice) + } + + override fun onParticipantDeviceRemoved( + conference: Conference, + participantDevice: ParticipantDevice + ) { + Log.i("[Conference] Participant device removed: ${participantDevice.address.asStringUriOnly()}") + removeParticipantDevice(participantDevice) + } + + override fun onParticipantAdminStatusChanged( + conference: Conference, + participant: Participant + ) { + Log.i("[Conference] Participant admin status changed") + isMeAdmin.value = conference.me.isAdmin + updateParticipantsList(conference) + val participantData = conferenceParticipants.value.orEmpty().find { data -> data.participant.address.weakEqual(participant.address) } + if (participantData != null) { + participantAdminStatusChangedEvent.value = Event(participantData) + } else { + Log.w("[Conference] Failed to find participant [${participant.address.asStringUriOnly()}] in conferenceParticipants list") + } + } + + override fun onSubjectChanged(conference: Conference, subject: String) { + Log.i("[Conference] Subject changed: $subject") + this@ConferenceViewModel.subject.value = subject + } + + override fun onParticipantDeviceJoined(conference: Conference, device: ParticipantDevice) { + if (conference.isMe(device.address)) { + Log.i("[Conference] Entered conference") + isConferenceLocallyPaused.value = false + } + } + + override fun onParticipantDeviceLeft(conference: Conference, device: ParticipantDevice) { + if (conference.isMe(device.address)) { + Log.i("[Conference] Left conference") + isConferenceLocallyPaused.value = true + } + } + + override fun onParticipantDeviceIsSpeakingChanged( + conference: Conference, + participantDevice: ParticipantDevice, + isSpeaking: Boolean + ) { + Log.i("[Conference] Participant [${participantDevice.address.asStringUriOnly()}] is ${if (isSpeaking) "speaking" else "not speaking"}") + if (isSpeaking) { + val device = conferenceParticipantDevices.value.orEmpty().find { + it.participantDevice.address.weakEqual(participantDevice.address) + } + if (device != null && device != speakingParticipant.value) { + Log.i("[Conference] Found participant device") + speakingParticipant.value = device!! + } else if (device == null) { + Log.w("[Conference] Participant device [${participantDevice.address.asStringUriOnly()}] is speaking but couldn't find it in devices list") + } + } + } + + override fun onStateChanged(conference: Conference, state: Conference.State) { + Log.i("[Conference] State changed: $state") + isVideoConference.value = conference.currentParams.isVideoEnabled + + when (state) { + Conference.State.Created -> { + configureConference(conference) + conferenceCreationPending.value = false + } + Conference.State.TerminationPending -> { + terminateConference(conference) + } + else -> {} + } + } + } + + private val listener = object : CoreListenerStub() { + override fun onConferenceStateChanged( + core: Core, + conference: Conference, + state: Conference.State + ) { + Log.i("[Conference] Conference state changed: $state") + if (state == Conference.State.Instantiated) { + conferenceCreationPending.value = true + initConference(conference) + } + } + } + + init { + coreContext.core.addListener(listener) + + conferenceParticipants.value = arrayListOf() + conferenceParticipantDevices.value = arrayListOf() + conferenceMosaicDisplayMode.value = false + conferenceActiveSpeakerDisplayMode.value = false + + subject.value = AppUtils.getString(R.string.conference_default_title) + + var conference = coreContext.core.conference ?: coreContext.core.currentCall?.conference + if (conference == null) { + for (call in coreContext.core.calls) { + if (call.conference != null) { + conference = call.conference + break + } + } + } + if (conference != null) { + val state = conference.state + Log.i("[Conference] Found an existing conference: $conference in state $state") + if (state != Conference.State.TerminationPending && state != Conference.State.Terminated) { + initConference(conference) + if (state == Conference.State.Created) { + configureConference(conference) + } else { + conferenceCreationPending.value = true + } + } + } + } + + override fun onCleared() { + coreContext.core.removeListener(listener) + conference.value?.removeListener(conferenceListener) + + conferenceParticipants.value.orEmpty().forEach(ConferenceParticipantData::destroy) + conferenceParticipantDevices.value.orEmpty().forEach(ConferenceParticipantDeviceData::destroy) + + super.onCleared() + } + + fun pauseConference() { + Log.i("[Conference] Leaving conference temporarily") + conference.value?.leave() + } + + fun resumeConference() { + Log.i("[Conference] Entering conference again") + conference.value?.enter() + } + + fun toggleRecording() { + if (conference.value?.isRecording == true) { + Log.i("[Conference] Stopping conference recording") + conference.value?.stopRecording() + } else { + val path = LinphoneUtils.getRecordingFilePathForConference() + Log.i("[Conference] Starting recording in file $path") + conference.value?.startRecording(path) + } + isRecording.value = conference.value?.isRecording + } + + fun initConference(conference: Conference) { + conferenceExists.value = true + + this@ConferenceViewModel.conference.value = conference + conference.addListener(conferenceListener) + + isRecording.value = conference.isRecording + + updateConferenceLayout(conference) + } + + fun configureConference(conference: Conference) { + updateParticipantsList(conference) + updateParticipantsDevicesList(conference) + + isConferenceLocallyPaused.value = !conference.isIn + isMeAdmin.value = conference.me.isAdmin + isVideoConference.value = conference.currentParams.isVideoEnabled + subject.value = if (conference.subject.isNullOrEmpty()) { + if (conference.me.isFocus) { + AppUtils.getString(R.string.conference_local_title) + } else { + AppUtils.getString(R.string.conference_default_title) + } + } else { + conference.subject + } + + updateConferenceLayout(conference) + } + + fun addCallsToConference() { + Log.i("[Conference] Trying to merge all calls into existing conference") + val conf = conference.value + conf ?: return + + for (call in coreContext.core.calls) { + if (call.conference == null) { + Log.i("[Conference] Adding call [$call] as participant for conference [$conf]") + conf.addParticipant(call) + } + } + if (!conf.isIn) { + Log.i("[Conference] Conference was paused, resuming it") + conf.enter() + } + } + + private fun updateConferenceLayout(conference: Conference) { + val layout = conference.layout + conferenceMosaicDisplayMode.value = layout == ConferenceLayout.Grid || layout == ConferenceLayout.Legacy + conferenceActiveSpeakerDisplayMode.value = layout == ConferenceLayout.ActiveSpeaker + Log.i("[Conference] Conference current layout is: $layout") + } + + private fun terminateConference(conference: Conference) { + conferenceExists.value = false + isVideoConference.value = false + + conference.removeListener(conferenceListener) + + conferenceParticipants.value.orEmpty().forEach(ConferenceParticipantData::destroy) + conferenceParticipantDevices.value.orEmpty().forEach(ConferenceParticipantDeviceData::destroy) + conferenceParticipants.value = arrayListOf() + conferenceParticipantDevices.value = arrayListOf() + } + + private fun updateParticipantsList(conference: Conference) { + conferenceParticipants.value.orEmpty().forEach(ConferenceParticipantData::destroy) + val participants = arrayListOf() + + val participantsList = conference.participantList + Log.i("[Conference] Conference has ${participantsList.size} participants") + for (participant in participantsList) { + val participantDevices = participant.devices + Log.i("[Conference] Participant found: ${participant.address.asStringUriOnly()} with ${participantDevices.size} device(s)") + + val participantData = ConferenceParticipantData(conference, participant) + participants.add(participantData) + } + + conferenceParticipants.value = participants + } + + private fun updateParticipantsDevicesList(conference: Conference) { + conferenceParticipantDevices.value.orEmpty().forEach(ConferenceParticipantDeviceData::destroy) + val devices = arrayListOf() + + val participantsList = conference.participantList + Log.i("[Conference] Conference has ${participantsList.size} participants") + for (participant in participantsList) { + val participantDevices = participant.devices + Log.i("[Conference] Participant found: ${participant.address.asStringUriOnly()} with ${participantDevices.size} device(s)") + + for (device in participantDevices) { + Log.i("[Conference] Participant device found: ${device.name} (${device.address.asStringUriOnly()})") + val deviceData = ConferenceParticipantDeviceData(device, false) + devices.add(deviceData) + } + } + if (devices.isNotEmpty()) { + speakingParticipant.value = devices.first() + } + + for (device in conference.me.devices) { + Log.i("[Conference] Participant device for myself found: ${device.name} (${device.address.asStringUriOnly()})") + val deviceData = ConferenceParticipantDeviceData(device, true) + devices.add(deviceData) + } + + conferenceParticipantDevices.value = devices + } + + private fun addParticipantDevice(device: ParticipantDevice) { + val devices = arrayListOf() + devices.addAll(conferenceParticipantDevices.value.orEmpty()) + + val existingDevice = devices.find { + it.participantDevice.address.weakEqual(device.address) + } + if (existingDevice != null) { + Log.e("[Conference] Participant is already in devices list: ${device.name} (${device.address.asStringUriOnly()})") + return + } + + Log.i("[Conference] New participant device found: ${device.name} (${device.address.asStringUriOnly()})") + val deviceData = ConferenceParticipantDeviceData(device, false) + devices.add(deviceData) + + if (speakingParticipant.value == null) { + speakingParticipant.value = deviceData + } + + conferenceParticipantDevices.value = devices + } + + private fun removeParticipantDevice(device: ParticipantDevice) { + val devices = arrayListOf() + + for (participantDevice in conferenceParticipantDevices.value.orEmpty()) { + if (participantDevice.participantDevice.address.asStringUriOnly() != device.address.asStringUriOnly()) { + devices.add(participantDevice) + } + } + if (devices.size == conferenceParticipantDevices.value.orEmpty().size) { + Log.e("[Conference] Failed to remove participant device: ${device.name} (${device.address.asStringUriOnly()})") + } else { + Log.i("[Conference] Participant device removed: ${device.name} (${device.address.asStringUriOnly()})") + } + + conferenceParticipantDevices.value = devices + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt new file mode 100644 index 000000000..65759f5fa --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.viewmodels + +import android.Manifest +import android.animation.ValueAnimator +import android.view.MotionEvent +import android.view.View +import android.view.animation.LinearInterpolator +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.LinphoneApplication.Companion.corePreferences +import org.linphone.R +import org.linphone.core.* +import org.linphone.core.tools.Log +import org.linphone.utils.AppUtils +import org.linphone.utils.AudioRouteUtils +import org.linphone.utils.Event +import org.linphone.utils.PermissionHelper + +class ControlsViewModel : ViewModel() { + val isMicrophoneMuted = MutableLiveData() + + val isMuteMicrophoneEnabled = MutableLiveData() + + val isSpeakerSelected = MutableLiveData() + + val isBluetoothHeadsetSelected = MutableLiveData() + + val audioRoutesSelected = MutableLiveData() + + val audioRoutesEnabled = MutableLiveData() + + val isVideoAvailable = MutableLiveData() + + val isVideoEnabled = MutableLiveData() + + val isVideoUpdateInProgress = MutableLiveData() + + val isSwitchCameraAvailable = MutableLiveData() + + val isOutgoingEarlyMedia = MutableLiveData() + + val showExtras = MutableLiveData() + + val fullScreenMode = MutableLiveData() + + val pipMode = MutableLiveData() + + val chatRoomCreationInProgress = MutableLiveData() + + val numpadVisible = MutableLiveData() + + val dtmfHistory = MutableLiveData() + + val callStatsVisible = MutableLiveData() + + val proximitySensorEnabled = MediatorLiveData() + + val goToConferenceParticipantsListEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val goToChatEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val goToCallsListEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val goToConferenceLayoutSettings: MutableLiveData> by lazy { + MutableLiveData>() + } + + val askPermissionEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val goToDialer: MutableLiveData> by lazy { + MutableLiveData>() + } + + private val nonEarpieceOutputAudioDevice = MutableLiveData() + + private var previewX: Float = 0f + private var previewY: Float = 0f + val previewTouchListener = View.OnTouchListener { view, event -> + when (event.action) { + MotionEvent.ACTION_DOWN -> { + previewX = view.x - event.rawX + previewY = view.y - event.rawY + true + } + MotionEvent.ACTION_MOVE -> { + view.animate() + .x(event.rawX + previewX) + .y(event.rawY + previewY) + .setDuration(0) + .start() + true + } + else -> { + view.performClick() + false + } + } + } + + private val listener: CoreListenerStub = object : CoreListenerStub() { + override fun onCallStateChanged( + core: Core, + call: Call, + state: Call.State, + message: String + ) { + Log.i("[Call Controls] State changed: $state") + + isOutgoingEarlyMedia.value = state == Call.State.OutgoingEarlyMedia + if (state == Call.State.StreamsRunning) { + isVideoUpdateInProgress.value = false + } else if (state == Call.State.PausedByRemote) { + fullScreenMode.value = false + } + + if (core.currentCall?.currentParams?.isVideoEnabled == true && !PermissionHelper.get().hasCameraPermission()) { + askPermissionEvent.value = Event(Manifest.permission.CAMERA) + } + + updateUI() + } + + override fun onAudioDeviceChanged(core: Core, audioDevice: AudioDevice) { + Log.i("[Call Controls] Audio device changed: ${audioDevice.deviceName}") + + nonEarpieceOutputAudioDevice.value = audioDevice.type != AudioDevice.Type.Earpiece + updateSpeakerState() + updateBluetoothHeadsetState() + } + + override fun onAudioDevicesListUpdated(core: Core) { + Log.i("[Call Controls] Audio devices list updated") + val wasBluetoothPreviouslyAvailable = audioRoutesEnabled.value == true + updateAudioRoutesState() + + if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) { + AudioRouteUtils.routeAudioToHeadset() + } else if (!wasBluetoothPreviouslyAvailable && corePreferences.routeAudioToBluetoothIfAvailable) { + // Only attempt to route audio to bluetooth automatically when bluetooth device is connected + if (AudioRouteUtils.isBluetoothAudioRouteAvailable()) { + AudioRouteUtils.routeAudioToBluetooth() + } + } + } + } + + val extraButtonsMenuTranslateY = MutableLiveData() + private val extraButtonsMenuAnimator: ValueAnimator by lazy { + ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.voip_call_extra_buttons_translate_y), 0f).apply { + addUpdateListener { + val value = it.animatedValue as Float + extraButtonsMenuTranslateY.value = value + } + duration = if (corePreferences.enableAnimations) 500 else 0 + } + } + + val audioRoutesMenuTranslateY = MutableLiveData() + private val audioRoutesMenuAnimator: ValueAnimator by lazy { + ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.voip_audio_routes_menu_translate_y), 0f).apply { + addUpdateListener { + val value = it.animatedValue as Float + audioRoutesMenuTranslateY.value = value + } + duration = if (corePreferences.enableAnimations) 500 else 0 + } + } + + val bouncyCounterTranslateY = MutableLiveData() + + private val bounceAnimator: ValueAnimator by lazy { + ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.voip_counter_bounce_offset), 0f).apply { + addUpdateListener { + val value = it.animatedValue as Float + bouncyCounterTranslateY.value = value + } + interpolator = LinearInterpolator() + duration = 250 + repeatMode = ValueAnimator.REVERSE + repeatCount = ValueAnimator.INFINITE + } + } + + init { + coreContext.core.addListener(listener) + + fullScreenMode.value = false + extraButtonsMenuTranslateY.value = AppUtils.getDimension(R.dimen.voip_call_extra_buttons_translate_y) + audioRoutesMenuTranslateY.value = AppUtils.getDimension(R.dimen.voip_audio_routes_menu_translate_y) + audioRoutesSelected.value = false + + nonEarpieceOutputAudioDevice.value = coreContext.core.outputAudioDevice?.type != AudioDevice.Type.Earpiece + proximitySensorEnabled.value = shouldProximitySensorBeEnabled() + proximitySensorEnabled.addSource(isVideoEnabled) { + proximitySensorEnabled.value = shouldProximitySensorBeEnabled() + } + proximitySensorEnabled.addSource(nonEarpieceOutputAudioDevice) { + proximitySensorEnabled.value = shouldProximitySensorBeEnabled() + } + + updateUI() + + if (corePreferences.enableAnimations) bounceAnimator.start() + } + + override fun onCleared() { + coreContext.core.removeListener(listener) + + super.onCleared() + } + + fun hangUp() { + val core = coreContext.core + when { + core.currentCall != null -> core.currentCall?.terminate() + core.conference?.isIn == true -> core.terminateConference() + else -> core.terminateAllCalls() + } + } + + fun answer() { + val currentCall = coreContext.core.currentCall + if (currentCall != null) { + coreContext.answerCall(currentCall) + } else { + Log.e("[Controls] Cant't find any current call to answer") + } + } + + fun toggleMuteMicrophone() { + if (!PermissionHelper.get().hasRecordAudioPermission()) { + askPermissionEvent.value = Event(Manifest.permission.RECORD_AUDIO) + return + } + + val micEnabled = coreContext.core.isMicEnabled + coreContext.core.isMicEnabled = !micEnabled + updateMicState() + } + + fun toggleSpeaker() { + if (AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed()) { + forceEarpieceAudioRoute() + } else { + forceSpeakerAudioRoute() + } + } + + fun toggleRoutesMenu() { + audioRoutesSelected.value = audioRoutesSelected.value != true + if (audioRoutesSelected.value == true) { + audioRoutesMenuAnimator.start() + } else { + audioRoutesMenuAnimator.reverse() + } + } + + fun forceEarpieceAudioRoute() { + if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) { + Log.i("[Call Controls] Headset found, route audio to it instead of earpiece") + AudioRouteUtils.routeAudioToHeadset() + } else { + AudioRouteUtils.routeAudioToEarpiece() + } + } + + fun forceSpeakerAudioRoute() { + AudioRouteUtils.routeAudioToSpeaker() + } + + fun forceBluetoothAudioRoute() { + AudioRouteUtils.routeAudioToBluetooth() + } + + fun toggleVideo() { + if (!PermissionHelper.get().hasCameraPermission()) { + askPermissionEvent.value = Event(Manifest.permission.CAMERA) + return + } + + val core = coreContext.core + val currentCall = core.currentCall + if (currentCall != null) { + val state = currentCall.state + if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) + return + + isVideoUpdateInProgress.value = true + val params = core.createCallParams(currentCall) + params?.isVideoEnabled = !currentCall.currentParams.isVideoEnabled + currentCall.update(params) + } + } + + fun switchCamera() { + coreContext.switchCamera() + } + + fun showExtraButtons() { + extraButtonsMenuAnimator.start() + showExtras.value = true + } + + fun hideExtraButtons(skipAnimation: Boolean) { + // Animation must be skipped when called from Fragment's onPause() ! + if (skipAnimation) { + extraButtonsMenuTranslateY.value = AppUtils.getDimension(R.dimen.voip_call_extra_buttons_translate_y) + } else { + extraButtonsMenuAnimator.reverse() + } + showExtras.value = false + chatRoomCreationInProgress.value = false + } + + fun toggleFullScreen() { + if (fullScreenMode.value == false && isVideoEnabled.value == false) return + fullScreenMode.value = fullScreenMode.value != true + } + + fun goToConferenceParticipantsList() { + goToConferenceParticipantsListEvent.value = Event(true) + } + + fun goToChat() { + chatRoomCreationInProgress.value = true + goToChatEvent.value = Event(true) + } + + fun showNumpad() { + hideExtraButtons(false) + numpadVisible.value = true + } + + fun hideNumpad() { + numpadVisible.value = false + } + + fun handleDtmfClick(key: Char) { + dtmfHistory.value = "${dtmfHistory.value.orEmpty()}$key" + coreContext.core.playDtmf(key, 1) + coreContext.core.currentCall?.sendDtmf(key) + } + + fun goToCallsList() { + goToCallsListEvent.value = Event(true) + } + + fun showCallStats() { + hideExtraButtons(false) + callStatsVisible.value = true + } + + fun hideCallStats() { + callStatsVisible.value = false + } + + fun goToConferenceLayout() { + goToConferenceLayoutSettings.value = Event(true) + } + + fun goToDialerForCallTransfer() { + goToDialer.value = Event(true) + } + + fun goToDialerForNewCall() { + goToDialer.value = Event(false) + } + + private fun updateUI() { + updateVideoAvailable() + updateVideoEnabled() + updateMicState() + updateSpeakerState() + updateAudioRoutesState() + } + + fun updateMicState() { + isMicrophoneMuted.value = !PermissionHelper.get().hasRecordAudioPermission() || !coreContext.core.isMicEnabled + isMuteMicrophoneEnabled.value = coreContext.core.currentCall != null || coreContext.core.conference?.isIn == true + } + + private fun updateSpeakerState() { + isSpeakerSelected.value = AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed() + } + + private fun updateBluetoothHeadsetState() { + isBluetoothHeadsetSelected.value = AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed() + } + + private fun updateAudioRoutesState() { + val bluetoothDeviceAvailable = AudioRouteUtils.isBluetoothAudioRouteAvailable() + audioRoutesEnabled.value = bluetoothDeviceAvailable + + if (!bluetoothDeviceAvailable) { + audioRoutesSelected.value = false + } + } + + private fun updateVideoAvailable() { + val core = coreContext.core + val currentCall = core.currentCall + isVideoAvailable.value = (core.isVideoCaptureEnabled || core.isVideoPreviewEnabled) && + ((currentCall != null && !currentCall.mediaInProgress()) || core.conference?.isIn == true) + } + + private fun updateVideoEnabled() { + val enabled = coreContext.core.currentCall?.currentParams?.isVideoEnabled ?: false + if (enabled && isVideoEnabled.value == false) { + Log.i("[Call Controls] Video is being turned on") + if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled) { + // Do not turn speaker on when video is enabled if headset or bluetooth is used + if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() && + !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed() + ) { + Log.i("[Call Controls] Video enabled and no wired headset not bluetooth in use, routing audio to speaker") + AudioRouteUtils.routeAudioToSpeaker() + } + } + } + isVideoEnabled.value = enabled + isSwitchCameraAvailable.value = enabled && coreContext.showSwitchCameraButton() + } + + private fun shouldProximitySensorBeEnabled(): Boolean { + return !(isVideoEnabled.value ?: false) && !(nonEarpieceOutputAudioDevice.value ?: false) + } +} diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/StatisticsListViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/StatisticsListViewModel.kt similarity index 92% rename from app/src/main/java/org/linphone/activities/call/viewmodels/StatisticsListViewModel.kt rename to app/src/main/java/org/linphone/activities/voip/viewmodels/StatisticsListViewModel.kt index 47bf460e7..e4c6f8915 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/StatisticsListViewModel.kt +++ b/app/src/main/java/org/linphone/activities/voip/viewmodels/StatisticsListViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2020 Belledonne Communications SARL. + * Copyright (c) 2010-2021 Belledonne Communications SARL. * * This file is part of linphone-android * (see https://www.linphone.org). @@ -17,12 +17,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.activities.call.viewmodels +package org.linphone.activities.voip.viewmodels import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.call.data.CallStatisticsData +import org.linphone.activities.voip.data.CallStatisticsData import org.linphone.core.Call import org.linphone.core.Core import org.linphone.core.CoreListenerStub diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/StatusViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/StatusViewModel.kt similarity index 95% rename from app/src/main/java/org/linphone/activities/call/viewmodels/StatusViewModel.kt rename to app/src/main/java/org/linphone/activities/voip/viewmodels/StatusViewModel.kt index 425872eeb..11d3fac31 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/StatusViewModel.kt +++ b/app/src/main/java/org/linphone/activities/voip/viewmodels/StatusViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2020 Belledonne Communications SARL. + * Copyright (c) 2010-2021 Belledonne Communications SARL. * * This file is part of linphone-android * (see https://www.linphone.org). @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.activities.call.viewmodels +package org.linphone.activities.voip.viewmodels import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext @@ -38,6 +38,10 @@ class StatusViewModel : StatusViewModel() { MutableLiveData>() } + val showCallStatsEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + private val listener = object : CoreListenerStub() { override fun onCallStatsUpdated(core: Core, call: Call, stats: CallStats) { updateCallQualityIcon() @@ -96,6 +100,10 @@ class StatusViewModel : StatusViewModel() { } } + fun showCallStats() { + showCallStatsEvent.value = Event(true) + } + fun updateEncryptionInfo(call: Call) { if (call.dir == Call.Dir.Incoming && call.state == Call.State.IncomingReceived && call.core.isMediaEncryptionMandatory) { // If the incoming call view is displayed while encryption is mandatory, diff --git a/app/src/main/java/org/linphone/activities/voip/views/HorizontalScrollDotsView.kt b/app/src/main/java/org/linphone/activities/voip/views/HorizontalScrollDotsView.kt new file mode 100644 index 000000000..a7b892d1f --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/views/HorizontalScrollDotsView.kt @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.util.AttributeSet +import android.view.View +import android.widget.HorizontalScrollView +import java.lang.Exception +import kotlin.math.ceil +import kotlin.math.roundToInt +import org.linphone.R +import org.linphone.core.tools.Log +import org.linphone.utils.AppUtils + +class HorizontalScrollDotsView : View { + private var count = 2 + private var selected = 0 + + private var radius: Float = 5f + private var margin: Float = 2f + private var screenWidth: Float = 0f + private var itemWidth: Float = 0f + + private lateinit var dotPaint: Paint + private lateinit var selectedDotPaint: Paint + + private var horizontalScrollViewRef = 0 + private lateinit var horizontalScrollView: HorizontalScrollView + private val scrollListener = OnScrollChangeListener { v, scrollX, _, _, _ -> + val childWidth: Int = (v as HorizontalScrollView).getChildAt(0).measuredWidth + val scrollViewWidth = v.measuredWidth + val scrollableX = childWidth - scrollViewWidth + + if (scrollableX > 0) { + val percent = (scrollX.toFloat() * 100 / scrollableX).toDouble() + if (count > 1) { + val selectedDot = percent / (100 / (count - 1)) + val dot = selectedDot.roundToInt() + if (dot != selected) { + setSelectedDot(dot) + } + } + } + } + + constructor(context: Context) : super(context) { init(context) } + + constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context) + + context.theme.obtainStyledAttributes( + attrs, + R.styleable.HorizontalScrollDot, + defStyleAttr, 0 + ).apply { + try { + radius = getDimension(R.styleable.HorizontalScrollDot_dotRadius, 5f) + + count = getInt(R.styleable.HorizontalScrollDot_dotCount, 1) + + val color = getColor(R.styleable.HorizontalScrollDot_dotColor, context.resources.getColor(R.color.voip_gray_background)) + dotPaint.color = color + val selectedColor = getColor(R.styleable.HorizontalScrollDot_selectedDotColor, context.resources.getColor(R.color.voip_dark_gray)) + selectedDotPaint.color = selectedColor + + selected = getInt(R.styleable.HorizontalScrollDot_selectedDot, 1) + + horizontalScrollViewRef = getResourceId(R.styleable.HorizontalScrollDot_horizontalScrollView, 0) + Log.d("[Horizontal Scroll Dots] HorizontalScrollView reference set is $horizontalScrollViewRef") + + invalidate() + } catch (e: Exception) { + Log.e("[Horizontal Scroll Dots] $e") + } finally { + recycle() + } + } + } + + fun init(context: Context) { + radius = AppUtils.dpToPixels(context, 5f) + margin = AppUtils.dpToPixels(context, 5f) + + dotPaint = Paint() + dotPaint.color = Color.parseColor("#D8D8D8") + selectedDotPaint = Paint() + selectedDotPaint.color = Color.parseColor("#4B5964") + + val screenRect = Rect() + getWindowVisibleDisplayFrame(screenRect) + screenWidth = screenRect.width().toFloat() + val marginBetweenItems = context.resources.getDimension(R.dimen.voip_active_speaker_miniature_margin) + itemWidth = context.resources.getDimension(R.dimen.voip_active_speaker_miniature_size) + marginBetweenItems + Log.d("[Horizontal Scroll Dots] Screen width is $screenWidth and item width is $itemWidth") + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + if (horizontalScrollViewRef > 0) { + try { + horizontalScrollView = (parent as View).findViewById(horizontalScrollViewRef) + horizontalScrollView.setOnScrollChangeListener(scrollListener) + Log.d("[Horizontal Scroll Dots] HorizontalScrollView scroll listener set") + } catch (e: Exception) { + Log.e("[Horizontal Scroll Dots] Failed to find HorizontalScrollView from id $horizontalScrollViewRef: $e") + } + } else { + Log.e("[Horizontal Scroll Dots] No HorizontalScrollView reference given") + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + for (i in 0 until count) { + if (i == selected) { + canvas.drawCircle( + (i + 1) * margin + (i * 2 + 1) * radius, + radius, + radius, + selectedDotPaint + ) + } else { + canvas.drawCircle( + (i + 1) * margin + (i * 2 + 1) * radius, + radius, + radius, + dotPaint + ) + } + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + val width = ((radius * 2 + margin) * count + margin).toInt() + val height: Int = (radius * 2).toInt() + + setMeasuredDimension(width, height) + } + + fun setDotCount(count: Int) { + this.count = count + requestLayout() + invalidate() + } + + fun setItemCount(items: Int) { + val itemsPerScreen = (screenWidth / itemWidth) + val dots = ceil(items.toDouble() / itemsPerScreen).toInt() + + Log.d("[Horizontal Scroll Dots] Calculated $count for $items items ($itemsPerScreen items fit in screen width), given that screen width is $screenWidth and item width is $itemWidth") + setDotCount(dots) + } + + fun setSelectedDot(index: Int) { + selected = index + invalidate() + } +} diff --git a/app/src/main/java/org/linphone/activities/voip/views/RoundCornersTextureView.kt b/app/src/main/java/org/linphone/activities/voip/views/RoundCornersTextureView.kt new file mode 100644 index 000000000..68ae85ca0 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/voip/views/RoundCornersTextureView.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.activities.voip.views + +import android.content.Context +import android.graphics.Outline +import android.graphics.Rect +import android.util.AttributeSet +import android.view.View +import android.view.ViewOutlineProvider +import org.linphone.R +import org.linphone.mediastream.video.capture.CaptureTextureView + +class RoundCornersTextureView : CaptureTextureView { + constructor(context: Context) : super(context) { + mAlignTopRight = true + mDisplayMode = DisplayMode.BLACK_BARS + setRoundCorners() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + readAttributes(attrs) + setRoundCorners() + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + readAttributes(attrs) + setRoundCorners() + } + + private fun readAttributes(attrs: AttributeSet) { + context.theme.obtainStyledAttributes( + attrs, + R.styleable.RoundCornersTextureView, + 0, + 0 + ).apply { + try { + mAlignTopRight = getBoolean(R.styleable.RoundCornersTextureView_alignTopRight, true) + val mode = getInteger(R.styleable.RoundCornersTextureView_displayMode, DisplayMode.BLACK_BARS.ordinal) + mDisplayMode = when (mode) { + 1 -> DisplayMode.OCCUPY_ALL_SPACE + 2 -> DisplayMode.HYBRID + else -> DisplayMode.BLACK_BARS + } + } finally { + recycle() + } + } + } + + private fun setRoundCorners() { + outlineProvider = object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + val rect = if (previewRectF != null && + actualDisplayMode == DisplayMode.BLACK_BARS && + mAlignTopRight + ) { + Rect( + previewRectF.left.toInt(), + previewRectF.top.toInt(), + previewRectF.right.toInt(), + previewRectF.bottom.toInt() + ) + } else { + Rect( + 0, + 0, + width, + height + ) + } + outline.setRoundRect(rect, context.resources.getDimension(R.dimen.voip_round_corners_texture_view_radius)) + } + } + clipToOutline = true + } + + override fun setAspectRatio(width: Int, height: Int) { + super.setAspectRatio(width, height) + + val previewSize = previewVideoSize + if (previewSize.width > 0 && previewSize.height > 0) { + setRoundCorners() + } + } +} diff --git a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt index db1a1b764..ecea7b14c 100644 --- a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt @@ -34,6 +34,8 @@ import android.os.Environment import android.os.Vibrator import android.provider.MediaStore import android.provider.Settings +import android.view.View +import android.view.Window import android.view.WindowManager import android.view.inputmethod.EditorInfo import org.linphone.R @@ -245,5 +247,21 @@ class Api21Compatibility { fun startForegroundService(context: Context, intent: Intent) { context.startService(intent) } + + fun hideAndroidSystemUI(hide: Boolean, window: Window) { + val decorView = window.decorView + val uiOptions = if (hide) { + View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + } else { + View.SYSTEM_UI_FLAG_VISIBLE + } + decorView.systemUiVisibility = uiOptions + + if (hide) { + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) + } + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt index a6e7be818..37f40cf90 100644 --- a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt @@ -53,11 +53,11 @@ class Api26Compatibility { fun enterPipMode(activity: Activity) { val supportsPip = activity.packageManager .hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) - Log.i("[Call] Is picture in picture supported: $supportsPip") + Log.i("[Call] Is PiP supported: $supportsPip") if (supportsPip) { val params = PictureInPictureParams.Builder().build() if (!activity.enterPictureInPictureMode(params)) { - Log.e("[Call] Failed to enter picture in picture mode") + Log.e("[Call] Failed to enter PiP mode") } } } diff --git a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt index 70637bbd2..556d755e0 100644 --- a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt @@ -24,6 +24,9 @@ import android.annotation.TargetApi import android.app.Activity import android.content.Context import android.content.pm.ShortcutManager +import android.view.Window +import android.view.WindowInsets +import android.view.WindowInsetsController import androidx.fragment.app.Fragment import org.linphone.core.ChatRoom import org.linphone.core.tools.Log @@ -69,5 +72,19 @@ class Api30Compatibility { val shortcutsToRemoveList = arrayListOf(id) shortcutManager.removeLongLivedShortcuts(shortcutsToRemoveList) } + + fun hideAndroidSystemUI(hide: Boolean, window: Window) { + if (hide) { + window.setDecorFitsSystemWindows(false) + window.insetsController?.let { + it.systemBarsBehavior = + WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + it.hide(WindowInsets.Type.systemBars()) + } + } else { + window.setDecorFitsSystemWindows(true) + window.insetsController?.show(WindowInsets.Type.systemBars()) + } + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt index 82262693e..44426abe8 100644 --- a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt @@ -20,16 +20,20 @@ package org.linphone.compatibility import android.annotation.TargetApi +import android.app.Activity import android.app.Notification import android.app.PendingIntent import android.app.Person +import android.app.PictureInPictureParams import android.content.Context +import android.content.pm.PackageManager import androidx.core.content.ContextCompat import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.contact.Contact import org.linphone.core.Call +import org.linphone.core.tools.Log import org.linphone.notifications.Notifiable import org.linphone.notifications.NotificationsManager import org.linphone.utils.ImageUtils @@ -91,7 +95,8 @@ class Api31Compatibility { channel: String, notificationsManager: NotificationsManager ): Notification { - val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress) + val contact: Contact? = + coreContext.contactsManager.findContactByAddress(call.remoteAddress) val pictureUri = contact?.getContactThumbnailPictureUri() val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) @@ -122,7 +127,9 @@ class Api31Compatibility { val builder = Notification.Builder( context, channel ) - .setStyle(Notification.CallStyle.forOngoingCall(caller, declineIntent).setIsVideo(isVideo)) + .setStyle( + Notification.CallStyle.forOngoingCall(caller, declineIntent).setIsVideo(isVideo) + ) .setSmallIcon(iconResourceId) .setAutoCancel(false) .setCategory(Notification.CATEGORY_CALL) @@ -131,7 +138,10 @@ class Api31Compatibility { .setShowWhen(true) .setOngoing(true) .setColor(ContextCompat.getColor(context, R.color.notification_led_color)) - .setFullScreenIntent(pendingIntent, true) // This is required for CallStyle notification + .setFullScreenIntent( + pendingIntent, + true + ) // This is required for CallStyle notification if (!corePreferences.preventInterfaceFromShowingUp) { builder.setContentIntent(pendingIntent) @@ -139,5 +149,16 @@ class Api31Compatibility { return builder.build() } + + fun enableAutoEnterPiP(activity: Activity, enable: Boolean) { + val supportsPip = activity.packageManager + .hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) + Log.i("[Call] Is PiP supported: $supportsPip") + if (supportsPip) { + val params = PictureInPictureParams.Builder().setAutoEnterEnabled(enable).build() + activity.setPictureInPictureParams(params) + Log.i("[Call] PiP auto enter enabled params set to $enable") + } + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index e01d09c95..316b02ea7 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -31,6 +31,7 @@ import android.os.Build import android.os.Vibrator import android.telephony.TelephonyManager import android.view.View +import android.view.Window import android.view.WindowManager import androidx.core.app.NotificationManagerCompat import androidx.fragment.app.Fragment @@ -219,11 +220,17 @@ class Compatibility { } fun enterPipMode(activity: Activity) { - if (Version.sdkAboveOrEqual(Version.API26_O_80)) { + if (Version.sdkStrictlyBelow(Version.API31_ANDROID_12) && Version.sdkAboveOrEqual(Version.API26_O_80)) { Api26Compatibility.enterPipMode(activity) } } + fun enableAutoEnterPiP(activity: Activity, enable: Boolean) { + if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) { + Api31Compatibility.enableAutoEnterPiP(activity, enable) + } + } + fun eventVibration(vibrator: Vibrator) { if (Version.sdkAboveOrEqual(Version.API26_O_80)) { Api26Compatibility.eventVibration(vibrator) @@ -239,6 +246,14 @@ class Compatibility { return false } + fun hideAndroidSystemUI(hide: Boolean, window: Window) { + if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { + Api30Compatibility.hideAndroidSystemUI(hide, window) + } else { + Api21Compatibility.hideAndroidSystemUI(hide, window) + } + } + /* Contacts */ fun createShortcutsToContacts(context: Context) { diff --git a/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt b/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt index 05b6a482c..0f5dbadbe 100644 --- a/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt @@ -54,7 +54,7 @@ class XiaomiCompatibility { val builder = NotificationCompat.Builder(context, context.getString(R.string.notification_channel_incoming_call_id)) .addPerson(notificationsManager.getPerson(contact, displayName, roundPicture)) .setSmallIcon(R.drawable.topbar_call_notification) - .setLargeIcon(roundPicture ?: BitmapFactory.decodeResource(context.resources, R.drawable.avatar)) + .setLargeIcon(roundPicture ?: BitmapFactory.decodeResource(context.resources, R.drawable.voip_single_contact_avatar)) .setContentTitle(displayName) .setContentText(address) .setSubText(context.getString(R.string.incoming_call_notification_title)) diff --git a/app/src/main/java/org/linphone/contact/BigContactAvatarView.kt b/app/src/main/java/org/linphone/contact/BigContactAvatarView.kt index a608d0c18..d72c97d43 100644 --- a/app/src/main/java/org/linphone/contact/BigContactAvatarView.kt +++ b/app/src/main/java/org/linphone/contact/BigContactAvatarView.kt @@ -25,7 +25,7 @@ import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout import androidx.databinding.DataBindingUtil -import org.linphone.LinphoneApplication +import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.databinding.ContactAvatarBigBinding import org.linphone.utils.AppUtils @@ -68,6 +68,6 @@ class BigContactAvatarView : LinearLayout { binding.initials = initials binding.generatedAvatarVisibility = initials.isNotEmpty() && initials != "+" binding.imagePath = contact?.getContactPictureUri() - binding.borderVisibility = LinphoneApplication.corePreferences.showBorderOnBigContactAvatar + binding.borderVisibility = corePreferences.showBorderOnBigContactAvatar } } diff --git a/app/src/main/java/org/linphone/contact/Contact.kt b/app/src/main/java/org/linphone/contact/Contact.kt index 8cba22e3a..64ad43bf8 100644 --- a/app/src/main/java/org/linphone/contact/Contact.kt +++ b/app/src/main/java/org/linphone/contact/Contact.kt @@ -136,7 +136,7 @@ open class Contact : Comparable { } open fun getContactPictureUri(): Uri? { - return null + return thumbnailUri } open fun getPerson(): Person { @@ -150,7 +150,7 @@ open class Contact : Comparable { val icon = if (bm == null) IconCompat.createWithResource( coreContext.context, - R.drawable.avatar + R.drawable.voip_single_contact_avatar ) else IconCompat.createWithAdaptiveBitmap(bm) if (icon != null) { personBuilder.setIcon(icon) diff --git a/app/src/main/java/org/linphone/contact/ContactDataInterface.kt b/app/src/main/java/org/linphone/contact/ContactDataInterface.kt index 5e58786ed..17a9a9fd0 100644 --- a/app/src/main/java/org/linphone/contact/ContactDataInterface.kt +++ b/app/src/main/java/org/linphone/contact/ContactDataInterface.kt @@ -21,9 +21,10 @@ package org.linphone.contact import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.main.viewmodels.ErrorReportingViewModel +import org.linphone.activities.main.viewmodels.MessageNotifierViewModel import org.linphone.core.Address import org.linphone.core.ChatRoomSecurityLevel +import org.linphone.utils.AppUtils import org.linphone.utils.LinphoneUtils interface ContactDataInterface { @@ -41,6 +42,8 @@ open class GenericContactData(private val sipAddress: Address) : ContactDataInte final override val contact: MutableLiveData = MutableLiveData() final override val displayName: MutableLiveData = MutableLiveData() final override val securityLevel: MutableLiveData = MutableLiveData() + val initials = MutableLiveData() + val displayInitials = MutableLiveData() private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() { override fun onContactUpdated(contact: Contact) { @@ -60,12 +63,20 @@ open class GenericContactData(private val sipAddress: Address) : ContactDataInte private fun contactLookup() { displayName.value = LinphoneUtils.getDisplayName(sipAddress) - contact.value = - coreContext.contactsManager.findContactByAddress(sipAddress) + + val c = coreContext.contactsManager.findContactByAddress(sipAddress) + contact.value = c + + initials.value = if (c != null) { + AppUtils.getInitials(c.fullName ?: c.firstName + " " + c.lastName) + } else { + AppUtils.getInitials(displayName.value ?: "") + } + displayInitials.value = initials.value.orEmpty().isNotEmpty() && initials.value.orEmpty() != "+" } } -abstract class GenericContactViewModel(private val sipAddress: Address) : ErrorReportingViewModel(), ContactDataInterface { +abstract class GenericContactViewModel(private val sipAddress: Address) : MessageNotifierViewModel(), ContactDataInterface { final override val contact: MutableLiveData = MutableLiveData() final override val displayName: MutableLiveData = MutableLiveData() final override val securityLevel: MutableLiveData = MutableLiveData() diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomCreationContactData.kt b/app/src/main/java/org/linphone/contact/ContactSelectionData.kt similarity index 92% rename from app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomCreationContactData.kt rename to app/src/main/java/org/linphone/contact/ContactSelectionData.kt index 1c02c2e87..192322ba4 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomCreationContactData.kt +++ b/app/src/main/java/org/linphone/contact/ContactSelectionData.kt @@ -17,16 +17,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.activities.main.chat.data +package org.linphone.contact import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.contact.Contact -import org.linphone.contact.ContactDataInterface import org.linphone.core.* import org.linphone.utils.LinphoneUtils -class ChatRoomCreationContactData(private val searchResult: SearchResult) : ContactDataInterface { +class ContactSelectionData(private val searchResult: SearchResult) : ContactDataInterface { override val contact: MutableLiveData = MutableLiveData() override val displayName: MutableLiveData = MutableLiveData() override val securityLevel: MutableLiveData = MutableLiveData() diff --git a/app/src/main/java/org/linphone/contact/ContactsManager.kt b/app/src/main/java/org/linphone/contact/ContactsManager.kt index fdc2af0d8..7bb7658ea 100644 --- a/app/src/main/java/org/linphone/contact/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contact/ContactsManager.kt @@ -164,6 +164,7 @@ class ContactsManager(private val context: Context) { @Synchronized fun updateLocalContacts() { + Log.i("[Contacts Manager] Updating local contact(s)") localAccountsContacts.clear() for (account in coreContext.core.accountList) { @@ -171,6 +172,7 @@ class ContactsManager(private val context: Context) { localContact.fullName = account.params.identityAddress?.displayName ?: account.params.identityAddress?.username val pictureUri = corePreferences.defaultAccountAvatarPath if (pictureUri != null) { + Log.i("[Contacts Manager] Found local picture URI: $pictureUri") localContact.setContactThumbnailPictureUri(Uri.fromFile(File(pictureUri))) } val address = account.params.identityAddress diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomCreationContactsAdapter.kt b/app/src/main/java/org/linphone/contact/ContactsSelectionAdapter.kt similarity index 80% rename from app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomCreationContactsAdapter.kt rename to app/src/main/java/org/linphone/contact/ContactsSelectionAdapter.kt index aef208d05..26cf8bda1 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomCreationContactsAdapter.kt +++ b/app/src/main/java/org/linphone/contact/ContactsSelectionAdapter.kt @@ -17,8 +17,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.activities.main.chat.adapters +package org.linphone.contact +import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.ViewGroup import androidx.databinding.DataBindingUtil @@ -29,38 +30,42 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R -import org.linphone.activities.main.chat.data.ChatRoomCreationContactData import org.linphone.core.Address import org.linphone.core.FriendCapability import org.linphone.core.SearchResult -import org.linphone.databinding.ChatRoomCreationContactCellBinding +import org.linphone.databinding.ContactSelectionCellBinding import org.linphone.utils.Event -class ChatRoomCreationContactsAdapter( +class ContactsSelectionAdapter( private val viewLifecycleOwner: LifecycleOwner ) : ListAdapter(SearchResultDiffCallback()) { val selectedContact = MutableLiveData>() - var groupChatEnabled: Boolean = false - private var selectedAddresses = ArrayList
() - private var securityEnabled: Boolean = false + private var requireGroupChatCapability: Boolean = false + private var requireLimeCapability: Boolean = false + @SuppressLint("NotifyDataSetChanged") fun updateSelectedAddresses(selection: ArrayList
) { selectedAddresses = selection notifyDataSetChanged() } - fun updateSecurity(enabled: Boolean) { - securityEnabled = enabled + @SuppressLint("NotifyDataSetChanged") + fun setLimeCapabilityRequired(enabled: Boolean) { + requireLimeCapability = enabled notifyDataSetChanged() } + fun setGroupChatCapabilityRequired(enabled: Boolean) { + requireGroupChatCapability = enabled + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val binding: ChatRoomCreationContactCellBinding = DataBindingUtil.inflate( + val binding: ContactSelectionCellBinding = DataBindingUtil.inflate( LayoutInflater.from(parent.context), - R.layout.chat_room_creation_contact_cell, parent, false + R.layout.contact_selection_cell, parent, false ) return ViewHolder(binding) } @@ -70,16 +75,16 @@ class ChatRoomCreationContactsAdapter( } inner class ViewHolder( - private val binding: ChatRoomCreationContactCellBinding + private val binding: ContactSelectionCellBinding ) : RecyclerView.ViewHolder(binding.root) { fun bind(searchResult: SearchResult) { with(binding) { - val searchResultViewModel = ChatRoomCreationContactData(searchResult) + val searchResultViewModel = ContactSelectionData(searchResult) data = searchResultViewModel lifecycleOwner = viewLifecycleOwner - updateSecurity(searchResult, searchResultViewModel, securityEnabled) + updateSecurity(searchResult, searchResultViewModel, requireLimeCapability) val selected = selectedAddresses.find { address -> val searchAddress = searchResult.address @@ -97,13 +102,13 @@ class ChatRoomCreationContactsAdapter( private fun updateSecurity( searchResult: SearchResult, - viewModel: ChatRoomCreationContactData, + viewModel: ContactSelectionData, securityEnabled: Boolean ) { val searchAddress = searchResult.address val isMyself = securityEnabled && searchAddress != null && coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(searchAddress) ?: false val limeCheck = !securityEnabled || (securityEnabled && searchResult.hasCapability(FriendCapability.LimeX3Dh)) - val groupCheck = !groupChatEnabled || (groupChatEnabled && searchResult.hasCapability(FriendCapability.GroupChat)) + val groupCheck = !requireGroupChatCapability || (requireGroupChatCapability && searchResult.hasCapability(FriendCapability.GroupChat)) val disabled = if (searchResult.friend != null) !limeCheck || !groupCheck || isMyself else false // Generated entry from search filter viewModel.isDisabled.value = disabled diff --git a/app/src/main/java/org/linphone/contact/ContactsSelectionViewModel.kt b/app/src/main/java/org/linphone/contact/ContactsSelectionViewModel.kt new file mode 100644 index 000000000..ad47ee550 --- /dev/null +++ b/app/src/main/java/org/linphone/contact/ContactsSelectionViewModel.kt @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * 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. + * + * This program 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 this program. If not, see . + */ +package org.linphone.contact + +import androidx.lifecycle.MutableLiveData +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.activities.main.viewmodels.MessageNotifierViewModel +import org.linphone.core.Address +import org.linphone.core.SearchResult +import org.linphone.core.tools.Log + +open class ContactsSelectionViewModel : MessageNotifierViewModel() { + val contactsList = MutableLiveData>() + + val sipContactsSelected = MutableLiveData() + + val selectedAddresses = MutableLiveData>() + + val filter = MutableLiveData() + private var previousFilter = "" + + private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() { + override fun onContactsUpdated() { + Log.i("[Contacts Selection] Contacts have changed") + updateContactsList() + } + } + + init { + sipContactsSelected.value = coreContext.contactsManager.shouldDisplaySipContactsList() + + selectedAddresses.value = arrayListOf() + + coreContext.contactsManager.addListener(contactsUpdatedListener) + } + + override fun onCleared() { + coreContext.contactsManager.removeListener(contactsUpdatedListener) + + super.onCleared() + } + + fun applyFilter() { + val filterValue = filter.value.orEmpty() + if (previousFilter == filterValue) return + + if (previousFilter.isNotEmpty() && previousFilter.length > filterValue.length) { + coreContext.contactsManager.magicSearch.resetSearchCache() + } + previousFilter = filterValue + + updateContactsList() + } + + fun updateContactsList() { + val domain = if (sipContactsSelected.value == true) coreContext.core.defaultAccount?.params?.domain ?: "" else "" + val results = coreContext.contactsManager.magicSearch.getContactListFromFilter(filter.value.orEmpty(), domain) + + val list = arrayListOf() + for (result in results) { + list.add(result) + } + contactsList.value = list + } + + fun toggleSelectionForSearchResult(searchResult: SearchResult) { + val address = searchResult.address + if (address != null) { + toggleSelectionForAddress(address) + } + } + + fun toggleSelectionForAddress(address: Address) { + val list = arrayListOf
() + list.addAll(selectedAddresses.value.orEmpty()) + + val found = list.find { + it.weakEqual(address) + } + + if (found != null) { + list.remove(found) + } else { + val contact = coreContext.contactsManager.findContactByAddress(address) + if (contact != null) { + val clone = address.clone() + clone.displayName = contact.fullName + list.add(clone) + } else { + list.add(address) + } + } + + selectedAddresses.value = list + } +} diff --git a/app/src/main/java/org/linphone/contact/NativeContact.kt b/app/src/main/java/org/linphone/contact/NativeContact.kt index c28f6fd6b..ff7a38c7e 100644 --- a/app/src/main/java/org/linphone/contact/NativeContact.kt +++ b/app/src/main/java/org/linphone/contact/NativeContact.kt @@ -70,7 +70,7 @@ class NativeContact(val nativeId: String, private val lookupKey: String? = null) val icon = if (bm == null) IconCompat.createWithResource( coreContext.context, - R.drawable.avatar + R.drawable.voip_single_contact_avatar ) else IconCompat.createWithAdaptiveBitmap(bm) if (icon != null) { personBuilder.setIcon(icon) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index d098d076d..99b216b69 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -51,9 +51,6 @@ import kotlinx.coroutines.* import org.linphone.BuildConfig import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R -import org.linphone.activities.call.CallActivity -import org.linphone.activities.call.IncomingCallActivity -import org.linphone.activities.call.OutgoingCallActivity import org.linphone.compatibility.Compatibility import org.linphone.compatibility.PhoneStateInterface import org.linphone.contact.Contact @@ -164,7 +161,11 @@ class CoreContext(val context: Context, coreConfig: Config) { } } } else if (state == Call.State.OutgoingInit) { - onOutgoingStarted() + val conferenceInfo = core.findConferenceInformationFromUri(call.remoteAddress) + // Do not show outgoing call view for conference calls, wait for connected state + if (conferenceInfo == null) { + onOutgoingStarted() + } } else if (state == Call.State.OutgoingProgress) { if (core.callsNb == 1 && corePreferences.routeAudioToBluetoothIfAvailable) { AudioRouteUtils.routeAudioToBluetooth(call) @@ -188,25 +189,10 @@ class CoreContext(val context: Context, coreConfig: Config) { } } } - - if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled && call.currentParams.isVideoEnabled) { - // Do not turn speaker on when video is enabled if headset or bluetooth is used - if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() && !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed( - call - ) - ) { - Log.i("[Context] Video enabled and no wired headset not bluetooth in use, routing audio to speaker") - AudioRouteUtils.routeAudioToSpeaker(call) - } - } } else if (state == Call.State.End || state == Call.State.Error || state == Call.State.Released) { - if (core.callsNb == 0) { - removeCallOverlay() - } - if (state == Call.State.Error) { Log.w("[Context] Call error reason is ${call.errorInfo.protocolCode} / ${call.errorInfo.reason} / ${call.errorInfo.phrase}") - val message = when (call.errorInfo.reason) { + val toastMessage = when (call.errorInfo.reason) { Reason.Busy -> context.getString(R.string.call_error_user_busy) Reason.IOError -> context.getString(R.string.call_error_io_error) Reason.NotAcceptable -> context.getString(R.string.call_error_incompatible_media_params) @@ -215,20 +201,26 @@ class CoreContext(val context: Context, coreConfig: Config) { Reason.TemporarilyUnavailable -> context.getString(R.string.call_error_temporarily_unavailable) else -> context.getString(R.string.call_error_generic).format("${call.errorInfo.protocolCode} / ${call.errorInfo.phrase}") } - callErrorMessageResourceId.value = Event(message) + callErrorMessageResourceId.value = Event(toastMessage) } else if (state == Call.State.End && call.dir == Call.Dir.Outgoing && - call.errorInfo.reason == Reason.Declined + call.errorInfo.reason == Reason.Declined && + core.callsNb == 0 ) { Log.i("[Context] Call has been declined") - val message = context.getString(R.string.call_error_declined) - callErrorMessageResourceId.value = Event(message) + val toastMessage = context.getString(R.string.call_error_declined) + callErrorMessageResourceId.value = Event(toastMessage) } } previousCallState = state } + override fun onLastCallEnded(core: Core) { + Log.i("[Context] Last call has ended") + removeCallOverlay() + } + override fun onMessageReceived(core: Core, chatRoom: ChatRoom, message: ChatMessage) { if (core.maxSizeForAutoDownloadIncomingFiles != -1) { var hasFile = false @@ -267,7 +259,8 @@ class CoreContext(val context: Context, coreConfig: Config) { init { if (context.resources.getBoolean(R.bool.crashlytics_enabled)) { - loggingService.addListener(loggingServiceListener) + // TODO: FIXME: uncomment + // loggingService.addListener(loggingServiceListener) Log.i("[Context] Crashlytics enabled, register logging service listener") } @@ -347,7 +340,8 @@ class CoreContext(val context: Context, coreConfig: Config) { core.stop() core.removeListener(listener) stopped = true - loggingService.removeListener(loggingServiceListener) + // TODO: FIXME: uncomment + // loggingService.removeListener(loggingServiceListener) } private fun configureCore() { @@ -367,12 +361,46 @@ class CoreContext(val context: Context, coreConfig: Config) { for (account in core.accountList) { if (account.params.identityAddress?.domain == corePreferences.defaultDomain) { - // Ensure conference URI is set on sip.linphone.org proxy configs + var paramsChanged = false + val params = account.params.clone() + + // Ensure conference factory URI is set on sip.linphone.org proxy configs if (account.params.conferenceFactoryUri == null) { - val params = account.params.clone() val uri = corePreferences.conferenceServerUri Log.i("[Context] Setting conference factory on proxy config ${params.identityAddress?.asString()} to default value: $uri") params.conferenceFactoryUri = uri + paramsChanged = true + } + + // Ensure audio/video conference factory URI is set on sip.linphone.org proxy configs + if (account.params.audioVideoConferenceFactoryAddress == null) { + val uri = corePreferences.audioVideoConferenceServerUri + val address = core.interpretUrl(uri) + if (address != null) { + Log.i("[Context] Setting audio/video conference factory on proxy config ${params.identityAddress?.asString()} to default value: $uri") + params.audioVideoConferenceFactoryAddress = address + paramsChanged = true + } else { + Log.e("[Context] Failed to parse audio/video conference factory URI: $uri") + } + } + + // Enable Bundle mode by default + if (!account.params.isRtpBundleEnabled) { + Log.i("[Context] Enabling RTP bundle mode on proxy config ${params.identityAddress?.asString()}") + params.isRtpBundleEnabled = true + paramsChanged = true + } + + // Ensure we allow CPIM messages in basic chat rooms + if (!account.params.isCpimInBasicChatRoomEnabled) { + params.isCpimInBasicChatRoomEnabled = true + paramsChanged = true + Log.i("[Context] CPIM allowed in basic chat rooms for account ${params.identityAddress?.asString()}") + } + + if (paramsChanged) { + Log.i("[Context] Account params have been updated, apply changes") account.params = params } @@ -385,12 +413,6 @@ class CoreContext(val context: Context, coreConfig: Config) { core.limeX3DhServerUrl = url } } - - // Ensure we allow CPIM messages in basic chat rooms - val newParams = account.params.clone() - newParams.isCpimInBasicChatRoomEnabled = true - account.params = newParams - Log.i("[Context] CPIM allowed in basic chat rooms for account ${newParams.identityAddress?.asStringUriOnly()}") } } @@ -462,6 +484,13 @@ class CoreContext(val context: Context, coreConfig: Config) { return false } + fun videoUpdateRequestTimedOut(call: Call) { + coroutineScope.launch { + Log.w("[Context] 30 seconds have passed, declining video request") + answerCallVideoUpdateRequest(call, false) + } + } + fun answerCallVideoUpdateRequest(call: Call, accept: Boolean) { val params = core.createCallParams(call) @@ -506,7 +535,7 @@ class CoreContext(val context: Context, coreConfig: Config) { call.terminate() } - fun transferCallTo(addressToCall: String) { + fun transferCallTo(addressToCall: String): Boolean { val currentCall = core.currentCall ?: core.calls.firstOrNull() if (currentCall == null) { Log.e("[Context] Couldn't find a call to transfer") @@ -515,8 +544,10 @@ class CoreContext(val context: Context, coreConfig: Config) { if (address != null) { Log.i("[Context] Transferring current call to $addressToCall") currentCall.transferTo(address) + return true } } + return false } fun startCall(to: String) { @@ -540,14 +571,19 @@ class CoreContext(val context: Context, coreConfig: Config) { startCall(address) } - fun startCall(address: Address, forceZRTP: Boolean = false, localAddress: Address? = null) { + fun startCall( + address: Address, + callParams: CallParams? = null, + forceZRTP: Boolean = false, + localAddress: Address? = null + ) { if (!core.isNetworkReachable) { Log.e("[Context] Network unreachable, abort outgoing call") callErrorMessageResourceId.value = Event(context.getString(R.string.call_error_network_unreachable)) return } - val params = core.createCallParams(null) + val params = callParams ?: core.createCallParams(null) if (params == null) { val call = core.inviteAddress(address) Log.w("[Context] Starting call $call without params") @@ -607,15 +643,6 @@ class CoreContext(val context: Context, coreConfig: Config) { return core.videoDevicesList.size > 2 // Count StaticImage camera } - fun isVideoCallOrConferenceActive(): Boolean { - val conference = core.conference - return if (conference != null && conference.isIn) { - conference.currentParams.isVideoEnabled - } else { - core.currentCall?.currentParams?.isVideoEnabled ?: false - } - } - fun createCallOverlay() { if (!corePreferences.showCallOverlay || !corePreferences.systemWideCallOverlay || callOverlay != null) { return @@ -787,9 +814,9 @@ class CoreContext(val context: Context, coreConfig: Config) { } Log.i("[Context] Starting IncomingCallActivity") - val intent = Intent(context, IncomingCallActivity::class.java) + val intent = Intent(context, org.linphone.activities.voip.CallActivity::class.java) // This flag is required to start an Activity from a Service context - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) context.startActivity(intent) } @@ -800,9 +827,9 @@ class CoreContext(val context: Context, coreConfig: Config) { } Log.i("[Context] Starting OutgoingCallActivity") - val intent = Intent(context, OutgoingCallActivity::class.java) + val intent = Intent(context, org.linphone.activities.voip.CallActivity::class.java) // This flag is required to start an Activity from a Service context - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) context.startActivity(intent) } @@ -813,7 +840,7 @@ class CoreContext(val context: Context, coreConfig: Config) { } Log.i("[Context] Starting CallActivity") - val intent = Intent(context, CallActivity::class.java) + val intent = Intent(context, org.linphone.activities.voip.CallActivity::class.java) // This flag is required to start an Activity from a Service context intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) context.startActivity(intent) diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index e69d23f9b..641694af1 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -324,12 +324,6 @@ class CorePreferences constructor(private val context: Context) { config.setBool("app", "user_disabled_self_managed_telecom_manager", value) } - var fullScreenCallUI: Boolean - get() = config.getBool("app", "full_screen_call", false) - set(value) { - config.setBool("app", "full_screen_call", value) - } - var routeAudioToBluetoothIfAvailable: Boolean get() = config.getBool("app", "route_audio_to_bluetooth_if_available", true) set(value) { @@ -350,6 +344,12 @@ class CorePreferences constructor(private val context: Context) { config.setBool("audio", "android_pause_calls_when_audio_focus_lost", value) } + var enableFullScreenWhenJoiningVideoConference: Boolean + get() = config.getBool("app", "enter_video_conference_enable_full_screen_mode", true) + set(value) { + config.setBool("app", "enter_video_conference_enable_full_screen_mode", value) + } + /* Assistant */ var firstStart: Boolean @@ -443,11 +443,6 @@ class CorePreferences constructor(private val context: Context) { val disableChat: Boolean get() = config.getBool("app", "disable_chat_feature", false) - // If enabled, this will cause the video to "freeze" on your correspondent screen - // as you won't send video packets anymore - val hideCameraPreviewInPipMode: Boolean - get() = config.getBool("app", "hide_camera_preview_in_pip_mode", false) - // This will prevent UI from showing up, except for the launcher & the foreground service notification val preventInterfaceFromShowingUp: Boolean get() = config.getBool("app", "keep_app_invisible", false) @@ -479,11 +474,21 @@ class CorePreferences constructor(private val context: Context) { val debugPopupCode: String get() = config.getString("app", "debug_popup_magic", "#1234#")!! + // If there is more participants than this value in a conference, force ActiveSpeaker layout + val maxConferenceParticipantsForMosaicLayout: Int = 6 + val conferenceServerUri: String get() = config.getString( "app", "default_conference_factory_uri", - "sip:conference-factory@sip.linphone.org" + "" + )!! + + val audioVideoConferenceServerUri: String + get() = config.getString( + "app", + "default_audio_video_conference_factory_uri", + "" )!! val limeX3dhServerUrl: String @@ -531,6 +536,9 @@ class CorePreferences constructor(private val context: Context) { val showRecordingsInSideMenu: Boolean get() = config.getBool("app", "side_menu_recordings", true) + val showScheduledConferencesInSideMenu: Boolean + get() = config.getBool("app", "side_menu_conferences", true) + val showAboutInSideMenu: Boolean get() = config.getBool("app", "side_menu_about", true) @@ -569,6 +577,9 @@ class CorePreferences constructor(private val context: Context) { val showAdvancedSettings: Boolean get() = config.getBool("app", "settings_advanced", true) + val showConferencesSettings: Boolean + get() = config.getBool("app", "settings_conferences", true) + /* Assets stuff */ val configPath: String diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 4df57605a..8a415fa9d 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -41,11 +41,9 @@ import kotlin.collections.HashMap import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R -import org.linphone.activities.call.CallActivity -import org.linphone.activities.call.IncomingCallActivity -import org.linphone.activities.call.OutgoingCallActivity import org.linphone.activities.chat_bubble.ChatBubbleActivity import org.linphone.activities.main.MainActivity +import org.linphone.activities.voip.CallActivity import org.linphone.compatibility.Compatibility import org.linphone.contact.Contact import org.linphone.core.* @@ -426,7 +424,7 @@ class NotificationsManager(private val context: Context) { if (picture != null) { IconCompat.createWithAdaptiveBitmap(picture) } else { - IconCompat.createWithResource(context, R.drawable.avatar) + IconCompat.createWithResource(context, R.drawable.voip_single_contact_avatar) } if (userIcon != null) builder.setIcon(userIcon) builder.build() @@ -446,7 +444,7 @@ class NotificationsManager(private val context: Context) { currentForegroundServiceNotificationId = 0 } - val incomingCallNotificationIntent = Intent(context, IncomingCallActivity::class.java) + val incomingCallNotificationIntent = Intent(context, org.linphone.activities.voip.CallActivity::class.java) incomingCallNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val pendingIntent = PendingIntent.getActivity( context, @@ -514,18 +512,6 @@ class NotificationsManager(private val context: Context) { fun displayCallNotification(call: Call, useAsForeground: Boolean = false) { val notifiable = getNotifiableForCall(call) - val callActivity: Class<*> = when (call.state) { - Call.State.Paused, Call.State.Pausing, Call.State.PausedByRemote -> { - CallActivity::class.java - } - Call.State.OutgoingRinging, Call.State.OutgoingProgress, Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia -> { - OutgoingCallActivity::class.java - } - else -> { - CallActivity::class.java - } - } - val serviceChannel = context.getString(R.string.notification_channel_service_id) val channelToUse = when (val serviceChannelImportance = Compatibility.getChannelImportance(notificationManager, serviceChannel)) { NotificationManagerCompat.IMPORTANCE_NONE -> { @@ -543,7 +529,7 @@ class NotificationsManager(private val context: Context) { } } - val callNotificationIntent = Intent(context, callActivity) + val callNotificationIntent = Intent(context, CallActivity::class.java) callNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val pendingIntent = PendingIntent.getActivity( context, @@ -651,8 +637,15 @@ class NotificationsManager(private val context: Context) { val pictureUri = contact?.getContactThumbnailPictureUri() val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(message.fromAddress) + var text = "" + + val isConferenceInvite = message.contents.firstOrNull()?.isIcalendar ?: false + text = if (isConferenceInvite) { + AppUtils.getString(R.string.conference_invitation_received_notification) + } else { + message.contents.find { content -> content.isText }?.utf8Text ?: "" + } - var text: String = message.contents.find { content -> content.isText }?.utf8Text ?: "" if (text.isEmpty()) { for (content in message.contents) { text += content.name @@ -781,7 +774,7 @@ class NotificationsManager(private val context: Context) { } style.isGroupConversation = notifiable.isGroup - val icon = lastPerson?.icon ?: IconCompat.createWithResource(context, R.drawable.avatar) + val icon = lastPerson?.icon ?: IconCompat.createWithResource(context, R.drawable.voip_single_contact_avatar) val bubble = NotificationCompat.BubbleMetadata.Builder(bubbleIntent, icon) .setDesiredHeightResId(R.dimen.chat_message_bubble_desired_height) .build() diff --git a/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt b/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt index 2173faff3..a93bfd003 100644 --- a/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt +++ b/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt @@ -44,7 +44,7 @@ class NativeCallWrapper(var callId: String) : Connection() { } override fun onStateChanged(state: Int) { - Log.i("[Connection] Telecom state changed [$state] for call with id: $callId") + Log.i("[Connection] Telecom state changed [${intStateToString(state)}] for call with id: $callId") super.onStateChanged(state) } @@ -70,6 +70,11 @@ class NativeCallWrapper(var callId: String) : Connection() { val call = getCall() if (call != null) { + if (getState() != STATE_ACTIVE) { + Log.w("[Connection] Call state isn't STATE_ACTIVE, ignoring mute mic & audio route directive from TelecomManager") + return + } + call.microphoneMuted = state.isMuted when (state.route) { CallAudioState.ROUTE_EARPIECE -> AudioRouteUtils.routeAudioToEarpiece(call, true) @@ -118,4 +123,18 @@ class NativeCallWrapper(var callId: String) : Connection() { destroy() } } + + private fun intStateToString(state: Int): String { + return when (state) { + STATE_INITIALIZING -> "STATE_INITIALIZING" + STATE_NEW -> "STATE_NEW" + STATE_RINGING -> "STATE_RINGING" + STATE_DIALING -> "STATE_DIALING" + STATE_ACTIVE -> "STATE_ACTIVE" + STATE_HOLDING -> "STATE_HOLDING" + STATE_DISCONNECTED -> "STATE_DISCONNECTED" + STATE_PULLING_CALL -> "STATE_PULLING_CALL" + else -> "STATE_UNKNOWN" + } + } } diff --git a/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt b/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt index d23b9759b..704c6b306 100644 --- a/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt +++ b/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt @@ -46,11 +46,12 @@ class AudioRouteUtils { } val typesNames = stringBuilder.toString() - if (coreContext.core.callsNb == 0) { - Log.e("[Audio Route Helper] No call found, aborting [$typesNames] audio route change") - return + val currentCall = if (coreContext.core.callsNb > 0) { + call ?: coreContext.core.currentCall ?: coreContext.core.calls[0] + } else { + Log.w("[Audio Route Helper] No call found, setting audio route on Core") + null } - val currentCall = call ?: coreContext.core.currentCall ?: coreContext.core.calls[0] val conference = coreContext.core.conference val capability = if (output) AudioDevice.Capabilities.CapabilityPlay @@ -63,10 +64,14 @@ class AudioRouteUtils { Log.i("[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName}], routing conference audio to it") if (output) conference.outputAudioDevice = audioDevice else conference.inputAudioDevice = audioDevice - } else { + } else if (currentCall != null) { Log.i("[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName}], routing call audio to it") if (output) currentCall.outputAudioDevice = audioDevice else currentCall.inputAudioDevice = audioDevice + } else { + Log.i("[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName}], changing core default audio device") + if (output) coreContext.core.outputAudioDevice = audioDevice + else coreContext.core.inputAudioDevice = audioDevice } return } @@ -96,11 +101,10 @@ class AudioRouteUtils { types: List, skipTelecom: Boolean = false ) { - val currentCall = call ?: coreContext.core.currentCall ?: coreContext.core.calls[0] - if ((call != null || currentCall != null) && !skipTelecom && TelecomHelper.exists()) { - val callToUse = call ?: currentCall + val currentCall = call ?: coreContext.core.currentCall ?: if (coreContext.core.callsNb > 0) coreContext.core.calls[0] else null + if (currentCall != null && !skipTelecom && TelecomHelper.exists()) { Log.i("[Audio Route Helper] Call provided & Telecom Helper exists, trying to dispatch audio route change through Telecom API") - val connection = TelecomHelper.get().findConnectionForCallId(callToUse.callLog.callId) + val connection = TelecomHelper.get().findConnectionForCallId(currentCall.callLog.callId) if (connection != null) { val route = when (types.first()) { AudioDevice.Type.Earpiece -> CallAudioState.ROUTE_EARPIECE @@ -114,13 +118,13 @@ class AudioRouteUtils { // but this time with skipTelecom = true if (!Compatibility.changeAudioRouteForTelecomManager(connection, route)) { Log.w("[Audio Route Helper] Connection is already using this route internally, make the change!") - applyAudioRouteChange(callToUse, types) - changeCaptureDeviceToMatchAudioRoute(callToUse, types) + applyAudioRouteChange(currentCall, types) + changeCaptureDeviceToMatchAudioRoute(currentCall, types) } } else { Log.w("[Audio Route Helper] Telecom Helper found but no matching connection!") - applyAudioRouteChange(callToUse, types) - changeCaptureDeviceToMatchAudioRoute(callToUse, types) + applyAudioRouteChange(currentCall, types) + changeCaptureDeviceToMatchAudioRoute(currentCall, types) } } else { applyAudioRouteChange(call, types) @@ -145,15 +149,21 @@ class AudioRouteUtils { } fun isSpeakerAudioRouteCurrentlyUsed(call: Call? = null): Boolean { - if (coreContext.core.callsNb == 0) { - Log.w("[Audio Route Helper] No call found, so speaker audio route isn't used") - return false + val currentCall = if (coreContext.core.callsNb > 0) { + call ?: coreContext.core.currentCall ?: coreContext.core.calls[0] + } else { + Log.w("[Audio Route Helper] No call found, checking audio route on Core") + null } - val currentCall = call ?: coreContext.core.currentCall ?: coreContext.core.calls[0] val conference = coreContext.core.conference - val audioDevice = if (conference != null && conference.isIn) conference.outputAudioDevice else currentCall.outputAudioDevice - Log.i("[Audio Route Helper] Playback audio device currently in use is [${audioDevice?.deviceName}] with type (${audioDevice?.type})") + val audioDevice = if (conference != null && conference.isIn) + conference.outputAudioDevice + else if (currentCall != null) + currentCall.outputAudioDevice + else + coreContext.core.outputAudioDevice + Log.i("[Audio Route Helper] Playback audio currently in use is [${audioDevice?.deviceName}] with type (${audioDevice?.type})") return audioDevice?.type == AudioDevice.Type.Speaker } diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index 62e2cfaf4..bf7778b6b 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -28,6 +28,7 @@ import android.text.Editable import android.text.TextWatcher import android.util.Patterns import android.view.LayoutInflater +import android.view.TextureView import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo @@ -51,6 +52,8 @@ import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.activities.GenericActivity import org.linphone.activities.main.settings.SettingListener +import org.linphone.activities.voip.data.ConferenceParticipantDeviceData +import org.linphone.activities.voip.views.HorizontalScrollDotsView import org.linphone.contact.ContactAvatarView import org.linphone.core.tools.Log import org.linphone.views.VoiceRecordProgressBar @@ -100,8 +103,8 @@ fun View.setLayoutSize(dimension: Float) { this.layoutParams.width = dimension.toInt() } -@BindingAdapter("android:background") -fun LinearLayout.setBackground(resource: Int) { +@BindingAdapter("backgroundImage") +fun LinearLayout.setBackgroundImage(resource: Int) { this.setBackgroundResource(resource) } @@ -326,7 +329,7 @@ fun loadAvatarWithGlideFallback(imageView: ImageView, path: String?) { .into(imageView) } else { Log.w("[Data Binding] [Glide] Can't load $path") - imageView.setImageResource(R.drawable.avatar) + imageView.setImageResource(R.drawable.voip_single_contact_avatar) } } @@ -355,6 +358,7 @@ fun loadAvatarWithGlide(imageView: ImageView, path: Uri?) { @BindingAdapter("glideAvatar") fun loadAvatarWithGlide(imageView: ImageView, path: String?) { if (path != null) { + imageView.visibility = View.VISIBLE GlideApp .with(imageView) .load(path) @@ -573,3 +577,75 @@ fun VoiceRecordProgressBar.setSecProgress(progress: Int) { fun VoiceRecordProgressBar.setSecProgressTint(color: Int) { setSecondaryProgressTint(color) } + +@BindingAdapter("android:layout_margin") +fun ConstraintLayout.setMargins(margins: Float) { + val params = layoutParams as ConstraintLayout.LayoutParams + val m = margins.toInt() + params.setMargins(m, m, m, m) + layoutParams = params +} + +@BindingAdapter("android:onTouch") +fun View.setTouchListener(listener: View.OnTouchListener) { + setOnTouchListener(listener) +} + +@BindingAdapter("entries") +fun Spinner.setEntries(entries: List?) { + if (entries != null) { + val arrayAdapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, entries) + arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + adapter = arrayAdapter + } +} + +@BindingAdapter("selectedValueAttrChanged") +fun Spinner.setInverseBindingListener(listener: InverseBindingListener) { + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { + if (tag != position) { + listener.onChange() + } + } + + override fun onNothingSelected(parent: AdapterView<*>) {} + } +} + +@BindingAdapter("selectedValue") +fun Spinner.setSelectedValue(value: Any?) { + if (adapter != null) { + val position = (adapter as ArrayAdapter).getPosition(value) + setSelection(position, false) + tag = position + } +} + +@InverseBindingAdapter(attribute = "selectedValue", event = "selectedValueAttrChanged") +fun Spinner.getSelectedValue(): Any? { + return selectedItem +} + +@BindingAdapter("participantTextureView") +fun setParticipantTextureView( + textureView: TextureView, + conferenceParticipantData: ConferenceParticipantDeviceData +) { + conferenceParticipantData.setTextureView(textureView) +} + +@BindingAdapter("app:dotCount") +fun HorizontalScrollDotsView.setDots(count: Int) { + setDotCount(count) +} + +@BindingAdapter("app:itemCount") +fun HorizontalScrollDotsView.setItems(count: Int) { + setItemCount(count) +} + +@BindingAdapter("app:selectedDot") +fun HorizontalScrollDotsView.setSelectedIndex(index: Int) { + setSelectedDot(index) +} diff --git a/app/src/main/java/org/linphone/utils/DialogUtils.kt b/app/src/main/java/org/linphone/utils/DialogUtils.kt index 5f27b4b83..8c7fe46cf 100644 --- a/app/src/main/java/org/linphone/utils/DialogUtils.kt +++ b/app/src/main/java/org/linphone/utils/DialogUtils.kt @@ -31,6 +31,7 @@ import androidx.databinding.DataBindingUtil import org.linphone.R import org.linphone.activities.main.viewmodels.DialogViewModel import org.linphone.databinding.DialogBinding +import org.linphone.databinding.VoipDialogBinding class DialogUtils { companion object { @@ -52,5 +53,24 @@ class DialogUtils { dialog.window?.setBackgroundDrawable(d) return dialog } + + fun getVoipDialog(context: Context, viewModel: DialogViewModel): Dialog { + val dialog = Dialog(context, R.style.AppTheme) + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) + + val binding: VoipDialogBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.voip_dialog, null, false) + binding.viewModel = viewModel + dialog.setContentView(binding.root) + + val d: Drawable = ColorDrawable(ContextCompat.getColor(dialog.context, R.color.voip_dark_gray)) + d.alpha = 166 + dialog.window + ?.setLayout( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT + ) + dialog.window?.setBackgroundDrawable(d) + return dialog + } } } diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index 30adf0ba1..665827373 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -316,7 +316,12 @@ class FileUtils { val nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) if (nameIndex != -1) { try { - name = returnCursor.getString(nameIndex) + val displayName = returnCursor.getString(nameIndex) + if (displayName != null) { + name = displayName + } else { + Log.e("[File Utils] Failed to get the display name for URI $uri, returned value is null") + } } catch (e: CursorIndexOutOfBoundsException) { Log.e("[File Utils] Failed to get the display name for URI $uri, exception is $e") } @@ -513,5 +518,20 @@ class FileUtils { } return false } + + fun writeIntoFile(bytes: ByteArray, file: File) { + val inStream = ByteArrayInputStream(bytes) + val outStream = FileOutputStream(file) + + val buffer = ByteArray(1024) + var read: Int + while (inStream.read(buffer).also { read = it } != -1) { + outStream.write(buffer, 0, read) + } + + inStream.close() + outStream.flush() + outStream.close() + } } } diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index 162811c99..6bea3de9e 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -40,7 +40,8 @@ class LinphoneUtils { companion object { private const val RECORDING_DATE_PATTERN = "dd-MM-yyyy-HH-mm-ss" - fun getDisplayName(address: Address): String { + fun getDisplayName(address: Address?): String { + if (address == null) return "[null]" if (address.displayName == null) { val account = coreContext.core.accountList.find { account -> account.params.identityAddress?.asStringUriOnly() == address.asStringUriOnly() @@ -76,6 +77,11 @@ class LinphoneUtils { return core.defaultAccount?.params?.conferenceFactoryUri != null } + fun isRemoteConferencingAvailable(): Boolean { + val core = coreContext.core + return core.defaultAccount?.params?.audioVideoConferenceFactoryAddress != null || core.defaultAccount?.params?.conferenceFactoryUri != null + } + fun createOneToOneChatRoom(participant: Address, isSecured: Boolean = false): ChatRoom? { val core: Core = coreContext.core val defaultAccount = core.defaultAccount diff --git a/app/src/main/java/org/linphone/utils/ShortcutsHelper.kt b/app/src/main/java/org/linphone/utils/ShortcutsHelper.kt index c02c25fed..8a30bb0f6 100644 --- a/app/src/main/java/org/linphone/utils/ShortcutsHelper.kt +++ b/app/src/main/java/org/linphone/utils/ShortcutsHelper.kt @@ -169,7 +169,7 @@ class ShortcutsHelper(val context: Context) { personsList.add(contact.getPerson()) } subject = contact?.fullName ?: LinphoneUtils.getDisplayName(chatRoom.peerAddress) - icon = contact?.getPerson()?.icon ?: IconCompat.createWithResource(context, R.drawable.avatar) + icon = contact?.getPerson()?.icon ?: IconCompat.createWithResource(context, R.drawable.voip_single_contact_avatar) } else if (chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) && chatRoom.participants.isNotEmpty()) { val address = chatRoom.participants.first().address val contact = @@ -178,7 +178,7 @@ class ShortcutsHelper(val context: Context) { personsList.add(contact.getPerson()) } subject = contact?.fullName ?: LinphoneUtils.getDisplayName(address) - icon = contact?.getPerson()?.icon ?: IconCompat.createWithResource(context, R.drawable.avatar) + icon = contact?.getPerson()?.icon ?: IconCompat.createWithResource(context, R.drawable.voip_single_contact_avatar) } else { for (participant in chatRoom.participants) { val contact = @@ -188,7 +188,7 @@ class ShortcutsHelper(val context: Context) { } } subject = chatRoom.subject.orEmpty() - icon = IconCompat.createWithResource(context, R.drawable.chat_group_avatar) + icon = IconCompat.createWithResource(context, R.drawable.voip_multiple_contacts_avatar) } val persons = arrayOfNulls(personsList.size) diff --git a/app/src/main/java/org/linphone/utils/TimestampUtils.kt b/app/src/main/java/org/linphone/utils/TimestampUtils.kt index 20e269448..c4998395c 100644 --- a/app/src/main/java/org/linphone/utils/TimestampUtils.kt +++ b/app/src/main/java/org/linphone/utils/TimestampUtils.kt @@ -20,8 +20,10 @@ package org.linphone.utils import java.text.DateFormat +import java.text.Format import java.text.SimpleDateFormat import java.util.* +import org.linphone.LinphoneApplication class TimestampUtils { companion object { @@ -54,6 +56,56 @@ class TimestampUtils { return isSameDay(cal1.time, cal2.time, false) } + fun dateToString(date: Long, timestampInSecs: Boolean = true): String { + val dateFormat: Format = android.text.format.DateFormat.getDateFormat( + LinphoneApplication.coreContext.context + ) + val pattern = (dateFormat as SimpleDateFormat).toLocalizedPattern() + + val calendar = Calendar.getInstance() + calendar.timeInMillis = if (timestampInSecs) date * 1000 else date + return SimpleDateFormat(pattern, Locale.getDefault()).format(calendar.time) + } + + fun timeToString(hour: Int, minutes: Int): String { + val use24hFormat = android.text.format.DateFormat.is24HourFormat(LinphoneApplication.coreContext.context) + val calendar = Calendar.getInstance() + calendar.set(Calendar.HOUR_OF_DAY, hour) + calendar.set(Calendar.MINUTE, minutes) + + return if (use24hFormat) { + SimpleDateFormat("HH'h'mm", Locale.getDefault()).format(calendar.time) + } else { + SimpleDateFormat("h:mm a", Locale.getDefault()).format(calendar.time) + } + } + + fun timeToString(time: Long, timestampInSecs: Boolean = true): String { + val use24hFormat = android.text.format.DateFormat.is24HourFormat(LinphoneApplication.coreContext.context) + val calendar = Calendar.getInstance() + calendar.timeInMillis = if (timestampInSecs) time * 1000 else time + + return if (use24hFormat) { + SimpleDateFormat("HH'h'mm", Locale.getDefault()).format(calendar.time) + } else { + SimpleDateFormat("h:mm a", Locale.getDefault()).format(calendar.time) + } + } + + fun durationToString(hours: Int, minutes: Int): String { + val calendar = Calendar.getInstance() + calendar.set(Calendar.HOUR_OF_DAY, hours) + calendar.set(Calendar.MINUTE, minutes) + val pattern = when { + hours == 0 -> "mm'min'" + hours < 10 && minutes == 0 -> "H'h'" + hours < 10 && minutes > 0 -> "H'h'mm" + hours >= 10 && minutes == 0 -> "HH'h'" + else -> "HH'h'mm" + } + return SimpleDateFormat(pattern, Locale.getDefault()).format(calendar.time) + } + private fun isSameYear(timestamp: Long, timestampInSecs: Boolean = true): Boolean { val cal = Calendar.getInstance() cal.timeInMillis = if (timestampInSecs) timestamp * 1000 else timestamp @@ -85,7 +137,7 @@ class TimestampUtils { } val millis = if (timestampInSecs) timestamp * 1000 else timestamp - return dateFormat.format(Date(millis)) + return dateFormat.format(Date(millis)).capitalize(Locale.getDefault()) } private fun isSameDay( diff --git a/app/src/main/res/color/security_switch_track_color.xml b/app/src/main/res/color/security_switch_track_color.xml deleted file mode 100644 index 2d884e585..000000000 --- a/app/src/main/res/color/security_switch_track_color.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/voip_extra_button_text_color.xml b/app/src/main/res/color/voip_extra_button_text_color.xml new file mode 100644 index 000000000..4c7f3d593 --- /dev/null +++ b/app/src/main/res/color/voip_extra_button_text_color.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/arrow_accept.png b/app/src/main/res/drawable-xhdpi/arrow_accept.png deleted file mode 100644 index c90817e48399b059734a9e5e95f4ee6623111349..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 446 zcmeAS@N?(olHy`uVBq!ia0vp^7C@ZE!3HE>9+>n9NU=1mg;^ebwM>~bp{pRctdfc}8G2d>{ z?Mi-gED9eT0gGA`Ru--XvA>Joab$ew3Y5`3&I^?~TbTXm$X0oCt~u#PzcQAy?l4a< zJ|(juw4qwxR!>Pw&@jNDx})RqCg#`I=Pz&4owuywAm15=txQi$4W_bIDhDm`n5Y!p zGe=Z2Xvqmbr?^fL5Tjlp-?d9bbJOGrla)Y>pp@h%bASq6w@F*_-zhi#1kxtkYQww= zBz-gMiPDFN1{=m>o0PvRIqz5aZLGXrDfj?W!N=a!Q-5*aQ9B{Gfa}Y(^LC7KTycA6 TzUTW741NYrS3j3^P6U~ diff --git a/app/src/main/res/drawable-xhdpi/arrow_hangup.png b/app/src/main/res/drawable-xhdpi/arrow_hangup.png deleted file mode 100644 index 8c113903c925ed8c89072ec1a4f84a81383b1e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 437 zcmeAS@N?(olHy`uVBq!ia0vp^7C@ZE!3HE>9+>n9NUo(`%iMR1G?>{M*#fi|v6?iR$;UEt zUu-b1S<9^Bc98dT@mvFMCijIC5AYO7%389{QJvs_V3o&2rQdxv{tE9KMJFn`c8O>P zEddG{JKXC8GN05dOb3fSk)J4japGmTf~oF$Ao(RH}W zG0g%f&?&6$H)luPBeT_A)&f8#Oe}`AKsxlOtlRP4E|3a~LdnP18uFORd3v7r%x0An z-Z6Pip=yD(&N>#e!;icYcyF-Yv#Vt_EV#d*G(m6HYuPPoRrgNoep3X7I)kUHpUXO@ GgeCw*SF+~- diff --git a/app/src/main/res/drawable-xhdpi/audio.png b/app/src/main/res/drawable-xhdpi/audio.png deleted file mode 100644 index 472ae2bff761458531466ddb44d1cd47367899e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11876 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 diff --git a/app/src/main/res/drawable-xhdpi/avatar.png b/app/src/main/res/drawable-xhdpi/avatar.png deleted file mode 100644 index 6c91dd6549ac3dc18eccdf8e0bbabe2136ec59b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26655 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 MI%fIcZ?DAv0sUv-dH?_b diff --git a/app/src/main/res/drawable-xhdpi/camera_default.png b/app/src/main/res/drawable-xhdpi/camera_default.png deleted file mode 100644 index 34a6c1c82ae295d7abab7c4c7b1edde98901ae5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1218 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 diff --git a/app/src/main/res/drawable-xhdpi/chat_group_avatar.png b/app/src/main/res/drawable-xhdpi/chat_group_avatar.png deleted file mode 100644 index db9230dbb761e64a13c78664bc1c77cf0d741ba2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5357 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 UyANL&P<=~8LXdC0&*}631LR{WmjD0& diff --git a/app/src/main/res/drawable-xhdpi/checkbox_unchecked.png b/app/src/main/res/drawable-xhdpi/checkbox_unchecked.png deleted file mode 100644 index f0ec61800f5d7cc8a0d6c59d7f39d70b73c3ada9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 992 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 diff --git a/app/src/main/res/drawable-xhdpi/conference_schedule_calendar_default.png b/app/src/main/res/drawable-xhdpi/conference_schedule_calendar_default.png new file mode 100644 index 0000000000000000000000000000000000000000..59fe950fb19eb989b09c5e74e85847332a92a418 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8t*47)h{y5dgav{H2`kv0*t@PW zi2h+eRMmAg;IKkrTgieabsHA_O*sCziNo}zLu|%@mWQ$tvqFCGtq^9FQ9LanVAGU! zfq%t*&t?T%`vBgi8Gc#^7G>X%c9D}2V(WGLzrt}bTd$PK2e!bqaf_pTy|?a?zb2)~ fo0Qobz{6m_X!0?!iA=0OhcI}$`njxgN@xNAQlUMB literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/conference_schedule_participants_default.png b/app/src/main/res/drawable-xhdpi/conference_schedule_participants_default.png new file mode 100644 index 0000000000000000000000000000000000000000..a58af55e1403888e283eb14c024bfa103f2508ce GIT binary patch literal 660 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6Xz}5igjtE6@fg!ItFh?gFHN;HUHMdLYGF z;1O92q!YmSL$=d-1_s7yo-U3d5r^MiJD(-uD8Tk1KFNo#Nnq~}c1M&_xp`y8wIy5+R+jIfI%s+_TJTBy+dHNa5eMq zyFyL#QaPN95y%^lx}#vD$a z)3-PON5l3C`|OX)H?+C`Ncs{`&v$=o$6Tp%j~Ms$DJI>xb@=atRYzA$yL-s_W$}c^ z0aDMdSEz68y0U9^#8Kbl`MH5xt#9N7&6IwTnqS#+d`It7!$;>^-cRn3{qUt=Z|4GW z$&+GXvnH^FE!JY*_uSWmPwB_x``-6ixPwmrIvCseQ;=l|_xl2_rn(=d+1!WcylUGu zKlIgO5tf6hbJm=;?OPDJ(CGUT`wZQvEkE9W5MH1-`74L^ni61Csg}4#l%ynFVdQ&MBb@0JL8ZNdN!< literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/conference_schedule_time_default.png b/app/src/main/res/drawable-xhdpi/conference_schedule_time_default.png new file mode 100644 index 0000000000000000000000000000000000000000..7a953461708206779772642d22c110e5c325840d GIT binary patch literal 442 zcmV;r0Y(0aP)h#V$p#kW?YTM-a>41Go<$_Cc|5NkGLa z7HK5Eg_Fo$X5EFY^h|g5%r|qcv+xh4K#d+Ak9;uW0gnr+tYw1~IpYn*tQSsLPm4Lk zkA*DPGtD#(ov$|d3~5lt#idMx;p%_|-DD>VTpQCVEu6@AhZa*~ag~Hx-e!brwQ(64 ztFF!DfCWPIDg5Go-#FNe&y;U&6U6M&;V6O{nXnh5pi_jHPzvcg2rpt?VuBDS(d)h< zL_4x29t3SuSD`S}Ov0ViAjgEF8dUcXVi2>z27&=X^nywcA?lexb%eMLD#r*>$poq( z#5ky&Ar#42GN^?IYXy+y)K-kZZLEn;uluxunZt$mEdPo#t~lG|X*an$BG{BwJb ka(p0jeX?`>iTD%p1#$}@xR9YyEdT%j07*qoM6N<$f_fpoJ^%m! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/dialer_alt_back.png b/app/src/main/res/drawable-xhdpi/dialer_alt_back.png deleted file mode 100644 index 9c1f84b91ac63e4f4db66011b3ab2c325991f574..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1007 zcmVZT>3|Id_Ef+glu`*c z82BUry9hiar4pTtbO3e?=#x?j_5kpG0QL=H0ZN+sB!O8fmQFkMO|SQqd?rJI~Br4p`1_-0PY&FSV}S68x^>PQu5(CfVTsu+k#a@m8!=>cIjmw3xm;HInT5;ee=Hl4PUf?b% z`EZkRf$Zf|V6c>Yxaqk__Ob)$l9CTMI~U4cwga6~^5N#@V%f`9%@f3M&*XyH%N9+p z#Bd8LGg2ER6vHjc)5Bg~l~4?~yfP^JiiBdg6%{yrKT?6~uXL&}NGOI|Q~7Jc5()We z33$EIsVVq9%i#0SWhMXs002ovPDHLkV1o5Z#Zv$P diff --git a/app/src/main/res/drawable-xhdpi/download_default.png b/app/src/main/res/drawable-xhdpi/download_default.png deleted file mode 100644 index 7b0a1095d4794ba59c652e1dde5902ad73c13bf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1512 zcmVP)EX>4Tx04R}tkv&MmP!xqvQ>7x64t5Z6$WWauh>AFBD-@wZs1;guFnQ@8G-*gu zTpR`0f`dPcRRN_;mtPe_uLvNF2!aq~W*PI6l!kA8-BUN!U7Tn6_x)J|YQbVaKqQ`HhG`RT5YKGd z2Iqa^C@ae<@j3ChNf#u3Zz9)ZSxS+2rbDh=*QdqzBx-kgEbl zjs?`9L3aJ%fAD*@R&i?5OA04|-WSLD7zIMRK&#<6-^Y&AIst;uz?I(d*P6iWC+Urj z7CQnW+rY(jM^pBI%N=0o$&gLim4dW{Vi9;hqi@OsgSSBcnm4!hIZhvdEX^u?0~{Oz zVd+z`M1La9XK~!ko?U-F?990y@|7T`m z2rY-5Rr~s(^^x{BR!BF$o~XHYaN^H zrfK$)z2h?8b=`eLw8t=vuYw?0-mU|Bp0{60d4pwHU--WNTWXKg>ve-Mc7}*90XV0W zT40Plx(xt>jIocDQWuCQkWwyqo_Bw0kh!_JhXFj)rWyoriZS+dUJ#DsTq~vg1i*uB z>S3kSnM$QHlr~4~jUH|QCu_CZGfDZL=4G!`DnpiKoh71sHdrq8_xF#c1zCuzLbTR1 zjIn34^2Ef%#EyZ1fwM7}2LP(CukUgy5Fx}{0G8rl08H0vwWC=;v|6om04fP))4uQj zlnNw@qTiKL2LN172$eS&V=tzKV2lm7S}h;I-3eyz*|z;cNAF0p*<4ae9ZZ4&@G4{M zr8E!#eEc&qij&i}Z9i78*Hy<4Vzb#?0^lU%3;?|1y6($cO?Jm|N<QIYz^w_JAJ0Ouzxw(XfzI|fyk^M zX*3!?YON2fRmf>%CJe(PX(2Lgtimw-g^2D2aPca`X_|BXT*qOVbqVge?rtJ#0@$|& z^C`~x^O@vVPEFA+rPMrt>k`af=bRtw>YhCaf@RY*JpeMn6UVaLGq>S`@B3d`mc;=4 zv98%VkySY59$M`(IXP*rtgK8ZrEb((&xK()pT|DAgJdaVV`F`#QfUVf4X>M%ty+w+ zxDm@Oob#1#0vQ<@xle2T@p`?K^1KCLXH1J*C~x&ZS1 O0000$3ql+0v>>@5A=yV;5|VR0YyL#6Cfd3 zmYwOIjAlcC5Pb=^TmYX3;LUJ( z3cv;;S|O!;*s`pD)WD|6tXZ>;wr%^D0M06+PAjC8SHY2*tXqLY16EtU=72#1He^^tQ`OrnWkwL`fOd-XAsd7 z01g|}NTMaNSZv{l1RDqhPS7-My}}aNrfJ$4ot>Q6i|Dml=Dgf6#(M#_&#LBlXE1#o6s*4F?$S%!`? zo`}wKRL&!oWqnw79qae|$9O!R4*~3#2K($-EVj6e^A15NrIl`Z)YsP^(%#fdA~+v14gD%Z)~(D*Nr$qJpjwnDo7MFrSn zO97RL+@QWwUtj-N*}+OF4^bpdhh>P8jRig{kcRYIZA z7p0U@08J{WwrvLziNrsvIteozp)zwpP-|c_LyfsWeciV0(=$P>fmIJwr#Mg>71^)Z zwjE3)693NjoSKeJgGxkEH>k`UOe7N9bMiQAICF<;S=RPkpH~A5AQ%juT1u!juntrq zM5hWW5xrUn)KRZv9jH+N2dbb-DfL32j_Mv84u_{@f=WceLZOaYu;Flcnv}9r1r@+6 zL==ohqwg0}#%NRphe|2iU7)@trJQA&=KIAKG8)0Ut@C2 z3+}OjK;T$S)6Qh(w=BzAIYOYmNkp?$P=mqXOd-UHnx?(rECeZij|~Qct(vB70B{cx z#SO!_X#_xhL({ZCEEfA$dWK;b-xETt25?U*m0A}Hh0ZMtSRur90O|%LUF-Mz#|~v` zM08{ps6o}LZ)c6yjRTT2X8uXJbZq)x$-26_x`T(Z?+@THm+aSV+YY!brH{p84GOE_ zh*3XyW%2z#$aU(kW^V1P8~p%Q0BCTzNPHgUR6ZY zpw_^;L7hQFOI@IDuYoNRZk_N7j4@P6H!kM zY@Se=`Dr(($z(E;NF;iyS;t`q)-Vhs6I5mnR~zcE18W$D0pMwuf$9Zj4x6UAvt|_( z2Gys63Lr96pbi_bx~_*ZKz)9wK^+!g4a1mCM9VWkoi)^;4tcP;uFnDR1k~H*pZ9vb zk)a56NQ2dNeGU;Vae=zV>-9!DIy!a@)%xcHHl;9cv@$^r5Ygivz4Twn)nAxfoE`=;c$2^5k2c-QvF2;;V3OMC>M%t+dddQpnKvoYvhGV2LcFiIZ(^<%-psKA`-qfM6?1S}j>dnnZt8SJS}rjVkIkk1CDF0W{07mWxDhjR zxK*{&+gP}8VO>v8kDr;BmdW@hW^Pq|0Xf_|Q`P>m^w?A?wH82njDhBGINY4+zPh@) z=P)?<&G_!_?%SAonG4i4#Tx&iP$-B20UGuWT*+i|WfoI!JU)#Cx(`>|wx25tSSKLZ z3yL=x$#h>wM@Jt3RWyw&0E}^gI;&Vam6@+oBzNYZt3&Bri0HL^f0>IsD;rm&U68|l z_vEO@pDP8_P$+aE6w&C2=>8n)aK0khDyvKDxR`_VQ7{;snCU*tvQ`0jCd=bj@7c4b zXsD4$cVu|%B%jYWt;qR>8A6D)s_=UN7RO?-j9-l3?{6fcD-}KcVE|lqu*qcd zeuWn{CY4Iv;5JJI_o=_VG7dn8VHmgj{r-dUsgGe82O5TPvlFL9x}MMTqMvDixkp=Ox+bCZh4k9Ah6Z%Ch`VCG+2mUTtN zz&gQoH(~HXiI=*&yJxOjw=NkDhvzyTvt*Uaxl!fcpVV25@{96>);& z37rO@1;B|=hY=YFb$MM~T{s$zzLSFrxRHo1QRvniW3gC!p*p|dKM2WW@*ahS00374 z_=C$!O9&wrb#!#}MWfN(EiEmJ09*^8ROFgS{a zWM-q-T|q@h<1`H8Y5=!pgX;77<}6>nd{34abtr&~g%Asv`3uP50eV3^wR~*?Sr3{&-xwVw#7Sv-yq0mC-;2n>`{);j57yzedctjU~n@!W~ zEEmvFD0C(>Uk|{@aM?O$o?T49hmwIc4C8D7e}ekAPIUL~-Sg_}>siyZJBjGBERXve zfZqc6b19Yz`ThQ}9*<`}5q&emV7gZxwQc(w#G|eX}RiMb>Q#;qKrxH;|E>Hm=9*=jlw6siN=7j(%b;j14 zM0AmD+s9QBYStd>=*S0M%(7>^UhjP8FKpiB>bicK5aN7hJ{#(&mxanMW`0r#@o-B^ z%gSuOV=4|-*Y)#>=#MT?+r3`zf&!uLBdx8i_5J<*GoAU+0D!4^PQ>&P(RyZ%Gjn{; zo;_V_)~xBRwwFgPpZ^8S{D2G86<)9RtWrRAtCL|EM-tH_X8tUIyD@mO{BmaA==FNv znm&E{JJsryd4bh+{c<9@%LV$QO-)S~E?&I2Qc>Yy7{+d-Etd27d=2?d>Qq`p9#9{t zG}PfNV!-GB?JQ6)szJ>Ytgh=9GxM)ppe}1_YPzHbHBYe4;R*M-*tAQVnwrjDym)bc z4OM>P0WeEt{4?`YJv}{(Ow&x(Fcqc>-gbjpfit&8F4%G7#w~GdR{oSJKTv~O0#)D) zN|!_;kskvmX*w1)pk;OtYElDR16u=IlN#9JAXLr5ofUt#0GI^#U6Rd;dTxPw zUqA}L7AfTor9P~E*npieV}`Gxpt6XU;|dP}@xf>w00000NkvXXu0mjfS;?Lj diff --git a/app/src/main/res/drawable-xhdpi/more_menu_default.png b/app/src/main/res/drawable-xhdpi/more_menu_default.png deleted file mode 100644 index 51162a31ab58fe2da536bf8e442e1442af060e6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2738 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 diff --git a/app/src/main/res/drawable-xhdpi/next_default.png b/app/src/main/res/drawable-xhdpi/next_default.png new file mode 100644 index 0000000000000000000000000000000000000000..3cea216fb5ec63c04a2e09c4214a3182cc002518 GIT binary patch literal 8512 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 Hu0mjftK_$8 diff --git a/app/src/main/res/drawable-xhdpi/options_rec_default.png b/app/src/main/res/drawable-xhdpi/options_rec_default.png deleted file mode 100644 index 075c0931484e9a43392d013dc61c13b0f78f3cb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3329 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 diff --git a/app/src/main/res/drawable-xhdpi/options_transfer_call_default.png b/app/src/main/res/drawable-xhdpi/options_transfer_call_default.png deleted file mode 100644 index 6b4bc16651d41c0a508a96a5fa92cf6b627a8a37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4665 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 diff --git a/app/src/main/res/drawable-xhdpi/pause_default.png b/app/src/main/res/drawable-xhdpi/pause_default.png deleted file mode 100644 index 78841141d0589e040656655ce0a37d43e96b20e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6705 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 NXSsR1mO6)Q{9kfM4LJY+ diff --git a/app/src/main/res/drawable-xhdpi/reply_message_default.png b/app/src/main/res/drawable-xhdpi/reply_message_default.png deleted file mode 100644 index f0d2ff97f83f9aa49ed4156e179c632c013622ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10985 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 YFHr*8V*S*fT)nw!sN7M0t!N(fKMlSqu>b%7 diff --git a/app/src/main/res/drawable-xhdpi/route_bluetooth_default.png b/app/src/main/res/drawable-xhdpi/route_bluetooth_default.png deleted file mode 100644 index 46793a4837ad0104db4444ca4d32a09a5d7ec9bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2402 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 Uq8?^83jhEB07*qoM6N<$f)Gh>ZvX%Q diff --git a/app/src/main/res/drawable-xhdpi/route_earpiece_default.png b/app/src/main/res/drawable-xhdpi/route_earpiece_default.png deleted file mode 100644 index 9a4950867dbbe746e4f4fa917e3728997f5135d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3027 zcmV;^3oP`BP)#z#W zvIr7gc6ydX6eK)EHoI!?cK5CWEU-N9KPl<$qYu7^11&j1*ey7Y1)k@$*fnu&4;#g*#~ zp-^bVgb5R#0`PMH6=$d}C8DWSRaMIm9z5v$-+-bh%J`EfPp%}Qn}lvhG}-I*PF%Qf z;Y!mq`B@K&rfH3mBpF0BWtgVZ`uh4VvMg)WSq+M=>-Pe90l@iL+MOZG@;J+~UODSP zX=!Px^!xow06dVPZ34hj08<4)dzLKARhDI~EjLi4A%@FT$8oj-_^L?HIq!8G=Ss%d zZA5ez08w!Lj;gA^DKEz5dCmgNM1W`Vk1mgR(HS>5FV3f0!u znncu)q1|rIxgLo`%;5}LmeuX|`z0c}Mxbt%W%;;eS({286iw6S0QgacHZOZTp6kNl z@V;z~+O}=VvV0DJMp4L;W%-C@S+AEgD1kuW8vw2mq}zuWWB2XZv*%>4hAqpA1OkCk z0O|$mR=?l>fo561Bxx5JWIb-Zz0KX9d!#RIaRn;*i2a4l3m!zj(BHCw~=Ds35gEuxdE(WkmpsR4X zT(u;1B5s50;IOlwR zS6A0=lz~V@G$0uFzFZBpwzk$0(Gm#QJR*895{Y!9OvKGOe_!ws7v)%sj7}z#D@7j0 zIe&&R_87`YBqFi|A5xd2opu0|1$JJos;XLyG7~A0Nc0Gin0PF zQ%?`U*A5cXG>_smi!bt+(L~g#Y1;I11LZYAkkoTA#27o`a=BUoY!j$`01Q=CFE1NV zh=^e@?KneGl#<& zB$@#15U4M3xm>2EX;aH#_&|~*(dykz`4}}#^AHhT3t*2xT@7HJuIrOZ9u&uM{w#VHgJhsGRd&fqDYxJfdluT+*OKB9SeE4;Vp23-dM37~AV|xtam=3Dg$@h&DGj zPb^tb0B}i?mI~V3Q^*6t;jm3aDu4q5^+ZXM)~TvGt{5MqC`z@aX}{MrZHuO9Pc$?% zoL49)NbPn^=T|0cnx+-lWVhS>27s>tmDQ|f~RLG^6`9gttn}~D({}up0jfleY=FQ8A%h7fHb^x8J0#PK5j^nIqYik>s zHz<)vWR0l!avbNU#TfU7VQd4S0~ipfXT;<2b(*G)&cYOFn)Wc~d{{FQY$nlTo zaSH&P6aW)Eb?VfznKNg4at6gT%>?KCA%SZ8z`#J!)@&GKuLDpr45dcLaf~xPrm3mv z3vRdjO(JT}VDdSi&!;fPUIcI*0MU}{e4o#^aww0XVaDuV0i<&w9|D-l7~5McaCBY2 zhI9T308ubx$BrGFKXT;ADMYjinRjeH(%9Je(3xvWs;b^VM9U!F)cBIm=UdRx(UHu` zQ$F!FzF1JVi~_Kvy}ey37AS^ctS6%R08WT9Ze?X<7ZI5m07-Dp7cs^jOkZS+L?SDQ zXaP>|%1W0n4-5=EEBcNhtTkDK(%ah` ztE;Qq&N;t5&7ed?b8BmBm)W*`q=>-0C(H5<05@lPKhF8~WHQ+tjYj{TtzFx;w+8}& z0|43}K1VRdvaEGkfdT+MJw3MH?;k}(>5Jx-MASNI(xjz5Jv}Fi2+VF-mUpK3oOG98 z7mLN_c6WCl%ERtke!u?%BAP1zW_BPDIM1@IaMm?PRaI4&*XvaPq)+w7a?TrTYHF7C z_V&h$2+X^(Ebj(z{l}e0L{BFYiQBuoyYt@tWZU-UKp=29RU-|mk7QXslGEu5MN!7P z-R?I5Ob``K6%`fp)~s1GP)u$k#n3f4j`L2c;46w~n)UzyQRV&~9I4)36AfolEs(!sxL5am;j{}$@@bo`0#$GJw#8yj7%hitKtimT} zlhb9X$KzSFcJ0~^O9c>3({9D-^N{JXQ<9|F;c)m^!N=HiU7y7{Uj;z8AWua504z2P zV`T}%kz&~ygt?$!17I~yAF#|M%N@sgFdB{ad=`KxigJ$I z?S2iw7X|9O7-P?7UD0M)QyjVQ!V91Gcs#X305h407Rj<)RaaNHwWp`&*ry3fU0vN{ zscLo*-M|=oI9uuy6hxZu++ zW?e4VoX*b9?L#&C%>F?T3WY}W_xImOM0WzvP{4VBY=v{aC=!W0JzTqz_%TB;7#tgq z$3v-!=2Z}!5h_rUMD(a(7~jvPamoFyqQ1V~d+xdCUd1`T8bCuTb5!PG>Ii@hM6^1Y zOs>kM9xC}?cqoeEcDvm*ob!MrNdd=k#uAY)9Vj9?%sD^EIp4=Q-oIW&+qUBXQxvg+?};{WUY+(Tx3TdWiXa{)#XRp9XMKv-VMIa)8%{ zVYojA;1K{vQ;5;=-g9Noz(yZ zfIk3ekB~ewK0dyFWMt%Q8sew}=zExXGk}bungMK*Qr=hlA1TC1wrtt5wpp`gJ<7~q ziIDsoz->~>ra`vaD}}VYnB-{0PaGAP5Ss>z-<*jgf{-XVWxwUDvn9Y#_V^;7%#!qi8|X zC$Ce?Jb$_$z|6$V8v(42ki4mBTAyv(LugUc0gw>lw?PnWK;w@KUeq*gjcwbnp@mJi zENcxj-`3~{6VcPvYIQ@UQWI^13`FT{+kTdaHYutW?ccwD zZDJwja=DuTJOJQw03QWV6hhn`rJplr&Sn75D?+q;5(|-t7AcA!%;)nfRr*q?6x7zo zDWkDaD10ud)A^txywbxkd_)z#)YiuefWOq22SKndkr1Vn!vHobs;=`qZ+%SD#}#FT z#5$H0D^?5;k)x>kR?J4qpA;kbv!-dzNhHK#u^0mAS5z&@=kr&n+PJR!27tfThdY|4 zU6kF->MF$|jON5>?5q zU7Wc!SVmpdn5JpJh-s?2Gqfj>X&(`-#oJq8_4bf$+oP&BD_5?(3P7Kts+82+`Cp2% zvhVx%M>WtiZ4-bQb;YCEY<4h_5DmlFfp>0ey#Syu>dn;J`nf?-^{Y~;^j0Dv4h{}Z zc%Ii0hT#{Px!W*|rBX`O=tQAVm`6lUV(QJ1Q@-zSJ@28D=$#KAK3oMb7^g8!b9N8} zyD;_G6aaowsZ>s%pYBLaX}YdI1mN?Es=e84cH29&5x;Sl5aMgo*e4&rtIXW*x^Al+ z-ghg?PG&Nhb+vPscZHa({Y|GWAflTs%lfM0IDc+-g3SCX5lx=TV?@+lE|*VC@!muf zHn49WV&d9SxBiLfP5}Qn=STqbIF55j83JMI?(W4l4Zu$=%X-Q*&DqTgQA+tz zHk(}l;Oe%vwvS6GWh8LaE_v2sp_aSknakz&64A|d!EuLe+xr?eYA44;K@hZEgtbK{ zq6cu1t{XQ4#Pg0twO~%8^;nkG&&)qlgjJV?VJL+VP5Jur%7;|0s6~i!=GV`+$k{f*A zKUhorQ;0E}0B73U+j{_PiI7}IM7CvF-%BCJSgBMBq?Gp((U$@IN9FXG`Ikb7-}LtO z&PXA~nBzFx0jvP<&j`sKp63mjrg>!wF+MwZK9k8T1#lojvRv2oL%CdTaSAcU%H{G2 z!!S$$k3~qXBBFgli1jJNc!x6~rMw-$w*gcYk`Dm*y=7SsN8M^oJj6-Udz+?d0>EfQ zDtVXZdC%2uF{BWq%(m@)nx=ICcqKwon3$N@M?`Z|i18r*=KKCa0Dp{-dqLcFjH=TI1iHvn)`h!-{@ zPZ@^MMMS@gkc>96KP~&6?Q=HyW979Na;NLM>Bq|d50IDiv-$r6z~I-}{#Vbz00000 LNkvXXu0mjfa~rp5 diff --git a/app/src/main/res/drawable-xhdpi/routes_default.png b/app/src/main/res/drawable-xhdpi/routes_default.png deleted file mode 100644 index cd78857dfa8b5b63ab1196de6bd1d97cadc091c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3472 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 MDrzZ|$^GW{UuBR}v;Y7A diff --git a/app/src/main/res/drawable-xhdpi/settings_conferences_default.png b/app/src/main/res/drawable-xhdpi/settings_conferences_default.png new file mode 100644 index 0000000000000000000000000000000000000000..97a08dba05ef59bebd6d3519e169d3f8b587b068 GIT binary patch literal 12641 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/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 V)K!=@>ZJgnEUzwCDPtD&e*g)d_?iF! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_audio_routes.png b/app/src/main/res/drawable-xhdpi/voip_audio_routes.png new file mode 100644 index 0000000000000000000000000000000000000000..fae4e84e3472a1f503c6af38e8d2f708eace4118 GIT binary patch literal 3729 zcmbuC_dna;7subRYJBX~(n^}5_FlC`XzhroQmUnBP&-A9nysyh+FN2aDE%NtqbLm> zW~jYKORdzX=-20O_@2i-_jMlUr~A0)@p#?mjYpW@WMvj$1^|H7=oTD#t{ML&Bg6TQ z(F8^T02GVX*GCxX>qC6}yj{_rE&w2w7M7-Ut6TSSucK8jyWJHju|0*wYNGtu@TRPkAfV57FSox*P$Y3b>dBNyYnsJU)x&S zvmn^Jex;hnm%jx}nf8=ANpwg&X)k4=u6M~rDKsYCFiPn551r}mBeyYr-P1l?W30ZE z2+~dqdVL9J)~=`7ffmd^jMtZvt?60oaTaxWd6f1eVZb}zAHx^q+HWel8n55ugpbYS za}Bn-RtROQ$u&QQkz6wO`P|;zaZL`{<7ZGfAt{j2Y*(KfNa;l}(!1~=Vv-r(sEhM) zHGwHR?Bi6fRvtfIVt+#fmr_4Q=LmedJx($=Z!wA#`*yad2EL}-CxfU_`*PT@G-?j~ zRrJBCQn3a!?SWmtnoicA@+OTHb!`!L6 z&fp4m-B*N!IpD$z;-i$eWUbkm$?sF|-2|vqz-HPnU+wm8@%daBoJ?-Q&-FZx_1m7E z3zN?+TmSPBlK)E(Oe`C&#ecIC)z4@n8eYgg}}q)=7N|0ovd6~qhY$vH(X&$3b`9{!rS#W4D? zyX@L`bu0Qs1PfBxnae3Q%C6#F>aHcHV_6K+kn3t-;~`mxi{n)rC;G{u>;9j?pQf&d ztJ}u~$3JhZ6^4Yb(L_NQ|G!nv@JeF6NW?h#i8>`1C|*++w2{Oh+zn#Xr4v3_rz?#M z07*ZBJ&N!eHWkLz!z*KJZ8zds0a4l`D|47+XcOVIy9?!(`)J9mKWVzGVcaju6!5Wh z(+mu+?o>=mucViwB{Bl4f%MbkuM$X!29hq%9M8WtXs(?SVvAyaFS%LXr5CFTDYf;=5vhLjs?-% zn4WfxZUZV!aY2|v-yJ{he&N?jb)9 z1%d)@zS5t4AT1})C;sfqSKrZ5sULAaQYuhzHC2$(i`($`vM}sRXg4WvhA6{;-_eYy z*Q(Uu{!v3ZQ@*%pTQ{0THR^Tnvch3Wb|uCio|G6qqI8^`QPqCdzYwC?15LMyO0|K>C;El7ZUi3N8b7}@UwoMdvx@!gXd9$We zBX>9mS^we^Qzx-i@>8J_T8@3m_iP-P7O(WWmP4=<4@2g8!qh^+wQt!N80RhqErjeK z?dE&@DEsKKaq^+1@Gj%LtPnC8Iv?xQePWw{ySWXSr-vr(9UW0x@cb@i2=wKe-4o53 zoWf0z@0^$0257t>6xzOQNvqE3EyFaG$k5_qw@zs1gw!=SH{vu@@ir#x|74=0%YZWt zge*e`%T9;9WM~nR^a-v@F@BfIq}~Yf++M*&Q2a1_r# zy(tu>{?VGVOb8wrHMB9LV-|RYx`ON`{rGj+Btsw!o~jz<%VORX54iTkH_>A_o!@{L zc}s?ekOoQ8rkd%OUTA4|eiZp>{91^Y*~Vd6tyKeLKR0D4r%qI=k#X`oFsX&22zC;Z z)3xaB0N}9@*m08(c?F;tuaqv_-?EO7{WUeUc*2v)wgXO#~{2~;Jb+P4WxOvDf& zaH{^?G;u*KxG}}qY608=I()8dxy1UuR}6;DIj_PtnU|w?$I%55yL=dWT)97eB;(5A z>&=C?7SXL=Pz?8upZ_HY>gvsOyA+)$t8hx_YnG0d5Es%Ef$-lMXOZ=0I(~+FgDQWQ z)>`0|{~+(SwJ2Joe!Z~%sjgw;Q+u(Z8)frC%8`~Rr!hy#+t}gZKEJ8_c|~e%q>c)g z;{+eMnMXp|K88!P)v>CPyX?E*A0yqZFpR3?`nuWGZuOfnT;`<@gDJtg<{gw8<@Jg| zIPh6ypyn%g2_2wo*&4>QCj2DAXXX6-J|gEOpEc^93@qN1TgmV5Kl2Ky?FkmZL1%)- z@ctQ-78sa^N^=Hbh3zrPjLN9CR^|`^PSt3!y0(%V@rTONY3x3Ak7HVBm(4-zTXh1J zOV5^DO*M=M`#(cR5$T`mz1HYLM7cN?l4FG0wQbMtNg*37$QsvaC&ofD%!?OfM#f*& zYgxnys4L8D(XbYG=B?8{!WAo0f4t2(f9HJ8E!%gmuqeQ^AEKz^;A!|e4q(oC<(n_6_2hnep`5%4*zvzDKI>pvaB0O10Pi2 zi)fJeBy!)jziBfQnah|dB8EM9iT<0?Wki7B@r@r!M&^v;)Lir417edWyQS2Wv!5{R zmwx!^rGcjl+m0iPAk8^Ylwh*$0&7k}l|~w57RayvotMfQI3Y3&!*KfRj)(V2l@WUC zf433qLISJkrnNr|HD^g+LJ0o*3X`w`(EMFm3UntZLWn&l4Pz{>wY4PDt-cl~1w&0cTgSn|8PwJRIeg(oKrYdBmw_DIy*^0a7XT17S| zy8(P7GU|@Yv5tauXKe=s$uB=1-30CqkpYCuzGvD#%E= zaJpw3q4qSd$VHtiDTG8fzYib~9(jKc^le}CxJN(5es_K?-@L}wL3TK^ydP+C_!KJY zNP-Jl=tDwr^0+?+xU1Jt7F+&<3e{cNtn-MszSid?U`q&CVJyn9)hX?BJjh@=qLbO9 z3XgDbSG(D}Wan1Wh zM>%@PK$ZV=Rp-9VR5zSE?9OTG4L#K(ig>yHlit;9##gGFNzY*OFXnx-go@?E##N5> zM6;MT%RYvLzad}kq|0sz*$*pBJ;2APUD4iaGlh;e$b4h=9_ISa1B6f|(s3f8vgwQ% zns;L>&&DotcRl)KAHC7~mZDF_qN~DRmN8~BS9vx{%aU_IKA3X?otD(N_~(|1>0NH+ zS+EpF3`T*8kBw2|SUUOF{~XX}HBqZhw&{GuBltm0v_YK9t7Pjp6B^l@sqm6zp4b{u zbglAyO~;4B4T3+#9m{6C6^E$k<-!_j6r5-R-~0=bq3Qo8#`};?+1ZLY<)W?Gl>Yn3 N7#Wzu@v!?*{{xAb~`{up%Q>r44j=}|3?B?j_qOGRG zZn1a8^4wdTFZl7^^|RNeM=dGJn}YYW_L}qsFGnx8Z2o(Zj_Zuh6ITCR7Ph2VgYtB3 zPPY4mN-V`o5=j4R)1%dPgeBG4SJtlHJmy4b`M76qs2aWaAdx9%VWdowDTHwT&tA>K ztjJSud+}26Pl;x?fQ%1)HL+kTL`~gw*n}Cyg`RE+5&XBZG>bX!;TT; z7zS|o8{1(R9i05_Oa?MSDIcgpL%S=pEITNzB_ekw&r3w zo;sK1Q1p#W@0$LoI?|+q>dRbg%&%}{d5@ZAKzaarVJez#HxlV^ndGojPHWJx{_eBKMmRkB+rTMNBO7&rDD_J^IFwH3$lCe~R zQWT}5lu(=;P({8cCkQ4*zRBN|9o@bsiMWE}0e&!UsXHBm@DELkyDAI|$mN0)XaaJK z5(_OLM)6=v{c>$63%U$-?fVV^3`Ke=L7!~~)a5%D5PzbWMhxQztRcPPR<{p8!O1>z zBDUeSu??}@kHn|5<{Rk_S|h2!b+p*>*Y6elqs1og>wI-xPqs_l&vgY@7`_7&lp6lh z;Rz0E8{|d@?U?a!?ivDinB2&hOQRcvoR;tVn7bz&9BM72A8t76&_>tAs+~N2zkgA< zMI5hfV}U2N@b78?Npjy%RtfCLe80fDvd7vpEz|rRyP=7)-MlR5c<^v-IDnGGK4Hpa zuIdqR=#Vt&<8`349?5EabyC}&=9Hk^bL3i8IbAl|&Np`WrT)!^M$cfKfZR8;ZE|d|k&~!r+ZWlH zkglv>;y{;%PbypC6~($oA03OTF(;syh9DW&XDZt|&smx%U6=#NGWF|{70tHdCC{?* zO}R86@2#Kn)cctJW5YTnjp3DMd;5clXM#DUbI_6y0S;CE)Td4HvcXv9*huyCguW=0 z17U44sBq`TWKRE3i^zGk(oTLL7=iNc-dE))AmH!~b$V}vk&y0#IbgD{I$K7mslyz_ z!>&D3vHpIJ*0O#eY+RRPO`2e&X+|8ldAvPX_oU)+xnJg|@wo=`32kmMl4LI1t}Z~n z1V(0G&;$79=?-uJh$#tPJ~~smVY0(&$!D`RBGi8-GK-~|uDbcn&Kt;2wY;S4yB7?6 zvx*4UEf4l*#*JiBykhl?ANXy>6#cknokmGu2JGv7KCuqSjwxj>{TR9C2PhJf3MLv4 z4Lw)jv^hTn#m?v?48Sl7bGtuSKy7OM12qW4^^J%%iweu8OD`mCOvX~FMU-4;mjXOu z?uX7YH0?YE(c|E@B1Dv!S6yvw+qn_7@~{(=|5Cn9d7=523!AWv!bL??hD~AokV+xc zd$Ge~PXKQg>vF3w9^HO4q;qdzkUVAtyYUNl;gB*_~+{;pKCaaD1C9_I|%KE1{t!=DF zUR$qWnNCqj!F=NeQ1Z00`T7W`mF$vWKM)HvUolFOHN~L%>e4>`@>z?1U4-|0y5@sH z%B$1N!UEf`ea0v$8H=5fiRAhW4{qk**Ge?^^d|6n_hc<024WtT=wf?+qS#)0g> z^_I7vm%~rxTT?#>(HH}zQ#j?cB+;U_wfgV(c|bedG31$&IrT?0ag-17;$WXn9qY;R z&y&Bdz0f=lEE?bGz1R~;FXa)!u&|(41Td2AaD_2R0tkJ9KLkGGNYm-1w=m(bEq~H) zXU~A7ay!<@8hrn#gdCvVjfyq+_9M1kUv<8V?)vspAZIarj7-3i3?wvJ#k>mlaJIGA zcAAZn?G!Vf|si4<&eUKeLLi%3@JsmqnDTO`G;~byii_%K7WN2$Z1K3|O5jxZyk=Mi3a! zF3|pyeI1C|ig8#EGFAjbA(n~5adEjKGhYu%HOFxgZneC^Qh;CeSgU;cxmHSpbHVCGsrH_~ z({Y&0zZEK-m(cg8yRrkp(oE&gHNynN*H3O7W@y^rv2r8Hsm^>kmNgo5vb|Gc1XhR` zf0_VH8whDU&nR9H^;&cWU&+`RRy2c*YC(ojK`^R5n8%vQbL$*YiZ&?PBjD2q`O=I78qX{Ww>5s@u> zX|l{@P(*gwC+e$z;d7tsx}WFV=l2Y_ux-xpJ;!_%koWwZsL(sP$KT4M+r2fduHa^;(YZ2Sr zfN#)1e+&~Uy(iU*{hnr-U&dkltytSjz_)OUsl`2rNOQj1Tf!~}TibhbqYZDzPOj7O zZwG)PZH=J^-4ZnMLR#a4(ceAX*4cdD?tfE^moq;85Inu8I%<$~qM_vOIlj}w?M|5L z3IaD|6L>7R`}wT?i-?}^O4vE?(}eV>4gV#OxI-C)O`1&T z6RQTJjXl-nE}&FYI!U!(V_lwQkt0%{zXdyP0H36tV3J1cA9n@VJAVsC-sXg~Fk>gb z((He$MFKFg)e4K68k1oPzI|hRV`G%|2mX0w6Ra8*lraJyQa;7P;(pUe|5Q)ckzQN$ zsjvka+uc7UIQcI@x-$6>PY=NlOw0_xi)@fHVldIr{1pH=vty#KgL+6=e-ZwGZ;!wK zUFfwNSxLP{; zKs#Dr3jlP^G5v3$YI)0}aD~G#5rlvV870L*4lnpjicw$61arkyg3VX?6}D-6YAKP!nv-WY-x_~=4b9@Q>=uFHg?N;eXX6j}p?W{|WQQ6$ zc=KpXTb14WZsIjryVBh3KrtRl$=jM>P`=?P3lY3E<>-6r%%TErwz;S}XoqI8wQe(DCCeRixNh zN`A;}9U8@zv|V3&J+0+mXjdAggm+FMGn^F72>u|pW` zy1R!Vx$_q40J~Az^DtnWkS?87CBU)B@g+^N*&0xj?JmMFdIXWrbWqa1YjPF8gP(W5 zWY?l!&c}|BaY?f?6#9EsyXXwuDQ9=(GwFhveihAH4?VGa237{yU5%yn92Ss zL=Y$eJa_(@t#aM5_Y2fpFQ~mPb8KXXThgy$FbnfU3*7k7eo+7h(vBCze1X__nnow% z{-h#JdEs9j^v*Uo*}i}7cx@^V1;|YaPL2t{ze4%0M^4|&PuCs_00myzRVl|U&pQ)F z8)qoDjLZ|Ns_Y#x+|~?->9y$$@L5ZNDH&{wrA!%$8xlW$&7sVovbOb3j%@H6tqh1- zc)eLf5-o!IwV*1R_ch}&C$5dMW$Hm8Hi9)AyoXDiZ6RUGcizvKMoOMuk*Tim$n5^X z74)T03zDZlcS9tkSEa8h<<1i3{8h&*e2UXP{mGgm1`hlxKUx9!vVsA~8>RBB%lg*) zMcq2@>j)yur|m_)UX%14AuZTUqlXv6Ip#duZa#D` zR#h}Y$>8xmb|y)+z0>M{dXh7+GL`aUTiuC!Y;Z+0S+8VvJ9e(Hk4ao2%_2p$@wuEd z^&&wV^)(7H_#rV(u*ykUrkFM7%EXHf+{mDZWDe;2WtQ2M*VQkslFHqj_u5LdW7&dv z_9K|Y;fumHR!uEN=vPooy4QQ2mwD{&2y_)k&66RQ7+sNPornxRd%w<(+il^9=uWkn z`WC^MNlmlZ6+@-NMn`&F`w(O2l>6+{E2|*{3k8ty(#MbEhDt5ka^VCH0}QWeL*vC6 zc!9wovB8(Z(z?*DEknnc#l7m|c^@{TDeK*j?;HL!7<2E5<35AHphO5XGb!}oVRCae zE675PWcVl<#Vm)q7e1Q?j2ForHgBefxonPZyVvCn=u?xm_$#rMmK*}K<_xyzH^HO% z!qgd$$EfyJ8L&3uYBP#;eY{Xu>3n|PvFrV5N%t#b;$I^F#N4_l#8+$?vu6}uH=4V< zJ1IRf@&uXeBu&e_h~#EXMBG&tN-(D`1dTyiPU799>!8N4Xy0(@^{CS9m*ifHwQP}4APueMsfFDPJU ziObM`n1q;b^~p?}1Px2}O#{%kqSa+3eHzZVL71s*3Pdr(8Mch|LI)oao*sMosmStW%IR)lq7H!qAYJrwFZ{`U!OWfTOIOha* zebv3;CVo6{_ow`;+>_6cP=-UvyA9@|{vW*BoRU#F7aKme)>W;L2-de&au`GJnO}q3 zmPq#*T#C&>P`m=2O~!(=pqnnD3mnhyvqt3VAq#|6bTfX`uP5`0?ctx^wd=#ln%m_) zib%`qgg0w~Q&%6oK4ZQr10d$wgwitUgFfnZFCcQ*Sr?i-f}(nB*8!jk2aV0VkqLH` zqiUk)9zT4s^d06@Aqi!lOtkzXqD3y-Ax0#4d>Q)YrIyWG`7tpCA~5|f8?C_960LOHd2H+Q(e@|mE9@}w@}*r%B2|lw zX8;Ajwj$M*TGGzfRBt5X{!ZpJpEB>oY%2lu*1M)%DtGRMt7}*RnnA{qXZcTG%>wDf z7UK<@wuVW4Uhpx@i;@1_*Zv;~dOC?FSezx(h{KtGeqn!oR?$@%u8zu0j^J$TD7Pz= z9;#kt5%#Jq;ORWp!G5DNb8Jk7x7=;Df=;&c&z$-a29M`@rP%%Wk(Xw^{)Kq;!Jk|} zc29+`a{R$T3P zEgmU?V%DcGdJk`bS64FhwL0+Z8N!W6R~6)ZVY<(!yU@AW;BR;8riMLtwssS1Nri{Z+67!El6>dDepLp7qbXg!5Zi(lJ=w*o(D|b!@{45r9!XEJ zW47LRA)BO4s~KLBl_X3wH`j=01`S7Rw+Qd6OtIm(*JZ~JcZ|}U+lWnzk*WnxEGqwy zb*e$?1Lw~^IyUp3%ZpeE=xVo7^<&9JpLb%NdvPW@h%XMt$yCztp2TM0} zO~Ph8_3jK6ZhIJArBdgoEpTE!YLg%)QZsxpPr;qM9C}(XW*{G{fC?9VX02Ab0bkor zbwH_xhdUdhb{RC=)F|X?vZP8*e9P?X=35*3yuE_8~C9gvRkKHT}!;{q}0hh0#p5a5v-%4_A z0EpY)&86}R?wYZsx($Br71yfw_bqPP6*A{VQSea?oVrSi@)nIbH3N$W4_px3Oe20V ztHL3*lbY*V^#nI-N8DgPb$hIErVu&H3gw={b`kvP znT_PuY3E2o+@KD2J3+!G4z9!ScS}KYs;je_DJt*TM2PPMsXMIaz!i_Ar+a=1b0x(+ z^Nz{WvhG|h zGanjr*1ebyG0QLy1`;12KZEAz72H+#hSVNgqKDbXM80PI!t-uFh%rs~4>&A~#Rd(> z+H@vc66I*s=LPQnh36`{!D6cCjuD}9M=|OeOUk3ww2kt6yabYVD>5?W8{JDE0(KRi zJEk{c5~Nt#Ic@pP41J$a`%G~w{`QTH9IVR1okzeZ!^bD!qh{{c={jS#llBX!CE2Vw zUU(#B)bo>S7pf28#e|1_V$+XcIK27knH##(JjE9b+5}BdNrhiqEGuVVkK@Ey|A)|( aJJd>j>Wg`g&As*SyJTWuu20l;jr)K6n-x+3 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_add.png b/app/src/main/res/drawable-xhdpi/voip_call_add.png new file mode 100644 index 0000000000000000000000000000000000000000..8237f3305006cb821191df94a5a40462c0a54aaf GIT binary patch literal 14157 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 TnL_or{{|?@sms<#TRi_?#`x`Z literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_chat.png b/app/src/main/res/drawable-xhdpi/voip_call_chat.png new file mode 100644 index 0000000000000000000000000000000000000000..be4a138de24fbea3ee93f74dbd7f65c19ac4201b GIT binary patch literal 13663 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_header_active.png b/app/src/main/res/drawable-xhdpi/voip_call_header_active.png new file mode 100644 index 0000000000000000000000000000000000000000..3ef0106bc98c795e193286178cfd14d36ae83b82 GIT binary patch literal 1787 zcma)6dpy(o8{agy=5`qCpoWl{lN2Gh4U;j>I+=7iuBkXm?v`v!6e&k$CZ$Q1Te`^Y zgy?K&u7{l|tsx!a7$e6m<9zwmKfix|zvuOOKF{a%Jn#4O`g~r`=lw}1cpg^VuDKlq z0x9BLoqqx%ZPVpsfHlSz!~=oAS3;be2skGvor{s-XF|e)K_H{-#B4j)-_hC}zmptg zu_*mlfC9JZHN?H=5%OuG2)-CZ) zza=^GKYO{=YrPWdRo)#=Qj)7}r^0}tP^VD%T(Rd1TMETBp0l2-$HDc<|?1D^|lU=;B$%m#yyk(tA~$GqVA5%srs;0 z^{){KSoz>L*ZRwI0(G%pA`lx-{?6+t`LzP#a#PxiiHVhrP{c1zq8uzH4N}L@6W7Ns zXG;D20$n_AHQau4zzXcx+mb7sKQ`6UwP&)1u3;^;T9Te(??OPP}6e3Yjt{ZPV743XTmq_mK_JL7 z&KctsKO}m8{v0tF)H1gr9CCcrwW8XeIj(=Hz$Gy34=uS-@00R4#ut^hP+H$r#;-M~ z#UfJ|y#NH&jjE@1!)R3}^|Wp(M+X@VIho-)@Eq1Xc>$q>dfg;QSQ5l63N{dJZ)Qj{ zqrT)j6NOQ;)w#ho9f42j$amb;bU{EvZvz7oT*lTnGLz@_U&j#wRcR&g943%(H#Nkh z;|O$LJXIA)(7s0f(gz9msIr5&-GO*eE_T?7s8~6q5a*D+cItq-{V7zAoS8a;H@zYj z#MUqJDU`wf{L64+G$R8V8i2BGP-o#^E*w09O)s-za#2iC$;XN?THd+)8A?av3CFsq zXN6a_W;@S+p?mJIg4*ogp8-0$T-{ik7u9a=l>oP$FQ@6Xz;^Lo@n5yoM8CQ(%Z6b_ z(Lcv1@_JBVVM`DDB>4FKh%j9 zMM=GWYB|idPd?!n-o1}>5Hxz>soc>_(emS<@VK{siP!e@z0oto+&I!J6x3E!rdhmm z2NQ7t+m)wU3I%nKD$~x^acm{I+AJ}%`5k>&eK!8#%Uf*VI_bN`pJ&$E*p`x96QBrc zCYf%SrFv4jEBb52MGwr>mAxq8>tplz>s;teWk_^ zJrI;9zo^TyRyD+w|az3OKX zh$cCQL?p|ociyF<>O1ddB};+YjQx`6O1JVvoxdYKJ4v&===&3c8qJxKd+-grzLG^DbL%AX8 z^Gfgf$02?ZVzlL$SkkTw&&)s`BJ3vGmAo0OW}BDw%(oQ)PYJiFo=ttZ;SIM|Ku)>l z-*!y)=`DEyfaAE7uh8S{Zww8yZ$PYq+kb?3`n&&v9bZKQIt_fbki1DhJ-N*Q5bo=K z*)4pDGK~Q-Fmy+3^`YBu3Y=k*xYd7?_?il^qyY+4#tpBb0MvE>C)o$I^9Oph{y%^= tzuJ_r+LXQbVR+lnK-PcbCsh%^*SFkE3^U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJhqx@Q2X##~O0_N&;h!tMZ5?Sc9LNuz>VCNU^Stcr)dVNjV2M_ii zUDe#Zc^%)nCWRl2kDfhS@&Q`` zvd}kGuKa8QZ4EQd=p_41n)E?7{yWEquX?98|2hBH^4FC)M;jVEY)*=<8y$4SZbf7!hnbi@k|=b!T5^g% z%_ht;zwp|r&})0t>k}=Ouh%J_`;0><;)sw+M?iopXX=fPORFX)DZ2hHl>fis_4E@< zC#+_1>thXB;8^zeLH&o_(c1Q=FI8I~J3Q}xT+_o8yW3&$GmHDj`z~8BI-Nf-_1>rO zR|n3hzuY=a%#!KX_Qvkl)0VwqoNj&K!eqtSo3vXq*gjW2_}jBjlbh!r)86d3{Ij)l z>=-g%X;m`I$S@_do{IZkSzcb9|9@WQx9`j~_ls8hJ8Me;qlGQW+ua371Hn({-St3< zv%n*=7#M@sL734=V|E2lkiEpy*OmP#rvQ(X*47*M#TXcv`8{16Ln>~)z3cBSk|=ZF z<9xHsX)S5&o?2!eR;4d}UnsszbD6$wFW2Jl7gS$K#ePjFeHF&i#NxPQ!}PT(%ty~$ zvrk>VM`X#5p5JfYJg=T}*v5ME{JGC>Hr{<%Wm8^f9&&P5Qu(WMTaWEsEVKD`W;>@w zYA2`0jE10nGa8yWHJ)~3aGEsZeH;$5i1;|HbT(jQ6%H_9T*?RHpVSU|zhS#X$E^on zJ33aZKXc^SxvWh6e;UV|GUp$k@X4KP?@%#_8P4Tm3ay)Nm{BivNjK+YBUEpLGTMWJW@k zJJ6}D!R^VXgn*7e$P!$)Ac2WXZN|SkEJ|h4%Iw`by|6Gr=jOj@ z=FgH}-qxOBC3@w>g|kn7*IWkXn0+=aE4}W2iu(Cr@9M+liL0bmJUe&&)EtQ!Mhv0x zGdsVob@S$X^7az*jG`Y3%j#U-%-?_GrS`dF8{Y{pl|OJk{_KxE|Ez8)ZfP4pv4c5t^m;=gs%JPC-4D@5D1XR^31E{u;M5ywWhrlWpBm}jB zy?Kfsq;$z2u%RSyUbT7NY;5v83DlbcG|VW#KvPm<#uOeAA1^j8HC1NT$&-kXt}-c4 kJQX=X#d8ui6lDEHx+HJoKL+N^^PsHc>FVdQ&MBb@0NEftE&u=k literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_header_outgoing.png b/app/src/main/res/drawable-xhdpi/voip_call_header_outgoing.png new file mode 100644 index 0000000000000000000000000000000000000000..474abe754b0051cfdd73021362c92862781495db GIT binary patch literal 1274 zcmeAS@N?(olHy`uVBq!ia0vp^Q$U!54M>*SFkE3^U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJhqx@Q2X##~O0_N&;h!tMZ5?Sc9LNuz>VCNU^Stcr)dVNjV2M_ii zUDe#Zc^%)nCWRl2kDfhS@&Q`` zvd}kGuKa8QZ4EQd=p_41n)E?7{yWEquX?98|2hBH^4FC)M;jVEY)*=<8y$4SZbf7!hnbi@k|=b!T5^g% z%_ht;zwp|r&})0t>k}=Ouh%J_`;0><;)sw+M?iopXX=fPORFX)DZ2hHl>fis_4E@< zC#+_1>thXB;8^zeLH&o_(c1Q=FI8I~J3Q}xT+_o8yW3&$GmHDj`z~8BI-Nf-_1>rO zR|n3hzuY=a%#!KX_Qvkl)0VwqoNj&K!eqtSo3vXq*gjW2_}jBjlbh!r)86d3{Ij)l z>=-g%X;m`I$S@_do{IZkSzcb9|9@WQx9`j~_ls8hJ8Me;qlGQW+ua371Hn({-St3< zv%n*=7#M@sL734=V|E2lkiEpy*OmP#rvQ&ME4$ETJ_ZJ+@18D>Ar-gY-rb)S94K?( zW3cwi_=QQD3zv2!2_AJl{%TQkcXOB04;H>h)<@C{K5<%gZ0pmzdhi=7i=&p8%ETZp zvEs{m0}L2hg#%s*0{J5ME-JCjoh#u#-CySY z4W+34b<4x!XJ;mti!a;MSXJ>ZXI1R38y{P3tn4S}?WwzccEv8aU`6}Ne*H5tr<|6# zw_RR4IoRN)^HEbf3x?pE4z_di7w&J6*k{Q{{ER9-NWz$_?xlpP!9D&(BCW#SivkaNU9gCN8xZ zpO}HZG&nd#9vCJ~oXh?IeJ|o;&?@pvy>+9Ps_p)=r~9|Ry%$?E-EQ~gOOjdp@7kG8 zRcmigU|O}K>(G{cn>Vk2pYSRvfctA&R!-jA+JD=&#@?9u^8QY#+0mH`@3p*~@VdLs z^-aXmP2VqcEt20Dd?Ci*Da)$U&6?kBLGDliyJA6t5G1$_RvrgC089X#2bTS60(P6m zjF2Rlsv}TsL7k2u_c|P0BGe4>T|<+nDjUc{n)V>2lOet%fuqSI;={$vI{6ULNCTiT qq;k7fTA9Zei>P=`!iui&{$+h+>&f5AK7Af2%Xqr_xvX*SFkE3^U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJhqx@Q2X##~O0_N&;h!tMZ5?Sc9LNuz>VCNU^Stcr)dVNjV2M_ii zUDe#Zc^%)nCWRl2kDfhS@&Q`` zvd}kGuKa8QZ4EQd=p_41n)E?7{yWEquX?98|2hBH^4FC)M;jVEY)*=<8y$4SZbf7!hnbi@k|=b!T5^g% z%_ht;zwp|r&})0t>k}=Ouh%J_`;0><;)sw+M?iopXX=fPORFX)DZ2hHl>fis_4E@< zC#+_1>thXB;8^zeLH&o_(c1Q=FI8I~J3Q}xT+_o8yW3&$GmHDj`z~8BI-Nf-_1>rO zR|n3hzuY=a%#!KX_Qvkl)0VwqoNj&K!eqtSo3vXq*gjW2_}jBjlbh!r)86d3{Ij)l z>=-g%X;m`I$S@_do{IZkSzcb9|9@WQx9`j~_ls8hJ8Me;qlGQW+ua371Hn({-St3< zv%n*=7#M@sL734=V|E2lkiEpy*OmP#rvQ(%sMONKSquzJF`h1tAr-gY-nG?d2^48~ zxY^n}_3`2h8ZOQ+)gOsRP3>n1)M&l2_J*V573p(pUZ4A1W_)s{XWqOsce2t9cJ8R; z551XSU}0x-!#eVrZTmD?wFMz6Y6}vWyq+d8aRE7c7#yx;MTU&5!T|=1OXnqk)BqLR z9uyHYeD&#(uW?WB$0!#U8{hPc+w#3`X)-V0zC#(N< zb(M3EKQ6iUQ^%+NkIVZ{{qz2REw(fGyT@-o|J7~zA3uEA^Cfad&Z*o3jZK~qCxF}m zvk~fq$yF?1-*c&j@{r63ssx4`m)cZzkWLafmju*AfqIq#9hM2yD5WuD3L7chOHUp> w`}^k8w-a6(OD}(ZoxEDXbCL=+wB-K<_I0WvY4hg&1Eo*~Pgg&ebxsLQ0J${98vp*SFkE3^U~J8Fb`J1#c2+1T%1_J8No8Qr zm{>c}*5h!1NUMMFQY}$d>kf;Q6^{}eLyiQl4rt+87?yGM;FG2HPLtbIjvfsx(6#un zIij?n@Y>abp#m)R4tMU%5s6)LpoFOa3WEHXE_VcG38EkSYP zo1%tay8ONb^(v?spPn&wiAv4&>$Xfa`@28t{I9NGmuH%8sGuNp-r(?-&g-4a&2pPR z+ZDEmrcV7ar{c;Iwodt->ni>n+xcMA0b}JfD^qtFMKNyOHCqg{XGwijDGa$F0C65oMuT|UN4F8xLA4Txc!4)szsVl zZZRkyw>fn|VRzjJ`yaN^+V+O;R9hcA{O*2S!^5~Rc0pX}ocGDcSI%cRnEt>k|JP~L zgkO`tWCeH2G1}ffaI`cy_*=ti*#-YkPVl`swJF5Oj(^Ymi{`0~`=S?QU!5KOSY|oD z!>qKadl>on8Xq#HuK&4b_u|sL|MxP_yk|G}FY4dq5Tp%^7q%pCcNZWH1V5d3*8?fe z0*}aIU=&^lVMZs7*%d%R_7YEDSN5lz0-PFRg*>L*K%rNjE{-7;x8B~~&C6scz;fZ^ zMOHypMXd!4CA=RyS2nbib#Q1f1+yHyx$nDHME3Dp8A3o!I6=f@qf5!>z8~K=t;%=P z+mt8aSu>wMerS1$Z`nkq(B)MxHGDibX)nF!cI#k3PzIZx%T3p=eFP~VlzJB=>weNma fC6Y86^FzD*;b~?;cX?Ob0?Bx~`njxgN@xNAQd1=> literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_more.png b/app/src/main/res/drawable-xhdpi/voip_call_more.png new file mode 100644 index 0000000000000000000000000000000000000000..73bb13b363d3bc0cab33b8fe1ce80dde319c8aa6 GIT binary patch literal 10488 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_numpad.png b/app/src/main/res/drawable-xhdpi/voip_call_numpad.png new file mode 100644 index 0000000000000000000000000000000000000000..f6e562387d0a4941a80b00094c63dfc1f7a6e5ba GIT binary patch literal 19610 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 ikX) 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 Qe&^k?l1dVlVur#03nY?;dH?_b literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_record.png b/app/src/main/res/drawable-xhdpi/voip_call_record.png new file mode 100644 index 0000000000000000000000000000000000000000..9c392dc4777ce1e62c22ae43db8a6b234bebed97 GIT binary patch literal 3890 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 V>Ed?GC2Fq%=xG_EOEqjm{tKd>sGR@+ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_calls_list.png b/app/src/main/res/drawable-xhdpi/voip_calls_list.png new file mode 100644 index 0000000000000000000000000000000000000000..a3d69b84eed51b5a68c5e211ab2d0251c220d932 GIT binary patch literal 14703 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_camera_off.png b/app/src/main/res/drawable-xhdpi/voip_camera_off.png new file mode 100644 index 0000000000000000000000000000000000000000..dd41c338be4dc0a32759dce59bb48f26f1a35c75 GIT binary patch literal 2971 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 V;L|R3regpA002ovPDHLkV1nCSOc4M8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_chat_rooms_list.png b/app/src/main/res/drawable-xhdpi/voip_chat_rooms_list.png new file mode 100644 index 0000000000000000000000000000000000000000..edf722c7e5edda4a64fc62f01a43e61aaa3e6136 GIT binary patch literal 4105 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_conference_active_speaker.png b/app/src/main/res/drawable-xhdpi/voip_conference_active_speaker.png new file mode 100644 index 0000000000000000000000000000000000000000..18f21106aafa24bd39aef95cbcd10a7bd55f0d99 GIT binary patch literal 7818 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 NR8PxTvrOGF>_2Ny literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_conference_mosaic.png b/app/src/main/res/drawable-xhdpi/voip_conference_mosaic.png new file mode 100644 index 0000000000000000000000000000000000000000..8fa0137b7a80771c75dbf45b3c29b48d007f0b51 GIT binary patch literal 8850 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 Y#82$0!cXia|B1HH`-WhZro)T>0Sn}_EdT%j literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_conference_paused_big.png b/app/src/main/res/drawable-xhdpi/voip_conference_paused_big.png new file mode 100644 index 0000000000000000000000000000000000000000..745f17220a3fc775725a28b2f9a2b0cb5119964a GIT binary patch literal 2250 zcmcIlX;70_7Y6g+oy6lmY+h+VcgL6A_ z`TlGZxoOuak_0(&{PhvDvv;AH-(733o&B2iUE#%hTLZWHgrezL*0S2i>3Owbutk1m z-S+S6mmb@8#cG-s9I*2I7NMu3efVO&MV0ezU8uo$My|!mAs@44T?u+rd){`RZ?*Bb zRt_OfYG&SYJ8|a^45K1YF>Nkgcyp>kD7QHw`rP-M18sn=)HoR2^@oRl+-*#MTSK$r_lBn=`D0U4~=CQpa>J zOg0C8CK_`I!AM|OBe+42LOvx$RR?ftV!0~X>b|ff_0}#Ceo|~&rrOijSH{R*iVF}0<9NvAy{W8 z(8?y?l`wB+mMgk}g(oN5+`!hI(i7r>LoJYmIvfNUoVFdb1eqjM>B9AiTG^dak%T4` zg43@7$;|nQdRg;mVT-xCMAOlShQzwQQta+KEzYBfqGNq^#n4 zC*Y-^F8VnJ-X<$pA~`8p?h0Yx`Gb~=H>|R{9?k9bSm_3+e58$ihF_C-euc8ss_GLX zFIgL*y4?}uGu_AT=O0{+udfi}UEncvgaS2hKsC(yN^lp&>ro}nongtYM~9b}Q=+ku zmOZOc$_#;A3|8ZQtu`Ytd(-@qkG8cn*|F-C{_+bh2k%$KiH_n^OYg*;k)R6zz;c1K9T;jOb12o>poM+M|O={AK@no z`N+P-?j!unJ12KuDW7XNhJwMb}d3v_rVGj!9yCT;*D0qE){gldpsv0NBffYMB}VvCvE zi5_!=m@EaM^C%Th&pRPphFo3wgooIxeCO?bvErm$Li;3`>?qLK>$Fq|i22A42V(Hw zz4p81QXptlEWDw$f&G#>WLq;z{*L%)^@+2`5*eTwVXxm_j(4d}ToF@NoCPmZGiGU& zaYJnJwd5K+-7*VyE*-{#tF@XWcXBkTEHlC7{xdnzzs}*WztkK9%~>&5le}Bqno6n% z6?970K7l+naaI7(46&}JV#cOSWy|bZaT_lHmZ6rEe51GfAr2pTS;`*fVmol+*o`$~ zUZDH6=rw)UrCflpX{m_z$Ni=%SOYWn6m||_O!{N8%VB%pE@r!2<&Q87sd`YT2IDx| zcup^$1hZ@4(#$Wka+q-IFNy=};yK?KpT7rBRKc{cZkCSY{4tDM7$);!ytrFo(!zj^ zDquQ1HolfL-UB-y!9+3OJ=1kE3|D@9K^mV0|DOE`%hl{A70u-cmMzZ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_conference_play_big.png b/app/src/main/res/drawable-xhdpi/voip_conference_play_big.png new file mode 100644 index 0000000000000000000000000000000000000000..303d05faa5f5fce96c7b0533a1dd37f07f111ba6 GIT binary patch literal 15790 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 y>-^N}?WcGJqKxgQ_)hkNXdEZU=Gz literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_dropdown.png b/app/src/main/res/drawable-xhdpi/voip_dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..d9fccac919fc541961a0f66f115f143f7ee3b9ed GIT binary patch literal 1256 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_edit.png b/app/src/main/res/drawable-xhdpi/voip_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..c24930212f56d7147112a52e8075d26276d4463e GIT binary patch literal 1834 zcma)6`8OMg7EWsmErU@^dA7z`3yO#x#7z@-G7l@$_hWu+7nhWEt<`TzhL>Cx$CcJJ=W_jp?{JT%2f;-Nsd z0_~&}L;q_jNaZn+8e`gnKLs4GQ>A2VJKuc%+=m|T>~VJ$a)pYY073}_Ct|wfdA5{C zHznj_8u>thya|Xc>2ih`idE$LFkT5O=XqYUd=0#jqyYO-TMt$EeUFrB-=tYoRO>&# zX>Sr@Qy;40!5!-2qu|-Pq&rFFcWxDYGY`$k1SKBkQ-+GJNLqw>k&b>Pynk#l`to|t zu4!n7n+B=wV`nv{q8rt%XFaYlR~YX8rKQKcX3TA!ev+8p!fF@_&$Y5cOW2Dm-?b6- zaekzXsIo~(*56P!J&8~C24y?cf@O`Qk%=Px+wtd}o590f3ki8raDbOC>s0S@0_nN$ zEUP^&h#hg6pZ;8{X5;nkXF2yd>p{0j{JTMXGLu1$pZIjh&z`G$fkCz+|Jjuh={Wu& z8YQ;UF3=SajIW8#HwXN%rWwvBFMqkjdf?#!g-pic&*`|m13o!QSb{&9affG%Allv* z##0`Stk|(U5e~6)C-5Acyx=8EjXVmkDMW-jS_>_T@C#qnYc-hp1ON!A!(mWo%Jjyp zm!D_2Y~S`AfkSWmz#swtL z$;_XBLiTkyZQ0~2Sw%f=Dt>4P#YaJZs?!2bPVEPp#p*8U_j4G!>pPsyWlbYtkkQf3 z$*3?~>(a%le*@1z{T~Ee`<5sTH+Occ^A)JX5(b2d=*M-);V32S$U#GBaZ#31#X;Zc zWC6~L&OTkSX1!%Y&J3WBcPgd$CvA#jojIqdreh=BhO)3Urb?VYKK6}Hwc?CYc{r6z zB@WA5EWA#C5P%OmI6gw>0t3wTn~xiU!>BRojWDO6i69FKRxT_1+s<-kq(cqcX|QX$ z{#Do-mAL7W1g8dXW1=BRxT1U9GCBsmina(O`UX+Y5bV?aipQ8}qjp7?#h56`E3-mW zu6EbEwEJ3W<$Pw9hqwFlK?2YaD5sc3;q((VHs}GMJxOPY!>?y**z6u6lrz;F^#J=P ze23-m1iV+;mk}K5csPw*X;-P_&O?*fdBQHBDJ%l9kBD{~e@os>CG;2q3I{L)()^u? zEZEL>aQkz!>LBgq1IEX+V(!0X9b>{H_0axPQxUTcXey&7IaLlco5flst2u9vm3yy# zM>p|$Nn@h9ek2D3>m2Ft8reqEsU0OLeG8m1_q7~GMLfADomzQ|{s*=3Hi6k36Q@#^ z(}S7LY8%Gj_ZX6W!YMT0f4pLR9WODcS!}}t{ROdvW;FG`Z8v)|R#sX9!8vU&RU%ue z0*?KfHw!f-bGTCvxU{EE+Z(79I16G@mMk2}_#JYFRsG|J+L(5>5n4q9tx_?N0_TKQ zi7D1L(tu1my@;4YKeG6VOY-r{qNL>i1x}$GhWvl{D4>2TpaIuJdNUODe3&X_RWc}h zd+#^xE5v_+63b;ocMtG}qX|se5$2QkpBMTZ1kfq!>@t%ari`c}so@=h)a3tVYeqZb z8YAd{t_Ih_Mh#_rJ$693Ezw(uoeuXUoUKJG{2V8;+M076a3_s}2K+9{aY|dDnh~0# zbm>wEI5f+(|Ha6pxyI^JV{Y5ZD&-4yWC`eRqn(lqsm+s9Qj5#o4qJAp0%w_6$9EHb zhS;OUFZxb}Fz32tDq_Kd>p?Ef>S@u?alfJ`F}9#ip0cRcZ;f1?@152Cv}WYFh#97O zD(6Ui4_)-)zUUfS9O*Zyzxlp?ygzrbvCHwy_5<%MB06NSO!T}?AuIEhxJ83=&-!V+P)sk>wXH-8Y@IXfM~3rq30=Ibz?t%B;5d1-zVUN4h^< zR(@-DlBG(;+OOvz^nEu22*#F3)n3IK6WNJh&2Hc#CIUQ=(meYw!!!*S6IegyhWQD~3m_^^if=+pVj%^~&m=k2re+C#Rk_WkVZYn>J^+ pr1wt5N0P4+%z=O70bPs~b)E7p*EO%93Z+snYPdB5R%`hv?k|ZMP4oZ& literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_export.png b/app/src/main/res/drawable-xhdpi/voip_export.png new file mode 100644 index 0000000000000000000000000000000000000000..3fdfa078aff91f4aa957b48684a1b9119377244d GIT binary patch literal 2383 zcmV-V39$BwP)EX>4Tx04R}tkv&MmKpe$iTSYBZ2Rn#3WT;LSM5{Pz6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfb8}L3krMxx6k5c1aNLh~_a1le0DryARI_6oP&La) zCE`LRyDD_OB8WcpUU6f~epZjz4Dmjw@K7n|a>4rtTK|H-_ z>74h8!>lMN#OK6g23?T&k?XR{Z=4Gb`*~*ANT=qB!^A?Njpa6GMMEWy5l0kNqkMnH zWrgz=XSG~q&3p0}hH~1OtZfq#X54x>=*)r00006VoOIv00000008+zyMF)x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru%+O%$p(A360XW~VT?YIR*YdbAP z;ty7}j35YV|C%(|w4t#U!?X@DO&Z<0&8UgHjW(stUHm@$5kp=3&Og$exl8l?^X~Qe z{Jr;m-skz;BLnlA*SzNCoM^86ke^CG^zwG$O0FDAU8q#CW05k&N#TZOR2AKZ;M(u@eeS*R$&TbRVty6 z*Sbcp+`U*c%y$4REjyJH=N8=f`Bv_!rKA>2++|^k5{lN=V+sK5m)pmbqIDL_syv6sjXzKFlj6r9`A=96o z(X|TNyB0^6cxqSLup`;t>YI9+6*{PFyI9=z>3?VB@rBki+&)pUb$iB`=?u4U5TKjL zVh_z3{zb(txgnVg&jvNFSTK3l8zZIkjT%h*X<3`{&01s6&r5JbapHZ2l)cuvE6K7p(%$7)e6D#cmfLp6oe#rj`}3eYW(JNCnhZq9p8nfSD@R7|E7UZ`DP&ms^q94S0N-3Cu>OMm#+G3zCmiH4 zrKPu2 z9It#PmI;+J$Z*m)MV8cnQY=H~%iYcu{Lfy3#zz84^FAQ2Q}hW|kVC4kCW%0f9JdMfCje6g;#1k;&j z;JD55Q2<^B)tSHB2`iC$*j*8noSG}H9hG8<$F0RO8p zOBjq)M~kt&o||oMA@4jgw9_fYcz9nz|E(L#643-?Xt@oW1#yd8O}Ui$I*kl3=@SA! zl!@sNA4rBjaWn1^%|M35X!53*(!P_9M-O@dj)|rqL&DjP7UPGhUH$)tM+S#)s@Bo(-6AITPp~fQnap^yj6itV#LxE zrVl{;?JDOA>c2}*qBM^e`v&zrZguy zi3~S~6W0o%h5&jD_DGchenAAx?I|hG*7|~`HYYlb3^zyOw+o`@tFc}JK30rv!wLOW zTT{M_=0qovAu#=Rz-+zH&!7nGxi3IC%1~R%L3J~l6RFwA@Uq#X$8Hgz0{~XmsK}oT z2F#R{QaME$I|9ba()+HNN6a=dw0Zz}v*l4{ei}fvvf*uLViPI;ffJ;W85@Tay8D_F zx9&)G!dFN!u%=om2CxB8n**fLoz02qUH!RqT>mi~gGsNZ=EUcu{C_5)Kh7j>2l&HkW3S5Q#Nk-@_hvr$U4X&>CIJ*2C5nTbb|&_owoEUj53jU~;Ao@r;z>^8Pg=vnDeADPLoDgHz(F8 ziJc{lZfs7hM`B{Zuc@-xdG0nR)~i%%+oWPVWpS$c&53T)m)@M{W;LTZQFzgqOl;tv zLZ}`v#;({DMLp%Fc-9yINW}(@`IJ}=;Jq28M1-ubAXZr6S5F%Q0DF?*Qyl?gIfH*v znw-^8j)*n|{l=4npzabEM9*Suy^p8;JaE`?fnl4sk57~niuRa<5L~WORu^0Q0|BTU>Tu13<>A-qBoXa;CuHn+Q|VY5Nn6sORwyNfu8ss7{@f zOyJCA5s5o8#5&O4?jg0=L_z8^t?dEY<_CmcVTZ-;MB}f?|B;XM_s z48H(YAAM`T3koNHI|u>&JNP2W>~CPK%e>0U$-*al$eg(g0N@7(I$D_E>CKF=I9u%1 z=x}QD-Jk{*U5C0{#_2Z`1j3QlEq5^J3YXyv2BhvE3J(-3X5?z<8dV!PqWxX|jIABt z+8RRWGH@W189>hk7>5%%1xLWoi5+rgg3nU~Y2UVH)0E4@(W6@fbL9h>&DE+c-v`rF z4>Lm7>ZvDLcU^91vWOEo5&v^Y`@j;t2<=FnYCN8T*#6AOZ-of!ZRC6~`PQeT;t&=sU8)ceJBViwf;Xmo(_x>w%_arOeB1LXN*0 zV*d5P*sG;(+vA5)wZL#MQ#TXxy>cHu);%P^57Ng}v|S^3fkP&0gR2S}y;Q}JIbeM)4-8QLaXpSIR1-iQ*(eB9 zXCtsMMTq(L#@158GiJ@XCs}`%gXg)DgMbqe%q1=PAfq)S%{`qL8D*`Wrs~{@v}I2Z zO@9M(ENh*ld3Jz!5X@(jq1^2!ZRIPEj7$g>rOh29dIpw35NtX2xd+!D5;0l36H z`GM#4zM`%w$ zXhWvynBJrNvV#c=l*9?sjejyUZ9+$d@8zMnAOBj?n~P#p4Q2G*g*Jzit&(gM0_s{K z-Tv7bfbWvFu4A8hC4HvNFDRc+l-l5+W`$44pwN+=XppSPw?ujgUp-vW=Gi*!WNIhA z&k;MP=KHNIA(TEq(R-@Goq%{%DG4l`;Y~5>HWeyLwZBT zomfKMj`Tr;XVETe;<;Xt4DXH-9bO2d>!k3usf8TrjwEY965pX4_CJtfq`n&M+;->1vU6n| z%<197C7_8)?+){)?N}hw1?FgWlwXL)Y6fx(S(=^%X@!IA_vW8Bg`%vy3cISLlUYUg zdvCC98pfS?fP_EsYiy|mx_OPEJI`WQ3Y`au^`|=6K}~obOKJGUhZDL+wRW@8Ig!{v ztSe59RBiGk?1_%;idqQy>a z^E(^b3N?@a&)-fj8~-m= zaNawoZI`=4z-H@MNJz1Bd0&*Bz4$7edJHi+_(an;n~&lsW)cg=1?SMt9F|JZM0im4 z70$5B_xI(Kva)!UfzLlbPer$OepWhVV+Eb#MVCaI>AmbIAfmm8Ir8IuXGdrT;*IV|DA(rY zV7Vj~si7zWe;%*&$5XUKlJ5FP##dB-R0t?2ktoSS)*)7jvq}ka=Fr_~-Q(-wVDJ7W zE9|OA3B0ZoBDzDFGraEFm+i%vF zM6Eq0*EV5_Vv*sA;}2_LNlG}%hEF#iUBj&vGE**o3^kFe-QT?jL}{czL9Rh*ic&?+ z-C{dQ1@7m{2F9D(0Tno3Z%k=_Qi0u>@vmmJDDR5wq%jpc3qil4?A;WA@NPTYX5g<4 z#vhZzqTvEeA^SVHiO0kqK|g)5W?pYHDJGy(?k%;MPKz(1gc@32BEL4aoX};(OhSMC zTkmOg$F-P>8}Wk4x26E#uXMwk3c_k|&Iq@TigmiqjS z+L}OnSK&sM+`HNXe#((^DaHY6S68*C9EaJY(v82d0@y=McD+qm;lM#<_ulDZyv~+z z^3MR58YdSo)V) zqt^p}!{W*OLB)ZYX`!_EJJd6_)(j^*m1@_y@6xdGm;pFj%tY8F?I4!{``oFnZa zj1vFaO(Ay2O-pQy8&LxN1Zp*xnG9*n1ac6um{8k-;IAEJRLAHp007z*vthee(I=CEEY#Io6eMhr9kYf4=Fe`mIV0bWL>15DpRl1qSTf A2mk;8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_info.png b/app/src/main/res/drawable-xhdpi/voip_info.png new file mode 100644 index 0000000000000000000000000000000000000000..ae8dd86f8a3121f7b4f80822360cde78c31c62a0 GIT binary patch literal 13635 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_menu_more.png b/app/src/main/res/drawable-xhdpi/voip_menu_more.png new file mode 100644 index 0000000000000000000000000000000000000000..7f7f7d8fd31b8bf96b81cf24fe2a50303142acbb GIT binary patch literal 1772 zcmVEX>4Tx04R}tkv&MmKpe$iQ>7{u2MdabWT;LSL`5963Pq?8YK2xEOfLNpnlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0Ya_BG^=e4&~)2O zCE{WxyCQ~O(TP5UFodYgEMr!ZlJFg0_XzOyF2=L`&;2>N)ttqEfJi*U4AUlFC!X50 z4bJ<-5muB{;&b9rlP*a7$aTfzH_io@1)do()2TV)2(egbVWovx(bR}1iKD8fQ@)V# zSmnIMSu0goAMvPXS6bmWZkNfxsT)#vvg`Uwzx2Cnp`zgz>RKS{4P zwdfJhvkhEaH#KDsxZDBypA6ZQT`5RY$mfCgGy0}1(0dDXt$MvR_Hp_Eq^Yaq4RCM> zj1(w)&F9@6t-bwwrqSOIa}{!@hjsIB00006VoOIv00000008+zyMF)x010qNS#tmY z4c7nw4c7reD4Tcy000McNliruDoOW)CO6Xkuptno+aiHJ2r7YS zLIgENd1xCG+Z3w;0TfVyErrU<8XIPJ?(t#EvTb*}A01xw|2B7Ke!2JDd+zxlOevuz ze_)kpUP{0`01#kU3!Ul=dyWGn+5V(VgKni}i{;uZpiKZOviy4qgs#!Jv8}7EIhGUL z;0ZlTEFS?VMLrT-6`{J0u=ktvVI%#a$sc+hBp(B~6ZwiTiwGMkA6t8+zxTW2lV;rH z3p^vBBLHTgIN1=akA!?*roe8cW^2p*ZOY}#RJ)SW@MywV+|||`V*n7#wK=CvDJK*F z=G`$P@eTkmAT+l7jws0zq9%XqY7y>-qmorDnwP?15%3@rFyCpg0P|TG)@_)v#RM91 z8mxsbbHc_CPFupIxsP{+y}yI-lY`(N-+#~ZXVO7LwmUXmqU~c20+gBY{X`gWfZJE* zy7B2a&u{d%E`a7f5Xy>#{zYt8wzsxiNX-}kB7rS`XrXlgE*Am41jy4jq2s^+&>8j) zP{ODJ=!*iZdN>ZLiUj?~bA~1WaC?K*LiCzIY5>fc02=|(Q33Di2zYw)j(O=)N^#V3 z%`-HY1AxssT5iUNl~Nl2DdjYU+Z!yknU_H8d&hQr$SN_<>{gpc3I^BK9yCf9|5``D zs{pN;7|j9L0(?(`y`?4AzD)L>1a>!vmy4nI3-C~$tNxh`-q^m?^GjCno4!B|gANpw zS0ojK;Dwu^e;nBD39ZyDJpg7ExnHBA>G6)Rw?E~1ZubWzn&q=;KmlMTv3#PGGE-pt zu2=(rMGl;@`>(~#`V^Qz4bH`nB#Rnb!pmUm6OKBEU@4PrFLylrldNC}m1j)=i_a@6 zSd&QMj-JC5M8XU>o>xF5$|N-C_{}CXNC42_3!Np%Vu!G2B0=v1oIK1rQsCxHaa|@^ z3;=V@cqf2!4qW1A=bG^k$9>b_3#=ee?{qF9t*N>*>>U^v{yT#HL4wo*h!p{iiKgdn zLMP=F?Lq%nHssL)lf*$7yrv`U{W^V+Cw{dn=s!{FvK|)5%YbwPurtph``21{Swos|rw0<-mhPklPbpA*`3BN(z!FX+F6#>=~S@`TsBTGut8X%jPKn O0000 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 W6E)utFgWp_YSvQIS1nhz4*Ne*KOR;9 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_micro_off.png b/app/src/main/res/drawable-xhdpi/voip_micro_off.png new file mode 100644 index 0000000000000000000000000000000000000000..57569b4f263db9b4df79e2f5678a52a471fa018e GIT binary patch literal 4208 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 MxrwDQ$+6obR40OU{Npt0KBFqXsbia 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_0.png b/app/src/main/res/drawable-xhdpi/voip_numpad_0.png new file mode 100644 index 0000000000000000000000000000000000000000..115bdb17d03552a9694fc6c9ff8d96b224f7e15c GIT binary patch literal 14607 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 sg75MpN2Ob%7 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_1.png b/app/src/main/res/drawable-xhdpi/voip_numpad_1.png new file mode 100644 index 0000000000000000000000000000000000000000..4d8b7f5cc10b9bb08008007c0da83b552f7cee02 GIT binary patch literal 10598 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 G{Qm&j{$XYS literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_2.png b/app/src/main/res/drawable-xhdpi/voip_numpad_2.png new file mode 100644 index 0000000000000000000000000000000000000000..6b561c468652c2c924e5ed12acae9421e4292004 GIT binary patch literal 8603 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_3.png b/app/src/main/res/drawable-xhdpi/voip_numpad_3.png new file mode 100644 index 0000000000000000000000000000000000000000..386715586a9396732fe466e8137399039c1141f2 GIT binary patch literal 8978 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 b344$yB!X(GpwgtWd7NR4%ngh5&z%1sJ(}BX literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_5.png b/app/src/main/res/drawable-xhdpi/voip_numpad_5.png new file mode 100644 index 0000000000000000000000000000000000000000..a18af28e5399e74ba046bbd78b3b695eb1856854 GIT binary patch literal 8747 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_6.png b/app/src/main/res/drawable-xhdpi/voip_numpad_6.png new file mode 100644 index 0000000000000000000000000000000000000000..79279cb998a9b1ef453e4821888e2776fd979376 GIT binary patch literal 10150 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_7.png b/app/src/main/res/drawable-xhdpi/voip_numpad_7.png new file mode 100644 index 0000000000000000000000000000000000000000..c68656fd34d1e3bc4aec4ec71a512a0f845a8f30 GIT binary patch literal 7606 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_8.png b/app/src/main/res/drawable-xhdpi/voip_numpad_8.png new file mode 100644 index 0000000000000000000000000000000000000000..8d84c96ba0ceb5bf8716c69c8f932df8cc1bc325 GIT binary patch literal 10857 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_9.png b/app/src/main/res/drawable-xhdpi/voip_numpad_9.png new file mode 100644 index 0000000000000000000000000000000000000000..af3e0e0bf371fa36f44236dc7bf40ae4662516a7 GIT binary patch literal 10035 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 OtD&x`PASYO{C@z>K{*=$ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_hash.png b/app/src/main/res/drawable-xhdpi/voip_numpad_hash.png new file mode 100644 index 0000000000000000000000000000000000000000..790e7d12e914b9ef6c562c4515179132ef9ac816 GIT binary patch literal 12417 zcmeHtWmH?;)^5#kFX0*B1BU#fw8JP#g-B z;y3O4p7WhE?)T@6aqqvAF*35V)|}6p^O>^OUP+{enmj%ZB@O@pz*kg|(L#Q@Bj2W2 zn7^-tq~-wtk`!MZ19vS8FIt4Fvo+iSM(gf_fYHLd;no0v_d-Rwz9*GXqwGCz)Kk<} ziii6~&X~@>+8)sl(z9w6?mB4=sf1xv3Sq`u-{0R;pK=FiRZjHk*f|Lq2SB4oy~K2` zZ_ciF9a^g{ihAe7E__-P_ZoWYSdX7(i`xfK-#0F|nXUQk`61^8%{A$f_JKWz( z!M)t(w3WHKp^Si6;bPqHBdT!EY>Wh&jKtI|_P1{x=>_p_1brjVPpe;Ewh*)>HLD*7 zJb27eCDv;Lki5^rHhp$z|4rgC(K~~zPx&ybSbSR6%fng z@QZ5X=I-vmGo{_(rRnD-f|t#1sa5KCr$0AVCXaKd2kBZ#RX zJSTUI^GW9)0d!wiBDGi?v8F$7QN@NnHjxRCvBx?hxMmiEr*TQxzlQplY#_wq4WBw)w->bgjutI zdlCCXPWwA+r3c9u{qg(9v+r zxrLa|9oj!G@edHszohY`5VsRi!SEZBUypb;LeWnrU8W|`MBPshgok@(7G>W&Wlc$y z+iW0f(r(xS%e|BOtlRYHMG}Jimu8h5mteY>w!kvcLbhCq4c{r1TKcvE8~l`a?ljt}Jr8J>)Il{AZu`bsjXXMlNU!lqBJ) zjuL$*H+Qjj8dKBpUrf|;VTY(r7Ey06jW`&^%w~R%Q!e^Rc#Uf7$-Qj@*~1d;{h&t5U^z8IL)PP0jC|6cc|Wi@eL#Ho)e zOy~QOziA_L3x9u`N2rBvWp7$rORS=~x&ny0vqL~j_l4b=QgGwWNKi7zE7wuP8w?)4 z(EVB95GUF}h|WUDV_F;8cc(mQ>-*1tWuSCkF2)pg9jSzBO@kVr@BUy3P0G?n7w{VL z52??5l8K)@0u97oi#k-9o_d-F(ndHIe0D`uc@QcLw=Rj>`moC}(sF=p)L+Bq|1pjo zTO?04Ek8f$it1a=l)gk)y7G_0$}|-BtF2_Ka0z`{nNmNcFNSt)l?@|?w)Ze&FCM3$ zFtXPwT~{-?k#$OX-%>DUu6*p&9-lesVmCB9OLE!d>`bzr$Po>5_HONOF=%r)QAvJy z9~j@L@AdM+seO?U7)LXP4c4p(YMl`8*%CH+S4!PuJ1d7NR_B{U9A_ z^P50YX6YFlb%th;oI!u~0Z~b9G!(;zjRC~~UKfF;kY%dFv-#N(yVC9x?fYfC8$hq} z6!lr1k1PPkvUIwAaar&4639+OF@Jih)v08MnQO9sBW-0{ zyZD1EF2Mnfs*Rj5jHX=>{#;k2HCwpBTN%aG9<^)|_{sFGacvl+7=-GshI*Aot8J2C zuW95#R4$|ZG=gHbYJQ@?D&g!VmW)9i%2{u`JKiKkT=EougIM;c?@2v^9+B6{TK~e7KH-(O4My;podEpSGR- z8htOdGJQnvHD2s{hlSWOdb&!LVPo0Fak3tV%uld^W;ucNfw#+@RseAUAYUpA?eE9}4CKPp`c%c!=mWl3atdvh~Gv*cTev|<9uUFcb?)5M9Zi2GoaZzFU6LR1ZP1mvmS-v$JLO@-2 zmKT74tvO-6voN68!-l;w1Iyqu?c{ummU-l1(F`Agg3~cJUAPHka|`ILPhM zjp9%A6>3in(jkIpb0&QZ5KEcr^Bj8=XMDpBvYKmyz^xJq&X-it$(`@n(}XPA8~E!- ztEYEgxhoO-1BKof<+U)`{M6oEy&z#U@XV2&QmE6x%J-2cTw^zJhQ#5dDX4o9xcb2R z-qqHfrpb!LY!N?Bz`X01k(fG$v4#ZkiS|Ymhg?0`x{m)9fw}`*yLmE@SuY?d6;!}S z^tufCls9%l06lC4z%@Y(j3S(gFxd=Nky`{l1d{dlhISVq+LVqwkCsbW?;7SmE{J8F zolvFPVY)|1_H#NZ#%U6`5y%-eur%De0@L5DFA=Bt`;joV#Xjq$uV~8&l5OsR&uYB4 z)Qr9JstX#=xVp3`m&LOGQ%kF-ZLf@#EcWv_mHlO#l(s{b8?(wL)Q(-%0fs zH0n51=_%==wKs&v+)S1^`@$K*wt z21x)|RajbNJy2K|=?FeJ)02Wqy4-cF0yH@? zqEIZI?doF+^;*{-#SrpMln7H1NEm`;XV`ddI-OcJOse1btI`_ziwwQsUhQ>@@cZN}{Y_VNgHdcL@r70a-BMi;5xFN_Fl5}Y< z;cW`O)L|<}hZ^q&hUGmQu}TjzmF6kmy?@3D516wv^Rfu0Bca{YaBlFDAqL3;oOL)W z4wNzYHJwbu>K}5d3*kof|5eWLwDJ_dLO zzE$Y!YLc*pI~|A?b)lkZQ^)aCy=+S2z4Hh#(4?03)Vh^`f730KK#wQ72O>Q5@J6l{mzA-OKuFJYI? zuYVZ4q)5H!qd6_V^E0@)1U))co~M{GwbR@6UN=QS;Y7%CvmKngwb^G9t7klk2yVBW z+Iclxj8;r4I{NX)!L0)?9q+Lt-P)!--1N$Uf^X{wh44Z!3nFR^V*^9g2&>%C zzAFnSRi{6gRe`~@!D+!roa*J9hRL5m2Y1hAF7CA#&9jq32Ahs zBi(aUh#K|*Lze3KSCv=EAk<)cDYn-%Xm(Y4yy5jW>7ije$K}pMb97p>IrxCioK7MR zPTRmT8e0_yFX#w)X4LnyF9syora_uP1NUByI=PBD>#Xat){;&mbXu|$b`Hv2`x;OqqYvek0C5}6WY0%0*sD6sybCXzW_O7+;KrZxbJVI?Q*^LA zeOf<#jd?9&3$iuEp$YGZWydZE$I>jjI514D9N}1U8y!g4VO_2{x2M%IBJgRk=hYk1 zRDTgaU>~Iy(N|ahylRMi>wt_ecxd*TQ{%&4>`B!#Kf!Dux1P(Jc5d^PiL+Fl>j_k! zQd@1MHIWh6aBbvL0Djk^=-oubL!vzEkH&jVaA8i@tVMV(x#E6N)42TyZDkg(t^{0w zC|49db%Oipo$)ijI>S#y#Wf!$u-VKS5L$wblJG6^oAHKADkq z2t_&r5E_JDV?e)pZ5S<0Yq^?X)SS2Cn`~&s;k!UE+lfn2-zBMogQ6zcZ|ZN|@?z<} zeQ?&?YhxIKd)HCISR zliHAdB+=j={0<`e>IfGlA7i)tLWw}@m7!}{kX&U5cV4UJT~v7XBUOJC6i`7iCg+QF zbHV4;>V6E9#%BJsifEELcFO{&Kg#%75=1-A^NR!`2cg`NmP0i`UCLvnKHNp{q|?+RakSGNC$SC-U>7Z;TAo%D`g8-dI0aYLa99C_dn9l z(R8x+Q_JzyVFep0dVk#I^sl4wXAjyka@vU83nLnL(7}6;`9YwYs#z0)FHLOr#iIQ| zTwI(|yII#A4)NCbT=a@S(Bod=bIJ@=a>=XIC|>WaT<&}-GtgR#UKWdKJN_eaFO~AA zwkIfGo1W;&@yqTD{4hV=_75S&4+tSx;>TocN}z$KmpfN;N>_v$u8?*aV0$kXi`^GN0nOQWRXv)nXwqJ<0L_Lfjr}XuPzJT)T zky1*7Qs)NUI}|L@&HNFZn0U=}?I`9d*LPTY{-^jshG~~L3`?73b_6cpuowsMw8~Mo z#ZYkNAM5z5n(q$E&!fi>Fo|MeKWxqLT7r@U^3j>$kG{rgiB@O{-yvgDE(?p9f?^Un zBw^TBYM#@Lb~)mPZn01+33J>^o5Db~(x28cnQQS=KT8i+_7y!byHFNw&7QlJGTRd8 z?ixSZic`{1f2yFWB1t95FH>=sqOgk#$D$)es|KqYJhDGm(N}jUo9%e}DX`Ns?k3;! zZQP11IPIIi{!aED>Rjd3SHeVmb-K_JoM6L)#D`NGL5bJh^AIZx1W?7X~;O;tOsXbYLX%&BBoMb8~ky`%@DG^g} zMleS1o+WNtK~Cuc@pM@04t70+Cv#d-Eupl>!pZTYdX!sLs-?fbrC?t@?OUbUe8K1d zwqatvudC_W&4 zSn#g%+KddS!@u>;ckW$z*=5nnx&L7AOs(jKGz5YG`0UFEyZX*XUcqL zIr`~R{o7m2)F4YCZ>b^W)bAfIu&zC)MX6)mFYc_gG(gtYU+6z<2G+0KK`2W!#moY?Tz%(dxZc`lSTSRGoqZF!p4Zsg>yh50V2KhByuAiYsQ$OQprXxv{i#b`gY^N{j1gG^uj@GLDo!EUo(emOLVM5)QT|2mo$r> zd+1mqX!=G29TVv9#TA|1>wgjQRO6eNc!7N$H7}t+)jwJ(#FFIfa3=*`fUe%7@_6Hh z=q;}-TtoZ;uG6|(m=FlM+;C#s*2!V};mV!p)Zx*C=o!}jCr;{7QZ-~DzWMFQVu5g4q+}VltH>L&D*~49w zo*p?*`w#mZ5vr>Hgm-fLn*}5v+};)lZeA`PZbwJ%f7Nhvm-R$~{B6+xs^O-CJbCBV zg1I?+xLU$wJz-An4F3vYW%*BigomrcA9t)QxnT}4N2I75a#h}cTT)(8RpXx;zbUYR zJ0kvQA<6zXNq4yQ|B&@>w*8*@i$}?!QU@!}mYJNGVlSVHsyjkKg7g%81hc zUSHVC*%EFg{MV(mg_R%_WWmWV2o>T4^I1YTg#-nxIH4dgga^zA69n@J{R@<$lbgGR zlO^mo6cU^Zj>G|32w3p*K&?6Xtsx*zFi4P}(}ItehZ6?1fI@i$!4?n@-@ibpyTXxK zY2om%R{e&uLP9|W1wcYpmVBHbs1Oe)*pgR}(*gn(;sk-Mz+fwwHOz`%@DG%grLdf{ ztD^-noNz}A8yGjj$>z_*Z^DHoH55hZ`M7xgXGFun!rdCFAWE+Sck=N5p9vkfBTU=f z;y0VT0w6FSFN7EAGCu?&^e@_aFjqHZCjQ3c<>BH3|55)P7Gb0_NMbF1=P454&p6T- zVQE*Gg}bw>jd!(*y}za`Z7rN^V95IZJE8tD4*y?*#RIVr;Irfdaq{!>!8pM% zFh3`h2gbuGWC7-bK%kbE5E$&=(cPS_-MuVaVUjjTPm$gr6X=gOw7@@9vi`fYmo4l! zPdt3QoX8a51?%wg3-bvHgTU-Oe8N0D^xS_t%>BEn|KYI+_y5I-$RB}!X#+^Tzs8X5 z1=+2*|Jkno=Il3({~up}$KwCv3P|YxF7hAo`(L{LOV@wIz<(tC-|YG?UH=gS|B>*2 zv+MsET{!>qzyota?t;9Kk27~+HLJ)+Axx;Uyv(16BEY3|%S+@HHbTMB4FJF;{{2P) zq-T;N2OqdAs>(jt#CeEMO^>>DQVam#H7LqR>Ub|4m^wOW&ryh7OwIi~Z=hbaABYuK zV9?Q%!77c4B7c3i^AQexLd>ITo0D4^1c27VOAp5D;m}|?hJq)BB%Ioz;KqhY^Fr;$ z(gY>_k3rxZU2M9@1oCG!Znw8Sw=O>|kDbK?zoA2mwl80-W^8Zy`;GaGzZmm*gprKx z`{G4HU0vPm{l)7%VCSibIeGuDtG%knkB3~&zH9Q5UzksB^ike3hK}GoAZpMk8XX^x`Y~H z2@4Bf-9l?@rRF2@Ap576Y!DHz!=(~4|*x|a9}WjMU|kt0r)g~J17rWSol+Z3YRiQ4#mzSff2j7^h%y8R)G;Zxapdq z5zL2|fQybQNe{$Cz66S<;22EB^A2N%6?*s=@(C);@dBYz7C(>IduWgg@dye&{cPUo!gB}o4zQ!^-QjYJJ9EN(m@60{ zaq8RF(YJ{c-b26-Lnh1GP!jt~CQcHNM&!T-fYfIaY`S-Nct;g(V{7{fjqaPK0CNI% zvk6xeRuqaP0dRWgf$Ee*GRz@+aB$EOug5?GCBA1p;^Xom8zBjr*t;Wqoo|X$;N$Dt zjc8h+S%n@eiF$*P=*GEkG?Mq%F)^Y+MtmEPhgNm-G;4@VG5_>+pLT8TVcvf3{*A{y&EpE8_kZuC$Sr)o-O{3nm3b)!P%lO^aRN9_}FNPWC`o* z>&+X|{lvzt`O-dau!ImOyiI(Bj_OQ|WR{=*9DTrr8u&z$MBJ-onU^X%IXRhn2mCSq zS=QLX&x6GTq)j6uBge%|AOK*^C*a#!*DE&blxr=44A}(;RqVRl4^?fPT?C{(EWLKA zt}9!PF+tIYn>MNsD$W!P&C@E`pBi5J;?Q{E0$K6iz~@aF#ui={j*!cbl>0HtFznR& zV~BkknW)*5U`I3nvJs?@GB7Z#re#&o7^II{f1aPeRF~Bp<4$CWR}eYfDS8(GidUFE zkMDz`WMyUP!wm1JLl9yyuS#d^9zUWH6InS6&W5-^j|OO7-dJ;xNpmTT@8xfwh_RtQ zSIIW25IDMjAcghx<&6tsce*5e>q0&)vkC=J3RV?=3u9iQ8EqZlNOvlBHX?gcfgO<} zqk&2fL?KMff#;c|zqe6gCN4}_msEX(tXm0~6EF&uY5*mB`|(W{fs_W2*rKbQ#Y)z=%>a>!FosH-x3pDELCK>>(BUt{W(@qa@=s$B!91{(5z zvY`M1`=F8g%PoSk$nL>NbdpE3hSvj7or*m_Keyn_&@G!GF7Cva!a`Qm537aXps6Bt zw+|*)*R8sJD_85tXkcWq*0zS21ImSrTm zq#z_Ap0J&+-2Uz-_7W@H(%Uc&6O0pqj5j$2#o#)^q$6Ot z1UOU&e4_X^aPgoL*_9AKz76uHzfaqBTy3T)7Ww(b(m+I8Tf5&ufIa`CN5UY8$v|Yn2Uz$pCy+-R{a+-YM=t868Vs&z@j_$2iu7jDO{#s zEo)K<`70yyu)5ce-??~quUEerv=Z3US>U+xYmc`kILp1=Rm+5|4{f^QKDW;U+MSPP z2(G^FgL*`ReA}L{onB@WqgzN`zn~4FO&E;G&(EjHeT#$FQ~X50;TO(;%Om&CL$=`e zaa2M?CS<`sfBsxiEYzaL^*5UW6GX}cK93Pi84hiD)MUc-s>SOFwXL9aV8dt4znM7! z?uq2v80p8|%-K}30mm#MPUzF!0p!i&xLODR$T6x2-r3oC5KvWE*tc`yos9ty;gcNH zjDTU%0|^rYv6LjxnQW_p7Xeb{bfNTH#$D)5SbcupWJ4%)t@lnIhFC+VH@cDpPg{p` z;Eg6+RctB4sNI;yBKJZ7+Hg8%*{QRHVj?%NDw^V3>*&6(XQ=)f8<4~JYz_P7n{UsK zS}nzpJBsiU9Xo1hzys8v&Z~`qxce8mB9FPB7jT(0Yh63e>QG%YQPPy#)Ki~v??kp49YmV%&S2OgS0IwMN@`wX5J@SpEZEJ+owKco3`28 z&Q43sgzSdd3XB0sO1UYTq_Pxfs$pTo(@srZJB~PYiw85v)=RlZWy_aVpTjkm3#YVA zpOr`NyQNLP4?qzFIXaT$Tf6zr&|Ip`(vJT z^l#P}RdBUoI4ZuC0OjTW+_bJUw94x-=K;uSR{J2$k-)YCQ`S5Z_g^>uezGw@9Zc(| zy8Xqxp{)uZqU{grDs^+fg9u8cdZqFNVSkL0NYL!g0(gT^1)hp^7G_D~GaicyOY`5nSNO8!s5Me79YQblCaR+Ou7=xka2*j~a zRGvz)L?5&!ao_WqUYNoZn3&B4g$_NKcIn{oFq=8}t4F9hYSv^4)c`(gRIsjmmlfY_ zo`KA2`Tn^toh_8(LypZ@W^Te@f$0$$a#JJU($Z36hdw$w>Nrsry5}zZ;CH(&)WS@L zHSJL~S3t7HKG7p2+g`O16BECr4~EjFsqpndC#pb#Bff1b7YOgLu&{-wKKd9obEaWF zg`z3dL literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_star.png b/app/src/main/res/drawable-xhdpi/voip_numpad_star.png new file mode 100644 index 0000000000000000000000000000000000000000..5a2649de4f42e33f68a270be9093e010319e7df7 GIT binary patch literal 10163 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_pause.png b/app/src/main/res/drawable-xhdpi/voip_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..e888da937c47ee8cb5d4d7ffe25a8172ffd247ed GIT binary patch literal 830 zcmeAS@N?(olHy`uVBq!ia0vp^Q$U!54M>*SFkE3^U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJZqgtY@)*de>EVPOg5G}l-C9=?Ig=kc%!Okz-vrJSp_4=B!4<774 zx~jQ*^E$qDO$t94A3b}v3r@d!PR%gpxD7o#U`Y1_l zx7N2ki^Q8b6q+`i(Mk53H0g!KzIP8FzmDxmFSYyr{6%=N7;}x%M;&9I*%i$dRhP{y z-V3)!1cr5z3aOW~vPbrM^|O{C+we zOP=~!EPZWE>e=c~;%vpgP8nU#benKMX_|s#OOuwD#$mUfu(=}Knz#DPKeWg0chPH& zQ8g&iS-`}*={5WR=KFV}exBsi*OXx26)*EVndQYzRw>itKkdrq9OHaY&$jN%wEaQs zX?C9Lw{CeMXt04dCT6=yD*ui;q zl9j8U@D>zs^oVIx?>)Sm`R(4n)yr<)=XX;qn%cI()BqSQY)RhkE`yrbIAjbYUuv2GW768w#WAGf*4rzFd50VX+Ac=6 zypfyu+Eqp|Bp{D-yTeX7jlFk$x-|Amr>y_B_uTD2QZFYfgRH>-4c|3vWg@d><33lM zK5_Z}#ixZb%d*#&<`t%&{$?xlb52|Jls~`zpF8onSjx*~PV}9dn_|`fKRoZ6zxEAB zR%v@Hh~{!^Tp%FQ#N?=`!P2_GA%La=P64$)B7a;v)wdvQ>c_n=)=r(vd;5FAlj9cg vai3>MR(5r01j-#R+_h~NGlpM50$*N;vsk$Psww`q5XAL#^>bP0l+XkKpZiHz literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_remote_recording.png b/app/src/main/res/drawable-xhdpi/voip_remote_recording.png new file mode 100644 index 0000000000000000000000000000000000000000..3e4e940093ca57f4eb605944c60461665c6e35c5 GIT binary patch literal 2673 zcmV-%3Xb)OP)EX>4Tx04R}tkv&MmKpe$iQ>9fZ4i*t{$WR@`f~bh2R-p(LLaorMgUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwH#a9m7b)?7Nufoo2gm(*ckglc4iIW3rdb{1fTr7K zG9DAtnN>0HiZ1jchyX@qW*KvmltA0@bq^n3@1i`b{kcC!kD9d@;1h{wnPJ+*8^qI_ zw!wLyILr#NN_2;D7MDTPrs?;U$G)KgvaeK00?bKL_t(&-o=`Gj9hgU$3N#c z^XqKcK6aNDrKp7>-GYfx7L>J6$9=RNSAtPXQ~D5y&jg|ptPP3=YY0%nI}k{0rF408 z36X@l-QC%Bpfo^CjV6M-KmZ#pku}=K(rst%y~jU#TkifkcV>4xvps+Q&hOrPzW2P( z?`9l#vy`JY?G8KRT z`uqFeu+}yM{SNDbAP9b+PN!?e4XU=b_I}rgMC94}`uZz#x!ed-NZsAt2d%Xoz;o`u zPbQQ1jLZDOu>G{Fq5AszD<|qdx|&%eSVX`)1dCF@un(U>>Ez%<{j5+Z>;PKFd_#3b ze}Dh8v7o}lI_Tt~LC^Cp9j}IT9p|8|0Iou~BBpHeQFZ~(W7#g7**D(WvQQ}e30N4- zV?bjzn>|p}{K85h3TPC#dOU#i8tQc&cK~l8+>4MdQ3oav(g^qB@vg4re!Z5u@tC5B ztPD%3W4(+uMJs zs<%a!3dv-0;m(~q-y5ST5n1olazk}>b$5>g(DzVYI7L7JbAX+?j-Qp=m`o<`1m2D; zSa?>|!#&>GqiHdQH-->2)ylq}&r?@3jCFS1mVOeR;HFu$M*m@*@t)+rTE|>F+F%b1lvj*HMam~Ryps^At>!%s=6&~ zcaFz}=EaK_&osX8f7$ute=F5K=sL~;ZkVEi;zzoMv*VcAZ1x>sFd8sYsnq2r2!by< z<vrzYsQQjA395Cn6MH*LBqqt@q+t| zo+eElj$@6CjMO?yhYc`Beb(ApC0@Rd(?kD?V~mcDy7C?}z_1e%KUG3ggB+WCPbGOI z;v=###+CO2BO>kt3uALaCkIhpo?=ih%4QCg0Odx;A)~5;&f+CyKJxq&gDUsEOB_oZ zGOGHTv*{}(Ut!tq2A3+*VFI2lHy(6Ay=taSoA#39C!blqeEGStCU|T}^1u{Jkj~q)YNQu#!ly1Ygd)5 zj*m@!ZmL!vC6AvpY92!k4GpV-`O%c6s$C~Q?bxy7e}LN>-6-AkCo1pe;{(m;F!K&axC`?6{Q7cI$m2W)e6<2THjJ`NwsPkL)8Rb0Swn>Eh!@!*w7RnP0l-u0W(sD7d z!O_yg(Gi3=YOPBqlY<-^<(_C7t+khiQL-%Q8qOxc3Y4pWB{BV%2fT#vJU(5e!nQN( zn+@y%&W|j=Z>?PvmG7iekM{QV6{@=3S?;&ic64`lADxH_^m=Aepb^770#X=`60i@; zP^r?V7=@=EJAt&r`ertp9dpbhF@NL{;0Mm~bJp5xCJL+)6aso0_`1V-D4WgRcybyo zW*eUO6DJ`9e4|h(>oSxqxn#zvy05SAFH->M z^T2lC(@p?;-59eZ?v!52Is<%&@Htnuzm}5Gft_j2T2AnHGBJ#TL z`^yFf24Wk>viB&KEnC*$`~C|YyG-TM+rUQ8^Pa54J&LBLCfm@^usTev3yN&nol2#y z42uOO4Jz{I?g4&SG|H*IXN=jRs@uX*@Pw$PrR98M%v#`Dr}P;U6s)y3kH<15dN=7? zBJv>c@et!aKm=R zKt&StufvdUs~9Kpz{A$sUyV2MPa0IjWab-V)&tj5*#m=7;J3i0Y&N@hGB1z{M5?;* z1aCF4n8}T$J>X>#=~UIO==Fiq0?Gk6!&<^VH**}wr{Bpk~fzeD?K f5Cpp_d6VYEX>4Tx04R}tkv&MmKpe$iQ^l`}gB?T^GE^rEq9Tr3g(6f4wL+^7CYOFelZGV4 z#ZhoAIQX$xb#QUk)xlK|1V2FB+?*6$q{ROvg%&X$9QWhhy~o`h82bYU1hh$130%b1g-1boNWJpz2ai}Ec0bAOIrHES^-AQI0q!?cMvh^IGg zgY!Odm=$D|_?&pmqze*1a$WKGjdR{*foF!zRC1m;Of2SGSZQHaFg4;S;)ts0lrN+` zRyl8R)=CxDxF>&MD66k5bDic8;#kBIB#2N@K@la`)K?KYDHf8nAM^1Kx_*gV3b~44 z_MJOu zMiLJpo2LMcka$!)6xqfo5@gWKAmL&jUI9v>P7O|0?YcOoX|kHS?qD0&tsN6XS#CmT z?6!6=WG#b5iZydb8e^$A0fcQHvN32RBfzqdWDL>>=H7eygMlgngkGb$kMpfH3znoi z`<%VMyU#w4y$`{pa5|l7q9|q(Q95HRJ^F7dfQ}ylz#srg(Z^Z=Tm_&4XuAF(q9(>z zEn_Ss2tsWn5;+|V2J6kJS29B<3kwS;S}c}tGsb2Cm=0hDfRq@EZ2|BxfTKiokTF&% zNm30bluo^m)oLvUP{bH305D1;O*;>uf-zRg7<;d*tn4f<$rpyQc$K4q$QA3+Vx$ z5YZkY+UfK8j&VZ88T04QAE&D78UQN*Ox3w9PB6xHD2noHJ;z_VBh<2G%PcJ|EeipB zA3!kxi@`<>1;9Qc+LoW6zt`*as+>@j6wQ$)qVnM6VLjuY5jVD<@R{ z#708_JOyCg0L;v^ie@5uRTRaa@7uTU8&0TRdL*mWx{fjSw*ZEN1Gu*C^|BxcTeRx2 zv`8rOcs$=_jQtG2IL@KlE#YW*S1d-R|i`^c%)lHs?T0a8yy0mF4B- z#|K*QfX6Eyk7p%-JpjgXyT=S81wnYk;c(Q4LZOch>7krXr)aTQUM8X^xxEJzn`Bx3 zaWEK^4MV8tw#nN79^m$k%kqMsF}6&Sq>H*Il*i*4!x-BO;9K0Z@r|nhER-ba3*8dR z?RHNE;OFs@W;|8BEX(fl^77AOB_n3j^?CE=WdjIsGHGZ{fw44;#j@Yy@#N@=P=$qs zvn&?N2LMKMGinYa7-IpK%XObFhCG+cHG_!C!A}Ti9T!zqEi5Z5tI{l?TrSrnB6=U% zNebZT8jS$HBS})V<_Hx%-+h3`OuEAvRaNu)D+8xrVyOK5{K1N%lyEZX65|9xcrSV~ zHvWXV=bn44cDwzzL^PAzMz@#-V29J`6yr{);lqc&3}8ODiSAKswOaqBuV2!q&0&wn zvyw6PuiO@fL7v0m_%sv>_4<@TuY>YvrtXIT61g1=h0BVf%cmtGBdPN&lb;J-PU z42fh>6yMIv%d-y{p+r%93D+u^a2ODbvFxOzq>TeIwX2}?AayIanuTML__DE=jY$GL7zi!_JB@p&i|I@;$UFPdO* z68x~7sX;{bHk)noof1gzIE0C!_#`J22V*QP91j0=pFC9bNJ9!n*Z9SBtC~G;HIBtRAilTh4 zM;iT(X+|P z$+-94d*ShT@a30Z;>?*dW?4o^s;Q~jaYqjoz4ogXCa85O2m;ouS%VcTR`i#Wv2*86 zY~Q}!48x#IL=*ge|G6$bR905j4*?V!T^k7r3D~e<1B!}@`ZfNVHf%`kyk*<}nBtxNEaMpwk@ z)vK{|>C%|W$jHdRl`B_p{P=Mrt8%2n;n)=lg&J?^p@?X?(aGd+IPmbp4-bf}wQJWR zGc(iZUTRraL*8Pe6Y9}N9|h6C9ETSK0gpZQn9)_b_?ConI-O|%3|;1V#*7&;i<`QV zIeq$cBdayXlNKZsOatChQ*XAQ;?955Et1=NlD1cGVaC$V{Gmp5vs7Ta3Ty|bKXT+qL+dvZiR5=2@(h3Bq1|qetNg@7!`BtL z?*^e5V}`Fj7Y>KxDqm3)L+h7_X51hY5luHbp;}s6;wt~rrAtOv@ijt47c>ojeSLFt zb6n-0J9p0LDo!mbDjF?_qL>L2;KYd&@tjs0eUlgfAj@*50FzWxX=rFbZEbB_WS=^9 z3XP48hS#wm2wF6-rkOqk`hzs zmoYX$Afmg?La65E<^ebEJMX*`_Zj8@;w}NeT`&=L?AU>(rltXr)!5jGojZ4$Mz=(C zmjIJx2VcB+5gRsafGo%MPG?z`v2o)@T)uqSG`eMsr3wIsz+|YduEuMxy%tj$TeogC z#Zo~h3>5$*z=Sw-=upgtzQ)Ez96o&5jA~6Fx7*zYz;2cvj~+c5D^{#PadB}>5538< zj9@T`x88coG}*)e+Q{v8%P>Kfu*2cNs#U8nckbK)OHu#;RaNo+`|sn;H{V2EU7gX@ ztQek9Hk%ErSFgshWy>H4@hf7iC<@+v_g%dH`s-+GYcn*V3_Ik_o;@4u)~)MNHI-PS zsi_Iuw{M5v?>C~#+sN&9e+^)$k$RPokbozictX?JCV@Zzn>KA4@Rj=*qeTERmj2UAj^M;>{^a2dH-Sy@=MYLzbAE(k)cz!=jdp&SlJ z+^VU>8q1e2M@B}5?%K^53kj;Key&HQKmPdR5Jl0DgtAyHSi5$u9@~9dC@U*FtGAin zq)C&EGR?j%X3d(VH!pRew6yg6_1)5AdTRMY4?P4kV9AmtdTQ>+!2cJ^_(U(Qwpy*o z%gZwbq2|n)qnlx!F?Q?*p@`_9UX+}hn`?$jCIGIjqR*VEk2)qq^oJXSVvJSlMac#) zfO->jw9i*@gHVzr)c`oJhgKW8w4jdrdsdR9FFM9hHyEaMj2>;S`?Ph^-~HGB)3FM$ zPY*3mNl7skq4dyS##rf1go;EW?*TB~)s#92b~Vo@Q51bQ5h@rA)d%5np}vmDY#k-`BoKqwS46`?fV-;X3os=h6uqAqNQrU&dJM`B(%MJteH86SQ0k(QhI_DvqE z^AGfJiFHCk0&TZ@w?PolcI8dX(QXlQ8Ai`sWf zlC<=W9tr?F9?v4i*dD#U< z`g(*yA)GvU5~og`((SC3G4@~}5ZK!-LV3Mjp`xPVGXRFFVV<0vjI^{gj2JNjsi~l$3-)g9gE7vmr4t5rQB$Oih^)BjBq%NNF;*R z)>d4-dKKYt7%eR=XliOgb8|DAnwrqi(17~-dMyuKy}`+Xf`X}DuUG9Bp`ydy69Be= zgX3oJ^jCpE;1{?0-EHgJ*zNZJfB}*~Ir<%qZEbD;dAncUmQW=nC2d5snd^_ElXu#5 z;J|@!j|l|;kx1kf=sdHSqhE2My}kY4y844I2^9yabso&LG9vhq@|2?YRx zAZ!6}j;qu3IMdeFcE?)WcO;b0=W7M<_gtT*_f9|VDt5Z#vO(v+bGzLK0Nl@2Yf2oD zBq<-=IDpyRvbanTgeL*0T(_o!A_&6Ypqr<8yS?Fc`t<3u6DLkgzk2A<4d3g-mi(dg1qx6$J>9yK*JryUMQ z3K7lbIydxC_evlT`1hVZ*Q>iK1`QhY6X>YG21k!^Oi`4ld-GtglfLH7o0n~|Sbh&6 ziR<4`xCG$-&S%AA>Y)IjyuAE45&Z>^r;Ihy5z*S-lBriQRHu0A(}@!&4grwIZJ~b< z(Mx{6|JA;HN1yNeibNtm0q`C-hwkyaNF?$PeI{jpv?M=2f3V$d-wz;%+eMc+BFpmJ zo-Pr@#6tlE$k06^&6o8AX-hh5Wtt*M!G>Gfa1=%b($a) z0Qmj>&jHK_(9CV6d3+6^s4p|Rm=g*BBuP5T7%S$a(lnZh$RkP8k^cVTnB3|d2m}r? z#^!*Z^wc66EEY?l-|s&Zlck4PwtU{ad6O*`iyy!^Zbxkd;EXX(AP_hiD*-XRcfP#5 z{4H2A|bxo#XTQ&cs^KfRrey zt*yN}e*E}d7KJEpiyL~Hzh z|E_@)8kd^IE|=?1i0C%}W^lX5z%4QMF%hlo?=>dP40)Xq3WXXcPoDg1RaLD3z8x=O z8<9&yw8d(*uJHMMaoMqmyCGdJmj}Qvi71`(p>OXgfFDSbB*n84ajzQ~3WaK>O`En& zmSr&-unU|UJ%S2gn=H#q%gV|=jdx=-HPpCVuKS`Tgm?l(cZ^5$zeKdo=kp!bOq;Z* zCy&STUB=i;048wS+!AL2{8W;p|ANMjU~95eZcRO>krlXB zT4;(%h*U#Fn{77Rwvv*PHeJMZdegAi>lHru;DZN<==%U303aHSL^6Qi0oYbhP_XY- zrJFgSVg!%JGe%X_?-9{T0J^^2I=Z^O!(y?#(eeH{-OW6W@>;vy?x_Hl16Yh}**9A6 z8v}5Ji1rZCPM^>BsS)g8$Sn`2(>Y2M#RWt(KYDU>tR|FARL>aO&lvM5ic%U31`U_c zVTwJp;^N};NFWr>y({9QZ6%~z^ zWjT|G97L4P7@H7XmPqaJ5u=~M(Z?=DgJv1mZkf3Lxj;m9078thkSxomjOcy3gZ~3T W6u|dSg(!yr0000zTbIf=Jh=D!^|@?&of7?zOEKC6E_n80ASXBs9|`HssDtL{#^Us2TTG0 zz$mo3y1ur$I{%ZWp3Z0wCjbDF6q2O)@SO@zAL>ybjN^iEjGSY+E~kd3&jgIUK&_m^ z!dJZY=)Wwf+fdPj2>0&p#J-B>arHHqYa;lmz%Lsc#hU^`f5e~-F+a&IeBbsJ_m_Zb zje}B^m`h_m^T^&pdsqj|uf341gE5~^QldUyUK>}mP9;Vs)?!)nrcM_KMV24TE`5*f ztsfD`d}03bT4$czuE%2^U@oKtzA3#G(VJ*xdOvhE$J?BYza@#!1Yc&2YXzBPWv=VI z^iP(na#L9U51ME^YMIk$kDvNBgyI_66dtxJlaJ)$d_#9pfIZbfg;hqNVY}Gu;aF%x zRM^;0#G2utF+gyD20q5$7W!S8x>%i-%SB0!Fvvga^HO^8EWtY8#4G2U!@%oYz#kVf z+>?qhwmnTbf~)_>2UMIV_4%qlIu0M^wIvU|(wR;BEfwX#>91Vm38xKdR+-6~u?*oW zV^K*a;+6pn(WHk7Z)Ysq?~N_Yce!kzodKQ=Kdm+&_dh!C3%xx;OXD2R=|sIP>>MsW zd1&r^-olxG0#t)!`=1*be6)2m8P+deVdRurbH7jm09+zzYp5Fe&+X*~`LIv%4s>;y z`$a@d3#eK)QVY|H8kt~sOqkdM=?KQEb8um$SGyoaoVIiN=7qPCH>x5)ZM_ZAhRZaC zO}I&|;Wuz$&D7%@x5jd6RV0i-f6&7(^z6B-x*rJS$mT5D^-=!B=*Wo!Qf?QfnHZ`qMRWf$% zJPpTV8nI_!xBl93Gs}56=-fxDXjXE{LFH9&O7_VkOFV!ywE$H0NKq=({AvLIiT3H4 z0vP+}mF!;LcH@;Cp+a5#2p4WuiqLnk&dvS}I1dextoqUq5@E3O!A^Y||DecBLX2S5 zEDj^*=I}ZKMxDb(Nz??yz91lBXIf!l!v(;-DU`A^o>^@C(^-H1;nVVjga-TL^~ZzO zGr1C)BUJQb$SQb(b6lb*<9594^95aj$&4N&w|)*uWdNDHpBk&OHzZYBL)@;;Z%+3K z*b_&*e#Zm$QhpAi4mL4s+#ty{BNnYnLWuu`a+6kWRxXdWNR=*C1m5GDLkgrWzgn#8 z#QXK^FzyPn{n5<|;=J4S*$FS@iRU_cG0me;rexQiE4Bwx3mBGSjr<8<{yFP-qN?lf zXxf%vew}Z_?RPu@DeBZeDO%+(Mwrtjw#%FtP9jrjJx+2wkmM8Jba@v3eYlQL+VPWc z0u9;s*@16{gu~9N_wvVPPWA#NJzUzhUP9xgd_;)M?8WiY={{jTTKmlk+}l8C(Bw%jkP)oOpW!4CL7cKU(a; zu^cqs2@1JvY;UIgWN{1?>?BZid1o|cVW{|IFmh)6$-r7Yvcsl)JX6tH@IG_?XJHgg zOR3zhRh`-EJ?om`x>3;dG_Y!fo|~ifa>r&Yj~fDb^#I>2Do` zy^{%jSC?JH8vfcdxj(G8n;4fzLz`75ycl*8^bj%2%WbQQrnx+9+LJoL0&lJiq$>RqoR4EvOyxTmAtu7 z?<7OM4CC9QSoM9vyvI=w`+y84p#ecS4OFmBu!@q3{X{ftA&- z7uaKGg?fWMvyBIi5jzvW1*p6c(7?X`Bdr|=uwb_)ZFzB#CyNZ7=qze}S+XvTqErfU z$nMjwcqMlJI@L}|kJc#fiA8>O&yNPO2Vke>IuO@Pc6Uje81WwbvX1F{?7{PmLYnY- z=N~_`LfsF!{tb{Idm&n}Anao`nF+s(?FIHNi6DPjN{6hl>Kg>?x|+!okvMiLU2xPotmTVqVNEMkVw zbbO`(u0e@Es9Vmz-35%4HjvK25|Yc`Bww-yfSq>Rj1Z>UZZ_n#)ma8oxDk(_klB2@B3mbWJe|m5uC}GrX>ONNi0J~N)xt6 z`{Fnk>z13N7#rjfS4hfWfu-Dzn9ElS4hpg*tI(l~JkRrfOYQz{8So78AAEN+Iaa?r z4rzLKWyS{cE+4Esku5qDg}juw|FXfaRNu?wXgsrtq55piW~no2%Z;AFo^yahXAZ0MuIbo1Du%bn$FQkOCk+wP8GkNex>@ecU;clxxOXu%4DT=XBw>KlRb z;nT0}jOwKserT;}UPnKlru=Ct?!G1i`HBDP#XW5#4sz}T74`|6AV*{LRR{4SR1a~^ zfT7;_Xq@j?m&IFbbrdwMHTK0bR-jC1 z5g0!oY@#ezK8T;QmS{|8A|;Czj>j7y)AdkQ3jjFtT6k2&!d!BE*AEkMX^WV z6v{5XCvtgucB4r3@mCcs2)(g}j=S?^-1dK@Uk_j_vb4Tmxx3s|o;|BWV-<`zkJw~4 zt!zJ&|Y#Om@!8;{HA%rUp-fUEkyJvC5y^$owuwvJUOxWL2GiM3>K3#Bp9Hq zh*~mT0;{N1m(_Hfw$<}=he(J$fbAh7sLaa=x4&I2n7frGo=3CF2_PKXHfp8xf7E1l zheh_>SQo;0hrZe8*(OTahW*W~tIL(!4BS9Rwjp$W`IV$Dh=?^5w+oe>IEEeHkzPH- z4|yt#TjyL{M1(AG*eri{`o8-Bd_VXF(buqph^{O*0I*gFYdB2P&>IJ!LdmDaPN~Ii zFv3TYA}uM9@aClaRmxHw<(`OIEysDKD!Vgx7KiP;-b)A)!gkA)lqew>m%j#pJBQGH zE}hjz)^|kY(PosIrh`e2%3VJEW4*bJ-6m8*MRo3MYC}9)!-p483uZcHvkRDv2mc14 zm@Yt!CgUw_srOG+n}slk{l~|(aW|OxF+AZ9YH0u!pSRf+mCkPojkE|(9Mpa;4k&Sd zMj{m75HHWmZQd_Tm}nTGkf_mR>Ew$Ej~bLDnw`rx4PU9<)npF3`>d7{i)=3SI4m7a zHM6vC#K+|%0ZdLGN=Gh?#C;o6f!K?Rv`=3lx1CG0{gh(lJGXu{w%F5yOM0g(K<+F@>UETedD zcjZrD&!}^+es|d5XO3`-H}9>c(i=WZ*~SAte=CYh!R~DNmUv&|0W+=V>LW`^jT!j2 zL!82mM4jA(rgrQU6_Zuy!xfn`kFrh5pg%EkA;Axa0JT5D? zgf44^4PRlH{w5!s2u1g$yIjA1l*o1)*e=Wi4K914;5`6h@i8l7`}XVbBD>I z<5_L1ID8LO%KxpE1g^IMP_C};TcIPx7z>62E_Y1*o0h@rEKgo;vbh#y;-WolHV*9> z#i=ifojwD%lCvoZA}4MDbMG~)@g|%;L#E-o&Tx{ce-?dS+FSd$h-rUq;igF;tW-F~ z^3Kw@#JW|a0x4%raN?)0$!?^tS& z0y2cE&915~UCGpQ#Q6W2WzmY~roh4-fWayn^(pDC$WQ^*D|G*7UO`a#J~RvCU@SJ2 Qf4`5mrmjYfnr+1Y0ME-N^Z)<= literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_speaker_on.png b/app/src/main/res/drawable-xhdpi/voip_speaker_on.png new file mode 100644 index 0000000000000000000000000000000000000000..07f13c9b62dc3813fb24c677dc1838f7fe060c8e GIT binary patch literal 2599 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 PzZbe%Mw+!6PBH%jae3XB literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/voip_spinner.png b/app/src/main/res/drawable-xhdpi/voip_spinner.png new file mode 100644 index 0000000000000000000000000000000000000000..8238de56bb93bfba96162300ceec55aa69e827e6 GIT binary patch literal 2499 zcma)-cQhO78^>d$uGQcg(FUoSQL05zvucwz)QVlH6)_@2RfBMqUTH&XR;(_eMT=5J zjUco}YjoHBsx9y8X&_1CT3Qa?I(5jrcKWY%GecU-w;2D+mjF9bbzja(u=_!Hu_{d0X&t*yGT z|3G&)gVGHswA80Z&}1i|1>b8T`u5r{Ic12>;AXIiYL@$5k>BUF`+&*70!ht*?&ull zYnK32{XOJo#NCp+w7<*j-lt)a(~TWSRcGWz!G02vQu|=$Vy}>oiwT4`CC$!^*ViTG zQ(3x?tks?!h@CQfHO)rBnU@6Nry3{C?XV8W*yVf;qK(iXj)reTicpAG#FfA@>u4CXC-Q)mXEX?^RcloJ<20k zmmA`af;Y?(i90g*_4ja?DCONeDtRKT;ATAYykf!-2nehSK5AO5OfNay99bO?B02a= z(bt(cKk+hSSye=eo13eLs|eIR6)Ls~eAVE6530u%K=Xv%0|u41QoEm4Qq2RE1XW{{ zO&YS#+)Wxsrk1+eWE@j!uLbjUnW;_N-zvV~ieK0#ygoe~;m@f0i1*vtw%QV8=+iX% zH}V*|j9lD8e9C{rvKfa?n6u7y%D&0LKdaFXspHV*$j--#kUe6@wD2`U(snk=2m=^@ z<%#=|6+Bs7L}~oJlNc#YovlPh9VXgYwZxdv(}w8HH;mN_vlvMtq1KDM4%?~)Lt%*U z-o|+?R01{*6c)7bZ1ri!jMqxQcz7(duDAqDkOOaM`${MPqVo8;3y1!Y_n^bYhO5K2 z7!%r5qZdu3*U9Eoj{dCe?iVi5m&#XL&eer_(%(Ne<~7F3H1#n=l@@Id;|*VTZ)?9& zchG1{snm3rM40$!jZVR#7vKIs(?V-Qnkb#_ktZ;>WgHes=2U%yCFOGJ?Jl}e$Xj}R zO#EkKVfB&LY^nWyG^6Ox`ROGsWv`y^GJ90dI7HDebz9%!GJguLne)!GZ&5k92A(I% z&_yM_A{&Lv4_SRruuvXY8aY!9da|N5VkwT-XBB;?GwT$`A*luc%7+R+{CU9S$shi5(Gt|R%yE#w zhpHX#+s+R$D2wH+4e5#7B$VW_O&5Ldji?L59^fCu56#v3)vPAi+8C#IaMfxsJP$M( zGf5cKGdi;?|E=y?jh(>kSDDVHWG_rb%-Ou%j3j44I&&X*zf!TSo~!aldcf@SC{gMm zHYk)&51fZCY<~mKoS)wNn%iMAweZ<7g!`v>CsrS?%h|gMzbdmj&hxZdny!6Vl90KR zPa_`5Wu010qHlGUkU7Z;hZWhC_=mfdi{LUgWobD6)5l)~qoj>JGNaqmp=h`Kz{7&+ z*=mMcJP;ihdJ%!(abMc49+{fD>G=K_j9HQF`l@E(c>e~7Be7cPF!XHKPHk|cV%oAS zQ~Uy1>egodgS*!tX}!L(>068?eA%cNm5uvnldHi-!L|vQLuqwK$^=*JrN470E*Uz6 zLcSr-Utb!X@+-+XoWS)--@P!*b{H>5Q8N})OB>IDz2*BdIc9g5uEJ~TE}G)i{`fav zn#(K1h5rZ3bbjTcgPh3vmb9VA^t8!Bjvq+v zT<3Uvv)stHF!yn)@g`WICk6=4HJ?BG4yqBkX}s1|x?C)qp2FxEEx3{c5HM$*b3L7& z5>nbPtQ*PubAnc)Lcu-MICCL#GQE0|^AUgjlMmSylp6&xL(_hELe5GiV9j4IZ^?7; zk4y|7&J*8Uej}EeMyv5g4%==tHcg33lFoyHA$E|hvo21$Zba(>QYq3L>v~Bhdk6D) zFGe_3A9qreIv3JYH3kR92826IT*i7pW<%AJEB3b4IO4*0t_sXC*f50TBRy>q$obusTNHUh=ifNY4x@f1+3S|F z$h9c1xab4gc4-^8cjwY6RG*G_q*`o|4Q+T5>o?r;w7vO241=pFjaBhJQM?=VXfz+s zfvd(X`qb3pPPZ1Lu4tcB>prdHzFdN0-f?wvn`I51jgCpYF_WX5NWMQm^n`wgWO#5e zcb)T&9T{JPwlIc6NBKM9{_= zUX6ac^+JQE=Vk4a`a@*R@!{qHq%wW16Vw;CRg7`^@Ud-pLw)qPz^dQIUIFuLNKoL8 zls%aE?#ueo3oTOoh>N88_J=MWb=Zk%{^<7hG5V#<>>Ii3@5Eua3&l@fA7(rrdIl}f zrMPe~2OWR5YietDgwR^bflkWSthlVGCi>cdX7Y}`Ru*UGeY?O_-?5j^?TZGwcqInL zHXB`Sh}H&~lFe|9XeqQR5Ee-jeSQ3AiX@7vN~w{hP7Z3#tCW`obSnc6SC zY(x-2u1LXht*baOj@{PYeNQJU5l7-QOqxAo{zPIs%ghZ8D$6wej~HUv9P)tc#!5n_ RD!>1JE3+%66(-&Z{{rv#z>NR^ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/waiting_time.png b/app/src/main/res/drawable-xhdpi/waiting_time.png deleted file mode 100644 index cd949a26f902e341b709e88e918c655142dc2ecb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4407 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 diff --git a/app/src/main/res/drawable/button_background.xml b/app/src/main/res/drawable/button_background.xml new file mode 100644 index 000000000..1b672fdfc --- /dev/null +++ b/app/src/main/res/drawable/button_background.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/button_background_dark.xml b/app/src/main/res/drawable/button_background_dark.xml index 034a4fd2e..8f13c7bd7 100644 --- a/app/src/main/res/drawable/button_background_dark.xml +++ b/app/src/main/res/drawable/button_background_dark.xml @@ -3,9 +3,9 @@ + android:drawable="@color/voip_dark_color" /> + android:drawable="@color/voip_dark_color" /> + android:drawable="@color/voip_dark_color" /> diff --git a/app/src/main/res/drawable/button_background_reverse.xml b/app/src/main/res/drawable/button_background_reverse.xml new file mode 100644 index 000000000..9b7432f25 --- /dev/null +++ b/app/src/main/res/drawable/button_background_reverse.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/button_call_answer_background.xml b/app/src/main/res/drawable/button_call_answer_background.xml new file mode 100644 index 000000000..e7f8c8abd --- /dev/null +++ b/app/src/main/res/drawable/button_call_answer_background.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/drawable/button_call_context_menu_background.xml b/app/src/main/res/drawable/button_call_context_menu_background.xml new file mode 100644 index 000000000..c20f7aa56 --- /dev/null +++ b/app/src/main/res/drawable/button_call_context_menu_background.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/drawable/button_call_numpad_background.xml b/app/src/main/res/drawable/button_call_numpad_background.xml new file mode 100644 index 000000000..b30990ebf --- /dev/null +++ b/app/src/main/res/drawable/button_call_numpad_background.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/drawable/button_call_recording_background.xml b/app/src/main/res/drawable/button_call_recording_background.xml new file mode 100644 index 000000000..1f3680b7c --- /dev/null +++ b/app/src/main/res/drawable/button_call_recording_background.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/button_call_terminate_background.xml b/app/src/main/res/drawable/button_call_terminate_background.xml new file mode 100644 index 000000000..f584585fe --- /dev/null +++ b/app/src/main/res/drawable/button_call_terminate_background.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/drawable/button_conference_info.xml b/app/src/main/res/drawable/button_conference_info.xml new file mode 100644 index 000000000..89ec60ccc --- /dev/null +++ b/app/src/main/res/drawable/button_conference_info.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/button_green_background.xml b/app/src/main/res/drawable/button_green_background.xml new file mode 100644 index 000000000..1ebc96355 --- /dev/null +++ b/app/src/main/res/drawable/button_green_background.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/drawable/button_round_background.xml b/app/src/main/res/drawable/button_round_background.xml new file mode 100644 index 000000000..afc56ca3b --- /dev/null +++ b/app/src/main/res/drawable/button_round_background.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/button_toggle_background.xml b/app/src/main/res/drawable/button_toggle_background.xml new file mode 100644 index 000000000..c2292dae4 --- /dev/null +++ b/app/src/main/res/drawable/button_toggle_background.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/app/src/main/res/drawable/button_toggle_background_reverse.xml b/app/src/main/res/drawable/button_toggle_background_reverse.xml new file mode 100644 index 000000000..3ef75d708 --- /dev/null +++ b/app/src/main/res/drawable/button_toggle_background_reverse.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/app/src/main/res/drawable/call_hangup_background.xml b/app/src/main/res/drawable/call_hangup_background.xml deleted file mode 100644 index 2e55eaf93..000000000 --- a/app/src/main/res/drawable/call_hangup_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/call_numpad.xml b/app/src/main/res/drawable/call_numpad.xml deleted file mode 100644 index 340e506d4..000000000 --- a/app/src/main/res/drawable/call_numpad.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/call_pause.xml b/app/src/main/res/drawable/call_pause.xml deleted file mode 100644 index 6cc693d86..000000000 --- a/app/src/main/res/drawable/call_pause.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/call_screenshot.xml b/app/src/main/res/drawable/call_screenshot.xml deleted file mode 100644 index 638610ae3..000000000 --- a/app/src/main/res/drawable/call_screenshot.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/camera.xml b/app/src/main/res/drawable/camera.xml deleted file mode 100644 index d30ef1407..000000000 --- a/app/src/main/res/drawable/camera.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/chat_room_menu_security.xml b/app/src/main/res/drawable/chat_room_menu_devices.xml similarity index 100% rename from app/src/main/res/drawable/chat_room_menu_security.xml rename to app/src/main/res/drawable/chat_room_menu_devices.xml diff --git a/app/src/main/res/drawable/checkbox.xml b/app/src/main/res/drawable/checkbox.xml deleted file mode 100644 index accbf4d23..000000000 --- a/app/src/main/res/drawable/checkbox.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/download.xml b/app/src/main/res/drawable/download.xml deleted file mode 100644 index 1304c34b0..000000000 --- a/app/src/main/res/drawable/download.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/drawable/field_button_background.xml b/app/src/main/res/drawable/field_button_background.xml deleted file mode 100644 index 6e7b32df8..000000000 --- a/app/src/main/res/drawable/field_button_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/footer_button.xml b/app/src/main/res/drawable/footer_button.xml index e0e1f6cbb..b23178869 100644 --- a/app/src/main/res/drawable/footer_button.xml +++ b/app/src/main/res/drawable/footer_button.xml @@ -3,7 +3,7 @@ + android:drawable="@color/voip_dark_color" /> + android:drawable="@color/voip_dark_color" /> diff --git a/app/src/main/res/drawable/menu_more.xml b/app/src/main/res/drawable/icon_apply.xml similarity index 70% rename from app/src/main/res/drawable/menu_more.xml rename to app/src/main/res/drawable/icon_apply.xml index 8e9603b3f..3be5be72e 100644 --- a/app/src/main/res/drawable/menu_more.xml +++ b/app/src/main/res/drawable/icon_apply.xml @@ -1,15 +1,15 @@ - - - diff --git a/app/src/main/res/drawable/record_stop_dark.xml b/app/src/main/res/drawable/icon_audio_routes.xml similarity index 56% rename from app/src/main/res/drawable/record_stop_dark.xml rename to app/src/main/res/drawable/icon_audio_routes.xml index c6d3ef924..b694e1b51 100644 --- a/app/src/main/res/drawable/record_stop_dark.xml +++ b/app/src/main/res/drawable/icon_audio_routes.xml @@ -1,8 +1,7 @@ - + - diff --git a/app/src/main/res/drawable/icon_bluetooth.xml b/app/src/main/res/drawable/icon_bluetooth.xml new file mode 100644 index 000000000..42ad75065 --- /dev/null +++ b/app/src/main/res/drawable/icon_bluetooth.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_call_add.xml b/app/src/main/res/drawable/icon_call_add.xml new file mode 100644 index 000000000..be2d29f8d --- /dev/null +++ b/app/src/main/res/drawable/icon_call_add.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_call_answer.xml b/app/src/main/res/drawable/icon_call_answer.xml new file mode 100644 index 000000000..86a06a63b --- /dev/null +++ b/app/src/main/res/drawable/icon_call_answer.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_call_camera_switch.xml b/app/src/main/res/drawable/icon_call_camera_switch.xml new file mode 100644 index 000000000..d8b0d6de6 --- /dev/null +++ b/app/src/main/res/drawable/icon_call_camera_switch.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_call_chat.xml b/app/src/main/res/drawable/icon_call_chat.xml new file mode 100644 index 000000000..a7fa4fa36 --- /dev/null +++ b/app/src/main/res/drawable/icon_call_chat.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_call_chat_rooms.xml b/app/src/main/res/drawable/icon_call_chat_rooms.xml new file mode 100644 index 000000000..584556375 --- /dev/null +++ b/app/src/main/res/drawable/icon_call_chat_rooms.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_call_forward.xml b/app/src/main/res/drawable/icon_call_forward.xml new file mode 100644 index 000000000..f8b6134ed --- /dev/null +++ b/app/src/main/res/drawable/icon_call_forward.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_call_hangup.xml b/app/src/main/res/drawable/icon_call_hangup.xml new file mode 100644 index 000000000..35cc67534 --- /dev/null +++ b/app/src/main/res/drawable/icon_call_hangup.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_call_more.xml b/app/src/main/res/drawable/icon_call_more.xml new file mode 100644 index 000000000..488e9e3bf --- /dev/null +++ b/app/src/main/res/drawable/icon_call_more.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_call_numpad.xml b/app/src/main/res/drawable/icon_call_numpad.xml new file mode 100644 index 000000000..56932679b --- /dev/null +++ b/app/src/main/res/drawable/icon_call_numpad.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_call_participants.xml b/app/src/main/res/drawable/icon_call_participants.xml new file mode 100644 index 000000000..a36b93b60 --- /dev/null +++ b/app/src/main/res/drawable/icon_call_participants.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_call_record.xml b/app/src/main/res/drawable/icon_call_record.xml new file mode 100644 index 000000000..7051c16aa --- /dev/null +++ b/app/src/main/res/drawable/icon_call_record.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_call_stats.xml b/app/src/main/res/drawable/icon_call_stats.xml new file mode 100644 index 000000000..d506adbbf --- /dev/null +++ b/app/src/main/res/drawable/icon_call_stats.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_calls_list.xml b/app/src/main/res/drawable/icon_calls_list.xml new file mode 100644 index 000000000..2c85e187a --- /dev/null +++ b/app/src/main/res/drawable/icon_calls_list.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_cancel.xml b/app/src/main/res/drawable/icon_cancel.xml new file mode 100644 index 000000000..052b79e93 --- /dev/null +++ b/app/src/main/res/drawable/icon_cancel.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_cancel_alt.xml b/app/src/main/res/drawable/icon_cancel_alt.xml new file mode 100644 index 000000000..07d6cc058 --- /dev/null +++ b/app/src/main/res/drawable/icon_cancel_alt.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_conf_layout.xml b/app/src/main/res/drawable/icon_conf_layout.xml new file mode 100644 index 000000000..f5d2aea2d --- /dev/null +++ b/app/src/main/res/drawable/icon_conf_layout.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_conference_layout_active_speaker.xml b/app/src/main/res/drawable/icon_conference_layout_active_speaker.xml new file mode 100644 index 000000000..cb565b345 --- /dev/null +++ b/app/src/main/res/drawable/icon_conference_layout_active_speaker.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_conference_layout_grid.xml b/app/src/main/res/drawable/icon_conference_layout_grid.xml new file mode 100644 index 000000000..8a66fb552 --- /dev/null +++ b/app/src/main/res/drawable/icon_conference_layout_grid.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_copy.xml b/app/src/main/res/drawable/icon_copy.xml new file mode 100644 index 000000000..278fa0562 --- /dev/null +++ b/app/src/main/res/drawable/icon_copy.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_delete.xml b/app/src/main/res/drawable/icon_delete.xml new file mode 100644 index 000000000..bd647fa54 --- /dev/null +++ b/app/src/main/res/drawable/icon_delete.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_earpiece.xml b/app/src/main/res/drawable/icon_earpiece.xml new file mode 100644 index 000000000..69b03ecc0 --- /dev/null +++ b/app/src/main/res/drawable/icon_earpiece.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_edit.xml b/app/src/main/res/drawable/icon_edit.xml new file mode 100644 index 000000000..d9a3f921a --- /dev/null +++ b/app/src/main/res/drawable/icon_edit.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_edit_alt.xml b/app/src/main/res/drawable/icon_edit_alt.xml new file mode 100644 index 000000000..74da8157b --- /dev/null +++ b/app/src/main/res/drawable/icon_edit_alt.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_info.xml b/app/src/main/res/drawable/icon_info.xml new file mode 100644 index 000000000..99fd6a38f --- /dev/null +++ b/app/src/main/res/drawable/icon_info.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_info_selected.xml b/app/src/main/res/drawable/icon_info_selected.xml new file mode 100644 index 000000000..67b628ef2 --- /dev/null +++ b/app/src/main/res/drawable/icon_info_selected.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/options_add_call.xml b/app/src/main/res/drawable/icon_menu_more.xml similarity index 68% rename from app/src/main/res/drawable/options_add_call.xml rename to app/src/main/res/drawable/icon_menu_more.xml index 97922cc63..19c7e24b7 100644 --- a/app/src/main/res/drawable/options_add_call.xml +++ b/app/src/main/res/drawable/icon_menu_more.xml @@ -1,15 +1,15 @@ - - - diff --git a/app/src/main/res/drawable/icon_merge_calls_local_conference.xml b/app/src/main/res/drawable/icon_merge_calls_local_conference.xml new file mode 100644 index 000000000..2fdd2e68a --- /dev/null +++ b/app/src/main/res/drawable/icon_merge_calls_local_conference.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/options_transfer_call.xml b/app/src/main/res/drawable/icon_next.xml similarity index 66% rename from app/src/main/res/drawable/options_transfer_call.xml rename to app/src/main/res/drawable/icon_next.xml index d979d34db..968192312 100644 --- a/app/src/main/res/drawable/options_transfer_call.xml +++ b/app/src/main/res/drawable/icon_next.xml @@ -1,15 +1,15 @@ - - - diff --git a/app/src/main/res/drawable/icon_pause.xml b/app/src/main/res/drawable/icon_pause.xml new file mode 100644 index 000000000..09787beba --- /dev/null +++ b/app/src/main/res/drawable/icon_pause.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_schedule_date.xml b/app/src/main/res/drawable/icon_schedule_date.xml new file mode 100644 index 000000000..5f29ac622 --- /dev/null +++ b/app/src/main/res/drawable/icon_schedule_date.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_schedule_participants.xml b/app/src/main/res/drawable/icon_schedule_participants.xml new file mode 100644 index 000000000..45741792a --- /dev/null +++ b/app/src/main/res/drawable/icon_schedule_participants.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/conference.xml b/app/src/main/res/drawable/icon_schedule_time.xml similarity index 56% rename from app/src/main/res/drawable/conference.xml rename to app/src/main/res/drawable/icon_schedule_time.xml index 25f309f2f..f165174c6 100644 --- a/app/src/main/res/drawable/conference.xml +++ b/app/src/main/res/drawable/icon_schedule_time.xml @@ -1,7 +1,7 @@ - + diff --git a/app/src/main/res/drawable/conference_remove_participant.xml b/app/src/main/res/drawable/icon_share.xml similarity index 75% rename from app/src/main/res/drawable/conference_remove_participant.xml rename to app/src/main/res/drawable/icon_share.xml index d8fcc664b..4b7a252ee 100644 --- a/app/src/main/res/drawable/conference_remove_participant.xml +++ b/app/src/main/res/drawable/icon_share.xml @@ -1,8 +1,9 @@ - + diff --git a/app/src/main/res/drawable/icon_speaker.xml b/app/src/main/res/drawable/icon_speaker.xml new file mode 100644 index 000000000..ab3fd031b --- /dev/null +++ b/app/src/main/res/drawable/icon_speaker.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_spinner.xml b/app/src/main/res/drawable/icon_spinner.xml new file mode 100644 index 000000000..f3537e7bd --- /dev/null +++ b/app/src/main/res/drawable/icon_spinner.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_spinner_background.xml b/app/src/main/res/drawable/icon_spinner_background.xml new file mode 100644 index 000000000..de81ede9d --- /dev/null +++ b/app/src/main/res/drawable/icon_spinner_background.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_spinner_rotating.xml b/app/src/main/res/drawable/icon_spinner_rotating.xml new file mode 100644 index 000000000..6b8ed48ca --- /dev/null +++ b/app/src/main/res/drawable/icon_spinner_rotating.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/icon_toggle_camera.xml b/app/src/main/res/drawable/icon_toggle_camera.xml new file mode 100644 index 000000000..80a10a84f --- /dev/null +++ b/app/src/main/res/drawable/icon_toggle_camera.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_toggle_mic.xml b/app/src/main/res/drawable/icon_toggle_mic.xml new file mode 100644 index 000000000..40b18e3f8 --- /dev/null +++ b/app/src/main/res/drawable/icon_toggle_mic.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_toggle_speaker.xml b/app/src/main/res/drawable/icon_toggle_speaker.xml new file mode 100644 index 000000000..e9f43e1a1 --- /dev/null +++ b/app/src/main/res/drawable/icon_toggle_speaker.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_video_conf_new.xml b/app/src/main/res/drawable/icon_video_conf_new.xml new file mode 100644 index 000000000..ec949655b --- /dev/null +++ b/app/src/main/res/drawable/icon_video_conf_new.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/micro.xml b/app/src/main/res/drawable/micro.xml deleted file mode 100644 index 888275bc4..000000000 --- a/app/src/main/res/drawable/micro.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/options.xml b/app/src/main/res/drawable/options.xml deleted file mode 100644 index 4b8c7d20e..000000000 --- a/app/src/main/res/drawable/options.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/options_rec.xml b/app/src/main/res/drawable/options_rec.xml deleted file mode 100644 index 3d75c109a..000000000 --- a/app/src/main/res/drawable/options_rec.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/options_start_conference.xml b/app/src/main/res/drawable/options_start_conference.xml deleted file mode 100644 index 1a974f2e9..000000000 --- a/app/src/main/res/drawable/options_start_conference.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/route_bluetooth.xml b/app/src/main/res/drawable/route_bluetooth.xml deleted file mode 100644 index 072a96f2c..000000000 --- a/app/src/main/res/drawable/route_bluetooth.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/route_earpiece.xml b/app/src/main/res/drawable/route_earpiece.xml deleted file mode 100644 index 3549a4b20..000000000 --- a/app/src/main/res/drawable/route_earpiece.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/route_speaker.xml b/app/src/main/res/drawable/route_speaker.xml deleted file mode 100644 index 6d4d54da5..000000000 --- a/app/src/main/res/drawable/route_speaker.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/routes.xml b/app/src/main/res/drawable/routes.xml deleted file mode 100644 index 3d0fd6f0a..000000000 --- a/app/src/main/res/drawable/routes.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/security_switch_track.xml b/app/src/main/res/drawable/security_switch_track.xml deleted file mode 100644 index 943b363d1..000000000 --- a/app/src/main/res/drawable/security_switch_track.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/settings_conferences.xml b/app/src/main/res/drawable/settings_conferences.xml new file mode 100644 index 000000000..1c4d4d873 --- /dev/null +++ b/app/src/main/res/drawable/settings_conferences.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape_audio_routes_background.xml b/app/src/main/res/drawable/shape_audio_routes_background.xml new file mode 100644 index 000000000..25038835b --- /dev/null +++ b/app/src/main/res/drawable/shape_audio_routes_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_button_background.xml b/app/src/main/res/drawable/shape_button_background.xml new file mode 100644 index 000000000..b12bba2b6 --- /dev/null +++ b/app/src/main/res/drawable/shape_button_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_button_disabled_background.xml b/app/src/main/res/drawable/shape_button_disabled_background.xml new file mode 100644 index 000000000..b9a64291c --- /dev/null +++ b/app/src/main/res/drawable/shape_button_disabled_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_button_pressed_background.xml b/app/src/main/res/drawable/shape_button_pressed_background.xml new file mode 100644 index 000000000..adcc6ce8b --- /dev/null +++ b/app/src/main/res/drawable/shape_button_pressed_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_answer_background.xml b/app/src/main/res/drawable/shape_call_answer_background.xml new file mode 100644 index 000000000..f456f09ef --- /dev/null +++ b/app/src/main/res/drawable/shape_call_answer_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_answer_pressed_background.xml b/app/src/main/res/drawable/shape_call_answer_pressed_background.xml new file mode 100644 index 000000000..72b56a9a5 --- /dev/null +++ b/app/src/main/res/drawable/shape_call_answer_pressed_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/field_button_background_default.xml b/app/src/main/res/drawable/shape_call_contact_avatar_background.xml similarity index 55% rename from app/src/main/res/drawable/field_button_background_default.xml rename to app/src/main/res/drawable/shape_call_contact_avatar_background.xml index 579aad9ab..a042ac939 100644 --- a/app/src/main/res/drawable/field_button_background_default.xml +++ b/app/src/main/res/drawable/shape_call_contact_avatar_background.xml @@ -1,5 +1,5 @@ - - + + diff --git a/app/src/main/res/drawable/field_button_background_over.xml b/app/src/main/res/drawable/shape_call_contact_avatar_background_alt.xml similarity index 52% rename from app/src/main/res/drawable/field_button_background_over.xml rename to app/src/main/res/drawable/shape_call_contact_avatar_background_alt.xml index 6be50af16..397dd51bb 100644 --- a/app/src/main/res/drawable/field_button_background_over.xml +++ b/app/src/main/res/drawable/shape_call_contact_avatar_background_alt.xml @@ -1,5 +1,5 @@ - - + + diff --git a/app/src/main/res/drawable/shape_call_numpad_background.xml b/app/src/main/res/drawable/shape_call_numpad_background.xml new file mode 100644 index 000000000..d5a2c3cd7 --- /dev/null +++ b/app/src/main/res/drawable/shape_call_numpad_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_numpad_pressed_background.xml b/app/src/main/res/drawable/shape_call_numpad_pressed_background.xml new file mode 100644 index 000000000..8e2fd88e2 --- /dev/null +++ b/app/src/main/res/drawable/shape_call_numpad_pressed_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_popup_background.xml b/app/src/main/res/drawable/shape_call_popup_background.xml new file mode 100644 index 000000000..9c9303055 --- /dev/null +++ b/app/src/main/res/drawable/shape_call_popup_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_recording_off_background.xml b/app/src/main/res/drawable/shape_call_recording_off_background.xml new file mode 100644 index 000000000..8fb61094a --- /dev/null +++ b/app/src/main/res/drawable/shape_call_recording_off_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_recording_on_background.xml b/app/src/main/res/drawable/shape_call_recording_on_background.xml new file mode 100644 index 000000000..35da9f606 --- /dev/null +++ b/app/src/main/res/drawable/shape_call_recording_on_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_terminate_background.xml b/app/src/main/res/drawable/shape_call_terminate_background.xml new file mode 100644 index 000000000..a8b903a58 --- /dev/null +++ b/app/src/main/res/drawable/shape_call_terminate_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_terminate_pressed_background.xml b/app/src/main/res/drawable/shape_call_terminate_pressed_background.xml new file mode 100644 index 000000000..622eae85f --- /dev/null +++ b/app/src/main/res/drawable/shape_call_terminate_pressed_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_conference_active_speaker_border.xml b/app/src/main/res/drawable/shape_conference_active_speaker_border.xml new file mode 100644 index 000000000..98a0c1b7f --- /dev/null +++ b/app/src/main/res/drawable/shape_conference_active_speaker_border.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_conference_divider.xml b/app/src/main/res/drawable/shape_conference_divider.xml new file mode 100644 index 000000000..609aae7d2 --- /dev/null +++ b/app/src/main/res/drawable/shape_conference_divider.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_conference_divider_fullscreen.xml b/app/src/main/res/drawable/shape_conference_divider_fullscreen.xml new file mode 100644 index 000000000..105fefea9 --- /dev/null +++ b/app/src/main/res/drawable/shape_conference_divider_fullscreen.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_conference_invite_background.xml b/app/src/main/res/drawable/shape_conference_invite_background.xml new file mode 100644 index 000000000..041c8baa4 --- /dev/null +++ b/app/src/main/res/drawable/shape_conference_invite_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_conference_selected_border.xml b/app/src/main/res/drawable/shape_conference_selected_border.xml new file mode 100644 index 000000000..8bc5cd57f --- /dev/null +++ b/app/src/main/res/drawable/shape_conference_selected_border.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_dialog_background.xml b/app/src/main/res/drawable/shape_dialog_background.xml new file mode 100644 index 000000000..1321d5ec0 --- /dev/null +++ b/app/src/main/res/drawable/shape_dialog_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_edittext_background.xml b/app/src/main/res/drawable/shape_edittext_background.xml new file mode 100644 index 000000000..7738c0b77 --- /dev/null +++ b/app/src/main/res/drawable/shape_edittext_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_extra_buttons_background.xml b/app/src/main/res/drawable/shape_extra_buttons_background.xml new file mode 100644 index 000000000..294ee7cb4 --- /dev/null +++ b/app/src/main/res/drawable/shape_extra_buttons_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_form_field_background.xml b/app/src/main/res/drawable/shape_form_field_background.xml new file mode 100644 index 000000000..fce78c8c5 --- /dev/null +++ b/app/src/main/res/drawable/shape_form_field_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_orange_circle.xml b/app/src/main/res/drawable/shape_orange_circle.xml new file mode 100644 index 000000000..d894e038a --- /dev/null +++ b/app/src/main/res/drawable/shape_orange_circle.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/security_switch_thumb.xml b/app/src/main/res/drawable/shape_rect_gray_button.xml similarity index 56% rename from app/src/main/res/drawable/security_switch_thumb.xml rename to app/src/main/res/drawable/shape_rect_gray_button.xml index f5898deab..7bd9f74ca 100644 --- a/app/src/main/res/drawable/security_switch_thumb.xml +++ b/app/src/main/res/drawable/shape_rect_gray_button.xml @@ -1,6 +1,6 @@ - + + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rect_green_button.xml b/app/src/main/res/drawable/shape_rect_green_button.xml new file mode 100644 index 000000000..0a51a8db5 --- /dev/null +++ b/app/src/main/res/drawable/shape_rect_green_button.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rect_orange_button.xml b/app/src/main/res/drawable/shape_rect_orange_button.xml new file mode 100644 index 000000000..e239fdbfc --- /dev/null +++ b/app/src/main/res/drawable/shape_rect_orange_button.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_remote_background.xml b/app/src/main/res/drawable/shape_remote_background.xml new file mode 100644 index 000000000..f77318418 --- /dev/null +++ b/app/src/main/res/drawable/shape_remote_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_remote_paused_background.xml b/app/src/main/res/drawable/shape_remote_paused_background.xml new file mode 100644 index 000000000..8663b555c --- /dev/null +++ b/app/src/main/res/drawable/shape_remote_paused_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_remote_recording_background.xml b/app/src/main/res/drawable/shape_remote_recording_background.xml new file mode 100644 index 000000000..7247f620a --- /dev/null +++ b/app/src/main/res/drawable/shape_remote_recording_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/call_video_conference_participant_background.xml b/app/src/main/res/drawable/shape_remote_video_background.xml similarity index 79% rename from app/src/main/res/drawable/call_video_conference_participant_background.xml rename to app/src/main/res/drawable/shape_remote_video_background.xml index f3cf29b28..6b924bcef 100644 --- a/app/src/main/res/drawable/call_video_conference_participant_background.xml +++ b/app/src/main/res/drawable/shape_remote_video_background.xml @@ -1,5 +1,5 @@ + - - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_button.xml b/app/src/main/res/drawable/shape_round_button.xml new file mode 100644 index 000000000..e4607cb25 --- /dev/null +++ b/app/src/main/res/drawable/shape_round_button.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_button_disabled.xml b/app/src/main/res/drawable/shape_round_button_disabled.xml new file mode 100644 index 000000000..b9a64291c --- /dev/null +++ b/app/src/main/res/drawable/shape_round_button_disabled.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_button_pressed.xml b/app/src/main/res/drawable/shape_round_button_pressed.xml new file mode 100644 index 000000000..adcc6ce8b --- /dev/null +++ b/app/src/main/res/drawable/shape_round_button_pressed.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_gray_background.xml b/app/src/main/res/drawable/shape_round_gray_background.xml new file mode 100644 index 000000000..adc21b355 --- /dev/null +++ b/app/src/main/res/drawable/shape_round_gray_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_gray_background_with_orange_border.xml b/app/src/main/res/drawable/shape_round_gray_background_with_orange_border.xml new file mode 100644 index 000000000..2dd378d80 --- /dev/null +++ b/app/src/main/res/drawable/shape_round_gray_background_with_orange_border.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_toggle_disabled_background.xml b/app/src/main/res/drawable/shape_toggle_disabled_background.xml new file mode 100644 index 000000000..b9a64291c --- /dev/null +++ b/app/src/main/res/drawable/shape_toggle_disabled_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_toggle_off_background.xml b/app/src/main/res/drawable/shape_toggle_off_background.xml new file mode 100644 index 000000000..b12bba2b6 --- /dev/null +++ b/app/src/main/res/drawable/shape_toggle_off_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_toggle_on_background.xml b/app/src/main/res/drawable/shape_toggle_on_background.xml new file mode 100644 index 000000000..e4607cb25 --- /dev/null +++ b/app/src/main/res/drawable/shape_toggle_on_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_toggle_pressed_background.xml b/app/src/main/res/drawable/shape_toggle_pressed_background.xml new file mode 100644 index 000000000..adcc6ce8b --- /dev/null +++ b/app/src/main/res/drawable/shape_toggle_pressed_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/speaker.xml b/app/src/main/res/drawable/speaker.xml deleted file mode 100644 index b27e3b04b..000000000 --- a/app/src/main/res/drawable/speaker.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-land/about_fragment.xml b/app/src/main/res/layout-land/about_fragment.xml index b57607329..9862830be 100644 --- a/app/src/main/res/layout-land/about_fragment.xml +++ b/app/src/main/res/layout-land/about_fragment.xml @@ -74,7 +74,7 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" - android:background="@color/dark_grey_color" + android:background="@color/voip_dark_color2" android:gravity="center" android:orientation="vertical" android:padding="20dp"> diff --git a/app/src/main/res/layout-land/call_controls_fragment.xml b/app/src/main/res/layout-land/call_controls_fragment.xml deleted file mode 100644 index 2e955b8b8..000000000 --- a/app/src/main/res/layout-land/call_controls_fragment.xml +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-land/call_statistics_cell.xml b/app/src/main/res/layout-land/call_statistics_cell.xml deleted file mode 100644 index daf66caac..000000000 --- a/app/src/main/res/layout-land/call_statistics_cell.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-land/dialer_fragment.xml b/app/src/main/res/layout-land/dialer_fragment.xml index a9cff520c..565863732 100644 --- a/app/src/main/res/layout-land/dialer_fragment.xml +++ b/app/src/main/res/layout-land/dialer_fragment.xml @@ -6,6 +6,9 @@ + @@ -65,8 +68,20 @@ android:layout_width="match_parent" android:layout_height="@dimen/main_activity_top_bar_size"> + + + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw533dp-land/dialer_fragment.xml b/app/src/main/res/layout-sw533dp-land/dialer_fragment.xml index 4eefc4ed3..ea0688ed9 100644 --- a/app/src/main/res/layout-sw533dp-land/dialer_fragment.xml +++ b/app/src/main/res/layout-sw533dp-land/dialer_fragment.xml @@ -7,6 +7,9 @@ + @@ -106,8 +109,20 @@ android:layout_width="match_parent" android:layout_height="@dimen/main_activity_top_bar_size"> + + + @@ -106,8 +109,20 @@ android:layout_width="match_parent" android:layout_height="@dimen/main_activity_top_bar_size"> + + diff --git a/app/src/main/res/layout/call_activity.xml b/app/src/main/res/layout/call_activity.xml deleted file mode 100644 index 15c6da418..000000000 --- a/app/src/main/res/layout/call_activity.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_conference.xml b/app/src/main/res/layout/call_conference.xml deleted file mode 100644 index 4a8dd1c86..000000000 --- a/app/src/main/res/layout/call_conference.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_conference_cell.xml b/app/src/main/res/layout/call_conference_cell.xml deleted file mode 100644 index f829398c4..000000000 --- a/app/src/main/res/layout/call_conference_cell.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_conference_participant.xml b/app/src/main/res/layout/call_conference_participant.xml deleted file mode 100644 index 0f8ef7c20..000000000 --- a/app/src/main/res/layout/call_conference_participant.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_controls_fragment.xml b/app/src/main/res/layout/call_controls_fragment.xml deleted file mode 100644 index aee62bcad..000000000 --- a/app/src/main/res/layout/call_controls_fragment.xml +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_incoming_activity.xml b/app/src/main/res/layout/call_incoming_activity.xml deleted file mode 100644 index 664053c96..000000000 --- a/app/src/main/res/layout/call_incoming_activity.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_incoming_answer_decline_buttons.xml b/app/src/main/res/layout/call_incoming_answer_decline_buttons.xml deleted file mode 100644 index 8ee8b90a5..000000000 --- a/app/src/main/res/layout/call_incoming_answer_decline_buttons.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_incoming_notification_heads_up.xml b/app/src/main/res/layout/call_incoming_notification_heads_up.xml index b5b8fb38c..bb3e5907d 100644 --- a/app/src/main/res/layout/call_incoming_notification_heads_up.xml +++ b/app/src/main/res/layout/call_incoming_notification_heads_up.xml @@ -36,7 +36,7 @@ android:layout_width="50dp" android:layout_height="50dp" android:adjustViewBounds="true" - android:src="@drawable/avatar" + android:src="@drawable/voip_single_contact_avatar" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginTop="5dp" diff --git a/app/src/main/res/layout/call_outgoing_activity.xml b/app/src/main/res/layout/call_outgoing_activity.xml deleted file mode 100644 index 7e807f974..000000000 --- a/app/src/main/res/layout/call_outgoing_activity.xml +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_paused.xml b/app/src/main/res/layout/call_paused.xml deleted file mode 100644 index 5382ede33..000000000 --- a/app/src/main/res/layout/call_paused.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_paused_cell.xml b/app/src/main/res/layout/call_paused_cell.xml deleted file mode 100644 index ff965b604..000000000 --- a/app/src/main/res/layout/call_paused_cell.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_primary_buttons.xml b/app/src/main/res/layout/call_primary_buttons.xml deleted file mode 100644 index 0e57e2b50..000000000 --- a/app/src/main/res/layout/call_primary_buttons.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_secondary_buttons.xml b/app/src/main/res/layout/call_secondary_buttons.xml deleted file mode 100644 index 74db426a6..000000000 --- a/app/src/main/res/layout/call_secondary_buttons.xml +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_statistics_cell_header.xml b/app/src/main/res/layout/call_statistics_cell_header.xml deleted file mode 100644 index c9f1dca7b..000000000 --- a/app/src/main/res/layout/call_statistics_cell_header.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_statistics_fragment.xml b/app/src/main/res/layout/call_statistics_fragment.xml deleted file mode 100644 index c24873765..000000000 --- a/app/src/main/res/layout/call_statistics_fragment.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/call_video_fragment.xml b/app/src/main/res/layout/call_video_fragment.xml deleted file mode 100644 index abb3a14a6..000000000 --- a/app/src/main/res/layout/call_video_fragment.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/chat_message_conference_invitation_content_cell.xml b/app/src/main/res/layout/chat_message_conference_invitation_content_cell.xml new file mode 100644 index 000000000..1d820fa3b --- /dev/null +++ b/app/src/main/res/layout/chat_message_conference_invitation_content_cell.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_message_content_cell.xml b/app/src/main/res/layout/chat_message_content_cell.xml index 72fa6c4be..a969f48ba 100644 --- a/app/src/main/res/layout/chat_message_content_cell.xml +++ b/app/src/main/res/layout/chat_message_content_cell.xml @@ -59,6 +59,13 @@ app:longClickListener="@{longClickListener}" android:visibility="@{!data.downloadable && data.voiceRecording ? View.VISIBLE : View.GONE, default=gone}" /> + + - + @@ -33,6 +34,9 @@ + @@ -53,9 +57,9 @@ android:background="@drawable/menu_background" android:visibility="@{resendHidden ? View.GONE : View.VISIBLE}" android:onClick="@{resendClickListener}" - android:drawableRight="@drawable/chat_send_message" style="@style/popup_item_font" - android:text="@string/chat_message_context_menu_resend" /> + android:text="@string/chat_message_context_menu_resend" + app:drawableRightCompat="@drawable/chat_send_message" /> + android:text="@string/chat_message_context_menu_copy_text" + app:drawableRightCompat="@drawable/menu_copy_text" /> + android:text="@string/chat_message_context_menu_forward" + app:drawableRightCompat="@drawable/menu_forward" /> + android:text="@string/chat_message_context_menu_reply" + app:drawableRightCompat="@drawable/menu_reply" /> + android:text="@string/chat_message_context_menu_imdn_info" + app:drawableRightCompat="@drawable/menu_imdn_info" /> + android:text="@string/chat_message_context_menu_add_to_contacts" + app:drawableRightCompat="@drawable/menu_add_contact" /> + android:text="@string/chat_message_context_menu_delete" + app:drawableRightCompat="@drawable/menu_delete" /> diff --git a/app/src/main/res/layout/chat_message_reply_content_cell.xml b/app/src/main/res/layout/chat_message_reply_content_cell.xml index 5e4acae7a..fbcdb9c62 100644 --- a/app/src/main/res/layout/chat_message_reply_content_cell.xml +++ b/app/src/main/res/layout/chat_message_reply_content_cell.xml @@ -55,7 +55,7 @@ android:gravity="center" android:text="@{data.formattedDuration, default=`00:00`}" android:textColor="@color/light_primary_text_color" - android:drawableTop="@drawable/audio_recording_reply_preview_default"/> + app:drawableTopCompat="@drawable/audio_recording_reply_preview_default" /> + app:drawableLeftCompat="@drawable/audio_recording_reply_preview_default" /> + android:textOn=""/> + app:layout="@{@layout/contact_selected_cell}" /> diff --git a/app/src/main/res/layout/chat_room_detail_fragment.xml b/app/src/main/res/layout/chat_room_detail_fragment.xml index 1f3bbd365..9b305bfba 100644 --- a/app/src/main/res/layout/chat_room_detail_fragment.xml +++ b/app/src/main/res/layout/chat_room_detail_fragment.xml @@ -124,7 +124,7 @@ android:layout_weight="0.2" android:background="?attr/button_background_drawable" android:padding="15dp" - android:src="@drawable/menu_more"/> + android:src="@drawable/icon_menu_more"/> + android:layout_marginRight="8dp" + android:adjustViewBounds="true" + android:contentDescription="@{viewModel.securityLevelContentDescription}" + android:onClick="@{securityIconClickListener}" + android:src="@{viewModel.securityLevelIcon, default=@drawable/security_alert_indicator}" + android:visibility="@{viewModel.encryptedChatRoom ? View.VISIBLE : View.GONE}" /> - - - - - + android:paddingBottom="8dp" + android:layout_marginTop="10dp" + android:layout_marginBottom="10dp" + android:layout_gravity="center" + android:background="@drawable/shape_rect_orange_button" + android:text="@string/chat_room_group_info_leave" + style="@style/big_orange_button_font"/> + bind:visibility="@{viewModel.waitForChatRoomCreation}" /> diff --git a/app/src/main/res/layout/chat_room_group_info_participant_cell.xml b/app/src/main/res/layout/chat_room_group_info_participant_cell.xml index 287abd3b5..0fab23c90 100644 --- a/app/src/main/res/layout/chat_room_group_info_participant_cell.xml +++ b/app/src/main/res/layout/chat_room_group_info_participant_cell.xml @@ -47,29 +47,29 @@ android:src="@drawable/chat_group_delete" /> + android:layout_toLeftOf="@id/delete" + android:visibility="@{data.showAdminControls && data.canBeSetAdmin ? View.VISIBLE : View.GONE}"> + android:layout_marginRight="20dp" + android:onClick="@{() -> data.unSetAdmin()}" + android:visibility="@{data.admin ? View.VISIBLE : View.GONE}"> + android:layout_marginRight="20dp" + android:onClick="@{() -> data.setAdmin()}" + android:visibility="@{data.admin ? View.GONE : View.VISIBLE}"> + android:maxLines="2" + android:text="@{viewModel.lastMessageText}" /> - + @@ -38,9 +39,9 @@ android:visibility="@{groupInfoHidden ? View.GONE : View.VISIBLE}" android:background="@drawable/menu_background" android:onClick="@{groupInfoListener}" - android:drawableRight="@drawable/chat_room_menu_group_info" style="@style/popup_item_font" - android:text="@string/chat_room_context_menu_group_info" /> + android:text="@string/chat_room_context_menu_group_info" + app:drawableRightCompat="@drawable/chat_room_menu_group_info" /> + android:text="@string/chat_room_context_menu_participants_devices" + app:drawableRightCompat="@drawable/chat_room_menu_devices" /> + android:text="@string/chat_message_context_menu_ephemeral_messages" + app:drawableRightCompat="@drawable/chat_room_menu_ephemeral" /> + android:text="@string/chat_message_context_menu_delete_messages" + app:drawableRightCompat="@drawable/chat_room_menu_delete" /> diff --git a/app/src/main/res/layout/conference_paused.xml b/app/src/main/res/layout/conference_paused.xml deleted file mode 100644 index 4b47e69d6..000000000 --- a/app/src/main/res/layout/conference_paused.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/conference_schedule_cell.xml b/app/src/main/res/layout/conference_schedule_cell.xml new file mode 100644 index 000000000..2968a25b3 --- /dev/null +++ b/app/src/main/res/layout/conference_schedule_cell.xml @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conference_schedule_list_header.xml b/app/src/main/res/layout/conference_schedule_list_header.xml new file mode 100644 index 000000000..55c3f8434 --- /dev/null +++ b/app/src/main/res/layout/conference_schedule_list_header.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conference_scheduling_fragment.xml b/app/src/main/res/layout/conference_scheduling_fragment.xml new file mode 100644 index 000000000..042e8ae4d --- /dev/null +++ b/app/src/main/res/layout/conference_scheduling_fragment.xml @@ -0,0 +1,374 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conference_scheduling_participant_cell.xml b/app/src/main/res/layout/conference_scheduling_participant_cell.xml new file mode 100644 index 000000000..38fc01fb2 --- /dev/null +++ b/app/src/main/res/layout/conference_scheduling_participant_cell.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conference_scheduling_participants_list_fragment.xml b/app/src/main/res/layout/conference_scheduling_participants_list_fragment.xml new file mode 100644 index 000000000..6ca7d29f7 --- /dev/null +++ b/app/src/main/res/layout/conference_scheduling_participants_list_fragment.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conference_scheduling_summary_fragment.xml b/app/src/main/res/layout/conference_scheduling_summary_fragment.xml new file mode 100644 index 000000000..5059918a4 --- /dev/null +++ b/app/src/main/res/layout/conference_scheduling_summary_fragment.xml @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conference_waiting_room_fragment.xml b/app/src/main/res/layout/conference_waiting_room_fragment.xml new file mode 100644 index 000000000..b172506c4 --- /dev/null +++ b/app/src/main/res/layout/conference_waiting_room_fragment.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conferences_scheduled_fragment.xml b/app/src/main/res/layout/conferences_scheduled_fragment.xml new file mode 100644 index 000000000..04b3d7211 --- /dev/null +++ b/app/src/main/res/layout/conferences_scheduled_fragment.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/contact_avatar.xml b/app/src/main/res/layout/contact_avatar.xml index cde6ae382..98c03b62a 100644 --- a/app/src/main/res/layout/contact_avatar.xml +++ b/app/src/main/res/layout/contact_avatar.xml @@ -46,7 +46,7 @@ android:layout_alignParentLeft="true" android:adjustViewBounds="true" android:contentDescription="@null" - android:src="@{groupChatAvatarVisibility ? @drawable/chat_group_avatar : @drawable/avatar}"/> + android:src="@{groupChatAvatarVisibility ? @drawable/voip_multiple_contacts_avatar : @drawable/voip_single_contact_avatar}"/> + android:src="@drawable/voip_single_contact_avatar"/> + type="org.linphone.contact.ContactsSelectionViewModel" /> + type="org.linphone.contact.ContactSelectionData" /> + @@ -66,8 +69,20 @@ android:layout_height="60dp" android:layout_alignParentBottom="true"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/history_detail_cell.xml b/app/src/main/res/layout/history_detail_cell.xml index b2aadd39a..f682ba7a3 100644 --- a/app/src/main/res/layout/history_detail_cell.xml +++ b/app/src/main/res/layout/history_detail_cell.xml @@ -5,7 +5,7 @@ + type="org.linphone.activities.main.history.viewmodels.CallLogViewModel" /> + type="org.linphone.activities.main.history.viewmodels.CallLogViewModel" /> @@ -98,7 +98,7 @@ - - + @@ -43,17 +38,17 @@ android:layout_weight="0.2"> + + + + + + + + + android:layout_weight="0.2" /> + android:visibility="@{viewModel.displayedCallLogs.empty && viewModel.filter == CallLogsFilter.ALL ? View.VISIBLE : View.GONE}" /> + android:visibility="@{viewModel.displayedCallLogs.empty && viewModel.filter == CallLogsFilter.MISSED ? View.VISIBLE : View.GONE}" /> + + + android:layout_height="match_parent"> + + + + diff --git a/app/src/main/res/layout/settings_call_fragment.xml b/app/src/main/res/layout/settings_call_fragment.xml index 97c901951..827758b7a 100644 --- a/app/src/main/res/layout/settings_call_fragment.xml +++ b/app/src/main/res/layout/settings_call_fragment.xml @@ -112,13 +112,6 @@ linphone:listener="@{viewModel.useTelecomManagerListener}" linphone:checked="@={viewModel.useTelecomManager}"/> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml index 7f2bd98a7..a46a6b9b4 100644 --- a/app/src/main/res/layout/settings_fragment.xml +++ b/app/src/main/res/layout/settings_fragment.xml @@ -153,6 +153,13 @@ linphone:title="@{@string/settings_call_title}" linphone:icon="@{@drawable/settings_call}" /> + + + @@ -50,7 +53,7 @@ android:layout_height="match_parent" android:adjustViewBounds="true" android:padding="10dp" - android:src="@drawable/avatar" + android:src="@drawable/voip_single_contact_avatar" android:contentDescription="@string/content_description_change_own_picture" glideAvatarFallback="@{viewModel.defaultAccountAvatar}" /> @@ -162,6 +165,7 @@ @@ -191,6 +195,7 @@ @@ -220,6 +225,37 @@ + + + + + + + + + + @@ -249,6 +285,7 @@ diff --git a/app/src/main/res/layout/voip_active_call_or_conference_fragment.xml b/app/src/main/res/layout/voip_active_call_or_conference_fragment.xml new file mode 100644 index 000000000..4e5f52de4 --- /dev/null +++ b/app/src/main/res/layout/voip_active_call_or_conference_fragment.xml @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_activity.xml b/app/src/main/res/layout/voip_activity.xml new file mode 100644 index 000000000..8a2f06742 --- /dev/null +++ b/app/src/main/res/layout/voip_activity.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_buttons.xml b/app/src/main/res/layout/voip_buttons.xml new file mode 100644 index 000000000..cfba92728 --- /dev/null +++ b/app/src/main/res/layout/voip_buttons.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_buttons_audio_routes.xml b/app/src/main/res/layout/voip_buttons_audio_routes.xml new file mode 100644 index 000000000..67b97794e --- /dev/null +++ b/app/src/main/res/layout/voip_buttons_audio_routes.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_buttons_extra.xml b/app/src/main/res/layout/voip_buttons_extra.xml new file mode 100644 index 000000000..b79f433d4 --- /dev/null +++ b/app/src/main/res/layout/voip_buttons_extra.xml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_buttons_incoming.xml b/app/src/main/res/layout/voip_buttons_incoming.xml new file mode 100644 index 000000000..c9acbf3ab --- /dev/null +++ b/app/src/main/res/layout/voip_buttons_incoming.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_buttons_outgoing.xml b/app/src/main/res/layout/voip_buttons_outgoing.xml new file mode 100644 index 000000000..99a7ab394 --- /dev/null +++ b/app/src/main/res/layout/voip_buttons_outgoing.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_call.xml b/app/src/main/res/layout/voip_call.xml new file mode 100644 index 000000000..e299c6049 --- /dev/null +++ b/app/src/main/res/layout/voip_call.xml @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_call_context_menu.xml b/app/src/main/res/layout/voip_call_context_menu.xml new file mode 100644 index 000000000..510245543 --- /dev/null +++ b/app/src/main/res/layout/voip_call_context_menu.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_call_incoming_fragment.xml b/app/src/main/res/layout/voip_call_incoming_fragment.xml new file mode 100644 index 000000000..99449cf91 --- /dev/null +++ b/app/src/main/res/layout/voip_call_incoming_fragment.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_call_outgoing_fragment.xml b/app/src/main/res/layout/voip_call_outgoing_fragment.xml new file mode 100644 index 000000000..3ab17e027 --- /dev/null +++ b/app/src/main/res/layout/voip_call_outgoing_fragment.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_call_paused.xml b/app/src/main/res/layout/voip_call_paused.xml new file mode 100644 index 000000000..1bbc392c6 --- /dev/null +++ b/app/src/main/res/layout/voip_call_paused.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_call_paused_by_remote.xml b/app/src/main/res/layout/voip_call_paused_by_remote.xml new file mode 100644 index 000000000..a003842cf --- /dev/null +++ b/app/src/main/res/layout/voip_call_paused_by_remote.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/call_single_statistic_cell.xml b/app/src/main/res/layout/voip_call_stat_cell.xml similarity index 77% rename from app/src/main/res/layout/call_single_statistic_cell.xml rename to app/src/main/res/layout/voip_call_stat_cell.xml index 2d9c44362..99a1b2ba6 100644 --- a/app/src/main/res/layout/call_single_statistic_cell.xml +++ b/app/src/main/res/layout/voip_call_stat_cell.xml @@ -5,12 +5,13 @@ + type="org.linphone.activities.voip.data.StatItemData" /> + android:gravity="center" /> + android:gravity="center" /> diff --git a/app/src/main/res/layout/voip_call_stats.xml b/app/src/main/res/layout/voip_call_stats.xml new file mode 100644 index 000000000..d7f148140 --- /dev/null +++ b/app/src/main/res/layout/voip_call_stats.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/call_statistics_cell.xml b/app/src/main/res/layout/voip_call_stats_cell.xml similarity index 71% rename from app/src/main/res/layout/call_statistics_cell.xml rename to app/src/main/res/layout/voip_call_stats_cell.xml index 458ffa243..07c1c8975 100644 --- a/app/src/main/res/layout/call_statistics_cell.xml +++ b/app/src/main/res/layout/voip_call_stats_cell.xml @@ -7,7 +7,7 @@ + type="org.linphone.activities.voip.data.CallStatisticsData" /> - - - - + app:layout="@{@layout/voip_call_stat_cell}"/> - - + app:layout="@{@layout/voip_call_stat_cell}" /> diff --git a/app/src/main/res/layout/voip_calls_cell.xml b/app/src/main/res/layout/voip_calls_cell.xml new file mode 100644 index 000000000..2cbd37552 --- /dev/null +++ b/app/src/main/res/layout/voip_calls_cell.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_calls_list_fragment.xml b/app/src/main/res/layout/voip_calls_list_fragment.xml new file mode 100644 index 000000000..9aad7b9ed --- /dev/null +++ b/app/src/main/res/layout/voip_calls_list_fragment.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_chat_fragment.xml b/app/src/main/res/layout/voip_chat_fragment.xml new file mode 100644 index 000000000..a83e49fed --- /dev/null +++ b/app/src/main/res/layout/voip_chat_fragment.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_conference_active_speaker.xml b/app/src/main/res/layout/voip_conference_active_speaker.xml new file mode 100644 index 000000000..ee0d898b8 --- /dev/null +++ b/app/src/main/res/layout/voip_conference_active_speaker.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_conference_creation_pending_wait_layout.xml b/app/src/main/res/layout/voip_conference_creation_pending_wait_layout.xml new file mode 100644 index 000000000..e0aa9338e --- /dev/null +++ b/app/src/main/res/layout/voip_conference_creation_pending_wait_layout.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_conference_grid.xml b/app/src/main/res/layout/voip_conference_grid.xml new file mode 100644 index 000000000..f267ac657 --- /dev/null +++ b/app/src/main/res/layout/voip_conference_grid.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_conference_layout_fragment.xml b/app/src/main/res/layout/voip_conference_layout_fragment.xml new file mode 100644 index 000000000..058dec71f --- /dev/null +++ b/app/src/main/res/layout/voip_conference_layout_fragment.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_conference_participant_cell.xml b/app/src/main/res/layout/voip_conference_participant_cell.xml new file mode 100644 index 000000000..c986a571f --- /dev/null +++ b/app/src/main/res/layout/voip_conference_participant_cell.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_conference_participant_remote_active_speaker_miniature.xml b/app/src/main/res/layout/voip_conference_participant_remote_active_speaker_miniature.xml new file mode 100644 index 000000000..d053de2e4 --- /dev/null +++ b/app/src/main/res/layout/voip_conference_participant_remote_active_speaker_miniature.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_conference_participant_remote_grid.xml b/app/src/main/res/layout/voip_conference_participant_remote_grid.xml new file mode 100644 index 000000000..924ffaf1f --- /dev/null +++ b/app/src/main/res/layout/voip_conference_participant_remote_grid.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_conference_participants_add_fragment.xml b/app/src/main/res/layout/voip_conference_participants_add_fragment.xml new file mode 100644 index 000000000..039559fdc --- /dev/null +++ b/app/src/main/res/layout/voip_conference_participants_add_fragment.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_conference_participants_fragment.xml b/app/src/main/res/layout/voip_conference_participants_fragment.xml new file mode 100644 index 000000000..d277ad0f1 --- /dev/null +++ b/app/src/main/res/layout/voip_conference_participants_fragment.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_conference_paused.xml b/app/src/main/res/layout/voip_conference_paused.xml new file mode 100644 index 000000000..62528a2f3 --- /dev/null +++ b/app/src/main/res/layout/voip_conference_paused.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_contact_avatar.xml b/app/src/main/res/layout/voip_contact_avatar.xml new file mode 100644 index 000000000..558e7cce5 --- /dev/null +++ b/app/src/main/res/layout/voip_contact_avatar.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_contact_avatar_alt.xml b/app/src/main/res/layout/voip_contact_avatar_alt.xml new file mode 100644 index 000000000..3be1438c0 --- /dev/null +++ b/app/src/main/res/layout/voip_contact_avatar_alt.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_dialog.xml b/app/src/main/res/layout/voip_dialog.xml new file mode 100644 index 000000000..85b4473db --- /dev/null +++ b/app/src/main/res/layout/voip_dialog.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_dialog_info.xml b/app/src/main/res/layout/voip_dialog_info.xml new file mode 100644 index 000000000..0a1bc924b --- /dev/null +++ b/app/src/main/res/layout/voip_dialog_info.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/voip_numpad.xml b/app/src/main/res/layout/voip_numpad.xml new file mode 100644 index 000000000..93cfe7873 --- /dev/null +++ b/app/src/main/res/layout/voip_numpad.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/call_status_fragment.xml b/app/src/main/res/layout/voip_status_fragment.xml similarity index 82% rename from app/src/main/res/layout/call_status_fragment.xml rename to app/src/main/res/layout/voip_status_fragment.xml index 501a0fb58..76341c9de 100644 --- a/app/src/main/res/layout/call_status_fragment.xml +++ b/app/src/main/res/layout/voip_status_fragment.xml @@ -3,15 +3,12 @@ - + type="org.linphone.activities.voip.viewmodels.StatusViewModel" /> + android:contentDescription="@{viewModel.callQualityContentDescription}" + android:padding="10dp" + android:src="@{viewModel.callQualityIcon, default=@drawable/call_quality_indicator_0}" /> @@ -52,9 +49,10 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_centerVertical="true" - android:layout_toRightOf="@id/status_led" + android:layout_toEndOf="@id/status_led" android:gravity="center_vertical" - android:paddingLeft="5dp" /> + android:paddingStart="5dp" + android:paddingEnd="5dp" /> diff --git a/app/src/main/res/layout/wait_layout.xml b/app/src/main/res/layout/wait_layout.xml index b74fd9680..976ee049c 100644 --- a/app/src/main/res/layout/wait_layout.xml +++ b/app/src/main/res/layout/wait_layout.xml @@ -22,7 +22,7 @@ android:layout_height="wrap_content" android:layout_centerInParent="true" android:indeterminate="true" - android:indeterminateTint="?attr/accentColor"/> + android:indeterminateTint="?attr/accentColor" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/contacts_nav_graph.xml b/app/src/main/res/navigation/contacts_nav_graph.xml index 7d2d06d68..500609dbf 100644 --- a/app/src/main/res/navigation/contacts_nav_graph.xml +++ b/app/src/main/res/navigation/contacts_nav_graph.xml @@ -2,7 +2,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 5a9df56af..7b1ad74dc 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -2,7 +2,6 @@ @@ -52,15 +50,12 @@ android:id="@+id/action_masterContactsFragment_to_masterChatRoomsFragment" app:destination="@id/masterChatRoomsFragment" /> @@ -129,13 +124,13 @@ tools:layout="@layout/settings_fragment" android:label="SettingsFragment" > + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/settings_nav_graph.xml b/app/src/main/res/navigation/settings_nav_graph.xml index 3e226bf78..1da8f94e0 100644 --- a/app/src/main/res/navigation/settings_nav_graph.xml +++ b/app/src/main/res/navigation/settings_nav_graph.xml @@ -2,7 +2,6 @@ + @@ -134,4 +136,9 @@ android:name="IsLinking" app:argType="boolean" /> + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index cbd864f2f..a1fd30e79 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -8,7 +8,6 @@ Datenschutz-Bestimmungen Lagdatei wurde gelöscht Logdatei-URL wurde in die Zwischenablage kopiert - Fehler Telefonnummer Telefonnummern Wähle dein Land @@ -111,7 +110,6 @@ Anruf pausiert Ausgehender Anruf Laufender Anruf - Konferenz Gesprächspartner möchte das Video einschalten Inkompatible Medienparameter Netzwerk nicht erreichbar @@ -119,7 +117,6 @@ &appName; wurde automatich gestartet Hochladen der Logdatei fehlgeschlagen! Ländername oder Vorwahl - Ungültige E-Mail-Adresse der freie SIP client Diesen Eintrag löschen\? Hallo, benutze &appName;! Du kannst es kostenlos hier %s herunterladen @@ -131,7 +128,6 @@ %s ist kein Administrator mehr Teilnehmer Du bist jetzt Administrator - Unterhaltung auswählen oder eine neue beginnen Nochmal senden Die Nachricht in diesen Raum weiterleiten\? Nachrichten löschen @@ -145,7 +141,6 @@ Bestätigungscode Weiter SIP-Konto benutzen - Das Konto wurde noch nicht überprüft Das Konto existiert nicht oder das Passwort ist falsch Den Benutzernamen und das Passwort anstatt der Telefonnummer verwenden Bitte den Benutzernamen und das Passwort des &appName;-Kontos eingeben @@ -180,8 +175,6 @@ Domäne Erweitert Ersetze + durch 00 - Nach rechts streichen um den Anruf zu beenden - Nach links streichen um den Anruf anzunehmen Anruf beantworten Mikrofon stummschalten Audioausgabe auf Lautsprecher diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 1f6dea08d..03c558a5b 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -9,7 +9,6 @@ Esto es necesario para recibir llamadas en segundo plano &appName; contactos Visite nuestra política de privacidad - Error Número de teléfono Números de teléfonos Selecciona tu país @@ -32,15 +31,12 @@ %d días Compartir enlace de registros usando… - Email inválido Hoy Ayer Transporte UDP TCP TLS - MD5 - SHA-256 El cliente libre Conectado No conectado @@ -115,27 +111,19 @@ Muestra para qué se usará tu número de teléfono Vacía el campo de texto que está a su lado Cambiar de la cámara delantera a la trasera y viceversa - Retirar al participante de la conferencia - Pausar la conferencia - Su llamada ha sido puesta en espera Pausar la llamada Habilitar o deshabilitar la grabación de la llamada Termina la llamada - Deslíce a la izquierda para contestar la llamada Contestar la llamada Silencie su micrófono Activar el micrófono Dirigir el audio al altavoz Volver a la vista de llamadas - Reanudar la llamada actualmente en espera - Reanudar la conferencia actualmente en espera - Mostrar la lista de conversaciones Habilitar el video en la llamada Deshabilitar el video en la llamada Mostrar u ocultar el menú de salida de audio Usar el auricular como salida de audio Usar el altavoz como salida de audio - Mostrar u ocultar el menú de acciones avanzadas Iniciar nueva llamada Transferir la llamada actual a otra persona Eliminar la transferencia de archivos pendiente @@ -168,7 +156,6 @@ Guardar los cambios Cambiar foto de contacto Cambiar mi propia foto - Dirección de llamada Salir del modo de edición Seleccionar todos los temas de la lista Deseleccionar todos los elementos de la lista @@ -209,25 +196,19 @@ Calidad de llamada máxima La llamada no está asegurada La llamada está asegurada - Mostrar u ocultar las estadísticas de llamadas Video adjunto Estás seguro que quieres borrar estas conversaciones\? - Fusionar todas las llamadas en una conferencia Llamada saliente - Deslíce a la derecha para terminar la llamada Llamada ha sido declinada Dirigir el audio al auricular El corresponsal quisiera encender el video - Mostrar u ocultar los dígitos para enviar DTMFs Resolución de video recibida: Usar auriculares bluetooth como salida de audio - Filtro de pantalla: Este asistente le ayudará a configurar y utilizar su cuenta SIP. Pendiente de la transferencia de archivos Contacto es seleccionado Mostrat una lsita de todos los contactos SIP Adjunte un archivo al mensaje - \nGracias a tu número de teléfono, tus amigos te encontrarán más fácilmente.\n\n Verás en tu libreta de direcciones quién está usando &appName; y tus amigos sabrán que pueden contactarte en &appName; también.\n Cambiar la duración efímera por el valor seleccionado El contacto es un administrador en esta conversación Sólo puedes usar tu número de teléfono con una cuenta de &appName;.\n\nSi ya habías vinculado tu número a otra cuenta pero prefieres usar esta, simplemente vincúlala ahora y tu número se moverá automáticamente a esta cuenta. @@ -266,7 +247,6 @@ Sólo se mostrará el nombre del remitente Crear atajos para las salas de chat en el lanzador Esconder las salas de chat de las configuraciones de proxy eliminadas - Usar los parámetros de notificación de los envíos anteriores Insertar atajos de información desde el &appName; contacto en los contactos nativos de Android Si el contacto es deshabilitado se almacena localmente Nombre de dispositivo @@ -308,8 +288,6 @@ Borrar Agregar a contactos Quieres reenviar el mensaje en esta sala\? - Selecciona una conversación o crea una nueva - La creación de la sala de chat falló Inforamción del grupo Conversaciones en estos dispositivos Mensajes efímeros @@ -331,11 +309,9 @@ <Redactado> No hay grabaciones Llamada entrante - La llamada ha sido interrumpida por su corresponsal Llamada pausada Llamada saliente Llamada en curso - Conferencia Usuario está ocupado El usuario no ha sido encontrado Parámetros de medios incompatibles @@ -372,7 +348,6 @@ Las contraseñas no coinciden La dirección de correo electrónico es inválida El nombre de usuario tiene demasiados caracteres - Su cuenta no ha sido validada todavía Use su &appName; cuenta Por favor, introduzca su nombre de usuario y contraseña de &appName; cuenta Usa tu nombre de usuario y contraseña en lugar de tu número de teléfono @@ -384,7 +359,6 @@ Usar cuenta SIP Por favor, introduzca su nombre de usuario y contraseña con su dominio SIP Mostrar nombre (opcional) - Algoritmo hash Confirmación de contraseña Su cuenta está creada. Por favor, comprueba tus correos para validar tu cuenta: Una vez hecho, vuelve aquí y pulsa el botón. @@ -471,8 +445,6 @@ Configuración de notificación de Android Usar sólo WiFi Permitir IPv6 - Habilitar noticiaciones push - Requerido cuando se usa Flexisip < 2.0 Usar puertos aleatorios Puerto SIP a usar Suscribirse a la lista de amigos @@ -541,7 +513,6 @@ &appName; notificaciones de mensajes Llamada entrante Estado de envío - No se puede abrir el archivo, no hay una apliación disponible para este formato. Los mensajes instantáneos se cifran de extremo a extremo en conversaciones seguras. Es posible mejorar el nivel de seguridad de una conversación autenticando a los participantes. Para ello, llame al contacto y siga el proceso de autenticación. Enviar registros Restablecer registros @@ -563,12 +534,9 @@ Servidor timeout condiciones de uso Acepto Belledonne Communications\' %1$s and %2$s - pantalla completa durante una llamada Mostrar overlay fuera de la aplicación - Habilitar mensajes efímeros (beta) &appName; notificaciones de llamadas perdidas Reenviar mensaje en esta conversación - Tomar una captura de pantalla del video recibido Ver archivo de configuración Seleccione o cree una conversación para reenviar el mensaje Seleccione o cree una conversación para compartir los archivos @@ -581,10 +549,8 @@ Exportar Abrir como texto Temporalmente no disponible - Erro política de privacidad Cuenta principal - Oculta las barras de status y navegación Redirigir las llamadas rechazadas al correo de voz Sonar en los primeros entrantes Pausar llamadas cuando se pierde el enfoque del audio @@ -596,7 +562,6 @@ Cancelar Dirigir el audio al dispositivo bluetooth si existe El mensaje es una respuesta - Archivo adjunto al mensaje Cancelar la respuesta Respuesta Responder diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index df0cff122..c9f057d54 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -10,21 +10,15 @@ Codec : La calibration de l\'annulateur d\'écho est en cours Mettre fin à l\'appel - Faire glisser vers la droite pour raccrocher - Faire glisser vers la gauche pour décrocher Décrocher Couper le micro Réactiver le micro Contrôle qualité adaptatif Afficher la caméra sur la vue d\'accueil - Reprendre l\'appel - Reprendre la conférence - Afficher la liste des conversations Désactiver la vidéo en appel Activer la vidéo en appel Utiliser un casque Bluetooth comme périphérique de sortie Utiliser des écouteurs comme périphérique de sortie - Afficher ou masquer le menu des actions avancées Nouvel appel Transférer l\'appel Transfert d\'appel en attente @@ -43,7 +37,6 @@ Contacts &appName; Consulter notre politique de confidentialité URL des traces copiée dans le presse-papier - Erreur Numéro de téléphone Numéros de téléphone Préfixe @@ -56,7 +49,6 @@ Adresse SIP Initier les appels en vidéo Annuler - Email non valide Aujourd\'hui Hier Transport @@ -64,8 +56,6 @@ Toujours envoyer une demande d\'activation de la vidéo TCP TLS - MD5 - SHA-256 Le client SIP open source Connecté Non connecté @@ -132,9 +122,6 @@ Supprimer Toujours accepter d\'activer la vidéo Accepter d\'activer la vidéo - Sélectionnez une conversation ou créez-en une nouvelle - Impossible d\'ouvrir le fichier, aucune application disponible pour ce format. - La conversation n\'a pas pu être créée Infos du groupe Terminaux de la conversation Messages éphémères @@ -152,11 +139,9 @@ Aucun enregistrement disponible Appel entrant Appel sortant - L\'appel a été mis en pause par votre interlocuteur Appel en pause Appel sortant Appel en cours - Conférence L\'appel a été refusé L\'utilisateur est occupé L\'utilisateur n\'a pu être trouvé @@ -196,7 +181,6 @@ Les mots de passe ne correspondent pas L\'adresse email est invalide Le nom d\'utilisateur contient trop de caractères - Votre compte n\'a pas encore été activé Passer Lier le compte Télécharger la configuration distante @@ -223,7 +207,6 @@ Continuer Utiliser un compte SIP Veuillez saisir votre nom d\'utilisateur et votre mot de passe avec le domaine SIP - Algorithme de hachage Veuillez saisir un nom d\'utilisateur, un email et un mot de passe pour votre compte &appName; Confirmation du mot de passe Utiliser un nom d\'utilisateur (optionnel) @@ -269,17 +252,14 @@ Requis par le mode double Exemple : john, si votre compte est john@sip.example.org Vous devrez re-saisir votre mot de passe si vous changez votre nom d\'utilisateur ou le domaine - Utiliser les anciens paramètres de notifications push Notification de service &appName; Notifications de messages entrants &appName; Faire passer tous les appels via le serveur mandataire SIP Intervalle AVPF RTCP régulier Ne plus afficher Dans les conversations sécurisées, les messages sont chiffrés de bout en bout. Il est possible d\'augmenter le niveau de sécurité en vérifiant l\'identité des participants. Pour ce faire, appelez le contact et suivez la procédure d\'authentification. - Retirer le participant de la conférence Activer ou désactiver l\'enregistrement d\'appel Revenir à la vue d\'appel - Afficher ou masquer le pavé numérique pour envoyer des DTMFs Utiliser une enceinte comme périphérique de sortie Le message a été transféré Le chiffrement de bout-en-bout est activé @@ -328,7 +308,6 @@ Paramètres de notification Android Utiliser uniquement la connexion Wi-Fi Autoriser IPv6 - Requis quand vous utilisez un serveur Flexisip < 2.0 Utiliser des ports aléatoires Port SIP à utiliser Information de présence dans le contact natif @@ -338,7 +317,6 @@ Toujours demander dans quel compte le nouveau contact doit être ajouté Si désactivé, le contact sera enregistré localement Traces de déboggage - Activer les notifications push Mode arrière-plan Affiche une notification pour maintenir la connexion avec l\'application Démarrer au lancement du téléphone @@ -427,9 +405,6 @@ Revenir en arrière Affiche à quoi le numéro de téléphone va-t-il servir Intervertir la source de capture de la vidéo - Mettre la conférence en pause - Fusionner les appels dans une conférence - Votre appel a été mis en pause Mettre l\'appel en pause Editer Ajouter une pièce jointe @@ -449,7 +424,6 @@ Sauvegarder les modifications Modifier la photo du contact Modifier ma photo - Direction d\'appel Aller à la vue de détails de l\'appel Tout sélectionner Tout désélectionner @@ -490,7 +464,6 @@ L\'appel n\'est pas chiffré Voulez-vous supprimer cet enregistrement \? Le chiffrement est en cours - Afficher ou masquer les statistiques d\'appel Vidéo en pièce jointe Aucun compte configuré Bonjour ! Rejoins-moi sur &appName;! Disponible gratuitement ici : %s @@ -524,9 +497,7 @@ Bande passante d\'envoi : Connectivité ICE : Résolution vidéo envoyée : - Filtre d\'affichage : Cet assistant va vous aider à configurer et utiliser votre compte SIP. - \nGrâce à votre numéro de téléphone, vos amis vous trouverons plus facilement.\n\nVous verrez dans votre carnet d\'adresses qui utilise &appName; et vos amis verront également qu\'ils peuvent vous contacter via &appName;.\n \nVos amis pourront vous joindre plus facilement si vous associez votre compte à votre numéro de téléphone.\n\nVous verrez dans votre carnet d\'adresses les contacts qui utilisent &appName; et vos amis sauront qu\'ils peuvent vous contacter.\n Le compte n\'existe pas ou les mots de passe ne correspondent pas Veuillez saisir le nom d\'utilisateur et mot de passe de votre compte &appName; @@ -544,7 +515,6 @@ Transport Vider le champ texte à côté Notifications d\'appels manqués &appName; - Prendre une capture d\'écran de la vidéo reçue les conditions d\'utilisation Fermer la bulle de notification la politique de confidentialité @@ -569,8 +539,6 @@ Autres paramètres Afficher la notification flottante en dehors de l\'app Quand un nombre est saisi, applique le préfixe - Plein écran pendant un appel - Cache la barre de status et de navigation Fichier non trouvé Applique le préfixe pour les appels sortants et les messages Compte principal @@ -589,14 +557,11 @@ Sélectionnez ou créez une conversation pour transférer le message Sélectionnez ou créez une conversation pour partager le(s) fichier(s) Sélectionnez ou créez une conversation pour partager le texte - Messages éphémères (bêta) Répondre Réponse Message Réponse Gardez le bouton enfoncé pour enregistrer un message vocal - Fichier attaché au message - Enregistrer un message vocal Annuler la réponse Enregistrer un message vocal Annuler l\'enregistrement du message vocal diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 01937613c..0f773030b 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -8,7 +8,6 @@ A naplók URL-je a vágólapra másolva Az adatvédelmi szabályzatunkat megtekintése &appName; névjegyek - Hiba Telefonszám Ország/régió kiválasztása Nemzetközi előhívószám @@ -24,15 +23,12 @@ %d nap %d nap - Érvénytelen e-mail-cím Ma Tegnap Forgalom UDP TCP TLS - MD5 - SHA-256 a szabad SIP-ügyfél Csatlakoztatva Nincs csatlakozva @@ -99,7 +95,6 @@ Kézbesítés állapota Törlés Fájl nem található - A csevegőszoba létrehozása sikertelen Csoport adatai Üzenetek törlése Elmúló üzenetek @@ -125,11 +120,8 @@ %s írnak… Szeretné továbbítani az üzenetet ebben a szobában\? - Beszélgetés kiválasztása vagy új létrehozása - Nem lehet megnyitni a fájlt, ehhez a formátumhoz nem áll rendelkezésre alkalmazás. Kimenő hívás Kimenő hívás - Konferencia Felhasználó elfoglalt Hálózat elérhetetlen A szolgáltatás nem érhető el vagy hálózati hiba történt @@ -154,14 +146,12 @@ Vibrálás tartalék: Kódoló: Lejátszói szűrő: - Megjelenítési szűrő: Rögzítési szűrő: Isten hozott! Fiók létrehozása &appName;-fiók használata SIP-fiók használata Távoli beállítások lekérése - A hívott fél szüneteltette a hívását Hogyan fog a telefonszámom használódni\? Telefonszámát csak egy &appName;-fiókjával használhatja.\n\nHa már összekapcsolta telefonszámát egy másik fiókkal, de inkább ezt használja, egyszerűen kapcsolja össze most, és a telefonszám önműködően átkerül erre a fiókra. Ez a felhasználónév már foglalt @@ -180,7 +170,6 @@ Visszaigazolási kód SIP-fiók használata Kérjük, adja meg felhasználónevét és jelszavát a SIP-tartományhoz - Kivonatoló algoritmus Jelszó megerősítése Kérjük, adjon meg egy felhasználónevet, e-mail-címet és jelszót a(z) &appName;-fiókhoz Kihagyás @@ -237,7 +226,6 @@ ZRTP DTLS Média titkosítása kötelező - Teljes képernyős alkalmazás hívás közben Hívás értesítés átfedése Átfedés megjelenítése az alkalmazáson kívül Megkérjük, hogy adjon átfedési engedélyt @@ -311,7 +299,6 @@ Hívásfogadás Megjelölés olvasottként Nem fogadott hívás - Távolítsa el a résztvevőt a konferenciáról Erősítse meg a következő SAS-kódot a hívott felével Elfogad Elutasítás @@ -323,13 +310,11 @@ Elfogad Elutasítás Hívás - Szüneteltesse a konferenciát Szüneteltesse a hívást Megszakítja a hívást Fogadja a hívást Mikrofonjának némításának feloldása Menjen vissza a hívás nézethez - Beszélgetések listájának megjelenítése A kapcsolattartó nem felügyelő ebben a beszélgetésben Az üzenetek elmúló ebben a beszélgetésben Elmúló időtartam kiválasztva @@ -341,7 +326,6 @@ Hangkimeneti menü megjelenítése vagy elrejtése Használja a Bluetooth-fejbeszélőt hang kimenetként Használja a fülhallgatót hang kimenetként - Speciális műveletek menü megjelenítése vagy elrejtése Új hívás indítása Az aktuális hívás átadása másnak Fájl átvitele függőben van @@ -365,7 +349,6 @@ Változtatások elvetése Névjegyfénykép módosítása Saját névjegyfénykép módosítása - Hívásirány &appName; szolgáltatás A(z) &appName; önműködően elindult Telefonszámok @@ -405,11 +388,9 @@ Végpontok közötti titkosítás engedélyezve Kattintson a visszhangkioltás beállításának megkezdéséhez Kattintson a visszhang ellenőrzés indításához - \nTelefonszámának köszönhetően ismerősei könnyebben megtalálják Önt.\n\nA címjegyzékében látni fogja, hogy ki használja a(z) &appName; és az ismerősök tudni fogják, hogy elérhetik Önt a(z) &appName; is.\n \nAz ismerősei könnyebben megtalálja Önt, ha összekapcsolja fiókját a telefonszámával.\n\nA címjegyzékében látni fogja, hogy ki használja a(z) &appName; és az ismerősök tudni fogják, hogy elérhetik Önt a(z) &appName; is.\n Ismerőslista feliratkozása Kérjük, adja meg a(z) &appName;-fiókjának felhasználónevét és jelszavát - Fiókját még nem érvényesítettük Országának nemzetközi előhívószáma Kérjük, erősítse meg nemzetközi előhívószámát, és adja meg telefonszámát Jelszó @@ -422,11 +403,9 @@ Lejátszás erősítése Mindig fogadja el a videó kéréseket Használja az eszköz csengőhangját - Elrejti az állapot és navigációs sávokat Hívásfogadások önműködően Cseng a bejövő korai média közben Üres csevegő szobák elrejtése - Leküldéses értesítések engedélyezése Kettős mód engedélyezve Önműködő Alapértelmezésként használja @@ -451,7 +430,6 @@ A hívás önműködően elindul, ha egy másik alkalmazásból indul Ha a megadott maximális méretnél könnyebb Ha engedélyezve van, a névjegyek parancsikonjai váltják fel - A Flexisip < 2.0 használatakor szükséges Mindig kérdezze meg, hogy melyik fiókba mentse az újonnan létrehozott névjegyet A változásokat a következő indításkor alkalmazzák Kiszolgáló feltöltés URL-jének naplói @@ -460,7 +438,6 @@ Ha beír egy számot, alkalmazza az előhívószámot Biztosan törli ezt az elemet\? Egy SIP-cím mező hozzáadása - Egyesítse az összes hívást egy konferenciába Hang átirányítása a fülhallgatóhoz Információs parancsikonok beszúrása a &appName; névjegyből a natív Android névjegyekbe Akár fájlok letöltéséhez csatolt beérkezett csevegő üzenetek önműködően vagy sem @@ -477,7 +454,6 @@ Ne szerkessze hacsak nem tudja, mit csinál! A bejövő fájlok önműködő letöltésének szabályzata Ha hiányoznak csevegőszobák, próbálja meg törölni ezt a beállítást - Használjon örökölt leküldéses értesítési paramétereket Jelenléti információk natív névjegyben Hozzon létre parancsikonokat a névjegyekhez az indítóban Helyettesíti a csevegőszoba parancsikonjait ha bármilyen @@ -509,12 +485,7 @@ Mutassa meg, hogy mire fogják használni a telefonszámát Ürítse ki a mellette lévő szöveg mezőt Váltás az elülső kameráról hátra és fordítva - Hívását várakoztattuk - Csúsztatás jobbra a hívás befejezéséhez - Csúsztatás balra a hívás fogadásához Hang átirányítása a hangszóróhoz - Folytassa a jelenleg tartott hívást - A számok megjelenítése vagy elrejtése a DTMF-ek küldéséhez Titkosított beszélgetésekbe meghívható partner Összes elem kijelölése a listán 2 @@ -547,7 +518,6 @@ Maximális hívásminőség A hívás biztosítva A hívásbiztonság függőben van - Hívás statisztikák megjelenítése vagy elrejtése Videómelléklet Az értesítési buborék bezárása A beszélgetés megnyitása az alkalmazásban a buborék helyett @@ -570,7 +540,6 @@ &appName; azonnali üzenetek értesítései Szeretné megpróbálni megnyitni egyszerű szöveges fájlként\? Engedélyezze vagy tiltsa le a hívás rögzítését - Folytassa a jelenleg tartott konferenciát Az ismerős egy &appName; felhasználó A kijelölt elemek törlése a listából 9 @@ -578,7 +547,6 @@ Az összes hívás megjelenítése Titkosított A hívás nincs biztosítva - Készítsen képernyőképet a kapott videóról Az elutasított hívások átirányítása a hangposta URI-ra Ugrás a hívás részleteire A lista összes elemének kijelölése megszüntetése @@ -587,14 +555,11 @@ Beszélgetés kiválasztása vagy létrehozása az üzenet továbbításához Beszélgetés kiválasztása vagy létrehozása a fájl(ok) megosztásához Az üzenet továbbításának visszavonása - Ideiglenes üzenetek engedélyezése (béta) Beszélgetés kiválasztása vagy létrehozása a szöveg megosztásához Üzenet továbbítása ebben a beszélgetésben Válasz Üzenet Tartsa lenyomva a gombot a hangüzenet felvételéhez - Az üzenethez mellékelt fájl - Hangüzenet felvétele Hangüzenet felvétele Hangfelvétel leállítása Hangfelvétel szüneteltetése diff --git a/app/src/main/res/values-night-v27/styles.xml b/app/src/main/res/values-night-v27/styles.xml new file mode 100644 index 000000000..1bba957ec --- /dev/null +++ b/app/src/main/res/values-night-v27/styles.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 83cce0c6d..6c75a077c 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -1,7 +1,7 @@ - + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 6b2460429..1ff7a8424 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -6,8 +6,6 @@ 传输 UDP 用户数据报协议 TLS安全传输层协议 - MD5哈希散列算法 - SHA-256加密算法 已连接 未连接 正在进行连接 @@ -102,8 +100,6 @@ 安卓通知设置 只使用WiFi 允许IPv6 - 允许推送通知 - 使用旧式推送通知参数 要使用的SIP端口 订阅好友列表 本机联系人中的状态信息 @@ -185,28 +181,18 @@ 返回 清空旁边的文本字段 从前置摄像头切换到后置摄像头,反之亦然 - 从会议中删除参与者 - 暂停会议 - 将所有通话合并到一个会议中 - 您的通话已被置于保留 启用或禁用通话录音 终止通话 - 向右滑动可终止通话 - 向左滑动以接听电话 接听来电 使麦克风静音 将音频传到扬声器 将音频传到耳机 返回通话视图 - 恢复当前保留的通话 - 恢复当前保留的会议 - 显示对话列表 禁用通话视频 启用通话视频 显示或隐藏音频输出菜单 使用蓝牙耳机为音频输出 使用耳机为音频输出 - 显示或隐藏高级操作菜单 发起新通话 将当前通话转接给其他人 待传输文件 @@ -240,7 +226,6 @@ 保存更改 更改联系人图片 更改自己的图像 - 通话方向 前往通话记录 退出编辑模式 选择列表中的所有项目 @@ -297,13 +282,11 @@ 最佳通话质量 通话不安全 安全通话 - 显示或隐藏通话统计 视频附件 来电时振动 自动接听时间 是否自动下载接收到的聊天信息中附加的文件 在启动器中创建至聊天室的快捷方式 - 使用Flexisip <2.0时需要 从&appName;联系人信息快捷方式插入Android本机通讯录 请勿随意编辑! 关联您的帐户 @@ -319,7 +302,6 @@ 显示您的电话号码将用于什么 暂停通话 取消麦克风静音 - 显示或隐藏要发送DTMF的数字 使用扬声器作为音频输出 信息是「阅后即焚」 端到端加密已禁用 @@ -337,7 +319,6 @@ &appName; 联系人 查看我们的隐私策略 日志链接已复制到剪贴板 - 出错 电话号码 电话号码 选择您的国家 @@ -355,7 +336,6 @@ %d 天 共享日志链接使用… - 邮箱无效 今日 退出 通话 @@ -422,8 +402,6 @@ 发送状态 发送状态 添加联系人 - 无法打开文件,没有适用于此格式的应用程序。 - 聊天室创建失败 组信息 对话中的设备 「阅后即焚」信息 @@ -445,11 +423,9 @@ 无录音 来电 去电 - 通话已被对方暂停 暂停通话 去电 通话中 - 会议 通话已被拒绝 用户忙线中 找不到用户 @@ -481,7 +457,6 @@ 密码错误 电子邮件地址无效 用户名过长 - 您的账户还未激活 账户不存在或密码不匹配 使用您的&appName;账户 请确认您的国家码再输入您的电话号码 @@ -494,7 +469,6 @@ 继续 使用SIP账户 显示名(可选) - 哈希算法 请为您的&appName;帐户输入用户名和密码 密码确认 接收方丢失率: @@ -532,15 +506,12 @@ 离开群组 已转发 是否要在此聊天室转发信息? - 选择一个对话或创建一个新的对话 短暂信息 群组创建失败 \@string/chat_room_delete_one_dialog 对方要求启用视讯 - 显示筛选器: - \n益于您的电话号码,您的朋友们可以更容易的找到您。\n\n您在通讯录里可以查看谁在使用&appName;,他们也同样可以通过&appName;联系您。\n \n如果将您的帐户关联您的电话号码,您的朋友们可以更容易的找到你。\n\n您在通讯录里可以查看谁在使用&appName;,他们也同样可以通过&appName;联系您。\n 请输入您的用户名与密码和您的SIP域名 您的帐户已经创建。请检查邮件以驗證您的帐户: @@ -553,7 +524,6 @@ 重启日志 显示名 用户名 - 启用短暂消息(测试版) 上传日志失败 查看配置文件 回复 @@ -571,8 +541,6 @@ DTLS 如果输入了数字,则将前缀应用于数字 留言即回复 - 录制音频信息 - 截取收到的视频截图 在应用程序中打开对话而不是气泡 日志已清除 回复 @@ -597,8 +565,6 @@ 隐私政策 我接受 Belledonne Communications 的 %1$s 和 %2$s 需要一些额外的权限 - 通话时全屏应用 - 隐藏状态栏和导航栏 在应用程序外显示叠加层 音频焦点丢失时暂停通话 自动开始通话录音 @@ -611,7 +577,6 @@ &appName; 未接来电通知 应用前缀于拨出的电话和聊天 在此对话中转发消息 - 附加到消息的文件 关闭通知气泡 在第三方应用程序中打开文件 取消消息转发 @@ -631,4 +596,4 @@ 消息将被删除 中止 我明白 - \ No newline at end of file + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index e1bf7eaa8..c6a3e16a4 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -10,7 +10,6 @@ &appName;聯絡人 訪問我們的隱私政策 日誌網址複製到剪貼板 - 錯誤 電話號碼 電話號碼 前綴 @@ -26,15 +25,12 @@ %d 天 - 郵箱無效 今日 昨日 傳輸 UDP用戶數據報協議 TCP傳輸控制協議 TLS安全傳輸層協議 - MD5 哈希散列算法 - SHA-256加密算法 已連接 未連接 正在進行連接 @@ -79,7 +75,6 @@ %s造成安全級別下降 參加人數上限超過%s人 您禁用了「閱後即焚」訊息 - 您開啟了「閱後即焚」訊息 「閱後即焚」訊息到期日:%s 下載 選擇源 @@ -117,9 +112,6 @@ 發送狀態 刪除 加入通訊錄 - 選擇一個對話或創建一個新的對話 - 無法打開文件,沒有適用於此格式的應用程序。 - 聊天室創建失敗 群組信息 對話中的設備 「閱後即焚」信息 @@ -142,11 +134,9 @@ 無錄音 來電 去電 - 通話已被對方暫停 暫停通話 去電 通話中 - 會議 用戶忙線中 找不到用戶 不兼容的媒體參數 @@ -169,7 +159,6 @@ 編碼器: 解碼器: 播放過濾器: - 顯示過濾器: 捕捉過濾器: 歡迎 創建帳戶 @@ -197,7 +186,6 @@ 使用SIP帳戶 請輸入您的用戶名、密碼和SIP域名 顯示名字(可選) - 哈希算法 確認密碼 您的帳戶已經創建。請檢查郵件以驗證您的帳戶: 一旦完成,請返回並點擊此處。 @@ -211,9 +199,7 @@ URL網址 不明的URL網址格式,無法下載配置資源… 助手將會幫助您配置和使用您的SIP帳戶。 - \n益於您的電話號碼,您的朋友們將更容易找到您。\n\n您在通訊錄裡可以查看誰在使用&appName;,他們也同樣可以通過&appName;聯繫您。\n 您的電話號碼只能鏈接一個&appName;賬戶。\n\n如果您已經把您的號碼鏈接了其他帳戶,但是您更想使用這個。只需鏈接您現在的賬戶,你的號碼就會自動轉移到這個賬戶。 - 您的帳戶尚未經驗證 帳戶不存在或密碼不匹配 繼續 請為您的&appName;帳戶輸入用戶名和密碼 @@ -293,7 +279,6 @@ 重疊來電通知 已開始回声消除器校准 迴聲測試已停止 - 使用舊式推通知參數 訂閱好友列表 在啟動器中創建聯絡人的快捷方式 顯示通知以使應用程序保持活動狀態 @@ -310,9 +295,7 @@ 確認您的對話者說: 安全的對話中的即時短信是被端到端加密的。通過驗證參與者可以提高對話的安全級別。為此,請致電聯繫人並遵循身份驗證過程。 從原始攝像頭切換到後置攝像頭,反之亦然 - 向左滑以接聽來電 取消麥克風靜音 - 恢復當前保留的通話 顯示或隱藏音頻輸出菜單 將當前的通話轉接給別人 端到端加密已禁用 @@ -346,8 +329,6 @@ 安卓通知設置 只使用Wi-Fi 允許IPv6 - 允許推送通知 - 使用Flexisip <2.0時需要 使用隨機端口 要使用的SIP端口 本機聯繫人中的狀態信息 @@ -444,28 +425,19 @@ 返回 顯示您的電話號碼將用於什麼 清空旁邊的文本字段 - 從會議中刪除參與者 - 暫停會議 - 將所有通話合併到一個會議中 - 您的通話已被置於保留 暫停通話 啟用或禁用通話錄音 終止通話 - 向右滑可終止通話 接聽來電 使麥克風靜音 將音頻傳到揚聲器 將音頻傳到耳機 返回通話視圖 - 恢復當前保留的會議 - 顯示或隱藏要發送DTMF的數字 - 顯示對話列表 啟用通話視頻 禁用通話視頻 使用藍芽耳機為音頻輸出 使用耳機為音頻輸出 使用揚聲器為音頻輸出 - 顯示或隱藏高級操作菜單 發起新通話 待傳輸文件 刪除待傳輸文件 @@ -498,7 +470,6 @@ 保存更改 更改聯絡人圖片 更改自己的圖像 - 通話方向 前往通訊錄 退出編輯模式 選擇列表中的所有項目 @@ -540,7 +511,6 @@ 通話不安全 安全通話 通話安全性待定 - 顯示後隱藏通話統計 視頻附件 顯示名 用戶名 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index fc3b14770..cac2445e5 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -13,6 +13,8 @@ + + @@ -23,12 +25,23 @@ - + + + + + + + + + + + + @@ -49,4 +62,23 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3bbc1bda6..83700c7fa 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -15,11 +15,34 @@ #ffffff #ff0000 #96c11f + #7d9f21 + + #798791 + #D0D8DE + #4B5964 + #96A5B1 + #D8D8D8 + #EBEBEB + #F0F1F2 + #A64B5964 + #E64B5964 + #E4E4E4 + #AFAFAF + #303030 + #A6B2BC + #252E35 + #3F464B + #475663 + #2D3841 + #353B3F + #6E8596 + #A2A2A2 + + #F7F7F7 #3eb5c0 #e1e1e1 #f3f3f3 - #c2c2c2 #a1a1a1 #f3f3f3 diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 9344f15d7..0098f2806 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -8,7 +8,6 @@ 200dp 100dp 120dp - 30dp 50dp 600dp 200dp @@ -19,9 +18,6 @@ 5dp 5dp 35dp - 60dp - 300dp - 240dp 20dp 3dp 60dp @@ -38,4 +34,26 @@ 8sp 14sp 1sp + 50dp + 10dp + 0dp + 60dp + 80dp + 50dp + 250dp + 60dp + 200dp + 210dp + 60dp + 50dp + 260dp + 350dp + 400dp + 390dp + 20dp + 5dp + 100dp + 10dp + 60dp + 50dp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d1782128e..fca3b40ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -31,7 +31,6 @@ linphone-android@belledonne-communications.com - Error Phone number Phone numbers Select your country @@ -52,7 +51,6 @@ Share logs link using… - Invalid email Today @@ -63,8 +61,6 @@ UDP TCP TLS - MD5 - SHA-256 the libre SIP client @@ -87,6 +83,7 @@ Calls No call in your history No missed call in your history + No conference call in your history Do you want to delete this record? Do you want to delete these records? @@ -133,6 +130,8 @@ @string/debug_popup_show_config_file @string/cancel + Call is being transferred + Can\'t transfer call No conversations @@ -181,13 +180,10 @@ Add to contacts Do you want to forward the message in this room? dummy subject - Select a conversation or create a new one Select or create a conversation to forward the message Select or create a conversation to share the file(s) Select or create a conversation to share the text - Can\'t open file, no application available for this format. File not found - Chat room creation failed Group info Conversation\'s devices Ephemeral messages @@ -229,18 +225,72 @@ Message will be deleted Abort + Failed to create chat room No recordings + + Conference + Start a conference + Do you want to schedule this conference for later? + Mandatory + Subject + Conference subject + Conference address + Add a description + Description + Date + Time + Duration + Timezone + Send invite via &appName; + Send invite via email + Would you like to encrypt the conference? + Invite will be sent out from my &appName; account + Participants list + Conference info + Create conference + Schedule conference + Conference address copied into clipboard + Failed to create conference! + Failed to send conference info to a participant + You are currently out of the conference. + Click on play button to join it back. + Remote conference + Local conference + Conference invite: + Description: + Join + %d participants + Mosaic mode + Active speaker mode + Start + Cancel + Video is currently disabled + Conferences + You can\'t change conference layout as there is too many participants + There is too many participants for mosaic layout, switching to active speaker + (paused) + No scheduled conference yet. + Organizer: + Conference\'s chat room + Failed to create conference + %s is now admin + %s is no longer admin + Conference has been scheduled + Do you really want to delete this conference? + Conference info has been deleted + You are currently alone in this conference + You have been invited to a conference + Conference invitation + Incoming Call Outgoing Call - Call has been paused by your correspondent Paused call Outgoing call Call running - Conference Call has been declined User is busy User hasn\'t been found @@ -251,6 +301,23 @@ Temporarily unavailable Error: %s Correspondent would like to turn the video on + Participants list + Chat + Calls list + Numpad + Change layout + Call statistics + Start new call + Transfer call + Resume call + Pause call + Transfer call + Answer call + Terminate call + This call is being recorded. + Call has been paused by remote. + You have paused the call. + Click on play button to resume it. Audio @@ -271,7 +338,6 @@ Encoder: Decoder: Player filter: - Display filter: Capture filter: @@ -283,7 +349,6 @@ Fetch remote configuration Echo canceler calibration in progress What will my phone number be used for? - \nThanks to your phone number, your friends will find you more easily.\n\n You will see in your address book who is using &appName; and your friends will know that they can reach you on &appName; as well.\n \nYour friends will find you more easily if you link your account to your phone number\n\nYou will see in your address book who is using &appName; and your friends will know that they can reach you on &appName; as well.\n You can only use your phone number with one &appName; account.\n\nIf you had already linked your number to an other account but you prefer to use this one, simply link it now and your number will automatically be moved to this account. https://www.linphone.org/general-terms @@ -303,7 +368,6 @@ Passwords do not match Email address is invalid Username has too many characters - Your account has not been validated yet Account does not exist or password does not match @@ -322,7 +386,6 @@ Use SIP account Please enter your username and password with your SIP domain Display name (optional) - Hash algorithm Please enter a username, email and password for your &appName; account @@ -359,6 +422,7 @@ Network Contacts Advanced + Conferences Primary Account Display Name Username @@ -417,8 +481,6 @@ Media encryption mandatory Improve interactions with bluetooth devices Requires some extra permissions - Full screen app while in call - Hides status and navigation bars Overlay call notification Show overlay outside of app You will be asked to grant overlay permission @@ -438,6 +500,7 @@ Pause calls when audio focus is lost Android notification settings Automatically start call recording + Get notified when call is being recorded by your correspondent Mark as read upon notification dismissal @@ -462,15 +525,11 @@ Android notification settings Always open files inside this app You\'ll still be able to export them in third-party apps - Enable ephemeral messages (beta) Auto download incoming voice recordings Use WiFi only Allow IPv6 - Enable push notifications - Use legacy push notification params - Required when using Flexisip < 2.0 Use random ports SIP port to use @@ -564,6 +623,11 @@ Apply prefix for outgoing calls and chat If a number is entered, apply prefix to number Replace + by 00 + Conference factory URI + Audio/video conference factory URI + + + Default layout linphone_notification_service_id @@ -616,32 +680,21 @@ Show what your phone number will be used for Empty the text field next to it Switch from front camera to back and vice versa - Remove participant from the conference - Pause the conference - Merge all calls into one conference - Your call has been put on hold Pause the call Enable or disable the recording of the call Terminate the call - Slide to the right to terminate the call - Slide to the left to answer the call Answer the call Mute your microphone Un-mute your microphone Route audio to the speaker Route audio to the earpiece Go back to the call view - Resume the call currently on hold - Resume the conference currently on hold - Display or hide the digits to send DTMFs - Display the conversations list Enable video in call Disable video in call Show or hide audio output menu Use bluetooth headset as audio output Use earpiece as audio output Use speaker as audio output - Show or hide advanced actions menu Start a new call Transfer current call to someone else Pending file transfer @@ -664,9 +717,7 @@ Show chat room menu Enter edition mode Attach a file to the message - File attached to the message Send message - Record audio message Show or hide the participant devices Ephemeral duration selected Change ephemeral duration by selected value @@ -685,7 +736,6 @@ Save changes Change contact picture Change own picture - Call direction Go to call details Quit edition mode Select all items in list @@ -731,9 +781,7 @@ Call is not secured Call is secured Call security is pending - Show or hide call statistics Video attachment - Take a screenshot of received video Close notification bubble Open conversation in app instead of bubble Open file in third-party app @@ -746,4 +794,15 @@ Pause voice recording Play voice recording Scroll to bottom or first unread message + Open call context menu + Create a conference call + Copy conference address + Edit conference + Delete conference + Conference will be encrypted + Export event to calendar + Hide call statistics + Show numpad + Hide numpad + Go to conversations list diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 276db38de..5e950e187 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -7,8 +7,7 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + diff --git a/gradle.properties b/gradle.properties index fd519955b..f9cfc7eac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx4096m +org.gradle.jvmargs=-Xmx8192m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects