From 6c799d9e7ff15257e84324432f5d68236b22ccc5 Mon Sep 17 00:00:00 2001 From: Sarto Date: Thu, 7 May 2026 13:51:59 +0330 Subject: [PATCH] IOS codes --- .gitignore | 8 + Makefile | 43 +- README-FA.md | 13 + README.md | 26 + go.mod | 15 +- go.sum | 30 +- internal/web/resolver_lists_test.go | 15 +- internal/web/static/index.html | 59 +- internal/web/web.go | 21 +- ios/Thefeed.xcodeproj/project.pbxproj | 512 ++++++++++++++++++ ios/Thefeed.xcodeproj/project.pbxproj.bak | 512 ++++++++++++++++++ .../xcshareddata/xcschemes/Thefeed.xcscheme | 102 ++++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 14 + .../AppIcon.appiconset/image.png | Bin 0 -> 634112 bytes ios/Thefeed/Assets.xcassets/Contents.json | 6 + ios/Thefeed/Bridge.swift | 190 +++++++ ios/Thefeed/ContentView.swift | 25 + ios/Thefeed/Info.plist | 51 ++ ios/Thefeed/LanguagePicker.swift | 68 +++ ios/Thefeed/ServerController.swift | 68 +++ ios/Thefeed/ThefeedApp.swift | 24 + ios/Thefeed/WebView.swift | 76 +++ ios/ThefeedTests/ThefeedTests.swift | 39 ++ mobile/mobile.go | 82 +++ mobile/mobile_test.go | 89 +++ mobile/tools_gomobile.go | 12 + 27 files changed, 2069 insertions(+), 42 deletions(-) create mode 100644 ios/Thefeed.xcodeproj/project.pbxproj create mode 100644 ios/Thefeed.xcodeproj/project.pbxproj.bak create mode 100644 ios/Thefeed.xcodeproj/xcshareddata/xcschemes/Thefeed.xcscheme create mode 100644 ios/Thefeed/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 ios/Thefeed/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 ios/Thefeed/Assets.xcassets/AppIcon.appiconset/image.png create mode 100644 ios/Thefeed/Assets.xcassets/Contents.json create mode 100644 ios/Thefeed/Bridge.swift create mode 100644 ios/Thefeed/ContentView.swift create mode 100644 ios/Thefeed/Info.plist create mode 100644 ios/Thefeed/LanguagePicker.swift create mode 100644 ios/Thefeed/ServerController.swift create mode 100644 ios/Thefeed/ThefeedApp.swift create mode 100644 ios/Thefeed/WebView.swift create mode 100644 ios/ThefeedTests/ThefeedTests.swift create mode 100644 mobile/mobile.go create mode 100644 mobile/mobile_test.go create mode 100644 mobile/tools_gomobile.go diff --git a/.gitignore b/.gitignore index ab36ab1..1642507 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,11 @@ todo.md tmp .claude + +# iOS build artifacts +ios/Mobile.xcframework +ios/build +ios/DerivedData +ios/*.xcuserdata +ios/Thefeed.xcodeproj/xcuserdata +ios/Thefeed.xcodeproj/project.xcworkspace/xcuserdata diff --git a/Makefile b/Makefile index fbd382e..c51f985 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -.PHONY: all build build-server build-client test clean lint fmt vet +.PHONY: all build build-server build-client test clean lint fmt vet \ + ios-bind ios-bind-catalyst ios-build ios-test ios-clean ios-list-sims ios-deps BINARY_SERVER = thefeed-server BINARY_CLIENT = thefeed-client @@ -97,6 +98,46 @@ build-android-arm: @mkdir -p $(BUILD_DIR) GOOS=linux GOARCH=arm GOARM=7 go build $(call CLIENT_GOFLAGS,thefeed-client-android-arm) -o $(BUILD_DIR)/$(BINARY_CLIENT)-android-arm ./cmd/client +# ===== iOS / Mac Catalyst ===== +# Requires: Xcode + gomobile (go install golang.org/x/mobile/cmd/gomobile@latest && gomobile init) + +IOS_DIR = ios +IOS_FRAMEWORK = $(IOS_DIR)/Mobile.xcframework +IOS_SCHEME = Thefeed +IOS_PROJECT = $(IOS_DIR)/Thefeed.xcodeproj +# Default simulator: pick the first available iPhone (override with IOS_SIM_NAME='iPhone 17'). +IOS_SIM_NAME ?= $(shell xcrun simctl list devices available 2>/dev/null | awk -F'[()]' '/-- iOS [0-9]/{ios=1;next} /^-- /{ios=0} ios && /iPhone/{print $$1; exit}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$$//') + +ios-deps: + @grep -q "golang.org/x/mobile" go.mod || go get golang.org/x/mobile/bind golang.org/x/mobile/bind/objc + go mod tidy + +ios-bind: ios-deps + @command -v gomobile >/dev/null 2>&1 || { echo "gomobile not found. Run: go install golang.org/x/mobile/cmd/gomobile@latest && gomobile init"; exit 1; } + gomobile bind -iosversion=14.0 -target=ios,iossimulator -o $(IOS_FRAMEWORK) ./mobile + +ios-bind-catalyst: ios-deps + @command -v gomobile >/dev/null 2>&1 || { echo "gomobile not found"; exit 1; } + gomobile bind -iosversion=14.0 -target=ios,iossimulator,maccatalyst -o $(IOS_FRAMEWORK) ./mobile + +ios-list-sims: + xcrun simctl list devices available + +ios-build: $(IOS_FRAMEWORK) + xcodebuild -project $(IOS_PROJECT) -scheme $(IOS_SCHEME) \ + -destination 'platform=iOS Simulator,name=$(IOS_SIM_NAME)' \ + build + +ios-test: $(IOS_FRAMEWORK) + xcodebuild test -project $(IOS_PROJECT) -scheme $(IOS_SCHEME) \ + -destination 'platform=iOS Simulator,name=$(IOS_SIM_NAME)' + +$(IOS_FRAMEWORK): + $(MAKE) ios-bind + +ios-clean: + rm -rf $(IOS_FRAMEWORK) $(IOS_DIR)/build $(IOS_DIR)/DerivedData + # UPX compression (requires upx in PATH) — only for Linux/Windows binaries upx: @command -v upx >/dev/null 2>&1 || { echo "upx not found, skipping compression"; exit 0; } diff --git a/README-FA.md b/README-FA.md index f84e018..ce4e676 100644 --- a/README-FA.md +++ b/README-FA.md @@ -362,6 +362,19 @@ chmod +x thefeed-client برنامه در اولین اجرا اجازه‌ی Battery Optimization Exemption را می‌گیرد تا سرویس پس‌زمینه توسط سیستم بسته نشود. +### iOS + +نسخه‌ی iOS در حال توسعه است. سورس در پوشه‌ی [`ios/`](ios/) قرار دارد. برای ساخت روی مک: + +``` +go install golang.org/x/mobile/cmd/gomobile@latest && gomobile init +make ios-bind # ساخت Mobile.xcframework +make ios-build # بیلد روی iOS Simulator +make ios-test # اجرای تست‌ها +``` + +سپس `ios/Thefeed.xcodeproj` را در Xcode باز کنید. + ## ⚙️ تنظیمات DNS شما به **دو رکورد DNS** نیاز دارید. فرض کنید IP سرور شما `203.0.113.10` است: diff --git a/README.md b/README.md index 4492979..6af6c92 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ DNS-based feed reader for Telegram channels and public X accounts. Designed for sudo bash -c "$(curl -Ls https://raw.githubusercontent.com/sartoopjj/thefeed/main/scripts/install.sh)" ``` - **Android APK** (Android 7.0+): pick `arm64-v8a` for any phone newer than ~2017, `armeabi-v7a` for older 32-bit-only devices. +- **iOS** (iOS 14+): App Store build planned. Source under [ios/](ios/) — see [iOS development](#ios-development) below. Public configs to test with: [@thefeedconfig](https://t.me/thefeedconfig). @@ -529,6 +530,31 @@ make fmt # Format code make clean # Remove build artifacts ``` +## iOS development + +Wraps the Go client as a gomobile-bound xcframework consumed by a SwiftUI app under `ios/`. Server runs in-process on `127.0.0.1:`; foreground only (iOS does not allow long-lived background servers). + +Prereqs on macOS: Xcode 15+, Go 1.22+, gomobile. + +``` +go install golang.org/x/mobile/cmd/gomobile@latest +gomobile init +``` + +Common targets: + +``` +make ios-bind # build Mobile.xcframework (iOS device + Simulator) +make ios-bind-catalyst # also include Mac Catalyst slice +make ios-build # build the app for the Simulator +make ios-test # run unit tests on the Simulator +make ios-list-sims # list available simulator destinations +``` + +Override the default simulator with `IOS_SIM_NAME='iPhone 16'`. + +Open `ios/Thefeed.xcodeproj` in Xcode after `make ios-bind` to run from Xcode. + ## Releases (GitHub Actions) Pushing a tag that starts with `v` triggers CI build + GitHub Release. diff --git a/go.mod b/go.mod index 60d74b3..026c1de 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,10 @@ require ( github.com/gotd/td v0.142.0 github.com/miekg/dns v1.1.72 github.com/refraction-networking/utls v1.6.7 - golang.org/x/crypto v0.49.0 - golang.org/x/net v0.52.0 - golang.org/x/term v0.41.0 + golang.org/x/crypto v0.50.0 + golang.org/x/mobile v0.0.0-20260410095206-2cfb76559b7b + golang.org/x/net v0.53.0 + golang.org/x/term v0.42.0 ) require ( @@ -40,11 +41,11 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/mod v0.34.0 // indirect + golang.org/x/mod v0.35.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect - golang.org/x/tools v0.43.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/tools v0.44.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect rsc.io/qr v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index f2d09cc..96cbf4b 100644 --- a/go.sum +++ b/go.sum @@ -81,26 +81,28 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/mobile v0.0.0-20260410095206-2cfb76559b7b h1:Qt2eaXcZ8x20iAcoZ6AceeMMtnjuPHvC51KRCH1DKSQ= +golang.org/x/mobile v0.0.0-20260410095206-2cfb76559b7b/go.mod h1:5Fu78lew5ucMXt8w2KYcwvxu2rkC/liHzUvaoiI+H/M= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/web/resolver_lists_test.go b/internal/web/resolver_lists_test.go index 291523b..e7a631e 100644 --- a/internal/web/resolver_lists_test.go +++ b/internal/web/resolver_lists_test.go @@ -265,13 +265,18 @@ func TestPersistScanResultsRescanOverwrites(t *testing.T) { } } -func TestPersistScanResultsNoSelectedList(t *testing.T) { +func TestPersistScanResultsSeedsDefaultListOnFirstRun(t *testing.T) { s := newTestServerWithProfiles(t, &ProfileList{}) - // No-op when there's no selected list to write into. - s.persistScanResultsToList([]string{"a:53"}) + s.persistScanResultsToList([]string{"a:53", "b:53"}) pl := loadProfilesT(t, s) - if len(pl.ActiveLists) != 0 { - t.Errorf("ActiveLists got created: %v", pl.ActiveLists) + if len(pl.ActiveLists) != 1 || pl.ActiveLists[0].Name != defaultListName { + t.Fatalf("ActiveLists = %v, want one Default list", pl.ActiveLists) + } + if got := pl.ActiveLists[0].Resolvers; len(got) != 2 { + t.Errorf("Default list = %v, want 2 entries", got) + } + if pl.SelectedList != defaultListName { + t.Errorf("SelectedList = %q, want %q", pl.SelectedList, defaultListName) } } diff --git a/internal/web/static/index.html b/internal/web/static/index.html index 7484fbe..188dee3 100644 --- a/internal/web/static/index.html +++ b/internal/web/static/index.html @@ -202,12 +202,12 @@ } .sidebar-search { - padding: 7px 12px; + padding: 5px 10px; border: none; - border-radius: 18px; + border-radius: 14px; background: var(--surface); color: var(--text); - font-size: 13px; + font-size: 12px; outline: none; width: 100% } @@ -959,7 +959,11 @@ background: var(--bg); display: none; flex-direction: column; - color: var(--text) + color: var(--text); + padding-top: var(--safe-top); + padding-bottom: var(--safe-bottom); + padding-left: var(--safe-left); + padding-right: var(--safe-right) } .tm-modal.active { display: flex } @@ -3271,9 +3275,9 @@