diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt b/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt index 9e8301b8a2..2cebbcf97c 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt @@ -114,6 +114,10 @@ enum class Methods(val method: String) { // Smart routing SetRoutingMode("setRoutingMode"), + + // Unbounded + SetUnboundedEnabled("setUnboundedEnabled"), + IsUnboundedEnabled("isUnboundedEnabled"), } class MethodHandler : FlutterPlugin, @@ -1039,6 +1043,19 @@ class MethodHandler : FlutterPlugin, } } + Methods.SetUnboundedEnabled.method -> { + scope.handleResult(result, "SetUnboundedEnabled") { + val enabled = call.argument("enabled") ?: error("Missing enabled") + Mobile.setUnboundedEnabled(enabled) + } + } + + Methods.IsUnboundedEnabled.method -> { + scope.handleValue(result, "IsUnboundedEnabled") { + Mobile.isUnboundedEnabled() + } + } + else -> { result.notImplemented() } diff --git a/assets/images/auto_mode.svg b/assets/images/auto_mode.svg new file mode 100644 index 0000000000..4c4e9d8ff9 --- /dev/null +++ b/assets/images/auto_mode.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/locales/en.po b/assets/locales/en.po index 2c38dd9cfc..5958a397d9 100644 --- a/assets/locales/en.po +++ b/assets/locales/en.po @@ -31,6 +31,12 @@ msgstr "Unbounded" msgid "help_fight_global_internet_censorship" msgstr "Help Fight Global Internet Censorship" +msgid "share_bandwidth" +msgstr "Share Bandwidth" + +msgid "unbounded_description" +msgstr "When enabled, Unbounded shares a small amount of your bandwidth to help people in censored countries access the open internet." + msgid "vpn_settings" msgstr "VPN Settings" @@ -1481,6 +1487,33 @@ msgstr "Dark" msgid "system" msgstr "System" +msgid "help_others_bypass_censorship" +msgstr "Help others bypass censorship by securely sharing your connection." + +msgid "people_you_are_helping_right_now" +msgstr "People you are helping right now:" + +msgid "total_people_helped_to_date" +msgstr "Total people helped to date:" + +msgid "status" +msgstr "Status" + +msgid "auto_enable_unbounded" +msgstr "Auto-enable Unbounded" + +msgid "turn_on_automatically_when_lantern_is_open" +msgstr "Turn on automatically when Lantern is open" + +msgid "welcome_to_unbounded" +msgstr "Welcome to Unbounded!" + +msgid "unbounded_description" +msgstr "Unbounded lets you share a small amount of your internet bandwidth to help people in censored countries access the open web." + +msgid "your_connection_stays_secure_and_private" +msgstr "Your connection stays secure and private. You control when sharing is active." + diff --git a/assets/unbounded/uv-map-dark.png b/assets/unbounded/uv-map-dark.png new file mode 100644 index 0000000000..afe9a07568 Binary files /dev/null and b/assets/unbounded/uv-map-dark.png differ diff --git a/assets/unbounded/uv-map.png b/assets/unbounded/uv-map.png new file mode 100644 index 0000000000..98953b96df Binary files /dev/null and b/assets/unbounded/uv-map.png differ diff --git a/go.mod b/go.mod index bc4c31cc8e..e8af6502a3 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,16 @@ go 1.25.4 // replace github.com/getlantern/radiance => ../radiance +// replace github.com/getlantern/broflake => ../unbounded + // replace github.com/getlantern/lantern-server-provisioner => ../lantern-server-provisioner // replace github.com/sagernet/sing-box => ../sing-box-minimal +replace github.com/enobufs/go-nats => github.com/noahlevenson/go-nats v0.0.0-20230720174341-49df1f749775 + +replace github.com/quic-go/quic-go => github.com/getlantern/quic-go-unbounded-fork v0.51.3-unbounded + replace github.com/sagernet/sing => github.com/getlantern/sing v0.7.18-lantern replace github.com/sagernet/sing-box => github.com/getlantern/sing-box-minimal v1.12.19-lantern @@ -22,7 +28,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 github.com/alecthomas/assert/v2 v2.3.0 github.com/getlantern/lantern-server-provisioner v0.0.0-20251031121934-8ea031fccfa9 - github.com/getlantern/radiance v0.0.0-20260221215045-6049f134d863 + github.com/getlantern/radiance v0.0.0-20260224202913-7520e330886d github.com/sagernet/sing-box v1.12.22 golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0 golang.org/x/sys v0.40.0 @@ -156,6 +162,7 @@ require ( github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect + github.com/enobufs/go-nats v0.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/flynn/noise v1.0.1-0.20220214164934-d803f5c4b0f4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect @@ -164,14 +171,15 @@ require ( github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 // indirect github.com/getlantern/amp v0.0.0-20260113204224-600f8e8dfe5f // indirect github.com/getlantern/appdir v0.0.0-20250324200952-507a0625eb01 // indirect - github.com/getlantern/common v1.2.1-0.20260121160752-d8ee5791108f // indirect + github.com/getlantern/broflake v0.0.0-20260223195036-4065257e0911 // indirect + github.com/getlantern/common v1.2.1-0.20260223192400-cc00002ef6c7 // indirect github.com/getlantern/dnstt v0.0.0-20260112160750-05100563bd0d // indirect github.com/getlantern/fronted v0.0.0-20260219001615-7eabaa834efe // indirect github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect github.com/getlantern/iptool v0.0.0-20230112135223-c00e863b2696 // indirect github.com/getlantern/keepcurrent v0.0.0-20240126172110-2e0264ca385d // indirect github.com/getlantern/kindling v0.0.0-20260219202502-df15c15dc5fb // indirect - github.com/getlantern/lantern-box v0.0.6-0.20260220213333-4b20583e43ff // indirect + github.com/getlantern/lantern-box v0.0.6-0.20260224193811-b50ea1f2e9db // indirect github.com/getlantern/lantern-water v0.0.0-20260130212632-d5ea08838250 // indirect github.com/getlantern/mtime v0.0.0-20200417132445-23682092d1f7 // indirect github.com/getlantern/netx v0.0.0-20240830183145-c257516187f0 // indirect @@ -185,6 +193,7 @@ require ( github.com/go-llsqlite/adapter v0.0.0-20230927005056-7f5ce7f0c916 // indirect github.com/go-llsqlite/crawshaw v0.4.0 // indirect github.com/go-resty/resty/v2 v2.16.5 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gobwas/ws v1.4.0 // indirect github.com/goccy/go-yaml v1.19.0 // indirect @@ -193,6 +202,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect + github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect github.com/gorilla/securecookie v1.1.2 // indirect @@ -222,26 +232,37 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/nwaples/rardecode v1.1.2 // indirect + github.com/onsi/ginkgo/v2 v2.12.0 // indirect github.com/pion/datachannel v1.5.10 // indirect - github.com/pion/dtls/v2 v2.2.7 // indirect + github.com/pion/dtls/v2 v2.2.12 // indirect + github.com/pion/dtls/v3 v3.0.6 // indirect github.com/pion/ice/v2 v2.3.24 // indirect - github.com/pion/interceptor v0.1.37 // indirect - github.com/pion/logging v0.2.3 // indirect + github.com/pion/ice/v4 v4.0.10 // indirect + github.com/pion/interceptor v0.1.40 // indirect + github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns v0.0.12 // indirect + github.com/pion/mdns/v2 v2.0.7 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.15 // indirect - github.com/pion/rtp v1.8.12 // indirect - github.com/pion/sctp v1.8.37 // indirect - github.com/pion/sdp/v3 v3.0.11 // indirect + github.com/pion/rtp v1.8.19 // indirect + github.com/pion/sctp v1.8.39 // indirect + github.com/pion/sdp/v3 v3.0.14 // indirect github.com/pion/srtp/v2 v2.0.18 // indirect + github.com/pion/srtp/v3 v3.0.6 // indirect github.com/pion/stun v0.6.1 // indirect - github.com/pion/transport/v2 v2.2.4 // indirect + github.com/pion/stun/v3 v3.0.0 // indirect + github.com/pion/transport v0.14.1 // indirect + github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/transport/v3 v3.0.7 // indirect + github.com/pion/turn v1.3.7 // indirect github.com/pion/turn/v2 v2.1.3 // indirect + github.com/pion/turn/v4 v4.0.2 // indirect github.com/pion/webrtc/v3 v3.2.40 // indirect + github.com/pion/webrtc/v4 v4.1.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect + github.com/quic-go/quic-go v0.51.0 // indirect github.com/r3labs/sse/v2 v2.10.0 // indirect github.com/refraction-networking/utls v1.8.2 // indirect github.com/refraction-networking/water v0.7.1-alpha // indirect @@ -269,6 +290,7 @@ require ( github.com/tjfoc/gmsm v1.4.1 // indirect github.com/tkuchiki/go-timezone v0.2.0 // indirect github.com/ulikunitz/xz v0.5.10 // indirect + github.com/wlynxg/anet v0.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xtaci/kcp-go/v5 v5.6.20 // indirect diff --git a/go.sum b/go.sum index 044c08d72b..599bf44767 100644 --- a/go.sum +++ b/go.sum @@ -193,8 +193,10 @@ github.com/getlantern/amp v0.0.0-20260113204224-600f8e8dfe5f h1:NLGftemDrbGf7Wce github.com/getlantern/amp v0.0.0-20260113204224-600f8e8dfe5f/go.mod h1:qnMv9szb8JK3kA9W4N2FlYUMj1GkA0x7QEUEPD7tk4o= github.com/getlantern/appdir v0.0.0-20250324200952-507a0625eb01 h1:Mmeh4/DA1OKN9tVWRAvTL5efFx4c7v9/55hoK17NclA= github.com/getlantern/appdir v0.0.0-20250324200952-507a0625eb01/go.mod h1:3vR6+jQdWfWojZ77w+htCqEF5MO/Y2twJOpAvFuM9po= -github.com/getlantern/common v1.2.1-0.20260121160752-d8ee5791108f h1:EqRKCaOBuvVkFsIjeWUYluE4s4TZtVQSClfIWFqcSks= -github.com/getlantern/common v1.2.1-0.20260121160752-d8ee5791108f/go.mod h1:eSSuV4bMPgQJnczBw+KWWqWNo1itzmVxC++qUBPRTt0= +github.com/getlantern/broflake v0.0.0-20260223195036-4065257e0911 h1:HmTK1otLrb3zOi3b+7VEODbKdMKKxs0Ka6Ux17PjgLc= +github.com/getlantern/broflake v0.0.0-20260223195036-4065257e0911/go.mod h1:b40tuN7lS/j6OXOlUyo4m/CY+JbZ/dj1SBOtGfqPUKI= +github.com/getlantern/common v1.2.1-0.20260223192400-cc00002ef6c7 h1:WhfyxuQ6cf7Ia9NF5PNkhCyfY2nhogXx/CCRRqy7DhI= +github.com/getlantern/common v1.2.1-0.20260223192400-cc00002ef6c7/go.mod h1:eSSuV4bMPgQJnczBw+KWWqWNo1itzmVxC++qUBPRTt0= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo= @@ -222,8 +224,8 @@ github.com/getlantern/keepcurrent v0.0.0-20240126172110-2e0264ca385d h1:2/9rPC1x github.com/getlantern/keepcurrent v0.0.0-20240126172110-2e0264ca385d/go.mod h1:enUAvxkJ15QUtTKOKoO9WJV2L5u33P8YmqkC+iu8iT4= github.com/getlantern/kindling v0.0.0-20260219202502-df15c15dc5fb h1:ATakfsW9OOvlnoU++kz/zG2kK9JCQdUdFWd2N2QwI2w= github.com/getlantern/kindling v0.0.0-20260219202502-df15c15dc5fb/go.mod h1:8t/DQxsfk7LBzHFwpv+qOLlj2bG8vL5Ckw73Y0/FY1s= -github.com/getlantern/lantern-box v0.0.6-0.20260220213333-4b20583e43ff h1:r6iJ6hcsemDVG62zpVDgAGscObWPZcO8NlL5T2ZpNWw= -github.com/getlantern/lantern-box v0.0.6-0.20260220213333-4b20583e43ff/go.mod h1:OnSmUR2+rpmGcS5DA0iUyEPwfPEEftnEtj2A6rBq+ko= +github.com/getlantern/lantern-box v0.0.6-0.20260224193811-b50ea1f2e9db h1:N+qr2VV6w8hpXBQuDHI/u4/3fQzgFRhV1dM47GyNi5M= +github.com/getlantern/lantern-box v0.0.6-0.20260224193811-b50ea1f2e9db/go.mod h1:OnSmUR2+rpmGcS5DA0iUyEPwfPEEftnEtj2A6rBq+ko= github.com/getlantern/lantern-server-provisioner v0.0.0-20251031121934-8ea031fccfa9 h1:6seyD2f9tz2am0YQd/Qn+q7LFiiQgnmxgwWFnVceGZw= github.com/getlantern/lantern-server-provisioner v0.0.0-20251031121934-8ea031fccfa9/go.mod h1:s0VKrlJf/z+M0U8IKHFL2hfuflocRw3SINmMacrTlMA= github.com/getlantern/lantern-water v0.0.0-20260130212632-d5ea08838250 h1:xculJyC6hS0kNSQKWBP1FQbpSVmeJyhUGID804jgKCA= @@ -242,8 +244,10 @@ github.com/getlantern/osversion v0.0.0-20240418205916-2e84a4a4e175 h1:JWH5BB2o0e github.com/getlantern/osversion v0.0.0-20240418205916-2e84a4a4e175/go.mod h1:h3S9LBmmzN/xM+lwYZHE4abzTtCTtidKtG+nxZcCZX0= github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535 h1:rtDmW8YLAuT8r51ApR5z0d8/qjhHu3TW+divQ2C98Ac= github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535/go.mod h1:WKJEdjMOD4IuTRYwjQHjT4bmqDl5J82RShMLxPAvi0Q= -github.com/getlantern/radiance v0.0.0-20260221215045-6049f134d863 h1:nlx+23+ieMbmXA12ZY3IMuedCsAKgp/s2Fauf8M10mk= -github.com/getlantern/radiance v0.0.0-20260221215045-6049f134d863/go.mod h1:JwM46TRAnU3PCmdhj7gar3bpHH5SQTufj7d2LSdi2tk= +github.com/getlantern/quic-go-unbounded-fork v0.51.3-unbounded h1:qA1oi5so1/C6psHLPlyPGyq6JhZsPvA4EutsNhjzodc= +github.com/getlantern/quic-go-unbounded-fork v0.51.3-unbounded/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= +github.com/getlantern/radiance v0.0.0-20260224202913-7520e330886d h1:Xnc+eYMyAPzzyJ4Tzn/rZbRZScQ/ZwXZasiaD+c32dY= +github.com/getlantern/radiance v0.0.0-20260224202913-7520e330886d/go.mod h1:bdUNd0PV0wR1/VdEWjTuFfkp2JSMvLTTyizz2BFMd+4= github.com/getlantern/samizdat v0.0.2 h1:PkMu6jsfUz7DLZUH2xh548XfzgPASmq5CajZyUKj/9Y= github.com/getlantern/samizdat v0.0.2/go.mod h1:uEeykQSW2/6rTjfPlj3MTTo59poSHXfAHTGgzYDkbr0= github.com/getlantern/sing v0.7.18-lantern h1:QKGgIUA3LwmKYP/7JlQTRkxj9jnP4cX2Q/B+nd8XEjo= @@ -297,6 +301,8 @@ github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= @@ -357,6 +363,8 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -488,12 +496,18 @@ github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOl github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/noahlevenson/go-nats v0.0.0-20230720174341-49df1f749775 h1:CVBqDCqhtrS2etCKGuwruUkwg3f/axVpa2Il5IQQtEs= +github.com/noahlevenson/go-nats v0.0.0-20230720174341-49df1f749775/go.mod h1:dXVvPZcJIwdWDH5ZXQ8oVA7dz+Ecu9TPF+6biaMl1dY= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M= github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= @@ -506,45 +520,68 @@ github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= +github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= +github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E= +github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU= github.com/pion/ice/v2 v2.3.24 h1:RYgzhH/u5lH0XO+ABatVKCtRd+4U1GEaCXSMjNr13tI= github.com/pion/ice/v2 v2.3.24/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw= -github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI= -github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y= +github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= +github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= +github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4= +github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= -github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= +github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= +github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8= github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk= +github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= +github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= -github.com/pion/rtp v1.8.12 h1:nsKs8Wi0jQyBFHU3qmn/OvtZrhktVfJY0vRxwACsL5U= -github.com/pion/rtp v1.8.12/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4= -github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs= -github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= -github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI= -github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c= +github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= +github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE= +github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= +github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI= +github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo= github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA= +github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= +github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= +github.com/pion/stun v0.3.2/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= +github.com/pion/stun v0.3.3/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= +github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= +github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= +github.com/pion/transport v0.8.8/go.mod h1:lpeSM6KJFejVtZf8k0fgeN7zE73APQpTF83WvA1FVP8= +github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= +github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc= github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= -github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= +github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= +github.com/pion/turn v1.3.7 h1:/nyM2XrlZILD7KKfnh0oYEBTRG5JlbH21ibjluRoCeo= +github.com/pion/turn v1.3.7/go.mod h1:js0LBFqMcKAlaWAXoYqNjefGI7kfJCrkCBfHGuTToXE= github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA= github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= +github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= +github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= github.com/pion/webrtc/v3 v3.2.40 h1:Wtfi6AZMQg+624cvCXUuSmrKWepSB7zfgYDOYqsSOVU= github.com/pion/webrtc/v3 v3.2.40/go.mod h1:M1RAe3TNTD1tzyvqHrbVODfwdPGSXOUo/OgpoGGJqFY= +github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= +github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -705,6 +742,9 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= @@ -828,6 +868,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -878,6 +919,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -892,6 +934,7 @@ golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= @@ -907,6 +950,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= diff --git a/ios/Runner/Handlers/MethodHandler.swift b/ios/Runner/Handlers/MethodHandler.swift index 447354900d..9791a0e271 100644 --- a/ios/Runner/Handlers/MethodHandler.swift +++ b/ios/Runner/Handlers/MethodHandler.swift @@ -214,6 +214,16 @@ class MethodHandler { return } self.setSmartRouteMode(mode: mode, result: result) + + // Unbounded + case "setUnboundedEnabled": + let data = call.arguments as? [String: Any] + let enabled = data?["enabled"] as? Bool ?? false + self.setUnboundedEnabled(result: result, enabled: enabled) + + case "isUnboundedEnabled": + self.isUnboundedEnabled(result: result) + default: result(FlutterMethodNotImplemented) } @@ -970,6 +980,36 @@ class MethodHandler { } + // MARK: - Unbounded + + func setUnboundedEnabled(result: @escaping FlutterResult, enabled: Bool) { + Task { + var error: NSError? + MobileSetUnboundedEnabled(enabled, &error) + if let error { + await self.handleFlutterError(error, result: result, code: "SET_UNBOUNDED_ENABLED_ERROR") + return + } + await MainActor.run { + result("ok") + } + } + } + + func isUnboundedEnabled(result: @escaping FlutterResult) { + Task { + var error: NSError? + let enabled = MobileIsUnboundedEnabled() + if let error { + await self.handleFlutterError(error, result: result, code: "IS_UNBOUNDED_ENABLED_ERROR") + return + } + await MainActor.run { + result(enabled) + } + } + } + // MARK: - Utils /// Helper for handling Flutter errors diff --git a/lantern-core/core.go b/lantern-core/core.go index e7df5b1402..482366bca7 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -33,9 +33,10 @@ import ( type EventType = string const ( - EventTypeConfig EventType = "config" - EventTypeServerLocation EventType = "server-location" - DefaultLogLevel = "trace" + EventTypeConfig EventType = "config" + EventTypeServerLocation EventType = "server-location" + EventTypeUnboundedConnection EventType = "unbounded-connection" + DefaultLogLevel = "trace" ) // LanternCore is the main structure accessing the Lantern backend. @@ -135,6 +136,11 @@ type SmartRouting interface { IsSmartRoutingEnabled() bool } +type Unbounded interface { + SetUnboundedEnabled(bool) error + IsUnboundedEnabled() bool +} + type Core interface { App User @@ -143,6 +149,7 @@ type Core interface { SplitTunnel Ads SmartRouting + Unbounded } // Make sure LanternCore implements the Core interface @@ -216,6 +223,7 @@ func (lc *LanternCore) initialize(opts *utils.Opts, eventEmitter utils.FlutterEv lc.listeningServerLocationChanges() lc.listeningDataCapChanges() + lc.listeningUnboundedConnectionChanges() slog.Debug("LanternCore initialized successfully") // If we have a legacy user ID, fetch user data @@ -260,6 +268,17 @@ func (lc *LanternCore) listeningDataCapChanges() { }) } +func (lc *LanternCore) listeningUnboundedConnectionChanges() { + events.Subscribe(func(evt vpn.UnboundedConnectionEvent) { + jsonBytes, err := json.Marshal(evt) + if err != nil { + slog.Error("Error marshalling unbounded connection event", "error", err) + return + } + lc.notifyFlutter(EventTypeUnboundedConnection, string(jsonBytes)) + }) +} + func (lc *LanternCore) UpdateTelemetryConsent(consent bool) error { slog.Debug("Updating telemetry consent", "consent", consent) if consent { @@ -807,6 +826,14 @@ func (lc *LanternCore) IsSmartRoutingEnabled() bool { return vpn.SmartRoutingEnabled() } +func (lc *LanternCore) SetUnboundedEnabled(enabled bool) error { + return vpn.SetUnbounded(enabled) +} + +func (lc *LanternCore) IsUnboundedEnabled() bool { + return vpn.UnboundedEnabled() +} + func (lc *LanternCore) AddServerBasedOnURLs(urls string, skipCertVerification bool, serverName string) error { slog.Debug("Adding server based on URLs", "urls", urls, "skipCertVerification", skipCertVerification) return lc.serverManager.AddServerBasedOnURLs(context.Background(), urls, skipCertVerification, serverName) diff --git a/lantern-core/ffi/ffi.go b/lantern-core/ffi/ffi.go index 27acd5b73c..050c9357fd 100644 --- a/lantern-core/ffi/ffi.go +++ b/lantern-core/ffi/ffi.go @@ -1002,3 +1002,24 @@ func isSmartRoutingEnabled() C.int { } return 0 } + +//export setUnboundedEnabled +func setUnboundedEnabled(enabled C.int) *C.char { + c, errStr := requireCore() + if errStr != nil { + return errStr + } + if err := c.SetUnboundedEnabled(enabled != 0); err != nil { + return SendError(err) + } + return C.CString("ok") +} + +//export isUnboundedEnabled +func isUnboundedEnabled() C.int { + c, _ := requireCore() + if c != nil && c.IsUnboundedEnabled() { + return 1 + } + return 0 +} diff --git a/lantern-core/mobile/mobile.go b/lantern-core/mobile/mobile.go index 7d9fe7126a..0c2125a3a7 100644 --- a/lantern-core/mobile/mobile.go +++ b/lantern-core/mobile/mobile.go @@ -518,6 +518,25 @@ func AddServerBasedOnURLs(urls string, skipCertVerification bool, serverName str }) } +// Unbounded Methods + +func SetUnboundedEnabled(enabled bool) error { + slog.Info("unbounded: SetUnboundedEnabled", "enabled", enabled) + return withCore(func(c lanterncore.Core) error { + return c.SetUnboundedEnabled(enabled) + }) +} + +func IsUnboundedEnabled() bool { + ok, err := withCoreR(func(c lanterncore.Core) (bool, error) { + return c.IsUnboundedEnabled(), nil + }) + if err != nil { + return false + } + return ok +} + // Smart Routing Methods // SetSmartRoutingMode sets the smart routing mode. diff --git a/lib/core/common/app_image_paths.dart b/lib/core/common/app_image_paths.dart index 84250edfd6..c7961b37fd 100644 --- a/lib/core/common/app_image_paths.dart +++ b/lib/core/common/app_image_paths.dart @@ -111,6 +111,10 @@ class AppImagePaths { static const automatic = 'assets/images/automatic.svg'; static const lightMode = 'assets/images/light_mode.svg'; static const darkMode = 'assets/images/dark_mode.svg'; + static const autoMode = 'assets/images/auto_mode.svg'; + static const darkMap = 'assets/unbounded/uv-map-dark.png'; + static const lightMap = 'assets/unbounded/uv-map.png'; + /// Validates and returns a safe flag path for the given country code. /// Returns null if the country code is invalid or the flag asset doesn't exist. diff --git a/lib/core/common/app_semantic_colors.dart b/lib/core/common/app_semantic_colors.dart index 74b15e2d5a..446e18cc79 100644 --- a/lib/core/common/app_semantic_colors.dart +++ b/lib/core/common/app_semantic_colors.dart @@ -6,30 +6,30 @@ import 'app_colors.dart'; /// /// Usage: context.textPrimary, context.bgElevated, context.borderInput … extension AppSemanticColors on BuildContext { - bool get _isDark => Theme.of(this).brightness == Brightness.dark; + bool get isDark => Theme.of(this).brightness == Brightness.dark; // ── Text ──────────────────────────────────────────────────────────────────── /// text.primary Gray.900 light / Gray.200 dark - Color get textPrimary => _isDark ? AppColors.gray2 : AppColors.gray9; + Color get textPrimary => isDark ? AppColors.gray2 : AppColors.gray9; /// text.secondary Gray.800 light / Gray.300 dark - Color get textSecondary => _isDark ? AppColors.gray3 : AppColors.gray8; + Color get textSecondary => isDark ? AppColors.gray3 : AppColors.gray8; /// text.tertiary Gray.700 light / Gray.400 dark - Color get textTertiary => _isDark ? AppColors.gray4 : AppColors.gray7; + Color get textTertiary => isDark ? AppColors.gray4 : AppColors.gray7; /// text.link Blue.700 light / Blue.200 dark - Color get textLink => _isDark ? AppColors.blue2 : AppColors.blue7; + Color get textLink => isDark ? AppColors.blue2 : AppColors.blue7; /// text.disabled Gray.600 light / Gray.500 dark - Color get textDisabled => _isDark ? AppColors.gray5 : AppColors.gray6; + Color get textDisabled => isDark ? AppColors.gray5 : AppColors.gray6; /// text.inverse Gray.100 light / Gray.900 dark - Color get textInverse => _isDark ? AppColors.gray9 : AppColors.gray1; + Color get textInverse => isDark ? AppColors.gray9 : AppColors.gray1; /// text.inverse-color Blue.400 light / Blue.600 dark - Color get textInverseColor => _isDark ? AppColors.blue6 : AppColors.blue4; + Color get textInverseColor => isDark ? AppColors.blue6 : AppColors.blue4; /// text.promo-icon Yellow.300 both modes Color get textPromoIcon => AppColors.yellow3; @@ -37,48 +37,48 @@ extension AppSemanticColors on BuildContext { // ── Background ────────────────────────────────────────────────────────────── /// bg.surface Gray.100 light / Gray.900 dark - Color get bgSurface => _isDark ? AppColors.gray9 : AppColors.gray1; + Color get bgSurface => isDark ? AppColors.gray9 : AppColors.gray1; /// bg.elevated White light / Gray.850 dark - Color get bgElevated => _isDark ? AppColors.gray850 : AppColors.white; + Color get bgElevated => isDark ? AppColors.gray850 : AppColors.white; /// bg.input White light / Gray.850 dark - Color get bgInput => _isDark ? AppColors.gray850 : AppColors.white; + Color get bgInput => isDark ? AppColors.gray850 : AppColors.white; /// bg.hover Blue.100 light / Blue.900 dark - Color get bgHover => _isDark ? AppColors.blue9 : AppColors.blue1; + Color get bgHover => isDark ? AppColors.blue9 : AppColors.blue1; /// bg.overlay Gray.100 light / Gray.900 dark - Color get bgOverlay => _isDark ? AppColors.gray9 : AppColors.gray1; + Color get bgOverlay => isDark ? AppColors.gray9 : AppColors.gray1; /// bg.callout Gray.200 light / Gray.800 dark - Color get bgCallout => _isDark ? AppColors.gray8 : AppColors.gray2; + Color get bgCallout => isDark ? AppColors.gray8 : AppColors.gray2; /// bg.snackbar Blue.900 light / Blue.200 dark - Color get bgSnackbar => _isDark ? AppColors.blue2 : AppColors.blue9; + Color get bgSnackbar => isDark ? AppColors.blue2 : AppColors.blue9; /// bg.snackbar-error Red.700 light / Red.500 dark - Color get bgSnackbarError => _isDark ? AppColors.red5 : AppColors.red7; + Color get bgSnackbarError => isDark ? AppColors.red5 : AppColors.red7; /// bg.promo Yellow.100 light / Gray.900 dark - Color get bgPromo => _isDark ? AppColors.gray9 : AppColors.yellow1; + Color get bgPromo => isDark ? AppColors.gray9 : AppColors.yellow1; // ── Border ────────────────────────────────────────────────────────────────── /// border.default Gray.200 light / Gray.800 dark - Color get borderDefault => _isDark ? AppColors.gray8 : AppColors.gray2; + Color get borderDefault => isDark ? AppColors.gray8 : AppColors.gray2; /// border.input Gray.300 light / Gray.700 dark - Color get borderInput => _isDark ? AppColors.gray7 : AppColors.gray3; + Color get borderInput => isDark ? AppColors.gray7 : AppColors.gray3; /// border.input-focus Blue.800 light / Blue.200 dark - Color get borderInputFocus => _isDark ? AppColors.blue2 : AppColors.blue8; + Color get borderInputFocus => isDark ? AppColors.blue2 : AppColors.blue8; /// border.input-filled Gray.900 light / Gray.400 dark - Color get borderInputFilled => _isDark ? AppColors.gray4 : AppColors.gray9; + Color get borderInputFilled => isDark ? AppColors.gray4 : AppColors.gray9; /// border.error Red.600 light / Red.500 dark - Color get borderError => _isDark ? AppColors.red5 : AppColors.red6; + Color get borderError => isDark ? AppColors.red5 : AppColors.red6; /// border.promo Yellow.500 both modes Color get borderPromo => AppColors.yellow5; @@ -86,125 +86,125 @@ extension AppSemanticColors on BuildContext { // ── Status ────────────────────────────────────────────────────────────────── /// status.error-text Red.800 light / Red.200 dark - Color get statusErrorText => _isDark ? AppColors.red2 : AppColors.red8; + Color get statusErrorText => isDark ? AppColors.red2 : AppColors.red8; /// status.error-bg Red.200 light / Red.800 dark - Color get statusErrorBg => _isDark ? AppColors.red8 : AppColors.red2; + Color get statusErrorBg => isDark ? AppColors.red8 : AppColors.red2; /// status.error-border Red.400 light / Red.600 dark - Color get statusErrorBorder => _isDark ? AppColors.red6 : AppColors.red4; + Color get statusErrorBorder => isDark ? AppColors.red6 : AppColors.red4; /// status.success-text Green.800 light / Green.300 dark - Color get statusSuccessText => _isDark ? AppColors.green3 : AppColors.green8; + Color get statusSuccessText => isDark ? AppColors.green3 : AppColors.green8; /// status.success-bg Green.200 light / Green.700 dark - Color get statusSuccessBg => _isDark ? AppColors.green7 : AppColors.green2; + Color get statusSuccessBg => isDark ? AppColors.green7 : AppColors.green2; /// status.success-border Green.400 light / Green.600 dark Color get statusSuccessBorder => - _isDark ? AppColors.green6 : AppColors.green4; + isDark ? AppColors.green6 : AppColors.green4; /// status.warning-text Yellow.500 light / Yellow.200 dark - Color get statusWarningText => _isDark ? AppColors.yellow2 : AppColors.yellow5; + Color get statusWarningText => isDark ? AppColors.yellow2 : AppColors.yellow5; /// status.warning-bg-dot Yellow.300 light / Yellow.500 dark Color get statusWarningBgDot => - _isDark ? AppColors.yellow5 : AppColors.yellow3; + isDark ? AppColors.yellow5 : AppColors.yellow3; /// status.neutral-text Gray.600 light / Gray.200 dark - Color get statusNeutralText => _isDark ? AppColors.gray2 : AppColors.gray6; + Color get statusNeutralText => isDark ? AppColors.gray2 : AppColors.gray6; /// status.informational-text Blue.800 light / Blue.200 dark - Color get statusInfoText => _isDark ? AppColors.blue2 : AppColors.blue8; + Color get statusInfoText => isDark ? AppColors.blue2 : AppColors.blue8; /// status.Informational-bg Blue.200 light / Blue.700 dark - Color get statusInfoBg => _isDark ? AppColors.blue7 : AppColors.blue2; + Color get statusInfoBg => isDark ? AppColors.blue7 : AppColors.blue2; /// status.Informational-border Blue.400 light / Blue.600 dark - Color get statusInfoBorder => _isDark ? AppColors.blue6 : AppColors.blue4; + Color get statusInfoBorder => isDark ? AppColors.blue6 : AppColors.blue4; /// status.error-bg-dot Red.600 light / Red.800 dark - Color get statusErrorBgDot => _isDark ? AppColors.red8 : AppColors.red6; + Color get statusErrorBgDot => isDark ? AppColors.red8 : AppColors.red6; /// status.error-border-dot Red.300 light / Red.500 dark - Color get statusErrorBorderDot => _isDark ? AppColors.red5 : AppColors.red3; + Color get statusErrorBorderDot => isDark ? AppColors.red5 : AppColors.red3; /// status.warning-border-dot Yellow.200 light / Yellow.400 dark Color get statusWarningBorderDot => - _isDark ? AppColors.yellow4 : AppColors.yellow2; + isDark ? AppColors.yellow4 : AppColors.yellow2; /// status.success-bg-dot Green.600 light / Green.700 dark Color get statusSuccessBgDot => - _isDark ? AppColors.green7 : AppColors.green6; + isDark ? AppColors.green7 : AppColors.green6; /// status.success-border-dot Green.300 light / Green.500 dark Color get statusSuccessBorderDot => - _isDark ? AppColors.green5 : AppColors.green3; + isDark ? AppColors.green5 : AppColors.green3; /// status.neutral-bg-dot Gray.500 light / Gray.700 dark - Color get statusNeutralBgDot => _isDark ? AppColors.gray7 : AppColors.gray5; + Color get statusNeutralBgDot => isDark ? AppColors.gray7 : AppColors.gray5; /// status.neutral-border-dot Gray.300 light / Gray.500 dark Color get statusNeutralBorderDot => - _isDark ? AppColors.gray5 : AppColors.gray3; + isDark ? AppColors.gray5 : AppColors.gray3; // ── Action / Primary ──────────────────────────────────────────────────────── /// action.primary.primary-bg Blue.1000 light / Blue.600 dark - Color get actionPrimaryBg => _isDark ? AppColors.blue6 : AppColors.blue10; + Color get actionPrimaryBg => isDark ? AppColors.blue6 : AppColors.blue10; /// action.primary.primary-bg-hover Blue.800 light / Blue.500 dark - Color get actionPrimaryBgHover => _isDark ? AppColors.blue5 : AppColors.blue8; + Color get actionPrimaryBgHover => isDark ? AppColors.blue5 : AppColors.blue8; /// action.primary.primary-text Gray.100 both modes Color get actionPrimaryText => AppColors.gray1; /// action.primary.primary-disabled-bg Gray.200 light / Gray.700 dark Color get actionPrimaryDisabledBg => - _isDark ? AppColors.gray7 : AppColors.gray2; + isDark ? AppColors.gray7 : AppColors.gray2; /// action.primary.primary-disabled-text Gray.500 both modes Color get actionPrimaryDisabledText => AppColors.gray5; /// action.primary.primary-disabled-border Gray.400 light / Gray.500 dark Color get actionPrimaryDisabledBorder => - _isDark ? AppColors.gray5 : AppColors.gray4; + isDark ? AppColors.gray5 : AppColors.gray4; // ── Action / Secondary ────────────────────────────────────────────────────── /// action.secondary.secondary-bg Gray.100 light / Gray.900 dark - Color get actionSecondaryBg => _isDark ? AppColors.gray9 : AppColors.gray1; + Color get actionSecondaryBg => isDark ? AppColors.gray9 : AppColors.gray1; /// action.secondary.secondary-bg-hover Gray.200 light / Gray.800 dark Color get actionSecondaryBgHover => - _isDark ? AppColors.gray8 : AppColors.gray2; + isDark ? AppColors.gray8 : AppColors.gray2; /// action.secondary.secondary-text Gray.900 light / Gray.100 dark - Color get actionSecondaryText => _isDark ? AppColors.gray1 : AppColors.gray9; + Color get actionSecondaryText => isDark ? AppColors.gray1 : AppColors.gray9; /// action.secondary.secondary-border Gray.500 light / Gray.600 dark Color get actionSecondaryBorder => - _isDark ? AppColors.gray6 : AppColors.gray5; + isDark ? AppColors.gray6 : AppColors.gray5; /// action.secondary.secondary-disabled-bg Gray.200 light / Gray.900 dark Color get actionSecondaryDisabledBg => - _isDark ? AppColors.gray9 : AppColors.gray2; + isDark ? AppColors.gray9 : AppColors.gray2; /// action.secondary.secondary-disabled-text Gray.500 both modes Color get actionSecondaryDisabledText => AppColors.gray5; /// action.secondary.secondary-disabled-border Gray.300 light / Gray.700 dark Color get actionSecondaryDisabledBorder => - _isDark ? AppColors.gray7 : AppColors.gray3; + isDark ? AppColors.gray7 : AppColors.gray3; // ── Action / Tertiary ─────────────────────────────────────────────────────── /// action.tertiary.tertiary-text Gray.900 light / Gray.100 dark - Color get actionTertiaryText => _isDark ? AppColors.gray1 : AppColors.gray9; + Color get actionTertiaryText => isDark ? AppColors.gray1 : AppColors.gray9; /// action.tertiary.tertiary-hover-bg Gray.200 light / Gray.800 dark Color get actionTertiaryHoverBg => - _isDark ? AppColors.gray8 : AppColors.gray2; + isDark ? AppColors.gray8 : AppColors.gray2; /// action.tertiary.tertiary-disabled-text Gray.500 both modes Color get actionTertiaryDisabledText => AppColors.gray5; @@ -212,61 +212,61 @@ extension AppSemanticColors on BuildContext { // ── Action / Tonal ────────────────────────────────────────────────────────── /// action.tonal.tonal-bg Blue.100 light / Blue.700 dark - Color get actionTonalBg => _isDark ? AppColors.blue7 : AppColors.blue1; + Color get actionTonalBg => isDark ? AppColors.blue7 : AppColors.blue1; /// action.tonal.tonal-border Gray.200 light / Gray.800 dark - Color get actionTonalBorder => _isDark ? AppColors.gray8 : AppColors.gray2; + Color get actionTonalBorder => isDark ? AppColors.gray8 : AppColors.gray2; /// action.tonal.tonal-bg-hover Blue.200 light / Blue.600 dark - Color get actionTonalBgHover => _isDark ? AppColors.blue6 : AppColors.blue2; + Color get actionTonalBgHover => isDark ? AppColors.blue6 : AppColors.blue2; /// action.tonal.tonal-text Gray.900 light / Gray.100 dark - Color get actionTonalText => _isDark ? AppColors.gray1 : AppColors.gray9; + Color get actionTonalText => isDark ? AppColors.gray1 : AppColors.gray9; /// action.tonal.tonal-disabled-bg Gray.100 light / Gray.800 dark Color get actionTonalDisabledBg => - _isDark ? AppColors.gray8 : AppColors.gray1; + isDark ? AppColors.gray8 : AppColors.gray1; /// action.tonal.tonal-disabled-border Gray.200 light / Gray.800 dark Color get actionTonalDisabledBorder => - _isDark ? AppColors.gray8 : AppColors.gray2; + isDark ? AppColors.gray8 : AppColors.gray2; /// action.tonal.tonal-disabled-text Gray.400 light / Gray.500 dark Color get actionTonalDisabledText => - _isDark ? AppColors.gray5 : AppColors.gray4; + isDark ? AppColors.gray5 : AppColors.gray4; // ── Action / Toggle ───────────────────────────────────────────────────────── /// action.toggle.toggle-active-bg Green.500 light / Green.700 dark Color get actionToggleActiveBg => - _isDark ? AppColors.green7 : AppColors.green5; + isDark ? AppColors.green7 : AppColors.green5; /// action.toggle.toggle-brand-active-bg Blue.400 light / Blue.600 dark Color get actionToggleBrandActiveBg => - _isDark ? AppColors.blue6 : AppColors.blue4; + isDark ? AppColors.blue6 : AppColors.blue4; /// action.toggle.toggle-disabled-bg Gray.700 both modes Color get actionToggleDisabledBg => AppColors.gray7; /// action.toggle.toggle-knob-bg Gray.000 light / Gray.100 dark - Color get actionToggleKnobBg => _isDark ? AppColors.gray1 : AppColors.gray0; + Color get actionToggleKnobBg => isDark ? AppColors.gray1 : AppColors.gray0; /// action.toggle.toggle-border Gray.200 light / Gray.700 dark - Color get actionToggleBorder => _isDark ? AppColors.gray7 : AppColors.gray2; + Color get actionToggleBorder => isDark ? AppColors.gray7 : AppColors.gray2; // ── Action / Tabbar ───────────────────────────────────────────────────────── /// action.tabbar.tabbar-bg Blue.200 light / Blue.800 dark - Color get actionTabbarBg => _isDark ? AppColors.blue8 : AppColors.blue2; + Color get actionTabbarBg => isDark ? AppColors.blue8 : AppColors.blue2; /// action.tabbar.tabbar-border Blue.300 light / Blue.700 dark - Color get actionTabbarBorder => _isDark ? AppColors.blue7 : AppColors.blue3; + Color get actionTabbarBorder => isDark ? AppColors.blue7 : AppColors.blue3; /// action.tabbar.tabbar-selected-text Blue.1000 light / Blue.100 dark Color get actionTabbarSelectedText => - _isDark ? AppColors.blue1 : AppColors.blue10; + isDark ? AppColors.blue1 : AppColors.blue10; /// action.tabbar.tabbar-disabled-text Gray.600 light / Gray.200 dark Color get actionTabbarDisabledText => - _isDark ? AppColors.gray2 : AppColors.gray6; + isDark ? AppColors.gray2 : AppColors.gray6; } diff --git a/lib/core/models/entity/app_setting_entity.dart b/lib/core/models/entity/app_setting_entity.dart index 1283efbaab..ba09feb28c 100644 --- a/lib/core/models/entity/app_setting_entity.dart +++ b/lib/core/models/entity/app_setting_entity.dart @@ -22,6 +22,10 @@ class AppSetting { bool onboardingCompleted; String themeMode; String environment; + bool unboundedEnabled; + bool autoEnableUnbounded; + bool unboundedWelcomeSeen; + bool hideUnbounded; AppSetting({ this.id = 0, @@ -41,6 +45,10 @@ class AppSetting { this.onboardingCompleted = false, this.themeMode = 'system', this.environment = 'prod', + this.unboundedEnabled = false, + this.autoEnableUnbounded = true, + this.unboundedWelcomeSeen = false, + this.hideUnbounded = false, }); AppSetting copyWith({ @@ -60,6 +68,10 @@ class AppSetting { bool? onboardingCompleted, String? themeMode, String? environment, + bool? unboundedEnabled, + bool? autoEnableUnbounded, + bool? unboundedWelcomeSeen, + bool? hideUnbounded, }) { return AppSetting( id: id, @@ -79,6 +91,10 @@ class AppSetting { onboardingCompleted: onboardingCompleted ?? this.onboardingCompleted, themeMode: themeMode ?? this.themeMode, environment: environment ?? this.environment, + unboundedEnabled: unboundedEnabled ?? this.unboundedEnabled, + autoEnableUnbounded: autoEnableUnbounded ?? this.autoEnableUnbounded, + unboundedWelcomeSeen: unboundedWelcomeSeen ?? this.unboundedWelcomeSeen, + hideUnbounded: hideUnbounded ?? this.hideUnbounded, ); } diff --git a/lib/core/models/unbounded_connection_event.dart b/lib/core/models/unbounded_connection_event.dart new file mode 100644 index 0000000000..eb643f255a --- /dev/null +++ b/lib/core/models/unbounded_connection_event.dart @@ -0,0 +1,28 @@ +/// Represents a consumer connection change from the broflake widget proxy. +class UnboundedConnectionEvent { + final int state; // 1 = connected, -1 = disconnected + final int workerIdx; + final String addr; // IP address + + UnboundedConnectionEvent({ + required this.state, + required this.workerIdx, + required this.addr, + }); + + factory UnboundedConnectionEvent.fromJson(Map json) { + return UnboundedConnectionEvent( + state: json['state'] as int, + workerIdx: json['workerIdx'] as int, + addr: json['addr'] as String? ?? '', + ); + } +} + +/// Tracks live and cumulative connection counts for Unbounded. +class UnboundedStats { + final int activeCount; + final int totalCount; + + const UnboundedStats({this.activeCount = 0, this.totalCount = 0}); +} diff --git a/lib/core/router/router.dart b/lib/core/router/router.dart index 6bb8c84719..c1c5c69570 100644 --- a/lib/core/router/router.dart +++ b/lib/core/router/router.dart @@ -184,6 +184,14 @@ class AppRouter extends RootStackRouter { path: '/smart-routing', page: SmartRouting.page, ), + AutoRoute( + path: '/unbounded', + page: UnboundedScreen.page, + ), + AutoRoute( + path: '/unbounded-settings', + page: UnboundedSettingsScreen.page, + ), AutoRoute( path: '/intro', page: Onboarding.page, diff --git a/lib/core/router/router.gr.dart b/lib/core/router/router.gr.dart index f2e685b236..47c56dd94b 100644 --- a/lib/core/router/router.gr.dart +++ b/lib/core/router/router.gr.dart @@ -9,10 +9,10 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i44; -import 'package:collection/collection.dart' as _i48; -import 'package:flutter/material.dart' as _i45; -import 'package:lantern/core/common/common.dart' as _i46; +import 'package:auto_route/auto_route.dart' as _i46; +import 'package:collection/collection.dart' as _i50; +import 'package:flutter/material.dart' as _i47; +import 'package:lantern/core/common/common.dart' as _i48; import 'package:lantern/core/widgets/app_webview.dart' as _i3; import 'package:lantern/features/account/account.dart' as _i1; import 'package:lantern/features/account/delete_account.dart' as _i9; @@ -58,27 +58,29 @@ import 'package:lantern/features/setting/follow_us.dart' as _i13; import 'package:lantern/features/setting/invite_friends.dart' as _i15; import 'package:lantern/features/setting/setting.dart' as _i35; import 'package:lantern/features/setting/smart_routing.dart' as _i38; -import 'package:lantern/features/setting/vpn_setting.dart' as _i42; +import 'package:lantern/features/setting/vpn_setting.dart' as _i44; import 'package:lantern/features/split_tunneling/apps_split_tunneling.dart' as _i5; import 'package:lantern/features/split_tunneling/split_tunneling.dart' as _i39; import 'package:lantern/features/split_tunneling/split_tunneling_info.dart' as _i40; import 'package:lantern/features/split_tunneling/website_split_tunneling.dart' - as _i43; + as _i45; import 'package:lantern/features/support/support.dart' as _i41; +import 'package:lantern/features/unbounded/unbounded.dart' as _i42; +import 'package:lantern/features/unbounded/unbounded_settings.dart' as _i43; import 'package:lantern/features/vpn/server_selection.dart' as _i34; -import 'package:lantern/lantern/protos/protos/auth.pb.dart' as _i47; +import 'package:lantern/lantern/protos/protos/auth.pb.dart' as _i49; /// generated route for /// [_i1.Account] -class Account extends _i44.PageRouteInfo { - const Account({List<_i44.PageRouteInfo>? children}) +class Account extends _i46.PageRouteInfo { + const Account({List<_i46.PageRouteInfo>? children}) : super(Account.name, initialChildren: children); static const String name = 'Account'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i1.Account(); @@ -88,12 +90,12 @@ class Account extends _i44.PageRouteInfo { /// generated route for /// [_i2.AddEmail] -class AddEmail extends _i44.PageRouteInfo { +class AddEmail extends _i46.PageRouteInfo { AddEmail({ - _i45.Key? key, - _i46.AuthFlow authFlow = _i46.AuthFlow.signUp, + _i47.Key? key, + _i48.AuthFlow authFlow = _i48.AuthFlow.signUp, String? password, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( AddEmail.name, args: AddEmailArgs(key: key, authFlow: authFlow, password: password), @@ -102,7 +104,7 @@ class AddEmail extends _i44.PageRouteInfo { static const String name = 'AddEmail'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs( @@ -120,13 +122,13 @@ class AddEmail extends _i44.PageRouteInfo { class AddEmailArgs { const AddEmailArgs({ this.key, - this.authFlow = _i46.AuthFlow.signUp, + this.authFlow = _i48.AuthFlow.signUp, this.password, }); - final _i45.Key? key; + final _i47.Key? key; - final _i46.AuthFlow authFlow; + final _i48.AuthFlow authFlow; final String? password; @@ -150,12 +152,12 @@ class AddEmailArgs { /// generated route for /// [_i3.AppWebView] -class AppWebview extends _i44.PageRouteInfo { +class AppWebview extends _i46.PageRouteInfo { AppWebview({ - _i45.Key? key, + _i47.Key? key, required String title, required String url, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( AppWebview.name, args: AppWebviewArgs(key: key, title: title, url: url), @@ -164,7 +166,7 @@ class AppWebview extends _i44.PageRouteInfo { static const String name = 'AppWebview'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -176,7 +178,7 @@ class AppWebview extends _i44.PageRouteInfo { class AppWebviewArgs { const AppWebviewArgs({this.key, required this.title, required this.url}); - final _i45.Key? key; + final _i47.Key? key; final String title; @@ -200,13 +202,13 @@ class AppWebviewArgs { /// generated route for /// [_i4.Appearance] -class Appearance extends _i44.PageRouteInfo { - const Appearance({List<_i44.PageRouteInfo>? children}) +class Appearance extends _i46.PageRouteInfo { + const Appearance({List<_i46.PageRouteInfo>? children}) : super(Appearance.name, initialChildren: children); static const String name = 'Appearance'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i4.Appearance(); @@ -216,13 +218,13 @@ class Appearance extends _i44.PageRouteInfo { /// generated route for /// [_i5.AppsSplitTunneling] -class AppsSplitTunneling extends _i44.PageRouteInfo { - const AppsSplitTunneling({List<_i44.PageRouteInfo>? children}) +class AppsSplitTunneling extends _i46.PageRouteInfo { + const AppsSplitTunneling({List<_i46.PageRouteInfo>? children}) : super(AppsSplitTunneling.name, initialChildren: children); static const String name = 'AppsSplitTunneling'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i5.AppsSplitTunneling(); @@ -232,13 +234,13 @@ class AppsSplitTunneling extends _i44.PageRouteInfo { /// generated route for /// [_i6.ChoosePaymentMethod] -class ChoosePaymentMethod extends _i44.PageRouteInfo { +class ChoosePaymentMethod extends _i46.PageRouteInfo { ChoosePaymentMethod({ - _i45.Key? key, + _i47.Key? key, required String email, String? code, - required _i46.AuthFlow authFlow, - List<_i44.PageRouteInfo>? children, + required _i48.AuthFlow authFlow, + List<_i46.PageRouteInfo>? children, }) : super( ChoosePaymentMethod.name, args: ChoosePaymentMethodArgs( @@ -252,7 +254,7 @@ class ChoosePaymentMethod extends _i44.PageRouteInfo { static const String name = 'ChoosePaymentMethod'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -274,13 +276,13 @@ class ChoosePaymentMethodArgs { required this.authFlow, }); - final _i45.Key? key; + final _i47.Key? key; final String email; final String? code; - final _i46.AuthFlow authFlow; + final _i48.AuthFlow authFlow; @override String toString() { @@ -304,13 +306,13 @@ class ChoosePaymentMethodArgs { /// generated route for /// [_i7.ConfirmEmail] -class ConfirmEmail extends _i44.PageRouteInfo { +class ConfirmEmail extends _i46.PageRouteInfo { ConfirmEmail({ - _i45.Key? key, + _i47.Key? key, required String email, String? password, - _i46.AuthFlow authFlow = _i46.AuthFlow.signUp, - List<_i44.PageRouteInfo>? children, + _i48.AuthFlow authFlow = _i48.AuthFlow.signUp, + List<_i46.PageRouteInfo>? children, }) : super( ConfirmEmail.name, args: ConfirmEmailArgs( @@ -324,7 +326,7 @@ class ConfirmEmail extends _i44.PageRouteInfo { static const String name = 'ConfirmEmail'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -343,16 +345,16 @@ class ConfirmEmailArgs { this.key, required this.email, this.password, - this.authFlow = _i46.AuthFlow.signUp, + this.authFlow = _i48.AuthFlow.signUp, }); - final _i45.Key? key; + final _i47.Key? key; final String email; final String? password; - final _i46.AuthFlow authFlow; + final _i48.AuthFlow authFlow; @override String toString() { @@ -376,13 +378,13 @@ class ConfirmEmailArgs { /// generated route for /// [_i8.CreatePassword] -class CreatePassword extends _i44.PageRouteInfo { +class CreatePassword extends _i46.PageRouteInfo { CreatePassword({ - _i45.Key? key, + _i47.Key? key, required String email, - required _i46.AuthFlow authFlow, + required _i48.AuthFlow authFlow, required String code, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( CreatePassword.name, args: CreatePasswordArgs( @@ -396,7 +398,7 @@ class CreatePassword extends _i44.PageRouteInfo { static const String name = 'CreatePassword'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -418,11 +420,11 @@ class CreatePasswordArgs { required this.code, }); - final _i45.Key? key; + final _i47.Key? key; final String email; - final _i46.AuthFlow authFlow; + final _i48.AuthFlow authFlow; final String code; @@ -448,13 +450,13 @@ class CreatePasswordArgs { /// generated route for /// [_i9.DeleteAccount] -class DeleteAccount extends _i44.PageRouteInfo { - const DeleteAccount({List<_i44.PageRouteInfo>? children}) +class DeleteAccount extends _i46.PageRouteInfo { + const DeleteAccount({List<_i46.PageRouteInfo>? children}) : super(DeleteAccount.name, initialChildren: children); static const String name = 'DeleteAccount'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i9.DeleteAccount(); @@ -464,13 +466,13 @@ class DeleteAccount extends _i44.PageRouteInfo { /// generated route for /// [_i10.DeveloperMode] -class DeveloperMode extends _i44.PageRouteInfo { - const DeveloperMode({List<_i44.PageRouteInfo>? children}) +class DeveloperMode extends _i46.PageRouteInfo { + const DeveloperMode({List<_i46.PageRouteInfo>? children}) : super(DeveloperMode.name, initialChildren: children); static const String name = 'DeveloperMode'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i10.DeveloperMode(); @@ -480,11 +482,11 @@ class DeveloperMode extends _i44.PageRouteInfo { /// generated route for /// [_i11.DeviceLimitReached] -class DeviceLimitReached extends _i44.PageRouteInfo { +class DeviceLimitReached extends _i46.PageRouteInfo { DeviceLimitReached({ - _i45.Key? key, - required List<_i47.UserResponse_Device> devices, - List<_i44.PageRouteInfo>? children, + _i47.Key? key, + required List<_i49.UserResponse_Device> devices, + List<_i46.PageRouteInfo>? children, }) : super( DeviceLimitReached.name, args: DeviceLimitReachedArgs(key: key, devices: devices), @@ -493,7 +495,7 @@ class DeviceLimitReached extends _i44.PageRouteInfo { static const String name = 'DeviceLimitReached'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -505,9 +507,9 @@ class DeviceLimitReached extends _i44.PageRouteInfo { class DeviceLimitReachedArgs { const DeviceLimitReachedArgs({this.key, required this.devices}); - final _i45.Key? key; + final _i47.Key? key; - final List<_i47.UserResponse_Device> devices; + final List<_i49.UserResponse_Device> devices; @override String toString() { @@ -519,7 +521,7 @@ class DeviceLimitReachedArgs { if (identical(this, other)) return true; if (other is! DeviceLimitReachedArgs) return false; return key == other.key && - const _i48.ListEquality<_i47.UserResponse_Device>().equals( + const _i50.ListEquality<_i49.UserResponse_Device>().equals( devices, other.devices, ); @@ -528,18 +530,18 @@ class DeviceLimitReachedArgs { @override int get hashCode => key.hashCode ^ - const _i48.ListEquality<_i47.UserResponse_Device>().hash(devices); + const _i50.ListEquality<_i49.UserResponse_Device>().hash(devices); } /// generated route for /// [_i12.DownloadLinks] -class DownloadLinks extends _i44.PageRouteInfo { - const DownloadLinks({List<_i44.PageRouteInfo>? children}) +class DownloadLinks extends _i46.PageRouteInfo { + const DownloadLinks({List<_i46.PageRouteInfo>? children}) : super(DownloadLinks.name, initialChildren: children); static const String name = 'DownloadLinks'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i12.DownloadLinks(); @@ -549,13 +551,13 @@ class DownloadLinks extends _i44.PageRouteInfo { /// generated route for /// [_i13.FollowUs] -class FollowUs extends _i44.PageRouteInfo { - const FollowUs({List<_i44.PageRouteInfo>? children}) +class FollowUs extends _i46.PageRouteInfo { + const FollowUs({List<_i46.PageRouteInfo>? children}) : super(FollowUs.name, initialChildren: children); static const String name = 'FollowUs'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i13.FollowUs(); @@ -565,13 +567,13 @@ class FollowUs extends _i44.PageRouteInfo { /// generated route for /// [_i14.Home] -class Home extends _i44.PageRouteInfo { - const Home({List<_i44.PageRouteInfo>? children}) +class Home extends _i46.PageRouteInfo { + const Home({List<_i46.PageRouteInfo>? children}) : super(Home.name, initialChildren: children); static const String name = 'Home'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i14.Home(); @@ -581,13 +583,13 @@ class Home extends _i44.PageRouteInfo { /// generated route for /// [_i15.InviteFriends] -class InviteFriends extends _i44.PageRouteInfo { - const InviteFriends({List<_i44.PageRouteInfo>? children}) +class InviteFriends extends _i46.PageRouteInfo { + const InviteFriends({List<_i46.PageRouteInfo>? children}) : super(InviteFriends.name, initialChildren: children); static const String name = 'InviteFriends'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i15.InviteFriends(); @@ -597,11 +599,11 @@ class InviteFriends extends _i44.PageRouteInfo { /// generated route for /// [_i16.JoinPrivateServer] -class JoinPrivateServer extends _i44.PageRouteInfo { +class JoinPrivateServer extends _i46.PageRouteInfo { JoinPrivateServer({ - _i45.Key? key, + _i47.Key? key, Map? deepLinkData, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( JoinPrivateServer.name, args: JoinPrivateServerArgs(key: key, deepLinkData: deepLinkData), @@ -610,7 +612,7 @@ class JoinPrivateServer extends _i44.PageRouteInfo { static const String name = 'JoinPrivateServer'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs( @@ -627,7 +629,7 @@ class JoinPrivateServer extends _i44.PageRouteInfo { class JoinPrivateServerArgs { const JoinPrivateServerArgs({this.key, this.deepLinkData}); - final _i45.Key? key; + final _i47.Key? key; final Map? deepLinkData; @@ -641,7 +643,7 @@ class JoinPrivateServerArgs { if (identical(this, other)) return true; if (other is! JoinPrivateServerArgs) return false; return key == other.key && - const _i48.MapEquality().equals( + const _i50.MapEquality().equals( deepLinkData, other.deepLinkData, ); @@ -650,18 +652,18 @@ class JoinPrivateServerArgs { @override int get hashCode => key.hashCode ^ - const _i48.MapEquality().hash(deepLinkData); + const _i50.MapEquality().hash(deepLinkData); } /// generated route for /// [_i17.Language] -class Language extends _i44.PageRouteInfo { - const Language({List<_i44.PageRouteInfo>? children}) +class Language extends _i46.PageRouteInfo { + const Language({List<_i46.PageRouteInfo>? children}) : super(Language.name, initialChildren: children); static const String name = 'Language'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i17.Language(); @@ -671,12 +673,12 @@ class Language extends _i44.PageRouteInfo { /// generated route for /// [_i18.LanternProLicense] -class LanternProLicense extends _i44.PageRouteInfo { +class LanternProLicense extends _i46.PageRouteInfo { LanternProLicense({ - _i45.Key? key, + _i47.Key? key, required String email, required String code, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( LanternProLicense.name, args: LanternProLicenseArgs(key: key, email: email, code: code), @@ -685,7 +687,7 @@ class LanternProLicense extends _i44.PageRouteInfo { static const String name = 'LanternProLicense'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -705,7 +707,7 @@ class LanternProLicenseArgs { required this.code, }); - final _i45.Key? key; + final _i47.Key? key; final String email; @@ -729,13 +731,13 @@ class LanternProLicenseArgs { /// generated route for /// [_i19.Logs] -class Logs extends _i44.PageRouteInfo { - const Logs({List<_i44.PageRouteInfo>? children}) +class Logs extends _i46.PageRouteInfo { + const Logs({List<_i46.PageRouteInfo>? children}) : super(Logs.name, initialChildren: children); static const String name = 'Logs'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i19.Logs(); @@ -745,13 +747,13 @@ class Logs extends _i44.PageRouteInfo { /// generated route for /// [_i20.MacOSExtensionDialog] -class MacOSExtensionDialog extends _i44.PageRouteInfo { - const MacOSExtensionDialog({List<_i44.PageRouteInfo>? children}) +class MacOSExtensionDialog extends _i46.PageRouteInfo { + const MacOSExtensionDialog({List<_i46.PageRouteInfo>? children}) : super(MacOSExtensionDialog.name, initialChildren: children); static const String name = 'MacOSExtensionDialog'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i20.MacOSExtensionDialog(); @@ -761,13 +763,13 @@ class MacOSExtensionDialog extends _i44.PageRouteInfo { /// generated route for /// [_i21.ManagePrivateServer] -class ManagePrivateServer extends _i44.PageRouteInfo { - const ManagePrivateServer({List<_i44.PageRouteInfo>? children}) +class ManagePrivateServer extends _i46.PageRouteInfo { + const ManagePrivateServer({List<_i46.PageRouteInfo>? children}) : super(ManagePrivateServer.name, initialChildren: children); static const String name = 'ManagePrivateServer'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i21.ManagePrivateServer(); @@ -777,13 +779,13 @@ class ManagePrivateServer extends _i44.PageRouteInfo { /// generated route for /// [_i22.ManuallyServerSetup] -class ManuallyServerSetup extends _i44.PageRouteInfo { - const ManuallyServerSetup({List<_i44.PageRouteInfo>? children}) +class ManuallyServerSetup extends _i46.PageRouteInfo { + const ManuallyServerSetup({List<_i46.PageRouteInfo>? children}) : super(ManuallyServerSetup.name, initialChildren: children); static const String name = 'ManuallyServerSetup'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i22.ManuallyServerSetup(); @@ -793,13 +795,13 @@ class ManuallyServerSetup extends _i44.PageRouteInfo { /// generated route for /// [_i23.Onboarding] -class Onboarding extends _i44.PageRouteInfo { - const Onboarding({List<_i44.PageRouteInfo>? children}) +class Onboarding extends _i46.PageRouteInfo { + const Onboarding({List<_i46.PageRouteInfo>? children}) : super(Onboarding.name, initialChildren: children); static const String name = 'Onboarding'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i23.Onboarding(); @@ -809,13 +811,13 @@ class Onboarding extends _i44.PageRouteInfo { /// generated route for /// [_i24.Plans] -class Plans extends _i44.PageRouteInfo { - const Plans({List<_i44.PageRouteInfo>? children}) +class Plans extends _i46.PageRouteInfo { + const Plans({List<_i46.PageRouteInfo>? children}) : super(Plans.name, initialChildren: children); static const String name = 'Plans'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i24.Plans(); @@ -825,13 +827,13 @@ class Plans extends _i44.PageRouteInfo { /// generated route for /// [_i25.PrivateServerAddBilling] -class PrivateServerAddBilling extends _i44.PageRouteInfo { - const PrivateServerAddBilling({List<_i44.PageRouteInfo>? children}) +class PrivateServerAddBilling extends _i46.PageRouteInfo { + const PrivateServerAddBilling({List<_i46.PageRouteInfo>? children}) : super(PrivateServerAddBilling.name, initialChildren: children); static const String name = 'PrivateServerAddBilling'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i25.PrivateServerAddBilling(); @@ -841,11 +843,11 @@ class PrivateServerAddBilling extends _i44.PageRouteInfo { /// generated route for /// [_i26.PrivateServerDeploy] -class PrivateServerDeploy extends _i44.PageRouteInfo { +class PrivateServerDeploy extends _i46.PageRouteInfo { PrivateServerDeploy({ - _i45.Key? key, + _i47.Key? key, required String serverName, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( PrivateServerDeploy.name, args: PrivateServerDeployArgs(key: key, serverName: serverName), @@ -854,7 +856,7 @@ class PrivateServerDeploy extends _i44.PageRouteInfo { static const String name = 'PrivateServerDeploy'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -869,7 +871,7 @@ class PrivateServerDeploy extends _i44.PageRouteInfo { class PrivateServerDeployArgs { const PrivateServerDeployArgs({this.key, required this.serverName}); - final _i45.Key? key; + final _i47.Key? key; final String serverName; @@ -892,14 +894,14 @@ class PrivateServerDeployArgs { /// generated route for /// [_i27.PrivateServerLocation] class PrivateServerLocation - extends _i44.PageRouteInfo { + extends _i46.PageRouteInfo { PrivateServerLocation({ - _i45.Key? key, + _i47.Key? key, required List location, required String? selectedLocation, required dynamic Function(String) onLocationSelected, - required _i46.CloudProvider provider, - List<_i44.PageRouteInfo>? children, + required _i48.CloudProvider provider, + List<_i46.PageRouteInfo>? children, }) : super( PrivateServerLocation.name, args: PrivateServerLocationArgs( @@ -914,7 +916,7 @@ class PrivateServerLocation static const String name = 'PrivateServerLocation'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -938,7 +940,7 @@ class PrivateServerLocationArgs { required this.provider, }); - final _i45.Key? key; + final _i47.Key? key; final List location; @@ -946,7 +948,7 @@ class PrivateServerLocationArgs { final dynamic Function(String) onLocationSelected; - final _i46.CloudProvider provider; + final _i48.CloudProvider provider; @override String toString() { @@ -958,7 +960,7 @@ class PrivateServerLocationArgs { if (identical(this, other)) return true; if (other is! PrivateServerLocationArgs) return false; return key == other.key && - const _i48.ListEquality().equals(location, other.location) && + const _i50.ListEquality().equals(location, other.location) && selectedLocation == other.selectedLocation && provider == other.provider; } @@ -966,20 +968,20 @@ class PrivateServerLocationArgs { @override int get hashCode => key.hashCode ^ - const _i48.ListEquality().hash(location) ^ + const _i50.ListEquality().hash(location) ^ selectedLocation.hashCode ^ provider.hashCode; } /// generated route for /// [_i28.PrivateServerSetup] -class PrivateServerSetup extends _i44.PageRouteInfo { - const PrivateServerSetup({List<_i44.PageRouteInfo>? children}) +class PrivateServerSetup extends _i46.PageRouteInfo { + const PrivateServerSetup({List<_i46.PageRouteInfo>? children}) : super(PrivateServerSetup.name, initialChildren: children); static const String name = 'PrivateServerSetup'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i28.PrivateServerSetup(); @@ -990,13 +992,13 @@ class PrivateServerSetup extends _i44.PageRouteInfo { /// generated route for /// [_i29.PrivateSeverDetails] class PrivateServerDetails - extends _i44.PageRouteInfo { + extends _i46.PageRouteInfo { PrivateServerDetails({ - _i45.Key? key, + _i47.Key? key, required List accounts, - required _i46.CloudProvider provider, + required _i48.CloudProvider provider, bool isPreFilled = false, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( PrivateServerDetails.name, args: PrivateServerDetailsArgs( @@ -1010,7 +1012,7 @@ class PrivateServerDetails static const String name = 'PrivateServerDetails'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -1032,11 +1034,11 @@ class PrivateServerDetailsArgs { this.isPreFilled = false, }); - final _i45.Key? key; + final _i47.Key? key; final List accounts; - final _i46.CloudProvider provider; + final _i48.CloudProvider provider; final bool isPreFilled; @@ -1050,7 +1052,7 @@ class PrivateServerDetailsArgs { if (identical(this, other)) return true; if (other is! PrivateServerDetailsArgs) return false; return key == other.key && - const _i48.ListEquality().equals(accounts, other.accounts) && + const _i50.ListEquality().equals(accounts, other.accounts) && provider == other.provider && isPreFilled == other.isPreFilled; } @@ -1058,20 +1060,20 @@ class PrivateServerDetailsArgs { @override int get hashCode => key.hashCode ^ - const _i48.ListEquality().hash(accounts) ^ + const _i50.ListEquality().hash(accounts) ^ provider.hashCode ^ isPreFilled.hashCode; } /// generated route for /// [_i30.QrCodeScanner] -class QrCodeScanner extends _i44.PageRouteInfo { - const QrCodeScanner({List<_i44.PageRouteInfo>? children}) +class QrCodeScanner extends _i46.PageRouteInfo { + const QrCodeScanner({List<_i46.PageRouteInfo>? children}) : super(QrCodeScanner.name, initialChildren: children); static const String name = 'QrCodeScanner'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i30.QrCodeScanner(); @@ -1081,12 +1083,12 @@ class QrCodeScanner extends _i44.PageRouteInfo { /// generated route for /// [_i31.ReportIssue] -class ReportIssue extends _i44.PageRouteInfo { +class ReportIssue extends _i46.PageRouteInfo { ReportIssue({ - _i45.Key? key, + _i47.Key? key, String? description, String? type, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( ReportIssue.name, args: ReportIssueArgs(key: key, description: description, type: type), @@ -1095,7 +1097,7 @@ class ReportIssue extends _i44.PageRouteInfo { static const String name = 'ReportIssue'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs( @@ -1113,7 +1115,7 @@ class ReportIssue extends _i44.PageRouteInfo { class ReportIssueArgs { const ReportIssueArgs({this.key, this.description, this.type}); - final _i45.Key? key; + final _i47.Key? key; final String? description; @@ -1139,12 +1141,12 @@ class ReportIssueArgs { /// generated route for /// [_i32.ResetPassword] -class ResetPassword extends _i44.PageRouteInfo { +class ResetPassword extends _i46.PageRouteInfo { ResetPassword({ - _i45.Key? key, + _i47.Key? key, required String email, required String code, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( ResetPassword.name, args: ResetPasswordArgs(key: key, email: email, code: code), @@ -1153,7 +1155,7 @@ class ResetPassword extends _i44.PageRouteInfo { static const String name = 'ResetPassword'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -1169,7 +1171,7 @@ class ResetPassword extends _i44.PageRouteInfo { class ResetPasswordArgs { const ResetPasswordArgs({this.key, required this.email, required this.code}); - final _i45.Key? key; + final _i47.Key? key; final String email; @@ -1193,11 +1195,11 @@ class ResetPasswordArgs { /// generated route for /// [_i33.ResetPasswordEmail] -class ResetPasswordEmail extends _i44.PageRouteInfo { +class ResetPasswordEmail extends _i46.PageRouteInfo { ResetPasswordEmail({ - _i45.Key? key, + _i47.Key? key, String? email, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( ResetPasswordEmail.name, args: ResetPasswordEmailArgs(key: key, email: email), @@ -1206,7 +1208,7 @@ class ResetPasswordEmail extends _i44.PageRouteInfo { static const String name = 'ResetPasswordEmail'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs( @@ -1220,7 +1222,7 @@ class ResetPasswordEmail extends _i44.PageRouteInfo { class ResetPasswordEmailArgs { const ResetPasswordEmailArgs({this.key, this.email}); - final _i45.Key? key; + final _i47.Key? key; final String? email; @@ -1242,13 +1244,13 @@ class ResetPasswordEmailArgs { /// generated route for /// [_i34.ServerSelection] -class ServerSelection extends _i44.PageRouteInfo { - const ServerSelection({List<_i44.PageRouteInfo>? children}) +class ServerSelection extends _i46.PageRouteInfo { + const ServerSelection({List<_i46.PageRouteInfo>? children}) : super(ServerSelection.name, initialChildren: children); static const String name = 'ServerSelection'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i34.ServerSelection(); @@ -1258,13 +1260,13 @@ class ServerSelection extends _i44.PageRouteInfo { /// generated route for /// [_i35.Setting] -class Setting extends _i44.PageRouteInfo { - const Setting({List<_i44.PageRouteInfo>? children}) +class Setting extends _i46.PageRouteInfo { + const Setting({List<_i46.PageRouteInfo>? children}) : super(Setting.name, initialChildren: children); static const String name = 'Setting'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i35.Setting(); @@ -1274,13 +1276,13 @@ class Setting extends _i44.PageRouteInfo { /// generated route for /// [_i36.SignInEmail] -class SignInEmail extends _i44.PageRouteInfo { - const SignInEmail({List<_i44.PageRouteInfo>? children}) +class SignInEmail extends _i46.PageRouteInfo { + const SignInEmail({List<_i46.PageRouteInfo>? children}) : super(SignInEmail.name, initialChildren: children); static const String name = 'SignInEmail'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i36.SignInEmail(); @@ -1290,12 +1292,12 @@ class SignInEmail extends _i44.PageRouteInfo { /// generated route for /// [_i37.SignInPassword] -class SignInPassword extends _i44.PageRouteInfo { +class SignInPassword extends _i46.PageRouteInfo { SignInPassword({ - _i45.Key? key, + _i47.Key? key, required String email, bool fromChangeEmail = false, - List<_i44.PageRouteInfo>? children, + List<_i46.PageRouteInfo>? children, }) : super( SignInPassword.name, args: SignInPasswordArgs( @@ -1308,7 +1310,7 @@ class SignInPassword extends _i44.PageRouteInfo { static const String name = 'SignInPassword'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -1328,7 +1330,7 @@ class SignInPasswordArgs { this.fromChangeEmail = false, }); - final _i45.Key? key; + final _i47.Key? key; final String email; @@ -1354,13 +1356,13 @@ class SignInPasswordArgs { /// generated route for /// [_i38.SmartRouting] -class SmartRouting extends _i44.PageRouteInfo { - const SmartRouting({List<_i44.PageRouteInfo>? children}) +class SmartRouting extends _i46.PageRouteInfo { + const SmartRouting({List<_i46.PageRouteInfo>? children}) : super(SmartRouting.name, initialChildren: children); static const String name = 'SmartRouting'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i38.SmartRouting(); @@ -1370,13 +1372,13 @@ class SmartRouting extends _i44.PageRouteInfo { /// generated route for /// [_i39.SplitTunneling] -class SplitTunneling extends _i44.PageRouteInfo { - const SplitTunneling({List<_i44.PageRouteInfo>? children}) +class SplitTunneling extends _i46.PageRouteInfo { + const SplitTunneling({List<_i46.PageRouteInfo>? children}) : super(SplitTunneling.name, initialChildren: children); static const String name = 'SplitTunneling'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i39.SplitTunneling(); @@ -1386,13 +1388,13 @@ class SplitTunneling extends _i44.PageRouteInfo { /// generated route for /// [_i40.SplitTunnelingInfo] -class SplitTunnelingInfo extends _i44.PageRouteInfo { - const SplitTunnelingInfo({List<_i44.PageRouteInfo>? children}) +class SplitTunnelingInfo extends _i46.PageRouteInfo { + const SplitTunnelingInfo({List<_i46.PageRouteInfo>? children}) : super(SplitTunnelingInfo.name, initialChildren: children); static const String name = 'SplitTunnelingInfo'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i40.SplitTunnelingInfo(); @@ -1402,13 +1404,13 @@ class SplitTunnelingInfo extends _i44.PageRouteInfo { /// generated route for /// [_i41.Support] -class Support extends _i44.PageRouteInfo { - const Support({List<_i44.PageRouteInfo>? children}) +class Support extends _i46.PageRouteInfo { + const Support({List<_i46.PageRouteInfo>? children}) : super(Support.name, initialChildren: children); static const String name = 'Support'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { return const _i41.Support(); @@ -1417,33 +1419,65 @@ class Support extends _i44.PageRouteInfo { } /// generated route for -/// [_i42.VPNSetting] -class VPNSetting extends _i44.PageRouteInfo { - const VPNSetting({List<_i44.PageRouteInfo>? children}) +/// [_i42.UnboundedScreen] +class UnboundedScreen extends _i46.PageRouteInfo { + const UnboundedScreen({List<_i46.PageRouteInfo>? children}) + : super(UnboundedScreen.name, initialChildren: children); + + static const String name = 'UnboundedScreen'; + + static _i46.PageInfo page = _i46.PageInfo( + name, + builder: (data) { + return const _i42.UnboundedScreen(); + }, + ); +} + +/// generated route for +/// [_i43.UnboundedSettingsScreen] +class UnboundedSettingsScreen extends _i46.PageRouteInfo { + const UnboundedSettingsScreen({List<_i46.PageRouteInfo>? children}) + : super(UnboundedSettingsScreen.name, initialChildren: children); + + static const String name = 'UnboundedSettingsScreen'; + + static _i46.PageInfo page = _i46.PageInfo( + name, + builder: (data) { + return const _i43.UnboundedSettingsScreen(); + }, + ); +} + +/// generated route for +/// [_i44.VPNSetting] +class VPNSetting extends _i46.PageRouteInfo { + const VPNSetting({List<_i46.PageRouteInfo>? children}) : super(VPNSetting.name, initialChildren: children); static const String name = 'VPNSetting'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { - return const _i42.VPNSetting(); + return const _i44.VPNSetting(); }, ); } /// generated route for -/// [_i43.WebsiteSplitTunneling] -class WebsiteSplitTunneling extends _i44.PageRouteInfo { - const WebsiteSplitTunneling({List<_i44.PageRouteInfo>? children}) +/// [_i45.WebsiteSplitTunneling] +class WebsiteSplitTunneling extends _i46.PageRouteInfo { + const WebsiteSplitTunneling({List<_i46.PageRouteInfo>? children}) : super(WebsiteSplitTunneling.name, initialChildren: children); static const String name = 'WebsiteSplitTunneling'; - static _i44.PageInfo page = _i44.PageInfo( + static _i46.PageInfo page = _i46.PageInfo( name, builder: (data) { - return const _i43.WebsiteSplitTunneling(); + return const _i45.WebsiteSplitTunneling(); }, ); } diff --git a/lib/core/services/db/objectbox-model.json b/lib/core/services/db/objectbox-model.json index 064ce660ae..d39b299ede 100644 --- a/lib/core/services/db/objectbox-model.json +++ b/lib/core/services/db/objectbox-model.json @@ -59,7 +59,7 @@ }, { "id": "2:687217704776011576", - "lastPropertyId": "23:853140105317312758", + "lastPropertyId": "27:1490069847328736408", "name": "AppSetting", "properties": [ { @@ -147,6 +147,26 @@ "id": "23:853140105317312758", "name": "themeMode", "type": 9 + }, + { + "id": "24:7256187684976579666", + "name": "unboundedEnabled", + "type": 1 + }, + { + "id": "25:4271307681061492505", + "name": "autoEnableUnbounded", + "type": 1 + }, + { + "id": "26:6311603266760598594", + "name": "unboundedWelcomeSeen", + "type": 1 + }, + { + "id": "27:1490069847328736408", + "name": "hideUnbounded", + "type": 1 } ], "relations": [] diff --git a/lib/core/services/db/objectbox.g.dart b/lib/core/services/db/objectbox.g.dart index 83066d1841..d839ab083b 100644 --- a/lib/core/services/db/objectbox.g.dart +++ b/lib/core/services/db/objectbox.g.dart @@ -93,7 +93,7 @@ final _entities = [ obx_int.ModelEntity( id: const obx_int.IdUid(2, 687217704776011576), name: 'AppSetting', - lastPropertyId: const obx_int.IdUid(23, 853140105317312758), + lastPropertyId: const obx_int.IdUid(27, 1490069847328736408), flags: 0, properties: [ obx_int.ModelProperty( @@ -198,6 +198,30 @@ final _entities = [ type: 9, flags: 0, ), + obx_int.ModelProperty( + id: const obx_int.IdUid(24, 7256187684976579666), + name: 'unboundedEnabled', + type: 1, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(25, 4271307681061492505), + name: 'autoEnableUnbounded', + type: 1, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(26, 6311603266760598594), + name: 'unboundedWelcomeSeen', + type: 1, + flags: 0, + ), + obx_int.ModelProperty( + id: const obx_int.IdUid(27, 1490069847328736408), + name: 'hideUnbounded', + type: 1, + flags: 0, + ), ], relations: [], backlinks: [], @@ -1014,7 +1038,7 @@ obx_int.ModelDefinition getObjectBoxModel() { final dataCapThresholdOffset = fbb.writeString(object.dataCapThreshold); final environmentOffset = fbb.writeString(object.environment); final themeModeOffset = fbb.writeString(object.themeMode); - fbb.startTable(24); + fbb.startTable(28); fbb.addInt64(0, object.id); fbb.addBool(1, object.isPro); fbb.addBool(2, object.isSplitTunnelingOn); @@ -1032,6 +1056,10 @@ obx_int.ModelDefinition getObjectBoxModel() { fbb.addBool(20, object.onboardingCompleted); fbb.addOffset(21, environmentOffset); fbb.addOffset(22, themeModeOffset); + fbb.addBool(23, object.unboundedEnabled); + fbb.addBool(24, object.autoEnableUnbounded); + fbb.addBool(25, object.unboundedWelcomeSeen); + fbb.addBool(26, object.hideUnbounded); fbb.finish(fbb.endTable()); return object.id; }, @@ -1119,6 +1147,30 @@ obx_int.ModelDefinition getObjectBoxModel() { final environmentParam = const fb.StringReader( asciiOptimization: true, ).vTableGet(buffer, rootOffset, 46, ''); + final unboundedEnabledParam = const fb.BoolReader().vTableGet( + buffer, + rootOffset, + 50, + false, + ); + final autoEnableUnboundedParam = const fb.BoolReader().vTableGet( + buffer, + rootOffset, + 52, + false, + ); + final unboundedWelcomeSeenParam = const fb.BoolReader().vTableGet( + buffer, + rootOffset, + 54, + false, + ); + final hideUnboundedParam = const fb.BoolReader().vTableGet( + buffer, + rootOffset, + 56, + false, + ); final object = AppSetting( id: idParam, isPro: isProParam, @@ -1137,6 +1189,10 @@ obx_int.ModelDefinition getObjectBoxModel() { onboardingCompleted: onboardingCompletedParam, themeMode: themeModeParam, environment: environmentParam, + unboundedEnabled: unboundedEnabledParam, + autoEnableUnbounded: autoEnableUnboundedParam, + unboundedWelcomeSeen: unboundedWelcomeSeenParam, + hideUnbounded: hideUnboundedParam, ); return object; @@ -2077,6 +2133,26 @@ class AppSetting_ { static final themeMode = obx.QueryStringProperty( _entities[1].properties[16], ); + + /// See [AppSetting.unboundedEnabled]. + static final unboundedEnabled = obx.QueryBooleanProperty( + _entities[1].properties[17], + ); + + /// See [AppSetting.autoEnableUnbounded]. + static final autoEnableUnbounded = obx.QueryBooleanProperty( + _entities[1].properties[18], + ); + + /// See [AppSetting.unboundedWelcomeSeen]. + static final unboundedWelcomeSeen = obx.QueryBooleanProperty( + _entities[1].properties[19], + ); + + /// See [AppSetting.hideUnbounded]. + static final hideUnbounded = obx.QueryBooleanProperty( + _entities[1].properties[20], + ); } /// [DeviceEntity] entity fields to define ObjectBox queries. diff --git a/lib/core/services/geo_lookup_service.dart b/lib/core/services/geo_lookup_service.dart new file mode 100644 index 0000000000..50150fe375 --- /dev/null +++ b/lib/core/services/geo_lookup_service.dart @@ -0,0 +1,173 @@ +import 'dart:convert'; + +import 'package:flutter_earth_globe/globe_coordinates.dart'; +import 'package:http/http.dart' as http; + +class GeoLookupService { + static const _geoUrl = 'https://geo.getiantem.org'; + + // ISO country code → approximate centre coordinates + static const _countries = { + 'AF': (lat: 33.0, lng: 65.0), + 'AL': (lat: 41.0, lng: 20.0), + 'DZ': (lat: 28.0, lng: 3.0), + 'AD': (lat: 42.5, lng: 1.6), + 'AO': (lat: -12.5, lng: 18.5), + 'AR': (lat: -34.0, lng: -64.0), + 'AM': (lat: 40.0, lng: 45.0), + 'AU': (lat: -27.0, lng: 133.0), + 'AT': (lat: 47.33, lng: 13.33), + 'AZ': (lat: 40.5, lng: 47.5), + 'BD': (lat: 24.0, lng: 90.0), + 'BY': (lat: 53.0, lng: 28.0), + 'BE': (lat: 50.83, lng: 4.0), + 'BJ': (lat: 9.5, lng: 2.25), + 'BO': (lat: -17.0, lng: -65.0), + 'BA': (lat: 44.0, lng: 18.0), + 'BR': (lat: -10.0, lng: -55.0), + 'BG': (lat: 43.0, lng: 25.0), + 'KH': (lat: 13.0, lng: 105.0), + 'CM': (lat: 6.0, lng: 12.0), + 'CA': (lat: 60.0, lng: -95.0), + 'CL': (lat: -30.0, lng: -71.0), + 'CN': (lat: 35.0, lng: 105.0), + 'CO': (lat: 4.0, lng: -72.0), + 'CD': (lat: 0.0, lng: 25.0), + 'CR': (lat: 10.0, lng: -84.0), + 'HR': (lat: 45.17, lng: 15.5), + 'CU': (lat: 21.5, lng: -80.0), + 'CZ': (lat: 49.75, lng: 15.5), + 'DK': (lat: 56.0, lng: 10.0), + 'DO': (lat: 19.0, lng: -70.67), + 'EC': (lat: -2.0, lng: -77.5), + 'EG': (lat: 27.0, lng: 30.0), + 'SV': (lat: 13.83, lng: -88.92), + 'EE': (lat: 59.0, lng: 26.0), + 'ET': (lat: 8.0, lng: 38.0), + 'FI': (lat: 64.0, lng: 26.0), + 'FR': (lat: 46.0, lng: 2.0), + 'GE': (lat: 42.0, lng: 43.5), + 'DE': (lat: 51.0, lng: 9.0), + 'GH': (lat: 8.0, lng: -2.0), + 'GR': (lat: 39.0, lng: 22.0), + 'GT': (lat: 15.5, lng: -90.25), + 'HN': (lat: 15.0, lng: -86.5), + 'HK': (lat: 22.25, lng: 114.17), + 'HU': (lat: 47.0, lng: 20.0), + 'IS': (lat: 65.0, lng: -18.0), + 'IN': (lat: 20.0, lng: 77.0), + 'ID': (lat: -5.0, lng: 120.0), + 'IR': (lat: 32.0, lng: 53.0), + 'IQ': (lat: 33.0, lng: 44.0), + 'IE': (lat: 53.0, lng: -8.0), + 'IL': (lat: 31.5, lng: 34.75), + 'IT': (lat: 42.83, lng: 12.83), + 'CI': (lat: 8.0, lng: -5.0), + 'JP': (lat: 36.0, lng: 138.0), + 'JO': (lat: 31.0, lng: 36.0), + 'KZ': (lat: 48.0, lng: 68.0), + 'KE': (lat: 1.0, lng: 38.0), + 'KR': (lat: 37.0, lng: 127.5), + 'KW': (lat: 29.34, lng: 47.66), + 'KG': (lat: 41.0, lng: 75.0), + 'LA': (lat: 18.0, lng: 105.0), + 'LV': (lat: 57.0, lng: 25.0), + 'LB': (lat: 33.83, lng: 35.83), + 'LT': (lat: 56.0, lng: 24.0), + 'MG': (lat: -20.0, lng: 47.0), + 'MY': (lat: 2.5, lng: 112.5), + 'ML': (lat: 17.0, lng: -4.0), + 'MX': (lat: 23.0, lng: -102.0), + 'MD': (lat: 47.0, lng: 29.0), + 'MN': (lat: 46.0, lng: 105.0), + 'MA': (lat: 32.0, lng: -5.0), + 'MZ': (lat: -18.25, lng: 35.0), + 'MM': (lat: 22.0, lng: 98.0), + 'NP': (lat: 28.0, lng: 84.0), + 'NL': (lat: 52.5, lng: 5.75), + 'NZ': (lat: -41.0, lng: 174.0), + 'NI': (lat: 13.0, lng: -85.0), + 'NG': (lat: 10.0, lng: 8.0), + 'NO': (lat: 62.0, lng: 10.0), + 'OM': (lat: 21.0, lng: 57.0), + 'PK': (lat: 30.0, lng: 70.0), + 'PA': (lat: 9.0, lng: -80.0), + 'PY': (lat: -23.0, lng: -58.0), + 'PE': (lat: -10.0, lng: -76.0), + 'PH': (lat: 13.0, lng: 122.0), + 'PL': (lat: 52.0, lng: 20.0), + 'PT': (lat: 39.5, lng: -8.0), + 'QA': (lat: 25.5, lng: 51.25), + 'RO': (lat: 46.0, lng: 25.0), + 'RU': (lat: 60.0, lng: 100.0), + 'SA': (lat: 25.0, lng: 45.0), + 'SN': (lat: 14.0, lng: -14.0), + 'RS': (lat: 44.0, lng: 21.0), + 'SG': (lat: 1.37, lng: 103.8), + 'SK': (lat: 48.67, lng: 19.5), + 'SI': (lat: 46.0, lng: 15.0), + 'ZA': (lat: -29.0, lng: 24.0), + 'ES': (lat: 40.0, lng: -4.0), + 'LK': (lat: 7.0, lng: 81.0), + 'SE': (lat: 62.0, lng: 15.0), + 'CH': (lat: 47.0, lng: 8.0), + 'SY': (lat: 35.0, lng: 38.0), + 'TW': (lat: 23.5, lng: 121.0), + 'TJ': (lat: 39.0, lng: 71.0), + 'TZ': (lat: -6.0, lng: 35.0), + 'TH': (lat: 15.0, lng: 100.0), + 'TN': (lat: 34.0, lng: 9.0), + 'TR': (lat: 39.0, lng: 35.0), + 'TM': (lat: 40.0, lng: 60.0), + 'UA': (lat: 49.0, lng: 32.0), + 'AE': (lat: 24.0, lng: 54.0), + 'GB': (lat: 54.0, lng: -2.0), + 'US': (lat: 38.0, lng: -97.0), + 'UY': (lat: -33.0, lng: -56.0), + 'UZ': (lat: 41.0, lng: 64.0), + 'VE': (lat: 8.0, lng: -66.0), + 'VN': (lat: 16.0, lng: 106.0), + 'YE': (lat: 15.0, lng: 48.0), + 'ZM': (lat: -15.0, lng: 30.0), + 'ZW': (lat: -20.0, lng: 30.0), + }; + + static GlobeCoordinates _isoToCoords(String iso) { + final c = _countries[iso] ?? _countries['US']!; + return GlobeCoordinates(c.lat, c.lng); + } + + /// Looks up the current device's location (no IP argument). + static Future selfLookup() async { + try { + final response = await http + .get(Uri.parse('$_geoUrl/')) + .timeout(const Duration(seconds: 5)); + if (response.statusCode == 200) { + final data = jsonDecode(response.body) as Map; + final iso = + (data['Country'] as Map?)?['IsoCode'] as String? ?? + 'US'; + return _isoToCoords(iso); + } + } catch (_) {} + return _isoToCoords('US'); + } + + /// Looks up the country for a peer [ip] address. + static Future peerLookup(String ip) async { + try { + final response = await http + .get(Uri.parse('$_geoUrl/$ip')) + .timeout(const Duration(seconds: 5)); + if (response.statusCode == 200) { + final data = jsonDecode(response.body) as Map; + final iso = + (data['Country'] as Map?)?['IsoCode'] as String? ?? + 'IR'; + return _isoToCoords(iso); + } + } catch (_) {} + return _isoToCoords('IR'); + } +} diff --git a/lib/features/home/provider/app_event_notifier.dart b/lib/features/home/provider/app_event_notifier.dart index 591399b029..12d8ad1a92 100644 --- a/lib/features/home/provider/app_event_notifier.dart +++ b/lib/features/home/provider/app_event_notifier.dart @@ -5,6 +5,7 @@ import 'package:i18n_extension/default.i18n.dart'; import 'package:lantern/core/common/app_eum.dart'; import 'package:lantern/core/models/datacap_info.dart'; import 'package:lantern/core/models/entity/server_location_entity.dart'; +import 'package:lantern/core/models/unbounded_connection_event.dart'; import 'package:lantern/core/services/logger_service.dart'; import 'package:lantern/features/home/provider/home_notifier.dart'; import 'package:lantern/features/vpn/provider/available_servers_notifier.dart'; @@ -14,6 +15,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../core/models/available_servers.dart'; import 'data_cap_info_provider.dart' show dataCapInfoProvider; +import '../../unbounded/provider/unbounded_notifier.dart' + show unboundedProvider; part 'app_event_notifier.g.dart'; @@ -87,6 +90,15 @@ class AppEventNotifier extends _$AppEventNotifier { appLogger.error('Error parsing data-cap-event: $e'); } break; + case 'unbounded-connection': + try { + final data = jsonDecode(event.message); + final connEvent = UnboundedConnectionEvent.fromJson(data); + ref.read(unboundedProvider.notifier).onConnectionEvent(connEvent); + } catch (e) { + appLogger.error('Error parsing unbounded-connection event: $e'); + } + break; default: break; } diff --git a/lib/features/home/provider/app_event_notifier.g.dart b/lib/features/home/provider/app_event_notifier.g.dart index 1e2d10aaa8..50612e750e 100644 --- a/lib/features/home/provider/app_event_notifier.g.dart +++ b/lib/features/home/provider/app_event_notifier.g.dart @@ -42,7 +42,7 @@ final class AppEventNotifierProvider AppEventNotifier create() => AppEventNotifier(); } -String _$appEventNotifierHash() => r'836d8ae705eb943827db277d28cb55fc1c4ac19d'; +String _$appEventNotifierHash() => r'c51a71f75787e810f536afc9c340a2c2a330895d'; /// Listens for application-wide events and triggers corresponding actions. /// This can be used for all listening to events that go sends and handling them diff --git a/lib/features/home/provider/app_setting_notifier.dart b/lib/features/home/provider/app_setting_notifier.dart index 5f1597f15a..846016b8cd 100644 --- a/lib/features/home/provider/app_setting_notifier.dart +++ b/lib/features/home/provider/app_setting_notifier.dart @@ -106,6 +106,31 @@ class AppSettingNotifier extends _$AppSettingNotifier { }); } + void setUnboundedEnabled(bool value) { + final prev = state.unboundedEnabled; + update(state.copyWith(unboundedEnabled: value)); + + final svc = ref.read(lanternServiceProvider); + svc.setUnboundedEnabled(value).then((res) { + res.match((err) { + appLogger.error('setUnboundedEnabled failed: ${err.error}'); + update(state.copyWith(unboundedEnabled: prev)); + }, (_) {}); + }); + } + + void setAutoEnableUnbounded(bool value) { + update(state.copyWith(autoEnableUnbounded: value)); + } + + void setUnboundedWelcomeSeen(bool value) { + update(state.copyWith(unboundedWelcomeSeen: value)); + } + + void setHideUnbounded(bool value) { + update(state.copyWith(hideUnbounded: value)); + } + void updateAnonymousDataConsent(bool value) { update(state.copyWith(telemetryConsent: value)); updateTelemetryConsent(value); diff --git a/lib/features/home/provider/app_setting_notifier.g.dart b/lib/features/home/provider/app_setting_notifier.g.dart index 04531964c4..91b8fb52f3 100644 --- a/lib/features/home/provider/app_setting_notifier.g.dart +++ b/lib/features/home/provider/app_setting_notifier.g.dart @@ -42,7 +42,7 @@ final class AppSettingNotifierProvider } String _$appSettingNotifierHash() => - r'4e2663a63cead2d7429e8ae9d728439d2d9e3d21'; + r'6830d1af12a28788b3cd599e7fe6d957a38498b2'; abstract class _$AppSettingNotifier extends $Notifier { AppSetting build(); diff --git a/lib/features/setting/setting.dart b/lib/features/setting/setting.dart index 7b91e42b75..439c5ee66e 100644 --- a/lib/features/setting/setting.dart +++ b/lib/features/setting/setting.dart @@ -214,24 +214,41 @@ class _SettingState extends ConsumerState { ), ), const SizedBox(height: 4), - Card( - child: AppTile( - minHeight: 72, - icon: AppImagePaths.lanternLogoRounded, - iconUseThemeColor: false, - trailing: AppImage(path: AppImagePaths.outsideBrowser), - label: 'unbounded'.i18n, - subtitle: Text( - 'help_fight_global_internet_censorship'.i18n, - style: textTheme.labelMedium!.copyWith( - color: context.textTertiary, - ), + if (!appSetting.hideUnbounded) + Card( + child: Column( + children: [ + AppTile( + minHeight: 72, + icon: AppImagePaths.lanternLogoRounded, + iconUseThemeColor: false, + trailing: AppImage( + path: AppImagePaths.arrowForward, + height: 20, + ), + label: 'unbounded'.i18n, + subtitle: Text( + 'help_fight_global_internet_censorship'.i18n, + style: textTheme.labelMedium!.copyWith( + color: context.textTertiary, + ), + ), + onPressed: () { + appRouter.push(const UnboundedScreen()); + }, + ), + DividerSpace(), + AppTile( + label: 'Unbounded Settings', + icon: AppImagePaths.lanternLogoRounded, + iconUseThemeColor: false, + onPressed: () { + appRouter.push(const UnboundedSettingsScreen()); + }, + ), + ], ), - onPressed: () { - UrlUtils.openUrl(AppUrls.unbounded); - }, ), - ), SizedBox(height: defaultSize), ], ), diff --git a/lib/features/unbounded/provider/unbounded_notifier.dart b/lib/features/unbounded/provider/unbounded_notifier.dart new file mode 100644 index 0000000000..bec491bdaf --- /dev/null +++ b/lib/features/unbounded/provider/unbounded_notifier.dart @@ -0,0 +1,40 @@ +import 'dart:async'; + +import 'package:lantern/core/models/unbounded_connection_event.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'unbounded_notifier.g.dart'; + +@Riverpod(keepAlive: true) +class UnboundedNotifier extends _$UnboundedNotifier { + final _eventController = + StreamController.broadcast(); + + /// Individual connection events for the globe view to animate arcs. + Stream get connectionEvents => + _eventController.stream; + + @override + UnboundedStats build() { + ref.onDispose(_eventController.close); + return const UnboundedStats(); + } + + /// Called by [AppEventNotifier] whenever an `unbounded-connection` event + /// arrives from the Go bridge. + void onConnectionEvent(UnboundedConnectionEvent event) { + final s = state; + if (event.state == 1) { + state = UnboundedStats( + activeCount: s.activeCount + 1, + totalCount: s.totalCount + 1, + ); + } else if (event.state == -1) { + state = UnboundedStats( + activeCount: (s.activeCount - 1).clamp(0, s.activeCount), + totalCount: s.totalCount, + ); + } + _eventController.add(event); + } +} diff --git a/lib/features/unbounded/provider/unbounded_notifier.g.dart b/lib/features/unbounded/provider/unbounded_notifier.g.dart new file mode 100644 index 0000000000..f340d335fb --- /dev/null +++ b/lib/features/unbounded/provider/unbounded_notifier.g.dart @@ -0,0 +1,60 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'unbounded_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(UnboundedNotifier) +const unboundedProvider = UnboundedNotifierProvider._(); + +final class UnboundedNotifierProvider + extends $NotifierProvider { + const UnboundedNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'unboundedProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$unboundedNotifierHash(); + + @$internal + @override + UnboundedNotifier create() => UnboundedNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(UnboundedStats value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$unboundedNotifierHash() => r'f5d26079a98de84fe0b96f881a0030c28902c067'; + +abstract class _$UnboundedNotifier extends $Notifier { + UnboundedStats build(); + @$mustCallSuper + @override + void runBuild() { + final created = build(); + final ref = this.ref as $Ref; + final element = ref.element as $ClassProviderElement< + AnyNotifier, + UnboundedStats, + Object?, + Object?>; + element.handleValue(ref, created); + } +} diff --git a/lib/features/unbounded/unbounded.dart b/lib/features/unbounded/unbounded.dart new file mode 100644 index 0000000000..0dca42861a --- /dev/null +++ b/lib/features/unbounded/unbounded.dart @@ -0,0 +1,352 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:auto_route/annotations.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_earth_globe/flutter_earth_globe.dart'; +import 'package:flutter_earth_globe/flutter_earth_globe_controller.dart'; +import 'package:flutter_earth_globe/globe_coordinates.dart'; +import 'package:flutter_earth_globe/point.dart'; +import 'package:flutter_earth_globe/point_connection.dart'; +import 'package:flutter_earth_globe/point_connection_style.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:lantern/core/common/common.dart'; +import 'package:lantern/core/models/entity/app_setting_entity.dart'; +import 'package:lantern/core/models/unbounded_connection_event.dart'; +import 'package:lantern/core/services/geo_lookup_service.dart'; +import 'package:lantern/core/widgets/info_row.dart'; +import 'package:lantern/core/widgets/switch_button.dart'; +import 'package:lantern/features/home/provider/app_setting_notifier.dart'; +import 'package:lantern/features/unbounded/provider/unbounded_notifier.dart'; + +@RoutePage(name: 'UnboundedScreen') +class UnboundedScreen extends HookConsumerWidget { + const UnboundedScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textTheme = TextTheme.of(context); + final appSetting = ref.watch(appSettingProvider); + final settingNotifier = ref.read(appSettingProvider.notifier); + final stats = ref.watch(unboundedProvider); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!appSetting.unboundedWelcomeSeen) { + settingNotifier.setUnboundedWelcomeSeen(true); + _showWelcomeDialog(context); + } + }); + + return BaseScreen( + title: 'unbounded'.i18n, + body: Column( + children: [ + InfoRow(text: 'help_others_bypass_censorship'.i18n), + SizedBox(height: defaultSize), + Expanded(flex: 3, child: _GlobeView()), + SizedBox(height: defaultSize), + _buildStatusCard( + context, appSetting, stats, settingNotifier, textTheme), + SizedBox(height: defaultSize), + _buildAutoEnableCard(context, appSetting, settingNotifier, textTheme), + SizedBox(height: defaultSize), + ], + ), + ); + } + + // ── Card builders ────────────────────────────────────────────────────────── + + Widget _buildStatusCard( + BuildContext context, + AppSetting appSetting, + UnboundedStats stats, + AppSettingNotifier notifier, + TextTheme textTheme, + ) { + return AppCard( + padding: EdgeInsets.zero, + child: Column( + children: [ + AppTile( + label: 'status'.i18n, + labelWidget: Row( + children: [ + Text( + '${'status'.i18n}: ', + style: + textTheme.bodyLarge!.copyWith(color: context.textPrimary), + ), + Text( + appSetting.unboundedEnabled + ? 'enabled'.i18n + : 'disabled'.i18n, + style: textTheme.titleMedium!.copyWith( + color: appSetting.unboundedEnabled + ? AppColors.green6 + : context.textTertiary, + ), + ), + ], + ), + tileTextStyle: textTheme.bodyLarge, + icon: Icon(Icons.language, color: context.textPrimary), + trailing: SwitchButton( + value: appSetting.unboundedEnabled, + onChanged: notifier.setUnboundedEnabled, + ), + ), + DividerSpace(), + AppTile( + label: 'people_you_are_helping_right_now'.i18n, + tileTextStyle: textTheme.bodyLarge, + icon: Icon(Icons.person_outline, color: context.textPrimary), + trailing: Text( + stats.activeCount.toString(), + style: textTheme.titleMedium!.copyWith(color: context.textLink), + ), + ), + DividerSpace(), + AppTile( + label: 'total_people_helped_to_date'.i18n, + tileTextStyle: textTheme.bodyLarge!, + icon: Icon(Icons.people, color: context.textPrimary), + trailing: Text( + stats.totalCount.toString(), + style: textTheme.titleMedium!.copyWith(color: context.textLink), + ), + ), + ], + ), + ); + } + + Widget _buildAutoEnableCard( + BuildContext context, + AppSetting appSetting, + AppSettingNotifier notifier, + TextTheme textTheme, + ) { + return AppCard( + padding: EdgeInsets.zero, + child: AppTile( + icon: AppImagePaths.autoMode, + label: 'auto_enable_unbounded'.i18n, + subtitle: Text( + 'turn_on_automatically_when_lantern_is_open'.i18n, + style: textTheme.labelMedium!.copyWith(color: context.textTertiary), + ), + trailing: Checkbox( + value: appSetting.autoEnableUnbounded, + onChanged: (value) => notifier.setAutoEnableUnbounded(value!), + ), + ), + ); + } + + // ── Welcome dialog ───────────────────────────────────────────────────────── + + void _showWelcomeDialog(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + AppDialog.customDialog( + context: context, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 24), + Icon(Icons.handshake_outlined, size: 48, color: context.textLink), + const SizedBox(height: 16), + Text( + 'welcome_to_unbounded'.i18n, + style: textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + Text( + 'unbounded_description'.i18n, + style: textTheme.bodyMedium!.copyWith(color: context.textSecondary), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + 'your_connection_stays_secure_and_private'.i18n, + style: textTheme.bodyMedium!.copyWith(color: context.textSecondary), + textAlign: TextAlign.center, + ), + ], + ), + action: [ + AppTextButton( + label: 'learn_more'.i18n, + onPressed: () => UrlUtils.openUrl(AppUrls.unbounded), + ), + AppTextButton( + label: 'got_it'.i18n, + onPressed: () => appRouter.maybePop(), + ), + ], + ); + } +} + +// ── Globe widget ─────────────────────────────────────────────────────────────── + +class _GlobeView extends ConsumerStatefulWidget { + @override + ConsumerState<_GlobeView> createState() => _GlobeViewState(); +} + +class _GlobeViewState extends ConsumerState<_GlobeView> { + // Arc: blue4 at 75 % opacity + static final _arcColor = AppColors.blue4.withValues(alpha: 0.75); + + // Dots: blue4 (origin) and yellow3 (peer) both at 15 % opacity + static final _originPointColor = AppColors.blue4.withValues(alpha: 0.15); + static final _peerPointColor = AppColors.yellow3.withValues(alpha: 0.15); + + // Atmosphere: solid blue4 (dark theme) / blue6 (light theme) + static const _atmosphereDark = AppColors.blue4; + static const _atmosphereLight = AppColors.blue6; + + final FlutterEarthGlobeController _globeController = + FlutterEarthGlobeController( + isRotating: true, + rotationSpeed: 0.04, + zoom: 0, + isZoomEnabled: false, + showAtmosphere: true, + atmosphereColor: _atmosphereDark, + atmosphereOpacity: 0.2, + atmosphereBlur: 20, + ); + + StreamSubscription? _eventSub; + GlobeCoordinates? _originCoords; + + @override + void initState() { + super.initState(); + + _globeController.onLoaded = () { + if (!mounted) return; + _applyTheme(); + }; + + listingToConnectionEvents(); + _initOrigin(); + } + + void listingToConnectionEvents() { + _eventSub = ref + .read(unboundedProvider.notifier) + .connectionEvents + .listen(_handleConnectionEvent); + } + + @override + void dispose() { + _eventSub?.cancel(); + _globeController.dispose(); + super.dispose(); + } + + void _applyTheme() { + final isDark = context.isDark; + _globeController.loadSurface(AssetImage( + isDark ? AppImagePaths.darkMap : AppImagePaths.lightMap, + )); + _globeController.atmosphereColor = + isDark ? _atmosphereDark : _atmosphereLight; + } + + Future _initOrigin() async { + final coords = await GeoLookupService.selfLookup(); + if (!mounted) return; + _originCoords = coords; + _globeController.addPoint(Point( + id: 'origin', + coordinates: coords, + style: PointStyle(color: _originPointColor, size: 8), + )); + } + + Future _handleConnectionEvent(UnboundedConnectionEvent event) async { + if (event.state == 1 && event.addr.isNotEmpty) { + await _addPeer(event.workerIdx, event.addr); + } else if (event.state == -1) { + _removePeer(event.workerIdx); + } + } + + Future _addPeer(int workerIdx, String addr) async { + final coords = await _resolveCoords(addr); + if (!mounted) return; + + _globeController.addPointConnection(PointConnection( + id: 'conn_$workerIdx', + start: _originCoords ?? const GlobeCoordinates(0, 0), + end: coords, + curveScale: .6, + style: PointConnectionStyle( + color: _arcColor, + lineWidth: 3, + type: PointConnectionType.solid, + dashAnimateTime: 1000, + dashSize: 13, + spacing: 15, + dotSize: 10, + animateOnAdd: true, + ), + )); + + _globeController.addPoint(Point( + id: 'peer_$workerIdx', + coordinates: coords, + style: PointStyle(color: _peerPointColor, size: 6), + )); + } + + void _removePeer(int workerIdx) { + _globeController.removePointConnection('conn_$workerIdx'); + _globeController.removePoint('peer_$workerIdx'); + } + + Future _resolveCoords(String addr) => + GeoLookupService.peerLookup(addr); + + // ── Build ────────────────────────────────────────────────────────────────── + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + // RotatingGlobe uses MediaQuery.size to center the sphere on screen. + // We override it so the globe centers within this widget's bounds + // rather than the full screen. + final widgetSize = Size(constraints.maxWidth, constraints.maxHeight); + + // 70 % of the shorter edge keeps the atmosphere glow from being clipped. + final radius = + min(constraints.maxWidth, constraints.maxHeight) / 2 * 0.7; + + // ClipRect prevents arcs from painting outside this widget's box. + return ClipRect( + child: MediaQuery( + data: MediaQueryData(size: widgetSize), + child: Stack( + children: [ + Positioned.fill( + child: FlutterEarthGlobe( + controller: _globeController, + radius: radius, + alignment: const Alignment(0.0, 0.1), + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/unbounded/unbounded_settings.dart b/lib/features/unbounded/unbounded_settings.dart new file mode 100644 index 0000000000..0e11b4ef64 --- /dev/null +++ b/lib/features/unbounded/unbounded_settings.dart @@ -0,0 +1,68 @@ +import 'package:auto_route/annotations.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:lantern/core/common/common.dart'; +import 'package:lantern/core/widgets/switch_button.dart'; +import 'package:lantern/features/home/provider/app_setting_notifier.dart'; + +@RoutePage(name: 'UnboundedSettingsScreen') +class UnboundedSettingsScreen extends HookConsumerWidget { + const UnboundedSettingsScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final appSetting = ref.watch(appSettingProvider); + final notifier = ref.read(appSettingProvider.notifier); + final textTheme = Theme.of(context).textTheme; + + return BaseScreen( + title: 'Unbounded Settings', + body: Column( + children: [ + const SizedBox(height: defaultSize), + AppCard( + padding: EdgeInsets.zero, + child: AppTile( + label: 'Auto-enable Unbounded', + subtitle: Text( + 'Turn on automatically when Lantern is open', + style: textTheme.labelMedium!.copyWith( + color: context.textTertiary, + ), + ), + icon: Icon(Icons.play_circle_outline, size: 22), + trailing: Checkbox( + value: appSetting.autoEnableUnbounded, + onChanged: (val) => + notifier.setAutoEnableUnbounded(val ?? false), + activeColor: context.textLink, + ), + onPressed: () => notifier + .setAutoEnableUnbounded(!appSetting.autoEnableUnbounded), + ), + ), + const SizedBox(height: defaultSize), + AppCard( + padding: EdgeInsets.zero, + child: AppTile( + label: 'Hide Unbounded', + subtitle: Text( + 'Removes Unbounded from the UI', + style: textTheme.labelMedium!.copyWith( + color: context.textTertiary, + ), + ), + icon: Icon(Icons.visibility_off_outlined, size: 22), + trailing: SwitchButton( + value: appSetting.hideUnbounded, + onChanged: notifier.setHideUnbounded, + ), + onPressed: () => + notifier.setHideUnbounded(!appSetting.hideUnbounded), + ), + ), + ], + ), + ); + } +} diff --git a/lib/lantern/lantern_core_service.dart b/lib/lantern/lantern_core_service.dart index 022a5937f1..ded29a6f6d 100644 --- a/lib/lantern/lantern_core_service.dart +++ b/lib/lantern/lantern_core_service.dart @@ -45,6 +45,10 @@ abstract class LanternCoreService { Future> isBlockAdsEnabled(); + Future> setUnboundedEnabled(bool enabled); + + Future> isUnboundedEnabled(); + ///Payments methods Future> stipeSubscriptionPaymentRedirect( {required BillingType type, diff --git a/lib/lantern/lantern_ffi_service.dart b/lib/lantern/lantern_ffi_service.dart index c1527cdb89..483e12a761 100644 --- a/lib/lantern/lantern_ffi_service.dart +++ b/lib/lantern/lantern_ffi_service.dart @@ -1422,6 +1422,32 @@ class LanternFFIService implements LanternCoreService { } } + @override + Future> setUnboundedEnabled(bool enabled) async { + try { + final result = _ffiService + .setUnboundedEnabled(enabled ? 1 : 0) + .cast() + .toDartString(); + checkAPIError(result); + return right(unit); + } catch (e, st) { + appLogger.error('setUnboundedEnabled error: $e', e, st); + return Left(e.toFailure()); + } + } + + @override + Future> isUnboundedEnabled() async { + try { + final res = _ffiService.isUnboundedEnabled(); + return right(res != 0); + } catch (e, st) { + appLogger.error('isUnboundedEnabled error: $e', e, st); + return Left(e.toFailure()); + } + } + @override Future> triggerSystemExtension() { throw Exception("This is not supported on desktop"); diff --git a/lib/lantern/lantern_generated_bindings.dart b/lib/lantern/lantern_generated_bindings.dart index 132a871a13..d9abbe7b9d 100644 --- a/lib/lantern/lantern_generated_bindings.dart +++ b/lib/lantern/lantern_generated_bindings.dart @@ -6077,6 +6077,29 @@ class LanternBindings { _lookup>('isSmartRoutingEnabled'); late final _isSmartRoutingEnabled = _isSmartRoutingEnabledPtr.asFunction(); + + ffi.Pointer setUnboundedEnabled( + int enabled, + ) { + return _setUnboundedEnabled( + enabled, + ); + } + + late final _setUnboundedEnabledPtr = + _lookup Function(ffi.Int)>>( + 'setUnboundedEnabled'); + late final _setUnboundedEnabled = + _setUnboundedEnabledPtr.asFunction Function(int)>(); + + int isUnboundedEnabled() { + return _isUnboundedEnabled(); + } + + late final _isUnboundedEnabledPtr = + _lookup>('isUnboundedEnabled'); + late final _isUnboundedEnabled = + _isUnboundedEnabledPtr.asFunction(); } typedef va_list = ffi.Pointer; diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index 672949b984..5522ea9b3b 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -245,6 +245,30 @@ class LanternPlatformService implements LanternCoreService { } } + @override + Future> setUnboundedEnabled(bool enabled) async { + try { + await _methodChannel + .invokeMethod('setUnboundedEnabled', {'enabled': enabled}); + return right(unit); + } catch (e, st) { + appLogger.error('setUnboundedEnabled failed', e, st); + return Left(e.toFailure()); + } + } + + @override + Future> isUnboundedEnabled() async { + try { + final res = + await _methodChannel.invokeMethod('isUnboundedEnabled'); + return right(res ?? false); + } catch (e, st) { + appLogger.error('isUnboundedEnabled failed', e, st); + return Left(e.toFailure()); + } + } + List _mapToAppData( Iterable> rawApps, { required EnabledAppsSnapshot enabled, diff --git a/lib/lantern/lantern_service.dart b/lib/lantern/lantern_service.dart index 777f6386b3..7a3888591f 100644 --- a/lib/lantern/lantern_service.dart +++ b/lib/lantern/lantern_service.dart @@ -638,6 +638,22 @@ class LanternService implements LanternCoreService { return _platformService.setBlockAdsEnabled(enabled); } + @override + Future> setUnboundedEnabled(bool enabled) { + if (PlatformUtils.isFFISupported) { + return _ffiService.setUnboundedEnabled(enabled); + } + return _platformService.setUnboundedEnabled(enabled); + } + + @override + Future> isUnboundedEnabled() { + if (PlatformUtils.isFFISupported) { + return _ffiService.isUnboundedEnabled(); + } + return _platformService.isUnboundedEnabled(); + } + @override Future> attachReferralCode(String code) { if (PlatformUtils.isFFISupported) { diff --git a/macos/Runner/Handlers/MethodHandler.swift b/macos/Runner/Handlers/MethodHandler.swift index be1536bef9..4e34412055 100644 --- a/macos/Runner/Handlers/MethodHandler.swift +++ b/macos/Runner/Handlers/MethodHandler.swift @@ -278,6 +278,14 @@ class MethodHandler { let enable = self.decodeValue(from: call.arguments, result: result) as Bool? self.setRoutingMode(result: result, enable: enable ?? false) + // Unbounded + case "setUnboundedEnabled": + let enabled: Bool = requireArg(call: call, name: "enabled", result: result)! + self.setUnboundedEnabled(result: result, enabled: enabled) + + case "isUnboundedEnabled": + self.isUnboundedEnabled(result: result) + default: result(FlutterMethodNotImplemented) } @@ -1165,6 +1173,32 @@ class MethodHandler { } } + // Unbounded + + private func setUnboundedEnabled(result: @escaping FlutterResult, enabled: Bool) { + Task.detached { + var error: NSError? + MobileSetUnboundedEnabled(enabled, &error) + if let err = error { + await self.handleFlutterError(err, result: result, code: "SET_UNBOUNDED_ENABLED_FAILED") + return + } + await MainActor.run { result("ok") } + } + } + + private func isUnboundedEnabled(result: @escaping FlutterResult) { + Task.detached { + var error: NSError? + let enabled = MobileIsUnboundedEnabled() + if let err = error { + await self.handleFlutterError(err, result: result, code: "IS_UNBOUNDED_ENABLED_FAILED") + return + } + await MainActor.run { result(enabled) } + } + } + // MARK: - Utils /// Helper for handling Flutter errors diff --git a/pubspec.lock b/pubspec.lock index 8ad7171fcd..cab581b4dd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -531,6 +531,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_earth_globe: + dependency: "direct main" + description: + name: flutter_earth_globe + sha256: d5b2d9a9ae9ad667808b0e5efc59dd3155051218f409cdb645b89a2d722e1745 + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter_hooks: dependency: "direct main" description: @@ -800,7 +808,7 @@ packages: source: hosted version: "4.3.0" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" diff --git a/pubspec.yaml b/pubspec.yaml index 73ad26d5c9..e8e308c619 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -109,6 +109,8 @@ dependencies: device_info_plus: ^12.2.0 flutter_inappwebview: ^6.1.5 + flutter_earth_globe: ^2.2.0 + http: ^1.2.2 desktop_webview_window: ^0.2.3 store_checker: ^1.8.0 # payment @@ -156,6 +158,7 @@ flutter: - assets/images/ - assets/images/flags/ - assets/locales/ + - assets/unbounded/ - app.env objectbox: