Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
d946dbe50d
|
|||
|
9590166367
|
|||
|
efdecaf118
|
@@ -0,0 +1,49 @@
|
|||||||
|
name: Export Source Translations
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
export-source-translations:
|
||||||
|
name: Update translations branch
|
||||||
|
runs-on: macos-12
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- xcode: "14.2"
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Run export script
|
||||||
|
run: |
|
||||||
|
sh devtools/export-source-translation.sh
|
||||||
|
- name: Push source translations to Transifex
|
||||||
|
uses: transifex/cli-action@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.TX_TOKEN }}
|
||||||
|
args: push --branch ''
|
||||||
|
- name: Remove extraneous /tmp/tx file from running transifex cli that breaks the next pull step
|
||||||
|
run: |
|
||||||
|
rm -rf /tmp/tx
|
||||||
|
- name: Pull translations from Transifex
|
||||||
|
uses: transifex/cli-action@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.TX_TOKEN }}
|
||||||
|
args: pull --branch ''
|
||||||
|
- name: Commit translation changes
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v4
|
||||||
|
with:
|
||||||
|
commit_message: Update Translations 🤖
|
||||||
|
branch: translations
|
||||||
|
create_branch: true
|
||||||
|
push_options: '--force'
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: repo-sync/pull-request@v2
|
||||||
|
with:
|
||||||
|
source_branch: "translations"
|
||||||
|
destination_branch: "master"
|
||||||
|
pr_title: "Update Translations 🤖"
|
||||||
|
if: steps.auto-commit-action.outputs.changes_detected == 'true'
|
||||||
|
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
lang_map = aa_DJ: aa-DJ, af_ZA: af-ZA, am_ET: am-ET, ar_AA: ar-AA, ar_AE: ar-AE, ar_DZ: ar-DZ, ar_EG: ar-EG, ar_IQ: ar-IQ, ar_JO: ar-JO, ar_LB: ar-LB, ar_SA: ar-SA, ar_SD: ar-SD, ar_SY: ar-SY, as_IN: as-IN, ast_ES: ast-ES, az_AZ: az-AZ, az_IR: az-IR, be_BY: be-BY, bem_ZM: bem-ZM, bg_BG: bg-BG, bg_US: bg-US, bn_BD: bn-BD, bn_IN: bn-IN, bo_CN: bo-CN, bqi_IR: bqi-IR, br_FR: br-FR, bs_BA: bs-BA, bs_BA-SRP: bs-BA-SRP, ca_ES: ca-ES, cs_CZ: cs-CZ, cy_GB: cy-GB, da_DK: da-DK, de_AT: de-AT, de_CH: de-CH, de_DE: de-DE, dz_BT: dz-BT, el_CY: el-CY, el_DE: el-DE, el_GR: el-GR, en_AE: en-AE, en_AL: en-AL, en_AT: en-AT, en_AU: en-AU, en_BA: en-BA, en_BA-SRP: en-BA-SRP, en_BD: en-BD, en_BE: en-BE, en_BG: en-BG, en_BH: en-BH, en_BR: en-BR, en_CA: en-CA, en_CH: en-CH, en_CL: en-CL, en_CO: en-CO, en_CY: en-CY, en_CZ: en-CZ, en_DE: en-DE, en_DK: en-DK, en_EC: en-EC, en_EG: en-EG, en_ES: en-ES, en_FI: en-FI, en_FJ: en-FJ, en_FR: en-FR, en_GB: en-GB, en_GH: en-GH, en_GR: en-GR, en_HK: en-HK, en_HR: en-HR, en_HU: en-HU, en_IE: en-IE, en_IN: en-IN, en_IT: en-IT, en_JP: en-JP, en_KR: en-KR, en_KW: en-KW, en_LK: en-LK, en_MX: en-MX, en_MY: en-MY, en_NG: en-NG, en_NL: en-NL, en_NO: en-NO, en_NZ: en-NZ, en_PE: en-PE, en_PG: en-PG, en_PH: en-PH, en_PK: en-PK, en_PL: en-PL, en_PR: en-PR, en_PT: en-PT, en_QA: en-QA, en_RO: en-RO, en_RS: en-RS, en_SA: en-SA, en_SE: en-SE, en_SG: en-SG, en_SI: en-SI, en_SK: en-SK, en_TT: en-TT, en_UG: en-UG, en_ZA: en-ZA, en_ZM: en-ZM, en_ee: en-ee, en_lt: en-lt, en_lv: en-lv, es_419: es-419, es_AR: es-AR, es_BO: es-BO, es_CL: es-CL, es_CO: es-CO, es_CR: es-CR, es_CU: es-CU, es_DO: es-DO, es_EC: es-EC, es_ES: es-ES, es_GT: es-GT, es_HN: es-HN, es_MX: es-MX, es_NI: es-NI, es_PA: es-PA, es_PE: es-PE, es_PR: es-PR, es_PY: es-PY, es_SA: es-SA, es_SV: es-SV, es_US: es-US, es_UY: es-UY, es_VE: es-VE, et_EE: et-EE, eu_ES: eu-ES, fa_AF: fa-AF, fa_IR: fa-IR, ff_SN: ff-SN, fi_FI: fi-FI, fil_PH: fil-PH, fo_FO: fo-FO, fr_BE: fr-BE, fr_CA: fr-CA, fr_CH: fr-CH, fr_CI: fr-CI, fr_CM: fr-CM, fr_FR: fr-FR, fr_GA: fr-GA, fr_LU: fr-LU, fy_NL: fy-NL, ga_IE: ga-IE, gl_ES: gl-ES, gu_IN: gu-IN, gug_PY: gug-PY, he_IL: he-IL, hi_IN: hi-IN, hr_BA: hr-BA, hr_BA-SRP: hr-BA-SRP, hr_HR: hr-HR, ht_HT: ht-HT, hu_HU: hu-HU, hu_RO: hu-RO, hu_SK: hu-SK, hy_AM: hy-AM, hy_RU: hy-RU, hye_RU: hye-RU, id_ID: id-ID, is_IS: is-IS, it_CH: it-CH, it_IT: it-IT, ja_JP: ja-JP, ka_GE: ka-GE, kk_KZ: kk-KZ, km_KH: km-KH, kn_IN: kn-IN, ko_KR: ko-KR, ks_IN: ks-IN, ku_IQ: ku-IQ, lg_UG: lg-UG, lo_LA: lo-LA, loz_ZM: loz-ZM, lt_LT: lt-LT, lv_LV: lv-LV, mhr_RU: mhr-RU, mk_MK: mk-MK, ml_IN: ml-IN, mn_MN: mn-MN, mr_IN: mr-IN, ms_BN: ms-BN, ms_MY: ms-MY, mt_MT: mt-MT, my_MM: my-MM, nb_NO: nb-NO, ne_NP: ne-NP, nl_BE: nl-BE, nl_NL: nl-NL, nn_NO: nn-NO, no_NO: no-NO, or_IN: or-IN, pa_IN: pa-IN, pa_PK: pa-PK, pl_PL: pl-PL, ps_AF: ps-AF, pt_AO: pt-AO, pt_BR: pt-BR, pt_MZ: pt-MZ, pt_PT: pt-PT, qu_EC: qu-EC, ro_MD: ro-MD, ro_RO: ro-RO, ru_RU: ru-RU, ru_UA: ru-UA, ru_ee: ru-ee, ru_lt: ru-lt, ru_lv: ru-lv, si_LK: si-LK, sk_SK: sk-SK, sl_SI: sl-SI, sq_AL: sq-AL, sr_BA-SRP: sr-BA-SRP, sr_ME: sr-ME, sr_RS: sr-RS, st_ZA: st-ZA, sv_FI: sv-FI, sv_SE: sv-SE, sw_CD: sw-CD, sw_KE: sw-KE, sw_TZ: sw-TZ, sw_UG: sw-UG, ta_IN: ta-IN, ta_LK: ta-LK, te_IN: te-IN, tg_TJ: tg-TJ, th_TH: th-TH, tk_TM: tk-TM, tl_PH: tl-PH, tr_CY: tr-CY, tr_DE: tr-DE, tr_TR: tr-TR, uk_UA: uk-UA, ur_PK: ur-PK, uz_UZ: uz-UZ, vi_VN: vi-VN, wo_SN: wo-SN, yue_CN: yue-CN, zh_CN: zh-CN, zh_HK: zh-HK, zh_SG: zh-SG, zh_TW: zh-TW, zu_ZA: zu-ZA
|
||||||
|
|
||||||
|
[o:damus:p:damus-ios-staging:r:infopliststrings]
|
||||||
|
file_filter = damus/<lang>.lproj/InfoPlist.strings
|
||||||
|
source_file = damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings
|
||||||
|
type = STRINGS_UTF8
|
||||||
|
minimum_perc = 0
|
||||||
|
resource_name = damus..en-US.lproj/InfoPlist.strings (translations)
|
||||||
|
|
||||||
|
[o:damus:p:damus-ios-staging:r:localizablestrings]
|
||||||
|
file_filter = damus/<lang>.lproj/Localizable.strings
|
||||||
|
source_file = damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings
|
||||||
|
type = STRINGS_UTF8
|
||||||
|
minimum_perc = 0
|
||||||
|
resource_name = damus..en-US.lproj/Localizable.strings (translations)
|
||||||
|
|
||||||
|
[o:damus:p:damus-ios-staging:r:localizablestringsdict]
|
||||||
|
file_filter = damus/<lang>.lproj/Localizable.stringsdict
|
||||||
|
source_file = damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.stringsdict
|
||||||
|
type = STRINGSDICT
|
||||||
|
minimum_perc = 0
|
||||||
|
resource_name = damus..en-US.lproj/Localizable.stringsdict (translations)
|
||||||
+4
-120
@@ -1,99 +1,3 @@
|
|||||||
## [1.4.1-3] - 2023-04-05
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added text truncation settings (William Casarin)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Rename block to mute (William Casarin)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Reduce chopping of images (mainvolume)
|
|
||||||
- Fix some notification settings not saving (William Casarin)
|
|
||||||
- Fix broken camera uploads (again) (Joel Klabo)
|
|
||||||
|
|
||||||
|
|
||||||
[1.4.1-3]: https://github.com/damus-io/damus/releases/tag/v1.4.1-3
|
|
||||||
|
|
||||||
## [1.4.1-2] - 2023-04-04
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Reply counts (William Casarin)
|
|
||||||
- Add option to only show notification from people you follow (Swift)
|
|
||||||
- Added local notifications for other events (Swift)
|
|
||||||
- Show a custom view when tagged user isn't found (ericholguin)
|
|
||||||
- Show referenced notes in DMs (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Show full bleed images on selected events in threads (William Casarin)
|
|
||||||
- Improvement to square image displaying (mainvolume)
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix broken website links that have missing https:// prefixes (William Casarin)
|
|
||||||
- Get around CCP bootstrap relay banning by caching user's relays as their bootstrap relays (William Casarin)
|
|
||||||
|
|
||||||
|
|
||||||
[1.4.1-2]: https://github.com/damus-io/damus/releases/tag/v1.4.1-2
|
|
||||||
|
|
||||||
## [1.4.1] - 2023-04-03
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Profile Picture Upload (Joel Klabo)
|
|
||||||
- Enable offline posting (William Casarin)
|
|
||||||
- Add auto-translation caching to ruduce api usage (Terry Yiu)
|
|
||||||
- Added support for gif uploads (Swift)
|
|
||||||
- Add a Divider in the Follows List for Large Screens (Joel Klabo)
|
|
||||||
- Upload Photos and Videos from Camera (Joel Klabo)
|
|
||||||
- Added ability to lookup users by nip05 identifiers (William Casarin)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Only truncate timeline text if enabled in settings (William Casarin)
|
|
||||||
- Make mentions wide in notifications like in timeline (William Casarin)
|
|
||||||
- Broadcast events you are replying to (William Casarin)
|
|
||||||
- Broadcast now also broadcasts event user's profile (William Casarin)
|
|
||||||
- Improved look of reply view (ericholguin)
|
|
||||||
- Remove gradient in some places for visibility (ericholguin)
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix cropped images (mainvolume)
|
|
||||||
- Truncate long text in notification items (William Casarin)
|
|
||||||
- Restore missing reply description on selected events (William Casarin)
|
|
||||||
- Show sent DMs immediately (William Casarin)
|
|
||||||
- Fixed size of translated text (William Casarin)
|
|
||||||
- Fix crash when reposting (William Casarin)
|
|
||||||
- Fix unclickable image dismiss button (OlegAba)
|
|
||||||
|
|
||||||
|
|
||||||
[1.4.1]: https://github.com/damus-io/damus/releases/tag/v1.4.1
|
|
||||||
## [1.4.0] - 2023-03-27
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Local zap notifications (Swift)
|
|
||||||
- Add support for video uploads (Swift)
|
|
||||||
- Auto Translation (Terry Yiu)
|
|
||||||
- Portuguese (Brazil) translations (Andressa Munturo)
|
|
||||||
- Spanish (Spain) translations (Max Pleb)
|
|
||||||
- Vietnamese translations (ShiryoRyo)
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixed small notification hit boxes (Terry Yiu)
|
|
||||||
|
|
||||||
[1.4.0]: https://github.com/damus-io/damus/releases/tag/v1.4.0
|
|
||||||
|
|
||||||
## [1.3.0-7] - 2023-03-24
|
## [1.3.0-7] - 2023-03-24
|
||||||
|
|
||||||
- New experimental timeline view
|
- New experimental timeline view
|
||||||
@@ -153,10 +57,6 @@
|
|||||||
|
|
||||||
- Add image uploader (Swift)
|
- Add image uploader (Swift)
|
||||||
- Add option to always show images (never blur) (William Casarin)
|
- Add option to always show images (never blur) (William Casarin)
|
||||||
- Canadian French (Pierre - synoptic_okubo)
|
|
||||||
- Hungarian translations (Zoltan)
|
|
||||||
- Korean translations (sogoagain)
|
|
||||||
- Swedish translations (Pextar)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -179,9 +79,6 @@
|
|||||||
- Extend user tagging search to all local profiles (William Casarin)
|
- Extend user tagging search to all local profiles (William Casarin)
|
||||||
- Vibrate when a zap is received (Swift)
|
- Vibrate when a zap is received (Swift)
|
||||||
- New and Improved Share sheet (ericholguin)
|
- New and Improved Share sheet (ericholguin)
|
||||||
- Bulgarian translations (elsat)
|
|
||||||
- Persian translations (Mahdi Taghizadeh)
|
|
||||||
- Ukrainian translations (Valeriia Khudiakova, Tony B)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -280,8 +177,6 @@
|
|||||||
- Customized zaps (William Casarin)
|
- Customized zaps (William Casarin)
|
||||||
- Add new Notifications View (William Casarin)
|
- Add new Notifications View (William Casarin)
|
||||||
- Bookmarking (Joel Klabo)
|
- Bookmarking (Joel Klabo)
|
||||||
- Chinese, Traditional (Hong Kong) translations (rasputin)
|
|
||||||
- Chinese, Traditional (Taiwan) translations (rasputin)
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@@ -307,9 +202,6 @@
|
|||||||
- Added the ability to select text on posts (OlegAba)
|
- Added the ability to select text on posts (OlegAba)
|
||||||
- Added Posts or Post & Replies selector to Profile (ericholguin)
|
- Added Posts or Post & Replies selector to Profile (ericholguin)
|
||||||
- Improved profile navbar (OlegAba)
|
- Improved profile navbar (OlegAba)
|
||||||
- Czech translations (Martin Gabrhel)
|
|
||||||
- Indonesian translations (johnybergzy)
|
|
||||||
- Russian translations (Tony B)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -358,6 +250,7 @@
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Relay Filtering (William Casarin)
|
- Relay Filtering (William Casarin)
|
||||||
|
- Japanese translations (Terry Yiu)
|
||||||
- Add password autofill on account login and creation (Terry Yiu)
|
- Add password autofill on account login and creation (Terry Yiu)
|
||||||
- Show if relay is paid (William Casarin)
|
- Show if relay is paid (William Casarin)
|
||||||
- Add "Follows You" indicator on profile (William Casarin)
|
- Add "Follows You" indicator on profile (William Casarin)
|
||||||
@@ -370,10 +263,6 @@
|
|||||||
- Copy invoice button (Joel Klabo)
|
- Copy invoice button (Joel Klabo)
|
||||||
- Receive Lightning Zaps (William Casarin)
|
- Receive Lightning Zaps (William Casarin)
|
||||||
- Allow text selection in bio (Suhail Saqan)
|
- Allow text selection in bio (Suhail Saqan)
|
||||||
- Chinese, Simplified (China mainland) translations (haolong, rasputin)
|
|
||||||
- Dutch translations (Heimen Stoffels - Vistaus)
|
|
||||||
- Greek translations (milicode)
|
|
||||||
- Japanese translations (akiomik, foxytanuki, Guetsu Ren - Nighthaven, h3y6e, middlingphys)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -408,7 +297,6 @@
|
|||||||
- LibreTranslate note translations (Terry Yiu)
|
- LibreTranslate note translations (Terry Yiu)
|
||||||
- Added support for account deletion (William Casarin)
|
- Added support for account deletion (William Casarin)
|
||||||
- User tagging and autocompletion in posts (Swift)
|
- User tagging and autocompletion in posts (Swift)
|
||||||
- Polish translations (pysiak)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -431,8 +319,7 @@
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Arabic translations (Barodane)
|
- Added Arabic and Portuguese translations (Barodane, Antonio Chagas)
|
||||||
- Portuguese translations (Antonio Chagas)
|
|
||||||
- Add QRCode view for sharing your pubkey (ericholguin)
|
- Add QRCode view for sharing your pubkey (ericholguin)
|
||||||
- Added nostr: uri handling (William Casarin)
|
- Added nostr: uri handling (William Casarin)
|
||||||
|
|
||||||
@@ -459,8 +346,7 @@
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Reposts view (Terry Yiu)
|
- Reposts view (Terry Yiu)
|
||||||
- Italian translations (Nicolò Carcagnì)
|
- Translations for it_IT, it_CH, fr_FR, de_DE, de_AT and lv_LV (Nicolò Carcagnì, Solobalbo, Gregor, Peter Gerstbach, SYX)
|
||||||
- Latvian translations (SYX)
|
|
||||||
- Added ability to block users (William Casarin)
|
- Added ability to block users (William Casarin)
|
||||||
- Added a way to report content (William Casarin)
|
- Added a way to report content (William Casarin)
|
||||||
- Stretchable profile cover header (Swift)
|
- Stretchable profile cover header (Swift)
|
||||||
@@ -487,9 +373,7 @@
|
|||||||
|
|
||||||
- Show website on profiles (William Casarin)
|
- Show website on profiles (William Casarin)
|
||||||
- Add the ability to choose participants when replying (Joel Klabo)
|
- Add the ability to choose participants when replying (Joel Klabo)
|
||||||
- German translations (Gregor, Peter Gerstbach)
|
- Translations for de_AT, de_DE, tr_TR, fr_FR (Gregor, Peter Gerstbach, Taylan Benli, Solobalbo)
|
||||||
- Turkish translations (Taylan Benli)
|
|
||||||
- French (France) translations (Solobalbo)
|
|
||||||
- Add DM Message Requests (William Casarin)
|
- Add DM Message Requests (William Casarin)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+29
-134
@@ -17,7 +17,6 @@
|
|||||||
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F229A91366008A0F29 /* ProfileViewTests.swift */; };
|
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F229A91366008A0F29 /* ProfileViewTests.swift */; };
|
||||||
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */; };
|
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */; };
|
||||||
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
|
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
|
||||||
3A48E23B29D518F000BA313D /* Translations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E23A29D518F000BA313D /* Translations.swift */; };
|
|
||||||
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FC297E3CFF0090C62D /* RepostsModel.swift */; };
|
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FC297E3CFF0090C62D /* RepostsModel.swift */; };
|
||||||
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
|
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
|
||||||
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
|
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
|
||||||
@@ -38,13 +37,6 @@
|
|||||||
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F8E280F640A000448DE /* ThreadModel.swift */; };
|
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F8E280F640A000448DE /* ThreadModel.swift */; };
|
||||||
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F90280F6528000448DE /* ChatView.swift */; };
|
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F90280F6528000448DE /* ChatView.swift */; };
|
||||||
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; };
|
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; };
|
||||||
4C1A9A1A29DCA17E00516EAC /* ReplyCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A1929DCA17E00516EAC /* ReplyCounter.swift */; };
|
|
||||||
4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A1C29DDCF9B00516EAC /* NotificationSettingsView.swift */; };
|
|
||||||
4C1A9A1F29DDD24B00516EAC /* AppearanceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A1E29DDD24B00516EAC /* AppearanceSettingsView.swift */; };
|
|
||||||
4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */; };
|
|
||||||
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2229DDDB8100516EAC /* IconLabel.swift */; };
|
|
||||||
4C1A9A2529DDDF2600516EAC /* ZapSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */; };
|
|
||||||
4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */; };
|
|
||||||
4C216F32286E388800040376 /* DMChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F31286E388800040376 /* DMChatView.swift */; };
|
4C216F32286E388800040376 /* DMChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F31286E388800040376 /* DMChatView.swift */; };
|
||||||
4C216F34286F5ACD00040376 /* DMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F33286F5ACD00040376 /* DMView.swift */; };
|
4C216F34286F5ACD00040376 /* DMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F33286F5ACD00040376 /* DMView.swift */; };
|
||||||
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */; };
|
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */; };
|
||||||
@@ -137,7 +129,6 @@
|
|||||||
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */; };
|
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */; };
|
||||||
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; };
|
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; };
|
||||||
4C8682872814DE470026224F /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8682862814DE470026224F /* ProfileView.swift */; };
|
4C8682872814DE470026224F /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8682862814DE470026224F /* ProfileView.swift */; };
|
||||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
|
|
||||||
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */; };
|
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */; };
|
||||||
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
|
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
|
||||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
||||||
@@ -172,7 +163,6 @@
|
|||||||
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */; };
|
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */; };
|
||||||
4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */; };
|
4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */; };
|
||||||
4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */; };
|
4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */; };
|
||||||
4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC6193929DC777C006A86D1 /* RelayBootstrap.swift */; };
|
|
||||||
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAE6297EFA7B00430951 /* Zap.swift */; };
|
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAE6297EFA7B00430951 /* Zap.swift */; };
|
||||||
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */; };
|
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */; };
|
||||||
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEC297F0B9E00430951 /* Highlight.swift */; };
|
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEC297F0B9E00430951 /* Highlight.swift */; };
|
||||||
@@ -190,9 +180,6 @@
|
|||||||
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; };
|
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; };
|
||||||
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
|
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
|
||||||
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */; };
|
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */; };
|
||||||
4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F0F129D4FCFA005914DB /* DebouncedOnChange.swift */; };
|
|
||||||
4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F0F329D779B5005914DB /* PostBox.swift */; };
|
|
||||||
4CE4F0F829DB7399005914DB /* ThiccDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F0F729DB7399005914DB /* ThiccDivider.swift */; };
|
|
||||||
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
|
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
|
||||||
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; };
|
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; };
|
||||||
4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */; };
|
4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */; };
|
||||||
@@ -238,15 +225,12 @@
|
|||||||
4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */; };
|
4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */; };
|
||||||
4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */; };
|
4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */; };
|
||||||
4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */; };
|
4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */; };
|
||||||
4CFF8F6B29CD0079008DB934 /* RepostedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */; };
|
|
||||||
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; };
|
|
||||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
||||||
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
|
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
|
||||||
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
|
|
||||||
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
|
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
|
||||||
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
|
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
|
||||||
5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF72FC129B9142F00124A13 /* ShareAction.swift */; };
|
5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF72FC129B9142F00124A13 /* ShareAction.swift */; };
|
||||||
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfilePicImageView.swift */; };
|
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; };
|
||||||
643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643EA5C7296B764E005081BB /* RelayFilterView.swift */; };
|
643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643EA5C7296B764E005081BB /* RelayFilterView.swift */; };
|
||||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
||||||
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
|
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
|
||||||
@@ -264,12 +248,10 @@
|
|||||||
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
|
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
|
||||||
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
||||||
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
|
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
|
||||||
F757933A29D7AECD007DEAC1 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757933929D7AECD007DEAC1 /* ImagePicker.swift */; };
|
|
||||||
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12C29A1855400E10810 /* BookmarksManager.swift */; };
|
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12C29A1855400E10810 /* BookmarksManager.swift */; };
|
||||||
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12E29A18EF500E10810 /* BookmarksView.swift */; };
|
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12E29A18EF500E10810 /* BookmarksView.swift */; };
|
||||||
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E91298B0F0700AB113A /* RelayDetailView.swift */; };
|
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E91298B0F0700AB113A /* RelayDetailView.swift */; };
|
||||||
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */; };
|
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */; };
|
||||||
F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */; };
|
|
||||||
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */; };
|
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */; };
|
||||||
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParicipantsView.swift */; };
|
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParicipantsView.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
@@ -315,16 +297,9 @@
|
|||||||
3A3040FE29A91F31008A0F29 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3A3040FE29A91F31008A0F29 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3A3040FF29AB02D1008A0F29 /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-US"; path = "en-US.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3A3040FF29AB02D1008A0F29 /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-US"; path = "en-US.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroupViewTests.swift; sourceTree = "<group>"; };
|
3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroupViewTests.swift; sourceTree = "<group>"; };
|
||||||
3A325AC429C9E0B8002BE7ED /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = "<group>"; };
|
|
||||||
3A325AC529C9E0B8002BE7ED /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
|
||||||
3A325AC629C9E0B8002BE7ED /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = vi; path = vi.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
|
||||||
3A325AC729C9E0CF002BE7ED /* es-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-ES"; path = "es-ES.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
|
||||||
3A325AC829C9E0CF002BE7ED /* es-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-ES"; path = "es-ES.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
|
||||||
3A325AC929C9E0CF002BE7ED /* es-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-ES"; path = "es-ES.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
|
||||||
3A41E559299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
3A41E559299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
3A48E23A29D518F000BA313D /* Translations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Translations.swift; sourceTree = "<group>"; };
|
|
||||||
3A4F3320297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3A4F3320297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3A4F3321297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3A4F3321297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3A4F3322297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-FR"; path = "fr-FR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3A4F3322297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-FR"; path = "fr-FR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
@@ -369,9 +344,6 @@
|
|||||||
3AC524EE298C000B00693EBF /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
3AC524EE298C000B00693EBF /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
3AC524EF298C000B00693EBF /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3AC524EF298C000B00693EBF /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3AC524F0298C000B00693EBF /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3AC524F0298C000B00693EBF /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
3AC59CA729CDDB78007E04A6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
|
||||||
3AC59CA829CDDB78007E04A6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
|
||||||
3AC59CA929CDDB78007E04A6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
|
||||||
3ACB685B297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3ACB685B297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3ACB685E297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3ACB685E297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; sourceTree = "<group>"; };
|
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -408,13 +380,6 @@
|
|||||||
4C0A3F8E280F640A000448DE /* ThreadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadModel.swift; sourceTree = "<group>"; };
|
4C0A3F8E280F640A000448DE /* ThreadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadModel.swift; sourceTree = "<group>"; };
|
||||||
4C0A3F90280F6528000448DE /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
|
4C0A3F90280F6528000448DE /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
|
||||||
4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = "<group>"; };
|
4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = "<group>"; };
|
||||||
4C1A9A1929DCA17E00516EAC /* ReplyCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyCounter.swift; sourceTree = "<group>"; };
|
|
||||||
4C1A9A1C29DDCF9B00516EAC /* NotificationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsView.swift; sourceTree = "<group>"; };
|
|
||||||
4C1A9A1E29DDD24B00516EAC /* AppearanceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceSettingsView.swift; sourceTree = "<group>"; };
|
|
||||||
4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeySettingsView.swift; sourceTree = "<group>"; };
|
|
||||||
4C1A9A2229DDDB8100516EAC /* IconLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconLabel.swift; sourceTree = "<group>"; };
|
|
||||||
4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapSettingsView.swift; sourceTree = "<group>"; };
|
|
||||||
4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = "<group>"; };
|
|
||||||
4C216F31286E388800040376 /* DMChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMChatView.swift; sourceTree = "<group>"; };
|
4C216F31286E388800040376 /* DMChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMChatView.swift; sourceTree = "<group>"; };
|
||||||
4C216F33286F5ACD00040376 /* DMView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMView.swift; sourceTree = "<group>"; };
|
4C216F33286F5ACD00040376 /* DMView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMView.swift; sourceTree = "<group>"; };
|
||||||
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDismissKeyboard.swift; sourceTree = "<group>"; };
|
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDismissKeyboard.swift; sourceTree = "<group>"; };
|
||||||
@@ -537,7 +502,6 @@
|
|||||||
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofOfWork.swift; sourceTree = "<group>"; };
|
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofOfWork.swift; sourceTree = "<group>"; };
|
||||||
4C7FF7D42823313F009601DB /* Mentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mentions.swift; sourceTree = "<group>"; };
|
4C7FF7D42823313F009601DB /* Mentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mentions.swift; sourceTree = "<group>"; };
|
||||||
4C8682862814DE470026224F /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
4C8682862814DE470026224F /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||||
4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusColors.swift; sourceTree = "<group>"; };
|
|
||||||
4C90BD152839DB54008EE7EF /* NostrMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrMetadata.swift; sourceTree = "<group>"; };
|
4C90BD152839DB54008EE7EF /* NostrMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrMetadata.swift; sourceTree = "<group>"; };
|
||||||
4C90BD17283A9EE5008EE7EF /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
4C90BD17283A9EE5008EE7EF /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||||
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
|
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
|
||||||
@@ -572,7 +536,6 @@
|
|||||||
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNameView.swift; sourceTree = "<group>"; };
|
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNameView.swift; sourceTree = "<group>"; };
|
||||||
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowsYou.swift; sourceTree = "<group>"; };
|
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowsYou.swift; sourceTree = "<group>"; };
|
||||||
4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteLink.swift; sourceTree = "<group>"; };
|
4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteLink.swift; sourceTree = "<group>"; };
|
||||||
4CC6193929DC777C006A86D1 /* RelayBootstrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayBootstrap.swift; sourceTree = "<group>"; };
|
|
||||||
4CC7AAE6297EFA7B00430951 /* Zap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Zap.swift; sourceTree = "<group>"; };
|
4CC7AAE6297EFA7B00430951 /* Zap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Zap.swift; sourceTree = "<group>"; };
|
||||||
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuilderEventView.swift; sourceTree = "<group>"; };
|
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuilderEventView.swift; sourceTree = "<group>"; };
|
||||||
4CC7AAEC297F0B9E00430951 /* Highlight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Highlight.swift; sourceTree = "<group>"; };
|
4CC7AAEC297F0B9E00430951 /* Highlight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Highlight.swift; sourceTree = "<group>"; };
|
||||||
@@ -590,9 +553,6 @@
|
|||||||
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHolder.swift; sourceTree = "<group>"; };
|
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHolder.swift; sourceTree = "<group>"; };
|
||||||
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; };
|
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; };
|
||||||
4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InnerTimelineView.swift; sourceTree = "<group>"; };
|
4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InnerTimelineView.swift; sourceTree = "<group>"; };
|
||||||
4CE4F0F129D4FCFA005914DB /* DebouncedOnChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncedOnChange.swift; sourceTree = "<group>"; };
|
|
||||||
4CE4F0F329D779B5005914DB /* PostBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostBox.swift; sourceTree = "<group>"; };
|
|
||||||
4CE4F0F729DB7399005914DB /* ThiccDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThiccDivider.swift; sourceTree = "<group>"; };
|
|
||||||
4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
|
4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
|
||||||
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; };
|
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; };
|
||||||
4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldAlert.swift; sourceTree = "<group>"; };
|
4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldAlert.swift; sourceTree = "<group>"; };
|
||||||
@@ -641,15 +601,12 @@
|
|||||||
4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenuModifier.swift; sourceTree = "<group>"; };
|
4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenuModifier.swift; sourceTree = "<group>"; };
|
||||||
4CFF8F6629CC9E3A008DB934 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
|
4CFF8F6629CC9E3A008DB934 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
|
||||||
4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContainerView.swift; sourceTree = "<group>"; };
|
4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContainerView.swift; sourceTree = "<group>"; };
|
||||||
4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostedEvent.swift; sourceTree = "<group>"; };
|
|
||||||
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; };
|
|
||||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
||||||
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
|
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
|
||||||
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
|
|
||||||
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
|
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
|
||||||
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
|
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
|
||||||
5CF72FC129B9142F00124A13 /* ShareAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAction.swift; sourceTree = "<group>"; };
|
5CF72FC129B9142F00124A13 /* ShareAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAction.swift; sourceTree = "<group>"; };
|
||||||
6439E013296790CF0020672B /* ProfilePicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePicImageView.swift; sourceTree = "<group>"; };
|
6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
|
||||||
643EA5C7296B764E005081BB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; };
|
643EA5C7296B764E005081BB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; };
|
||||||
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
|
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
|
||||||
64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||||
@@ -666,12 +623,10 @@
|
|||||||
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
|
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
|
||||||
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
|
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
|
||||||
E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; };
|
E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; };
|
||||||
F757933929D7AECD007DEAC1 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; };
|
|
||||||
F75BA12C29A1855400E10810 /* BookmarksManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksManager.swift; sourceTree = "<group>"; };
|
F75BA12C29A1855400E10810 /* BookmarksManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksManager.swift; sourceTree = "<group>"; };
|
||||||
F75BA12E29A18EF500E10810 /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = "<group>"; };
|
F75BA12E29A18EF500E10810 /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = "<group>"; };
|
||||||
F7908E91298B0F0700AB113A /* RelayDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayDetailView.swift; sourceTree = "<group>"; };
|
F7908E91298B0F0700AB113A /* RelayDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayDetailView.swift; sourceTree = "<group>"; };
|
||||||
F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIPURLBuilder.swift; sourceTree = "<group>"; };
|
F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIPURLBuilder.swift; sourceTree = "<group>"; };
|
||||||
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfilePictureControl.swift; sourceTree = "<group>"; };
|
|
||||||
F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToDismiss.swift; sourceTree = "<group>"; };
|
F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToDismiss.swift; sourceTree = "<group>"; };
|
||||||
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParicipantsView.swift; sourceTree = "<group>"; };
|
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParicipantsView.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -709,7 +664,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */,
|
3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */,
|
||||||
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */,
|
|
||||||
);
|
);
|
||||||
path = "Empty Views";
|
path = "Empty Views";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -718,7 +672,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3AA24801297E3DC20090C62D /* RepostView.swift */,
|
3AA24801297E3DC20090C62D /* RepostView.swift */,
|
||||||
4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */,
|
|
||||||
);
|
);
|
||||||
path = Reposts;
|
path = Reposts;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -823,23 +776,10 @@
|
|||||||
3AA59D1C2999B0400061C48E /* DraftsModel.swift */,
|
3AA59D1C2999B0400061C48E /* DraftsModel.swift */,
|
||||||
4C54AA0629A540BA003E4487 /* NotificationsModel.swift */,
|
4C54AA0629A540BA003E4487 /* NotificationsModel.swift */,
|
||||||
4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */,
|
4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */,
|
||||||
3A48E23A29D518F000BA313D /* Translations.swift */,
|
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
4C1A9A1B29DDCF8B00516EAC /* Settings */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
4C1A9A1C29DDCF9B00516EAC /* NotificationSettingsView.swift */,
|
|
||||||
4C1A9A1E29DDD24B00516EAC /* AppearanceSettingsView.swift */,
|
|
||||||
4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */,
|
|
||||||
4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */,
|
|
||||||
4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */,
|
|
||||||
);
|
|
||||||
path = Settings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
4C30AC7029A5676F00E2BD5A /* Notifications */ = {
|
4C30AC7029A5676F00E2BD5A /* Notifications */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -863,7 +803,6 @@
|
|||||||
4C75EFA227FA576C0006080F /* Views */ = {
|
4C75EFA227FA576C0006080F /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4C1A9A1B29DDCF8B00516EAC /* Settings */,
|
|
||||||
4CFF8F6129CC9A80008DB934 /* Images */,
|
4CFF8F6129CC9A80008DB934 /* Images */,
|
||||||
4CCEB7AC29B53D180078AA28 /* Search */,
|
4CCEB7AC29B53D180078AA28 /* Search */,
|
||||||
4C30AC7029A5676F00E2BD5A /* Notifications */,
|
4C30AC7029A5676F00E2BD5A /* Notifications */,
|
||||||
@@ -900,7 +839,6 @@
|
|||||||
4C75EFAC28049CFB0006080F /* PostButton.swift */,
|
4C75EFAC28049CFB0006080F /* PostButton.swift */,
|
||||||
4C75EFA327FA577B0006080F /* PostView.swift */,
|
4C75EFA327FA577B0006080F /* PostView.swift */,
|
||||||
9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */,
|
9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */,
|
||||||
F757933929D7AECD007DEAC1 /* ImagePicker.swift */,
|
|
||||||
9C83F89229A937B900136C08 /* TextViewWrapper.swift */,
|
9C83F89229A937B900136C08 /* TextViewWrapper.swift */,
|
||||||
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */,
|
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */,
|
||||||
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
|
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
|
||||||
@@ -919,6 +857,7 @@
|
|||||||
647D9A8C2968520300A295DE /* SideMenuView.swift */,
|
647D9A8C2968520300A295DE /* SideMenuView.swift */,
|
||||||
9609F057296E220800069BF3 /* BannerImageView.swift */,
|
9609F057296E220800069BF3 /* BannerImageView.swift */,
|
||||||
4CB8838E296F781C00DC99E7 /* ReactionsView.swift */,
|
4CB8838E296F781C00DC99E7 /* ReactionsView.swift */,
|
||||||
|
6439E013296790CF0020672B /* ProfileZoomView.swift */,
|
||||||
4CF0ABD529817F5B00D66079 /* ReportView.swift */,
|
4CF0ABD529817F5B00D66079 /* ReportView.swift */,
|
||||||
4CF0ABE42981EE0C00D66079 /* EULAView.swift */,
|
4CF0ABE42981EE0C00D66079 /* EULAView.swift */,
|
||||||
3AA247FE297E3D900090C62D /* RepostsView.swift */,
|
3AA247FE297E3D900090C62D /* RepostsView.swift */,
|
||||||
@@ -951,7 +890,6 @@
|
|||||||
4C7FF7D628233637009601DB /* Util */ = {
|
4C7FF7D628233637009601DB /* Util */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4CE4F0F329D779B5005914DB /* PostBox.swift */,
|
|
||||||
7C0F392D29B57C8F0039859C /* Extensions */,
|
7C0F392D29B57C8F0039859C /* Extensions */,
|
||||||
4CE879492995B58700F758CC /* Relays */,
|
4CE879492995B58700F758CC /* Relays */,
|
||||||
4CF0ABEA29844B2F00D66079 /* AnyCodable */,
|
4CF0ABEA29844B2F00D66079 /* AnyCodable */,
|
||||||
@@ -984,8 +922,6 @@
|
|||||||
3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */,
|
3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */,
|
||||||
4C30AC7729A577AB00E2BD5A /* EventCache.swift */,
|
4C30AC7729A577AB00E2BD5A /* EventCache.swift */,
|
||||||
4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */,
|
4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */,
|
||||||
4CE4F0F129D4FCFA005914DB /* DebouncedOnChange.swift */,
|
|
||||||
4C1A9A1929DCA17E00516EAC /* ReplyCounter.swift */,
|
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1028,7 +964,6 @@
|
|||||||
children = (
|
children = (
|
||||||
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
|
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
|
||||||
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
|
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
|
||||||
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */,
|
|
||||||
4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */,
|
4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */,
|
||||||
4C8682862814DE470026224F /* ProfileView.swift */,
|
4C8682862814DE470026224F /* ProfileView.swift */,
|
||||||
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
|
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
|
||||||
@@ -1052,7 +987,6 @@
|
|||||||
4CF0ABE6298444FC00D66079 /* MutedEventView.swift */,
|
4CF0ABE6298444FC00D66079 /* MutedEventView.swift */,
|
||||||
4C3D52B5298DB4E6001C5831 /* ZapEvent.swift */,
|
4C3D52B5298DB4E6001C5831 /* ZapEvent.swift */,
|
||||||
4C3D52B7298DB5C6001C5831 /* TextEvent.swift */,
|
4C3D52B7298DB5C6001C5831 /* TextEvent.swift */,
|
||||||
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */,
|
|
||||||
);
|
);
|
||||||
path = Events;
|
path = Events;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1102,9 +1036,6 @@
|
|||||||
4CB883AF297705DD00DC99E7 /* ZapButton.swift */,
|
4CB883AF297705DD00DC99E7 /* ZapButton.swift */,
|
||||||
4C42812B298C848200DBF26F /* TranslateView.swift */,
|
4C42812B298C848200DBF26F /* TranslateView.swift */,
|
||||||
7CFF6316299FEFE5005D382A /* SelectableText.swift */,
|
7CFF6316299FEFE5005D382A /* SelectableText.swift */,
|
||||||
4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */,
|
|
||||||
4CE4F0F729DB7399005914DB /* ThiccDivider.swift */,
|
|
||||||
4C1A9A2229DDDB8100516EAC /* IconLabel.swift */,
|
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1198,7 +1129,6 @@
|
|||||||
children = (
|
children = (
|
||||||
4CE8794729941DA700F758CC /* RelayFilters.swift */,
|
4CE8794729941DA700F758CC /* RelayFilters.swift */,
|
||||||
4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */,
|
4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */,
|
||||||
4CC6193929DC777C006A86D1 /* RelayBootstrap.swift */,
|
|
||||||
);
|
);
|
||||||
path = Relays;
|
path = Relays;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1259,7 +1189,6 @@
|
|||||||
children = (
|
children = (
|
||||||
4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */,
|
4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */,
|
||||||
4CFF8F6629CC9E3A008DB934 /* ImageView.swift */,
|
4CFF8F6629CC9E3A008DB934 /* ImageView.swift */,
|
||||||
6439E013296790CF0020672B /* ProfilePicImageView.swift */,
|
|
||||||
4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */,
|
4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */,
|
||||||
);
|
);
|
||||||
path = Images;
|
path = Images;
|
||||||
@@ -1374,35 +1303,32 @@
|
|||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
Base,
|
Base,
|
||||||
ar,
|
|
||||||
bg,
|
|
||||||
cs,
|
|
||||||
de,
|
|
||||||
"el-GR",
|
|
||||||
"en-US",
|
|
||||||
"es-419",
|
"es-419",
|
||||||
"es-ES",
|
"en-US",
|
||||||
fa,
|
|
||||||
"fr-CA",
|
|
||||||
"fr-FR",
|
|
||||||
"hu-HU",
|
|
||||||
id,
|
|
||||||
"it-IT",
|
|
||||||
ja,
|
|
||||||
ko,
|
|
||||||
"lv-LV",
|
|
||||||
nl,
|
|
||||||
"pl-PL",
|
|
||||||
"pt-BR",
|
|
||||||
"pt-PT",
|
|
||||||
ru,
|
|
||||||
"sv-SE",
|
|
||||||
"tr-TR",
|
"tr-TR",
|
||||||
uk,
|
"fr-FR",
|
||||||
vi,
|
"lv-LV",
|
||||||
|
"it-IT",
|
||||||
|
de,
|
||||||
|
"pt-PT",
|
||||||
|
"pl-PL",
|
||||||
|
ar,
|
||||||
|
nl,
|
||||||
"zh-CN",
|
"zh-CN",
|
||||||
|
"el-GR",
|
||||||
|
ja,
|
||||||
|
id,
|
||||||
|
cs,
|
||||||
|
ru,
|
||||||
"zh-HK",
|
"zh-HK",
|
||||||
"zh-TW",
|
"zh-TW",
|
||||||
|
uk,
|
||||||
|
bg,
|
||||||
|
fa,
|
||||||
|
ko,
|
||||||
|
"hu-HU",
|
||||||
|
"sv-SE",
|
||||||
|
"fr-CA",
|
||||||
);
|
);
|
||||||
mainGroup = 4CE6DEDA27F7A08100C66700;
|
mainGroup = 4CE6DEDA27F7A08100C66700;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
@@ -1475,7 +1401,6 @@
|
|||||||
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
|
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
|
||||||
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */,
|
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */,
|
||||||
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
||||||
F757933A29D7AECD007DEAC1 /* ImagePicker.swift in Sources */,
|
|
||||||
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
|
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
|
||||||
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
|
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
|
||||||
4CCEB7A929B29DD50078AA28 /* SearchResultsModel.swift in Sources */,
|
4CCEB7A929B29DD50078AA28 /* SearchResultsModel.swift in Sources */,
|
||||||
@@ -1486,7 +1411,6 @@
|
|||||||
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
|
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
|
||||||
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
||||||
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
|
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
|
||||||
4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */,
|
|
||||||
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
||||||
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
|
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
|
||||||
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
||||||
@@ -1565,7 +1489,6 @@
|
|||||||
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */,
|
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */,
|
||||||
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */,
|
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */,
|
||||||
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
|
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
|
||||||
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
|
|
||||||
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
|
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
|
||||||
4CE879522996B68900F758CC /* RelayType.swift in Sources */,
|
4CE879522996B68900F758CC /* RelayType.swift in Sources */,
|
||||||
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
|
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
|
||||||
@@ -1579,7 +1502,6 @@
|
|||||||
4CE879582996C45300F758CC /* ZapsView.swift in Sources */,
|
4CE879582996C45300F758CC /* ZapsView.swift in Sources */,
|
||||||
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */,
|
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */,
|
||||||
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */,
|
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */,
|
||||||
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */,
|
|
||||||
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */,
|
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */,
|
||||||
4C363A94282704FA006E126D /* Post.swift in Sources */,
|
4C363A94282704FA006E126D /* Post.swift in Sources */,
|
||||||
4C216F32286E388800040376 /* DMChatView.swift in Sources */,
|
4C216F32286E388800040376 /* DMChatView.swift in Sources */,
|
||||||
@@ -1589,11 +1511,9 @@
|
|||||||
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
|
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
|
||||||
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
||||||
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */,
|
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */,
|
||||||
4CE4F0F829DB7399005914DB /* ThiccDivider.swift in Sources */,
|
|
||||||
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
|
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
|
||||||
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
||||||
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
|
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
|
||||||
F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */,
|
|
||||||
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
|
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
|
||||||
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
||||||
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
|
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
|
||||||
@@ -1609,20 +1529,16 @@
|
|||||||
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */,
|
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */,
|
||||||
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
|
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
|
||||||
4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */,
|
4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */,
|
||||||
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */,
|
|
||||||
4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */,
|
4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */,
|
||||||
4CFF8F6B29CD0079008DB934 /* RepostedEvent.swift in Sources */,
|
|
||||||
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
|
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
|
||||||
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
|
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
|
||||||
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */,
|
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */,
|
||||||
4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */,
|
|
||||||
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
|
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
|
||||||
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
|
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
|
||||||
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
|
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
|
||||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */,
|
|
||||||
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
||||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
||||||
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */,
|
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */,
|
||||||
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
||||||
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
||||||
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
||||||
@@ -1653,10 +1569,8 @@
|
|||||||
4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */,
|
4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */,
|
||||||
4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */,
|
4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */,
|
||||||
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
||||||
4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */,
|
|
||||||
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
|
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
|
||||||
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
||||||
4C1A9A1A29DCA17E00516EAC /* ReplyCounter.swift in Sources */,
|
|
||||||
643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */,
|
643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */,
|
||||||
4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */,
|
4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */,
|
||||||
4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */,
|
4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */,
|
||||||
@@ -1666,11 +1580,9 @@
|
|||||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
|
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
|
||||||
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */,
|
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */,
|
||||||
4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */,
|
4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */,
|
||||||
3A48E23B29D518F000BA313D /* Translations.swift in Sources */,
|
|
||||||
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */,
|
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */,
|
||||||
4CF0ABD42980996B00D66079 /* Report.swift in Sources */,
|
4CF0ABD42980996B00D66079 /* Report.swift in Sources */,
|
||||||
4C06670B28FDE64700038D2A /* damus.c in Sources */,
|
4C06670B28FDE64700038D2A /* damus.c in Sources */,
|
||||||
4C1A9A2529DDDF2600516EAC /* ZapSettingsView.swift in Sources */,
|
|
||||||
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */,
|
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */,
|
||||||
3AAA95CC298E07E900F3D526 /* DeepLPlan.swift in Sources */,
|
3AAA95CC298E07E900F3D526 /* DeepLPlan.swift in Sources */,
|
||||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */,
|
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */,
|
||||||
@@ -1681,19 +1593,15 @@
|
|||||||
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */,
|
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */,
|
||||||
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
||||||
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
|
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
|
||||||
4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */,
|
|
||||||
4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */,
|
|
||||||
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
||||||
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
|
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
|
||||||
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
|
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
|
||||||
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */,
|
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */,
|
||||||
4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */,
|
4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */,
|
||||||
7C0F392F29B57CAF0039859C /* Binding+.swift in Sources */,
|
7C0F392F29B57CAF0039859C /* Binding+.swift in Sources */,
|
||||||
4C1A9A1F29DDD24B00516EAC /* AppearanceSettingsView.swift in Sources */,
|
|
||||||
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */,
|
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */,
|
||||||
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */,
|
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */,
|
||||||
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
|
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
|
||||||
4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -1774,9 +1682,6 @@
|
|||||||
3AD14EB529C40F38009D2D9C /* hu-HU */,
|
3AD14EB529C40F38009D2D9C /* hu-HU */,
|
||||||
3AD14EB829C40F3F009D2D9C /* sv-SE */,
|
3AD14EB829C40F3F009D2D9C /* sv-SE */,
|
||||||
3AD14EBC29C40F47009D2D9C /* fr-CA */,
|
3AD14EBC29C40F47009D2D9C /* fr-CA */,
|
||||||
3A325AC629C9E0B8002BE7ED /* vi */,
|
|
||||||
3A325AC929C9E0CF002BE7ED /* es-ES */,
|
|
||||||
3AC59CA929CDDB78007E04A6 /* pt-BR */,
|
|
||||||
);
|
);
|
||||||
name = Localizable.stringsdict;
|
name = Localizable.stringsdict;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1809,9 +1714,6 @@
|
|||||||
3AD14EB629C40F38009D2D9C /* hu-HU */,
|
3AD14EB629C40F38009D2D9C /* hu-HU */,
|
||||||
3AD14EB929C40F3F009D2D9C /* sv-SE */,
|
3AD14EB929C40F3F009D2D9C /* sv-SE */,
|
||||||
3AD14EBB29C40F47009D2D9C /* fr-CA */,
|
3AD14EBB29C40F47009D2D9C /* fr-CA */,
|
||||||
3A325AC529C9E0B8002BE7ED /* vi */,
|
|
||||||
3A325AC829C9E0CF002BE7ED /* es-ES */,
|
|
||||||
3AC59CA829CDDB78007E04A6 /* pt-BR */,
|
|
||||||
);
|
);
|
||||||
name = InfoPlist.strings;
|
name = InfoPlist.strings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1845,9 +1747,6 @@
|
|||||||
3AD14EB729C40F38009D2D9C /* hu-HU */,
|
3AD14EB729C40F38009D2D9C /* hu-HU */,
|
||||||
3AD14EBA29C40F3F009D2D9C /* sv-SE */,
|
3AD14EBA29C40F3F009D2D9C /* sv-SE */,
|
||||||
3AD14EBD29C40F47009D2D9C /* fr-CA */,
|
3AD14EBD29C40F47009D2D9C /* fr-CA */,
|
||||||
3A325AC429C9E0B8002BE7ED /* vi */,
|
|
||||||
3A325AC729C9E0CF002BE7ED /* es-ES */,
|
|
||||||
3AC59CA729CDDB78007E04A6 /* pt-BR */,
|
|
||||||
);
|
);
|
||||||
name = Localizable.strings;
|
name = Localizable.strings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1983,7 +1882,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 3;
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1991,9 +1890,7 @@
|
|||||||
INFOPLIST_FILE = damus/Info.plist;
|
INFOPLIST_FILE = damus/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Damus;
|
INFOPLIST_KEY_CFBundleDisplayName = Damus;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
INFOPLIST_KEY_NSCameraUsageDescription = "Damus needs access to your camera if you want to upload photos from it";
|
|
||||||
INFOPLIST_KEY_NSFaceIDUsageDescription = "Local authentication to access private key";
|
INFOPLIST_KEY_NSFaceIDUsageDescription = "Local authentication to access private key";
|
||||||
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Damus needs access to your microphone if you want to upload recorded videos from it";
|
|
||||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images.";
|
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images.";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
@@ -2008,7 +1905,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)",
|
"$(PROJECT_DIR)",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.1;
|
MARKETING_VERSION = 1.3.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -2027,7 +1924,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 3;
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -2035,9 +1932,7 @@
|
|||||||
INFOPLIST_FILE = damus/Info.plist;
|
INFOPLIST_FILE = damus/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Damus;
|
INFOPLIST_KEY_CFBundleDisplayName = Damus;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
INFOPLIST_KEY_NSCameraUsageDescription = "Damus needs access to your camera if you want to upload photos from it";
|
|
||||||
INFOPLIST_KEY_NSFaceIDUsageDescription = "Local authentication to access private key";
|
INFOPLIST_KEY_NSFaceIDUsageDescription = "Local authentication to access private key";
|
||||||
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Damus needs access to your microphone if you want to upload recorded videos from it";
|
|
||||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images.";
|
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images.";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
@@ -2052,7 +1947,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)",
|
"$(PROJECT_DIR)",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.1;
|
MARKETING_VERSION = 1.3.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xF4",
|
|
||||||
"green" : "0xEE",
|
|
||||||
"red" : "0xEE"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x1E",
|
|
||||||
"green" : "0x1C",
|
|
||||||
"red" : "0x1C"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "bitcoin-logo.svg",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20px" height="20px" viewBox="0 0 20 20" version="1.1">
|
|
||||||
<g id="surface1">
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(96.862745%,57.647059%,10.196078%);fill-opacity:1;" d="M 19.699219 12.417969 C 18.363281 17.777344 12.9375 21.035156 7.582031 19.699219 C 2.226562 18.363281 -1.035156 12.9375 0.300781 7.582031 C 1.636719 2.222656 7.0625 -1.035156 12.417969 0.300781 C 17.773438 1.632812 21.035156 7.0625 19.699219 12.417969 Z M 19.699219 12.417969 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 14.410156 8.574219 C 14.609375 7.246094 13.59375 6.53125 12.210938 6.050781 L 12.660156 4.25 L 11.5625 3.976562 L 11.125 5.730469 C 10.835938 5.660156 10.539062 5.589844 10.246094 5.523438 L 10.6875 3.757812 L 9.589844 3.484375 L 9.140625 5.285156 C 8.902344 5.230469 8.667969 5.179688 8.4375 5.121094 L 8.441406 5.117188 L 6.925781 4.738281 L 6.636719 5.910156 C 6.636719 5.910156 7.449219 6.097656 7.433594 6.109375 C 7.875 6.21875 7.957031 6.511719 7.941406 6.746094 L 7.429688 8.800781 C 7.460938 8.808594 7.5 8.820312 7.546875 8.835938 L 7.429688 8.808594 L 6.710938 11.683594 C 6.65625 11.820312 6.519531 12.023438 6.210938 11.945312 C 6.21875 11.960938 5.410156 11.746094 5.410156 11.746094 L 4.867188 13 L 6.296875 13.359375 C 6.5625 13.425781 6.820312 13.492188 7.078125 13.558594 L 6.621094 15.382812 L 7.71875 15.65625 L 8.167969 13.851562 C 8.46875 13.933594 8.757812 14.007812 9.042969 14.078125 L 8.59375 15.875 L 9.691406 16.148438 L 10.144531 14.328125 C 12.015625 14.683594 13.425781 14.539062 14.015625 12.847656 C 14.492188 11.484375 13.992188 10.699219 13.007812 10.1875 C 13.726562 10.019531 14.265625 9.550781 14.410156 8.574219 Z M 11.902344 12.089844 C 11.5625 13.453125 9.269531 12.71875 8.523438 12.53125 L 9.128906 10.117188 C 9.871094 10.300781 12.253906 10.667969 11.902344 12.089844 Z M 12.242188 8.554688 C 11.933594 9.796875 10.023438 9.164062 9.402344 9.011719 L 9.949219 6.820312 C 10.570312 6.976562 12.5625 7.261719 12.242188 8.554688 Z M 12.242188 8.554688 "/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -8,8 +8,8 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
let RECTANGLE_GRADIENT = LinearGradient(gradient: Gradient(colors: [
|
let RECTANGLE_GRADIENT = LinearGradient(gradient: Gradient(colors: [
|
||||||
DamusColors.purple,
|
Color("DamusPurple"),
|
||||||
DamusColors.blue
|
Color("DamusBlue")
|
||||||
]), startPoint: .leading, endPoint: .trailing)
|
]), startPoint: .leading, endPoint: .trailing)
|
||||||
|
|
||||||
struct CustomPicker<SelectionValue: Hashable, Content: View>: View {
|
struct CustomPicker<SelectionValue: Hashable, Content: View>: View {
|
||||||
@@ -56,6 +56,6 @@ struct CustomPicker<SelectionValue: Hashable, Content: View>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func textColor() -> Color {
|
func textColor() -> Color {
|
||||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
//
|
|
||||||
// DamusColors.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-03-27.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
class DamusColors {
|
|
||||||
static let adaptableGrey = Color("DamusAdaptableGrey")
|
|
||||||
static let white = Color("DamusWhite")
|
|
||||||
static let black = Color("DamusBlack")
|
|
||||||
static let lightGrey = Color("DamusLightGrey")
|
|
||||||
static let mediumGrey = Color("DamusMediumGrey")
|
|
||||||
static let darkGrey = Color("DamusDarkGrey")
|
|
||||||
static let green = Color("DamusGreen")
|
|
||||||
static let purple = Color("DamusPurple")
|
|
||||||
static let blue = Color("DamusBlue")
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
//
|
|
||||||
// IconLabel.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-04-05.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
struct IconLabel: View {
|
|
||||||
let text: String
|
|
||||||
let img_name: String
|
|
||||||
let img_color: Color
|
|
||||||
|
|
||||||
init(_ text: String, img_name: String, color: Color) {
|
|
||||||
self.text = text
|
|
||||||
self.img_name = img_name
|
|
||||||
self.img_color = color
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: img_name)
|
|
||||||
.foregroundColor(img_color)
|
|
||||||
.frame(width: 20)
|
|
||||||
.padding([.trailing], 20)
|
|
||||||
Text(text)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
|
|
||||||
struct IconLabel_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
Form {
|
|
||||||
Section {
|
|
||||||
IconLabel(NSLocalizedString("Keys", comment: "Settings section for managing keys"), img_name: "key.fill", color: .orange)
|
|
||||||
|
|
||||||
IconLabel(NSLocalizedString("Local Notifications", comment: "Section header for damus local notifications user configuration"), img_name: "bell.fill", color: .blue)
|
|
||||||
|
|
||||||
IconLabel(NSLocalizedString("Appearance", comment: "Section header for text and appearance settings"), img_name: "textformat", color: .red)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,42 +32,14 @@ struct ShareSheet: UIViewControllerRepresentable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum ImageShape {
|
|
||||||
case square
|
|
||||||
case landscape
|
|
||||||
case portrait
|
|
||||||
case unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct ImageCarousel: View {
|
struct ImageCarousel: View {
|
||||||
var urls: [URL]
|
var urls: [URL]
|
||||||
|
|
||||||
let evid: String
|
@State var open_sheet: Bool = false
|
||||||
let previews: PreviewCache
|
@State var current_url: URL? = nil
|
||||||
|
|
||||||
@State private var open_sheet: Bool = false
|
|
||||||
@State private var current_url: URL? = nil
|
|
||||||
@State private var image_fill: ImageFill? = nil
|
|
||||||
@State private var fillHeight: CGFloat = 350
|
|
||||||
@State private var maxHeight: CGFloat = UIScreen.main.bounds.height * 0.85
|
|
||||||
|
|
||||||
init(previews: PreviewCache, evid: String, urls: [URL]) {
|
|
||||||
_open_sheet = State(initialValue: false)
|
|
||||||
_current_url = State(initialValue: nil)
|
|
||||||
_image_fill = State(initialValue: previews.lookup_image_meta(evid))
|
|
||||||
self.urls = urls
|
|
||||||
self.evid = evid
|
|
||||||
self.previews = previews
|
|
||||||
}
|
|
||||||
|
|
||||||
var filling: Bool {
|
|
||||||
image_fill?.filling == true
|
|
||||||
}
|
|
||||||
|
|
||||||
var height: CGFloat {
|
|
||||||
image_fill?.height ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TabView {
|
TabView {
|
||||||
@@ -75,32 +47,31 @@ struct ImageCarousel: View {
|
|||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(Color.clear)
|
.foregroundColor(Color.clear)
|
||||||
.overlay {
|
.overlay {
|
||||||
GeometryReader { geo in
|
KFAnimatedImage(url)
|
||||||
KFAnimatedImage(url)
|
.imageContext(.note)
|
||||||
.callbackQueue(.dispatch(.global(qos:.background)))
|
.cancelOnDisappear(true)
|
||||||
.backgroundDecode(true)
|
.configure { view in
|
||||||
.imageContext(.note)
|
view.framePreloadCount = 3
|
||||||
.cancelOnDisappear(true)
|
}
|
||||||
.configure { view in
|
.aspectRatio(contentMode: .fit)
|
||||||
view.framePreloadCount = 3
|
.cornerRadius(10)
|
||||||
}
|
.tabItem {
|
||||||
.imageFill(for: geo.size, max: maxHeight, fill: fillHeight) { fill in
|
Text(url.absoluteString)
|
||||||
previews.cache_image_meta(evid: evid, image_fill: fill)
|
}
|
||||||
image_fill = fill
|
.id(url.absoluteString)
|
||||||
}
|
// .contextMenu {
|
||||||
.aspectRatio(contentMode: filling ? .fill : .fit)
|
// Button(NSLocalizedString("Copy Image", comment: "Context menu option to copy an image to clipboard.")) {
|
||||||
.tabItem {
|
// UIPasteboard.general.string = url.absoluteString
|
||||||
Text(url.absoluteString)
|
// }
|
||||||
}
|
// }
|
||||||
.id(url.absoluteString)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.fullScreenCover(isPresented: $open_sheet) {
|
.fullScreenCover(isPresented: $open_sheet) {
|
||||||
ImageView(urls: urls)
|
ImageView(urls: urls)
|
||||||
}
|
}
|
||||||
.frame(height: height)
|
.frame(height: 350)
|
||||||
|
.clipped()
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
open_sheet = true
|
open_sheet = true
|
||||||
}
|
}
|
||||||
@@ -108,71 +79,8 @@ struct ImageCarousel: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Image Modifier
|
|
||||||
extension KFOptionSetter {
|
|
||||||
/// Sets a block to get image size
|
|
||||||
///
|
|
||||||
/// - Parameter block: The block which is used to read the image object.
|
|
||||||
/// - Returns: `Self` value after read size
|
|
||||||
public func imageFill(for size: CGSize, max: CGFloat, fill: CGFloat, block: @escaping (ImageFill) throws -> Void) -> Self {
|
|
||||||
let modifier = AnyImageModifier { image -> KFCrossPlatformImage in
|
|
||||||
let img_size = image.size
|
|
||||||
let geo_size = size
|
|
||||||
let fill = ImageFill.calculate_image_fill(geo_size: geo_size,
|
|
||||||
img_size: img_size,
|
|
||||||
maxHeight: max,
|
|
||||||
fillHeight: fill)
|
|
||||||
DispatchQueue.main.async { [block, fill] in
|
|
||||||
try? block(fill)
|
|
||||||
}
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
options.imageModifier = modifier
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public struct ImageFill {
|
|
||||||
let filling: Bool?
|
|
||||||
let height: CGFloat
|
|
||||||
|
|
||||||
|
|
||||||
static func determine_image_shape(_ size: CGSize) -> ImageShape {
|
|
||||||
guard size.height > 0 else {
|
|
||||||
return .unknown
|
|
||||||
}
|
|
||||||
let imageRatio = size.width / size.height
|
|
||||||
switch imageRatio {
|
|
||||||
case 1.0: return .square
|
|
||||||
case ..<1.0: return .portrait
|
|
||||||
case 1.0...: return .landscape
|
|
||||||
default: return .unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func calculate_image_fill(geo_size: CGSize, img_size: CGSize, maxHeight: CGFloat, fillHeight: CGFloat) -> ImageFill {
|
|
||||||
let shape = determine_image_shape(img_size)
|
|
||||||
|
|
||||||
let xfactor = geo_size.width / img_size.width
|
|
||||||
let scaled = img_size.height * xfactor
|
|
||||||
// calculate scaled image height
|
|
||||||
// set scale factor and constrain images to minimum 150
|
|
||||||
// and animations to scaled factor for dynamic size adjustment
|
|
||||||
switch shape {
|
|
||||||
case .portrait, .landscape:
|
|
||||||
let filling = scaled > maxHeight
|
|
||||||
let height = filling ? fillHeight : scaled
|
|
||||||
return ImageFill(filling: filling, height: height)
|
|
||||||
case .square, .unknown:
|
|
||||||
return ImageFill(filling: nil, height: scaled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ImageCarousel_Previews: PreviewProvider {
|
struct ImageCarousel_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ImageCarousel(previews: test_damus_state().previews, evid: "evid", urls: [URL(string: "https://jb55.com/red-me.jpg")!,URL(string: "https://jb55.com/red-me.jpg")!])
|
ImageCarousel(urls: [URL(string: "https://jb55.com/red-me.jpg")!,URL(string: "https://jb55.com/red-me.jpg")!])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ struct InvoiceView: View {
|
|||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "checkmark.circle")
|
Image(systemName: "checkmark.circle")
|
||||||
.foregroundColor(DamusColors.green)
|
.foregroundColor(Color("DamusGreen"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,33 +24,19 @@ struct NIP05Badge: View {
|
|||||||
self.clickable = clickable
|
self.clickable = clickable
|
||||||
}
|
}
|
||||||
|
|
||||||
var nip05_color: Bool {
|
var nip05_color: Color {
|
||||||
return use_nip05_color(pubkey: pubkey, contacts: contacts)
|
return get_nip05_color(pubkey: pubkey, contacts: contacts)
|
||||||
}
|
|
||||||
|
|
||||||
var Seal: some View {
|
|
||||||
Group {
|
|
||||||
if nip05_color {
|
|
||||||
LINEAR_GRADIENT
|
|
||||||
.mask(Image(systemName: "checkmark.seal.fill")
|
|
||||||
.resizable()
|
|
||||||
).frame(width: 14, height: 14)
|
|
||||||
} else {
|
|
||||||
Image(systemName: "checkmark.seal.fill")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Seal
|
Image(systemName: "checkmark.seal.fill")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(nip05_color)
|
||||||
if show_domain {
|
if show_domain {
|
||||||
if clickable {
|
if clickable {
|
||||||
Text(nip05.host)
|
Text(nip05.host)
|
||||||
.nip05_colorized(gradient: nip05_color)
|
.foregroundColor(nip05_color)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if let nip5url = nip05.siteUrl {
|
if let nip5url = nip05.siteUrl {
|
||||||
openURL(nip5url)
|
openURL(nip5url)
|
||||||
@@ -58,7 +44,7 @@ struct NIP05Badge: View {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(nip05.host)
|
Text(nip05.host)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(nip05_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,19 +52,8 @@ struct NIP05Badge: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension View {
|
func get_nip05_color(pubkey: String, contacts: Contacts) -> Color {
|
||||||
func nip05_colorized(gradient: Bool) -> some View {
|
return contacts.is_friend_or_self(pubkey) ? .accentColor : .gray
|
||||||
if gradient {
|
|
||||||
return AnyView(self.foregroundStyle(LINEAR_GRADIENT))
|
|
||||||
} else {
|
|
||||||
return AnyView(self.foregroundColor(.gray))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func use_nip05_color(pubkey: String, contacts: Contacts) -> Bool {
|
|
||||||
return contacts.is_friend_or_self(pubkey) ? true : false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NIP05Badge_Previews: PreviewProvider {
|
struct NIP05Badge_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ struct Reposted: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Image(systemName: "arrow.2.squarepath")
|
Image(systemName: "arrow.2.squarepath")
|
||||||
|
.font(.system(size: 13, weight: .heavy))
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: true, show_nip5_domain: false)
|
ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: true, show_nip5_domain: false)
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
Text("Reposted", comment: "Text indicating that the post was reposted (i.e. re-shared).")
|
Text("Reposted", comment: "Text indicating that the post was reposted (i.e. re-shared).")
|
||||||
|
.font(.system(size: 14, weight: .heavy))
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,12 @@ struct SelectableText: View {
|
|||||||
@State private var selectedTextHeight: CGFloat = .zero
|
@State private var selectedTextHeight: CGFloat = .zero
|
||||||
@State private var selectedTextWidth: CGFloat = .zero
|
@State private var selectedTextWidth: CGFloat = .zero
|
||||||
|
|
||||||
let size: EventViewKind
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geo in
|
GeometryReader { geo in
|
||||||
TextViewRepresentable(
|
TextViewRepresentable(
|
||||||
attributedString: attributedString,
|
attributedString: attributedString,
|
||||||
textColor: UIColor.label,
|
textColor: UIColor.label,
|
||||||
font: eventviewsize_to_uifont(size),
|
font: UIFont.preferredFont(forTextStyle: .title2),
|
||||||
fixedWidth: selectedTextWidth,
|
fixedWidth: selectedTextWidth,
|
||||||
height: $selectedTextHeight
|
height: $selectedTextHeight
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
//
|
|
||||||
// ThiccDivider.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-04-03.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ThiccDivider: View {
|
|
||||||
var body: some View {
|
|
||||||
Rectangle()
|
|
||||||
.frame(height: 4)
|
|
||||||
.foregroundColor(DamusColors.adaptableGrey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ThiccDivider_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
ThiccDivider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,131 +11,135 @@ import NaturalLanguage
|
|||||||
struct TranslateView: View {
|
struct TranslateView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let size: EventViewKind
|
|
||||||
|
|
||||||
@State var checkingTranslationStatus: Bool = false
|
@State var checkingTranslationStatus: Bool = false
|
||||||
@State var translatable: Bool = true
|
@State var currentLanguage: String = "en"
|
||||||
|
@State var noteLanguage: String? = nil
|
||||||
@State var noteLanguage: String?
|
@State var translated_note: String? = nil
|
||||||
@State var show_translated_note: Bool
|
@State var show_translated_note: Bool = false
|
||||||
@State var translated_artifacts: NoteArtifacts?
|
@State var translated_artifacts: NoteArtifacts? = nil
|
||||||
|
|
||||||
let preferredLanguages = Set(Locale.preferredLanguages.map { localeToLanguage($0) })
|
|
||||||
|
|
||||||
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind) {
|
|
||||||
self.damus_state = damus_state
|
|
||||||
self.event = event
|
|
||||||
self.size = size
|
|
||||||
self._noteLanguage = State(initialValue: damus_state.translations.detectLanguage(event, state: damus_state))
|
|
||||||
|
|
||||||
if let translationWithLanguage = damus_state.translations.cachedTranslation(event) {
|
|
||||||
self._noteLanguage = State(initialValue: translationWithLanguage.language)
|
|
||||||
|
|
||||||
let translatedBlocks = event.get_blocks(content: translationWithLanguage.translation)
|
|
||||||
self._translated_artifacts = State.init(initialValue: render_blocks(blocks: translatedBlocks, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey))
|
|
||||||
} else {
|
|
||||||
self._translated_artifacts = State(initialValue: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
self._show_translated_note = State(initialValue: damus_state.settings.auto_translate)
|
|
||||||
}
|
|
||||||
|
|
||||||
var TranslateButton: some View {
|
var TranslateButton: some View {
|
||||||
Button(NSLocalizedString("Translate Note", comment: "Button to translate note from different language.")) {
|
Button(NSLocalizedString("Translate Note into your language", comment: "Button to translate note from different language.")) {
|
||||||
show_translated_note = true
|
show_translated_note = true
|
||||||
processTranslation()
|
|
||||||
}
|
}
|
||||||
.translate_button_style()
|
.translate_button_style()
|
||||||
}
|
}
|
||||||
|
|
||||||
func processTranslation() {
|
|
||||||
guard noteLanguage != nil && !checkingTranslationStatus && translatable else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
checkingTranslationStatus = true
|
|
||||||
show_translated_note = true
|
|
||||||
|
|
||||||
Task {
|
|
||||||
let translationWithLanguage = await damus_state.translations.translate(event, state: damus_state)
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
guard translationWithLanguage != nil else {
|
|
||||||
noteLanguage = damus_state.translations.targetLanguage
|
|
||||||
checkingTranslationStatus = false
|
|
||||||
show_translated_note = false
|
|
||||||
translatable = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
noteLanguage = translationWithLanguage!.language
|
|
||||||
|
|
||||||
// Render translated note.
|
|
||||||
let translatedBlocks = event.get_blocks(content: translationWithLanguage!.translation)
|
|
||||||
translated_artifacts = render_blocks(blocks: translatedBlocks, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
|
|
||||||
|
|
||||||
translatable = true
|
|
||||||
|
|
||||||
checkingTranslationStatus = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Translated(lang: String, artifacts: NoteArtifacts) -> some View {
|
func Translated(lang: String, artifacts: NoteArtifacts) -> some View {
|
||||||
return Group {
|
return Group {
|
||||||
Button(String(format: NSLocalizedString("Translated from %@", comment: "Button to indicate that the note has been translated from a different language."), lang)) {
|
Button(NSLocalizedString("Translated from \(lang)", comment: "Button to indicate that the note has been translated from a different language.")) {
|
||||||
show_translated_note = false
|
show_translated_note = false
|
||||||
}
|
}
|
||||||
.translate_button_style()
|
.translate_button_style()
|
||||||
|
|
||||||
SelectableText(attributedString: artifacts.content, size: self.size)
|
SelectableText(attributedString: artifacts.content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckingStatus(lang: String) -> some View {
|
||||||
|
return Button(NSLocalizedString("Translating from \(lang)...", comment: "Button to indicate that the note is in the process of being translated from a different language.")) {
|
||||||
|
show_translated_note = false
|
||||||
|
}
|
||||||
|
.translate_button_style()
|
||||||
|
}
|
||||||
|
|
||||||
func MainContent(note_lang: String) -> some View {
|
func MainContent(note_lang: String) -> some View {
|
||||||
return Group {
|
return Group {
|
||||||
if translatable {
|
let languageName = Locale.current.localizedString(forLanguageCode: note_lang)
|
||||||
let languageName = Locale.current.localizedString(forLanguageCode: note_lang)
|
if let lang = languageName, show_translated_note {
|
||||||
if let languageName, let translated_artifacts, show_translated_note {
|
if checkingTranslationStatus {
|
||||||
Translated(lang: languageName, artifacts: translated_artifacts)
|
CheckingStatus(lang: lang)
|
||||||
} else if !damus_state.settings.auto_translate {
|
} else if let artifacts = translated_artifacts {
|
||||||
TranslateButton
|
Translated(lang: lang, artifacts: artifacts)
|
||||||
} else {
|
|
||||||
EmptyView()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
TranslateButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let note_lang = noteLanguage, note_lang != damus_state.translations.targetLanguage {
|
if let note_lang = noteLanguage, noteLanguage != currentLanguage {
|
||||||
MainContent(note_lang: note_lang)
|
MainContent(note_lang: note_lang)
|
||||||
.task {
|
|
||||||
if show_translated_note {
|
|
||||||
processTranslation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Text("")
|
Text("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.task {
|
||||||
}
|
guard noteLanguage == nil && !checkingTranslationStatus && damus_state.settings.can_translate(damus_state.pubkey) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
checkingTranslationStatus = true
|
||||||
|
|
||||||
extension View {
|
if #available(iOS 16, *) {
|
||||||
func translate_button_style() -> some View {
|
currentLanguage = Locale.current.language.languageCode?.identifier ?? "en"
|
||||||
return self
|
} else {
|
||||||
.font(.footnote)
|
currentLanguage = Locale.current.languageCode ?? "en"
|
||||||
.contentShape(Rectangle())
|
}
|
||||||
.padding([.top, .bottom], 10)
|
|
||||||
|
// Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in
|
||||||
|
// and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer.
|
||||||
|
let originalBlocks = event.blocks(damus_state.keypair.privkey)
|
||||||
|
let originalOnlyText = originalBlocks.compactMap { $0.is_text }.joined(separator: " ")
|
||||||
|
|
||||||
|
// Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate.
|
||||||
|
let languageRecognizer = NLLanguageRecognizer()
|
||||||
|
languageRecognizer.processString(originalOnlyText)
|
||||||
|
noteLanguage = languageRecognizer.languageHypotheses(withMaximum: 1).first(where: { $0.value >= 0.5 })?.key.rawValue ?? currentLanguage
|
||||||
|
|
||||||
|
if let lang = noteLanguage, noteLanguage != currentLanguage {
|
||||||
|
// If the detected dominant language is a variant, remove the variant component and just take the language part as translation services typically only supports the variant-less language.
|
||||||
|
if #available(iOS 16, *) {
|
||||||
|
noteLanguage = Locale.LanguageCode(stringLiteral: lang).identifier(.alpha2)
|
||||||
|
} else {
|
||||||
|
noteLanguage = NSLocale(localeIdentifier: lang).languageCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let note_lang = noteLanguage else {
|
||||||
|
noteLanguage = currentLanguage
|
||||||
|
translated_note = nil
|
||||||
|
checkingTranslationStatus = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if note_lang != currentLanguage {
|
||||||
|
do {
|
||||||
|
// If the note language is different from our language, send a translation request.
|
||||||
|
let translator = Translator(damus_state.settings)
|
||||||
|
let originalContent = event.get_content(damus_state.keypair.privkey)
|
||||||
|
translated_note = try await translator.translate(originalContent, from: note_lang, to: currentLanguage)
|
||||||
|
|
||||||
|
if originalContent == translated_note {
|
||||||
|
// If the translation is the same as the original, don't bother showing it.
|
||||||
|
noteLanguage = currentLanguage
|
||||||
|
translated_note = nil
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// If for whatever reason we're not able to figure out the language of the note, or translate the note, fail gracefully and do not retry. It's not the end of the world. Don't want to take down someone's translation server with an accidental denial of service attack.
|
||||||
|
noteLanguage = currentLanguage
|
||||||
|
translated_note = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let translated = translated_note {
|
||||||
|
// Render translated note.
|
||||||
|
let translatedBlocks = event.get_blocks(content: translated)
|
||||||
|
translated_artifacts = render_blocks(blocks: translatedBlocks, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkingTranslationStatus = false
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TranslateView_Previews: PreviewProvider {
|
struct TranslateView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let ds = test_damus_state()
|
let ds = test_damus_state()
|
||||||
TranslateView(damus_state: ds, event: test_event, size: .normal)
|
TranslateView(damus_state: ds, event: test_event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ struct WebsiteLink: View {
|
|||||||
}, label: {
|
}, label: {
|
||||||
Text(link_text)
|
Text(link_text)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ struct ZapButton: View {
|
|||||||
|
|
||||||
struct ZapButton_Previews: PreviewProvider {
|
struct ZapButton_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let bar = ActionBarModel(likes: 0, boosts: 0, zaps: 10, zap_total: 15623414, replies: 2, our_like: nil, our_boost: nil, our_zap: nil, our_reply: nil)
|
let bar = ActionBarModel(likes: 0, boosts: 0, zaps: 10, zap_total: 15623414, our_like: nil, our_boost: nil, our_zap: nil)
|
||||||
ZapButton(damus_state: test_damus_state(), event: test_event, lnurl: "lnurl", bar: bar)
|
ZapButton(damus_state: test_damus_state(), event: test_event, lnurl: "lnurl", bar: bar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-73
@@ -8,10 +8,16 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Starscream
|
import Starscream
|
||||||
|
|
||||||
|
var BOOTSTRAP_RELAYS = [
|
||||||
|
"wss://relay.damus.io",
|
||||||
|
"wss://eden.nostr.land",
|
||||||
|
"wss://nostr.wine",
|
||||||
|
"wss://nos.lol",
|
||||||
|
]
|
||||||
|
|
||||||
struct TimestampedProfile {
|
struct TimestampedProfile {
|
||||||
let profile: Profile
|
let profile: Profile
|
||||||
let timestamp: Int64
|
let timestamp: Int64
|
||||||
let event: NostrEvent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Sheets: Identifiable {
|
enum Sheets: Identifiable {
|
||||||
@@ -76,9 +82,9 @@ struct ContentView: View {
|
|||||||
@State var profile_open: Bool = false
|
@State var profile_open: Bool = false
|
||||||
@State var thread_open: Bool = false
|
@State var thread_open: Bool = false
|
||||||
@State var search_open: Bool = false
|
@State var search_open: Bool = false
|
||||||
@State var muting: String? = nil
|
@State var blocking: String? = nil
|
||||||
@State var confirm_mute: Bool = false
|
@State var confirm_block: Bool = false
|
||||||
@State var user_muted_confirm: Bool = false
|
@State var user_blocked_confirm: Bool = false
|
||||||
@State var confirm_overwrite_mutelist: Bool = false
|
@State var confirm_overwrite_mutelist: Bool = false
|
||||||
@State var current_boost: NostrEvent? = nil
|
@State var current_boost: NostrEvent? = nil
|
||||||
@State var filter_state : FilterState = .posts_and_replies
|
@State var filter_state : FilterState = .posts_and_replies
|
||||||
@@ -141,8 +147,22 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var timelineNavItem: Text {
|
var timelineNavItem: Text {
|
||||||
return Text(timeline_name(selected_timeline))
|
switch selected_timeline {
|
||||||
.bold()
|
case .home:
|
||||||
|
return Text("Home", comment: "Navigation bar title for Home view where posts and replies appear from those who the user is following.")
|
||||||
|
.bold()
|
||||||
|
case .dms:
|
||||||
|
return Text("DMs", comment: "Toolbar label for DMs view, where DM is the English abbreviation for Direct Message.")
|
||||||
|
.bold()
|
||||||
|
case .notifications:
|
||||||
|
return Text("Notifications", comment: "Toolbar label for Notifications view.")
|
||||||
|
.bold()
|
||||||
|
case .search:
|
||||||
|
return Text("Universe 🛸", comment: "Toolbar label for the universal view where posts from all connected relay servers appear.")
|
||||||
|
.bold()
|
||||||
|
case .none:
|
||||||
|
return Text(verbatim: "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MainContent(damus: DamusState) -> some View {
|
func MainContent(damus: DamusState) -> some View {
|
||||||
@@ -191,7 +211,7 @@ struct ContentView: View {
|
|||||||
Image("damus-home")
|
Image("damus-home")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width:30,height:30)
|
.frame(width:30,height:30)
|
||||||
.shadow(color: DamusColors.purple, radius: 2)
|
.shadow(color: Color("DamusPurple"), radius: 2)
|
||||||
.opacity(isSideBarOpened ? 0 : 1)
|
.opacity(isSideBarOpened ? 0 : 1)
|
||||||
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
|
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
|
||||||
} else {
|
} else {
|
||||||
@@ -228,9 +248,9 @@ struct ContentView: View {
|
|||||||
|
|
||||||
func MaybeReportView(target: ReportTarget) -> some View {
|
func MaybeReportView(target: ReportTarget) -> some View {
|
||||||
Group {
|
Group {
|
||||||
if let damus_state {
|
if let ds = damus_state {
|
||||||
if let sec = damus_state.keypair.privkey {
|
if let sec = ds.keypair.privkey {
|
||||||
ReportView(postbox: damus_state.postbox, target: target, privkey: sec)
|
ReportView(pool: ds.pool, target: target, privkey: sec)
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
@@ -306,9 +326,9 @@ struct ContentView: View {
|
|||||||
case .report(let target):
|
case .report(let target):
|
||||||
MaybeReportView(target: target)
|
MaybeReportView(target: target)
|
||||||
case .post:
|
case .post:
|
||||||
PostView(replying_to: nil, damus_state: damus_state!)
|
PostView(replying_to: nil, references: [], damus_state: damus_state!)
|
||||||
case .reply(let event):
|
case .reply(let event):
|
||||||
PostView(replying_to: event, damus_state: damus_state!)
|
ReplyView(replying_to: event, damus: damus_state!)
|
||||||
case .event:
|
case .event:
|
||||||
EventDetailView()
|
EventDetailView()
|
||||||
case .filter:
|
case .filter:
|
||||||
@@ -369,20 +389,14 @@ struct ContentView: View {
|
|||||||
let target = notif.object as! ReportTarget
|
let target = notif.object as! ReportTarget
|
||||||
self.active_sheet = .report(target)
|
self.active_sheet = .report(target)
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.mute)) { notif in
|
.onReceive(handle_notify(.block)) { notif in
|
||||||
let pubkey = notif.object as! String
|
let pubkey = notif.object as! String
|
||||||
self.muting = pubkey
|
self.blocking = pubkey
|
||||||
self.confirm_mute = true
|
self.confirm_block = true
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.broadcast_event)) { obj in
|
.onReceive(handle_notify(.broadcast_event)) { obj in
|
||||||
let ev = obj.object as! NostrEvent
|
let ev = obj.object as! NostrEvent
|
||||||
guard let ds = self.damus_state else {
|
self.damus_state?.pool.send(.event(ev))
|
||||||
return
|
|
||||||
}
|
|
||||||
ds.postbox.send(ev)
|
|
||||||
if let profile = ds.profiles.profiles[ev.pubkey] {
|
|
||||||
ds.postbox.send(profile.event)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.unfollow)) { notif in
|
.onReceive(handle_notify(.unfollow)) { notif in
|
||||||
guard let privkey = self.privkey else {
|
guard let privkey = self.privkey else {
|
||||||
@@ -396,7 +410,7 @@ struct ContentView: View {
|
|||||||
let target = notif.object as! FollowTarget
|
let target = notif.object as! FollowTarget
|
||||||
let pk = target.pubkey
|
let pk = target.pubkey
|
||||||
|
|
||||||
if let ev = unfollow_user(postbox: damus.postbox,
|
if let ev = unfollow_user(pool: damus.pool,
|
||||||
our_contacts: damus.contacts.event,
|
our_contacts: damus.contacts.event,
|
||||||
pubkey: damus.pubkey,
|
pubkey: damus.pubkey,
|
||||||
privkey: privkey,
|
privkey: privkey,
|
||||||
@@ -447,16 +461,7 @@ struct ContentView: View {
|
|||||||
//let to_relays = tup.1
|
//let to_relays = tup.1
|
||||||
print("post \(post.content)")
|
print("post \(post.content)")
|
||||||
let new_ev = post_to_event(post: post, privkey: privkey, pubkey: pubkey)
|
let new_ev = post_to_event(post: post, privkey: privkey, pubkey: pubkey)
|
||||||
guard let ds = self.damus_state else {
|
self.damus_state?.pool.send(.event(new_ev))
|
||||||
return
|
|
||||||
}
|
|
||||||
ds.postbox.send(new_ev)
|
|
||||||
for eref in new_ev.referenced_ids.prefix(3) {
|
|
||||||
// also broadcast at most 3 referenced events
|
|
||||||
if let ev = ds.events.lookup(eref.ref_id) {
|
|
||||||
ds.postbox.send(ev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case .cancel:
|
case .cancel:
|
||||||
active_sheet = nil
|
active_sheet = nil
|
||||||
print("post cancelled")
|
print("post cancelled")
|
||||||
@@ -474,23 +479,23 @@ struct ContentView: View {
|
|||||||
notify(.logout, ())
|
notify(.logout, ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alert(NSLocalizedString("User muted", comment: "Alert message to indicate the user has been muted"), isPresented: $user_muted_confirm, actions: {
|
.alert(NSLocalizedString("User blocked", comment: "Alert message to indicate the user has been blocked"), isPresented: $user_blocked_confirm, actions: {
|
||||||
Button(NSLocalizedString("Thanks!", comment: "Button to close out of alert that informs that the action to muted a user was successful.")) {
|
Button(NSLocalizedString("Thanks!", comment: "Button to close out of alert that informs that the action to block a user was successful.")) {
|
||||||
user_muted_confirm = false
|
user_blocked_confirm = false
|
||||||
}
|
}
|
||||||
}, message: {
|
}, message: {
|
||||||
if let pubkey = self.muting {
|
if let pubkey = self.blocking {
|
||||||
let profile = damus_state!.profiles.lookup(id: pubkey)
|
let profile = damus_state!.profiles.lookup(id: pubkey)
|
||||||
let name = Profile.displayName(profile: profile, pubkey: pubkey).username
|
let name = Profile.displayName(profile: profile, pubkey: pubkey).username
|
||||||
Text("\(name) has been muted", comment: "Alert message that informs a user was muted.")
|
Text("\(name) has been blocked", comment: "Alert message that informs a user was blocked.")
|
||||||
} else {
|
} else {
|
||||||
Text("User has been muted", comment: "Alert message that informs a user was d.")
|
Text("User has been blocked", comment: "Alert message that informs a user was blocked.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.alert(NSLocalizedString("Create new mutelist", comment: "Title of alert prompting the user to create a new mutelist."), isPresented: $confirm_overwrite_mutelist, actions: {
|
.alert(NSLocalizedString("Create new mutelist", comment: "Title of alert prompting the user to create a new mutelist."), isPresented: $confirm_overwrite_mutelist, actions: {
|
||||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of alert that creates a new mutelist.")) {
|
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of alert that creates a new mutelist.")) {
|
||||||
confirm_overwrite_mutelist = false
|
confirm_overwrite_mutelist = false
|
||||||
confirm_mute = false
|
confirm_block = false
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(NSLocalizedString("Yes, Overwrite", comment: "Text of button that confirms to overwrite the existing mutelist.")) {
|
Button(NSLocalizedString("Yes, Overwrite", comment: "Text of button that confirms to overwrite the existing mutelist.")) {
|
||||||
@@ -502,7 +507,7 @@ struct ContentView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let pubkey = muting else {
|
guard let pubkey = blocking else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,20 +516,20 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
damus_state?.contacts.set_mutelist(mutelist)
|
damus_state?.contacts.set_mutelist(mutelist)
|
||||||
ds.postbox.send(mutelist)
|
ds.pool.send(.event(mutelist))
|
||||||
|
|
||||||
confirm_overwrite_mutelist = false
|
confirm_overwrite_mutelist = false
|
||||||
confirm_mute = false
|
confirm_block = false
|
||||||
user_muted_confirm = true
|
user_blocked_confirm = true
|
||||||
}
|
}
|
||||||
}, message: {
|
}, message: {
|
||||||
Text("No mute list found, create a new one? This will overwrite any previous mute lists.", comment: "Alert message prompt that asks if the user wants to create a new mute list, overwriting previous mute lists.")
|
Text("No block list found, create a new one? This will overwrite any previous block lists.", comment: "Alert message prompt that asks if the user wants to create a new block list, overwriting previous block lists.")
|
||||||
})
|
})
|
||||||
.alert(NSLocalizedString("Mute User", comment: "Title of alert for muting a user."), isPresented: $confirm_mute, actions: {
|
.alert(NSLocalizedString("Block User", comment: "Title of alert for blocking a user."), isPresented: $confirm_block, actions: {
|
||||||
Button(NSLocalizedString("Cancel", comment: "Alert button to cancel out of alert for muting a user."), role: .cancel) {
|
Button(NSLocalizedString("Cancel", comment: "Alert button to cancel out of alert for blocking a user."), role: .cancel) {
|
||||||
confirm_mute = false
|
confirm_block = false
|
||||||
}
|
}
|
||||||
Button(NSLocalizedString("Mute", comment: "Alert button to mute a user."), role: .destructive) {
|
Button(NSLocalizedString("Block", comment: "Alert button to block a user."), role: .destructive) {
|
||||||
guard let ds = damus_state else {
|
guard let ds = damus_state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -535,7 +540,7 @@ struct ContentView: View {
|
|||||||
guard let keypair = ds.keypair.to_full() else {
|
guard let keypair = ds.keypair.to_full() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let pubkey = muting else {
|
guard let pubkey = blocking else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,16 +548,16 @@ struct ContentView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
damus_state?.contacts.set_mutelist(ev)
|
damus_state?.contacts.set_mutelist(ev)
|
||||||
ds.postbox.send(ev)
|
ds.pool.send(.event(ev))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, message: {
|
}, message: {
|
||||||
if let pubkey = muting {
|
if let pubkey = blocking {
|
||||||
let profile = damus_state?.profiles.lookup(id: pubkey)
|
let profile = damus_state?.profiles.lookup(id: pubkey)
|
||||||
let name = Profile.displayName(profile: profile, pubkey: pubkey).username
|
let name = Profile.displayName(profile: profile, pubkey: pubkey).username
|
||||||
Text("Mute \(name)?", comment: "Alert message prompt to ask if a user should be muted.")
|
Text("Block \(name)?", comment: "Alert message prompt to ask if a user should be blocked.")
|
||||||
} else {
|
} else {
|
||||||
Text("Could not find user to mute...", comment: "Alert message to indicate that the muted user could not be found.")
|
Text("Could not find user to block...", comment: "Alert message to indicate that the blocked user could not be found.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.alert(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post."), isPresented: $current_boost.mappedToBool()) {
|
.alert(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post."), isPresented: $current_boost.mappedToBool()) {
|
||||||
@@ -560,9 +565,7 @@ struct ContentView: View {
|
|||||||
current_boost = nil
|
current_boost = nil
|
||||||
}
|
}
|
||||||
Button(NSLocalizedString("Repost", comment: "Button to confirm reposting a post.")) {
|
Button(NSLocalizedString("Repost", comment: "Button to confirm reposting a post.")) {
|
||||||
if let current_boost {
|
self.damus_state?.pool.send(.event(current_boost!))
|
||||||
self.damus_state?.pool.send(.event(current_boost))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} message: {
|
} message: {
|
||||||
Text("Are you sure you want to repost this?", comment: "Alert message to ask if user wants to repost a post.")
|
Text("Are you sure you want to repost this?", comment: "Alert message to ask if user wants to repost a post.")
|
||||||
@@ -598,10 +601,9 @@ struct ContentView: View {
|
|||||||
let pool = RelayPool()
|
let pool = RelayPool()
|
||||||
let metadatas = RelayMetadatas()
|
let metadatas = RelayMetadatas()
|
||||||
let relay_filters = RelayFilters(our_pubkey: pubkey)
|
let relay_filters = RelayFilters(our_pubkey: pubkey)
|
||||||
let bootstrap_relays = load_bootstrap_relays(pubkey: pubkey)
|
|
||||||
|
|
||||||
let new_relay_filters = load_relay_filters(pubkey) == nil
|
let new_relay_filters = load_relay_filters(pubkey) == nil
|
||||||
for relay in bootstrap_relays {
|
for relay in BOOTSTRAP_RELAYS {
|
||||||
if let url = URL(string: relay) {
|
if let url = URL(string: relay) {
|
||||||
add_new_relay(relay_filters: relay_filters, metadatas: metadatas, pool: pool, url: url, info: .rw, new_relay_filters: new_relay_filters)
|
add_new_relay(relay_filters: relay_filters, metadatas: metadatas, pool: pool, url: url, info: .rw, new_relay_filters: new_relay_filters)
|
||||||
}
|
}
|
||||||
@@ -609,8 +611,6 @@ struct ContentView: View {
|
|||||||
|
|
||||||
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
||||||
|
|
||||||
let settings = UserSettingsStore()
|
|
||||||
|
|
||||||
self.damus_state = DamusState(pool: pool,
|
self.damus_state = DamusState(pool: pool,
|
||||||
keypair: keypair,
|
keypair: keypair,
|
||||||
likes: EventCounter(our_pubkey: pubkey),
|
likes: EventCounter(our_pubkey: pubkey),
|
||||||
@@ -622,16 +622,12 @@ struct ContentView: View {
|
|||||||
previews: PreviewCache(),
|
previews: PreviewCache(),
|
||||||
zaps: Zaps(our_pubkey: pubkey),
|
zaps: Zaps(our_pubkey: pubkey),
|
||||||
lnurls: LNUrls(),
|
lnurls: LNUrls(),
|
||||||
settings: settings,
|
settings: UserSettingsStore(),
|
||||||
relay_filters: relay_filters,
|
relay_filters: relay_filters,
|
||||||
relay_metadata: metadatas,
|
relay_metadata: metadatas,
|
||||||
drafts: Drafts(),
|
drafts: Drafts(),
|
||||||
events: EventCache(),
|
events: EventCache(),
|
||||||
bookmarks: BookmarksManager(pubkey: pubkey),
|
bookmarks: BookmarksManager(pubkey: pubkey)
|
||||||
postbox: PostBox(pool: pool),
|
|
||||||
bootstrap_relays: bootstrap_relays,
|
|
||||||
replies: ReplyCounter(our_pubkey: pubkey),
|
|
||||||
translations: Translations(settings)
|
|
||||||
)
|
)
|
||||||
home.damus_state = self.damus_state!
|
home.damus_state = self.damus_state!
|
||||||
|
|
||||||
@@ -780,6 +776,7 @@ func setup_notifications() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func find_event(state: DamusState, evid: String, search_type: SearchType, find_from: [String]?, callback: @escaping (NostrEvent?) -> ()) {
|
func find_event(state: DamusState, evid: String, search_type: SearchType, find_from: [String]?, callback: @escaping (NostrEvent?) -> ()) {
|
||||||
if let ev = state.events.lookup(evid) {
|
if let ev = state.events.lookup(evid) {
|
||||||
callback(ev)
|
callback(ev)
|
||||||
@@ -809,8 +806,6 @@ func find_event(state: DamusState, evid: String, search_type: SearchType, find_f
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch ev {
|
switch ev {
|
||||||
case .ok:
|
|
||||||
break
|
|
||||||
case .event(_, let ev):
|
case .event(_, let ev):
|
||||||
has_event = true
|
has_event = true
|
||||||
callback(ev)
|
callback(ev)
|
||||||
@@ -837,12 +832,12 @@ func timeline_name(_ timeline: Timeline?) -> String {
|
|||||||
}
|
}
|
||||||
switch timeline {
|
switch timeline {
|
||||||
case .home:
|
case .home:
|
||||||
return NSLocalizedString("Home", comment: "Navigation bar title for Home view where posts and replies appear from those who the user is following.")
|
return "Home"
|
||||||
case .notifications:
|
case .notifications:
|
||||||
return NSLocalizedString("Notifications", comment: "Toolbar label for Notifications view.")
|
return "Notifications"
|
||||||
case .search:
|
case .search:
|
||||||
return NSLocalizedString("Universe 🛸", comment: "Toolbar label for the universal view where posts from all connected relay servers appear.")
|
return "Universe 🛸"
|
||||||
case .dms:
|
case .dms:
|
||||||
return NSLocalizedString("DMs", comment: "Toolbar label for DMs view, where DM is the English abbreviation for Direct Message.")
|
return "DMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,5 @@
|
|||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSCameraUsageDescription</key>
|
|
||||||
<string>Damus needs access to your camera if you want to upload photos from it</string>
|
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
|
||||||
<string>Damus needs access to your microphone if you want to upload recorded videos from it</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -11,40 +11,34 @@ import Foundation
|
|||||||
class ActionBarModel: ObservableObject {
|
class ActionBarModel: ObservableObject {
|
||||||
@Published var our_like: NostrEvent?
|
@Published var our_like: NostrEvent?
|
||||||
@Published var our_boost: NostrEvent?
|
@Published var our_boost: NostrEvent?
|
||||||
@Published var our_reply: NostrEvent?
|
|
||||||
@Published var our_zap: Zap?
|
@Published var our_zap: Zap?
|
||||||
@Published var likes: Int
|
@Published var likes: Int
|
||||||
@Published var boosts: Int
|
@Published var boosts: Int
|
||||||
@Published var zaps: Int
|
@Published var zaps: Int
|
||||||
@Published var zap_total: Int64
|
@Published var zap_total: Int64
|
||||||
@Published var replies: Int
|
|
||||||
|
|
||||||
static func empty() -> ActionBarModel {
|
static func empty() -> ActionBarModel {
|
||||||
return ActionBarModel(likes: 0, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: nil, our_boost: nil, our_zap: nil, our_reply: nil)
|
return ActionBarModel(likes: 0, boosts: 0, zaps: 0, zap_total: 0, our_like: nil, our_boost: nil, our_zap: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(likes: Int, boosts: Int, zaps: Int, zap_total: Int64, replies: Int, our_like: NostrEvent?, our_boost: NostrEvent?, our_zap: Zap?, our_reply: NostrEvent?) {
|
init(likes: Int, boosts: Int, zaps: Int, zap_total: Int64, our_like: NostrEvent?, our_boost: NostrEvent?, our_zap: Zap?) {
|
||||||
self.likes = likes
|
self.likes = likes
|
||||||
self.boosts = boosts
|
self.boosts = boosts
|
||||||
self.zaps = zaps
|
self.zaps = zaps
|
||||||
self.replies = replies
|
|
||||||
self.zap_total = zap_total
|
self.zap_total = zap_total
|
||||||
self.our_like = our_like
|
self.our_like = our_like
|
||||||
self.our_boost = our_boost
|
self.our_boost = our_boost
|
||||||
self.our_zap = our_zap
|
self.our_zap = our_zap
|
||||||
self.our_reply = our_reply
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(damus: DamusState, evid: String) {
|
func update(damus: DamusState, evid: String) {
|
||||||
self.likes = damus.likes.counts[evid] ?? 0
|
self.likes = damus.likes.counts[evid] ?? 0
|
||||||
self.boosts = damus.boosts.counts[evid] ?? 0
|
self.boosts = damus.boosts.counts[evid] ?? 0
|
||||||
self.zaps = damus.zaps.event_counts[evid] ?? 0
|
self.zaps = damus.zaps.event_counts[evid] ?? 0
|
||||||
self.replies = damus.replies.get_replies(evid)
|
|
||||||
self.zap_total = damus.zaps.event_totals[evid] ?? 0
|
self.zap_total = damus.zaps.event_totals[evid] ?? 0
|
||||||
self.our_like = damus.likes.our_events[evid]
|
self.our_like = damus.likes.our_events[evid]
|
||||||
self.our_boost = damus.boosts.our_events[evid]
|
self.our_boost = damus.boosts.our_events[evid]
|
||||||
self.our_zap = damus.zaps.our_zaps[evid]?.first
|
self.our_zap = damus.zaps.our_zaps[evid]?.first
|
||||||
self.our_reply = damus.replies.our_reply(evid)
|
|
||||||
self.objectWillChange.send()
|
self.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,10 +54,6 @@ class ActionBarModel: ObservableObject {
|
|||||||
return our_like != nil
|
return our_like != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var replied: Bool {
|
|
||||||
return our_reply != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var boosted: Bool {
|
var boosted: Bool {
|
||||||
return our_boost != nil
|
return our_boost != nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ func follow_user(pool: RelayPool, our_contacts: NostrEvent?, pubkey: String, pri
|
|||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|
||||||
func unfollow_user(postbox: PostBox, our_contacts: NostrEvent?, pubkey: String, privkey: String, unfollow: String) -> NostrEvent? {
|
func unfollow_user(pool: RelayPool, our_contacts: NostrEvent?, pubkey: String, privkey: String, unfollow: String) -> NostrEvent? {
|
||||||
guard let cs = our_contacts else {
|
guard let cs = our_contacts else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ func unfollow_user(postbox: PostBox, our_contacts: NostrEvent?, pubkey: String,
|
|||||||
ev.calculate_id()
|
ev.calculate_id()
|
||||||
ev.sign(privkey: privkey)
|
ev.sign(privkey: privkey)
|
||||||
|
|
||||||
postbox.send(ev)
|
pool.send(.event(ev))
|
||||||
|
|
||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ class CreateAccountModel: ObservableObject {
|
|||||||
@Published var about: String = ""
|
@Published var about: String = ""
|
||||||
@Published var pubkey: String = ""
|
@Published var pubkey: String = ""
|
||||||
@Published var privkey: String = ""
|
@Published var privkey: String = ""
|
||||||
@Published var profile_image: String? = nil
|
|
||||||
|
|
||||||
var pubkey_bech32: String {
|
var pubkey_bech32: String {
|
||||||
return bech32_pubkey(self.pubkey) ?? ""
|
return bech32_pubkey(self.pubkey) ?? ""
|
||||||
|
|||||||
@@ -26,10 +26,7 @@ struct DamusState {
|
|||||||
let drafts: Drafts
|
let drafts: Drafts
|
||||||
let events: EventCache
|
let events: EventCache
|
||||||
let bookmarks: BookmarksManager
|
let bookmarks: BookmarksManager
|
||||||
let postbox: PostBox
|
|
||||||
let bootstrap_relays: [String]
|
|
||||||
let replies: ReplyCounter
|
|
||||||
let translations: Translations
|
|
||||||
var pubkey: String {
|
var pubkey: String {
|
||||||
return keypair.pubkey
|
return keypair.pubkey
|
||||||
}
|
}
|
||||||
@@ -39,7 +36,6 @@ struct DamusState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var empty: DamusState {
|
static var empty: DamusState {
|
||||||
let settings = UserSettingsStore()
|
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""))
|
||||||
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), translations: Translations(settings))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// DraftModel.swift
|
// DraftsModel.swift
|
||||||
// damus
|
// damus
|
||||||
//
|
//
|
||||||
// Created by Terry Yiu on 2/12/23.
|
// Created by Terry Yiu on 2/12/23.
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ class EventsModel: ObservableObject {
|
|||||||
handle_event(relay_id: relay_id, ev: ev)
|
handle_event(relay_id: relay_id, ev: ev)
|
||||||
case .notice(_):
|
case .notice(_):
|
||||||
break
|
break
|
||||||
case .ok:
|
|
||||||
break
|
|
||||||
case .eose(_):
|
case .eose(_):
|
||||||
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state)
|
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,9 +94,6 @@ class FollowersModel: ObservableObject {
|
|||||||
} else if sub_id == self.profiles_id {
|
} else if sub_id == self.profiles_id {
|
||||||
damus_state.pool.unsubscribe(sub_id: profiles_id, to: [relay_id])
|
damus_state.pool.unsubscribe(sub_id: profiles_id, to: [relay_id])
|
||||||
}
|
}
|
||||||
|
|
||||||
case .ok:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,8 +58,6 @@ class FollowingModel {
|
|||||||
break
|
break
|
||||||
case .nostr_event(let nev):
|
case .nostr_event(let nev):
|
||||||
switch nev {
|
switch nev {
|
||||||
case .ok:
|
|
||||||
break
|
|
||||||
case .event(_, let ev):
|
case .event(_, let ev):
|
||||||
if ev.kind == 0 {
|
if ev.kind == 0 {
|
||||||
process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||||
|
|||||||
+11
-151
@@ -130,7 +130,7 @@ class HomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_zap_event_with_zapper(profiles: Profiles, ev: NostrEvent, our_keypair: Keypair, zapper: String) {
|
func handle_zap_event_with_zapper(_ ev: NostrEvent, our_keypair: Keypair, zapper: String) {
|
||||||
guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else {
|
guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -145,15 +145,9 @@ class HomeModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if handle_last_event(ev: ev, timeline: .notifications) {
|
if handle_last_event(ev: ev, timeline: .notifications) && damus_state.settings.zap_vibration {
|
||||||
if damus_state.settings.zap_vibration {
|
// Generate zap vibration
|
||||||
// Generate zap vibration
|
zap_vibrate(zap_amount: zap.invoice.amount)
|
||||||
zap_vibrate(zap_amount: zap.invoice.amount)
|
|
||||||
}
|
|
||||||
if damus_state.settings.zap_notification {
|
|
||||||
// Create in-app local notification for zap received.
|
|
||||||
create_in_app_zap_notification(profiles: profiles, zap: zap)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -167,7 +161,7 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
let our_keypair = damus_state.keypair
|
let our_keypair = damus_state.keypair
|
||||||
if let local_zapper = damus_state.profiles.lookup_zapper(pubkey: ptag) {
|
if let local_zapper = damus_state.profiles.lookup_zapper(pubkey: ptag) {
|
||||||
handle_zap_event_with_zapper(profiles: self.damus_state.profiles, ev: ev, our_keypair: our_keypair, zapper: local_zapper)
|
handle_zap_event_with_zapper(ev, our_keypair: our_keypair, zapper: local_zapper)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +180,7 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.damus_state.profiles.zappers[ptag] = zapper
|
self.damus_state.profiles.zappers[ptag] = zapper
|
||||||
self.handle_zap_event_with_zapper(profiles: self.damus_state.profiles, ev: ev, our_keypair: our_keypair, zapper: zapper)
|
self.handle_zap_event_with_zapper(ev, our_keypair: our_keypair, zapper: zapper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,11 +329,7 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
self.loading = false
|
self.loading = false
|
||||||
break
|
break
|
||||||
|
|
||||||
case .ok:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +455,7 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
return m[kind]
|
return m[kind]
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_notification(ev: NostrEvent) {
|
func handle_notification(ev: NostrEvent) {
|
||||||
// don't show notifications from ourselves
|
// don't show notifications from ourselves
|
||||||
guard ev.pubkey != damus_state.pubkey else {
|
guard ev.pubkey != damus_state.pubkey else {
|
||||||
@@ -489,10 +479,7 @@ class HomeModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if handle_last_event(ev: ev, timeline: .notifications) {
|
handle_last_event(ev: ev, timeline: .notifications)
|
||||||
process_local_notification(damus_state: damus_state, event: ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
@@ -511,13 +498,11 @@ class HomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func handle_text_event(sub_id: String, _ ev: NostrEvent) {
|
func handle_text_event(sub_id: String, _ ev: NostrEvent) {
|
||||||
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
|
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
damus_state.replies.count_replies(ev)
|
|
||||||
damus_state.events.insert(ev)
|
damus_state.events.insert(ev)
|
||||||
|
|
||||||
if sub_id == home_subid {
|
if sub_id == home_subid {
|
||||||
@@ -543,13 +528,9 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
incoming_dms.append(ev)
|
incoming_dms.append(ev)
|
||||||
|
|
||||||
dm_debouncer.debounce { [self] in
|
dm_debouncer.debounce {
|
||||||
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
||||||
self.new_events = notifs
|
self.new_events = notifs
|
||||||
if damus_state.settings.dm_notification,
|
|
||||||
let displayName = damus_state.profiles.lookup(id: self.incoming_dms.last!.pubkey)?.display_name {
|
|
||||||
create_local_notification(displayName: displayName, conversation: "You have received a direct message", type: .dm)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.incoming_dms = []
|
self.incoming_dms = []
|
||||||
}
|
}
|
||||||
@@ -678,7 +659,7 @@ func process_metadata_event(our_pubkey: String, profiles: Profiles, ev: NostrEve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at, event: ev)
|
let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at)
|
||||||
profiles.add(id: ev.pubkey, profile: tprof)
|
profiles.add(id: ev.pubkey, profile: tprof)
|
||||||
|
|
||||||
if let nip05 = profile.nip05, old_nip05 != profile.nip05 {
|
if let nip05 = profile.nip05, old_nip05 != profile.nip05 {
|
||||||
@@ -690,7 +671,6 @@ func process_metadata_event(our_pubkey: String, profiles: Profiles, ev: NostrEve
|
|||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
profiles.validated[ev.pubkey] = validated
|
profiles.validated[ev.pubkey] = validated
|
||||||
profiles.nip05_pubkey[nip05] = ev.pubkey
|
|
||||||
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -744,7 +724,7 @@ func process_contact_event(state: DamusState, ev: NostrEvent) {
|
|||||||
|
|
||||||
func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
||||||
let bootstrap_dict: [String: RelayInfo] = [:]
|
let bootstrap_dict: [String: RelayInfo] = [:]
|
||||||
let old_decoded = m_old_ev.flatMap { decode_json_relays($0.content) } ?? state.bootstrap_relays.reduce(into: bootstrap_dict) { (d, r) in
|
let old_decoded = m_old_ev.flatMap { decode_json_relays($0.content) } ?? BOOTSTRAP_RELAYS.reduce(into: bootstrap_dict) { (d, r) in
|
||||||
d[r] = .rw
|
d[r] = .rw
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,7 +759,6 @@ func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
save_bootstrap_relays(pubkey: state.pubkey, relays: Array(new))
|
|
||||||
notify(.relays_changed, ())
|
notify(.relays_changed, ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -949,122 +928,3 @@ func zap_vibrate(zap_amount: Int64) {
|
|||||||
vibration_generator.impactOccurred()
|
vibration_generator.impactOccurred()
|
||||||
}
|
}
|
||||||
|
|
||||||
func zap_notification_title(_ zap: Zap) -> String {
|
|
||||||
if zap.private_request != nil {
|
|
||||||
return NSLocalizedString("Private Zap", comment: "Title of notification when a private zap is received.")
|
|
||||||
} else {
|
|
||||||
return NSLocalizedString("Zap", comment: "Title of notification when a non-private zap is received.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String {
|
|
||||||
let src = zap.private_request ?? zap.request.ev
|
|
||||||
let anon = event_is_anonymous(ev: src)
|
|
||||||
let pk = anon ? "anon" : src.pubkey
|
|
||||||
let profile = profiles.lookup(id: pk)
|
|
||||||
let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0))
|
|
||||||
let formattedSats = format_msats_abbrev(zap.invoice.amount)
|
|
||||||
let name = Profile.displayName(profile: profile, pubkey: pk).display_name
|
|
||||||
|
|
||||||
if src.content.isEmpty {
|
|
||||||
let format = localizedStringFormat(key: "zap_notification_no_message", locale: locale)
|
|
||||||
return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats, name)
|
|
||||||
} else {
|
|
||||||
let format = localizedStringFormat(key: "zap_notification_with_message", locale: locale)
|
|
||||||
return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats, name, src.content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func create_in_app_zap_notification(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) {
|
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
|
|
||||||
content.title = zap_notification_title(zap)
|
|
||||||
content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale)
|
|
||||||
content.sound = UNNotificationSound.default
|
|
||||||
|
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: "myZapNotification", content: content, trigger: trigger)
|
|
||||||
|
|
||||||
UNUserNotificationCenter.current().add(request) { error in
|
|
||||||
if let error = error {
|
|
||||||
print("Error: \(error)")
|
|
||||||
} else {
|
|
||||||
print("Local notification scheduled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func process_local_notification(damus_state: DamusState, event ev: NostrEvent) {
|
|
||||||
guard let type = ev.known_kind else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if damus_state.settings.notification_only_from_following,
|
|
||||||
damus_state.contacts.follow_state(ev.pubkey) != .follows
|
|
||||||
{
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if type == .text && damus_state.settings.mention_notification {
|
|
||||||
for block in ev.blocks(damus_state.keypair.privkey) {
|
|
||||||
if case .mention(let mention) = block, mention.ref.ref_id == damus_state.keypair.pubkey,
|
|
||||||
let displayName = damus_state.profiles.lookup(id: ev.pubkey)?.display_name {
|
|
||||||
let justContent = NSAttributedString(render_note_content(ev: ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content).string
|
|
||||||
create_local_notification(displayName: displayName, conversation: justContent, type: type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if type == .boost && damus_state.settings.repost_notification,
|
|
||||||
let displayName = damus_state.profiles.lookup(id: ev.pubkey)?.display_name {
|
|
||||||
|
|
||||||
if let inner_ev = ev.inner_event {
|
|
||||||
create_local_notification(displayName: displayName, conversation: inner_ev.content, type: type)
|
|
||||||
}
|
|
||||||
} else if type == .like && damus_state.settings.like_notification,
|
|
||||||
let displayName = damus_state.profiles.lookup(id: ev.pubkey)?.display_name,
|
|
||||||
let e_ref = ev.referenced_ids.first?.ref_id,
|
|
||||||
let content = damus_state.events.lookup(e_ref)?.content {
|
|
||||||
|
|
||||||
create_local_notification(displayName: displayName, conversation: content, type: type)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func create_local_notification(displayName: String, conversation: String, type: NostrKind) {
|
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
var title = ""
|
|
||||||
var identifier = ""
|
|
||||||
switch type {
|
|
||||||
case .text:
|
|
||||||
title = String(format: NSLocalizedString("Mentioned by %@", comment: "Mentioned by heading in local notification"), displayName)
|
|
||||||
identifier = "myMentionNotification"
|
|
||||||
case .boost:
|
|
||||||
title = String(format: NSLocalizedString("Reposted by %@", comment: "Reposted by heading in local notification"), displayName)
|
|
||||||
identifier = "myBoostNotification"
|
|
||||||
case .like:
|
|
||||||
title = String(format: NSLocalizedString("Liked by %@", comment: "Liked by heading in local notification"), displayName)
|
|
||||||
identifier = "myLikeNotification"
|
|
||||||
case .dm:
|
|
||||||
title = String(format: NSLocalizedString("DM by %@", comment: "DM by heading in local notification"), displayName)
|
|
||||||
identifier = "myDMNotification"
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
content.title = title
|
|
||||||
content.body = conversation
|
|
||||||
content.sound = UNNotificationSound.default
|
|
||||||
|
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
|
|
||||||
|
|
||||||
UNUserNotificationCenter.current().add(request) { error in
|
|
||||||
if let error = error {
|
|
||||||
print("Error: \(error)")
|
|
||||||
} else {
|
|
||||||
print("Local notification scheduled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,37 +9,11 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
|
||||||
enum MediaUpload {
|
|
||||||
case image(URL)
|
|
||||||
case video(URL)
|
|
||||||
|
|
||||||
var genericFileName: String {
|
|
||||||
"damus_generic_filename.\(file_extension)"
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_extension: String {
|
|
||||||
switch self {
|
|
||||||
case .image(let url):
|
|
||||||
return url.pathExtension
|
|
||||||
case .video(let url):
|
|
||||||
return url.pathExtension
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_image: Bool {
|
|
||||||
if case .image = self {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
|
class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
|
||||||
@Published var progress: Double? = nil
|
@Published var progress: Double? = nil
|
||||||
|
|
||||||
func start(media: MediaUpload, uploader: MediaUploader) async -> ImageUploadResult {
|
func start(img: UIImage, uploader: ImageUploader) async -> ImageUploadResult {
|
||||||
let res = await create_upload_request(mediaToUpload: media, mediaUploader: uploader, progress: self)
|
let res = await create_image_upload_request(imageToUpload: img, imageUploader: uploader, progress: self)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.progress = nil
|
self.progress = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,8 +133,6 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch resp {
|
switch resp {
|
||||||
case .ok:
|
|
||||||
break
|
|
||||||
case .event(_, let ev):
|
case .event(_, let ev):
|
||||||
add_event(ev)
|
add_event(ev)
|
||||||
case .notice(let notice):
|
case .notice(let notice):
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ class SearchHomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
case .notice(let msg):
|
case .notice(let msg):
|
||||||
print("search home notice: \(msg)")
|
print("search home notice: \(msg)")
|
||||||
case .ok:
|
|
||||||
break
|
|
||||||
case .eose(let sub_id):
|
case .eose(let sub_id):
|
||||||
loading = false
|
loading = false
|
||||||
|
|
||||||
|
|||||||
@@ -131,9 +131,6 @@ func handle_subid_event(pool: RelayPool, relay_id: String, ev: NostrConnectionEv
|
|||||||
case .event(let ev_subid, let ev):
|
case .event(let ev_subid, let ev):
|
||||||
handle(ev_subid, ev)
|
handle(ev_subid, ev)
|
||||||
return (ev_subid, false)
|
return (ev_subid, false)
|
||||||
|
|
||||||
case .ok:
|
|
||||||
return (nil, false)
|
|
||||||
|
|
||||||
case .notice(let note):
|
case .notice(let note):
|
||||||
if note.contains("Too many subscription filters") {
|
if note.contains("Too many subscription filters") {
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ class ThreadModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let the_ev = damus_state.events.upsert(ev)
|
let the_ev = damus_state.events.upsert(ev)
|
||||||
damus_state.replies.count_replies(the_ev)
|
|
||||||
damus_state.events.add_replies(ev: the_ev)
|
damus_state.events.add_replies(ev: the_ev)
|
||||||
|
|
||||||
event_map.insert(ev)
|
event_map.insert(ev)
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
//
|
|
||||||
// Translations.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Terry Yiu on 3/29/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import NaturalLanguage
|
|
||||||
|
|
||||||
class Translations: ObservableObject {
|
|
||||||
private static let languageDetectionMinConfidence = 0.5
|
|
||||||
|
|
||||||
@Published var translations: [NostrEvent: String] = [:]
|
|
||||||
@Published var languages: [NostrEvent: String] = [:]
|
|
||||||
|
|
||||||
let settings: UserSettingsStore
|
|
||||||
|
|
||||||
let translator: Translator
|
|
||||||
|
|
||||||
let targetLanguage = currentLanguage()
|
|
||||||
let preferredLanguages = Set(Locale.preferredLanguages.map { localeToLanguage($0) })
|
|
||||||
|
|
||||||
init(_ settings: UserSettingsStore) {
|
|
||||||
self.settings = settings
|
|
||||||
self.translator = Translator(settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Attempts to detect the language of the content of a given nostr event using Apple's offline NaturalLanguage API.
|
|
||||||
The detected language will be returned only if it has a 50% or more confidence.
|
|
||||||
This is a best effort guess and could be incorrect.
|
|
||||||
*/
|
|
||||||
func detectLanguage(_ event: NostrEvent, state: DamusState) -> String? {
|
|
||||||
if let cachedLanguage = languages[event] {
|
|
||||||
return cachedLanguage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in
|
|
||||||
// and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer.
|
|
||||||
let originalBlocks = event.blocks(state.keypair.privkey)
|
|
||||||
let originalOnlyText = originalBlocks.compactMap { $0.is_text }.joined(separator: " ")
|
|
||||||
|
|
||||||
// Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate.
|
|
||||||
let languageRecognizer = NLLanguageRecognizer()
|
|
||||||
languageRecognizer.processString(originalOnlyText)
|
|
||||||
|
|
||||||
guard let locale = languageRecognizer.languageHypotheses(withMaximum: 1).first(where: { $0.value >= Translations.languageDetectionMinConfidence })?.key.rawValue else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the variant component and just take the language part as translation services typically only supports the variant-less language.
|
|
||||||
// Moreover, speakers of one variant can generally understand other variants.
|
|
||||||
let language = localeToLanguage(locale)
|
|
||||||
languages[event] = language
|
|
||||||
return language
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Returns true if the given translation is effectively the same as the original note, ignoring whitespaces and new lines.
|
|
||||||
*/
|
|
||||||
private func translationSameAsOriginal(_ translation: String, event: NostrEvent, state: DamusState) -> Bool {
|
|
||||||
return translation.trimmingCharacters(in: .whitespacesAndNewlines) == event.get_content(state.keypair.privkey).trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasCachedTranslation(_ event: NostrEvent) -> Bool {
|
|
||||||
return languages[event] != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cachedTranslation(_ event: NostrEvent) -> TranslationWithLanguage? {
|
|
||||||
if let cachedLanguage = languages[event] {
|
|
||||||
if let cachedTranslation = translations[event] {
|
|
||||||
return TranslationWithLanguage(translation: cachedTranslation, language: cachedLanguage)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func translate(_ event: NostrEvent, state: DamusState) async -> TranslationWithLanguage? {
|
|
||||||
guard shouldTranslate(event, state: state) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let noteLanguage = detectLanguage(event, state: state) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if languages[event] != nil {
|
|
||||||
return cachedTranslation(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
guard let translationWithLanguage = try await translator.translate(event.get_content(state.keypair.privkey), from: noteLanguage, to: targetLanguage) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the translated content is identical to the original content, don't return the translation.
|
|
||||||
if translationSameAsOriginal(translationWithLanguage.translation, event: event, state: state) {
|
|
||||||
// Nil out the translation as it's the same as the original.
|
|
||||||
translations[event] = nil
|
|
||||||
// Leave an entry so that we don't attempt to translate it again in the future.
|
|
||||||
languages[event] = targetLanguage
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
translations[event] = translationWithLanguage.translation
|
|
||||||
languages[event] = translationWithLanguage.language
|
|
||||||
return translationWithLanguage
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldTranslate(_ event: NostrEvent, state: DamusState) -> Bool {
|
|
||||||
// Do not translate self-authored content because if the language recognizer guesses the wrong language for your own note,
|
|
||||||
// it's annoying and unexpected for the translation to show up.
|
|
||||||
if event.pubkey == state.pubkey && state.is_privkey_user {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid translating if no translation service is configured.
|
|
||||||
switch settings.translation_service {
|
|
||||||
case .none:
|
|
||||||
return false
|
|
||||||
case .libretranslate:
|
|
||||||
if URLComponents(string: settings.libretranslate_url) == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case .deepl:
|
|
||||||
if settings.deepl_api_key == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If translation was attempted before, use the results of the cached translation to determine if it should be shown.
|
|
||||||
if languages[event] != nil {
|
|
||||||
return translations[event] != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid translating notes if language cannot be detected or if it is in one of the user's preferred languages.
|
|
||||||
guard let noteLanguage = detectLanguage(event, state: state), !preferredLanguages.contains(noteLanguage) else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -50,10 +50,10 @@ func get_default_wallet(_ pubkey: String) -> Wallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_media_uploader(_ pubkey: String) -> MediaUploader {
|
func get_image_uploader(_ pubkey: String) -> ImageUploader {
|
||||||
if let defaultMediaUploader = UserDefaults.standard.string(forKey: "default_media_uploader"),
|
if let defaultImageUploader = UserDefaults.standard.string(forKey: "default_image_uploader"),
|
||||||
let defaultMediaUploader = MediaUploader(rawValue: defaultMediaUploader) {
|
let defaultImageUploader = ImageUploader(rawValue: defaultImageUploader) {
|
||||||
return defaultMediaUploader
|
return defaultImageUploader
|
||||||
} else {
|
} else {
|
||||||
return .nostrBuild
|
return .nostrBuild
|
||||||
}
|
}
|
||||||
@@ -98,9 +98,9 @@ class UserSettingsStore: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var default_media_uploader: MediaUploader {
|
@Published var default_image_uploader: ImageUploader {
|
||||||
didSet {
|
didSet {
|
||||||
UserDefaults.standard.set(default_media_uploader.rawValue, forKey: "default_media_uploader")
|
UserDefaults.standard.set(default_image_uploader.rawValue, forKey: "default_image_uploader")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,66 +128,6 @@ class UserSettingsStore: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var zap_notification: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(zap_notification, forKey: "zap_notification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var mention_notification: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(mention_notification, forKey: "mention_notification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var repost_notification: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(repost_notification, forKey: "repost_notification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var dm_notification: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(dm_notification, forKey: "dm_notification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var like_notification: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(like_notification, forKey: "like_notification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var notification_only_from_following: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(notification_only_from_following, forKey: "notification_only_from_following")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var truncate_timeline_text: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(truncate_timeline_text, forKey: "truncate_timeline_text")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var truncate_mention_text: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(truncate_mention_text, forKey: "truncate_mention_text")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var auto_translate: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(auto_translate, forKey: "auto_translate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var show_only_preferred_languages: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(show_only_preferred_languages, forKey: "show_only_preferred_languages")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var translation_service: TranslationService {
|
@Published var translation_service: TranslationService {
|
||||||
didSet {
|
didSet {
|
||||||
UserDefaults.standard.set(translation_service.rawValue, forKey: "translation_service")
|
UserDefaults.standard.set(translation_service.rawValue, forKey: "translation_service")
|
||||||
@@ -265,21 +205,11 @@ class UserSettingsStore: ObservableObject {
|
|||||||
show_wallet_selector = should_show_wallet_selector(pubkey)
|
show_wallet_selector = should_show_wallet_selector(pubkey)
|
||||||
always_show_images = UserDefaults.standard.object(forKey: "always_show_images") as? Bool ?? false
|
always_show_images = UserDefaults.standard.object(forKey: "always_show_images") as? Bool ?? false
|
||||||
|
|
||||||
default_media_uploader = get_media_uploader(pubkey)
|
default_image_uploader = get_image_uploader(pubkey)
|
||||||
|
|
||||||
left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false
|
left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false
|
||||||
zap_vibration = UserDefaults.standard.object(forKey: "zap_vibration") as? Bool ?? false
|
zap_vibration = UserDefaults.standard.object(forKey: "zap_vibration") as? Bool ?? false
|
||||||
zap_notification = UserDefaults.standard.object(forKey: "zap_notification") as? Bool ?? true
|
|
||||||
mention_notification = UserDefaults.standard.object(forKey: "mention_notification") as? Bool ?? true
|
|
||||||
repost_notification = UserDefaults.standard.object(forKey: "repost_notification") as? Bool ?? true
|
|
||||||
like_notification = UserDefaults.standard.object(forKey: "like_notification") as? Bool ?? true
|
|
||||||
dm_notification = UserDefaults.standard.object(forKey: "dm_notification") as? Bool ?? true
|
|
||||||
notification_only_from_following = UserDefaults.standard.object(forKey: "notification_only_from_following") as? Bool ?? false
|
|
||||||
truncate_timeline_text = UserDefaults.standard.object(forKey: "truncate_timeline_text") as? Bool ?? false
|
|
||||||
truncate_mention_text = UserDefaults.standard.object(forKey: "truncate_mention_text") as? Bool ?? false
|
|
||||||
disable_animation = should_disable_image_animation()
|
disable_animation = should_disable_image_animation()
|
||||||
auto_translate = UserDefaults.standard.object(forKey: "auto_translate") as? Bool ?? true
|
|
||||||
show_only_preferred_languages = UserDefaults.standard.object(forKey: "show_only_preferred_languages") as? Bool ?? false
|
|
||||||
|
|
||||||
// Note from @tyiu:
|
// Note from @tyiu:
|
||||||
// Default translation service is disabled by default for now until we gain some confidence that it is working well in production.
|
// Default translation service is disabled by default for now until we gain some confidence that it is working well in production.
|
||||||
@@ -336,6 +266,17 @@ class UserSettingsStore: ObservableObject {
|
|||||||
private func clearDeepLApiKey() throws {
|
private func clearDeepLApiKey() throws {
|
||||||
try Vault.deletePrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
try Vault.deletePrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func can_translate(_ pubkey: String) -> Bool {
|
||||||
|
switch translation_service {
|
||||||
|
case .none:
|
||||||
|
return false
|
||||||
|
case .libretranslate:
|
||||||
|
return URLComponents(string: libretranslate_url) != nil
|
||||||
|
case .deepl:
|
||||||
|
return deepl_api_key != ""
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DamusLibreTranslateKeychainConfiguration: KeychainConfiguration {
|
struct DamusLibreTranslateKeychainConfiguration: KeychainConfiguration {
|
||||||
|
|||||||
+13
-13
@@ -42,42 +42,42 @@ enum Wallet: String, CaseIterable, Identifiable {
|
|||||||
return .init(index: -1, tag: "systemdefaultwallet", displayName: NSLocalizedString("Local default", comment: "Dropdown option label for system default for Lightning wallet."),
|
return .init(index: -1, tag: "systemdefaultwallet", displayName: NSLocalizedString("Local default", comment: "Dropdown option label for system default for Lightning wallet."),
|
||||||
link: "lightning:", appStoreLink: "lightning:", image: "")
|
link: "lightning:", appStoreLink: "lightning:", image: "")
|
||||||
case .strike:
|
case .strike:
|
||||||
return .init(index: 0, tag: "strike", displayName: "Strike", link: "strike:",
|
return .init(index: 0, tag: "strike", displayName: NSLocalizedString("Strike", comment: "Dropdown option label for Lightning wallet, Strike."), link: "strike:",
|
||||||
appStoreLink: "https://apps.apple.com/us/app/strike-bitcoin-payments/id1488724463", image: "strike")
|
appStoreLink: "https://apps.apple.com/us/app/strike-bitcoin-payments/id1488724463", image: "strike")
|
||||||
case .cashapp:
|
case .cashapp:
|
||||||
return .init(index: 1, tag: "cashapp", displayName: "Cash App", link: "https://cash.app/launch/lightning/",
|
return .init(index: 1, tag: "cashapp", displayName: NSLocalizedString("Cash App", comment: "Dropdown option label for Lightning wallet, Cash App."), link: "https://cash.app/launch/lightning/",
|
||||||
appStoreLink: "https://apps.apple.com/us/app/cash-app/id711923939", image: "cashapp")
|
appStoreLink: "https://apps.apple.com/us/app/cash-app/id711923939", image: "cashapp")
|
||||||
case .muun:
|
case .muun:
|
||||||
return .init(index: 2, tag: "muun", displayName: "Muun", link: "muun:", appStoreLink: "https://apps.apple.com/us/app/muun-wallet/id1482037683", image: "muun")
|
return .init(index: 2, tag: "muun", displayName: NSLocalizedString("Muun", comment: "Dropdown option label for Lightning wallet, Muun."), link: "muun:", appStoreLink: "https://apps.apple.com/us/app/muun-wallet/id1482037683", image: "muun")
|
||||||
case .bluewallet:
|
case .bluewallet:
|
||||||
return .init(index: 3, tag: "bluewallet", displayName: "Blue Wallet", link: "bluewallet:lightning:",
|
return .init(index: 3, tag: "bluewallet", displayName: NSLocalizedString("Blue Wallet", comment: "Dropdown option label for Lightning wallet, Blue Wallet."), link: "bluewallet:lightning:",
|
||||||
appStoreLink: "https://apps.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040", image: "bluewallet")
|
appStoreLink: "https://apps.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040", image: "bluewallet")
|
||||||
case .walletofsatoshi:
|
case .walletofsatoshi:
|
||||||
return .init(index: 4, tag: "walletofsatoshi", displayName: "Wallet of Satoshi", link: "walletofsatoshi:lightning:",
|
return .init(index: 4, tag: "walletofsatoshi", displayName: NSLocalizedString("Wallet of Satoshi", comment: "Dropdown option label for Lightning wallet, Wallet of Satoshi."), link: "walletofsatoshi:lightning:",
|
||||||
appStoreLink: "https://apps.apple.com/us/app/wallet-of-satoshi/id1438599608", image: "walletofsatoshi")
|
appStoreLink: "https://apps.apple.com/us/app/wallet-of-satoshi/id1438599608", image: "walletofsatoshi")
|
||||||
case .zebedee:
|
case .zebedee:
|
||||||
return .init(index: 5, tag: "zebedee", displayName: "Zebedee", link: "zebedee:lightning:",
|
return .init(index: 5, tag: "zebedee", displayName: NSLocalizedString("Zebedee", comment: "Dropdown option label for Lightning wallet, Zebedee."), link: "zebedee:lightning:",
|
||||||
appStoreLink: "https://apps.apple.com/us/app/zebedee-wallet/id1484394401", image: "zebedee")
|
appStoreLink: "https://apps.apple.com/us/app/zebedee-wallet/id1484394401", image: "zebedee")
|
||||||
case .zeusln:
|
case .zeusln:
|
||||||
return .init(index: 6, tag: "zeusln", displayName: "Zeus LN", link: "zeusln:lightning:",
|
return .init(index: 6, tag: "zeusln", displayName: NSLocalizedString("Zeus LN", comment: "Dropdown option label for Lightning wallet, Zeus LN."), link: "zeusln:lightning:",
|
||||||
appStoreLink: "https://apps.apple.com/us/app/zeus-ln/id1456038895", image: "zeusln")
|
appStoreLink: "https://apps.apple.com/us/app/zeus-ln/id1456038895", image: "zeusln")
|
||||||
case .lnlink:
|
case .lnlink:
|
||||||
return .init(index: 7, tag: "lnlink", displayName: "LNLink", link: "lnlink:lightning:",
|
return .init(index: 7, tag: "lnlink", displayName: NSLocalizedString("LNLink", comment: "Dropdown option label for Lightning wallet, LNLink."), link: "lnlink:lightning:",
|
||||||
appStoreLink: "https://testflight.apple.com/join/aNY4yuuZ", image: "lnlink")
|
appStoreLink: "https://testflight.apple.com/join/aNY4yuuZ", image: "lnlink")
|
||||||
case .phoenix:
|
case .phoenix:
|
||||||
return .init(index: 8, tag: "phoenix", displayName: "Phoenix", link: "phoenix://",
|
return .init(index: 8, tag: "phoenix", displayName: NSLocalizedString("Phoenix", comment: "Dropdown option label for Lightning wallet, Phoenix."), link: "phoenix://",
|
||||||
appStoreLink: "https://apps.apple.com/us/app/phoenix-wallet/id1544097028", image: "phoenix")
|
appStoreLink: "https://apps.apple.com/us/app/phoenix-wallet/id1544097028", image: "phoenix")
|
||||||
case .breez:
|
case .breez:
|
||||||
return .init(index: 9, tag: "breez", displayName: "Breez", link: "breez:",
|
return .init(index: 9, tag: "breez", displayName: NSLocalizedString("Breez", comment: "Dropdown option label for Lightning wallet, Breez."), link: "breez:",
|
||||||
appStoreLink: "https://apps.apple.com/us/app/breez-lightning-client-pos/id1463604142", image: "breez")
|
appStoreLink: "https://apps.apple.com/us/app/breez-lightning-client-pos/id1463604142", image: "breez")
|
||||||
case .bitcoinbeach:
|
case .bitcoinbeach:
|
||||||
return .init(index: 10, tag: "bitcoinbeach", displayName: "Bitcoin Beach", link: "bitcoinbeach://",
|
return .init(index: 10, tag: "bitcoinbeach", displayName: NSLocalizedString("Bitcoin Beach", comment: "Dropdown option label for Lightning wallet, Bitcoin Beach."), link: "bitcoinbeach://",
|
||||||
appStoreLink: "https://apps.apple.com/sv/app/bitcoin-beach-wallet/id1531383905", image: "bbw")
|
appStoreLink: "https://apps.apple.com/sv/app/bitcoin-beach-wallet/id1531383905", image: "bbw")
|
||||||
case .blixtwallet:
|
case .blixtwallet:
|
||||||
return .init(index: 11, tag: "blixtwallet", displayName: "Blixt Wallet", link: "blixtwallet:lightning:",
|
return .init(index: 11, tag: "blixtwallet", displayName: NSLocalizedString("Blixt Wallet", comment: "Dropdown option label for Lightning wallet, Blixt Wallet"), link: "blixtwallet:lightning:",
|
||||||
appStoreLink: "https://testflight.apple.com/join/EXvGhRzS", image: "blixt-wallet")
|
appStoreLink: "https://testflight.apple.com/join/EXvGhRzS", image: "blixt-wallet")
|
||||||
case .river:
|
case .river:
|
||||||
return .init(index: 12, tag: "river", displayName: "River", link: "river://",
|
return .init(index: 12, tag: "river", displayName: NSLocalizedString("River", comment: "Dropdown option label for Lightning wallet, River"), link: "river://",
|
||||||
appStoreLink: "https://apps.apple.com/us/app/river-buy-mine-bitcoin/id1536176542", image: "river")
|
appStoreLink: "https://apps.apple.com/us/app/river-buy-mine-bitcoin/id1536176542", image: "river")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ class ZapsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch resp {
|
switch resp {
|
||||||
case .ok:
|
|
||||||
break
|
|
||||||
case .notice:
|
case .notice:
|
||||||
break
|
break
|
||||||
case .eose:
|
case .eose:
|
||||||
|
|||||||
+1
-10
@@ -98,16 +98,7 @@ struct Profile: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var website_url: URL? {
|
var website_url: URL? {
|
||||||
if self.website?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
|
return self.website.flatMap { URL(string: $0) }
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return self.website.flatMap { url in
|
|
||||||
let trim = url.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
if !(trim.hasPrefix("http://") || trim.hasPrefix("https://")) {
|
|
||||||
return URL(string: "https://" + trim)
|
|
||||||
}
|
|
||||||
return URL(string: trim)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var lnurl: String? {
|
var lnurl: String? {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import CommonCrypto
|
|||||||
import secp256k1
|
import secp256k1
|
||||||
import secp256k1_implementation
|
import secp256k1_implementation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import NaturalLanguage
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -519,21 +518,16 @@ func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
|
|||||||
guard let privkey = keypair.privkey else {
|
guard let privkey = keypair.privkey else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let bootstrap_relays = load_bootstrap_relays(pubkey: keypair.pubkey)
|
|
||||||
let rw_relay_info = RelayInfo(read: true, write: true)
|
let rw_relay_info = RelayInfo(read: true, write: true)
|
||||||
var relays: [String: RelayInfo] = [:]
|
var relays: [String: RelayInfo] = [:]
|
||||||
|
for relay in BOOTSTRAP_RELAYS {
|
||||||
for relay in bootstrap_relays {
|
|
||||||
relays[relay] = rw_relay_info
|
relays[relay] = rw_relay_info
|
||||||
}
|
}
|
||||||
|
|
||||||
let relay_json = encode_json(relays)!
|
let relay_json = encode_json(relays)!
|
||||||
let damus_pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
|
let damus_pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
|
||||||
let jb55_pubkey = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" // lol
|
|
||||||
let tags = [
|
let tags = [
|
||||||
["p", damus_pubkey],
|
["p", damus_pubkey],
|
||||||
["p", jb55_pubkey],
|
|
||||||
["p", keypair.pubkey] // you're a friend of yourself!
|
["p", keypair.pubkey] // you're a friend of yourself!
|
||||||
]
|
]
|
||||||
let ev = NostrEvent(content: relay_json,
|
let ev = NostrEvent(content: relay_json,
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ struct NostrMetadata: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func create_account_to_metadata(_ model: CreateAccountModel) -> NostrMetadata {
|
func create_account_to_metadata(_ model: CreateAccountModel) -> NostrMetadata {
|
||||||
return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: model.profile_image, banner: nil, lud06: nil, lud16: nil)
|
return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: nil, banner: nil, lud06: nil, lud16: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,13 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct CommandResult {
|
|
||||||
let event_id: String
|
|
||||||
let ok: Bool
|
|
||||||
let msg: String
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NostrResponse: Decodable {
|
enum NostrResponse: Decodable {
|
||||||
case event(String, NostrEvent)
|
case event(String, NostrEvent)
|
||||||
case notice(String)
|
case notice(String)
|
||||||
case eose(String)
|
case eose(String)
|
||||||
case ok(CommandResult)
|
|
||||||
|
|
||||||
var subid: String? {
|
var subid: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .ok(_):
|
|
||||||
return nil
|
|
||||||
case .event(let sub_id, _):
|
case .event(let sub_id, _):
|
||||||
return sub_id
|
return sub_id
|
||||||
case .eose(let sub_id):
|
case .eose(let sub_id):
|
||||||
@@ -57,23 +48,9 @@ enum NostrResponse: Decodable {
|
|||||||
let sub_id = try container.decode(String.self)
|
let sub_id = try container.decode(String.self)
|
||||||
self = .eose(sub_id)
|
self = .eose(sub_id)
|
||||||
return
|
return
|
||||||
} else if typ == "OK" {
|
|
||||||
var cr: CommandResult
|
|
||||||
do {
|
|
||||||
let event_id = try container.decode(String.self)
|
|
||||||
let ok = try container.decode(Bool.self)
|
|
||||||
let msg = try container.decode(String.self)
|
|
||||||
cr = CommandResult(event_id: event_id, ok: ok, msg: msg)
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
self = .ok(cr)
|
|
||||||
return
|
|
||||||
//ev.pow = count_hash_leading_zero_bits(ev.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "expected EVENT, NOTICE or OK, got \(typ)"))
|
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "expected EVENT or NOTICE, got \(typ)"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import UIKit
|
|||||||
class Profiles {
|
class Profiles {
|
||||||
var profiles: [String: TimestampedProfile] = [:]
|
var profiles: [String: TimestampedProfile] = [:]
|
||||||
var validated: [String: NIP05] = [:]
|
var validated: [String: NIP05] = [:]
|
||||||
var nip05_pubkey: [String: String] = [:]
|
|
||||||
var zappers: [String: String] = [:]
|
var zappers: [String: String] = [:]
|
||||||
|
|
||||||
func is_validated(_ pk: String) -> NIP05? {
|
func is_validated(_ pk: String) -> NIP05? {
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ class RelayPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle reconnect logic, etc?
|
||||||
for handler in handlers {
|
for handler in handlers {
|
||||||
handler.callback(relay_id, event)
|
handler.callback(relay_id, event)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
// https://github.com/Tunous/DebouncedOnChange/blob/5670ea13e8ad33e9cc3197f6d13ce492dc0e46ab/Sources/DebouncedOnChange/DebouncedChangeViewModifier.swift
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
/// Adds a modifier for this view that fires an action only when a time interval in seconds represented by
|
|
||||||
/// `debounceTime` elapses between value changes.
|
|
||||||
///
|
|
||||||
/// Each time the value changes before `debounceTime` passes, the previous action will be cancelled and the next
|
|
||||||
/// action /// will be scheduled to run after that time passes again. This mean that the action will only execute
|
|
||||||
/// after changes to the value /// stay unmodified for the specified `debounceTime` in seconds.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - value: The value to check against when determining whether to run the closure.
|
|
||||||
/// - debounceTime: The time in seconds to wait after each value change before running `action` closure.
|
|
||||||
/// - action: A closure to run when the value changes.
|
|
||||||
/// - Returns: A view that fires an action after debounced time when the specified value changes.
|
|
||||||
public func onChange<Value>(
|
|
||||||
of value: Value,
|
|
||||||
debounceTime: TimeInterval,
|
|
||||||
perform action: @escaping (_ newValue: Value) -> Void
|
|
||||||
) -> some View where Value: Equatable {
|
|
||||||
self.modifier(DebouncedChangeViewModifier(trigger: value, debounceTime: debounceTime, action: action))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct DebouncedChangeViewModifier<Value>: ViewModifier where Value: Equatable {
|
|
||||||
let trigger: Value
|
|
||||||
let debounceTime: TimeInterval
|
|
||||||
let action: (Value) -> Void
|
|
||||||
|
|
||||||
@State private var debouncedTask: Task<Void, Never>?
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
content.onChange(of: trigger) { value in
|
|
||||||
debouncedTask?.cancel()
|
|
||||||
debouncedTask = Task.delayed(seconds: debounceTime) { @MainActor in
|
|
||||||
action(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Task {
|
|
||||||
|
|
||||||
/// Asynchronously runs the given `operation` in its own task after the specified number of `seconds`.
|
|
||||||
///
|
|
||||||
/// The operation will be executed after specified number of `seconds` passes. You can cancel the task earlier
|
|
||||||
/// for the operation to be skipped.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - time: Delay time in seconds.
|
|
||||||
/// - operation: The operation to execute.
|
|
||||||
/// - Returns: Handle to the task which can be cancelled.
|
|
||||||
@discardableResult
|
|
||||||
public static func delayed(
|
|
||||||
seconds: TimeInterval,
|
|
||||||
operation: @escaping @Sendable () async -> Void
|
|
||||||
) -> Self where Success == Void, Failure == Never {
|
|
||||||
Self {
|
|
||||||
do {
|
|
||||||
try await Task<Never, Never>.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
|
|
||||||
await operation()
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -39,7 +39,7 @@ enum DisplayName {
|
|||||||
|
|
||||||
func parse_display_name(profile: Profile?, pubkey: String) -> DisplayName {
|
func parse_display_name(profile: Profile?, pubkey: String) -> DisplayName {
|
||||||
if pubkey == "anon" {
|
if pubkey == "anon" {
|
||||||
return .one(NSLocalizedString("Anonymous", comment: "Placeholder display name of anonymous user."))
|
return .one("Anonymous")
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let profile else {
|
guard let profile else {
|
||||||
|
|||||||
@@ -21,22 +21,3 @@ func localizedStringFormat(key: String, locale: Locale?) -> String {
|
|||||||
let fallback = bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: key, value: nil, table: nil)
|
let fallback = bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: key, value: nil, table: nil)
|
||||||
return bundle.localizedString(forKey: key, value: fallback, table: nil)
|
return bundle.localizedString(forKey: key, value: fallback, table: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func currentLanguage() -> String {
|
|
||||||
if #available(iOS 16, *) {
|
|
||||||
return Locale.current.language.languageCode?.identifier ?? "en"
|
|
||||||
} else {
|
|
||||||
return Locale.current.languageCode ?? "en"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Removes the variant part of a locale code so that it contains only the language code.
|
|
||||||
*/
|
|
||||||
func localeToLanguage(_ locale: String) -> String? {
|
|
||||||
if #available(iOS 16, *) {
|
|
||||||
return Locale.LanguageCode(stringLiteral: locale).identifier(.alpha2)
|
|
||||||
} else {
|
|
||||||
return NSLocale(localeIdentifier: locale).languageCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-22
@@ -39,20 +39,11 @@ enum NIP05Validation {
|
|||||||
case valid
|
case valid
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FetchedNIP05 {
|
func validate_nip05(pubkey: String, nip05_str: String) async -> NIP05? {
|
||||||
let response: NIP05Response
|
|
||||||
let nip05: NIP05Response
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetch_nip05_str(nip05_str: String) async -> NIP05Response? {
|
|
||||||
guard let nip05 = NIP05.parse(nip05_str) else {
|
guard let nip05 = NIP05.parse(nip05_str) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return await fetch_nip05(nip05: nip05)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetch_nip05(nip05: NIP05) async -> NIP05Response? {
|
|
||||||
guard let url = nip05.url else {
|
guard let url = nip05.url else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -66,18 +57,6 @@ func fetch_nip05(nip05: NIP05) async -> NIP05Response? {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return decoded
|
|
||||||
}
|
|
||||||
|
|
||||||
func validate_nip05(pubkey: String, nip05_str: String) async -> NIP05? {
|
|
||||||
guard let nip05 = NIP05.parse(nip05_str) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let decoded = await fetch_nip05(nip05: nip05) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let stored_pk = decoded.names[nip05.username] else {
|
guard let stored_pk = decoded.names[nip05.username] else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ extension Notification.Name {
|
|||||||
static var report: Notification.Name {
|
static var report: Notification.Name {
|
||||||
return Notification.Name("report")
|
return Notification.Name("report")
|
||||||
}
|
}
|
||||||
static var mute: Notification.Name {
|
static var block: Notification.Name {
|
||||||
return Notification.Name("mute")
|
return Notification.Name("block")
|
||||||
}
|
}
|
||||||
static var new_mutes: Notification.Name {
|
static var new_mutes: Notification.Name {
|
||||||
return Notification.Name("new_mutes")
|
return Notification.Name("new_mutes")
|
||||||
|
|||||||
@@ -6,116 +6,3 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
class Relayer {
|
|
||||||
let relay: String
|
|
||||||
var attempts: Int
|
|
||||||
var retry_after: Double
|
|
||||||
var last_attempt: Int64?
|
|
||||||
|
|
||||||
init(relay: String, attempts: Int, retry_after: Double) {
|
|
||||||
self.relay = relay
|
|
||||||
self.attempts = attempts
|
|
||||||
self.retry_after = retry_after
|
|
||||||
self.last_attempt = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PostedEvent {
|
|
||||||
let event: NostrEvent
|
|
||||||
var remaining: [Relayer]
|
|
||||||
|
|
||||||
init(event: NostrEvent, remaining: [String]) {
|
|
||||||
self.event = event
|
|
||||||
self.remaining = remaining.map {
|
|
||||||
Relayer(relay: $0, attempts: 0, retry_after: 2.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PostBox {
|
|
||||||
let pool: RelayPool
|
|
||||||
var events: [String: PostedEvent]
|
|
||||||
|
|
||||||
init(pool: RelayPool) {
|
|
||||||
self.pool = pool
|
|
||||||
self.events = [:]
|
|
||||||
pool.register_handler(sub_id: "postbox", handler: handle_event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func try_flushing_events() {
|
|
||||||
let now = Int64(Date().timeIntervalSince1970)
|
|
||||||
for kv in events {
|
|
||||||
let event = kv.value
|
|
||||||
for relayer in event.remaining {
|
|
||||||
if relayer.last_attempt == nil || (now >= (relayer.last_attempt! + Int64(relayer.retry_after))) {
|
|
||||||
print("attempt #\(relayer.attempts) to flush event '\(event.event.content)' to \(relayer.relay) after \(relayer.retry_after) seconds")
|
|
||||||
flush_event(event, to_relay: relayer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_event(relay_id: String, _ ev: NostrConnectionEvent) {
|
|
||||||
try_flushing_events()
|
|
||||||
|
|
||||||
guard case .nostr_event(let resp) = ev else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard case .ok(let cr) = resp else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_relayer(relay_id: relay_id, event_id: cr.event_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func remove_relayer(relay_id: String, event_id: String) {
|
|
||||||
guard let ev = self.events[event_id] else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ev.remaining = ev.remaining.filter {
|
|
||||||
$0.relay != relay_id
|
|
||||||
}
|
|
||||||
if ev.remaining.count == 0 {
|
|
||||||
self.events.removeValue(forKey: event_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func flush_event(_ event: PostedEvent, to_relay: Relayer? = nil) {
|
|
||||||
var relayers = event.remaining
|
|
||||||
if let to_relay {
|
|
||||||
relayers = [to_relay]
|
|
||||||
}
|
|
||||||
|
|
||||||
for relayer in relayers {
|
|
||||||
relayer.attempts += 1
|
|
||||||
relayer.last_attempt = Int64(Date().timeIntervalSince1970)
|
|
||||||
relayer.retry_after *= 1.5
|
|
||||||
pool.send(.event(event.event), to: [relayer.relay])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func flush() {
|
|
||||||
for event in events {
|
|
||||||
flush_event(event.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func send(_ event: NostrEvent) {
|
|
||||||
// Don't add event if we already have it
|
|
||||||
if events[event.id] != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let remaining = pool.descriptors.map {
|
|
||||||
$0.url.absoluteString
|
|
||||||
}
|
|
||||||
|
|
||||||
let posted_ev = PostedEvent(event: event, remaining: remaining)
|
|
||||||
events[event.id] = posted_ev
|
|
||||||
|
|
||||||
flush_event(posted_ev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,21 +24,12 @@ enum Preview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PreviewCache {
|
class PreviewCache {
|
||||||
private var previews: [String: Preview]
|
var previews: [String: Preview]
|
||||||
private var image_meta: [String: ImageFill]
|
|
||||||
|
|
||||||
func lookup(_ evid: String) -> Preview? {
|
func lookup(_ evid: String) -> Preview? {
|
||||||
return previews[evid]
|
return previews[evid]
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_image_meta(_ evid: String) -> ImageFill? {
|
|
||||||
return image_meta[evid]
|
|
||||||
}
|
|
||||||
|
|
||||||
func cache_image_meta(evid: String, image_fill: ImageFill) {
|
|
||||||
self.image_meta[evid] = image_fill
|
|
||||||
}
|
|
||||||
|
|
||||||
func store(evid: String, preview: LPLinkMetadata?) {
|
func store(evid: String, preview: LPLinkMetadata?) {
|
||||||
switch preview {
|
switch preview {
|
||||||
case .none:
|
case .none:
|
||||||
@@ -50,6 +41,5 @@ class PreviewCache {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.previews = [:]
|
self.previews = [:]
|
||||||
self.image_meta = [:]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
//
|
|
||||||
// RelayBootstrap.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-04-04.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
let BOOTSTRAP_RELAYS = [
|
|
||||||
"wss://relay.damus.io",
|
|
||||||
"wss://eden.nostr.land",
|
|
||||||
"wss://nostr.wine",
|
|
||||||
"wss://nos.lol",
|
|
||||||
]
|
|
||||||
|
|
||||||
func bootstrap_relays_setting_key(pubkey: String) -> String {
|
|
||||||
return pk_setting_key(pubkey, key: "bootstrap_relays")
|
|
||||||
}
|
|
||||||
|
|
||||||
func save_bootstrap_relays(pubkey: String, relays: [String]) {
|
|
||||||
let key = bootstrap_relays_setting_key(pubkey: pubkey)
|
|
||||||
|
|
||||||
UserDefaults.standard.set(relays, forKey: key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func load_bootstrap_relays(pubkey: String) -> [String] {
|
|
||||||
let key = bootstrap_relays_setting_key(pubkey: pubkey)
|
|
||||||
|
|
||||||
guard let relays = UserDefaults.standard.stringArray(forKey: key) else {
|
|
||||||
print("loading default bootstrap relays")
|
|
||||||
return BOOTSTRAP_RELAYS.map { $0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
if relays.count == 0 {
|
|
||||||
print("loading default bootstrap relays")
|
|
||||||
return BOOTSTRAP_RELAYS.map { $0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
let loaded_relays = Array(Set(relays + BOOTSTRAP_RELAYS))
|
|
||||||
print("Loading custom bootstrap relays: \(loaded_relays)")
|
|
||||||
return loaded_relays
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
//
|
|
||||||
// ReplyCounter.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-04-04.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class ReplyCounter {
|
|
||||||
private var replies: [String: Int]
|
|
||||||
private var counted: Set<String>
|
|
||||||
private var our_replies: [String: NostrEvent]
|
|
||||||
private let our_pubkey: String
|
|
||||||
|
|
||||||
init(our_pubkey: String) {
|
|
||||||
self.our_pubkey = our_pubkey
|
|
||||||
replies = [:]
|
|
||||||
counted = Set()
|
|
||||||
our_replies = [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func our_reply(_ evid: String) -> NostrEvent? {
|
|
||||||
return our_replies[evid]
|
|
||||||
}
|
|
||||||
|
|
||||||
func get_replies(_ evid: String) -> Int {
|
|
||||||
return replies[evid] ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func count_replies(_ event: NostrEvent) {
|
|
||||||
guard event.is_textlike else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if counted.contains(event.id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
counted.insert(event.id)
|
|
||||||
|
|
||||||
for reply in event.direct_replies(nil) {
|
|
||||||
if event.pubkey == our_pubkey {
|
|
||||||
self.our_replies[reply.ref_id] = event
|
|
||||||
}
|
|
||||||
|
|
||||||
if replies[reply.ref_id] != nil {
|
|
||||||
replies[reply.ref_id] = replies[reply.ref_id]! + 1
|
|
||||||
} else {
|
|
||||||
replies[reply.ref_id] = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,28 +20,18 @@ public struct Translator {
|
|||||||
self.userSettingsStore = userSettingsStore
|
self.userSettingsStore = userSettingsStore
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public func translate(_ text: String, from sourceLanguage: String, to targetLanguage: String) async throws -> String? {
|
||||||
Translates a string from source language to target language.
|
|
||||||
If the translation provider supports its own language detection, it may determine the source language by itself that could be
|
|
||||||
different from what is passed in as the sourceLanguage argument.
|
|
||||||
The source language that is actually used in the translation will be returned as part of the TranslationWithLanguage object.
|
|
||||||
If the translation was unable to be fetched for whatever reason, nil is returned.
|
|
||||||
*/
|
|
||||||
public func translate(_ text: String, from sourceLanguage: String, to targetLanguage: String) async throws -> TranslationWithLanguage? {
|
|
||||||
switch userSettingsStore.translation_service {
|
switch userSettingsStore.translation_service {
|
||||||
case .libretranslate:
|
case .libretranslate:
|
||||||
return try await translateWithLibreTranslate(text, from: sourceLanguage, to: targetLanguage)
|
return try await translateWithLibreTranslate(text, from: sourceLanguage, to: targetLanguage)
|
||||||
case .deepl:
|
case .deepl:
|
||||||
return try await translateWithDeepL(text, to: targetLanguage)
|
return try await translateWithDeepL(text, from: sourceLanguage, to: targetLanguage)
|
||||||
case .none:
|
case .none:
|
||||||
return nil
|
return text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private func translateWithLibreTranslate(_ text: String, from sourceLanguage: String, to targetLanguage: String) async throws -> String? {
|
||||||
Translates a string from sourceLanguage to targetLanguage using LibreTranslate. We do not rely on LibreTranslate's language detection API as it requires a separate API call. Instead, we rely on the passed in sourceLanguage argument.
|
|
||||||
*/
|
|
||||||
private func translateWithLibreTranslate(_ text: String, from sourceLanguage: String, to targetLanguage: String) async throws -> TranslationWithLanguage? {
|
|
||||||
let url = try makeURL(userSettingsStore.libretranslate_url, path: "/translate")
|
let url = try makeURL(userSettingsStore.libretranslate_url, path: "/translate")
|
||||||
|
|
||||||
var request = URLRequest(url: url)
|
var request = URLRequest(url: url)
|
||||||
@@ -61,15 +51,10 @@ public struct Translator {
|
|||||||
let translatedText: String
|
let translatedText: String
|
||||||
}
|
}
|
||||||
let response: Response = try await decodedData(for: request)
|
let response: Response = try await decodedData(for: request)
|
||||||
let translation = response.translatedText
|
return response.translatedText
|
||||||
|
|
||||||
return TranslationWithLanguage(translation: translation, language: targetLanguage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private func translateWithDeepL(_ text: String, from sourceLanguage: String, to targetLanguage: String) async throws -> String? {
|
||||||
Translates a string to targetLanguage using DeepL. We do not accept a sourceLanguage as an argument as DeepL performs language detection within the translate API, its models are generally fairly accurate, and does not require a separate API call like LibreTranslate.
|
|
||||||
*/
|
|
||||||
private func translateWithDeepL(_ text: String, to targetLanguage: String) async throws -> TranslationWithLanguage? {
|
|
||||||
if userSettingsStore.deepl_api_key == "" {
|
if userSettingsStore.deepl_api_key == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -83,9 +68,10 @@ public struct Translator {
|
|||||||
|
|
||||||
struct RequestBody: Encodable {
|
struct RequestBody: Encodable {
|
||||||
let text: [String]
|
let text: [String]
|
||||||
|
let source_lang: String
|
||||||
let target_lang: String
|
let target_lang: String
|
||||||
}
|
}
|
||||||
let body = RequestBody(text: [text], target_lang: targetLanguage.uppercased())
|
let body = RequestBody(text: [text], source_lang: sourceLanguage.uppercased(), target_lang: targetLanguage.uppercased())
|
||||||
request.httpBody = try encoder.encode(body)
|
request.httpBody = try encoder.encode(body)
|
||||||
|
|
||||||
struct Response: Decodable {
|
struct Response: Decodable {
|
||||||
@@ -97,13 +83,7 @@ public struct Translator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let response: Response = try await decodedData(for: request)
|
let response: Response = try await decodedData(for: request)
|
||||||
|
return response.translations.map { $0.text }.joined(separator: " ")
|
||||||
if response.translations.isEmpty {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let translation = response.translations.map { $0.text }.joined(separator: " ")
|
|
||||||
return TranslationWithLanguage(translation: translation, language: response.translations.first!.detected_source_language)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeURL(_ baseUrl: String, path: String) throws -> URL {
|
private func makeURL(_ baseUrl: String, path: String) throws -> URL {
|
||||||
@@ -124,11 +104,6 @@ public struct Translator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct TranslationWithLanguage {
|
|
||||||
let translation: String
|
|
||||||
let language: String
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension URLSession {
|
private extension URLSession {
|
||||||
func data(for request: URLRequest) async throws -> Data {
|
func data(for request: URLRequest) async throws -> Data {
|
||||||
var task: URLSessionDataTask?
|
var task: URLSessionDataTask?
|
||||||
|
|||||||
@@ -47,15 +47,10 @@ struct EventActionBar: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
if damus_state.keypair.privkey != nil {
|
if damus_state.keypair.privkey != nil {
|
||||||
HStack(spacing: 4) {
|
EventActionButton(img: "bubble.left", col: nil) {
|
||||||
EventActionButton(img: "bubble.left", col: bar.replied ? Color.blue : Color.gray) {
|
notify(.reply, event)
|
||||||
notify(.reply, event)
|
|
||||||
}
|
|
||||||
.accessibilityLabel(NSLocalizedString("Reply", comment: "Accessibility label for reply button"))
|
|
||||||
Text(verbatim: "\(bar.replies > 0 ? "\(bar.replies)" : "")")
|
|
||||||
.font(.footnote.weight(.medium))
|
|
||||||
.foregroundColor(bar.replied ? Color.blue : Color.gray)
|
|
||||||
}
|
}
|
||||||
|
.accessibilityLabel(NSLocalizedString("Reply", comment: "Accessibility label for reply button"))
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
@@ -82,10 +77,10 @@ struct EventActionBar: View {
|
|||||||
send_like()
|
send_like()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(verbatim: "\(bar.likes > 0 ? "\(bar.likes)" : "")")
|
Text(verbatim: "\(bar.likes > 0 ? "\(bar.likes)" : "")")
|
||||||
.font(.footnote.weight(.medium))
|
.font(.footnote.weight(.medium))
|
||||||
.nip05_colorized(gradient: bar.liked)
|
.foregroundColor(bar.liked ? Color.accentColor : Color.gray)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let lnurl = self.lnurl {
|
if let lnurl = self.lnurl {
|
||||||
@@ -159,7 +154,7 @@ struct EventActionBar: View {
|
|||||||
|
|
||||||
generator.impactOccurred()
|
generator.impactOccurred()
|
||||||
|
|
||||||
damus_state.postbox.send(like_ev)
|
damus_state.pool.send(.event(like_ev))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,15 +188,8 @@ struct LikeButton: View {
|
|||||||
amountOfAngleIncrease = 20.0
|
amountOfAngleIncrease = 20.0
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
if liked {
|
Image(liked ? "shaka-full" : "shaka-line")
|
||||||
LINEAR_GRADIENT
|
.foregroundColor(liked ? .accentColor : .gray)
|
||||||
.mask(Image("shaka-full")
|
|
||||||
.resizable()
|
|
||||||
).frame(width: 14, height: 14)
|
|
||||||
} else {
|
|
||||||
Image("shaka-line")
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.accessibilityLabel(NSLocalizedString("Like", comment: "Accessibility Label for Like button"))
|
.accessibilityLabel(NSLocalizedString("Like", comment: "Accessibility Label for Like button"))
|
||||||
.rotationEffect(Angle(degrees: shouldAnimate ? rotationAngle : 0))
|
.rotationEffect(Angle(degrees: shouldAnimate ? rotationAngle : 0))
|
||||||
@@ -230,12 +218,12 @@ struct EventActionBar_Previews: PreviewProvider {
|
|||||||
let ev = NostrEvent(content: "hi", pubkey: pk)
|
let ev = NostrEvent(content: "hi", pubkey: pk)
|
||||||
|
|
||||||
let bar = ActionBarModel.empty()
|
let bar = ActionBarModel.empty()
|
||||||
let likedbar = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: nil, our_boost: nil, our_zap: nil, our_reply: nil)
|
let likedbar = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, our_like: nil, our_boost: nil, our_zap: nil)
|
||||||
let likedbar_ours = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: test_event, our_boost: nil, our_zap: nil, our_reply: nil)
|
let likedbar_ours = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: nil, our_zap: nil)
|
||||||
let maxed_bar = ActionBarModel(likes: 999, boosts: 999, zaps: 999, zap_total: 99999999, replies: 999, our_like: test_event, our_boost: test_event, our_zap: nil, our_reply: nil)
|
let maxed_bar = ActionBarModel(likes: 999, boosts: 999, zaps: 999, zap_total: 99999999, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: NostrEvent(id: "", content: "", pubkey: ""), our_zap: nil)
|
||||||
let extra_max_bar = ActionBarModel(likes: 9999, boosts: 9999, zaps: 9999, zap_total: 99999999, replies: 9999, our_like: test_event, our_boost: test_event, our_zap: nil, our_reply: test_event)
|
let extra_max_bar = ActionBarModel(likes: 9999, boosts: 9999, zaps: 9999, zap_total: 99999999, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: NostrEvent(id: "", content: "", pubkey: ""), our_zap: nil)
|
||||||
let mega_max_bar = ActionBarModel(likes: 9999999, boosts: 99999, zaps: 9999, zap_total: 99999999, replies: 9999999, our_like: test_event, our_boost: test_event, our_zap: test_zap, our_reply: test_event)
|
let mega_max_bar = ActionBarModel(likes: 9999999, boosts: 99999, zaps: 9999, zap_total: 99999999, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: NostrEvent(id: "", content: "", pubkey: ""), our_zap: nil)
|
||||||
let zapbar = ActionBarModel(likes: 0, boosts: 0, zaps: 5, zap_total: 10000000, replies: 0, our_like: nil, our_boost: nil, our_zap: nil, our_reply: nil)
|
let zapbar = ActionBarModel(likes: 0, boosts: 0, zaps: 5, zap_total: 10000000, our_like: nil, our_boost: nil, our_zap: nil)
|
||||||
|
|
||||||
VStack(spacing: 50) {
|
VStack(spacing: 50) {
|
||||||
EventActionBar(damus_state: ds, event: ev, bar: bar)
|
EventActionBar(damus_state: ds, event: ev, bar: bar)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ struct ShareAction: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
let col = colorScheme == .light ? DamusColors.mediumGrey : DamusColors.white
|
let col = colorScheme == .light ? Color("DamusMediumGrey") : Color("DamusWhite")
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
Text("Share Note", comment: "Title text to indicate that the buttons below are meant to be used to share a note with others.")
|
Text("Share Note", comment: "Title text to indicate that the buttons below are meant to be used to share a note with others.")
|
||||||
@@ -40,15 +40,15 @@ struct ShareAction: View {
|
|||||||
|
|
||||||
HStack(alignment: .top, spacing: 25) {
|
HStack(alignment: .top, spacing: 25) {
|
||||||
|
|
||||||
ShareActionButton(img: "link", text: NSLocalizedString("Copy Link", comment: "Button to copy link to note"), col: col) {
|
ShareActionButton(img: "link", text: NSLocalizedString("Copy Link TempChange", comment: "Button to copy link to note"), col: col) {
|
||||||
show_share_action = false
|
show_share_action = false
|
||||||
UIPasteboard.general.string = "https://damus.io/" + (bech32_note_id(event.id) ?? event.id)
|
UIPasteboard.general.string = "https://damus.io/" + (bech32_note_id(event.id) ?? event.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
let bookmarkImg = isBookmarked ? "bookmark.slash" : "bookmark"
|
let bookmarkImg = isBookmarked ? "bookmark.slash" : "bookmark"
|
||||||
let bookmarkTxt = isBookmarked ? NSLocalizedString("Remove Bookmark", comment: "Button text to remove bookmark from a note.") : NSLocalizedString("Add Bookmark", comment: "Button text to add bookmark to a note.")
|
let bookmarkTxt = isBookmarked ? "Remove\nBookmark" : "Bookmark"
|
||||||
let boomarkCol = isBookmarked ? Color(.red) : col
|
let boomarkCol = isBookmarked ? Color(.red) : col
|
||||||
ShareActionButton(img: bookmarkImg, text: bookmarkTxt, col: boomarkCol) {
|
ShareActionButton(img: bookmarkImg, text: NSLocalizedString(bookmarkTxt, comment: "Button to bookmark to note"), col: boomarkCol) {
|
||||||
show_share_action = false
|
show_share_action = false
|
||||||
self.bookmarks.updateBookmark(event)
|
self.bookmarks.updateBookmark(event)
|
||||||
isBookmarked = self.bookmarks.isBookmarked(event)
|
isBookmarked = self.bookmarks.isBookmarked(event)
|
||||||
@@ -75,10 +75,10 @@ struct ShareAction: View {
|
|||||||
}) {
|
}) {
|
||||||
Text(NSLocalizedString("Cancel", comment: "Button to cancel a repost."))
|
Text(NSLocalizedString("Cancel", comment: "Button to cancel a repost."))
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .center)
|
.frame(minWidth: 300, maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .center)
|
||||||
.foregroundColor(colorScheme == .light ? DamusColors.black : DamusColors.white)
|
.foregroundColor(colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite"))
|
||||||
.overlay {
|
.overlay {
|
||||||
RoundedRectangle(cornerRadius: 24)
|
RoundedRectangle(cornerRadius: 24)
|
||||||
.stroke(colorScheme == .light ? DamusColors.mediumGrey : DamusColors.white, lineWidth: 1)
|
.stroke(colorScheme == .light ? Color("DamusMediumGrey") : Color("DamusWhite"), lineWidth: 1)
|
||||||
}
|
}
|
||||||
.padding(EdgeInsets(top: 10, leading: 50, bottom: 25, trailing: 50))
|
.padding(EdgeInsets(top: 10, leading: 50, bottom: 25, trailing: 50))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,41 +8,72 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct AddRelayView: View {
|
struct AddRelayView: View {
|
||||||
|
@Binding var show_add_relay: Bool
|
||||||
@Binding var relay: String
|
@Binding var relay: String
|
||||||
|
|
||||||
|
let action: (String?) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack{
|
Form {
|
||||||
TextField(NSLocalizedString("wss://some.relay.com", comment: "Placeholder example for relay server address."), text: $relay)
|
Section(NSLocalizedString("Add Relay", comment: "Label for section for adding a relay server.")) {
|
||||||
.padding(2)
|
ZStack(alignment: .leading) {
|
||||||
.padding(.leading, 25)
|
HStack{
|
||||||
.autocorrectionDisabled(true)
|
TextField(NSLocalizedString("wss://some.relay.com", comment: "Placeholder example for relay server address."), text: $relay)
|
||||||
.textInputAutocapitalization(.never)
|
.padding(2)
|
||||||
|
.padding(.leading, 25)
|
||||||
Label("", systemImage: "xmark.circle.fill")
|
.autocorrectionDisabled(true)
|
||||||
.foregroundColor(.accentColor)
|
.textInputAutocapitalization(.never)
|
||||||
.padding(.trailing, -25.0)
|
|
||||||
.opacity((relay == "") ? 0.0 : 1.0)
|
Label("", systemImage: "xmark.circle.fill")
|
||||||
.onTapGesture {
|
.foregroundColor(.blue)
|
||||||
self.relay = ""
|
.padding(.trailing, -25.0)
|
||||||
|
.opacity((relay == "") ? 0.0 : 1.0)
|
||||||
|
.onTapGesture {
|
||||||
|
self.relay = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label("", systemImage: "doc.on.clipboard")
|
||||||
|
.padding(.leading, -10)
|
||||||
|
.onTapGesture {
|
||||||
|
if let pastedrelay = UIPasteboard.general.string {
|
||||||
|
self.relay = pastedrelay
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label("", systemImage: "doc.on.clipboard")
|
VStack {
|
||||||
.padding(.leading, -10)
|
HStack {
|
||||||
.onTapGesture {
|
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of view adding user inputted relay.")) {
|
||||||
if let pastedrelay = UIPasteboard.general.string {
|
show_add_relay = false
|
||||||
self.relay = pastedrelay
|
action(nil)
|
||||||
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button(NSLocalizedString("Add", comment: "Button to confirm adding user inputted relay.")) {
|
||||||
|
show_add_relay = false
|
||||||
|
action(relay)
|
||||||
|
relay = ""
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AddRelayView_Previews: PreviewProvider {
|
struct AddRelayView_Previews: PreviewProvider {
|
||||||
|
@State static var show: Bool = true
|
||||||
@State static var relay: String = ""
|
@State static var relay: String = ""
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
AddRelayView(relay: $relay)
|
AddRelayView(show_add_relay: $show, relay: $relay, action: {_ in })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,22 +15,23 @@ enum ImageUploadResult {
|
|||||||
case failed(Error?)
|
case failed(Error?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func create_upload_body(mediaData: Data, boundary: String, mediaUploader: MediaUploader, mediaToUpload: MediaUpload) -> Data {
|
fileprivate func create_upload_body(imageDataKey: Data, boundary: String, imageUploader: ImageUploader) -> Data {
|
||||||
let body = NSMutableData();
|
let body = NSMutableData();
|
||||||
let contentType = mediaToUpload.is_image ? "image/jpg" : "video/mp4"
|
let contentType = "image/jpg"
|
||||||
body.appendString(string: "Content-Type: multipart/form-data; boundary=\(boundary)\r\n\r\n")
|
body.appendString(string: "Content-Type: multipart/form-data; boundary=\(boundary)\r\n\r\n")
|
||||||
body.appendString(string: "--\(boundary)\r\n")
|
body.appendString(string: "--\(boundary)\r\n")
|
||||||
body.appendString(string: "Content-Disposition: form-data; name=\(mediaUploader.nameParam); filename=\(mediaToUpload.genericFileName)\r\n")
|
body.appendString(string: "Content-Disposition: form-data; name=\(imageUploader.nameParam); filename=\"damus_generic_filename.jpg\"\r\n")
|
||||||
body.appendString(string: "Content-Type: \(contentType)\r\n\r\n")
|
body.appendString(string: "Content-Type: \(contentType)\r\n\r\n")
|
||||||
body.append(mediaData as Data)
|
body.append(imageDataKey as Data)
|
||||||
body.appendString(string: "\r\n")
|
body.appendString(string: "\r\n")
|
||||||
body.appendString(string: "--\(boundary)--\r\n")
|
body.appendString(string: "--\(boundary)--\r\n")
|
||||||
return body as Data
|
return body as Data
|
||||||
}
|
}
|
||||||
|
|
||||||
func create_upload_request(mediaToUpload: MediaUpload, mediaUploader: MediaUploader, progress: URLSessionTaskDelegate) async -> ImageUploadResult {
|
|
||||||
var mediaData: Data?
|
func create_image_upload_request(imageToUpload: UIImage, imageUploader: ImageUploader, progress: URLSessionTaskDelegate) async -> ImageUploadResult {
|
||||||
guard let url = URL(string: mediaUploader.postAPI) else {
|
|
||||||
|
guard let url = URL(string: imageUploader.postAPI) else {
|
||||||
return .failed(nil)
|
return .failed(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,26 +40,13 @@ func create_upload_request(mediaToUpload: MediaUpload, mediaUploader: MediaUploa
|
|||||||
let boundary = "Boundary-\(UUID().description)"
|
let boundary = "Boundary-\(UUID().description)"
|
||||||
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||||
|
|
||||||
switch mediaToUpload {
|
// otherwise convert to jpg
|
||||||
case .image(let url):
|
guard let jpegData = imageToUpload.jpegData(compressionQuality: 0.8) else {
|
||||||
do {
|
// somehow failed, just return original
|
||||||
mediaData = try Data(contentsOf: url)
|
|
||||||
} catch {
|
|
||||||
return .failed(error)
|
|
||||||
}
|
|
||||||
case .video(let url):
|
|
||||||
do {
|
|
||||||
mediaData = try Data(contentsOf: url)
|
|
||||||
} catch {
|
|
||||||
return .failed(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let mediaData else {
|
|
||||||
return .failed(nil)
|
return .failed(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.httpBody = create_upload_body(mediaData: mediaData, boundary: boundary, mediaUploader: mediaUploader, mediaToUpload: mediaToUpload)
|
request.httpBody = create_upload_body(imageDataKey: jpegData, boundary: boundary, imageUploader: imageUploader)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let (data, _) = try await URLSession.shared.data(for: request, delegate: progress)
|
let (data, _) = try await URLSession.shared.data(for: request, delegate: progress)
|
||||||
@@ -68,8 +56,8 @@ func create_upload_request(mediaToUpload: MediaUpload, mediaUploader: MediaUploa
|
|||||||
return .failed(nil)
|
return .failed(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let url = mediaUploader.getMediaURL(from: responseString, mediaIsImage: mediaToUpload.is_image) else {
|
guard let url = imageUploader.getImageURL(from: responseString) else {
|
||||||
print("Upload failed getting media url")
|
print("Upload failed getting image url")
|
||||||
return .failed(nil)
|
return .failed(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +66,67 @@ func create_upload_request(mediaToUpload: MediaUpload, mediaUploader: MediaUploa
|
|||||||
} catch {
|
} catch {
|
||||||
return .failed(error)
|
return .failed(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PostView {
|
||||||
|
struct ImagePicker: UIViewControllerRepresentable {
|
||||||
|
|
||||||
|
@Environment(\.presentationMode)
|
||||||
|
private var presentationMode
|
||||||
|
|
||||||
|
let sourceType: UIImagePickerController.SourceType
|
||||||
|
let onImagePicked: (UIImage) -> Void
|
||||||
|
|
||||||
|
final class Coordinator: NSObject,
|
||||||
|
UINavigationControllerDelegate,
|
||||||
|
UIImagePickerControllerDelegate {
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
private var presentationMode: PresentationMode
|
||||||
|
private let sourceType: UIImagePickerController.SourceType
|
||||||
|
private let onImagePicked: (UIImage) -> Void
|
||||||
|
|
||||||
|
init(presentationMode: Binding<PresentationMode>,
|
||||||
|
sourceType: UIImagePickerController.SourceType,
|
||||||
|
onImagePicked: @escaping (UIImage) -> Void) {
|
||||||
|
_presentationMode = presentationMode
|
||||||
|
self.sourceType = sourceType
|
||||||
|
self.onImagePicked = onImagePicked
|
||||||
|
}
|
||||||
|
|
||||||
|
func imagePickerController(_ picker: UIImagePickerController,
|
||||||
|
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||||
|
let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
|
||||||
|
onImagePicked(uiImage)
|
||||||
|
presentationMode.dismiss()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
||||||
|
presentationMode.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
return Coordinator(presentationMode: presentationMode,
|
||||||
|
sourceType: sourceType,
|
||||||
|
onImagePicked: onImagePicked)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
|
||||||
|
let picker = UIImagePickerController()
|
||||||
|
picker.sourceType = sourceType
|
||||||
|
picker.delegate = context.coordinator
|
||||||
|
return picker
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: UIImagePickerController,
|
||||||
|
context: UIViewControllerRepresentableContext<ImagePicker>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSMutableData {
|
extension NSMutableData {
|
||||||
@@ -89,7 +138,7 @@ extension NSMutableData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MediaUploader: String, CaseIterable, Identifiable {
|
enum ImageUploader: String, CaseIterable, Identifiable {
|
||||||
var id: String { self.rawValue }
|
var id: String { self.rawValue }
|
||||||
case nostrBuild
|
case nostrBuild
|
||||||
case nostrImg
|
case nostrImg
|
||||||
@@ -103,12 +152,12 @@ enum MediaUploader: String, CaseIterable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportsVideo: Bool {
|
var displayImageUploaderName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .nostrBuild:
|
case .nostrBuild:
|
||||||
return true
|
return "NostrBuild"
|
||||||
case .nostrImg:
|
case .nostrImg:
|
||||||
return false
|
return "NostrImg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +187,7 @@ enum MediaUploader: String, CaseIterable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMediaURL(from responseString: String, mediaIsImage: Bool) -> String? {
|
func getImageURL(from responseString: String) -> String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .nostrBuild:
|
case .nostrBuild:
|
||||||
guard let startIndex = responseString.range(of: "nostr.build_")?.lowerBound else {
|
guard let startIndex = responseString.range(of: "nostr.build_")?.lowerBound else {
|
||||||
@@ -150,7 +199,7 @@ enum MediaUploader: String, CaseIterable, Identifiable {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let nostrBuildImageName = responseString[startIndex..<endIndex]
|
let nostrBuildImageName = responseString[startIndex..<endIndex]
|
||||||
let nostrBuildURL = mediaIsImage ? "https://nostr.build/i/\(nostrBuildImageName)" : "https://nostr.build/av/\(nostrBuildImageName)"
|
let nostrBuildURL = "https://nostr.build/i/\(nostrBuildImageName)"
|
||||||
return nostrBuildURL
|
return nostrBuildURL
|
||||||
|
|
||||||
case .nostrImg:
|
case .nostrImg:
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ struct ChatView: View {
|
|||||||
show_images: show_images,
|
show_images: show_images,
|
||||||
size: .normal,
|
size: .normal,
|
||||||
artifacts: .just_content(event.content),
|
artifacts: .just_content(event.content),
|
||||||
options: [])
|
truncate: false)
|
||||||
|
|
||||||
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
|
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
|
||||||
let bar = make_actionbar_model(ev: event.id, damus: damus_state)
|
let bar = make_actionbar_model(ev: event.id, damus: damus_state)
|
||||||
|
|||||||
+267
-23
@@ -17,47 +17,217 @@ struct ConfigView: View {
|
|||||||
@State var confirm_logout: Bool = false
|
@State var confirm_logout: Bool = false
|
||||||
@State var delete_account_warning: Bool = false
|
@State var delete_account_warning: Bool = false
|
||||||
@State var confirm_delete_account: Bool = false
|
@State var confirm_delete_account: Bool = false
|
||||||
|
@State var show_privkey: Bool = false
|
||||||
|
@State var has_authenticated_locally: Bool = false
|
||||||
|
@State var show_api_key: Bool = false
|
||||||
|
@State var privkey: String
|
||||||
|
@State var privkey_copied: Bool = false
|
||||||
|
@State var pubkey_copied: Bool = false
|
||||||
@State var delete_text: String = ""
|
@State var delete_text: String = ""
|
||||||
|
@State var default_zap_amount: String
|
||||||
|
|
||||||
@ObservedObject var settings: UserSettingsStore
|
@ObservedObject var settings: UserSettingsStore
|
||||||
|
|
||||||
private let DELETE_KEYWORD = "DELETE"
|
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||||
|
|
||||||
init(state: DamusState) {
|
init(state: DamusState) {
|
||||||
self.state = state
|
self.state = state
|
||||||
|
let zap_amt = get_default_zap_amount(pubkey: state.pubkey).map({ "\($0)" }) ?? "1000"
|
||||||
|
_default_zap_amount = State(initialValue: zap_amt)
|
||||||
|
_privkey = State(initialValue: self.state.keypair.privkey_bech32 ?? "")
|
||||||
_settings = ObservedObject(initialValue: state.settings)
|
_settings = ObservedObject(initialValue: state.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func textColor() -> Color {
|
func textColor() -> Color {
|
||||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticateLocally(completion: @escaping (Bool) -> Void) {
|
||||||
|
// Need to authenticate only once while ConfigView is presented
|
||||||
|
guard !has_authenticated_locally else {
|
||||||
|
completion(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let context = LAContext()
|
||||||
|
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) {
|
||||||
|
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: NSLocalizedString("Local authentication to access private key", comment: "Face ID usage description shown when trying to access private key")) { success, error in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
has_authenticated_locally = success
|
||||||
|
completion(success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If there's no authentication set up on the device, let the user copy the key without it
|
||||||
|
has_authenticated_locally = true
|
||||||
|
completion(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: (jb55) could be more general but not gonna worry about it atm
|
||||||
|
func CopyButton(is_pk: Bool) -> some View {
|
||||||
|
return Button(action: {
|
||||||
|
let copyKey = {
|
||||||
|
UIPasteboard.general.string = is_pk ? self.state.keypair.pubkey_bech32 : self.privkey
|
||||||
|
self.privkey_copied = !is_pk
|
||||||
|
self.pubkey_copied = is_pk
|
||||||
|
generator.impactOccurred()
|
||||||
|
}
|
||||||
|
if is_pk {
|
||||||
|
// When trying to copy npub
|
||||||
|
copyKey()
|
||||||
|
} else {
|
||||||
|
// When trying to copy nsec
|
||||||
|
if has_authenticated_locally {
|
||||||
|
copyKey()
|
||||||
|
} else {
|
||||||
|
authenticateLocally { success in
|
||||||
|
if success {
|
||||||
|
copyKey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
let copied = is_pk ? self.pubkey_copied : self.privkey_copied
|
||||||
|
Image(systemName: copied ? "checkmark.circle" : "doc.on.doc")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section(NSLocalizedString("Public Account ID", comment: "Section title for the user's public account ID.")) {
|
||||||
NavigationLink(destination: KeySettingsView(keypair: state.keypair)) {
|
HStack {
|
||||||
IconLabel(NSLocalizedString("Keys", comment: "Settings section for managing keys"), img_name: "key.fill", color: .purple)
|
Text(state.keypair.pubkey_bech32)
|
||||||
|
|
||||||
|
CopyButton(is_pk: true)
|
||||||
}
|
}
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||||
NavigationLink(destination: AppearanceSettingsView(settings: settings)) {
|
}
|
||||||
IconLabel(NSLocalizedString("Appearance", comment: "Section header for text and appearance settings"), img_name: "textformat", color: .red)
|
|
||||||
|
if let sec = state.keypair.privkey_bech32 {
|
||||||
|
Section(NSLocalizedString("Secret Account Login Key", comment: "Section title for user's secret account login key.")) {
|
||||||
|
HStack {
|
||||||
|
if show_privkey == false || !has_authenticated_locally {
|
||||||
|
SecureField(NSLocalizedString("Private Key", comment: "Title of the secure field that holds the user's private key."), text: $privkey)
|
||||||
|
.disabled(true)
|
||||||
|
} else {
|
||||||
|
Text(sec)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyButton(is_pk: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(NSLocalizedString("Show", comment: "Toggle to show or hide user's secret account login key."), isOn: $show_privkey)
|
||||||
|
.onChange(of: show_privkey) { newValue in
|
||||||
|
if newValue {
|
||||||
|
authenticateLocally { success in
|
||||||
|
show_privkey = success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
NavigationLink(destination: NotificationSettingsView(settings: settings)) {
|
|
||||||
IconLabel(NSLocalizedString("Local Notifications", comment: "Section header for damus local notifications user configuration"), img_name: "bell.fill", color: .blue)
|
Section(NSLocalizedString("Wallet Selector", comment: "Section title for selection of wallet.")) {
|
||||||
}
|
Toggle(NSLocalizedString("Show wallet selector", comment: "Toggle to show or hide selection of wallet."), isOn: $settings.show_wallet_selector).toggleStyle(.switch)
|
||||||
|
Picker(NSLocalizedString("Select default wallet", comment: "Prompt selection of user's default wallet"),
|
||||||
NavigationLink(destination: ZapSettingsView(pubkey: state.pubkey, settings: settings)) {
|
selection: $settings.default_wallet) {
|
||||||
IconLabel(NSLocalizedString("Zaps", comment: "Section header for zap settings"), img_name: "bolt.fill", color: .orange)
|
ForEach(Wallet.allCases, id: \.self) { wallet in
|
||||||
}
|
Text(wallet.model.displayName)
|
||||||
|
.tag(wallet.model.tag)
|
||||||
NavigationLink(destination: TranslationSettingsView(settings: settings)) {
|
}
|
||||||
IconLabel(NSLocalizedString("Translation", comment: "Section header for text and appearance settings"), img_name: "globe.americas.fill", color: .green)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Section title for zap configuration")) {
|
||||||
|
TextField(String("1000"), text: $default_zap_amount)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
.onReceive(Just(default_zap_amount)) { newValue in
|
||||||
|
if let parsed = handle_string_amount(new_value: newValue) {
|
||||||
|
self.default_zap_amount = String(parsed)
|
||||||
|
set_default_zap_amount(pubkey: self.state.pubkey, amount: parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Section(NSLocalizedString("Translations", comment: "Section title for selecting the translation service.")) {
|
||||||
|
Picker(NSLocalizedString("Service", comment: "Prompt selection of translation service provider."), selection: $settings.translation_service) {
|
||||||
|
ForEach(TranslationService.allCases, id: \.self) { server in
|
||||||
|
Text(server.model.displayName)
|
||||||
|
.tag(server.model.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.translation_service == .libretranslate {
|
||||||
|
Picker(NSLocalizedString("Server", comment: "Prompt selection of LibreTranslate server to perform machine translations on notes"), selection: $settings.libretranslate_server) {
|
||||||
|
ForEach(LibreTranslateServer.allCases, id: \.self) { server in
|
||||||
|
Text(server.model.displayName)
|
||||||
|
.tag(server.model.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.libretranslate_server == .custom {
|
||||||
|
TextField(NSLocalizedString("URL", comment: "Example URL to LibreTranslate server"), text: $settings.libretranslate_url)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.autocapitalization(UITextAutocapitalizationType.none)
|
||||||
|
}
|
||||||
|
|
||||||
|
SecureField(NSLocalizedString("API Key (optional)", comment: "Prompt for optional entry of API Key to use translation server."), text: $settings.libretranslate_api_key)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.disabled(settings.translation_service != .libretranslate)
|
||||||
|
.autocapitalization(UITextAutocapitalizationType.none)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.translation_service == .deepl {
|
||||||
|
Picker(NSLocalizedString("Plan", comment: "Prompt selection of DeepL subscription plan to perform machine translations on notes"), selection: $settings.deepl_plan) {
|
||||||
|
ForEach(DeepLPlan.allCases, id: \.self) { server in
|
||||||
|
Text(server.model.displayName)
|
||||||
|
.tag(server.model.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SecureField(NSLocalizedString("API Key (required)", comment: "Prompt for required entry of API Key to use translation server."), text: $settings.deepl_api_key)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.disabled(settings.translation_service != .deepl)
|
||||||
|
.autocapitalization(UITextAutocapitalizationType.none)
|
||||||
|
|
||||||
|
if settings.deepl_api_key == "" {
|
||||||
|
Link(NSLocalizedString("Get API Key", comment: "Button to navigate to DeepL website to get a translation API key."), destination: URL(string: "https://www.deepl.com/pro-api")!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Section(NSLocalizedString("Miscellaneous", comment: "Section header for miscellaneous user configuration")) {
|
||||||
|
Toggle(NSLocalizedString("Left Handed", comment: "Moves the post button to the left side of the screen"), isOn: $settings.left_handed)
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
Toggle(NSLocalizedString("Zap Vibration", comment: "Setting to enable vibration on zap"), isOn: $settings.zap_vibration)
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
}
|
||||||
|
|
||||||
|
Section(NSLocalizedString("Images", comment: "Section title for images configuration.")) {
|
||||||
|
Toggle(NSLocalizedString("Disable animations", comment: "Button to disable image animation"), isOn: $settings.disable_animation)
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
.onChange(of: settings.disable_animation) { _ in
|
||||||
|
clear_kingfisher_cache()
|
||||||
|
}
|
||||||
|
Toggle(NSLocalizedString("Always show images", comment: "Setting to always show and never blur images"), isOn: $settings.always_show_images)
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
|
||||||
|
Button(NSLocalizedString("Clear Cache", comment: "Button to clear image cache.")) {
|
||||||
|
clear_kingfisher_cache()
|
||||||
|
}
|
||||||
|
|
||||||
|
Picker(NSLocalizedString("Select image uploader", comment: "Prompt selection of user's image uploader"),
|
||||||
|
selection: $settings.default_image_uploader) {
|
||||||
|
ForEach(ImageUploader.allCases, id: \.self) { uploader in
|
||||||
|
Text(uploader.model.displayName)
|
||||||
|
.tag(uploader.model.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
|
Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if state.keypair.privkey == nil {
|
if state.keypair.privkey == nil {
|
||||||
@@ -99,7 +269,7 @@ struct ConfigView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alert(NSLocalizedString("Permanently Delete Account", comment: "Alert for deleting the users account."), isPresented: $confirm_delete_account) {
|
.alert(NSLocalizedString("Permanently Delete Account", comment: "Alert for deleting the users account."), isPresented: $confirm_delete_account) {
|
||||||
TextField(String(format: NSLocalizedString("Type %@ to delete", comment: "Text field prompt asking user to type DELETE in all caps to confirm that they want to proceed with deleting their account."), DELETE_KEYWORD), text: $delete_text)
|
TextField(NSLocalizedString("Type DELETE to delete", comment: "Text field prompt asking user to type the word DELETE to confirm that they want to proceed with deleting their account. The all caps lock DELETE word should not be translated. Everything else should."), text: $delete_text)
|
||||||
Button(NSLocalizedString("Cancel", comment: "Cancel deleting the user."), role: .cancel) {
|
Button(NSLocalizedString("Cancel", comment: "Cancel deleting the user."), role: .cancel) {
|
||||||
confirm_delete_account = false
|
confirm_delete_account = false
|
||||||
}
|
}
|
||||||
@@ -108,12 +278,12 @@ struct ConfigView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard delete_text == DELETE_KEYWORD else {
|
guard delete_text == "DELETE" else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let ev = created_deleted_account_profile(keypair: full_kp)
|
let ev = created_deleted_account_profile(keypair: full_kp)
|
||||||
state.postbox.send(ev)
|
state.pool.send(.event(ev))
|
||||||
notify(.logout, ())
|
notify(.logout, ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,6 +302,80 @@ struct ConfigView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var libretranslate_view: some View {
|
||||||
|
VStack {
|
||||||
|
Picker(NSLocalizedString("Server", comment: "Prompt selection of LibreTranslate server to perform machine translations on notes"), selection: $settings.libretranslate_server) {
|
||||||
|
ForEach(LibreTranslateServer.allCases, id: \.self) { server in
|
||||||
|
Text(server.model.displayName)
|
||||||
|
.tag(server.model.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField(NSLocalizedString("URL", comment: "Example URL to LibreTranslate server"), text: $settings.libretranslate_url)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.disabled(settings.libretranslate_server != .custom)
|
||||||
|
.autocapitalization(UITextAutocapitalizationType.none)
|
||||||
|
HStack {
|
||||||
|
let libretranslate_api_key_placeholder = NSLocalizedString("API Key (optional)", comment: "Prompt for optional entry of API Key to use translation server.")
|
||||||
|
if show_api_key {
|
||||||
|
TextField(libretranslate_api_key_placeholder, text: $settings.libretranslate_api_key)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.autocapitalization(UITextAutocapitalizationType.none)
|
||||||
|
if settings.libretranslate_api_key != "" {
|
||||||
|
Button(NSLocalizedString("Hide API Key", comment: "Button to hide the LibreTranslate server API key.")) {
|
||||||
|
show_api_key = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SecureField(libretranslate_api_key_placeholder, text: $settings.libretranslate_api_key)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.autocapitalization(UITextAutocapitalizationType.none)
|
||||||
|
if settings.libretranslate_api_key != "" {
|
||||||
|
Button(NSLocalizedString("Show API Key", comment: "Button to show the LibreTranslate server API key.")) {
|
||||||
|
show_api_key = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var deepl_view: some View {
|
||||||
|
VStack {
|
||||||
|
Picker(NSLocalizedString("Plan", comment: "Prompt selection of DeepL subscription plan to perform machine translations on notes"), selection: $settings.deepl_plan) {
|
||||||
|
ForEach(DeepLPlan.allCases, id: \.self) { server in
|
||||||
|
Text(server.model.displayName)
|
||||||
|
.tag(server.model.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
let deepl_api_key_placeholder = NSLocalizedString("API Key (required)", comment: "Prompt for required entry of API Key to use translation server.")
|
||||||
|
if show_api_key {
|
||||||
|
TextField(deepl_api_key_placeholder, text: $settings.deepl_api_key)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.autocapitalization(UITextAutocapitalizationType.none)
|
||||||
|
if settings.deepl_api_key != "" {
|
||||||
|
Button(NSLocalizedString("Hide API Key", comment: "Button to hide the DeepL translation API key.")) {
|
||||||
|
show_api_key = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SecureField(deepl_api_key_placeholder, text: $settings.deepl_api_key)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.autocapitalization(UITextAutocapitalizationType.none)
|
||||||
|
if settings.deepl_api_key != "" {
|
||||||
|
Button(NSLocalizedString("Show API Key", comment: "Button to show the DeepL translation API key.")) {
|
||||||
|
show_api_key = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if settings.deepl_api_key == "" {
|
||||||
|
Link(NSLocalizedString("Get API Key", comment: "Button to navigate to DeepL website to get a translation API key."), destination: URL(string: "https://www.deepl.com/pro-api")!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ConfigView_Previews: PreviewProvider {
|
struct ConfigView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -9,12 +9,9 @@ import SwiftUI
|
|||||||
|
|
||||||
struct CreateAccountView: View {
|
struct CreateAccountView: View {
|
||||||
@StateObject var account: CreateAccountModel = CreateAccountModel()
|
@StateObject var account: CreateAccountModel = CreateAccountModel()
|
||||||
@StateObject var profileUploadViewModel = ProfileUploadingViewModel()
|
|
||||||
|
|
||||||
@State var is_light: Bool = false
|
@State var is_light: Bool = false
|
||||||
@State var is_done: Bool = false
|
@State var is_done: Bool = false
|
||||||
@State var reading_eula: Bool = false
|
@State var reading_eula: Bool = false
|
||||||
@State var profile_image: URL? = nil
|
|
||||||
|
|
||||||
func SignupForm<FormContent: View>(@ViewBuilder content: () -> FormContent) -> some View {
|
func SignupForm<FormContent: View>(@ViewBuilder content: () -> FormContent) -> some View {
|
||||||
return VStack(alignment: .leading, spacing: 10.0, content: content)
|
return VStack(alignment: .leading, spacing: 10.0, content: content)
|
||||||
@@ -35,7 +32,7 @@ struct CreateAccountView: View {
|
|||||||
.font(.title.bold())
|
.font(.title.bold())
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
|
||||||
ProfilePictureSelector(pubkey: account.pubkey, viewModel: profileUploadViewModel, callback: uploadedProfilePicture(image_url:))
|
ProfilePictureSelector(pubkey: account.pubkey)
|
||||||
|
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
VStack {
|
VStack {
|
||||||
@@ -84,8 +81,6 @@ struct CreateAccountView: View {
|
|||||||
self.is_done = true
|
self.is_done = true
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.disabled(profileUploadViewModel.isLoading)
|
|
||||||
.opacity(profileUploadViewModel.isLoading ? 0.5 : 1)
|
|
||||||
}
|
}
|
||||||
.padding(.leading, 14.0)
|
.padding(.leading, 14.0)
|
||||||
.padding(.trailing, 20.0)
|
.padding(.trailing, 20.0)
|
||||||
@@ -96,10 +91,6 @@ struct CreateAccountView: View {
|
|||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.navigationBarItems(leading: BackNav())
|
.navigationBarItems(leading: BackNav())
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadedProfilePicture(image_url: URL?) {
|
|
||||||
account.profile_image = image_url?.absoluteString
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BackNav: View {
|
struct BackNav: View {
|
||||||
|
|||||||
@@ -130,10 +130,7 @@ struct DMChatView: View {
|
|||||||
|
|
||||||
dms.draft = ""
|
dms.draft = ""
|
||||||
|
|
||||||
damus_state.postbox.send(dm)
|
damus_state.pool.send(.event(dm))
|
||||||
|
|
||||||
handle_incoming_dm(ev: dm, our_pubkey: damus_state.pubkey, dms: damus_state.dms, prev_events: NewEventsBits())
|
|
||||||
|
|
||||||
end_editing()
|
end_editing()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,18 +14,8 @@ struct DMView: View {
|
|||||||
var is_ours: Bool {
|
var is_ours: Bool {
|
||||||
event.pubkey == damus_state.pubkey
|
event.pubkey == damus_state.pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
var Mention: some View {
|
var body: some View {
|
||||||
Group {
|
|
||||||
if let mention = first_eref_mention(ev: event, privkey: damus_state.keypair.privkey) {
|
|
||||||
BuilderEventView(damus: damus_state, event_id: mention.ref.id)
|
|
||||||
} else {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var DM: some View {
|
|
||||||
HStack {
|
HStack {
|
||||||
if is_ours {
|
if is_ours {
|
||||||
Spacer(minLength: UIScreen.main.bounds.width * 0.2)
|
Spacer(minLength: UIScreen.main.bounds.width * 0.2)
|
||||||
@@ -33,7 +23,7 @@ struct DMView: View {
|
|||||||
|
|
||||||
let should_show_img = should_show_images(settings: damus_state.settings, contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
|
let should_show_img = should_show_images(settings: damus_state.settings, contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
|
||||||
|
|
||||||
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: .normal, artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), options: [])
|
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: .normal, artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), truncate: false)
|
||||||
.padding([.top, .leading, .trailing], 10)
|
.padding([.top, .leading, .trailing], 10)
|
||||||
.padding([.bottom], 25)
|
.padding([.bottom], 25)
|
||||||
.background(VisualEffectView(effect: UIBlurEffect(style: .prominent))
|
.background(VisualEffectView(effect: UIBlurEffect(style: .prominent))
|
||||||
@@ -46,20 +36,11 @@ struct DMView: View {
|
|||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
.opacity(0.8)
|
.opacity(0.8)
|
||||||
.offset(x: -10, y: -5), alignment: .bottomTrailing)
|
.offset(x: -10, y: -5), alignment: .bottomTrailing)
|
||||||
|
|
||||||
if !is_ours {
|
if !is_ours {
|
||||||
Spacer(minLength: UIScreen.main.bounds.width * 0.2)
|
Spacer(minLength: UIScreen.main.bounds.width * 0.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
Mention
|
|
||||||
DM
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DMView_Previews: PreviewProvider {
|
struct DMView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ struct EditMetadataView: View {
|
|||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
@State var confirm_ln_address: Bool = false
|
@State var confirm_ln_address: Bool = false
|
||||||
@StateObject var profileUploadViewModel = ProfileUploadingViewModel()
|
|
||||||
|
|
||||||
init (damus_state: DamusState) {
|
init (damus_state: DamusState) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
@@ -84,7 +83,7 @@ struct EditMetadataView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func imageBorderColor() -> Color {
|
func imageBorderColor() -> Color {
|
||||||
colorScheme == .light ? DamusColors.white : DamusColors.black
|
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
|
||||||
}
|
}
|
||||||
|
|
||||||
func save() {
|
func save() {
|
||||||
@@ -103,7 +102,7 @@ struct EditMetadataView: View {
|
|||||||
let m_metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: metadata)
|
let m_metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: metadata)
|
||||||
|
|
||||||
if let metadata_ev = m_metadata_ev {
|
if let metadata_ev = m_metadata_ev {
|
||||||
damus_state.postbox.send(metadata_ev)
|
damus_state.pool.send(.event(metadata_ev))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +126,7 @@ struct EditMetadataView: View {
|
|||||||
let pfp_size: CGFloat = 90.0
|
let pfp_size: CGFloat = 90.0
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ProfilePictureSelector(pubkey: damus_state.pubkey, damus_state: damus_state, viewModel: profileUploadViewModel, callback: uploadedProfilePicture(image_url:))
|
ProfilePicView(pubkey: damus_state.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles)
|
||||||
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
|
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -202,10 +201,8 @@ struct EditMetadataView: View {
|
|||||||
}, footer: {
|
}, footer: {
|
||||||
if let parts = nip05_parts {
|
if let parts = nip05_parts {
|
||||||
Text("'\(parts.username)' at '\(parts.host)' will be used for verification", comment: "Description of how the nip05 identifier would be used for verification.")
|
Text("'\(parts.username)' at '\(parts.host)' will be used for verification", comment: "Description of how the nip05 identifier would be used for verification.")
|
||||||
} else if !nip05.isEmpty {
|
|
||||||
Text("'\(nip05)' is an invalid NIP-05 identifier. It should look like an email.", comment: "Description of why the nip05 identifier is invalid.")
|
|
||||||
} else {
|
} else {
|
||||||
Text("") // without this, the keyboard dismisses unnecessarily when the footer changes state
|
Text("'\(nip05)' is an invalid NIP-05 identifier. It should look like an email.", comment: "Description of why the nip05 identifier is invalid.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -217,7 +214,6 @@ struct EditMetadataView: View {
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disabled(profileUploadViewModel.isLoading)
|
|
||||||
.alert(NSLocalizedString("Invalid Tip Address", comment: "Title of alerting as invalid tip address."), isPresented: $confirm_ln_address) {
|
.alert(NSLocalizedString("Invalid Tip Address", comment: "Title of alerting as invalid tip address."), isPresented: $confirm_ln_address) {
|
||||||
Button(NSLocalizedString("Ok", comment: "Button to dismiss the alert.")) {
|
Button(NSLocalizedString("Ok", comment: "Button to dismiss the alert.")) {
|
||||||
}
|
}
|
||||||
@@ -228,10 +224,6 @@ struct EditMetadataView: View {
|
|||||||
}
|
}
|
||||||
.ignoresSafeArea(edges: .top)
|
.ignoresSafeArea(edges: .top)
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadedProfilePicture(image_url: URL?) {
|
|
||||||
picture = image_url?.absoluteString ?? ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EditMetadataView_Previews: PreviewProvider {
|
struct EditMetadataView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
//
|
|
||||||
// EmptyUserSearchView.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by eric on 4/3/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
// EmptyUserSearchView.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by eric on 4/3/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct EmptyUserSearchView: View {
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
Image(systemName: "person.fill.questionmark")
|
|
||||||
.font(.system(size: 35))
|
|
||||||
.padding()
|
|
||||||
Text("Could not find the user you're looking for", comment: "Indicates that there are no users found.")
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.font(.callout.weight(.medium))
|
|
||||||
}
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EmptyUserSearchView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
EmptyUserSearchView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+29
-15
@@ -25,16 +25,7 @@ func eventviewsize_to_font(_ size: EventViewKind) -> Font {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func eventviewsize_to_uifont(_ size: EventViewKind) -> UIFont {
|
|
||||||
switch size {
|
|
||||||
case .small:
|
|
||||||
return .preferredFont(forTextStyle: .body)
|
|
||||||
case .normal:
|
|
||||||
return .preferredFont(forTextStyle: .body)
|
|
||||||
case .selected:
|
|
||||||
return .preferredFont(forTextStyle: .title2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EventView: View {
|
struct EventView: View {
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
@@ -69,7 +60,17 @@ struct EventView: View {
|
|||||||
VStack {
|
VStack {
|
||||||
if event.known_kind == .boost {
|
if event.known_kind == .boost {
|
||||||
if let inner_ev = event.inner_event {
|
if let inner_ev = event.inner_event {
|
||||||
RepostedEvent(damus: damus, event: event, inner_ev: inner_ev, options: options)
|
VStack(alignment: .leading) {
|
||||||
|
let prof = damus.profiles.lookup(id: event.pubkey)
|
||||||
|
let booster_profile = ProfileView(damus_state: damus, pubkey: event.pubkey)
|
||||||
|
|
||||||
|
NavigationLink(destination: booster_profile) {
|
||||||
|
Reposted(damus: damus, pubkey: event.pubkey, profile: prof)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
TextEvent(damus: damus, event: inner_ev, pubkey: inner_ev.pubkey, options: options)
|
||||||
|
.padding([.top], 1)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
@@ -81,7 +82,7 @@ struct EventView: View {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TextEvent(damus: damus, event: event, pubkey: pubkey, options: options)
|
TextEvent(damus: damus, event: event, pubkey: pubkey, options: options)
|
||||||
//.padding([.top], 6)
|
.padding([.top], 6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,9 +152,22 @@ func format_date(_ created_at: Int64) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func make_actionbar_model(ev: String, damus: DamusState) -> ActionBarModel {
|
func make_actionbar_model(ev: String, damus: DamusState) -> ActionBarModel {
|
||||||
let model = ActionBarModel.empty()
|
let likes = damus.likes.counts[ev]
|
||||||
model.update(damus: damus, evid: ev)
|
let boosts = damus.boosts.counts[ev]
|
||||||
return model
|
let zaps = damus.zaps.event_counts[ev]
|
||||||
|
let zap_total = damus.zaps.event_totals[ev]
|
||||||
|
let our_like = damus.likes.our_events[ev]
|
||||||
|
let our_boost = damus.boosts.our_events[ev]
|
||||||
|
let our_zap = damus.zaps.our_zaps[ev]
|
||||||
|
|
||||||
|
return ActionBarModel(likes: likes ?? 0,
|
||||||
|
boosts: boosts ?? 0,
|
||||||
|
zaps: zaps ?? 0,
|
||||||
|
zap_total: zap_total ?? 0,
|
||||||
|
our_like: our_like,
|
||||||
|
our_boost: our_boost,
|
||||||
|
our_zap: our_zap?.first
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,11 +30,7 @@ struct EmbeddedEventView: View {
|
|||||||
.minimumScaleFactor(0.75)
|
.minimumScaleFactor(0.75)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
|
||||||
if event_is_reply(event, privkey: damus_state.keypair.privkey) {
|
EventBody(damus_state: damus_state, event: event, size: .small)
|
||||||
ReplyDescription(event: event, profiles: damus_state.profiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
EventBody(damus_state: damus_state, event: event, size: .small, options: [.truncate_content])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,11 @@ struct EventBody: View {
|
|||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let size: EventViewKind
|
let size: EventViewKind
|
||||||
let should_show_img: Bool
|
let should_show_img: Bool
|
||||||
let options: EventViewOptions
|
|
||||||
|
|
||||||
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind, should_show_img: Bool? = nil, options: EventViewOptions) {
|
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind, should_show_img: Bool? = nil) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
self.event = event
|
self.event = event
|
||||||
self.size = size
|
self.size = size
|
||||||
self.options = options
|
|
||||||
self.should_show_img = should_show_img ?? should_show_images(settings: damus_state.settings, contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
|
self.should_show_img = should_show_img ?? should_show_images(settings: damus_state.settings, contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,13 +25,17 @@ struct EventBody: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, artifacts: .just_content(content), options: options)
|
if event_is_reply(event, privkey: damus_state.keypair.privkey) {
|
||||||
|
ReplyDescription(event: event, profiles: damus_state.profiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, artifacts: .just_content(content), truncate: true)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EventBody_Previews: PreviewProvider {
|
struct EventBody_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
EventBody(damus_state: test_damus_state(), event: test_event, size: .normal, options: [])
|
EventBody(damus_state: test_damus_state(), event: test_event, size: .normal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ struct MenuItems: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
notify(.mute, target_pubkey)
|
notify(.block, target_pubkey)
|
||||||
} label: {
|
} label: {
|
||||||
Label(NSLocalizedString("Mute", comment: "Context menu option for muting users."), systemImage: "exclamationmark.octagon")
|
Label(NSLocalizedString("Block", comment: "Context menu option for blocking users."), systemImage: "exclamationmark.octagon")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ struct MutedEventView: View {
|
|||||||
|
|
||||||
let selected: Bool
|
let selected: Bool
|
||||||
@State var shown: Bool
|
@State var shown: Bool
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
init(damus_state: DamusState, event: NostrEvent, scroller: ScrollViewProxy?, selected: Bool) {
|
init(damus_state: DamusState, event: NostrEvent, scroller: ScrollViewProxy?, selected: Bool) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
@@ -27,10 +28,14 @@ struct MutedEventView: View {
|
|||||||
return !should_show_event(contacts: damus_state.contacts, ev: event)
|
return !should_show_event(contacts: damus_state.contacts, ev: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var FillColor: Color {
|
||||||
|
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
||||||
|
}
|
||||||
|
|
||||||
var MutedBox: some View {
|
var MutedBox: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
RoundedRectangle(cornerRadius: 20)
|
RoundedRectangle(cornerRadius: 20)
|
||||||
.foregroundColor(DamusColors.adaptableGrey)
|
.foregroundColor(FillColor)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Post from a user you've blocked", comment: "Text to indicate that what is being shown is a post from a user who has been blocked.")
|
Text("Post from a user you've blocked", comment: "Text to indicate that what is being shown is a post from a user who has been blocked.")
|
||||||
@@ -46,7 +51,7 @@ struct MutedEventView: View {
|
|||||||
var Event: some View {
|
var Event: some View {
|
||||||
Group {
|
Group {
|
||||||
if selected {
|
if selected {
|
||||||
SelectedEventView(damus: damus_state, event: event, size: .selected)
|
SelectedEventView(damus: damus_state, event: event)
|
||||||
} else {
|
} else {
|
||||||
EventView(damus: damus_state, event: event)
|
EventView(damus: damus_state, event: event)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import SwiftUI
|
|||||||
struct SelectedEventView: View {
|
struct SelectedEventView: View {
|
||||||
let damus: DamusState
|
let damus: DamusState
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let size: EventViewKind
|
|
||||||
|
|
||||||
var pubkey: String {
|
var pubkey: String {
|
||||||
event.pubkey
|
event.pubkey
|
||||||
@@ -18,10 +17,9 @@ struct SelectedEventView: View {
|
|||||||
|
|
||||||
@StateObject var bar: ActionBarModel
|
@StateObject var bar: ActionBarModel
|
||||||
|
|
||||||
init(damus: DamusState, event: NostrEvent, size: EventViewKind) {
|
init(damus: DamusState, event: NostrEvent) {
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
self.event = event
|
self.event = event
|
||||||
self.size = size
|
|
||||||
self._bar = StateObject(wrappedValue: make_actionbar_model(ev: event.id, damus: damus))
|
self._bar = StateObject(wrappedValue: make_actionbar_model(ev: event.id, damus: damus))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,24 +37,17 @@ struct SelectedEventView: View {
|
|||||||
.padding([.bottom], 4)
|
.padding([.bottom], 4)
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
|
||||||
.minimumScaleFactor(0.75)
|
.minimumScaleFactor(0.75)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
|
||||||
if event_is_reply(event, privkey: damus.keypair.privkey) {
|
EventBody(damus_state: damus, event: event, size: .selected)
|
||||||
ReplyDescription(event: event, profiles: damus.profiles)
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
|
|
||||||
EventBody(damus_state: damus, event: event, size: size, options: [.pad_content])
|
|
||||||
|
|
||||||
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
|
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
|
||||||
BuilderEventView(damus: damus, event_id: mention.ref.id)
|
BuilderEventView(damus: damus, event_id: mention.ref.id)
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(verbatim: "\(format_date(event.created_at))")
|
Text(verbatim: "\(format_date(event.created_at))")
|
||||||
.padding([.top, .leading, .trailing])
|
.padding(.top, 10)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
|
|
||||||
@@ -65,13 +56,11 @@ struct SelectedEventView: View {
|
|||||||
|
|
||||||
if !bar.is_empty {
|
if !bar.is_empty {
|
||||||
EventDetailBar(state: damus, target: event.id, target_pk: event.pubkey)
|
EventDetailBar(state: damus, target: event.id, target_pk: event.pubkey)
|
||||||
.padding(.horizontal)
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
||||||
EventActionBar(damus_state: damus, event: event)
|
EventActionBar(damus_state: damus, event: event)
|
||||||
.padding([.top], 4)
|
.padding([.top], 4)
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
.padding([.top], 4)
|
.padding([.top], 4)
|
||||||
@@ -81,6 +70,7 @@ struct SelectedEventView: View {
|
|||||||
guard target == self.event.id else { return }
|
guard target == self.event.id else { return }
|
||||||
self.bar.update(damus: self.damus, evid: target)
|
self.bar.update(damus: self.damus, evid: target)
|
||||||
}
|
}
|
||||||
|
.padding([.leading], 2)
|
||||||
.compositingGroup()
|
.compositingGroup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,7 +78,7 @@ struct SelectedEventView: View {
|
|||||||
|
|
||||||
struct SelectedEventView_Previews: PreviewProvider {
|
struct SelectedEventView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
SelectedEventView(damus: test_damus_state(), event: test_event, size: .selected)
|
SelectedEventView(damus: test_damus_state(), event: test_event)
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ struct EventViewOptions: OptionSet {
|
|||||||
static let no_action_bar = EventViewOptions(rawValue: 1 << 0)
|
static let no_action_bar = EventViewOptions(rawValue: 1 << 0)
|
||||||
static let no_replying_to = EventViewOptions(rawValue: 1 << 1)
|
static let no_replying_to = EventViewOptions(rawValue: 1 << 1)
|
||||||
static let no_images = EventViewOptions(rawValue: 1 << 2)
|
static let no_images = EventViewOptions(rawValue: 1 << 2)
|
||||||
static let wide = EventViewOptions(rawValue: 1 << 3)
|
|
||||||
static let truncate_content = EventViewOptions(rawValue: 1 << 4)
|
|
||||||
static let pad_content = EventViewOptions(rawValue: 1 << 5)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TextEvent: View {
|
struct TextEvent: View {
|
||||||
@@ -28,12 +25,51 @@ struct TextEvent: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
HStack(alignment: .top) {
|
||||||
if options.contains(.wide) {
|
let profile = damus.profiles.lookup(id: pubkey)
|
||||||
WideStyle
|
|
||||||
} else {
|
let is_anon = event_is_anonymous(ev: event)
|
||||||
ThreadedStyle
|
VStack {
|
||||||
|
MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 1) {
|
||||||
|
HStack(alignment: .center, spacing: 0) {
|
||||||
|
let pk = is_anon ? "anon" : pubkey
|
||||||
|
|
||||||
|
EventProfileName(pubkey: pk, profile: profile, damus: damus, show_friend_confirmed: true, size: .normal)
|
||||||
|
|
||||||
|
Text(verbatim: "⋅")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
Text(verbatim: "\(format_relative_time(event.created_at))")
|
||||||
|
.font(.system(size: 16))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
EventMenuContext(event: event, keypair: damus.keypair, target_pubkey: event.pubkey, bookmarks: damus.bookmarks)
|
||||||
|
.padding([.bottom], 4)
|
||||||
|
|
||||||
|
}
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
EventBody(damus_state: damus, event: event, size: .normal)
|
||||||
|
|
||||||
|
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
|
||||||
|
BuilderEventView(damus: damus, event_id: mention.ref.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_action_bar {
|
||||||
|
Rectangle().frame(height: 2).opacity(0)
|
||||||
|
|
||||||
|
EventActionBar(damus_state: damus, event: event)
|
||||||
|
.padding([.top], 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding([.leading], 2)
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.background(event_validity_color(event.validity))
|
.background(event_validity_color(event.validity))
|
||||||
@@ -41,127 +77,11 @@ struct TextEvent: View {
|
|||||||
.frame(maxWidth: .infinity, minHeight: PFP_SIZE)
|
.frame(maxWidth: .infinity, minHeight: PFP_SIZE)
|
||||||
.padding([.bottom], 2)
|
.padding([.bottom], 2)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
func Pfp(is_anon: Bool) -> some View {
|
|
||||||
MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TopPart(is_anon: Bool) -> some View {
|
|
||||||
HStack(alignment: .center, spacing: 0) {
|
|
||||||
ProfileName(is_anon: is_anon)
|
|
||||||
TimeDot
|
|
||||||
Time
|
|
||||||
Spacer()
|
|
||||||
ContextButton
|
|
||||||
}
|
|
||||||
.lineLimit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ReplyPart: some View {
|
|
||||||
Group {
|
|
||||||
if event_is_reply(event, privkey: damus.keypair.privkey) {
|
|
||||||
ReplyDescription(event: event, profiles: damus.profiles)
|
|
||||||
} else {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var WideStyle: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
let is_anon = event_is_anonymous(ev: event)
|
|
||||||
|
|
||||||
HStack(spacing: 10) {
|
|
||||||
Pfp(is_anon: is_anon)
|
|
||||||
VStack {
|
|
||||||
TopPart(is_anon: is_anon)
|
|
||||||
ReplyPart
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
EvBody(options: self.options.union(.pad_content))
|
struct TextEvent_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
|
TextEvent(damus: test_damus_state(), event: test_event, pubkey: "pk", options: [])
|
||||||
Mention(mention)
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_action_bar {
|
|
||||||
//EmptyRect
|
|
||||||
ActionBar
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var TimeDot: some View {
|
|
||||||
Text(verbatim: "⋅")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
|
|
||||||
var Time: some View {
|
|
||||||
Text(verbatim: "\(format_relative_time(event.created_at))")
|
|
||||||
.font(.system(size: 16))
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ContextButton: some View {
|
|
||||||
EventMenuContext(event: event, keypair: damus.keypair, target_pubkey: event.pubkey, bookmarks: damus.bookmarks)
|
|
||||||
.padding([.bottom], 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProfileName(is_anon: Bool) -> some View {
|
|
||||||
let profile = damus.profiles.lookup(id: pubkey)
|
|
||||||
let pk = is_anon ? "anon" : pubkey
|
|
||||||
return EventProfileName(pubkey: pk, profile: profile, damus: damus, show_friend_confirmed: true, size: .normal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func EvBody(options: EventViewOptions) -> some View {
|
|
||||||
return EventBody(damus_state: damus, event: event, size: .normal, options: options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Mention(_ mention: Mention) -> some View {
|
|
||||||
return BuilderEventView(damus: damus, event_id: mention.ref.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ActionBar: some View {
|
|
||||||
return EventActionBar(damus_state: damus, event: event)
|
|
||||||
.padding([.top], 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
var EmptyRect: some View {
|
|
||||||
return Rectangle().frame(height: 2).opacity(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ThreadedStyle: some View {
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
|
|
||||||
let is_anon = event_is_anonymous(ev: event)
|
|
||||||
VStack {
|
|
||||||
Pfp(is_anon: is_anon)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
TopPart(is_anon: is_anon)
|
|
||||||
|
|
||||||
ReplyPart
|
|
||||||
EvBody(options: self.options)
|
|
||||||
|
|
||||||
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
|
|
||||||
Mention(mention)
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_action_bar {
|
|
||||||
EmptyRect
|
|
||||||
ActionBar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding([.leading], 2)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,16 +99,3 @@ func event_has_tag(ev: NostrEvent, tag: String) -> Bool {
|
|||||||
func event_is_anonymous(ev: NostrEvent) -> Bool {
|
func event_is_anonymous(ev: NostrEvent) -> Bool {
|
||||||
return ev.known_kind == .zap_request && event_has_tag(ev: ev, tag: "anon")
|
return ev.known_kind == .zap_request && event_has_tag(ev: ev, tag: "anon")
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TextEvent_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
VStack(spacing: 20) {
|
|
||||||
TextEvent(damus: test_damus_state(), event: test_event, pubkey: "pk", options: [])
|
|
||||||
.frame(height: 400)
|
|
||||||
|
|
||||||
TextEvent(damus: test_damus_state(), event: test_event, pubkey: "pk", options: [.wide])
|
|
||||||
.frame(height: 400)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
//
|
|
||||||
// WideEventView.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-03-23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct WideEventView: View {
|
|
||||||
let event: NostrEvent
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WideEventView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
WideEventView(event: test_event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@ struct ZapEvent: View {
|
|||||||
|
|
||||||
if zap.private_request != nil {
|
if zap.private_request != nil {
|
||||||
Image(systemName: "lock.fill")
|
Image(systemName: "lock.fill")
|
||||||
.foregroundColor(DamusColors.green)
|
.foregroundColor(Color("DamusGreen"))
|
||||||
.help(NSLocalizedString("Only you can see this message and who sent it.", comment: "Help text on green lock icon that explains that only the current user can see the message of a zap event and who sent the zap."))
|
.help(NSLocalizedString("Only you can see this message and who sent it.", comment: "Help text on green lock icon that explains that only the current user can see the message of a zap event and who sent the zap."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,11 +50,11 @@ struct FollowButtonView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func filledTextColor() -> Color {
|
func filledTextColor() -> Color {
|
||||||
colorScheme == .light ? DamusColors.white : DamusColors.black
|
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
|
||||||
}
|
}
|
||||||
|
|
||||||
func fillColor() -> Color {
|
func fillColor() -> Color {
|
||||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||||
}
|
}
|
||||||
|
|
||||||
func emptyColor() -> Color {
|
func emptyColor() -> Color {
|
||||||
@@ -62,7 +62,7 @@ struct FollowButtonView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func borderColor() -> Color {
|
func borderColor() -> Color {
|
||||||
colorScheme == .light ? DamusColors.darkGrey : DamusColors.lightGrey
|
colorScheme == .light ? Color("DamusDarkGrey") : Color("DamusLightGrey")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ struct FollowersView: View {
|
|||||||
LazyVStack(alignment: .leading) {
|
LazyVStack(alignment: .leading) {
|
||||||
ForEach(followers.contacts ?? [], id: \.self) { pk in
|
ForEach(followers.contacts ?? [], id: \.self) { pk in
|
||||||
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
||||||
Divider()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
@@ -46,6 +45,7 @@ struct FollowersView: View {
|
|||||||
followers.unsubscribe()
|
followers.unsubscribe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FollowingView: View {
|
struct FollowingView: View {
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
//
|
|
||||||
// ImagePicker.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Swift on 3/31/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ImagePicker: UIViewControllerRepresentable {
|
|
||||||
|
|
||||||
@Environment(\.presentationMode)
|
|
||||||
private var presentationMode
|
|
||||||
|
|
||||||
let sourceType: UIImagePickerController.SourceType
|
|
||||||
let pubkey: String
|
|
||||||
var imagesOnly: Bool = false
|
|
||||||
let onImagePicked: (URL) -> Void
|
|
||||||
let onVideoPicked: (URL) -> Void
|
|
||||||
|
|
||||||
final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
|
|
||||||
@Binding private var presentationMode: PresentationMode
|
|
||||||
private let sourceType: UIImagePickerController.SourceType
|
|
||||||
private let onImagePicked: (URL) -> Void
|
|
||||||
private let onVideoPicked: (URL) -> Void
|
|
||||||
|
|
||||||
init(presentationMode: Binding<PresentationMode>,
|
|
||||||
sourceType: UIImagePickerController.SourceType,
|
|
||||||
onImagePicked: @escaping (URL) -> Void,
|
|
||||||
onVideoPicked: @escaping (URL) -> Void) {
|
|
||||||
_presentationMode = presentationMode
|
|
||||||
self.sourceType = sourceType
|
|
||||||
self.onImagePicked = onImagePicked
|
|
||||||
self.onVideoPicked = onVideoPicked
|
|
||||||
}
|
|
||||||
|
|
||||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
|
||||||
if let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL {
|
|
||||||
// Handle the selected video
|
|
||||||
onVideoPicked(videoURL)
|
|
||||||
} else if let imageURL = info[UIImagePickerController.InfoKey.imageURL] as? URL {
|
|
||||||
// Handle the selected image
|
|
||||||
onImagePicked(imageURL)
|
|
||||||
} else if let cameraImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
|
|
||||||
if let imageURL = saveImageToTemporaryFolder(image: cameraImage, imageType: "jpeg") {
|
|
||||||
onImagePicked(imageURL)
|
|
||||||
}
|
|
||||||
} else if let editedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
|
|
||||||
if let editedImageURL = saveImageToTemporaryFolder(image: editedImage) {
|
|
||||||
onImagePicked(editedImageURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
presentationMode.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
|
||||||
presentationMode.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveImageToTemporaryFolder(image: UIImage, imageType: String = "png") -> URL? {
|
|
||||||
// Convert UIImage to Data
|
|
||||||
let imageData: Data?
|
|
||||||
if imageType.lowercased() == "jpeg" {
|
|
||||||
imageData = image.jpegData(compressionQuality: 1.0)
|
|
||||||
} else {
|
|
||||||
imageData = image.pngData()
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let data = imageData else {
|
|
||||||
print("Failed to convert UIImage to Data.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a temporary URL with a unique filename
|
|
||||||
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
|
||||||
let uniqueImageName = "\(UUID().uuidString).\(imageType)"
|
|
||||||
let temporaryImageURL = temporaryDirectoryURL.appendingPathComponent(uniqueImageName)
|
|
||||||
|
|
||||||
// Save the image data to the temporary URL
|
|
||||||
do {
|
|
||||||
try data.write(to: temporaryImageURL)
|
|
||||||
return temporaryImageURL
|
|
||||||
} catch {
|
|
||||||
print("Error saving image data to temporary URL: \(error.localizedDescription)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
|
||||||
return Coordinator(presentationMode: presentationMode,
|
|
||||||
sourceType: sourceType,
|
|
||||||
onImagePicked: { url in
|
|
||||||
// Handle the selected image URL
|
|
||||||
onImagePicked(url)
|
|
||||||
},
|
|
||||||
onVideoPicked: { videoURL in
|
|
||||||
// Handle the selected video URL
|
|
||||||
onVideoPicked(videoURL)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
|
|
||||||
let picker = UIImagePickerController()
|
|
||||||
picker.sourceType = sourceType
|
|
||||||
let mediaUploader = get_media_uploader(pubkey)
|
|
||||||
picker.mediaTypes = ["public.image", "com.compuserve.gif"]
|
|
||||||
if mediaUploader.supportsVideo && !imagesOnly {
|
|
||||||
picker.mediaTypes.append("public.movie")
|
|
||||||
}
|
|
||||||
picker.delegate = context.coordinator
|
|
||||||
return picker
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: UIImagePickerController,
|
|
||||||
context: UIViewControllerRepresentableContext<ImagePicker>) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,6 +16,26 @@ struct ImageView: View {
|
|||||||
@State private var selectedIndex = 0
|
@State private var selectedIndex = 0
|
||||||
@State var showMenu = true
|
@State var showMenu = true
|
||||||
|
|
||||||
|
var navBarView: some View {
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
/*
|
||||||
|
Text(urls[selectedIndex]?.lastPathComponent ?? "")
|
||||||
|
.bold()
|
||||||
|
*/
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}, label: {
|
||||||
|
Image(systemName: "xmark")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var tabViewIndicator: some View {
|
var tabViewIndicator: some View {
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
ForEach(urls.indices, id: \.self) { index in
|
ForEach(urls.indices, id: \.self) { index in
|
||||||
@@ -60,7 +80,7 @@ struct ImageView: View {
|
|||||||
.overlay(
|
.overlay(
|
||||||
VStack {
|
VStack {
|
||||||
if showMenu {
|
if showMenu {
|
||||||
NavDismissBarView()
|
navBarView
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
if (urls.count > 1) {
|
if (urls.count > 1) {
|
||||||
|
|||||||
@@ -79,15 +79,11 @@ struct LoginView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is a weird way to login anyways
|
|
||||||
/*
|
|
||||||
var bootstrap_relays = load_bootstrap_relays(pubkey: nip05.pubkey)
|
|
||||||
for relay in nip05.relays {
|
for relay in nip05.relays {
|
||||||
if !(bootstrap_relays.contains { $0 == relay }) {
|
if !(BOOTSTRAP_RELAYS.contains { $0 == relay }) {
|
||||||
bootstrap_relays.append(relay)
|
BOOTSTRAP_RELAYS.append(relay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
save_pubkey(pubkey: nip05.pubkey)
|
save_pubkey(pubkey: nip05.pubkey)
|
||||||
|
|
||||||
notify(.login, ())
|
notify(.login, ())
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ struct MutelistView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
damus_state.contacts.set_mutelist(new_ev)
|
damus_state.contacts.set_mutelist(new_ev)
|
||||||
damus_state.postbox.send(new_ev)
|
damus_state.pool.send(.event(new_ev))
|
||||||
users = get_mutelist_users(new_ev)
|
users = get_mutelist_users(new_ev)
|
||||||
} label: {
|
} label: {
|
||||||
Label(NSLocalizedString("Delete", comment: "Button to remove a user from their blocklist."), systemImage: "trash")
|
Label(NSLocalizedString("Delete", comment: "Button to remove a user from their blocklist."), systemImage: "trash")
|
||||||
@@ -43,7 +43,7 @@ struct MutelistView: View {
|
|||||||
RemoveAction(pubkey: pubkey)
|
RemoveAction(pubkey: pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(NSLocalizedString("Muted Users", comment: "Navigation title of view to see list of muted users."))
|
.navigationTitle(NSLocalizedString("Blocked Users", comment: "Navigation title of view to see list of blocked users."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,48 +28,49 @@ struct NoteContentView: View {
|
|||||||
let show_images: Bool
|
let show_images: Bool
|
||||||
let size: EventViewKind
|
let size: EventViewKind
|
||||||
let preview_height: CGFloat?
|
let preview_height: CGFloat?
|
||||||
let options: EventViewOptions
|
let truncate: Bool
|
||||||
let translatable: Bool
|
|
||||||
|
|
||||||
@State var artifacts: NoteArtifacts
|
@State var artifacts: NoteArtifacts
|
||||||
@State var preview: LinkViewRepresentable?
|
@State var preview: LinkViewRepresentable?
|
||||||
|
|
||||||
init(damus_state: DamusState, event: NostrEvent, show_images: Bool, size: EventViewKind, artifacts: NoteArtifacts, options: EventViewOptions) {
|
init(damus_state: DamusState, event: NostrEvent, show_images: Bool, size: EventViewKind, artifacts: NoteArtifacts, truncate: Bool) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
self.event = event
|
self.event = event
|
||||||
self.show_images = show_images
|
self.show_images = show_images
|
||||||
self.size = size
|
self.size = size
|
||||||
self.options = options
|
|
||||||
self.translatable = damus_state.translations.shouldTranslate(event, state: damus_state)
|
|
||||||
self._artifacts = State(initialValue: artifacts)
|
self._artifacts = State(initialValue: artifacts)
|
||||||
self.preview_height = lookup_cached_preview_size(previews: damus_state.previews, evid: event.id)
|
self.preview_height = lookup_cached_preview_size(previews: damus_state.previews, evid: event.id)
|
||||||
self._preview = State(initialValue: load_cached_preview(previews: damus_state.previews, evid: event.id))
|
self._preview = State(initialValue: load_cached_preview(previews: damus_state.previews, evid: event.id))
|
||||||
self._artifacts = State(initialValue: render_note_content(ev: event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey))
|
self._artifacts = State(initialValue: render_note_content(ev: event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey))
|
||||||
|
self.truncate = truncate
|
||||||
}
|
}
|
||||||
|
|
||||||
var truncate: Bool {
|
func MainContent() -> some View {
|
||||||
return options.contains(.truncate_content)
|
return VStack(alignment: .leading) {
|
||||||
}
|
|
||||||
|
if size == .selected {
|
||||||
var with_padding: Bool {
|
SelectableText(attributedString: artifacts.content)
|
||||||
return options.contains(.pad_content)
|
TranslateView(damus_state: damus_state, event: event)
|
||||||
}
|
} else {
|
||||||
|
TruncatedText(text: artifacts.content, maxChars: (truncate ? 280 : nil))
|
||||||
var truncatedText: some View {
|
.font(eventviewsize_to_font(size))
|
||||||
TruncatedText(text: artifacts.content, maxChars: (truncate ? 280 : nil))
|
}
|
||||||
.font(eventviewsize_to_font(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoicesView: some View {
|
|
||||||
InvoicesView(our_pubkey: damus_state.keypair.pubkey, invoices: artifacts.invoices)
|
|
||||||
}
|
|
||||||
|
|
||||||
var translateView: some View {
|
if show_images && artifacts.images.count > 0 {
|
||||||
TranslateView(damus_state: damus_state, event: event, size: self.size)
|
ImageCarousel(urls: artifacts.images)
|
||||||
}
|
} else if !show_images && artifacts.images.count > 0 {
|
||||||
|
ZStack {
|
||||||
var previewView: some View {
|
ImageCarousel(urls: artifacts.images)
|
||||||
Group {
|
Blur()
|
||||||
|
.disabled(true)
|
||||||
|
}
|
||||||
|
.cornerRadius(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
if artifacts.invoices.count > 0 {
|
||||||
|
InvoicesView(our_pubkey: damus_state.keypair.pubkey, invoices: artifacts.invoices)
|
||||||
|
}
|
||||||
|
|
||||||
if let preview = self.preview, show_images {
|
if let preview = self.preview, show_images {
|
||||||
if let preview_height {
|
if let preview_height {
|
||||||
preview
|
preview
|
||||||
@@ -84,64 +85,8 @@ struct NoteContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var MainContent: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
if size == .selected {
|
|
||||||
if with_padding {
|
|
||||||
SelectableText(attributedString: artifacts.content, size: self.size)
|
|
||||||
.padding(.horizontal)
|
|
||||||
} else {
|
|
||||||
SelectableText(attributedString: artifacts.content, size: self.size)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if with_padding {
|
|
||||||
truncatedText
|
|
||||||
.padding(.horizontal)
|
|
||||||
} else {
|
|
||||||
truncatedText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if translatable {
|
|
||||||
if with_padding {
|
|
||||||
translateView
|
|
||||||
.padding(.horizontal)
|
|
||||||
} else {
|
|
||||||
translateView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if show_images && artifacts.images.count > 0 {
|
|
||||||
ImageCarousel(previews: damus_state.previews, evid: event.id, urls: artifacts.images)
|
|
||||||
} else if !show_images && artifacts.images.count > 0 {
|
|
||||||
ZStack {
|
|
||||||
ImageCarousel(previews: damus_state.previews, evid: event.id, urls: artifacts.images)
|
|
||||||
Blur()
|
|
||||||
.disabled(true)
|
|
||||||
}
|
|
||||||
//.cornerRadius(10)
|
|
||||||
}
|
|
||||||
|
|
||||||
if artifacts.invoices.count > 0 {
|
|
||||||
if with_padding {
|
|
||||||
invoicesView
|
|
||||||
.padding(.horizontal)
|
|
||||||
} else {
|
|
||||||
invoicesView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if with_padding {
|
|
||||||
previewView.padding(.horizontal)
|
|
||||||
} else {
|
|
||||||
previewView
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
MainContent
|
MainContent()
|
||||||
.onReceive(handle_notify(.profile_updated)) { notif in
|
.onReceive(handle_notify(.profile_updated)) { notif in
|
||||||
let profile = notif.object as! ProfileUpdate
|
let profile = notif.object as! ProfileUpdate
|
||||||
let blocks = event.blocks(damus_state.keypair.privkey)
|
let blocks = event.blocks(damus_state.keypair.privkey)
|
||||||
@@ -197,14 +142,14 @@ struct NoteContentView: View {
|
|||||||
func hashtag_str(_ htag: String) -> AttributedString {
|
func hashtag_str(_ htag: String) -> AttributedString {
|
||||||
var attributedString = AttributedString(stringLiteral: "#\(htag)")
|
var attributedString = AttributedString(stringLiteral: "#\(htag)")
|
||||||
attributedString.link = URL(string: "damus:t:\(htag)")
|
attributedString.link = URL(string: "damus:t:\(htag)")
|
||||||
attributedString.foregroundColor = DamusColors.purple
|
attributedString.foregroundColor = Color("DamusPurple")
|
||||||
return attributedString
|
return attributedString
|
||||||
}
|
}
|
||||||
|
|
||||||
func url_str(_ url: URL) -> AttributedString {
|
func url_str(_ url: URL) -> AttributedString {
|
||||||
var attributedString = AttributedString(stringLiteral: url.absoluteString)
|
var attributedString = AttributedString(stringLiteral: url.absoluteString)
|
||||||
attributedString.link = url
|
attributedString.link = url
|
||||||
attributedString.foregroundColor = DamusColors.purple
|
attributedString.foregroundColor = Color("DamusPurple")
|
||||||
return attributedString
|
return attributedString
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,13 +161,13 @@ func mention_str(_ m: Mention, profiles: Profiles) -> AttributedString {
|
|||||||
let disp = Profile.displayName(profile: profile, pubkey: pk).username
|
let disp = Profile.displayName(profile: profile, pubkey: pk).username
|
||||||
var attributedString = AttributedString(stringLiteral: "@\(disp)")
|
var attributedString = AttributedString(stringLiteral: "@\(disp)")
|
||||||
attributedString.link = URL(string: "damus:\(encode_pubkey_uri(m.ref))")
|
attributedString.link = URL(string: "damus:\(encode_pubkey_uri(m.ref))")
|
||||||
attributedString.foregroundColor = DamusColors.purple
|
attributedString.foregroundColor = Color("DamusPurple")
|
||||||
return attributedString
|
return attributedString
|
||||||
case .event:
|
case .event:
|
||||||
let bevid = bech32_note_id(m.ref.ref_id) ?? m.ref.ref_id
|
let bevid = bech32_note_id(m.ref.ref_id) ?? m.ref.ref_id
|
||||||
var attributedString = AttributedString(stringLiteral: "@\(abbrev_pubkey(bevid))")
|
var attributedString = AttributedString(stringLiteral: "@\(abbrev_pubkey(bevid))")
|
||||||
attributedString.link = URL(string: "damus:\(encode_event_id_uri(m.ref))")
|
attributedString.link = URL(string: "damus:\(encode_event_id_uri(m.ref))")
|
||||||
attributedString.foregroundColor = DamusColors.purple
|
attributedString.foregroundColor = Color("DamusPurple")
|
||||||
return attributedString
|
return attributedString
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,7 +177,17 @@ struct NoteContentView_Previews: PreviewProvider {
|
|||||||
let state = test_damus_state()
|
let state = test_damus_state()
|
||||||
let content = "hi there ¯\\_(ツ)_/¯ https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg"
|
let content = "hi there ¯\\_(ツ)_/¯ https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg"
|
||||||
let artifacts = NoteArtifacts(content: AttributedString(stringLiteral: content), images: [], invoices: [], links: [])
|
let artifacts = NoteArtifacts(content: AttributedString(stringLiteral: content), images: [], invoices: [], links: [])
|
||||||
NoteContentView(damus_state: state, event: NostrEvent(content: content, pubkey: "pk"), show_images: true, size: .normal, artifacts: artifacts, options: [])
|
NoteContentView(damus_state: state, event: NostrEvent(content: content, pubkey: "pk"), show_images: true, size: .normal, artifacts: artifacts, truncate: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func translate_button_style() -> some View {
|
||||||
|
return self
|
||||||
|
.font(.footnote)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.padding([.top, .bottom], 10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,10 +217,7 @@ func render_blocks(blocks: [Block], profiles: Profiles, privkey: String?) -> Not
|
|||||||
.filter({ $0.is_note_mention })
|
.filter({ $0.is_note_mention })
|
||||||
.count == 1
|
.count == 1
|
||||||
|
|
||||||
var ind: Int = -1
|
|
||||||
let txt: AttributedString = blocks.reduce("") { str, block in
|
let txt: AttributedString = blocks.reduce("") { str, block in
|
||||||
ind = ind + 1
|
|
||||||
|
|
||||||
switch block {
|
switch block {
|
||||||
case .mention(let m):
|
case .mention(let m):
|
||||||
if m.type == .event && one_note_ref {
|
if m.type == .event && one_note_ref {
|
||||||
@@ -273,14 +225,7 @@ func render_blocks(blocks: [Block], profiles: Profiles, privkey: String?) -> Not
|
|||||||
}
|
}
|
||||||
return str + mention_str(m, profiles: profiles)
|
return str + mention_str(m, profiles: profiles)
|
||||||
case .text(let txt):
|
case .text(let txt):
|
||||||
var trimmed = txt
|
return str + AttributedString(stringLiteral: txt)
|
||||||
if let prev = blocks[safe: ind-1], case .url(let u) = prev, is_image_url(u) {
|
|
||||||
trimmed = " " + trim_prefix(trimmed)
|
|
||||||
}
|
|
||||||
if let next = blocks[safe: ind+1], case .url(let u) = next, is_image_url(u) {
|
|
||||||
trimmed = trim_suffix(trimmed)
|
|
||||||
}
|
|
||||||
return str + AttributedString(stringLiteral: trimmed)
|
|
||||||
case .hashtag(let htag):
|
case .hashtag(let htag):
|
||||||
return str + hashtag_str(htag)
|
return str + hashtag_str(htag)
|
||||||
case .invoice(let invoice):
|
case .invoice(let invoice):
|
||||||
@@ -358,14 +303,3 @@ struct TruncatedText: View {
|
|||||||
return AttributedString(truncatedAttributedString) + "..."
|
return AttributedString(truncatedAttributedString) + "..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// trim suffix whitespace and newlines
|
|
||||||
func trim_suffix(_ str: String) -> String {
|
|
||||||
return str.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression)
|
|
||||||
}
|
|
||||||
|
|
||||||
// trim prefix whitespace and newlines
|
|
||||||
func trim_prefix(_ str: String) -> String {
|
|
||||||
return str.replacingOccurrences(of: "^\\s+", with: "", options: .regularExpression)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func event_group_author_name(profiles: Profiles, ind: Int, group: EventGroupType
|
|||||||
}
|
}
|
||||||
|
|
||||||
if zap.is_anon {
|
if zap.is_anon {
|
||||||
return NSLocalizedString("Anonymous", comment: "Placeholder author name of the anonymous person who zapped an event.")
|
return "Anonymous"
|
||||||
}
|
}
|
||||||
|
|
||||||
return event_author_name(profiles: profiles, pubkey: zap.request.ev.pubkey)
|
return event_author_name(profiles: profiles, pubkey: zap.request.ev.pubkey)
|
||||||
@@ -184,12 +184,12 @@ struct EventGroupView: View {
|
|||||||
switch group {
|
switch group {
|
||||||
case .repost:
|
case .repost:
|
||||||
Image(systemName: "arrow.2.squarepath")
|
Image(systemName: "arrow.2.squarepath")
|
||||||
.foregroundColor(DamusColors.green)
|
.foregroundColor(Color("DamusGreen"))
|
||||||
case .reaction:
|
case .reaction:
|
||||||
LINEAR_GRADIENT
|
Image("shaka-full")
|
||||||
.mask(Image("shaka-full")
|
.resizable()
|
||||||
.resizable()
|
.frame(width: 24, height: 24)
|
||||||
).frame(width: 24, height: 24)
|
.foregroundColor(.accentColor)
|
||||||
case .profile_zap(let zapgrp):
|
case .profile_zap(let zapgrp):
|
||||||
ZapIcon(zapgrp)
|
ZapIcon(zapgrp)
|
||||||
case .zap(let zapgrp):
|
case .zap(let zapgrp):
|
||||||
@@ -206,21 +206,17 @@ struct EventGroupView: View {
|
|||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
ProfilePicturesView(state: state, events: group.events)
|
ProfilePicturesView(state: state, events: group.events)
|
||||||
|
|
||||||
|
GroupDescription
|
||||||
|
|
||||||
if let event {
|
if let event {
|
||||||
let thread = ThreadModel(event: event, damus_state: state)
|
let thread = ThreadModel(event: event, damus_state: state)
|
||||||
let dest = ThreadView(state: state, thread: thread)
|
let dest = ThreadView(state: state, thread: thread)
|
||||||
NavigationLink(destination: dest) {
|
NavigationLink(destination: dest) {
|
||||||
VStack(alignment: .leading) {
|
Text(render_note_content(ev: event, profiles: state.profiles, privkey: state.keypair.privkey).content)
|
||||||
GroupDescription
|
.padding([.top], 1)
|
||||||
EventBody(damus_state: state, event: event, size: .normal, options: [.truncate_content])
|
.foregroundColor(.gray)
|
||||||
.padding([.top], 1)
|
|
||||||
.padding([.trailing])
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
} else {
|
|
||||||
GroupDescription
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,6 @@ struct NotificationItemView: View {
|
|||||||
notification_item_event(events: state.events, notif: item)
|
notification_item_event(events: state.events, notif: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
var options: EventViewOptions {
|
|
||||||
if state.settings.truncate_mention_text {
|
|
||||||
return [.wide, .truncate_content]
|
|
||||||
}
|
|
||||||
|
|
||||||
return [.wide]
|
|
||||||
}
|
|
||||||
|
|
||||||
func Item(_ ev: NostrEvent?) -> some View {
|
func Item(_ ev: NostrEvent?) -> some View {
|
||||||
Group {
|
Group {
|
||||||
switch item {
|
switch item {
|
||||||
@@ -60,12 +52,13 @@ struct NotificationItemView: View {
|
|||||||
|
|
||||||
case .reply(let ev):
|
case .reply(let ev):
|
||||||
NavigationLink(destination: ThreadView(state: state, thread: ThreadModel(event: ev, damus_state: state))) {
|
NavigationLink(destination: ThreadView(state: state, thread: ThreadModel(event: ev, damus_state: state))) {
|
||||||
EventView(damus: state, event: ev, options: options)
|
EventView(damus: state, event: ev)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
|
|
||||||
ThiccDivider()
|
Divider()
|
||||||
|
.padding([.top,.bottom], 5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ struct NotificationsView: View {
|
|||||||
}
|
}
|
||||||
return Color.clear
|
return Color.clear
|
||||||
})
|
})
|
||||||
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
.coordinateSpace(name: "scroll")
|
.coordinateSpace(name: "scroll")
|
||||||
.onReceive(handle_notify(.scroll_to_top)) { notif in
|
.onReceive(handle_notify(.scroll_to_top)) { notif in
|
||||||
|
|||||||
@@ -16,34 +16,23 @@ struct ParticipantsView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Text("Replying to", comment: "Text indicating that the view is used for editing which participants are replied to in a note.")
|
Text("Edit participants", comment: "Text indicating that the view is used for editing which participants are replied to in a note.")
|
||||||
.font(.headline)
|
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// Remove all "p" refs, keep "e" refs
|
// Remove all "p" refs, keep "e" refs
|
||||||
references = originalReferences.eRefs
|
references = originalReferences.eRefs
|
||||||
} label: {
|
} label: {
|
||||||
Text("Remove all", comment: "Button label to remove all participants from a note reply.")
|
Text("Remove all", comment: "Button label to remove all participants from a note reply.")
|
||||||
}
|
}
|
||||||
.font(.system(size: 14, weight: .bold))
|
.buttonStyle(.borderedProminent)
|
||||||
.frame(width: 100, height: 30)
|
Spacer()
|
||||||
.foregroundColor(.white)
|
|
||||||
.background(LINEAR_GRADIENT)
|
|
||||||
.clipShape(Capsule())
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
references = originalReferences
|
references = originalReferences
|
||||||
} label: {
|
} label: {
|
||||||
Text("Add all", comment: "Button label to re-add all original participants as profiles to reply to in a note")
|
Text("Add all", comment: "Button label to re-add all original participants as profiles to reply to in a note")
|
||||||
}
|
}
|
||||||
.font(.system(size: 14, weight: .bold))
|
.buttonStyle(.borderedProminent)
|
||||||
.frame(width: 80, height: 30)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.background(LINEAR_GRADIENT)
|
|
||||||
.clipShape(Capsule())
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
VStack {
|
VStack {
|
||||||
@@ -67,7 +56,7 @@ struct ParticipantsView: View {
|
|||||||
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.font(.system(size: 30))
|
.font(.system(size: 30))
|
||||||
.foregroundColor(references.contains(participant) ? DamusColors.purple : .gray)
|
.foregroundColor(references.contains(participant) ? .purple : .gray)
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if references.contains(participant) {
|
if references.contains(participant) {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import SwiftUI
|
|||||||
|
|
||||||
let BUTTON_SIZE = 57.0
|
let BUTTON_SIZE = 57.0
|
||||||
let LINEAR_GRADIENT = LinearGradient(gradient: Gradient(colors: [
|
let LINEAR_GRADIENT = LinearGradient(gradient: Gradient(colors: [
|
||||||
DamusColors.purple,
|
Color("DamusPurple"),
|
||||||
DamusColors.blue
|
Color("DamusBlue")
|
||||||
]), startPoint: .topTrailing, endPoint: .bottomTrailing)
|
]), startPoint: .topTrailing, endPoint: .bottomTrailing)
|
||||||
|
|
||||||
func PostButton(action: @escaping () -> ()) -> some View {
|
func PostButton(action: @escaping () -> ()) -> some View {
|
||||||
|
|||||||
+74
-114
@@ -16,18 +16,16 @@ let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Tex
|
|||||||
|
|
||||||
struct PostView: View {
|
struct PostView: View {
|
||||||
@State var post: NSMutableAttributedString = NSMutableAttributedString()
|
@State var post: NSMutableAttributedString = NSMutableAttributedString()
|
||||||
|
@State var cursor: Int = 0
|
||||||
@FocusState var focus: Bool
|
@FocusState var focus: Bool
|
||||||
@State var showPrivateKeyWarning: Bool = false
|
@State var showPrivateKeyWarning: Bool = false
|
||||||
@State var attach_media: Bool = false
|
@State var attach_media: Bool = false
|
||||||
@State var attach_camera: Bool = false
|
|
||||||
@State var error: String? = nil
|
@State var error: String? = nil
|
||||||
|
|
||||||
@State var originalReferences: [ReferencedId] = []
|
|
||||||
@State var references: [ReferencedId] = []
|
|
||||||
|
|
||||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
||||||
|
|
||||||
let replying_to: NostrEvent?
|
let replying_to: NostrEvent?
|
||||||
|
let references: [ReferencedId]
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
@@ -80,25 +78,14 @@ struct PostView: View {
|
|||||||
attach_media = true
|
attach_media = true
|
||||||
}, label: {
|
}, label: {
|
||||||
Image(systemName: "photo")
|
Image(systemName: "photo")
|
||||||
.padding(6)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var CameraButton: some View {
|
|
||||||
Button(action: {
|
|
||||||
attach_camera = true
|
|
||||||
}, label: {
|
|
||||||
Image(systemName: "camera")
|
|
||||||
.padding(6)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var AttachmentBar: some View {
|
var AttachmentBar: some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ImageButton
|
ImageButton
|
||||||
CameraButton
|
.disabled(image_upload.progress != nil)
|
||||||
}
|
}
|
||||||
.disabled(image_upload.progress != nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var PostButton: some View {
|
var PostButton: some View {
|
||||||
@@ -109,18 +96,16 @@ struct PostView: View {
|
|||||||
self.send_post()
|
self.send_post()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disabled(is_post_empty)
|
|
||||||
.font(.system(size: 14, weight: .bold))
|
.font(.system(size: 14, weight: .bold))
|
||||||
.frame(width: 80, height: 30)
|
.frame(width: 80, height: 30)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.background(LINEAR_GRADIENT)
|
.background(LINEAR_GRADIENT)
|
||||||
.opacity(is_post_empty ? 0.5 : 1.0)
|
|
||||||
.clipShape(Capsule())
|
.clipShape(Capsule())
|
||||||
}
|
}
|
||||||
|
|
||||||
var TextEntry: some View {
|
var TextEntry: some View {
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
TextViewWrapper(attributedText: $post)
|
TextViewWrapper(attributedText: $post, cursor: $cursor)
|
||||||
.focused($focus)
|
.focused($focus)
|
||||||
.textInputAutocapitalization(.sentences)
|
.textInputAutocapitalization(.sentences)
|
||||||
.onChange(of: post) { _ in
|
.onChange(of: post) { _ in
|
||||||
@@ -156,7 +141,9 @@ struct PostView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
PostButton
|
if !is_post_empty {
|
||||||
|
PostButton
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let progress = image_upload.progress {
|
if let progress = image_upload.progress {
|
||||||
@@ -165,7 +152,7 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.padding([.bottom], 10)
|
.padding([.top, .bottom], 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func append_url(_ url: String) {
|
func append_url(_ url: String) {
|
||||||
@@ -182,11 +169,11 @@ struct PostView: View {
|
|||||||
post = combinedAttributedString
|
post = combinedAttributedString
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_upload(media: MediaUpload) {
|
func handle_upload(image: UIImage) {
|
||||||
let uploader = get_media_uploader(damus_state.pubkey)
|
let uploader = get_image_uploader(damus_state.pubkey)
|
||||||
|
|
||||||
Task.init {
|
Task.init {
|
||||||
let res = await image_upload.start(media: media, uploader: uploader)
|
let res = await image_upload.start(img: image, uploader: uploader)
|
||||||
|
|
||||||
switch res {
|
switch res {
|
||||||
case .success(let url):
|
case .success(let url):
|
||||||
@@ -204,102 +191,75 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { (deviceSize: GeometryProxy) in
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
let searching = get_searching_string(post.string, cursor: cursor)
|
||||||
|
|
||||||
|
TopBar
|
||||||
|
|
||||||
|
HStack(alignment: .top) {
|
||||||
|
ProfilePicView(pubkey: damus_state.pubkey, size: 45.0, highlight: .none, profiles: damus_state.profiles)
|
||||||
|
|
||||||
let searching = get_searching_string(post.string)
|
TextEntry
|
||||||
|
}
|
||||||
|
.frame(maxHeight: searching == nil ? .infinity : 50)
|
||||||
|
|
||||||
|
// This if-block observes @ for tagging
|
||||||
|
if let searching {
|
||||||
|
UserSearch(damus_state: damus_state, search: searching, post: $post, cursor: $cursor)
|
||||||
|
.frame(maxHeight: .infinity)
|
||||||
|
} else {
|
||||||
|
Divider()
|
||||||
|
.padding([.bottom], 10)
|
||||||
|
|
||||||
TopBar
|
AttachmentBar
|
||||||
|
|
||||||
ScrollViewReader { scroller in
|
|
||||||
ScrollView {
|
|
||||||
if let replying_to = replying_to {
|
|
||||||
ReplyView(replying_to: replying_to, damus: damus_state, originalReferences: $originalReferences, references: $references)
|
|
||||||
}
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
|
||||||
.padding(.leading, replying_to != nil ? 15 : 0)
|
|
||||||
|
|
||||||
TextEntry
|
|
||||||
}
|
|
||||||
.frame(height: deviceSize.size.height*0.78)
|
|
||||||
.id("post")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxHeight: searching == nil ? .infinity : 70)
|
|
||||||
.onAppear {
|
|
||||||
scroll_to_event(scroller: scroller, id: "post", delay: 1.0, animate: true, anchor: .top)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This if-block observes @ for tagging
|
|
||||||
if let searching {
|
|
||||||
UserSearch(damus_state: damus_state, search: searching, post: $post)
|
|
||||||
.padding(.leading, replying_to != nil ? 15 : 0)
|
|
||||||
.frame(maxHeight: .infinity)
|
|
||||||
} else {
|
|
||||||
Divider()
|
|
||||||
.padding([.bottom], 10)
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
AttachmentBar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
.sheet(isPresented: $attach_media) {
|
|
||||||
ImagePicker(sourceType: .photoLibrary, pubkey: damus_state.pubkey) { img in
|
|
||||||
handle_upload(media: .image(img))
|
|
||||||
} onVideoPicked: { url in
|
|
||||||
handle_upload(media: .video(url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $attach_camera) {
|
|
||||||
ImagePicker(sourceType: .camera, pubkey: damus_state.pubkey) { img in
|
|
||||||
handle_upload(media: .image(img))
|
|
||||||
} onVideoPicked: { url in
|
|
||||||
handle_upload(media: .video(url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear() {
|
|
||||||
if let replying_to {
|
|
||||||
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
|
|
||||||
originalReferences = references
|
|
||||||
if damus_state.drafts.replies[replying_to] == nil {
|
|
||||||
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
|
||||||
}
|
|
||||||
if let p = damus_state.drafts.replies[replying_to] {
|
|
||||||
post = p
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
post = damus_state.drafts.post
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
||||||
self.focus = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onDisappear {
|
|
||||||
if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
||||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
|
||||||
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
||||||
damus_state.drafts.post = NSMutableAttributedString(string : "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
|
|
||||||
Button(NSLocalizedString("No", comment: "Button to cancel out of posting a note after being alerted that it looks like they might be posting a private key."), role: .cancel) {
|
|
||||||
showPrivateKeyWarning = false
|
|
||||||
}
|
|
||||||
Button(NSLocalizedString("Yes, Post with Private Key", comment: "Button to proceed with posting a note even though it looks like they might be posting a private key."), role: .destructive) {
|
|
||||||
self.send_post()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
.padding()
|
||||||
|
.sheet(isPresented: $attach_media) {
|
||||||
|
ImagePicker(sourceType: .photoLibrary) { img in
|
||||||
|
handle_upload(image: img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear() {
|
||||||
|
if let replying_to {
|
||||||
|
if damus_state.drafts.replies[replying_to] == nil {
|
||||||
|
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
||||||
|
}
|
||||||
|
if let p = damus_state.drafts.replies[replying_to] {
|
||||||
|
post = p
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
post = damus_state.drafts.post
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
self.focus = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
||||||
|
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
damus_state.drafts.post = NSMutableAttributedString(string : "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
|
||||||
|
Button(NSLocalizedString("No", comment: "Button to cancel out of posting a note after being alerted that it looks like they might be posting a private key."), role: .cancel) {
|
||||||
|
showPrivateKeyWarning = false
|
||||||
|
}
|
||||||
|
Button(NSLocalizedString("Yes, Post with Private Key", comment: "Button to proceed with posting a note even though it looks like they might be posting a private key."), role: .destructive) {
|
||||||
|
self.send_post()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_searching_string(_ post: String) -> String? {
|
func get_searching_string(_ post: String, cursor: Int) -> String? {
|
||||||
guard let last_word = post.components(separatedBy: .whitespacesAndNewlines).last else {
|
guard cursor > 0 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let last_word = post[...post.index(post.startIndex, offsetBy: cursor - 1)].components(separatedBy: .whitespacesAndNewlines).last else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +281,6 @@ func get_searching_string(_ post: String) -> String? {
|
|||||||
|
|
||||||
struct PostView_Previews: PreviewProvider {
|
struct PostView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PostView(replying_to: nil, damus_state: test_damus_state())
|
PostView(replying_to: nil, references: [], damus_state: test_damus_state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ struct UserSearch: View {
|
|||||||
let search: String
|
let search: String
|
||||||
|
|
||||||
@Binding var post: NSMutableAttributedString
|
@Binding var post: NSMutableAttributedString
|
||||||
|
@Binding var cursor: Int
|
||||||
|
|
||||||
var users: [SearchedUser] {
|
var users: [SearchedUser] {
|
||||||
guard let contacts = damus_state.contacts.event else {
|
guard let contacts = damus_state.contacts.event else {
|
||||||
@@ -36,54 +37,68 @@ struct UserSearch: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all characters after the last '@'
|
// Remove all characters after the '@' and before the cursor
|
||||||
removeCharactersAfterLastAtSymbol()
|
let newCursor = removeCharactersAfterAtSymbol()
|
||||||
|
|
||||||
// Create and append the user tag
|
// Create and append the user tag
|
||||||
let tagAttributedString = createUserTag(for: user, with: pk)
|
let tagAttributedString = createUserTag(for: user, with: pk)
|
||||||
appendUserTag(tagAttributedString)
|
insertUserTag(tagAttributedString, cursor: newCursor)
|
||||||
|
|
||||||
|
cursor = newCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
private func removeCharactersAfterLastAtSymbol() {
|
private func removeCharactersAfterAtSymbol() -> Int {
|
||||||
while post.string.last != "@" {
|
let newCursor = cursor
|
||||||
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
|
|
||||||
|
guard newCursor > 0 else {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
|
|
||||||
|
var atSymbolOffset = newCursor
|
||||||
|
while atSymbolOffset > 0 && post.string[post.string.index(post.string.startIndex, offsetBy: atSymbolOffset - 1)] != "@" {
|
||||||
|
atSymbolOffset -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var endOfWordOffset = newCursor
|
||||||
|
while endOfWordOffset < post.string.count && !post.string[post.string.index(post.string.startIndex, offsetBy: endOfWordOffset)].isWhitespace {
|
||||||
|
endOfWordOffset += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post.deleteCharacters(in: NSRange(location: atSymbolOffset - 1, length: endOfWordOffset - atSymbolOffset + 1))
|
||||||
|
|
||||||
|
return atSymbolOffset - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createUserTag(for user: SearchedUser, with pk: String) -> NSMutableAttributedString {
|
private func createUserTag(for user: SearchedUser, with pk: String) -> NSMutableAttributedString {
|
||||||
let name = Profile.displayName(profile: user.profile, pubkey: pk).username
|
let name = Profile.displayName(profile: user.profile, pubkey: pk).username
|
||||||
let tagString = "@\(name)\u{200B} "
|
let tagString = "\u{200B}@\(name)\u{200B} "
|
||||||
|
|
||||||
let tagAttributedString = NSMutableAttributedString(string: tagString,
|
let tagAttributedString = NSMutableAttributedString(string: tagString,
|
||||||
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
||||||
NSAttributedString.Key.link: "@\(pk)"])
|
NSAttributedString.Key.link: "@\(pk)"])
|
||||||
|
tagAttributedString.removeAttribute(.link, range: NSRange(location: 0, length: 1))
|
||||||
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||||
|
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: 0, length: 1))
|
||||||
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||||
|
|
||||||
return tagAttributedString
|
return tagAttributedString
|
||||||
}
|
}
|
||||||
|
|
||||||
private func appendUserTag(_ tagAttributedString: NSMutableAttributedString) {
|
private func insertUserTag(_ tagAttributedString: NSMutableAttributedString, cursor: Int) {
|
||||||
let mutableString = NSMutableAttributedString()
|
let mutableString = NSMutableAttributedString()
|
||||||
mutableString.append(post)
|
mutableString.append(post)
|
||||||
mutableString.append(tagAttributedString)
|
mutableString.insert(tagAttributedString, at: cursor)
|
||||||
post = mutableString
|
post = mutableString
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack {
|
LazyVStack {
|
||||||
Divider()
|
ForEach(users) { user in
|
||||||
if users.count == 0 {
|
UserView(damus_state: damus_state, pubkey: user.pubkey)
|
||||||
EmptyUserSearchView()
|
.onTapGesture {
|
||||||
} else {
|
on_user_tapped(user: user)
|
||||||
ForEach(users) { user in
|
}
|
||||||
UserView(damus_state: damus_state, pubkey: user.pubkey)
|
|
||||||
.onTapGesture {
|
|
||||||
on_user_tapped(user: user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,9 +108,10 @@ struct UserSearch: View {
|
|||||||
struct UserSearch_Previews: PreviewProvider {
|
struct UserSearch_Previews: PreviewProvider {
|
||||||
static let search: String = "jb55"
|
static let search: String = "jb55"
|
||||||
@State static var post: NSMutableAttributedString = NSMutableAttributedString(string: "some @jb55")
|
@State static var post: NSMutableAttributedString = NSMutableAttributedString(string: "some @jb55")
|
||||||
|
@State static var cursor: Int = 0
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
UserSearch(damus_state: test_damus_state(), search: search, post: $post)
|
UserSearch(damus_state: test_damus_state(), search: search, post: $post, cursor: $cursor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
//
|
|
||||||
// ProfilePictureEditView.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Joel Klabo on 3/30/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct EditProfilePictureControl: View {
|
|
||||||
|
|
||||||
let pubkey: String
|
|
||||||
@Binding var profile_image: URL?
|
|
||||||
@ObservedObject var viewModel: ProfileUploadingViewModel
|
|
||||||
let callback: (URL?) -> Void
|
|
||||||
|
|
||||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
|
||||||
|
|
||||||
@State private var show_camera = false
|
|
||||||
@State private var show_library = false
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Menu {
|
|
||||||
Button(action: {
|
|
||||||
self.show_library = true
|
|
||||||
}) {
|
|
||||||
Text("Choose from Library", comment: "Option to select photo from library")
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
self.show_camera = true
|
|
||||||
}) {
|
|
||||||
Text("Take Photo", comment: "Option to take a photo with the camera")
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
if viewModel.isLoading {
|
|
||||||
ProgressView()
|
|
||||||
} else {
|
|
||||||
Image(systemName: "camera")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 25, height: 25)
|
|
||||||
.foregroundColor(DamusColors.white)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $show_camera) {
|
|
||||||
ImagePicker(sourceType: .camera, pubkey: pubkey, imagesOnly: true) { img in
|
|
||||||
handle_upload(media: .image(img))
|
|
||||||
} onVideoPicked: { url in
|
|
||||||
print("Cannot upload videos as profile image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $show_library) {
|
|
||||||
ImagePicker(sourceType: .photoLibrary, pubkey: pubkey, imagesOnly: true) { img in
|
|
||||||
handle_upload(media: .image(img))
|
|
||||||
} onVideoPicked: { url in
|
|
||||||
print("Cannot upload videos as profile image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handle_upload(media: MediaUpload) {
|
|
||||||
viewModel.isLoading = true
|
|
||||||
let uploader = get_media_uploader(pubkey)
|
|
||||||
Task {
|
|
||||||
let res = await image_upload.start(media: media, uploader: uploader)
|
|
||||||
|
|
||||||
switch res {
|
|
||||||
case .success(let urlString):
|
|
||||||
let url = URL(string: urlString)
|
|
||||||
profile_image = url
|
|
||||||
callback(url)
|
|
||||||
case .failed(let error):
|
|
||||||
if let error {
|
|
||||||
print("Error uploading profile image \(error.localizedDescription)")
|
|
||||||
} else {
|
|
||||||
print("Error uploading image :(")
|
|
||||||
}
|
|
||||||
callback(nil)
|
|
||||||
}
|
|
||||||
viewModel.isLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,11 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct FollowsYou: View {
|
struct FollowsYou: View {
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
|
var fill_color: Color {
|
||||||
|
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text("Follows you", comment: "Text to indicate that a user is following your profile.")
|
Text("Follows you", comment: "Text to indicate that a user is following your profile.")
|
||||||
@@ -16,7 +21,7 @@ struct FollowsYou: View {
|
|||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
.background {
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 5.0)
|
RoundedRectangle(cornerRadius: 5.0)
|
||||||
.foregroundColor(DamusColors.adaptableGrey)
|
.foregroundColor(fill_color)
|
||||||
}
|
}
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,11 @@ struct ProfileName: View {
|
|||||||
var current_nip05: NIP05? {
|
var current_nip05: NIP05? {
|
||||||
nip05 ?? damus_state.profiles.is_validated(pubkey)
|
nip05 ?? damus_state.profiles.is_validated(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nip05_color: Color {
|
||||||
|
return get_nip05_color(pubkey: pubkey, contacts: damus_state.contacts)
|
||||||
|
}
|
||||||
|
|
||||||
var current_display_name: DisplayName {
|
var current_display_name: DisplayName {
|
||||||
return display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)
|
return display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,60 +32,6 @@ func pfp_line_width(_ h: Highlight) -> CGFloat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EditProfilePictureView: View {
|
|
||||||
|
|
||||||
@Binding var url: URL?
|
|
||||||
|
|
||||||
let pubkey: String
|
|
||||||
let size: CGFloat
|
|
||||||
let highlight: Highlight
|
|
||||||
|
|
||||||
var damus_state: DamusState?
|
|
||||||
|
|
||||||
var PlaceholderColor: Color {
|
|
||||||
return id_to_color(pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
var Placeholder: some View {
|
|
||||||
PlaceholderColor
|
|
||||||
.frame(width: size, height: size)
|
|
||||||
.clipShape(Circle())
|
|
||||||
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
|
|
||||||
.padding(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ZStack {
|
|
||||||
Color(uiColor: .systemBackground)
|
|
||||||
|
|
||||||
KFAnimatedImage(get_profile_url())
|
|
||||||
.imageContext(.pfp)
|
|
||||||
.cancelOnDisappear(true)
|
|
||||||
.configure { view in
|
|
||||||
view.framePreloadCount = 3
|
|
||||||
}
|
|
||||||
.placeholder { _ in
|
|
||||||
Placeholder
|
|
||||||
}
|
|
||||||
.scaledToFill()
|
|
||||||
.opacity(0.5)
|
|
||||||
}
|
|
||||||
.frame(width: size, height: size)
|
|
||||||
.clipShape(Circle())
|
|
||||||
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func get_profile_url() -> URL? {
|
|
||||||
if let url {
|
|
||||||
return url
|
|
||||||
} else if let state = damus_state, let picture = state.profiles.lookup(id: pubkey)?.picture {
|
|
||||||
return URL(string: picture)
|
|
||||||
} else {
|
|
||||||
return url ?? URL(string: robohash(pubkey))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InnerProfilePicView: View {
|
struct InnerProfilePicView: View {
|
||||||
|
|
||||||
let url: URL?
|
let url: URL?
|
||||||
@@ -172,7 +118,7 @@ func make_preview_profiles(_ pubkey: String) -> Profiles {
|
|||||||
let profiles = Profiles()
|
let profiles = Profiles()
|
||||||
let picture = "http://cdn.jb55.com/img/red-me.jpg"
|
let picture = "http://cdn.jb55.com/img/red-me.jpg"
|
||||||
let profile = Profile(name: "jb55", display_name: "William Casarin", about: "It's me", picture: picture, banner: "", website: "https://jb55.com", lud06: nil, lud16: nil, nip05: "jb55.com")
|
let profile = Profile(name: "jb55", display_name: "William Casarin", about: "It's me", picture: picture, banner: "", website: "https://jb55.com", lud06: nil, lud16: nil, nip05: "jb55.com")
|
||||||
let ts_profile = TimestampedProfile(profile: profile, timestamp: 0, event: test_event)
|
let ts_profile = TimestampedProfile(profile: profile, timestamp: 0)
|
||||||
profiles.add(id: pubkey, profile: ts_profile)
|
profiles.add(id: pubkey, profile: ts_profile)
|
||||||
return profiles
|
return profiles
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,27 +7,13 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
import Combine
|
|
||||||
|
|
||||||
class ProfileUploadingViewModel: ObservableObject {
|
|
||||||
@Published var isLoading: Bool = false
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ProfilePictureSelector: View {
|
struct ProfilePictureSelector: View {
|
||||||
|
|
||||||
let pubkey: String
|
let pubkey: String
|
||||||
var size: CGFloat = 80.0
|
|
||||||
var damus_state: DamusState?
|
|
||||||
@ObservedObject var viewModel: ProfileUploadingViewModel
|
|
||||||
let callback: (URL?) -> Void
|
|
||||||
|
|
||||||
@State var profile_image: URL? = nil
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let highlight: Highlight = .custom(Color.white, 2.0)
|
let highlight: Highlight = .custom(Color.white, 2.0)
|
||||||
ZStack {
|
ZStack {
|
||||||
EditProfilePictureView(url: $profile_image, pubkey: pubkey, size: size, highlight: highlight, damus_state: damus_state)
|
ProfilePicView(pubkey: pubkey, size: 80.0, highlight: highlight, profiles: Profiles())
|
||||||
EditProfilePictureControl(pubkey: pubkey, profile_image: $profile_image, viewModel: viewModel, callback: callback)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,8 +21,6 @@ struct ProfilePictureSelector: View {
|
|||||||
struct ProfilePictureSelector_Previews: PreviewProvider {
|
struct ProfilePictureSelector_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let test_pubkey = "ff48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846"
|
let test_pubkey = "ff48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846"
|
||||||
ProfilePictureSelector(pubkey: test_pubkey, viewModel: ProfileUploadingViewModel()) { _ in
|
ProfilePictureSelector(pubkey: test_pubkey)
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,11 +87,11 @@ struct EditButton: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fillColor() -> Color {
|
func fillColor() -> Color {
|
||||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||||
}
|
}
|
||||||
|
|
||||||
func borderColor() -> Color {
|
func borderColor() -> Color {
|
||||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,8 +143,12 @@ struct ProfileView: View {
|
|||||||
@Environment(\.openURL) var openURL
|
@Environment(\.openURL) var openURL
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
|
func fillColor() -> Color {
|
||||||
|
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
||||||
|
}
|
||||||
|
|
||||||
func imageBorderColor() -> Color {
|
func imageBorderColor() -> Color {
|
||||||
colorScheme == .light ? DamusColors.white : DamusColors.black
|
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
|
||||||
}
|
}
|
||||||
|
|
||||||
func bannerBlurViewOpacity() -> Double {
|
func bannerBlurViewOpacity() -> Double {
|
||||||
@@ -221,8 +225,8 @@ struct ProfileView: View {
|
|||||||
notify(.report, target)
|
notify(.report, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(NSLocalizedString("Mute", comment: "Button to mute a profile."), role: .destructive) {
|
Button(NSLocalizedString("Block", comment: "Button to block a profile."), role: .destructive) {
|
||||||
notify(.mute, profile.pubkey)
|
notify(.block, profile.pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,7 +240,7 @@ struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
.padding(.top, 5)
|
.padding(.top, 5)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.accentColor(DamusColors.white)
|
.accentColor(Color("DamusWhite"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func lnButton(lnurl: String, profile: Profile) -> some View {
|
func lnButton(lnurl: String, profile: Profile) -> some View {
|
||||||
@@ -323,7 +327,7 @@ struct ProfileView: View {
|
|||||||
is_zoomed.toggle()
|
is_zoomed.toggle()
|
||||||
}
|
}
|
||||||
.fullScreenCover(isPresented: $is_zoomed) {
|
.fullScreenCover(isPresented: $is_zoomed) {
|
||||||
ProfilePicImageView(pubkey: profile.pubkey, profiles: damus_state.profiles) }
|
ProfileZoomView(pubkey: profile.pubkey, profiles: damus_state.profiles) }
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
@@ -480,7 +484,7 @@ func test_damus_state() -> DamusState {
|
|||||||
let damus = DamusState.empty
|
let damus = DamusState.empty
|
||||||
|
|
||||||
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io")
|
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io")
|
||||||
let tsprof = TimestampedProfile(profile: prof, timestamp: 0, event: test_event)
|
let tsprof = TimestampedProfile(profile: prof, timestamp: 0)
|
||||||
damus.profiles.add(id: pubkey, profile: tsprof)
|
damus.profiles.add(id: pubkey, profile: tsprof)
|
||||||
return damus
|
return damus
|
||||||
}
|
}
|
||||||
@@ -492,8 +496,12 @@ struct KeyView: View {
|
|||||||
|
|
||||||
@State private var isCopied = false
|
@State private var isCopied = false
|
||||||
|
|
||||||
|
func fillColor() -> Color {
|
||||||
|
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
||||||
|
}
|
||||||
|
|
||||||
func keyColor() -> Color {
|
func keyColor() -> Color {
|
||||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func copyPubkey(_ pubkey: String) {
|
private func copyPubkey(_ pubkey: String) {
|
||||||
@@ -530,7 +538,7 @@ struct KeyView: View {
|
|||||||
}
|
}
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.padding([.leading, .trailing], 3)
|
.padding([.leading, .trailing], 3)
|
||||||
.background(RoundedRectangle(cornerRadius: 11).foregroundColor(DamusColors.adaptableGrey))
|
.background(RoundedRectangle(cornerRadius: 11).foregroundColor(fillColor()))
|
||||||
|
|
||||||
if isCopied != true {
|
if isCopied != true {
|
||||||
Button {
|
Button {
|
||||||
@@ -541,7 +549,7 @@ struct KeyView: View {
|
|||||||
} icon: {
|
} icon: {
|
||||||
Image(systemName: "square.on.square.dashed")
|
Image(systemName: "square.on.square.dashed")
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.gray)
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
}
|
}
|
||||||
.labelStyle(IconOnlyLabelStyle())
|
.labelStyle(IconOnlyLabelStyle())
|
||||||
@@ -555,7 +563,7 @@ struct KeyView: View {
|
|||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.layoutPriority(1)
|
.layoutPriority(1)
|
||||||
}
|
}
|
||||||
.foregroundColor(DamusColors.green)
|
.foregroundColor(Color("DamusGreen"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,11 +39,14 @@ struct ProfileImageContainerView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NavDismissBarView: View {
|
struct ProfileZoomView: View {
|
||||||
|
|
||||||
|
let pubkey: String
|
||||||
|
let profiles: Profiles
|
||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
var body: some View {
|
var navBarView: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
presentationMode.wrappedValue.dismiss()
|
presentationMode.wrappedValue.dismiss()
|
||||||
@@ -58,14 +61,6 @@ struct NavDismissBarView: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
struct ProfilePicImageView: View {
|
|
||||||
|
|
||||||
let pubkey: String
|
|
||||||
let profiles: Profiles
|
|
||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -84,7 +79,7 @@ struct ProfilePicImageView: View {
|
|||||||
presentationMode.wrappedValue.dismiss()
|
presentationMode.wrappedValue.dismiss()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
.overlay(NavDismissBarView(), alignment: .top)
|
.overlay(navBarView, alignment: .top)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +87,7 @@ struct ProfileZoomView_Previews: PreviewProvider {
|
|||||||
static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846"
|
static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846"
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ProfilePicImageView(
|
ProfileZoomView(
|
||||||
pubkey: pubkey,
|
pubkey: pubkey,
|
||||||
profiles: make_preview_profiles(pubkey))
|
profiles: make_preview_profiles(pubkey))
|
||||||
}
|
}
|
||||||
@@ -42,23 +42,23 @@ struct QRCodeView: View {
|
|||||||
let profile = damus_state.profiles.lookup(id: damus_state.pubkey)
|
let profile = damus_state.profiles.lookup(id: damus_state.pubkey)
|
||||||
|
|
||||||
if (damus_state.profiles.lookup(id: damus_state.pubkey)?.picture) != nil {
|
if (damus_state.profiles.lookup(id: damus_state.pubkey)?.picture) != nil {
|
||||||
ProfilePicView(pubkey: damus_state.pubkey, size: 90.0, highlight: .custom(DamusColors.white, 4.0), profiles: damus_state.profiles)
|
ProfilePicView(pubkey: damus_state.pubkey, size: 90.0, highlight: .custom(Color("DamusWhite"), 4.0), profiles: damus_state.profiles)
|
||||||
.padding(.top, 50)
|
.padding(.top, 50)
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "person.fill")
|
Image(systemName: "person.fill")
|
||||||
.font(.system(size: 60))
|
.font(.system(size: 60))
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(Color("DamusWhite"))
|
||||||
.padding(.top, 50)
|
.padding(.top, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let display_name = profile?.display_name {
|
if let display_name = profile?.display_name {
|
||||||
Text(display_name)
|
Text(display_name)
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(Color("DamusWhite"))
|
||||||
.font(.system(size: 24, weight: .heavy))
|
.font(.system(size: 24, weight: .heavy))
|
||||||
}
|
}
|
||||||
if let name = profile?.name {
|
if let name = profile?.name {
|
||||||
Text("@" + name)
|
Text("@" + name)
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(Color("DamusWhite"))
|
||||||
.font(.body)
|
.font(.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,19 +73,19 @@ struct QRCodeView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(RoundedRectangle(cornerRadius: 10)
|
.overlay(RoundedRectangle(cornerRadius: 10)
|
||||||
.stroke(DamusColors.white, lineWidth: 1))
|
.stroke(Color("DamusWhite"), lineWidth: 1))
|
||||||
.shadow(radius: 10)
|
.shadow(radius: 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text("Follow me on nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")
|
Text("Follow me on nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(Color("DamusWhite"))
|
||||||
.font(.system(size: 24, weight: .heavy))
|
.font(.system(size: 24, weight: .heavy))
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
|
|
||||||
Text("Scan the code", comment: "Text on QR code view to prompt viewer to scan the QR code on screen with their device camera.")
|
Text("Scan the code", comment: "Text on QR code view to prompt viewer to scan the QR code on screen with their device camera.")
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(Color("DamusWhite"))
|
||||||
.font(.system(size: 18, weight: .ultraLight))
|
.font(.system(size: 18, weight: .ultraLight))
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@@ -12,33 +12,22 @@ struct RecommendedRelayView: View {
|
|||||||
let relay: String
|
let relay: String
|
||||||
let add_button: Bool
|
let add_button: Bool
|
||||||
|
|
||||||
@Binding var showActionButtons: Bool
|
init(damus: DamusState, relay: String) {
|
||||||
|
|
||||||
init(damus: DamusState, relay: String, showActionButtons: Binding<Bool>) {
|
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
self.relay = relay
|
self.relay = relay
|
||||||
self.add_button = true
|
self.add_button = true
|
||||||
self._showActionButtons = showActionButtons
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init(damus: DamusState, relay: String, add_button: Bool, showActionButtons: Binding<Bool>) {
|
init(damus: DamusState, relay: String, add_button: Bool) {
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
self.relay = relay
|
self.relay = relay
|
||||||
self.add_button = add_button
|
self.add_button = add_button
|
||||||
self._showActionButtons = showActionButtons
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
HStack {
|
HStack {
|
||||||
if let privkey = damus.keypair.privkey {
|
|
||||||
if showActionButtons && add_button {
|
|
||||||
AddButton(privkey: privkey, showText: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RelayType(is_paid: damus.relay_metadata.lookup(relay_id: relay)?.is_paid ?? false)
|
RelayType(is_paid: damus.relay_metadata.lookup(relay_id: relay)?.is_paid ?? false)
|
||||||
|
|
||||||
Text(relay).layoutPriority(1)
|
Text(relay).layoutPriority(1)
|
||||||
|
|
||||||
if let meta = damus.relay_metadata.lookup(relay_id: relay) {
|
if let meta = damus.relay_metadata.lookup(relay_id: relay) {
|
||||||
@@ -48,75 +37,41 @@ struct RecommendedRelayView: View {
|
|||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
.opacity(0.0)
|
.opacity(0.0)
|
||||||
.disabled(showActionButtons)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Image(systemName: "info.circle")
|
Image(systemName: "info.circle")
|
||||||
.font(.system(size: 20, weight: .regular))
|
|
||||||
.foregroundColor(Color.accentColor)
|
.foregroundColor(Color.accentColor)
|
||||||
} else {
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Image(systemName: "questionmark.circle")
|
|
||||||
.font(.system(size: 20, weight: .regular))
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.swipeActions {
|
.swipeActions {
|
||||||
if add_button {
|
if add_button {
|
||||||
if let privkey = damus.keypair.privkey {
|
if let privkey = damus.keypair.privkey {
|
||||||
AddButton(privkey: privkey, showText: false)
|
AddAction(privkey: privkey)
|
||||||
.tint(.accentColor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.contextMenu {
|
|
||||||
CopyAction(relay: relay)
|
|
||||||
|
|
||||||
if let privkey = damus.keypair.privkey {
|
|
||||||
AddButton(privkey: privkey, showText: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CopyAction(relay: String) -> some View {
|
func AddAction(privkey: String) -> some View {
|
||||||
Button {
|
Button {
|
||||||
UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text")
|
guard let ev_before_add = damus.contacts.event else {
|
||||||
} label: {
|
return
|
||||||
Label(NSLocalizedString("Copy", comment: "Button to copy a relay server address."), systemImage: "doc.on.doc")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddButton(privkey: String, showText: Bool) -> some View {
|
|
||||||
Button(action: {
|
|
||||||
add_action(privkey: privkey)
|
|
||||||
}) {
|
|
||||||
if showText {
|
|
||||||
Text(NSLocalizedString("Connect", comment: "Button to connect to recommended relay server."))
|
|
||||||
}
|
}
|
||||||
Image(systemName: "plus.circle.fill")
|
guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else {
|
||||||
.font(.system(size: 20, weight: .medium))
|
return
|
||||||
.foregroundColor(.accentColor)
|
}
|
||||||
.padding(.leading, 5)
|
process_contact_event(state: damus, ev: ev_after_add)
|
||||||
|
damus.pool.send(.event(ev_after_add))
|
||||||
|
} label: {
|
||||||
|
Label(NSLocalizedString("Add Relay", comment: "Button to add recommended relay server."), systemImage: "plus.circle")
|
||||||
}
|
}
|
||||||
}
|
.tint(.accentColor)
|
||||||
|
|
||||||
func add_action(privkey: String) {
|
|
||||||
guard let ev_before_add = damus.contacts.event else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
process_contact_event(state: damus, ev: ev_after_add)
|
|
||||||
damus.postbox.send(ev_after_add)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RecommendedRelayView_Previews: PreviewProvider {
|
struct RecommendedRelayView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
RecommendedRelayView(damus: test_damus_state(), relay: "wss://relay.damus.io", showActionButtons: .constant(false))
|
RecommendedRelayView(damus: test_damus_state(), relay: "wss://relay.damus.io")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import SwiftUI
|
|||||||
struct RelayConfigView: View {
|
struct RelayConfigView: View {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
@State var new_relay: String = ""
|
@State var new_relay: String = ""
|
||||||
|
@State var show_add_relay: Bool = false
|
||||||
@State var relays: [RelayDescriptor]
|
@State var relays: [RelayDescriptor]
|
||||||
@State private var showActionButtons = false
|
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ struct RelayConfigView: View {
|
|||||||
var recommended: [RelayDescriptor] {
|
var recommended: [RelayDescriptor] {
|
||||||
let rs: [RelayDescriptor] = []
|
let rs: [RelayDescriptor] = []
|
||||||
return BOOTSTRAP_RELAYS.reduce(into: rs) { xs, x in
|
return BOOTSTRAP_RELAYS.reduce(into: rs) { xs, x in
|
||||||
if state.pool.get_relay(x) == nil, let url = URL(string: x) {
|
if state.pool.get_relay(x) == nil {
|
||||||
xs.append(RelayDescriptor(url: url, info: .rw))
|
xs.append(RelayDescriptor(url: URL(string: x)!, info: .rw))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,122 +37,72 @@ struct RelayConfigView: View {
|
|||||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $show_add_relay) {
|
||||||
|
AddRelayView(show_add_relay: $show_add_relay, relay: $new_relay) { m_relay in
|
||||||
|
guard var relay = m_relay else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if relay.starts(with: "wss://") == false && relay.starts(with: "ws://") == false {
|
||||||
|
relay = "wss://" + relay
|
||||||
|
}
|
||||||
|
|
||||||
|
if relay.hasSuffix("/") {
|
||||||
|
relay.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let url = URL(string: relay) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let ev = state.contacts.event else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let privkey = state.keypair.privkey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = RelayInfo.rw
|
||||||
|
|
||||||
|
guard (try? state.pool.add_relay(url, info: info)) != nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.pool.connect(to: [relay])
|
||||||
|
|
||||||
|
guard let new_ev = add_relay(ev: ev, privkey: privkey, current_relays: state.pool.descriptors, relay: relay, info: info) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
process_contact_event(state: state, ev: ev)
|
||||||
|
|
||||||
|
state.pool.send(.event(new_ev))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var MainContent: some View {
|
var MainContent: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
|
||||||
AddRelayView(relay: $new_relay)
|
|
||||||
} header: {
|
|
||||||
HStack {
|
|
||||||
Text(NSLocalizedString("Connect To Relay", comment: "Label for section for adding a relay server."))
|
|
||||||
.font(.system(size: 18, weight: .heavy))
|
|
||||||
.padding(.bottom, 5)
|
|
||||||
}
|
|
||||||
} footer: {
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
if(!new_relay.isEmpty) {
|
|
||||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of view adding user inputted relay.")) {
|
|
||||||
new_relay = ""
|
|
||||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
|
||||||
}
|
|
||||||
.font(.system(size: 14, weight: .bold))
|
|
||||||
.frame(width: 80, height: 30)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.background(LINEAR_GRADIENT)
|
|
||||||
.clipShape(Capsule())
|
|
||||||
.padding(EdgeInsets(top: 15, leading: 0, bottom: 0, trailing: 0))
|
|
||||||
|
|
||||||
Button(NSLocalizedString("Add", comment: "Button to confirm adding user inputted relay.")) {
|
|
||||||
|
|
||||||
if new_relay.starts(with: "wss://") == false && new_relay.starts(with: "ws://") == false {
|
|
||||||
new_relay = "wss://" + new_relay
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_relay.hasSuffix("/") {
|
|
||||||
new_relay.removeLast();
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let url = URL(string: new_relay) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let ev = state.contacts.event else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let privkey = state.keypair.privkey else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let info = RelayInfo.rw
|
|
||||||
|
|
||||||
guard (try? state.pool.add_relay(url, info: info)) != nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
state.pool.connect(to: [new_relay])
|
|
||||||
|
|
||||||
guard let new_ev = add_relay(ev: ev, privkey: privkey, current_relays: state.pool.descriptors, relay: new_relay, info: info) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
process_contact_event(state: state, ev: ev)
|
|
||||||
|
|
||||||
state.pool.send(.event(new_ev))
|
|
||||||
|
|
||||||
new_relay = ""
|
|
||||||
|
|
||||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
|
||||||
}
|
|
||||||
.font(.system(size: 14, weight: .bold))
|
|
||||||
.frame(width: 80, height: 30)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.background(LINEAR_GRADIENT)
|
|
||||||
.clipShape(Capsule())
|
|
||||||
.padding(EdgeInsets(top: 15, leading: 0, bottom: 0, trailing: 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
List(Array(relays), id: \.url) { relay in
|
List(Array(relays), id: \.url) { relay in
|
||||||
RelayView(state: state, relay: relay.url.absoluteString, showActionButtons: $showActionButtons)
|
RelayView(state: state, relay: relay.url.absoluteString)
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
HStack {
|
HStack {
|
||||||
Text(NSLocalizedString("Connected Relays", comment: "Section title for relay servers that are connected."))
|
Text("Relays", comment: "Header text for relay server list for configuration.")
|
||||||
.font(.system(size: 18, weight: .heavy))
|
Spacer()
|
||||||
.padding(.bottom, 5)
|
Button(action: { show_add_relay = true }) {
|
||||||
|
Image(systemName: "plus")
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if recommended.count > 0 {
|
if recommended.count > 0 {
|
||||||
Section {
|
Section(NSLocalizedString("Recommended Relays", comment: "Section title for recommend relay servers that could be added as part of configuration")) {
|
||||||
List(recommended, id: \.url) { r in
|
List(recommended, id: \.url) { r in
|
||||||
RecommendedRelayView(damus: state, relay: r.url.absoluteString, showActionButtons: $showActionButtons)
|
RecommendedRelayView(damus: state, relay: r.url.absoluteString)
|
||||||
}
|
|
||||||
} header: {
|
|
||||||
Text(NSLocalizedString("Recommended Relays", comment: "Section title for recommend relay servers that could be added as part of configuration"))
|
|
||||||
.font(.system(size: 18, weight: .heavy))
|
|
||||||
.padding(.bottom, 5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle(NSLocalizedString("Relays", comment: "Title of relays view"))
|
|
||||||
.navigationBarTitleDisplayMode(.large)
|
|
||||||
.toolbar {
|
|
||||||
if state.keypair.privkey != nil {
|
|
||||||
if showActionButtons {
|
|
||||||
Button("Done") {
|
|
||||||
showActionButtons.toggle()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Button("Edit") {
|
|
||||||
showActionButtons.toggle()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user