diff --git a/package-lock.json b/package-lock.json index aca2c729..c1d52c0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7540,7 +7540,7 @@ "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, + "devOptional": true, "dependencies": { "anymatch": "~3.1.1", "braces": "~3.0.2", @@ -7561,7 +7561,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, + "devOptional": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -7574,7 +7574,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -7583,7 +7583,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "devOptional": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -7595,7 +7595,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "devOptional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7607,7 +7607,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -7621,7 +7620,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, + "devOptional": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -7633,7 +7632,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, + "devOptional": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -7645,7 +7644,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12.0" } @@ -7654,7 +7653,7 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, + "devOptional": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -7666,7 +7665,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "dependencies": { "is-number": "^7.0.0" }, @@ -19361,7 +19360,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8.6" }, @@ -25658,7 +25657,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "dev": true, "optional": true, "dependencies": { "chokidar": "^2.1.8" @@ -25669,7 +25667,6 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", - "dev": true, "optional": true, "dependencies": { "anymatch": "^2.0.0", @@ -33044,7 +33041,7 @@ "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, + "devOptional": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", @@ -33060,7 +33057,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, + "devOptional": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -33070,13 +33067,13 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true + "devOptional": true }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "devOptional": true, "requires": { "fill-range": "^7.0.1" } @@ -33085,7 +33082,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "devOptional": true, "requires": { "to-regex-range": "^5.0.1" } @@ -33094,14 +33091,13 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "optional": true }, "glob-parent": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, + "devOptional": true, "requires": { "is-glob": "^4.0.1" } @@ -33110,7 +33106,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, + "devOptional": true, "requires": { "binary-extensions": "^2.0.0" } @@ -33119,13 +33115,13 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "devOptional": true }, "readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, + "devOptional": true, "requires": { "picomatch": "^2.2.1" } @@ -33134,7 +33130,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "requires": { "is-number": "^7.0.0" } @@ -42059,7 +42055,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true + "devOptional": true }, "pify": { "version": "4.0.1", @@ -46911,7 +46907,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "dev": true, "optional": true, "requires": { "chokidar": "^2.1.8" @@ -46921,7 +46916,6 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, "optional": true, "requires": { "anymatch": "^2.0.0", diff --git a/src/components/inputField.ts b/src/components/inputField.ts index c85998ad..61a92939 100644 --- a/src/components/inputField.ts +++ b/src/components/inputField.ts @@ -101,8 +101,9 @@ export type InputFieldOptions = { maxLength?: number, showLengthOn?: number, plainText?: true, - animate?: true, + animate?: boolean, required?: boolean, + canBeEdited?: boolean, validate?: () => boolean }; @@ -132,7 +133,7 @@ class InputField { options.showLengthOn = Math.min(40, Math.round(options.maxLength / 3)); } - const {placeholder, maxLength, showLengthOn, name, plainText} = options; + const {placeholder, maxLength, showLengthOn, name, plainText, canBeEdited = true} = options; let label = options.label || options.labelText; @@ -143,7 +144,7 @@ class InputField { } this.container.innerHTML = ` -
+
`; input = this.container.firstElementChild as HTMLElement; diff --git a/src/components/peerProfile.ts b/src/components/peerProfile.ts index 8891bbb6..37bfcbde 100644 --- a/src/components/peerProfile.ts +++ b/src/components/peerProfile.ts @@ -47,9 +47,10 @@ export default class PeerProfile { private username: Row; private phone: Row; private notifications: Row; + private location: Row; private cleaned: boolean; - private setBioTimeout: number; + private setMoreDetailsTimeout: number; private setPeerStatusInterval: number; private peerId: PeerId; @@ -64,6 +65,7 @@ export default class PeerProfile { public init() { this.init = null; + this.element = document.createElement('div'); this.element.classList.add('profile-content'); @@ -122,13 +124,25 @@ export default class PeerProfile { } }); + this.location = new Row({ + title: ' ', + subtitleLangKey: 'ChatLocation', + icon: 'location' + }); + this.notifications = new Row({ checkboxField: new CheckboxField({toggle: true}), titleLangKey: 'Notifications', icon: 'unmute' }); - this.section.content.append(this.phone.container, this.username.container, this.bio.container, this.notifications.container); + this.section.content.append( + this.phone.container, + this.username.container, + this.location.container, + this.bio.container, + this.notifications.container + ); this.element.append(this.section.container); @@ -160,7 +174,7 @@ export default class PeerProfile { rootScope.addEventListener('peer_bio_edit', (peerId) => { if(peerId === this.peerId) { - this.setBio(true); + this.setMoreDetails(true); } }); @@ -197,11 +211,12 @@ export default class PeerProfile { this.bio.container.style.display = 'none'; this.phone.container.style.display = 'none'; this.username.container.style.display = 'none'; + this.location.container.style.display = 'none'; this.notifications.container.style.display = ''; this.notifications.checkboxField.checked = true; - if(this.setBioTimeout) { - window.clearTimeout(this.setBioTimeout); - this.setBioTimeout = 0; + if(this.setMoreDetailsTimeout) { + window.clearTimeout(this.setMoreDetailsTimeout); + this.setMoreDetailsTimeout = 0; } } @@ -279,7 +294,7 @@ export default class PeerProfile { //membersLi.style.display = appPeersManager.isBroadcast(peerId) ? 'none' : ''; } */ - this.setBio(); + this.setMoreDetails(); replaceContent(this.name, new PeerTitle({ peerId, @@ -294,10 +309,10 @@ export default class PeerProfile { this.setPeerStatus(true); } - public setBio(override?: true) { - if(this.setBioTimeout) { - window.clearTimeout(this.setBioTimeout); - this.setBioTimeout = 0; + public setMoreDetails(override?: true) { + if(this.setMoreDetailsTimeout) { + window.clearTimeout(this.setMoreDetailsTimeout); + this.setMoreDetailsTimeout = 0; } const peerId = this.peerId; @@ -335,13 +350,19 @@ export default class PeerProfile { setText(RichTextProcessor.wrapRichText(chatFull.about), this.bio); } + // @ts-ignore + if(chatFull?.location?._ == 'channelLocation') { + // @ts-ignore + setText(chatFull.location.address, this.location); + } + return true; }); } promise.then((canSetNext) => { if(canSetNext) { - this.setBioTimeout = window.setTimeout(() => this.setBio(true), 60e3); + this.setMoreDetailsTimeout = window.setTimeout(() => this.setMoreDetails(true), 60e3); } }); } diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index 6a4cf47c..e5a97be6 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -26,6 +26,7 @@ import AppContactsTab from "./tabs/contacts"; import AppArchivedTab from "./tabs/archivedTab"; import AppAddMembersTab from "./tabs/addMembers"; import { FormatterArguments, i18n_, LangPackKey } from "../../lib/langPack"; +import AppPeopleNearby from "./tabs/peopleNearby"; import { ButtonMenuItemOptions } from "../buttonMenu"; import CheckboxField from "../checkboxField"; import { IS_MOBILE_SAFARI } from "../../environment/userAgent"; @@ -131,6 +132,12 @@ export class AppSidebarLeft extends SidebarSlider { icon: 'user', text: 'Contacts', onClick: onContactsClick + }, { + icon: 'group', + text: 'PeopleNearby', + onClick: () => { + new AppPeopleNearby(this).open(); + } }, { icon: 'settings', text: 'Settings', diff --git a/src/components/sidebarLeft/tabs/generalSettings.ts b/src/components/sidebarLeft/tabs/generalSettings.ts index fbd4b0f9..97dd657e 100644 --- a/src/components/sidebarLeft/tabs/generalSettings.ts +++ b/src/components/sidebarLeft/tabs/generalSettings.ts @@ -135,6 +135,36 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable { container.append(form); } + { + const container = section('DistanceUnitsTitle'); + + const form = document.createElement('form'); + + const name = 'distance-unit'; + const stateKey = 'settings.distanceUnit'; + + const kilometersRow = new Row({ + radioField: new RadioField({ + langKey: 'DistanceUnitsKilometers', + name, + value: 'kilometers', + stateKey + }) + }); + + const milesRow = new Row({ + radioField: new RadioField({ + langKey: 'DistanceUnitsMiles', + name, + value: 'miles', + stateKey + }) + }); + + form.append(kilometersRow.container, milesRow.container); + container.append(form); + } + { const container = section('General.TimeFormat'); diff --git a/src/components/sidebarLeft/tabs/newGroup.ts b/src/components/sidebarLeft/tabs/newGroup.ts index 1693e406..dd92be92 100644 --- a/src/components/sidebarLeft/tabs/newGroup.ts +++ b/src/components/sidebarLeft/tabs/newGroup.ts @@ -12,15 +12,32 @@ import appUsersManager from "../../../lib/appManagers/appUsersManager"; import InputField from "../../inputField"; import { SliderSuperTab } from "../../slider"; import AvatarEdit from "../../avatarEdit"; +import I18n, { i18n } from "../../../lib/langPack"; import ButtonCorner from "../../buttonCorner"; +interface OpenStreetMapInterface { + place_id?: number; + license?: string; + osm_type?: string; + osm_id?: number; + lat?: string; + lon?: string; + display_name: string; + address?: object; + boundingbox?: object; +} + export default class AppNewGroupTab extends SliderSuperTab { private avatarEdit: AvatarEdit; private uploadAvatar: () => Promise = null; private peerIds: PeerId[]; + private isGeoChat: boolean = false; private nextBtn: HTMLButtonElement; private groupNameInputField: InputField; private list: HTMLUListElement; + private groupLocationInputField: InputField; + private userLocationCoords: { lat: number, long: number }; + private userLocationAddress: string; protected init() { this.container.classList.add('new-group-container'); @@ -40,11 +57,22 @@ export default class AppNewGroupTab extends SliderSuperTab { maxLength: 128 }); - inputWrapper.append(this.groupNameInputField.container); + this.groupLocationInputField = new InputField({ + label: 'ChatLocation', + name: 'location', + canBeEdited: false + }); + + inputWrapper.append( + this.groupNameInputField.container, + this.groupLocationInputField.container + ); this.groupNameInputField.input.addEventListener('input', () => { const value = this.groupNameInputField.value; - this.nextBtn.classList.toggle('is-visible', !!value.length && !this.groupNameInputField.input.classList.contains('error')); + let valueCheck = !!value.length && !this.groupNameInputField.input.classList.contains('error'); + if(this.isGeoChat) valueCheck = valueCheck && !!this.userLocationCoords && !!this.userLocationAddress; + this.nextBtn.classList.toggle('is-visible', !!valueCheck); }); this.nextBtn = ButtonCorner({icon: 'arrow_next'}); @@ -52,17 +80,35 @@ export default class AppNewGroupTab extends SliderSuperTab { this.nextBtn.addEventListener('click', () => { const title = this.groupNameInputField.value; - this.nextBtn.disabled = true; - appChatsManager.createChat(title, this.peerIds.map(peerId => peerId.toUserId())).then((chatId) => { - if(this.uploadAvatar) { - this.uploadAvatar().then((inputFile) => { - appChatsManager.editPhoto(chatId, inputFile); - }); - } - - appSidebarLeft.removeTabFromHistory(this); - appSidebarLeft.selectTab(0); - }); + if(this.isGeoChat){ + if(!this.userLocationAddress || !this.userLocationCoords) return; + appChatsManager.createGeoChat(title, '', this.userLocationCoords, this.userLocationAddress).then((chatId) => { + if(this.uploadAvatar) { + this.uploadAvatar().then((inputFile) => { + appChatsManager.editPhoto(chatId, inputFile); + }); + } + + if(this.peerIds.length){ + appChatsManager.inviteToChannel(chatId, this.peerIds); + } + + appSidebarLeft.removeTabFromHistory(this); + appSidebarLeft.selectTab(0); + }); + }else{ + this.nextBtn.disabled = true; + appChatsManager.createChat(title, this.peerIds.map(peerId => peerId.toUserId())).then((chatId) => { + if(this.uploadAvatar) { + this.uploadAvatar().then((inputFile) => { + appChatsManager.editPhoto(chatId, inputFile); + }); + } + + appSidebarLeft.removeTabFromHistory(this); + appSidebarLeft.selectTab(0); + }); + } }); const chatsSection = new SettingSection({ @@ -86,13 +132,25 @@ export default class AppNewGroupTab extends SliderSuperTab { this.avatarEdit.clear(); this.uploadAvatar = null; this.groupNameInputField.value = ''; + this.groupLocationInputField.container.classList.add('hide'); this.nextBtn.disabled = false; } - public open(peerIds: PeerId[]) { + public open(peerIds: PeerId[], isGeoChat: boolean = false) { + this.isGeoChat = isGeoChat; this.peerIds = peerIds; const result = super.open(); result.then(() => { + + if(isGeoChat){ + this.setTitle('NearbyCreateGroup'); + this.groupLocationInputField.container.classList.remove('hide'); + this.groupLocationInputField.setValueSilently(I18n.format('Loading', true)); + this.startLocating(); + }else{ + this.groupLocationInputField.container.classList.add('hide'); + } + this.peerIds.forEach(userId => { let {dom} = appDialogsManager.addDialogNew({ dialog: userId, @@ -108,4 +166,35 @@ export default class AppNewGroupTab extends SliderSuperTab { return result; } + + private startLocating(){ + navigator.geolocation.getCurrentPosition((location) => { + this.userLocationCoords = { + lat: location.coords.latitude, + long: location.coords.longitude + }; + + let uri = "https://nominatim.openstreetmap.org/reverse"; + uri += "?lat="+location.coords.latitude; + uri += "&lon="+location.coords.longitude; + uri += "&format=json"; + uri += "&addressdetails=1"; + uri += "&accept-language=en"; + fetch(uri) + // @ts-ignore + .then((response) => response.json() as OpenStreetMapInterface) + .then( + (response: OpenStreetMapInterface) => { + this.userLocationAddress = response.display_name; + this.groupLocationInputField.setValueSilently(response.display_name); + } + ); + }, (error) => { + if(error instanceof GeolocationPositionError){ + this.groupLocationInputField.setValueSilently('Location permission denied. Please retry later.'); + }else{ + this.groupLocationInputField.setValueSilently('An error has occurred. Please retry later.'); + } + }); + } } diff --git a/src/components/sidebarLeft/tabs/notifications.ts b/src/components/sidebarLeft/tabs/notifications.ts index 05eb9de6..cd0abfdb 100644 --- a/src/components/sidebarLeft/tabs/notifications.ts +++ b/src/components/sidebarLeft/tabs/notifications.ts @@ -39,7 +39,7 @@ export default class AppNotificationsTab extends SliderSuperTabEventable { }); const previewEnabledRow = new Row({ - checkboxField: new CheckboxField({text: 'Notifications.MessagePreview', checked: true}), + checkboxField: new CheckboxField({text: 'MessagePreview', checked: true}), subtitleLangKey: 'Loading', }); diff --git a/src/components/sidebarLeft/tabs/peopleNearby.ts b/src/components/sidebarLeft/tabs/peopleNearby.ts new file mode 100644 index 00000000..80537648 --- /dev/null +++ b/src/components/sidebarLeft/tabs/peopleNearby.ts @@ -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) + ) + ) + ) + ); + } +} diff --git a/src/lang.ts b/src/lang.ts index ed57c46e..159f1017 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -1,4 +1,22 @@ const lang = { + "DistanceUnitsTitle":"Distance units", + "DistanceUnitsKilometers":"Kilometers", + "DistanceUnitsMiles":"Miles", + "PeopleNearby":"PeopleNearby", + "MakeMyselfVisible":"Make myself visible", + "StopShowingMe":"Stop showing me", + "PeopleNearbyInfo2":"Exchange contact info with people nearby and find new friends.", + "NearbyCreateGroup":"Create a Local group", + "GrantPermissions":"Grant me permissions", + "AwayTo":"%1$s away", + "MessagePreview":"Message Preview", + "KMetersAway2":"%1$s km away", + "MetersAway2":"%1$s m away", + "MilesAway":"%1$s mi away", + "FootsAway":"%1$s ft away", + "PeopleNearbyHeader":"People nearby", + "ChatsNearbyHeader":"Groups nearby", + "ChatLocation":"Location", "Animations": "Animations", "AttachAlbum": "Album", "Appearance.Color.Hex": "HEX", @@ -73,7 +91,6 @@ const lang = { "ChatBackground.UploadWallpaper": "Upload Wallpaper", "ChatBackground.Blur": "Blur Wallpaper Image", "Notifications.Sound": "Notification Sound", - "Notifications.MessagePreview": "Message preview", "NewPrivateChat": "New Private Chat", "NewPoll.OptionLabel": "Option %d", "Message.Context.Selection.Copy": "Copy selected", @@ -400,6 +417,7 @@ const lang = { "MegaPrivateInfo": "Private groups can only be joined if you were invited or have an invite link.", "ChannelPrivateLinkHelp": "People can join your channel by following this link. You can revoke the link any time.", "MegaPrivateLinkHelp": "People can join your group by following this link. You can revoke the link any time.", + "ChannelGeoGroupLocation":"Group location", "RevokeButton": "Revoke", "RevokeLink": "Revoke Link", "RevokeAlert": "Are you sure you want to revoke this link? Once the link is revoked, no one will be able to join using it.", diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index 5f944424..2a189d49 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -11,7 +11,7 @@ import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug"; import { isObject, safeReplaceObject, copy, deepEqual } from "../../helpers/object"; -import { ChannelParticipant, Chat, ChatAdminRights, ChatBannedRights, ChatParticipant, ChatPhoto, InputChannel, InputChatPhoto, InputFile, InputPeer, Update, Updates } from "../../layer"; +import { InputGeoPoint, ChannelParticipant, Chat, ChatAdminRights, ChatBannedRights, ChatParticipant, ChatPhoto, InputChannel, InputChatPhoto, InputFile, InputPeer, Update, Updates } from "../../layer"; import apiManagerProxy from "../mtproto/mtprotoworker"; import apiManager from '../mtproto/mtprotoworker'; import { RichTextProcessor } from "../richtextprocessor"; @@ -502,6 +502,31 @@ export class AppChatsManager { }); } + public createGeoChat(title: string, about: string, gpoint: { + lat: number, + long: number + }, address: string): Promise { + let geo_point = { + _: 'inputGeoPoint', + lat: gpoint['lat'], + long: gpoint['long'] + } as InputGeoPoint; + return apiManager.invokeApi('channels.createChannel', { + megagroup: true, + title, + about, + geo_point, + address + }).then((updates) => { + apiUpdatesManager.processUpdateMessage(updates); + + const channelId = (updates as any).chats[0].id; + rootScope.dispatchEvent('history_focus', {peerId: channelId.toPeerId(true)}); + + return channelId; + }); + } + public inviteToChannel(id: ChatId, userIds: UserId[]) { const input = this.getChannelInput(id); const usersInputs = userIds.map(u => appUsersManager.getUserInput(u)); diff --git a/src/lib/appManagers/appStateManager.ts b/src/lib/appManagers/appStateManager.ts index 6fd5870a..110758e8 100644 --- a/src/lib/appManagers/appStateManager.ts +++ b/src/lib/appManagers/appStateManager.ts @@ -70,6 +70,7 @@ export type State = { hiddenPinnedMessages: {[peerId: PeerId]: number}, settings: { messagesTextSize: number, + distanceUnit: 'kilometers' | 'miles', sendShortcut: 'enter' | 'ctrlEnter', animationsEnabled: boolean, autoDownload: { @@ -123,6 +124,7 @@ export const STATE_INIT: State = { hiddenPinnedMessages: {}, settings: { messagesTextSize: 16, + distanceUnit: 'kilometers', sendShortcut: 'enter', animationsEnabled: true, autoDownload: { diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index d56864e0..1500e14e 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, 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'; @@ -862,6 +862,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/lib/mtproto/schema.ts b/src/lib/mtproto/schema.ts index ba2cde56..2adc2752 100644 --- a/src/lib/mtproto/schema.ts +++ b/src/lib/mtproto/schema.ts @@ -36,4 +36,4 @@ export default {"MTProto":{"constructors":[{"id":481674261,"predicate":"vector", constructorsIndex?: {[id: number]: number} }, layer: number, -}; +}; \ No newline at end of file diff --git a/src/lib/mtproto/tl_utils.ts b/src/lib/mtproto/tl_utils.ts index 69bba4a1..3d948b5b 100644 --- a/src/lib/mtproto/tl_utils.ts +++ b/src/lib/mtproto/tl_utils.ts @@ -829,4 +829,4 @@ class TLDeserialization { MOUNT_CLASS_TO.TLDeserialization = TLDeserialization; MOUNT_CLASS_TO.TLSerialization = TLSerialization; -export { TLDeserialization, TLSerialization }; +export { TLDeserialization, TLSerialization }; \ No newline at end of file diff --git a/src/scripts/out/langPack.strings b/src/scripts/out/langPack.strings index 4e528933..f6ec248b 100644 --- a/src/scripts/out/langPack.strings +++ b/src/scripts/out/langPack.strings @@ -1,3 +1,6 @@ +"GrantPermissions" = "Grant permissions"; +"NewGeoGroup" = "New GeoGroup"; +"ChannelGeoGroupLocation" = "Group location"; "Animations" = "Animations"; "AttachAlbum" = "Album"; "BlockModal.Search.Placeholder" = "Block user..."; diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index de2b9108..ad448a37 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -1493,4 +1493,4 @@ $background-transition-total-time: #{$input-transition-time - $background-transi margin-bottom: 1rem; } } -} +} \ No newline at end of file diff --git a/src/scss/partials/_peopleNearby.scss b/src/scss/partials/_peopleNearby.scss new file mode 100644 index 00000000..609d33a2 --- /dev/null +++ b/src/scss/partials/_peopleNearby.scss @@ -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%; } + } +} \ No newline at end of file diff --git a/src/scss/style.scss b/src/scss/style.scss index f5c074b0..45c8b8d7 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -299,6 +299,7 @@ $chat-input-inner-padding-handhelds: .25rem; @import "partials/row"; @import "partials/colorPicker"; @import "partials/replyKeyboard"; +@import "partials/peopleNearby"; @import "partials/popups/popup"; @import "partials/popups/editAvatar";