diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index 2924e7a6..03748185 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -25,6 +25,7 @@ import AppNewChannelTab from "./tabs/newChannel"; import AppContactsTab from "./tabs/contacts"; import AppArchivedTab from "./tabs/archivedTab"; import AppAddMembersTab from "./tabs/addMembers"; +import AppPeopleNearby from "./tabs/PeopleNearby"; import { i18n_, LangPackKey } from "../../lib/langPack"; import { ButtonMenuItemOptions } from "../buttonMenu"; import CheckboxField from "../checkboxField"; @@ -42,6 +43,7 @@ import { closeBtnMenu } from "../misc"; import { indexOfAndSplice } from "../../helpers/array"; import ButtonIcon from "../buttonIcon"; import confirmationPopup from "../confirmationPopup"; +import AppUsersManager from "../../lib/appManagers/appUsersManager"; export const LEFT_COLUMN_ACTIVE_CLASSNAME = 'is-left-column-shown'; @@ -141,6 +143,12 @@ export class AppSidebarLeft extends SidebarSlider { icon: 'user', text: 'Contacts', onClick: onContactsClick + }, { + icon: 'group', + text: 'PeopleNearby', + onClick: () => { + new AppPeopleNearby(this).opeddn(); + } }, { icon: 'settings', text: 'Settings', diff --git a/src/components/sidebarLeft/tabs/PeopleNearby.ts b/src/components/sidebarLeft/tabs/PeopleNearby.ts new file mode 100644 index 00000000..18dbd2c3 --- /dev/null +++ b/src/components/sidebarLeft/tabs/PeopleNearby.ts @@ -0,0 +1,186 @@ +/* + * 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 { 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 type { LazyLoadQueueIntersector } from "../../lazyLoadQueue"; +//import AppMediaViewer from "../../appMediaViewerNew"; + +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; + + protected lazyLoadQueue: LazyLoadQueueIntersector; + + protected init() { + this.container.classList.add('peoplenearby-container'); + this.setTitle('PeopleNearby'); + + const btnMenu = ButtonMenuToggle({}, 'bottom-left', [{ + icon: 'tip', + text: 'PeopleNearby.VisibilityYes', + onClick: this.startWatching + }, + { + icon: 'tip', + text: 'PeopleNearby.VisibilityNo', + onClick: this.startWatching + }]); + + this.header.append(btnMenu); + + 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); + } + + const chatsContainer = document.createElement('div'); + chatsContainer.classList.add('chatlist-container'); + chatsContainer.append(this.usersCategory.container); + chatsContainer.append(this.groupsCategory.container); + + this.scrollable.append(locatingAnimation, chatsContainer); + } + + private parseDistance(distance: number){ + return (distance >= 1000 ? String(distance/1000)+' km' : String(distance)+' m'); + } + + public opeddn() { + const result = super.open(); + result.then(() => { + navigator.geolocation.getCurrentPosition(location => { + this.latestLocationSaved = { + latitude: location.coords.latitude, + longitude: location.coords.longitude, + accuracy: location.coords.accuracy + }; + + 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 + 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(', '+String(participantsCount)+' members'); + } + }); + + this.usersCategory.nameEl.textContent = ''; + this.usersCategory.nameEl.append('Users'); + this.usersCategory.setActive(); + + this.groupsCategory.nameEl.textContent = ''; + this.groupsCategory.nameEl.append('Groups'); + this.groupsCategory.setActive(); + }); + }); + }); + } + + private startWatching(){ + if(!this.latestLocationSaved || this.isLocationWatched) return; + this.isLocationWatched = true; + + appUsersManager.getLocated( + this.latestLocationSaved.latitude, + this.latestLocationSaved.longitude, + this.latestLocationSaved.accuracy, + true, // background parameter + 3600 // 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 + ); + this.latestLocationSaved = { + latitude: result.coords.latitude, + longitude: result.coords.longitude, + accuracy: result.coords.accuracy + } + } + } + ) + } + + 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) + ) + ) + ) + ); + } +} diff --git a/src/lang.ts b/src/lang.ts index 105c8815..d7310bf9 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -1,4 +1,7 @@ const lang = { + "PeopleNearby":"PeopleNearby", + "PeopleNearby.VisibilityYes":"Make me visible", + "PeopleNearby.VisibilityNo":"Disable visibility", "GrantPermissions":"Grant me permissions", "Animations": "Animations", "AttachAlbum": "Album", diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index 4b5b694b..361b7890 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -17,7 +17,7 @@ import cleanUsername from "../../helpers/cleanUsername"; import { formatFullSentTimeRaw, tsNow } from "../../helpers/date"; import { formatPhoneNumber } from "../../helpers/formatPhoneNumber"; import { safeReplaceObject, isObject } from "../../helpers/object"; -import { Chat, InputContact, InputMedia, InputPeer, InputUser, User as MTUser, UserProfilePhoto, UserStatus } from "../../layer"; +import { Chat, InputContact, InputMedia, InputPeer, Updates, InputUser, User as MTUser, UserProfilePhoto, UserStatus, InputGeoPoint } from "../../layer"; import I18n, { i18n, LangPackKey } from "../langPack"; //import apiManager from '../mtproto/apiManager'; import apiManager from '../mtproto/mtprotoworker'; @@ -853,6 +853,32 @@ export class AppUsersManager { }); } + public getLocated( + lat: number, long: number, + accuracy_radius: number, + background: boolean = false, + self_expires: number = 0 + ) { + const _globalThis = this; + const geo_point = { + _: 'inputGeoPoint', + lat, + long, + accuracy_radius + } as InputGeoPoint; + + return apiManager.invokeApi( + 'contacts.getLocated', + {geo_point, background} + ).then((result) => { + // @ts-ignore + appUsersManager.saveApiUsers(result.users); + // @ts-ignore + appChatsManager.saveApiChats(result.chats); + return result; + }); + } + /* public searchContacts(query: string, limit = 20) { return Promise.all([ this.getContacts(query), diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 12968b83..bbb62bb5 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -1365,3 +1365,71 @@ $chat-helper-size: 36px; } } } + +.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%; } +} \ No newline at end of file