Browse Source

Merge branch 'audio-player-446'

master
Igor Zhukov 10 years ago
parent
commit
bf00606f8d
  1. 186
      app/css/app.css
  2. 67
      app/css/desktop.css
  3. 179
      app/css/mobile.css
  4. BIN
      app/img/icons/IconsetW.png
  5. BIN
      app/img/icons/IconsetW_1x.png
  6. BIN
      app/img/screenshot1_tile.png
  7. 1
      app/index.html
  8. 1
      app/js/app.js
  9. 87
      app/js/directives.js
  10. 1
      app/js/filters.js
  11. 1
      app/js/locales/en-us.json
  12. 26
      app/js/services.js
  13. 31
      app/partials/desktop/audio_player.html
  14. 6
      app/partials/desktop/message.html
  15. 32
      app/partials/desktop/message_attach_audio.html
  16. 4
      app/partials/desktop/message_attach_document.html
  17. 1
      app/partials/mobile/audio_player.html
  18. 15
      app/partials/mobile/message.html
  19. 31
      app/partials/mobile/message_attach_audio.html
  20. 4
      app/partials/mobile/message_attach_document.html
  21. 510
      app/vendor/angular-media-player/angular-media-player.js
  22. 2
      app/webogram.appcache

186
app/css/app.css

@ -425,7 +425,7 @@ input[type="number"] {
.modal-close-button i { .modal-close-button i {
display: inline-block; display: inline-block;
background: url(../img/icons/IconsetW.png) -15px -320px no-repeat; background: url(../img/icons/IconsetW.png) -15px -320px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
width: 12px; width: 12px;
height: 12px; height: 12px;
margin: 21px; margin: 21px;
@ -877,7 +877,7 @@ input.tg_range::-moz-range-thumb {
font-size: 12px; font-size: 12px;
line-height: normal; line-height: normal;
background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat; background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
border: 1px solid #F2F2F2; border: 1px solid #F2F2F2;
border-radius: 3px; border-radius: 3px;
padding: 6px 20px 6px 30px; padding: 6px 20px 6px 30px;
@ -905,7 +905,7 @@ input.tg_range::-moz-range-thumb {
height: 13px; height: 13px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -192px no-repeat; background: url(../img/icons/IconsetW.png) -15px -192px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
opacity: 0.6; opacity: 0.6;
} }
.is_1x .im_dialogs_search_clear { .is_1x .im_dialogs_search_clear {
@ -1111,7 +1111,7 @@ a.im_dialog_selected .im_dialog_date {
margin-left: 6px; margin-left: 6px;
background: url(../img/icons/IconsetW.png) -17px -444px no-repeat; background: url(../img/icons/IconsetW.png) -17px -444px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
} }
.is_1x .icon-caret { .is_1x .icon-caret {
background-image: url(../img/icons/IconsetW_1x.png); background-image: url(../img/icons/IconsetW_1x.png);
@ -1194,57 +1194,7 @@ a.im_dialog_selected .im_dialog_date {
.non_osx .im_message_fwd_author { .non_osx .im_message_fwd_author {
font-size: 12px; font-size: 12px;
} }
.im_grouped_short .im_message_from_photo,
.im_grouped_short .im_message_author,
.im_grouped .im_message_from_photo,
.im_grouped .im_message_author,
.im_grouped_fwd .im_message_author,
.im_grouped_fwd .im_message_from_photo,
.im_grouped_fwd_short .im_message_author,
.im_grouped_fwd_short .im_message_from_photo {
display: none;
}
.im_grouped_short .im_message_body,
.im_grouped .im_message_body,
.im_grouped_fwd .im_message_body,
.im_grouped_fwd_short .im_message_body {
margin-left: 46px;
}
.im_grouped_short .im_content_message_select_area,
.im_grouped .im_content_message_select_area {
height: 34px;
}
.im_history_appending .im_content_message_select_area {
height: 52px;
}
.im_grouped_short .icon-select-tick,
.im_grouped_fwd_short .icon-select-tick {
margin-top: 5px;
}
.im_grouped_short .icon-message-status,
.im_grouped_fwd_short .icon-message-status {
margin-top: 5px;
}
.im_grouped_fwd .im_message_fwd_from,
.im_grouped_fwd_short .im_message_fwd_from {
display: none;
}
.im_grouped_short .im_message_fwd .im_message_date,
.im_grouped .im_message_fwd .im_message_date,
.im_grouped_fwd .im_message_fwd .im_message_date,
.im_grouped_fwd_short .im_message_fwd .im_message_date {
display: none;
}
.im_grouped_fwd .im_message_fwd,
.im_grouped_fwd_short .im_message_fwd {
margin-top: 8px;
}
.im_grouped_fwd .im_message_fwd,
.im_grouped_fwd_short .im_message_fwd {
margin-top: 8px;
}
.im_message_from_photo, .im_message_from_photo,
.im_message_contact_photo, .im_message_contact_photo,
@ -1323,7 +1273,7 @@ div.im_message_video_thumb {
height: 42px; height: 42px;
background: url(../img/icons/IconsetW.png) 0 -590px no-repeat; background: url(../img/icons/IconsetW.png) 0 -590px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
z-index: 1; z-index: 1;
} }
.is_1x .icon-videoplay { .is_1x .icon-videoplay {
@ -1350,7 +1300,7 @@ div.im_message_video_thumb {
height: 19px; height: 19px;
background: url(../img/icons/IconsetW.png) -14px -389px no-repeat; background: url(../img/icons/IconsetW.png) -14px -389px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
} }
.is_1x .icon-geo-point { .is_1x .icon-geo-point {
background-image: url(../img/icons/IconsetW_1x.png); background-image: url(../img/icons/IconsetW_1x.png);
@ -1392,13 +1342,15 @@ div.im_message_video_thumb {
} }
.im_message_document, .im_message_document,
.im_message_audio,
.im_message_upload_file { .im_message_upload_file {
margin-top: 3px; margin-top: 3px;
border-radius: 3px; border-radius: 3px;
display: inline-block; display: inline-block;
width: 340px; width: 340px;
} }
.im_message_audio {
margin-top: 3px;
}
.icon-document, .icon-document,
.icon-photo, .icon-photo,
.icon-video { .icon-video {
@ -1409,7 +1361,7 @@ div.im_message_video_thumb {
vertical-align: text-top; vertical-align: text-top;
background: #F2F2F2 url(../img/icons/IconsetW.png) -2px -229px no-repeat; background: #F2F2F2 url(../img/icons/IconsetW.png) -2px -229px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
border-radius: 3px; border-radius: 3px;
margin-right: 10px; margin-right: 10px;
} }
@ -1478,44 +1430,39 @@ img.im_message_document_thumb {
color: #999; color: #999;
padding-left: 2px; padding-left: 2px;
} }
.im_message_document_actions a { .im_message_document_actions a,
.audio_player_actions a {
margin-right: 10px; margin-right: 10px;
} }
.audio_player_button {
.icon-audio {
display: block;
float: left;
width: 38px; width: 38px;
height: 38px; height: 38px;
vertical-align: text-top;
background: #F2F2F2 url(../img/icons/IconsetW.png) -2px -277px no-repeat;
background-size: 42px 891px;
border-radius: 3px; border-radius: 3px;
margin-right: 10px; margin-right: 12px;
}
.audio_player_btn_icon {
display: block;
width: 13px;
height: 18px;
background: url(../img/icons/IconsetW.png) -15px -898px no-repeat;
background-size: 42px 971px;
} }
.is_1x .icon-audio { .is_1x .audio_player_btn_icon {
background-image: url(../img/icons/IconsetW_1x.png); background-image: url(../img/icons/IconsetW_1x.png);
} }
.im_message_selected .icon-audio, .audio_player_btn_icon_pause {
.im_history_selectable .im_message_outer_wrap:hover .icon-audio { background-position: -15px -924px;
background-color: #dae6f0;
background-position: -2px -697px;
} }
.audio_player_btn_icon_cancel {
background-position: -15px -948px;
.im_message_audio_info {
float: left;
width: 292px;
} }
.im_message_audio_name_wrap { .audio_player_title_wrap {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
width: 290px;
padding: 0 0 1px; padding: 0 0 1px;
} }
.im_message_audio_name { .audio_player_title {
color: #222; color: #222;
display: inline-block; display: inline-block;
font-weight: bold; font-weight: bold;
@ -1525,25 +1472,33 @@ img.im_message_document_thumb {
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.im_message_audio_duration, .audio_player_title:hover {
.im_message_audio_size { color: #222;
}
.audio_player_meta {
overflow: hidden;
vertical-align: text-top;
display: inline-block;
}
.audio_player_size,
.audio_player_duration {
color: #999; color: #999;
padding-left: 2px; padding-left: 2px;
} }
.im_message_audio_info audio {
width: 100%;
height: 38px;
line-height: 38px;
}
.im_message_upload_progress_wrap, .im_message_upload_progress_wrap,
.im_message_download_progress_wrap { .im_message_download_progress_wrap {
margin-top: 5px; margin-top: 5px;
width: 290px; width: 290px;
} }
.audio_player_progress_wrap {
margin-top: 5px;
max-width: 290px;
border-radius: 2px;
overflow: hidden;
}
.im_message_document_thumbed .im_message_document_name_wrap, .im_message_document_thumbed .im_message_document_name_wrap,
.im_message_document_thumbed .im_message_audio_name_wrap,
.im_message_document_thumbed .im_message_upload_progress_wrap, .im_message_document_thumbed .im_message_upload_progress_wrap,
.im_message_document_thumbed .im_message_download_progress_wrap { .im_message_document_thumbed .im_message_download_progress_wrap {
width: 230px; width: 230px;
@ -1566,7 +1521,8 @@ img.im_message_document_thumb {
} }
.tg_up_progress, .tg_up_progress,
.tg_down_progress { .tg_down_progress,
.tg_play_progress {
height: 5px; height: 5px;
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -1577,7 +1533,8 @@ img.im_message_document_thumb {
box-shadow: none; box-shadow: none;
} }
.tg_up_progress .progress-bar, .tg_up_progress .progress-bar,
.tg_down_progress .progress-bar { .tg_down_progress .progress-bar,
.tg_play_progress .progress-bar {
height: 5px; height: 5px;
line-height: 5px; line-height: 5px;
background: #6B9ABD; background: #6B9ABD;
@ -1586,6 +1543,18 @@ img.im_message_document_thumb {
-webkit-box-shadow: none; -webkit-box-shadow: none;
box-shadow: none; box-shadow: none;
} }
.tg_play_progress {
background: #e4e9ed;
border-radius: 2px;
}
.tg_play_progress .progress-bar {
background: #628fb2;
border-radius: 2px;
/*-webkit-transition: width 1s linear;
transition: width 1s linear;*/
-webkit-transition: none;
transition: none;
}
@ -1626,24 +1595,11 @@ div.im_message_body {
display: block; display: block;
overflow: hidden; overflow: hidden;
} }
.im_message_fwd_from {
margin-top: 5px;
}
.im_grouped .im_message_fwd_from {
margin-top: 0;
}
.im_message_fwd {
margin-top: 4px;
margin-bottom: 4px;
}
a.im_message_fwd_photo { a.im_message_fwd_photo {
position: absolute; position: absolute;
margin-top: 1px; margin-top: 1px;
} }
.im_message_fwd_author {
margin-right: 5px;
}
.im_message_fwd_date { .im_message_fwd_date {
padding: 0; padding: 0;
} }
@ -1758,7 +1714,7 @@ textarea.im_message_field {
height: 23px; height: 23px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -12px -68px no-repeat; background: url(../img/icons/IconsetW.png) -12px -68px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
opacity: 0.8; opacity: 0.8;
} }
.is_1x .icon-paperclip { .is_1x .icon-paperclip {
@ -1786,7 +1742,7 @@ textarea.im_message_field {
height: 23px; height: 23px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -10px -4px no-repeat; background: url(../img/icons/IconsetW.png) -10px -4px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
opacity: 0.8; opacity: 0.8;
} }
.is_1x .icon-emoji { .is_1x .icon-emoji {
@ -1835,7 +1791,7 @@ textarea.im_message_field {
height: 21px; height: 21px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -9px -132px no-repeat; background: url(../img/icons/IconsetW.png) -9px -132px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
opacity: 0.8; opacity: 0.8;
} }
.is_1x .icon-camera { .is_1x .icon-camera {
@ -2174,7 +2130,7 @@ a:hover .icon-twitter {
font-size: 12px; font-size: 12px;
line-height: normal; line-height: normal;
background: url(../img/icons/IconsetW.png) -6px -205px no-repeat; background: url(../img/icons/IconsetW.png) -6px -205px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
border: 1px solid #d9dbde; border: 1px solid #d9dbde;
border-radius: 3px; border-radius: 3px;
padding: 6px 15px 6px 30px; padding: 6px 15px 6px 30px;
@ -2193,7 +2149,7 @@ a:hover .icon-twitter {
height: 13px; height: 13px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -192px no-repeat; background: url(../img/icons/IconsetW.png) -15px -192px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
opacity: 0.6; opacity: 0.6;
} }
.is_1x .contacts_modal_search_clear { .is_1x .contacts_modal_search_clear {
@ -2309,7 +2265,7 @@ img.chat_modal_participant_photo {
width: 25px; width: 25px;
height: 25px; height: 25px;
background: url(../img/icons/IconsetW.png) -9px -516px no-repeat; background: url(../img/icons/IconsetW.png) -9px -516px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
opacity: 0.5; opacity: 0.5;
} }
.is_1x .icon-contact-tick { .is_1x .icon-contact-tick {
@ -2378,8 +2334,8 @@ img.chat_modal_participant_photo {
.im_message_focus .im_message_date, .im_message_focus .im_message_date,
.im_message_focus .im_message_document_size, .im_message_focus .im_message_document_size,
.im_message_focus .im_message_audio_duration, .im_message_focus .audio_player_duration,
.im_message_focus .im_message_audio_size, .im_message_focus .audio_player_size,
.im_message_focus .im_message_fwd_date { .im_message_focus .im_message_fwd_date {
color: #68839c; color: #68839c;
} }
@ -2390,7 +2346,7 @@ img.chat_modal_participant_photo {
height: 26px; height: 26px;
margin: 13px 0 0 40px; margin: 13px 0 0 40px;
background: url(../img/icons/IconsetW.png) -9px -516px no-repeat; background: url(../img/icons/IconsetW.png) -9px -516px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
} }
.is_1x .icon-select-tick { .is_1x .icon-select-tick {
background-image: url(../img/icons/IconsetW_1x.png); background-image: url(../img/icons/IconsetW_1x.png);
@ -2509,7 +2465,7 @@ ce671b orange
font-size: 12px; font-size: 12px;
line-height: normal; line-height: normal;
background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat; background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
border: 1px solid #F2F2F2; border: 1px solid #F2F2F2;
border-radius: 3px; border-radius: 3px;
padding: 6px 20px 6px 30px; padding: 6px 20px 6px 30px;
@ -2532,7 +2488,7 @@ ce671b orange
height: 13px; height: 13px;
vertical-align: text-top; vertical-align: text-top;
background: url(../img/icons/IconsetW.png) -15px -192px no-repeat; background: url(../img/icons/IconsetW.png) -15px -192px no-repeat;
background-size: 42px 891px; background-size: 42px 971px;
opacity: 0.6; opacity: 0.6;
} }
.is_1x .countries_modal_search_clear { .is_1x .countries_modal_search_clear {

67
app/css/desktop.css

@ -462,6 +462,9 @@ a.footer_lang_link.active:active {
} }
.im_message_fwd_author {
margin-right: 5px;
}
.im_message_fwd .im_message_fwd_author_wrap, .im_message_fwd .im_message_fwd_author_wrap,
.im_message_fwd .im_message_text, .im_message_fwd .im_message_text,
.im_message_fwd .im_message_media { .im_message_fwd .im_message_media {
@ -788,6 +791,70 @@ div.im_panel_own_photo {
} }
} }
/* Groupings */
.im_message_fwd_from {
margin-top: 5px;
}
.im_grouped .im_message_fwd_from {
margin-top: 0;
}
.im_message_fwd {
margin-top: 4px;
margin-bottom: 4px;
}
.im_grouped_short .im_message_from_photo,
.im_grouped_short .im_message_author,
.im_grouped .im_message_from_photo,
.im_grouped .im_message_author,
.im_grouped_fwd .im_message_author,
.im_grouped_fwd .im_message_from_photo,
.im_grouped_fwd_short .im_message_author,
.im_grouped_fwd_short .im_message_from_photo {
display: none;
}
.im_grouped_short .im_message_body,
.im_grouped .im_message_body,
.im_grouped_fwd .im_message_body,
.im_grouped_fwd_short .im_message_body {
margin-left: 46px;
}
.im_grouped_short .im_content_message_select_area,
.im_grouped .im_content_message_select_area {
height: 34px;
}
.im_history_appending .im_content_message_select_area {
height: 52px;
}
.im_grouped_short .icon-select-tick,
.im_grouped_fwd_short .icon-select-tick {
margin-top: 5px;
}
.im_grouped_short .icon-message-status,
.im_grouped_fwd_short .icon-message-status {
margin-top: 5px;
}
.im_grouped_fwd .im_message_fwd_from,
.im_grouped_fwd_short .im_message_fwd_from {
display: none;
}
.im_grouped_short .im_message_fwd .im_message_date,
.im_grouped .im_message_fwd .im_message_date,
.im_grouped_fwd .im_message_fwd .im_message_date,
.im_grouped_fwd_short .im_message_fwd .im_message_date {
display: none;
}
.im_grouped_fwd .im_message_fwd,
.im_grouped_fwd_short .im_message_fwd {
margin-top: 8px;
}
.im_grouped_fwd .im_message_fwd,
.im_grouped_fwd_short .im_message_fwd {
margin-top: 8px;
}
.tooltip-inner { .tooltip-inner {
background: rgba(0,0,0, 0.8); background: rgba(0,0,0, 0.8);
} }

179
app/css/mobile.css

@ -39,8 +39,6 @@ html {
} }
.tg_page_head .navbar-inverse .navbar-toggle:hover, .tg_page_head .navbar-inverse .navbar-toggle:hover,
/*.tg_page_head .navbar-inverse .navbar-toggle:active,*/
.tg_page_head .navbar-inverse .navbar-toggle:focus,
.tg_page_head .navbar-inverse .open .navbar-toggle { .tg_page_head .navbar-inverse .open .navbar-toggle {
background-color: rgba(0,0,0,0.1); background-color: rgba(0,0,0,0.1);
} }
@ -331,14 +329,12 @@ html {
} }
.im_message_body, .im_message_body {
.im_message_document {
padding: 7px 10px; padding: 7px 10px;
border-radius: 3px; border-radius: 3px;
background: #f1f1f1; background: #f1f1f1;
} }
.im_message_out .im_message_body, .im_message_out .im_message_body {
.im_message_out .im_message_document {
background: #e4ecf2; background: #e4ecf2;
} }
.im_message_body_media, .im_message_body_media,
@ -348,7 +344,8 @@ html {
background: none; background: none;
} }
.im_message_selected .im_message_body, .im_message_selected .im_message_body,
.im_message_selected .im_message_document { .im_message_selected .im_message_document,
.im_message_selected .im_message_audio {
background: #497495; background: #497495;
color: #FFF; color: #FFF;
} }
@ -357,68 +354,44 @@ html {
color: inherit; color: inherit;
} }
.im_message_fwd .im_message_body, .im_service_message {
.im_message_fwd .im_message_document { font-size: 13px;
padding: 7px 8px;
}
.im_grouped_fwd_start .im_message_body,
.im_grouped_fwd_short .im_message_body,
.im_grouped_fwd .im_message_body,
.im_grouped .im_message_fwd .im_message_body,
.im_grouped_fwd_start .im_message_document,
.im_grouped_fwd_short .im_message_document,
.im_grouped_fwd .im_message_document,
.im_grouped .im_message_fwd .im_message_document {
border-radius: 0;
}
.im_grouped_fwd_start .im_message_fwd .im_message_body,
.im_grouped_fwd_start .im_message_fwd .im_message_document {
border-radius: 3px 3px 0 0;
} }
.im_grouped_fwd_end .im_message_fwd .im_message_body, .audio_player_button {
.im_grouped_fwd_end .im_message_fwd .im_message_document { margin-right: 8px;
border-radius: 0 0 3px 3px;
} }
.im_message_body_media .im_message_document,
.im_message_fwd .im_message_text { .im_message_body_media .im_message_audio {
min-height: 21px; padding: 5px;
border-radius: 3px;
background: #f1f1f1;
margin-top: 0;
} }
.im_grouped_fwd .im_message_text, .im_message_body_media a.im_message_photo_thumb,
.im_grouped_fwd_short .im_message_text { .im_message_body_media .im_message_video {
min-height: 0; margin-top: 0;
} }
.im_message_out .im_message_body_media .im_message_document,
.im_service_message { .im_message_out .im_message_body_media .im_message_audio {
font-size: 13px; background: #e4ecf2;
} }
.im_message_document { .im_message_document {
padding: 5px;
width: auto; width: auto;
max-width: 250px; max-width: 250px;
margin-top: 0;
} }
.im_message_audio,
.im_message_document { .im_message_document {
position: relative; position: relative;
} }
.im_message_audio > a,
.im_message_document .icon-document, .im_message_document .icon-document,
.im_message_audio .icon-audio,
.im_message_document_thumb_wrap { .im_message_document_thumb_wrap {
position: absolute; position: absolute;
} }
.im_message_out .im_message_audio .icon-audio,
.im_message_out .im_message_document .icon-document { .im_message_out .im_message_document .icon-document {
background-color: #e4ecf2; background-color: #e4ecf2;
} }
.im_message_document_thumb_wrap { .im_message_document_thumb_wrap {
background-color: transparent; background-color: transparent;
} }
.im_message_audio_done i {
display: none;
}
.im_message_audio_info,
.im_message_document_info { .im_message_document_info {
float: none; float: none;
margin-left: 43px; margin-left: 43px;
@ -428,25 +401,6 @@ html {
.im_message_document_thumbed .im_message_document_info { .im_message_document_thumbed .im_message_document_info {
margin-left: 110px; margin-left: 110px;
} }
.im_message_audio_name,
.im_message_audio_duration {
display: inline;
vertical-align: baseline;
line-height: 38px;
}
.im_message_audio_progress .im_message_audio_name,
.im_message_audio_progress .im_message_audio_duration {
line-height: 18px;
}
.im_message_audio_done .im_message_audio_info {
margin-left: 0;
width: auto;
float: none;
}
.im_message_audio_player_wrap {
height: 38px;
line-height: 38px;
}
.im_message_document_size { .im_message_document_size {
display: block; display: block;
padding-left: 0; padding-left: 0;
@ -454,13 +408,11 @@ html {
.im_message_document_thumbed .im_message_document_info { .im_message_document_thumbed .im_message_document_info {
min-height: 100px; min-height: 100px;
} }
.im_message_document_info .cancelable_progress_wrap, .im_message_document_info .cancelable_progress_wrap {
.im_message_audio_info .cancelable_progress_wrap {
margin-top: 4px; margin-top: 4px;
} }
.im_message_document_thumbed .im_message_document_name_wrap, .im_message_document_thumbed .im_message_document_name_wrap,
.im_message_document_name_wrap, .im_message_document_name_wrap {
.im_message_audio_name_wrap {
width: auto; width: auto;
} }
.im_message_document_progress .im_message_document_size { .im_message_document_progress .im_message_document_size {
@ -504,10 +456,7 @@ img.im_message_video_thumb,
.im_message_document_thumb_wrap { .im_message_document_thumb_wrap {
position: absolute; position: absolute;
} }
.im_grouped_short .im_message_body, .im_grouped .im_message_body {
.im_grouped .im_message_body,
.im_grouped_fwd .im_message_body,
.im_grouped_fwd_short .im_message_body {
margin-left: 0; margin-left: 0;
} }
@ -551,42 +500,19 @@ img.im_message_video_thumb,
padding-left: 0; padding-left: 0;
padding-right: 42px; padding-right: 42px;
} }
.im_message_fwd.im_content_message_wrap {
float: none;
}
.im_history_messages_group .im_message_in.im_content_message_wrap, .im_history_messages_group .im_message_in.im_content_message_wrap,
.im_history_messages_group .im_grouped_short .im_message_in.im_content_message_wrap, .im_history_messages_group .im_grouped_short .im_message_in.im_content_message_wrap,
.im_history_messages_group .im_grouped .im_message_in.im_content_message_wrap, .im_history_messages_group .im_grouped .im_message_in.im_content_message_wrap {
.im_history_messages_group .im_grouped_fwd .im_message_in.im_content_message_wrap,
.im_history_messages_group .im_grouped_fwd_short .im_message_in.im_content_message_wrap {
padding-left: 45px; padding-left: 45px;
} }
.im_grouped_short .im_message_out.im_message_fwd,
.im_grouped .im_message_out.im_message_fwd,
.im_grouped_fwd .im_message_out.im_message_fwd,
.im_grouped_fwd_short .im_message_out.im_message_fwd {
padding-left: 80px;
}
.im_grouped_short .im_content_message_wrap, .im_grouped_short .im_content_message_wrap,
.im_grouped .im_content_message_wrap { .im_grouped .im_content_message_wrap {
margin-top: 0; margin-top: 0;
} }
.im_grouped_fwd_start .im_message_fwd.im_content_message_wrap, .im_message_fwd_header {
.im_grouped_short .im_message_fwd.im_content_message_wrap, font-size: 12px;
.im_grouped .im_message_fwd.im_content_message_wrap,
.im_grouped_fwd .im_message_fwd.im_content_message_wrap,
.im_grouped_fwd_short .im_message_fwd.im_content_message_wrap {
margin-top: 0;
margin-bottom: 0;
}
.im_grouped_fwd_start .im_message_fwd.im_content_message_wrap {
margin-top: 8px;
}
.im_grouped_fwd_end .im_message_fwd.im_content_message_wrap {
margin-bottom: 8px;
} }
.im_message_meta { .im_message_meta {
float: none; float: none;
position: absolute; position: absolute;
@ -602,18 +528,6 @@ img.im_message_video_thumb,
left: 0; left: 0;
} }
.im_grouped_fwd .im_message_meta,
.im_grouped_fwd_short .im_message_meta,
.im_grouped_fwd_start .im_message_meta {
display: none;
}
.im_grouped_fwd_end .im_message_meta {
display: block;
}
.im_grouped_fwd_end .im_message_fwd .im_message_date {
display: inline;
}
.im_message_out .im_message_fwd_date { .im_message_out .im_message_fwd_date {
color: #93a2ae; color: #93a2ae;
} }
@ -624,27 +538,21 @@ img.im_message_video_thumb,
background-color: #dae6f0; background-color: #dae6f0;
background-position: -2px -542px; background-position: -2px -542px;
} }
.im_message_out .icon-audio,
.im_history_selectable .im_message_outer_wrap:hover .icon-audio {
background-color: #dae6f0;
background-position: -2px -697px;
}
.im_message_out .im_message_document_size, .im_message_out .im_message_document_size,
.im_message_out .im_message_audio_duration, .im_message_out .audio_player_duration,
.im_message_out .im_message_audio_size, .im_message_out .audio_player_size,
.im_message_out .im_message_fwd_date, .im_message_out .im_message_fwd_date,
.im_message_selected .im_message_document_size, .im_message_selected .im_message_document_size,
.im_message_selected .im_message_audio_duration, .im_message_selected .audio_player_duration,
.im_message_selected .im_message_audio_size, .im_message_selected .audio_player_size,
.im_message_selected .im_message_fwd_date, .im_message_selected .im_message_fwd_date,
.im_message_focus .im_message_document_size, .im_message_focus .im_message_document_size,
.im_message_focus .im_message_audio_duration, .im_message_focus .audio_player_duration,
.im_message_focus .im_message_audio_size, .im_message_focus .audio_player_size,
.im_message_focus .im_message_fwd_date, .im_message_focus .im_message_fwd_date,
.im_history_selectable .im_message_outer_wrap:hover .im_message_document_size, .im_history_selectable .im_message_outer_wrap:hover .im_message_document_size,
.im_history_selectable .im_message_outer_wrap:hover .im_message_audio_duration, .im_history_selectable .im_message_outer_wrap:hover .audio_player_duration,
.im_history_selectable .im_message_outer_wrap:hover .im_message_audio_size, .im_history_selectable .im_message_outer_wrap:hover .audio_player_size,
.im_history_selectable .im_message_outer_wrap:hover .im_message_fwd_date { .im_history_selectable .im_message_outer_wrap:hover .im_message_fwd_date {
color: #68839c; color: #68839c;
} }
@ -853,15 +761,15 @@ a.im_dialog_selected .im_dialog_message_text {
} }
.im_message_selected .im_message_body a, .im_message_selected .im_message_body a,
.im_message_selected .im_message_fwd_title, .im_message_selected .im_message_fwd_title,
.im_message_selected .im_message_audio_name, .im_message_selected .im_message_fwd_date,
.im_message_selected .im_message_document_name, .im_message_selected .im_message_document_name,
.im_message_selected .im_message_document_size, .im_message_selected .im_message_document_size,
.im_message_selected .im_message_audio_duration, .im_message_selected .audio_player_title,
.im_message_selected .im_message_audio_size, .im_message_selected .audio_player_duration,
.im_message_selected .im_message_fwd_date, .im_message_selected .audio_player_size,
.im_history_selectable .im_message_selected:hover .im_message_document_size, .im_history_selectable .im_message_selected:hover .im_message_document_size,
.im_history_selectable .im_message_selected:hover .im_message_audio_duration, .im_history_selectable .im_message_selected:hover .audio_player_duration,
.im_history_selectable .im_message_selected:hover .im_message_audio_size, .im_history_selectable .im_message_selected:hover .audio_player_size,
.im_history_selectable .im_message_selected:hover .im_message_fwd_date { .im_history_selectable .im_message_selected:hover .im_message_fwd_date {
color: #FFF; color: #FFF;
} }
@ -870,13 +778,6 @@ a.im_dialog_selected .im_dialog_message_text {
a.im_message_fwd_author { a.im_message_fwd_author {
color: #323232; color: #323232;
} }
.im_message_fwd .im_message_fwd_author_wrap,
.im_message_fwd .im_message_text,
.im_message_fwd .im_message_media {
margin-left: 50px;
}
.im_dialogs_scrollable_wrap a.im_dialog:hover, .im_dialogs_scrollable_wrap a.im_dialog:hover,
.im_dialogs_scrollable_wrap a.im_dialog_selected, .im_dialogs_scrollable_wrap a.im_dialog_selected,

BIN
app/img/icons/IconsetW.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

BIN
app/img/icons/IconsetW_1x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

BIN
app/img/screenshot1_tile.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

1
app/index.html

@ -50,6 +50,7 @@
<script type="text/javascript" src="vendor/angular/angular-sanitize.js"></script> <script type="text/javascript" src="vendor/angular/angular-sanitize.js"></script>
<script type="text/javascript" src="vendor/angular/angular-touch.js"></script> <script type="text/javascript" src="vendor/angular/angular-touch.js"></script>
<script type="text/javascript" src="vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.js"></script> <script type="text/javascript" src="vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.js"></script>
<script type="text/javascript" src="vendor/angular-media-player/angular-media-player.js"></script>
<script type="text/javascript" src="vendor/jsbn/jsbn_combined.js"></script> <script type="text/javascript" src="vendor/jsbn/jsbn_combined.js"></script>
<script type="text/javascript" src="vendor/cryptoJS/crypto.js"></script> <script type="text/javascript" src="vendor/cryptoJS/crypto.js"></script>

1
app/js/app.js

@ -13,6 +13,7 @@ angular.module('myApp', [
'ngSanitize', 'ngSanitize',
'ngTouch', 'ngTouch',
'ui.bootstrap', 'ui.bootstrap',
'mediaPlayer',
'izhukov.utils', 'izhukov.utils',
'izhukov.mtproto', 'izhukov.mtproto',
'izhukov.mtproto.wrapper', 'izhukov.mtproto.wrapper',

87
app/js/directives.js

@ -1482,27 +1482,6 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
}) })
.directive('myAudioAutoplay', function() {
return {
link: link,
scope: {
audio: '='
}
};
function link ($scope, element, attrs) {
$scope.$watch('audio.autoplay', function (autoplay) {
if (autoplay) {
element.autoplay = true;
element[0].play();
} else {
element.autoplay = false;
}
});
}
})
.directive('myFocused', function(){ .directive('myFocused', function(){
return { return {
link: function($scope, element, attrs) { link: function($scope, element, attrs) {
@ -1842,3 +1821,69 @@ angular.module('myApp.directives', ['myApp.filters'])
} }
}) })
.directive('myAudioPlayer', function ($sce, $timeout, $q, FileManager, MtpApiFileManager) {
return {
link: link,
scope: {
audio: '='
},
templateUrl: templateUrl('audio_player')
};
function downloadAudio (audio) {
var inputFileLocation = {
_: audio._ == 'document' ? 'inputDocumentFileLocation' : 'inputAudioFileLocation',
id: audio.id,
access_hash: audio.access_hash
};
audio.progress = {enabled: true, percent: 1, total: audio.size};
var downloadPromise = MtpApiFileManager.downloadFile(audio.dc_id, inputFileLocation, audio.size, {mime: 'audio/ogg'});
audio.progress.cancel = downloadPromise.cancel;
return downloadPromise.then(function (url) {
delete audio.progress;
audio.rawUrl = url;
audio.url = $sce.trustAsResourceUrl(url);
}, function (e) {
console.log('audio download failed', e);
audio.progress.enabled = false;
}, function (progress) {
console.log('audio dl progress', progress);
audio.progress.done = progress.done;
audio.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
});
}
function link($scope, element, attrs) {
$scope.mediaPlayer = {};
$scope.download = function () {
($scope.audio.rawUrl ? $q.when() : downloadAudio($scope.audio)).then(
function () {
FileManager.download($scope.audio.rawUrl, $scope.audio.mime_type || 'audio/ogg', $scope.audio.file_name || 'audio.ogg');
}
);
};
$scope.togglePlay = function () {
if ($scope.audio.url) {
$scope.mediaPlayer.player.playPause();
}
else if ($scope.audio.progress && $scope.audio.progress.enabled) {
$scope.audio.progress.cancel();
}
else {
downloadAudio($scope.audio).then(function () {
onContentLoaded(function () {
$scope.mediaPlayer.player.play();
})
})
}
};
}
})

1
app/js/filters.js

@ -104,6 +104,7 @@ angular.module('myApp.filters', ['myApp.i18n'])
.filter('duration', [function() { .filter('duration', [function() {
return function (duration) { return function (duration) {
duration = parseInt(duration);
var secs = duration % 60, var secs = duration % 60,
mins = Math.floor((duration - secs) / 60.0); mins = Math.floor((duration - secs) / 60.0);

1
app/js/locales/en-us.json

@ -304,6 +304,7 @@
"login_controller_unknown_country": "Unknown", "login_controller_unknown_country": "Unknown",
"message_forwarded_message": "Forwarded message", "message_forwarded_message": "Forwarded message",
"message_forwarded_message_mobile": "Forwarded from {from}, {date}",
"message_attach_audio_message": "Voice message", "message_attach_audio_message": "Voice message",
"message_attach_audio_play": "Play", "message_attach_audio_play": "Play",

26
app/js/services.js

@ -1809,6 +1809,9 @@ angular.module('myApp.services', ['myApp.i18n'])
{noLinks: true, noLinebreaks: true} {noLinks: true, noLinebreaks: true}
); );
break; break;
case 'messageMediaEmpty':
delete message.media;
} }
} }
else if (message.action) { else if (message.action) {
@ -1839,7 +1842,8 @@ angular.module('myApp.services', ['myApp.i18n'])
len = history.length, len = history.length,
end = len, end = len,
i, curDay, prevDay, curMessage, prevMessage, curGrouped, prevGrouped, i, curDay, prevDay, curMessage, prevMessage, curGrouped, prevGrouped,
wasUpdated = false; wasUpdated = false,
groupFwd = !Config.Mobile;
if (limit > 0) { if (limit > 0) {
end = Math.min(limit, len); end = Math.min(limit, len);
@ -1874,12 +1878,12 @@ angular.module('myApp.services', ['myApp.i18n'])
curMessage.date < prevMessage.date + 900) { curMessage.date < prevMessage.date + 900) {
var singleLine = curMessage.message && curMessage.message.length < 70 && curMessage.message.indexOf("\n") == -1; var singleLine = curMessage.message && curMessage.message.length < 70 && curMessage.message.indexOf("\n") == -1;
if (curMessage.fwd_from_id && curMessage.fwd_from_id == prevMessage.fwd_from_id) { if (groupFwd && curMessage.fwd_from_id && curMessage.fwd_from_id == prevMessage.fwd_from_id) {
curMessage.grouped = singleLine ? 'im_grouped_fwd_short' : 'im_grouped_fwd'; curMessage.grouped = singleLine ? 'im_grouped_fwd_short' : 'im_grouped_fwd';
} else { } else {
curMessage.grouped = !curMessage.fwd_from_id && singleLine ? 'im_grouped_short' : 'im_grouped'; curMessage.grouped = !curMessage.fwd_from_id && singleLine ? 'im_grouped_short' : 'im_grouped';
} }
if (curMessage.fwd_from_id) { if (groupFwd && curMessage.fwd_from_id) {
if (!prevMessage.grouped) { if (!prevMessage.grouped) {
prevMessage.grouped = 'im_grouped_fwd_start'; prevMessage.grouped = 'im_grouped_fwd_start';
} }
@ -1890,7 +1894,7 @@ angular.module('myApp.services', ['myApp.i18n'])
} else if (prevMessage || !i) { } else if (prevMessage || !i) {
delete curMessage.grouped; delete curMessage.grouped;
if (prevMessage && prevMessage.grouped && prevMessage.fwd_from_id) { if (groupFwd && prevMessage && prevMessage.grouped && prevMessage.fwd_from_id) {
prevMessage.grouped += ' im_grouped_fwd_end'; prevMessage.grouped += ' im_grouped_fwd_end';
} }
} }
@ -2328,8 +2332,8 @@ angular.module('myApp.services', ['myApp.i18n'])
function wrapForHistory (photoID) { function wrapForHistory (photoID) {
var photo = angular.copy(photos[photoID]) || {_: 'photoEmpty'}, var photo = angular.copy(photos[photoID]) || {_: 'photoEmpty'},
width = Math.min(windowW - 80, 260), width = Math.min(windowW - 80, Config.Mobile ? 210 : 260),
height = Math.min(windowH - 100, 260), height = Math.min(windowH - 100, Config.Mobile ? 210 : 260),
thumbPhotoSize = choosePhotoSize(photo, width, height), thumbPhotoSize = choosePhotoSize(photo, width, height),
thumb = { thumb = {
placeholder: 'img/placeholders/PhotoThumbConversation.gif', placeholder: 'img/placeholders/PhotoThumbConversation.gif',
@ -2506,8 +2510,8 @@ angular.module('myApp.services', ['myApp.i18n'])
} }
var video = angular.copy(videos[videoID]), var video = angular.copy(videos[videoID]),
width = Math.min(windowW - 80, windowW <= 479 ? 260 : 200), width = Math.min(windowW - 80, Config.Mobile ? 210 : 200),
height = Math.min(windowH - 100, windowW <= 479 ? 260 : 200), height = Math.min(windowH - 100, Config.Mobile ? 210 : 200),
thumbPhotoSize = video.thumb, thumbPhotoSize = video.thumb,
thumb = { thumb = {
placeholder: 'img/placeholders/VideoThumbConversation.gif', placeholder: 'img/placeholders/VideoThumbConversation.gif',
@ -2677,6 +2681,7 @@ angular.module('myApp.services', ['myApp.i18n'])
var doc = angular.copy(docs[docID]), var doc = angular.copy(docs[docID]),
isGif = doc.mime_type == 'image/gif', isGif = doc.mime_type == 'image/gif',
isAudio = doc.mime_type.substr(0, 6) == 'audio/',
width = isGif ? Math.min(windowW - 80, 260) : 100, width = isGif ? Math.min(windowW - 80, 260) : 100,
height = isGif ? Math.min(windowH - 100, 260) : 100, height = isGif ? Math.min(windowH - 100, 260) : 100,
thumbPhotoSize = doc.thumb, thumbPhotoSize = doc.thumb,
@ -2711,6 +2716,9 @@ angular.module('myApp.services', ['myApp.i18n'])
if (doc.withPreview && isGif) { if (doc.withPreview && isGif) {
doc.isSpecial = 'gif'; doc.isSpecial = 'gif';
} }
else if (isAudio) {
doc.isSpecial = 'audio';
}
return docsForHistory[docID] = doc; return docsForHistory[docID] = doc;
} }
@ -2836,6 +2844,8 @@ angular.module('myApp.services', ['myApp.i18n'])
}, updateDownloadProgress); }, updateDownloadProgress);
historyAudio.progress.cancel = downloadPromise.cancel; historyAudio.progress.cancel = downloadPromise.cancel;
return downloadPromise;
} }
$rootScope.openAudio = openAudio; $rootScope.openAudio = openAudio;

31
app/partials/desktop/audio_player.html

@ -0,0 +1,31 @@
<div class="audio_player_wrap clearfix">
<button class="btn btn-primary pull-left audio_player_button" ng-click="togglePlay()">
<i class="icon audio_player_btn_icon" ng-class="{audio_player_btn_icon_pause: mediaPlayer.player.playing, audio_player_btn_icon_cancel: audio.progress.enabled}"></i>
</button>
<div class="audio_player_title_wrap clearfix">
<a ng-click="download()" class="audio_player_title" ng-switch="::audio.file_name.length > 0">
<span ng-switch-when="true" ng-bind="::audio.file_name"></span>
<span ng-switch-default my-i18n="message_attach_audio_message"></span>
</a>
<div class="audio_player_meta" ng-switch="audio.progress.enabled ? 'progress' : (!mediaPlayer.player.duration &amp;&amp; !audio.duration ? 'size' : '')">
<span ng-switch-when="progress" class="audio_player_size" ng-bind="audio.progress | formatSizeProgress"></span>
<span ng-switch-when="size" class="audio_player_size" ng-bind="audio.size | formatSize"></span>
<span ng-switch-default class="audio_player_duration" ng-bind="(mediaPlayer.player.playing || mediaPlayer.player.currentTime > 0) ? mediaPlayer.player.currentTime : (mediaPlayer.player.duration || audio.duration) | duration"></span>
</div>
</div>
<div class="audio_player_actions" ng-if="!audio.progress.enabled &amp;&amp; !audio.url">
<a ng-click="togglePlay()" my-i18n="message_attach_audio_play"></a>
<a ng-if="audio._ == 'document'" ng-click="download()" my-i18n="message_attach_document_download"></a>
</div>
<div class="audio_player_progress_wrap" ng-if="audio.progress.enabled || audio.url" ng-switch="audio.progress.enabled">
<div ng-switch-when="true" class="progress tg_down_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: audio.progress.percent + '%'}"></div>
</div>
<div ng-switch-default class="progress tg_play_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: mediaPlayer.player.currentTime / (mediaPlayer.player.duration || audio.duration) * 100 + '%'}"></div>
</div>
</div>
<audio ng-if="audio.url" media-player="mediaPlayer.player">
<source ng-src="{{::audio.url}}" type="audio/ogg" />
</audio>
</div>

6
app/partials/desktop/message.html

@ -37,18 +37,18 @@
<span class="im_message_date" ng-bind="::historyMessage.date | time"></span> <span class="im_message_date" ng-bind="::historyMessage.date | time"></span>
</div> </div>
<div class="im_message_body" ng-class="::{im_message_body_media: historyMessage._ == 'message' &amp;&amp; historyMessage.media &amp;&amp; historyMessage.media._ != 'messageMediaEmpty'}"> <div class="im_message_body" ng-class="::{im_message_body_media: historyMessage._ == 'message' &amp;&amp; historyMessage.media ? true : false}">
<a class="im_message_author" my-user-link="historyMessage.from_id" short="!historyMessage.to_id.chat_id" color="historyMessage.to_id.chat_id > 0"></a> <a class="im_message_author" my-user-link="historyMessage.from_id" short="!historyMessage.to_id.chat_id" color="historyMessage.to_id.chat_id > 0"></a>
<div ng-if="::historyMessage._ == 'messageForwarded' || false" class="im_message_fwd_from"> <div ng-if="::historyMessage._ == 'messageForwarded' || false" class="im_message_fwd_from">
<a class="im_message_fwd_photo pull-left" my-user-photolink="historyMessage.fwd_from_id" img-class="im_message_fwd_photo"></a> <a class="im_message_fwd_photo pull-left" my-user-photolink="historyMessage.fwd_from_id" img-class="im_message_fwd_photo"></a>
<div class="im_message_fwd_author_wrap"> <div class="im_message_fwd_author_wrap">
<a class="im_message_fwd_author" my-user-link="historyMessage.fwd_from_id" short="true"></a><span class="im_message_fwd_date" ng-bind="historyMessage.fwd_date | dateOrTime"></span> <a class="im_message_fwd_author" my-user-link="historyMessage.fwd_from_id" short="true"></a><span class="im_message_fwd_date" ng-bind="::historyMessage.fwd_date | dateOrTime"></span>
</div> </div>
</div> </div>
<div ng-if="::historyMessage.media &amp;&amp; historyMessage.media._ != 'messageMediaEmpty' || false" class="im_message_media" ng-switch="historyMessage.media._"> <div ng-if="::historyMessage.media ? true : false" class="im_message_media" ng-switch="historyMessage.media._">
<div ng-switch-when="messageMediaPhoto" my-message-photo></div> <div ng-switch-when="messageMediaPhoto" my-message-photo></div>
<div ng-switch-when="messageMediaVideo" my-message-video></div> <div ng-switch-when="messageMediaVideo" my-message-video></div>

32
app/partials/desktop/message_attach_audio.html

@ -1,31 +1,3 @@
<div class="im_message_document im_message_audio" ng-class="{im_message_audio_done: historyMessage.media.audio.url, im_message_audio_progress: historyMessage.media.audio.progress.enabled}"> <div class="im_message_audio">
<a href="" ng-click="openAudio(historyMessage.media.audio.id)" ng-if="!historyMessage.media.audio.progress.enabled &amp;&amp; !historyMessage.media.audio.url"> <div my-audio-player audio="historyMessage.media.audio"></div>
<i class="icon icon-audio"></i>
</a>
<i class="icon icon-audio" ng-if="historyMessage.media.audio.progress.enabled || historyMessage.media.audio.url"></i>
<div class="im_message_audio_info">
<div class="im_message_audio_name_wrap" ng-if="!historyMessage.media.audio.url">
<span class="im_message_audio_name" my-i18n="message_attach_audio_message"></span>
<span class="im_message_audio_duration" ng-if="!historyMessage.media.audio.progress.enabled" ng-bind="::historyMessage.media.audio.duration | duration"></span>
<span class="im_message_audio_size" ng-if="historyMessage.media.audio.progress.enabled" ng-bind="historyMessage.media.audio.progress | formatSizeProgress"></span>
</div>
<div class="im_message_audio_actions" ng-if="!historyMessage.media.audio.progress.enabled &amp;&amp; !historyMessage.media.audio.url">
<a href="" ng-click="openAudio(historyMessage.media.audio.id)" my-i18n="message_attach_audio_play"></a>
</div>
<div class="clearfix cancelable_progress_wrap" ng-if="historyMessage.media.audio.progress.enabled">
<a class="im_message_media_progress_cancel pull-right" ng-click="historyMessage.media.audio.progress.cancel()" my-i18n="modal_cancel"></a>
<div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: historyMessage.media.audio.progress.percent + '%'}"></div>
</div>
</div>
</div>
<div class="im_message_audio_player_wrap" ng-if="historyMessage.media.audio.url">
<audio my-audio-autoplay audio="historyMessage.media.audio" controls="controls">
<source ng-src="{{::historyMessage.media.audio.url}}" type="audio/ogg" />
<embed ng-src="{{::historyMessage.media.audio.url}}" hidden="true" autostart="true" loop="false" />
</audio>
</div>
</div>
</div> </div>

4
app/partials/desktop/message_attach_document.html

@ -2,6 +2,10 @@
<div ng-switch-when="gif" my-load-gif document="historyMessage.media.document"></div> <div ng-switch-when="gif" my-load-gif document="historyMessage.media.document"></div>
<div ng-switch-when="audio" class="im_message_audio">
<div my-audio-player audio="historyMessage.media.document"></div>
</div>
<div ng-switch-default class="im_message_document" ng-class="{im_message_document_thumbed: !!historyMessage.media.document.thumb, im_message_document_progress: historyMessage.media.document.progress.enabled}"> <div ng-switch-default class="im_message_document" ng-class="{im_message_document_thumbed: !!historyMessage.media.document.thumb, im_message_document_progress: historyMessage.media.document.progress.enabled}">
<a href="" ng-click="downloadDoc(historyMessage.media.document.id, historyMessage.media.document.withPreview)" ng-class="{im_message_document_link_disabled: historyMessage.media.document.progress.enabled}"> <a href="" ng-click="downloadDoc(historyMessage.media.document.id, historyMessage.media.document.withPreview)" ng-class="{im_message_document_link_disabled: historyMessage.media.document.progress.enabled}">

1
app/partials/mobile/audio_player.html

@ -0,0 +1 @@
../desktop/audio_player.html

15
app/partials/mobile/message.html

@ -19,7 +19,7 @@
</div> </div>
<div ng-if="::historyMessage._ != 'messageService'" class="im_content_message_wrap" ng-class="::[historyMessage.out ? 'im_message_out' : 'im_message_in', historyMessage._ == 'messageForwarded' ? 'im_message_fwd' : '']"> <div ng-if="::historyMessage._ != 'messageService'" class="im_content_message_wrap" ng-class="::[historyMessage.out ? 'im_message_out' : 'im_message_in']">
<i ng-if="::historyMessage.unread || historyMessage.pending || false" class="icon-message-status" ng-class="{'icon-message-status-unread': historyMessage.unread, 'icon-message-status-pending': historyMessage.pending}" ng-show="!historyMessage.error"></i> <i ng-if="::historyMessage.unread || historyMessage.pending || false" class="icon-message-status" ng-class="{'icon-message-status-unread': historyMessage.unread, 'icon-message-status-pending': historyMessage.pending}" ng-show="!historyMessage.error"></i>
<a class="im_message_from_photo pull-left" my-user-photolink="historyMessage.from_id" img-class="im_message_from_photo"></a> <a class="im_message_from_photo pull-left" my-user-photolink="historyMessage.from_id" img-class="im_message_from_photo"></a>
@ -29,19 +29,16 @@
<span class="im_message_date" ng-bind="::historyMessage.date | time"></span> <span class="im_message_date" ng-bind="::historyMessage.date | time"></span>
</div> </div>
<div class="im_message_body" ng-class="::{im_message_body_media: historyMessage._ == 'message' &amp;&amp; historyMessage.media &amp;&amp; historyMessage.media._ != 'messageMediaEmpty'}"> <div class="im_message_body" ng-class="::{im_message_body_media: !!historyMessage.media}">
<a class="im_message_author" my-user-link="historyMessage.from_id" short="!historyMessage.to_id.chat_id" color="historyMessage.to_id.chat_id > 0"></a> <a class="im_message_author" my-user-link="historyMessage.from_id" short="!historyMessage.to_id.chat_id" color="historyMessage.to_id.chat_id > 0"></a>
<div ng-if="::historyMessage._ == 'messageForwarded' || false" class="im_message_fwd_from"> <div ng-if="::historyMessage._ == 'messageForwarded' &amp;&amp; !historyMessage.media" class="im_message_fwd_header" my-i18n="message_forwarded_message_mobile">
<div class="im_message_fwd_title" ng-if="::historyMessage.grouped == 'im_grouped_fwd_start'">Forwarded message</div> <a my-i18n-param="from" class="im_message_fwd_author" my-user-link="historyMessage.fwd_from_id"></a>
<a class="im_message_fwd_photo pull-left" my-user-photolink="historyMessage.fwd_from_id" img-class="im_message_fwd_photo"></a> <span my-i18n-param="date" class="im_message_fwd_date" ng-bind="::historyMessage.fwd_date | dateOrTime"></span>
<div class="im_message_fwd_author_wrap">
<a class="im_message_fwd_author" my-user-link="historyMessage.fwd_from_id" short="true"></a><span class="im_message_fwd_date" ng-bind="historyMessage.fwd_date | dateOrTime"></span>
</div>
</div> </div>
<div ng-if="::historyMessage.media &amp;&amp; historyMessage.media._ != 'messageMediaEmpty' || false" class="im_message_media" ng-switch="historyMessage.media._"> <div ng-if="::historyMessage.media || false" class="im_message_media" ng-switch="historyMessage.media._">
<div ng-switch-when="messageMediaPhoto" my-message-photo></div> <div ng-switch-when="messageMediaPhoto" my-message-photo></div>
<div ng-switch-when="messageMediaVideo" my-message-video></div> <div ng-switch-when="messageMediaVideo" my-message-video></div>

31
app/partials/mobile/message_attach_audio.html

@ -1,30 +1,3 @@
<div class="im_message_document im_message_audio" ng-class="{im_message_audio_done: historyMessage.media.audio.url, im_message_audio_progress: historyMessage.media.audio.progress.enabled}"> <div class="im_message_audio">
<a href="" ng-click="openAudio(historyMessage.media.audio.id)" ng-if="!historyMessage.media.audio.progress.enabled &amp;&amp; !historyMessage.media.audio.url"> <div my-audio-player audio="historyMessage.media.audio"></div>
<i class="icon icon-audio"></i>
</a>
<i class="icon icon-audio" ng-if="historyMessage.media.audio.progress.enabled || historyMessage.media.audio.url"></i>
<div class="im_message_audio_info">
<div class="im_message_audio_name_wrap" ng-if="!historyMessage.media.audio.url">
<span class="im_message_audio_name">
Voice message
</span>
<span class="im_message_audio_duration" ng-if="!historyMessage.media.audio.progress.enabled" ng-bind="::historyMessage.media.audio.duration | duration"></span>
<span class="im_message_audio_size" ng-if="historyMessage.media.audio.progress.enabled" ng-bind="historyMessage.media.audio.progress | formatSizeProgress"></span>
</div>
<div class="clearfix cancelable_progress_wrap" ng-if="historyMessage.media.audio.progress.enabled">
<a class="im_message_media_progress_cancel pull-right" ng-click="historyMessage.media.audio.progress.cancel()">Cancel</a>
<div class="im_message_download_progress_wrap">
<div class="progress tg_down_progress">
<div class="progress-bar progress-bar-success" ng-style="{width: historyMessage.media.audio.progress.percent + '%'}"></div>
</div>
</div>
</div>
<div class="im_message_audio_player_wrap" ng-if="historyMessage.media.audio.url">
<audio my-audio-autoplay audio="historyMessage.media.audio" controls="controls">
<source ng-src="{{::historyMessage.media.audio.url}}" type="audio/ogg" />
<embed ng-src="{{::historyMessage.media.audio.url}}" hidden="true" autostart="true" loop="false" />
</audio>
</div>
</div>
</div> </div>

4
app/partials/mobile/message_attach_document.html

@ -2,6 +2,10 @@
<div ng-switch-when="gif" my-load-gif document="historyMessage.media.document"></div> <div ng-switch-when="gif" my-load-gif document="historyMessage.media.document"></div>
<div ng-switch-when="audio" class="im_message_audio">
<div my-audio-player audio="historyMessage.media.document"></div>
</div>
<div ng-switch-default class="im_message_document" ng-class="{im_message_document_thumbed: !!historyMessage.media.document.thumb, im_message_document_progress: historyMessage.media.document.progress.enabled}"> <div ng-switch-default class="im_message_document" ng-class="{im_message_document_thumbed: !!historyMessage.media.document.thumb, im_message_document_progress: historyMessage.media.document.progress.enabled}">
<a href="" ng-click="downloadDoc(historyMessage.media.document.id, historyMessage.media.document.withPreview)" ng-class="{im_message_document_link_disabled: historyMessage.media.document.progress.enabled}"> <a href="" ng-click="downloadDoc(historyMessage.media.document.id, historyMessage.media.document.withPreview)" ng-class="{im_message_document_link_disabled: historyMessage.media.document.progress.enabled}">

510
app/vendor/angular-media-player/angular-media-player.js vendored

@ -0,0 +1,510 @@
/**
* MDN references for hackers:
* ===========================
* Media events on <audio> and <video> tags:
* https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events
* Properties and Methods:
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
* Internet Exploder version:
* http://msdn.microsoft.com/en-us/library/ff975061(v=vs.85).aspx
*
* Understanding TimeRanges objects:
* http://html5doctor.com/html5-audio-the-state-of-play/
*/
angular.module('mediaPlayer', ['mediaPlayer.helpers'])
.constant('mp.playerDefaults', {
// general properties
currentTrack: 0,
ended: undefined,
network: undefined,
playing: false,
seeking: false,
tracks: 0,
volume: 1,
// formatted properties
formatDuration: '00:00',
formatTime: '00:00',
loadPercent: 0
})
.directive('mediaPlayer', ['$rootScope', '$interpolate', '$timeout', 'mp.throttle', 'mp.playerDefaults',
function ($rootScope, $interpolate, $timeout, throttle, playerDefaults) {
var playerMethods = {
/**
* @usage load([mediaElement], [autoplay]);
*
* @param {mediaElement Obj} mediaElement a single mediaElement, may contain multiple <source>(s)
* @param {boolean} autoplay: flag to autostart loaded element
*/
load: function (mediaElement, autoplay) {
// method overloading
if (typeof mediaElement === 'boolean') {
autoplay = mediaElement;
mediaElement = null;
} else if (typeof mediaElement === 'object') {
this.$clearSourceList();
this.$addSourceList(mediaElement);
}
this.$domEl.load();
this.ended = undefined;
if (autoplay) {
this.$element.one('canplay', this.play.bind(this));
}
},
/**
* resets the player (clear the <source>s) and reloads the playlist
*
* @usage reset([autoplay]);
* @param {boolean} autoplay: flag to autoplay once resetted
*/
reset: function (autoplay) {
angular.extend(this, playerDefaults);
this.$clearSourceList();
this.load(this.$playlist, autoplay);
},
/**
* @usage play([index], [selectivePlay])
* @param {integer} index: playlist index (0...n), to start playing from
* @param {boolean} selectivePlay: only correct value is `true`, in which case will only play the specified,
* or current, track. The default is to continue playing the next one.
*/
play: function (index, selectivePlay) {
// method overloading
if (typeof index === 'boolean') {
selectivePlay = index;
index = undefined;
}
if (selectivePlay) {
this.$selective = true;
}
if (this.$playlist.length > index) {
this.currentTrack = index + 1;
return this.load(this.$playlist[index], true);
}
// readyState = HAVE_NOTHING (0) means there's nothing into the <audio>/<video> tag
if (!this.currentTrack && this.$domEl.readyState) { this.currentTrack++; }
// In case the stream is completed, reboot it with a load()
if (this.ended) {
this.load(true);
} else {
this.$domEl.play();
}
},
playPause: function (index, selectivePlay) {
// method overloading
if (typeof index === 'boolean') {
selectivePlay = index;
index = undefined;
}
if (selectivePlay) {
this.$selective = true;
}
if (typeof index === 'number' && index + 1 !== this.currentTrack) {
this.play(index);
} else if (this.playing) {
this.pause();
} else {
this.play();
}
},
pause: function () {
this.$domEl.pause();
},
stop: function () {
this.reset();
},
toggleMute: function () {
this.muted = this.$domEl.muted = !this.$domEl.muted;
},
next: function (autoplay) {
var self = this;
if (self.currentTrack && self.currentTrack < self.tracks) {
var wasPlaying = autoplay || self.playing;
self.pause();
$timeout(function () {
self.$clearSourceList();
self.$addSourceList(self.$playlist[self.currentTrack]);
self.load(wasPlaying); // setup autoplay here.
self.currentTrack++;
});
}
},
prev: function (autoplay) {
var self = this;
if (self.currentTrack && self.currentTrack - 1) {
var wasPlaying = autoplay || self.playing;
self.pause();
$timeout(function () {
self.$clearSourceList();
self.$addSourceList(self.$playlist[self.currentTrack - 2]);
self.load(wasPlaying); // setup autoplay here.
self.currentTrack--;
});
}
},
setPlaybackRate: function (value) {
this.$domEl.playbackRate = value;
},
setVolume: function (value) {
this.$domEl.volume = value;
},
seek: function (value) {
var doubleval = 0, valuesArr;
if (typeof value === 'string') {
valuesArr = value.split(':');
doubleval += parseInt(valuesArr.pop(), 10);
if (valuesArr.length) { doubleval += parseInt(valuesArr.pop(), 10) * 60; }
if (valuesArr.length) { doubleval += parseInt(valuesArr.pop(), 10) * 3600; }
if (!isNaN(doubleval)) {
return this.$domEl.currentTime = doubleval;
}
} else {
return this.$domEl.currentTime = value;
}
},
/**
* binds a specific event directly to the element
* @param {string} type event name
* @param {function} fn callback
* @return {function} unbind function, call it in order to remove the listener
*/
on: function (type, fn) {
return this.$element.on(type, fn);
},
off: function (type, fn) {
return this.$element.off(type, fn);
},
one: function (type, fn) {
return this.$element.one(type, fn);
},
$addSourceList: function (sourceList) {
var self = this;
if (angular.isArray(sourceList)) {
angular.forEach(sourceList, function (singleElement, index) {
var sourceElem = document.createElement('SOURCE');
['src', 'type', 'media'].forEach(function (key) { // use only a subset of the properties
if (singleElement[key] !== undefined) { // firefox is picky if you set undefined attributes
sourceElem.setAttribute(key, singleElement[key]);
}
});
self.$element.append(sourceElem);
});
} else if (angular.isObject(sourceList)) {
var sourceElem = document.createElement('SOURCE');
['src', 'type', 'media'].forEach(function (key) {
if (sourceList[key] !== undefined) {
sourceElem.setAttribute(key, sourceList[key]);
}
});
self.$element.append(sourceElem);
}
},
$clearSourceList: function () {
this.$element.contents().remove();
},
$formatTime: function (seconds) {
if (seconds === Infinity) {
return '∞'; // If the data is streaming
}
var hours = parseInt(seconds / 3600, 10) % 24,
minutes = parseInt(seconds / 60, 10) % 60,
secs = parseInt(seconds % 60, 10),
result,
fragment = (minutes < 10 ? '0' + minutes : minutes) + ':' + (secs < 10 ? '0' + secs : secs);
if (hours > 0) {
result = (hours < 10 ? '0' + hours : hours) + ':' + fragment;
} else {
result = fragment;
}
return result;
},
$attachPlaylist: function (pl) {
if (pl === undefined || pl === null) {
this.playlist = [];
} else {
this.$playlist = pl;
}
}
};
/**
* Binding function that gives life to AngularJS scope
* @param {Scope} au Player Scope
* @param {HTMLMediaElement} HTML5 element
* @param {jQlite} element <audio>/<video> element
* @return {function}
*
* Returns an unbinding function
*/
var bindListeners = function (au, al, element) {
var listeners = {
playing: function () {
au.$apply(function (scope) {
scope.playing = true;
scope.ended = false;
});
},
pause: function () {
au.$apply(function (scope) {
scope.playing = false;
});
},
ended: function () {
if (!au.$selective && au.currentTrack < au.tracks) {
au.next(true);
} else {
au.$apply(function (scope) {
scope.ended = true;
scope.playing = false; // IE9 does not throw 'pause' when file ends
});
}
},
timeupdate: throttle(1000, false, function () {
au.$apply(function (scope) {
scope.currentTime = al.currentTime;
scope.formatTime = scope.$formatTime(scope.currentTime);
});
}),
loadedmetadata: function () {
au.$apply(function (scope) {
if (!scope.currentTrack) { scope.currentTrack++; } // This is triggered *ONLY* the first time a <source> gets loaded.
scope.duration = al.duration;
scope.formatDuration = scope.$formatTime(scope.duration);
if (al.buffered.length) {
scope.loadPercent = Math.round((al.buffered.end(al.buffered.length - 1) / scope.duration) * 100);
}
});
},
progress: function () {
if (au.$domEl.buffered.length) {
au.$apply(function (scope) {
scope.loadPercent = Math.round((al.buffered.end(al.buffered.length - 1) / scope.duration) * 100);
scope.network = 'progress';
});
}
},
volumechange: function () { // Sent when volume changes (both when the volume is set and when the muted attribute is changed).
au.$apply(function (scope) {
// scope.volume = Math.floor(al.volume * 100);
scope.volume = al.volume;
scope.muted = al.muted;
});
},
seeked: function () {
au.$apply(function (scope) {
scope.seeking = false;
});
},
seeking: function () {
au.$apply(function (scope) {
scope.seeking = true;
});
},
ratechange: function () {
au.$apply(function (scope) {
// scope.playbackRate = Math.floor(al.playbackRate * 100);
scope.playbackRate = al.playbackRate;
});
},
stalled: function () {
au.$apply(function (scope) {
scope.network = 'stalled';
});
},
suspend: function () {
au.$apply(function (scope) {
scope.network = 'suspend';
});
}
};
angular.forEach(listeners, function (f, listener) {
element.on(listener, f);
});
};
var MediaPlayer = function (element) {
var mediaScope = angular.extend($rootScope.$new(true), {
$element: element,
$domEl: element[0],
$playlist: undefined,
// bind TimeRanges structures to actual MediaElement
buffered: element[0].buffered,
played: element[0].played,
seekable: element[0].seekable,
}, playerDefaults, playerMethods);
bindListeners(mediaScope, element[0], element);
return mediaScope;
};
// creates a watch function bound to the specific player
// optimizable: closures eats ram
function playlistWatch(player) {
return function (playlistNew, playlistOld, watchScope) {
var currentTrack,
newTrackNum = null;
// on every playlist change, it refreshes the reference, safer/shorter approach
// than using multiple ifs and refresh only if it changed; there's no benefit in that
player.$attachPlaylist(playlistNew);
if (playlistNew === undefined && playlistOld !== undefined) {
return player.pause();
}
/**
* Playlist update logic:
* If the player has started ->
* Check if the playing track is in the new Playlist [EXAMPLE BELOW]
* If it is ->
* Assign to it the new tracknumber
* Else ->
* If the new Playlist has some song ->
* Pause the player, and get the new Playlist
* Else ->
* Reset the player, and await for orders
*
* Else (if the player hasn't started yet)
* Just replace the <src> tags
*
* Example
* playlist: [a,b,c], playing: c, trackNum: 2
* ----delay 5 sec-----
* playlist: [f,a,b,c], playing: c, trackNum: 3
*
*/
if (player.currentTrack) {
currentTrack = playlistOld ? playlistOld[player.currentTrack - 1] : -1;
for (var i = 0; i < playlistNew.length; i++) {
if (angular.equals(playlistNew[i], currentTrack)) { newTrackNum = i; break; }
}
if (newTrackNum !== null) { // currentTrack it's still in the new playlist, update trackNumber
player.currentTrack = newTrackNum + 1;
player.tracks = playlistNew.length;
} else { // currentTrack has been removed.
player.pause();
if (playlistNew.length) { // if the new playlist has some elements, replace actual.
$timeout(function () { // need $timeout because the mediaTag needs a little time to launch the 'pause' event
player.$clearSourceList();
player.$addSourceList(playlistNew[0]);
player.load();
player.tracks = playlistNew.length;
});
} else { // the new playlist has no elements, clear actual
player.reset();
}
}
} else if (playlistNew.length) { // new playlist has elements, load them
player.$clearSourceList();
player.$addSourceList(playlistNew[0]);
player.load();
player.tracks = playlistNew.length;
} else { // new playlist has no elements, clear actual
player.reset();
}
};
}
return {
/**
* The directive uses the Scope in which gets declared,
* so it can read/write it with ease.
* The only isolated Scope here gets created for the MediaPlayer.
*/
scope: false,
link: function (scope, element, attrs, ctrl) {
var playlistName = attrs.playlist,
mediaName = attrs.mediaPlayer || attrs.playerControl;
var player = new MediaPlayer(element), playlist = scope[playlistName];
// create data-structures in the father Scope
if (playlistName === undefined) {
playlist = []; // local playlist gets defined as new
} else if (scope[playlistName] === undefined) {
playlist = scope[playlistName] = []; // define playlist on father scope
} else {
playlist = scope[playlistName];
}
if (mediaName !== undefined) {
scope.$eval(mediaName + ' = player', {player: player});
}
if (element[0].tagName !== 'AUDIO' && element[0].tagName !== 'VIDEO') {
return new Error('player directive works only when attached to an <audio>/<video> type tag');
}
var mediaElement = [],
sourceElements = element.find('source');
// create a single playlist element from <source> tag(s).
// if the <source> tag is one, use object notation...
if (sourceElements.length === 1) {
playlist.unshift({ src: sourceElements[0].src, type: sourceElements[0].type, media: sourceElements[0].media });
} else if (sourceElements.length > 1) { // otherwise use array notation
angular.forEach(sourceElements, function (sourceElement) {
mediaElement.push({ src: sourceElement.src, type: sourceElement.type, media: sourceElement.media });
});
// put mediaElement as first element in the playlist
playlist.unshift(mediaElement);
}
/**
* If the user wants to keep track of the playlist changes
* has to use data-playlist="variableName" in the <audio>/<video> tag
*
* Otherwise: it will be created an empty playlist and attached to the player.
*/
if (playlistName === undefined) {
player.$attachPlaylist(playlist); // empty playlist case
} else if (playlist.length) {
playlistWatch(player)(playlist, undefined, scope); // playlist already populated gets bootstrapped
scope.$watch(playlistName, playlistWatch(player), true); // then watch gets applied
} else {
scope.$watch(playlistName, playlistWatch(player), true); // playlist empty, only watch
}
}
};
}]
);
angular.module('mediaPlayer.helpers', [])
.factory('mp.throttle', ['$timeout', function ($timeout) {
return function (delay, no_trailing, callback, debounce_mode) {
var timeout_id,
last_exec = 0;
if (typeof no_trailing !== 'boolean') {
debounce_mode = callback;
callback = no_trailing;
no_trailing = undefined;
}
var wrapper = function () {
var that = this,
elapsed = +new Date() - last_exec,
args = arguments,
exec = function () {
last_exec = +new Date();
callback.apply(that, args);
},
clear = function () {
timeout_id = undefined;
};
if (debounce_mode && !timeout_id) { exec(); }
if (timeout_id) { $timeout.cancel(timeout_id); }
if (debounce_mode === undefined && elapsed > delay) {
exec();
} else if (no_trailing !== true) {
timeout_id = $timeout(debounce_mode ? clear : exec, debounce_mode === undefined ? delay - elapsed : delay);
}
};
return wrapper;
};
}]);

2
app/webogram.appcache

@ -1,6 +1,6 @@
CACHE MANIFEST CACHE MANIFEST
# 33 # 34
NETWORK: NETWORK:
* *

Loading…
Cancel
Save