Eduard Kuzmenko
3 years ago
18 changed files with 645 additions and 64 deletions
@ -0,0 +1,283 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/ |
||||||
|
|
||||||
|
import { SliderSuperTab } from "../../slider"; |
||||||
|
import AvatarElement from "../../avatar"; |
||||||
|
import ButtonCorner from "../../buttonCorner"; |
||||||
|
import { InputUser } from "../../../layer"; |
||||||
|
import apiManager from "../../../lib/mtproto/mtprotoworker"; |
||||||
|
import appUsersManager from "../../../lib/appManagers/appUsersManager"; |
||||||
|
import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; |
||||||
|
import appImManager from "../../../lib/appManagers/appImManager"; |
||||||
|
import ButtonMenuToggle from "../../buttonMenuToggle"; |
||||||
|
import { SearchGroup } from "../../appSearch"; |
||||||
|
import Button from "../../button"; |
||||||
|
import PeerTitle from "../../peerTitle"; |
||||||
|
import lottieLoader from "../../../lib/rlottie/lottieLoader"; |
||||||
|
import PopupPeer from "../../popups/peer"; |
||||||
|
import AppNewGroupTab from "./newGroup"; |
||||||
|
import { toast } from "../../toast"; |
||||||
|
import { ButtonMenuItemOptions } from "../../buttonMenu"; |
||||||
|
import { cancelEvent } from "../../../helpers/dom/cancelEvent"; |
||||||
|
import type { LazyLoadQueueIntersector } from "../../lazyLoadQueue"; |
||||||
|
import I18n, { i18n } from "../../../lib/langPack"; |
||||||
|
import rootScope from '../../../lib/rootScope'; |
||||||
|
|
||||||
|
export default class AppPeopleNearby extends SliderSuperTab { |
||||||
|
private usersCategory = new SearchGroup(true, 'contacts', true, 'people-nearby-users', false); |
||||||
|
private groupsCategory = new SearchGroup(true, 'contacts', true, 'people-nearby-groups', false); |
||||||
|
private latestLocationSaved: { latitude: number, longitude: number, accuracy: number }; |
||||||
|
private isLocationWatched: boolean = false; |
||||||
|
private errorCategory: HTMLElement; |
||||||
|
private retryBtn: HTMLButtonElement; |
||||||
|
private btnOptions: HTMLButtonElement; |
||||||
|
private menuButtons: (ButtonMenuItemOptions & {verify?: () => boolean})[]; |
||||||
|
|
||||||
|
protected lazyLoadQueue: LazyLoadQueueIntersector; |
||||||
|
|
||||||
|
protected init() { |
||||||
|
this.container.classList.add('peoplenearby-container'); |
||||||
|
this.setTitle('PeopleNearby'); |
||||||
|
|
||||||
|
this.menuButtons = [{ |
||||||
|
icon: 'tip', |
||||||
|
text: 'MakeMyselfVisible', |
||||||
|
onClick: () => this.startWatching(), |
||||||
|
verify: () => !this.isLocationWatched |
||||||
|
}, |
||||||
|
{ |
||||||
|
icon: 'tip', |
||||||
|
text: 'StopShowingMe', |
||||||
|
onClick: () => this.stopWatching(), |
||||||
|
verify: () => this.isLocationWatched |
||||||
|
}, |
||||||
|
{ |
||||||
|
icon: 'newgroup', |
||||||
|
text: 'NearbyCreateGroup', |
||||||
|
onClick: () => { |
||||||
|
new AppNewGroupTab(this.slider).open([], true); |
||||||
|
} |
||||||
|
}]; |
||||||
|
|
||||||
|
this.btnOptions = ButtonMenuToggle({}, 'bottom-left', this.menuButtons, () => this.verifyButtons()); |
||||||
|
|
||||||
|
this.header.append(this.btnOptions); |
||||||
|
|
||||||
|
const locatingIcon = document.createElement('span'); |
||||||
|
locatingIcon.classList.add('tgico', 'tgico-location'); |
||||||
|
|
||||||
|
const locatingAnimation = document.createElement('div'); |
||||||
|
locatingAnimation.classList.add('locating-animation-container'); |
||||||
|
locatingAnimation.appendChild(locatingIcon); |
||||||
|
|
||||||
|
for(let i=1; i<=4; i++){ |
||||||
|
let animatingWaves = document.createElement('div'); |
||||||
|
animatingWaves.classList.add('locating-animation-waves', 'wave-'+i); |
||||||
|
locatingAnimation.appendChild(animatingWaves); |
||||||
|
} |
||||||
|
|
||||||
|
this.errorCategory = document.createElement('div'); |
||||||
|
this.errorCategory.classList.add('text', 'hide', 'nearby-error'); |
||||||
|
|
||||||
|
this.retryBtn = ButtonCorner({icon: 'check'}); |
||||||
|
|
||||||
|
const textContainer = document.createElement('div'); |
||||||
|
textContainer.classList.add('text', 'nearby-description'); |
||||||
|
textContainer.appendChild(i18n('PeopleNearbyInfo2')); |
||||||
|
|
||||||
|
const chatsContainer = document.createElement('div'); |
||||||
|
chatsContainer.classList.add('chatlist-container'); |
||||||
|
chatsContainer.append(this.usersCategory.container); |
||||||
|
chatsContainer.append(this.groupsCategory.container); |
||||||
|
|
||||||
|
this.content.append(this.retryBtn); |
||||||
|
this.scrollable.append( |
||||||
|
locatingAnimation, |
||||||
|
textContainer, |
||||||
|
this.errorCategory, |
||||||
|
chatsContainer |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
public onCloseAfterTimeout() { |
||||||
|
this.usersCategory.clear(); |
||||||
|
this.groupsCategory.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
private verifyButtons(e?: Event){ |
||||||
|
const isMenuOpen = !!e || !!(this.btnOptions && this.btnOptions.classList.contains('menu-open')); |
||||||
|
e && cancelEvent(e); |
||||||
|
|
||||||
|
this.menuButtons.filter(button => button.verify).forEach(button => { |
||||||
|
button.element.classList.toggle('hide', !button.verify()); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private parseDistance(distance: number){ |
||||||
|
if(rootScope.settings.distanceUnit == 'miles'){ |
||||||
|
if(distance > 1609.34) { |
||||||
|
return i18n('MilesAway', [Math.round(distance / 1609)]); |
||||||
|
}else{ |
||||||
|
return i18n('FootsAway', [Math.round(distance * 3.281)]); |
||||||
|
} |
||||||
|
}else{ |
||||||
|
if(distance >= 1000){ |
||||||
|
return i18n('KMetersAway2', [distance / 1000]); |
||||||
|
}else{ |
||||||
|
return i18n('MetersAway2', [distance]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
public open() { |
||||||
|
const result = super.open(); |
||||||
|
result.then(() => { |
||||||
|
this.retryBtn.classList.remove('is-visible'); |
||||||
|
navigator.geolocation.getCurrentPosition((location) => { |
||||||
|
this.latestLocationSaved = { |
||||||
|
latitude: location.coords.latitude, |
||||||
|
longitude: location.coords.longitude, |
||||||
|
accuracy: location.coords.accuracy |
||||||
|
}; |
||||||
|
|
||||||
|
console.log(this.latestLocationSaved); |
||||||
|
|
||||||
|
appUsersManager.getLocated( |
||||||
|
location.coords.latitude, |
||||||
|
location.coords.longitude, |
||||||
|
location.coords.accuracy |
||||||
|
).then((response) => { |
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const orderedPeers = response?.updates[0]?.peers.sort((a, b) => a.distance-b.distance); |
||||||
|
// @ts-ignore
|
||||||
|
const groupsCounter = response?.updates[0]?.peers.filter((e) => e.peer._ == 'peerChannel').length; |
||||||
|
// @ts-ignore
|
||||||
|
const usersCounter = response?.updates[0]?.peers.filter((e) => e.peer._ != 'peerChannel').length; |
||||||
|
// @ts-ignore
|
||||||
|
orderedPeers?.forEach(peer => { |
||||||
|
const isChannel = peer.peer._ == 'peerChannel'; |
||||||
|
const peerId = (isChannel ? -peer.peer.channel_id : peer.peer.user_id); |
||||||
|
|
||||||
|
let {dialog, dom} = appDialogsManager.addDialogNew({ |
||||||
|
dialog: peerId, |
||||||
|
container: (isChannel ? this.groupsCategory : this.usersCategory).list, |
||||||
|
drawStatus: false, |
||||||
|
rippleEnabled: true, |
||||||
|
meAsSaved: true, |
||||||
|
avatarSize: 48, |
||||||
|
lazyLoadQueue: this.lazyLoadQueue |
||||||
|
}); |
||||||
|
|
||||||
|
dom.lastMessageSpan.append(this.parseDistance(peer.distance)); |
||||||
|
dom.containerEl.onclick = () => appImManager.setPeer(peerId); |
||||||
|
|
||||||
|
if(isChannel){ |
||||||
|
let participantsCount = 0; |
||||||
|
// @ts-ignore
|
||||||
|
for(let chat of response.chats){ |
||||||
|
if(chat.id == peer.peer.channel_id){ |
||||||
|
participantsCount = chat.participants_count; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
dom.lastMessageSpan.append(', ', i18n('Members', [participantsCount])); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
this.usersCategory.nameEl.textContent = ''; |
||||||
|
this.usersCategory.nameEl.append(i18n('PeopleNearbyHeader')); |
||||||
|
usersCounter && this.usersCategory.setActive(); |
||||||
|
|
||||||
|
this.groupsCategory.nameEl.textContent = ''; |
||||||
|
this.groupsCategory.nameEl.append(i18n('ChatsNearbyHeader')); |
||||||
|
groupsCounter && this.groupsCategory.setActive(); |
||||||
|
|
||||||
|
this.errorCategory.classList.toggle('hide', (usersCounter || groupsCounter)); |
||||||
|
this.errorCategory.innerHTML = "No groups or channels found around you."; |
||||||
|
}); |
||||||
|
}, (error) => { |
||||||
|
this.errorCategory.classList.remove('hide'); |
||||||
|
this.retryBtn.classList.add('is-visible'); |
||||||
|
this.retryBtn.addEventListener('click', this.open); |
||||||
|
if(error instanceof GeolocationPositionError){ |
||||||
|
this.errorCategory.innerHTML = "Location permission denied. Click below to retry."; |
||||||
|
}else{ |
||||||
|
this.errorCategory.innerHTML = "An error has occurred. Please retry later clicking the button below."; |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private startWatching(){ |
||||||
|
if(!this.latestLocationSaved || this.isLocationWatched) return; |
||||||
|
this.isLocationWatched = true; |
||||||
|
|
||||||
|
toast('Your position is now being shared. Do not close the page or it will be suspended.'); |
||||||
|
|
||||||
|
appUsersManager.getLocated( |
||||||
|
this.latestLocationSaved.latitude, |
||||||
|
this.latestLocationSaved.longitude, |
||||||
|
this.latestLocationSaved.accuracy, |
||||||
|
true, // background parameter
|
||||||
|
0x7fffffff // self_expires parameter
|
||||||
|
); |
||||||
|
|
||||||
|
navigator.geolocation.watchPosition( |
||||||
|
(result) => { |
||||||
|
const isLongitudeDifferent = result.coords.longitude != this.latestLocationSaved.longitude; |
||||||
|
const isLatitudeDifferent = result.coords.latitude != this.latestLocationSaved.latitude; |
||||||
|
const distanceCheck = this.calculateDistance( |
||||||
|
result.coords.latitude, result.coords.longitude, |
||||||
|
this.latestLocationSaved.latitude, this.latestLocationSaved.longitude |
||||||
|
) > 100; |
||||||
|
if((isLatitudeDifferent || isLongitudeDifferent) && distanceCheck){ |
||||||
|
appUsersManager.getLocated( |
||||||
|
result.coords.latitude, |
||||||
|
result.coords.longitude, |
||||||
|
result.coords.accuracy, |
||||||
|
true, // background parameter
|
||||||
|
0x7fffffff // self_expires parameter
|
||||||
|
); |
||||||
|
this.latestLocationSaved = { |
||||||
|
latitude: result.coords.latitude, |
||||||
|
longitude: result.coords.longitude, |
||||||
|
accuracy: result.coords.accuracy |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
private stopWatching(){ |
||||||
|
if(!this.isLocationWatched) return; |
||||||
|
this.isLocationWatched = false; |
||||||
|
toast('The sharing of your position has been stopped. You will no longer be visible to other users.'); |
||||||
|
appUsersManager.getLocated( |
||||||
|
0, // latitude parameter
|
||||||
|
0, // longitude parameter
|
||||||
|
0, // accuracy parameter
|
||||||
|
false, // background parameter
|
||||||
|
0 // self_expires parameter
|
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
private calculateDistance(lat1: number, long1: number, lat2: number, long2: number){ |
||||||
|
const p = 0.017453292519943295; // Math.PI/180
|
||||||
|
return ( |
||||||
|
12742 * Math.asin( |
||||||
|
Math.sqrt( |
||||||
|
(0.5 - Math.cos((lat2-lat1) * p)) + |
||||||
|
( |
||||||
|
Math.cos(lat1 * p) * Math.cos(lat2 * p) |
||||||
|
* (1 - Math.cos((long2 - long1) * p)/2) |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
.peoplenearby-container { |
||||||
|
div.text.nearby-description { |
||||||
|
margin-top: 15px; |
||||||
|
text-align: center; |
||||||
|
color: var(--primary-text-color); |
||||||
|
} |
||||||
|
|
||||||
|
div.text.nearby-error { |
||||||
|
color: var(--gc-secondary-text-color); |
||||||
|
margin-top: 10px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.locating-animation-container { |
||||||
|
min-height: 140px; |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
|
||||||
|
& .tgico.tgico-location { |
||||||
|
padding: 50px; |
||||||
|
background: var(--avatar-color-bottom); |
||||||
|
width: 140px; |
||||||
|
height: 140px; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
border-radius: 50%; |
||||||
|
|
||||||
|
&::before { |
||||||
|
font-size: 58px; |
||||||
|
color: white; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
& .locating-animation-waves { |
||||||
|
position: fixed; |
||||||
|
|
||||||
|
&.wave-1, |
||||||
|
&.wave-3 { |
||||||
|
animation: 3s waves-animation infinite; |
||||||
|
width: 50px; |
||||||
|
height: 50px; |
||||||
|
border: 5px solid white; |
||||||
|
border-radius: 50%; |
||||||
|
clip-path: polygon(72% 0, 100% 0, 100% 100%, 72% 100%); |
||||||
|
margin-left: 36px; |
||||||
|
} |
||||||
|
|
||||||
|
&.wave-2, |
||||||
|
&.wave-4 { |
||||||
|
animation: 5s waves-animation infinite; |
||||||
|
width: 66px; |
||||||
|
height: 71px; |
||||||
|
border: 5px solid white; |
||||||
|
border-radius: 50%; |
||||||
|
clip-path: polygon(72% 0, 100% 0, 100% 100%, 72% 100%); |
||||||
|
margin-left: 51px; |
||||||
|
margin-top: 1px; |
||||||
|
animation-delay: 2s; |
||||||
|
} |
||||||
|
|
||||||
|
&.wave-3 { |
||||||
|
margin-left: -36px !important; |
||||||
|
transform: rotateY(180deg); |
||||||
|
} |
||||||
|
|
||||||
|
&.wave-4 { |
||||||
|
margin-left: -51px !important; |
||||||
|
transform: rotateY(180deg); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes waves-animation { |
||||||
|
from { opacity: 100%; } |
||||||
|
50% { opacity: 0%; } |
||||||
|
to { opacity: 100%; } |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue