From 4aba5f46aa7d7d49eb1ce2d698c016fd58561fd7 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Tue, 12 Apr 2022 15:09:14 +0200 Subject: [PATCH] Rewrite app in SwiftUI --- .env.ios | 2 +- .env.mac | 8 +- .github/workflows/release.yml | 2 +- .gitignore | 1 - .gitmodules | 4 +- Config.xcconfig | 2 + Gemfile.lock | 50 +- Passepartout.xcodeproj/project.pbxproj | 1817 ++++++++--------- .../xcshareddata/swiftpm/Package.resolved | 41 +- .../xcschemes/Passepartout-macOS.xcscheme | 102 - ...out-iOS.xcscheme => Passepartout.xcscheme} | 81 +- Passepartout/App/Descriptible.swift | 182 -- .../App/Scripts/build_wireguard_go_bridge.sh | 40 + .../App/Scripts/copy_coredata_codegen.sh | 9 + .../App/{macOS => Shared}/App.entitlements | 18 +- .../AppIcon.appiconset/AppIcon-1024.png | Bin .../AppIcon.appiconset/AppIcon-120.png | Bin .../AppIcon.appiconset/AppIcon-152.png | Bin .../AppIcon.appiconset/AppIcon-167.png | Bin .../AppIcon.appiconset/AppIcon-180.png | Bin .../AppIcon.appiconset/AppIcon-76.png | Bin .../AppIcon.appiconset/Contents.json | 0 .../Assets.xcassets}/Contents.json | 0 .../accentColor.colorset/Contents.json | 20 + .../lightTextColor.colorset/Contents.json | 20 + .../logo.imageset/Contents.json | 0 .../Assets.xcassets/logo.imageset/logo@2x.png | Bin .../Assets.xcassets/logo.imageset/logo@3x.png | Bin .../primaryColor.colorset/Contents.json | 20 + .../App/Shared/Constants/AppContext.swift | 208 ++ .../Constants/Constants+Extensions.swift | 282 +++ .../Shared/Constants/SwiftGen+Assets.swift | 418 ++++ .../Shared/Constants/SwiftGen+Strings.swift | 1114 ++++++++++ Passepartout/App/Shared/Constants/Theme.swift | 394 ++++ .../Extensions/DebugLog+Constants.swift | 37 + .../PassepartoutProviders+Extensions.swift | 106 + .../Extensions/TunnelKit+Identifiable.swift | 45 + .../VPNProtocolType+FileExtensions.swift | 44 + .../Flags.xcassets}/Contents.json | 0 .../Flags.xcassets/flags}/Contents.json | 3 + .../flags}/ad.imageset/Contents.json | 0 .../flags}/ad.imageset/ad@2x.png | Bin .../flags}/ad.imageset/ad@3x.png | Bin .../flags}/ae.imageset/Contents.json | 0 .../flags}/ae.imageset/ae@2x.png | Bin .../flags}/ae.imageset/ae@3x.png | Bin .../flags}/af.imageset/Contents.json | 0 .../flags}/af.imageset/af@2x.png | Bin .../flags}/af.imageset/af@3x.png | Bin .../flags}/ag.imageset/Contents.json | 0 .../flags}/ag.imageset/ag@2x.png | Bin .../flags}/ag.imageset/ag@3x.png | Bin .../flags}/ai.imageset/Contents.json | 0 .../flags}/ai.imageset/ai@2x.png | Bin .../flags}/ai.imageset/ai@3x.png | Bin .../flags}/al.imageset/Contents.json | 0 .../flags}/al.imageset/al@2x.png | Bin .../flags}/al.imageset/al@3x.png | Bin .../flags}/am.imageset/Contents.json | 0 .../flags}/am.imageset/am@2x.png | Bin .../flags}/am.imageset/am@3x.png | Bin .../flags}/ao.imageset/Contents.json | 0 .../flags}/ao.imageset/ao@2x.png | Bin .../flags}/ao.imageset/ao@3x.png | Bin .../flags}/aq.imageset/Contents.json | 0 .../flags}/aq.imageset/aq@2x.png | Bin .../flags}/aq.imageset/aq@3x.png | Bin .../flags}/ar.imageset/Contents.json | 0 .../flags}/ar.imageset/ar@2x.png | Bin .../flags}/ar.imageset/ar@3x.png | Bin .../flags}/as.imageset/Contents.json | 0 .../flags}/as.imageset/as@2x.png | Bin .../flags}/as.imageset/as@3x.png | Bin .../flags}/at.imageset/Contents.json | 0 .../flags}/at.imageset/at@2x.png | Bin .../flags}/at.imageset/at@3x.png | Bin .../flags}/au.imageset/Contents.json | 0 .../flags}/au.imageset/au@2x.png | Bin .../flags}/au.imageset/au@3x.png | Bin .../flags}/aw.imageset/Contents.json | 0 .../flags}/aw.imageset/aw@2x.png | Bin .../flags}/aw.imageset/aw@3x.png | Bin .../flags}/ax.imageset/Contents.json | 0 .../flags}/ax.imageset/ax@2x.png | Bin .../flags}/ax.imageset/ax@3x.png | Bin .../flags}/az.imageset/Contents.json | 0 .../flags}/az.imageset/az@2x.png | Bin .../flags}/az.imageset/az@3x.png | Bin .../flags}/ba.imageset/Contents.json | 0 .../flags}/ba.imageset/ba@2x.png | Bin .../flags}/ba.imageset/ba@3x.png | Bin .../flags}/bb.imageset/Contents.json | 0 .../flags}/bb.imageset/bb@2x.png | Bin .../flags}/bb.imageset/bb@3x.png | Bin .../flags}/bd.imageset/Contents.json | 0 .../flags}/bd.imageset/bd@2x.png | Bin .../flags}/bd.imageset/bd@3x.png | Bin .../flags}/be.imageset/Contents.json | 0 .../flags}/be.imageset/be@2x.png | Bin .../flags}/be.imageset/be@3x.png | Bin .../flags}/bf.imageset/Contents.json | 0 .../flags}/bf.imageset/bf@2x.png | Bin .../flags}/bf.imageset/bf@3x.png | Bin .../flags}/bg.imageset/Contents.json | 0 .../flags}/bg.imageset/bg@2x.png | Bin .../flags}/bg.imageset/bg@3x.png | Bin .../flags}/bh.imageset/Contents.json | 0 .../flags}/bh.imageset/bh@2x.png | Bin .../flags}/bh.imageset/bh@3x.png | Bin .../flags}/bi.imageset/Contents.json | 0 .../flags}/bi.imageset/bi@2x.png | Bin .../flags}/bi.imageset/bi@3x.png | Bin .../flags}/bj.imageset/Contents.json | 0 .../flags}/bj.imageset/bj@2x.png | Bin .../flags}/bj.imageset/bj@3x.png | Bin .../flags}/bl.imageset/Contents.json | 0 .../flags}/bl.imageset/bl@2x.png | Bin .../flags}/bl.imageset/bl@3x.png | Bin .../flags}/bm.imageset/Contents.json | 0 .../flags}/bm.imageset/bm@2x.png | Bin .../flags}/bm.imageset/bm@3x.png | Bin .../flags}/bn.imageset/Contents.json | 0 .../flags}/bn.imageset/bn@2x.png | Bin .../flags}/bn.imageset/bn@3x.png | Bin .../flags}/bo.imageset/Contents.json | 0 .../flags}/bo.imageset/bo@2x.png | Bin .../flags}/bo.imageset/bo@3x.png | Bin .../flags}/bq.imageset/Contents.json | 0 .../flags}/bq.imageset/bq@2x.png | Bin .../flags}/bq.imageset/bq@3x.png | Bin .../flags}/br.imageset/Contents.json | 0 .../flags}/br.imageset/br@2x.png | Bin .../flags}/br.imageset/br@3x.png | Bin .../flags}/bs.imageset/Contents.json | 0 .../flags}/bs.imageset/bs@2x.png | Bin .../flags}/bs.imageset/bs@3x.png | Bin .../flags}/bt.imageset/Contents.json | 0 .../flags}/bt.imageset/bt@2x.png | Bin .../flags}/bt.imageset/bt@3x.png | Bin .../flags}/bv.imageset/Contents.json | 0 .../flags}/bv.imageset/bv@2x.png | Bin .../flags}/bv.imageset/bv@3x.png | Bin .../flags}/bw.imageset/Contents.json | 0 .../flags}/bw.imageset/bw@2x.png | Bin .../flags}/bw.imageset/bw@3x.png | Bin .../flags}/by.imageset/Contents.json | 0 .../flags}/by.imageset/by@2x.png | Bin .../flags}/by.imageset/by@3x.png | Bin .../flags}/bz.imageset/Contents.json | 0 .../flags}/bz.imageset/bz@2x.png | Bin .../flags}/bz.imageset/bz@3x.png | Bin .../flags}/ca.imageset/Contents.json | 0 .../flags}/ca.imageset/ca@2x.png | Bin .../flags}/ca.imageset/ca@3x.png | Bin .../flags}/cc.imageset/Contents.json | 0 .../flags}/cc.imageset/cc@2x.png | Bin .../flags}/cc.imageset/cc@3x.png | Bin .../flags}/cd.imageset/Contents.json | 0 .../flags}/cd.imageset/cd@2x.png | Bin .../flags}/cd.imageset/cd@3x.png | Bin .../flags}/cf.imageset/Contents.json | 0 .../flags}/cf.imageset/cf@2x.png | Bin .../flags}/cf.imageset/cf@3x.png | Bin .../flags}/cg.imageset/Contents.json | 0 .../flags}/cg.imageset/cg@2x.png | Bin .../flags}/cg.imageset/cg@3x.png | Bin .../flags}/ch.imageset/Contents.json | 0 .../flags}/ch.imageset/ch@2x.png | Bin .../flags}/ch.imageset/ch@3x.png | Bin .../flags}/ci.imageset/Contents.json | 0 .../flags}/ci.imageset/ci@2x.png | Bin .../flags}/ci.imageset/ci@3x.png | Bin .../flags}/ck.imageset/Contents.json | 0 .../flags}/ck.imageset/ck@2x.png | Bin .../flags}/ck.imageset/ck@3x.png | Bin .../flags}/cl.imageset/Contents.json | 0 .../flags}/cl.imageset/cl@2x.png | Bin .../flags}/cl.imageset/cl@3x.png | Bin .../flags}/cm.imageset/Contents.json | 0 .../flags}/cm.imageset/cm@2x.png | Bin .../flags}/cm.imageset/cm@3x.png | Bin .../flags}/cn.imageset/Contents.json | 0 .../flags}/cn.imageset/cn@2x.png | Bin .../flags}/cn.imageset/cn@3x.png | Bin .../flags}/co.imageset/Contents.json | 0 .../flags}/co.imageset/co@2x.png | Bin .../flags}/co.imageset/co@3x.png | Bin .../flags}/cr.imageset/Contents.json | 0 .../flags}/cr.imageset/cr@2x.png | Bin .../flags}/cr.imageset/cr@3x.png | Bin .../flags}/cu.imageset/Contents.json | 0 .../flags}/cu.imageset/cu@2x.png | Bin .../flags}/cu.imageset/cu@3x.png | Bin .../flags}/cv.imageset/Contents.json | 0 .../flags}/cv.imageset/cv@2x.png | Bin .../flags}/cv.imageset/cv@3x.png | Bin .../flags}/cw.imageset/Contents.json | 0 .../flags}/cw.imageset/cw@2x.png | Bin .../flags}/cw.imageset/cw@3x.png | Bin .../flags}/cx.imageset/Contents.json | 0 .../flags}/cx.imageset/cx@2x.png | Bin .../flags}/cx.imageset/cx@3x.png | Bin .../flags}/cy.imageset/Contents.json | 0 .../flags}/cy.imageset/cy@2x.png | Bin .../flags}/cy.imageset/cy@3x.png | Bin .../flags}/cz.imageset/Contents.json | 0 .../flags}/cz.imageset/cz@2x.png | Bin .../flags}/cz.imageset/cz@3x.png | Bin .../flags}/de.imageset/Contents.json | 0 .../flags}/de.imageset/de@2x.png | Bin .../flags}/de.imageset/de@3x.png | Bin .../flags}/dj.imageset/Contents.json | 0 .../flags}/dj.imageset/dj@2x.png | Bin .../flags}/dj.imageset/dj@3x.png | Bin .../flags}/dk.imageset/Contents.json | 0 .../flags}/dk.imageset/dk@2x.png | Bin .../flags}/dk.imageset/dk@3x.png | Bin .../flags}/dm.imageset/Contents.json | 0 .../flags}/dm.imageset/dm@2x.png | Bin .../flags}/dm.imageset/dm@3x.png | Bin .../flags}/do.imageset/Contents.json | 0 .../flags}/do.imageset/do@2x.png | Bin .../flags}/do.imageset/do@3x.png | Bin .../flags}/dz.imageset/Contents.json | 0 .../flags}/dz.imageset/dz@2x.png | Bin .../flags}/dz.imageset/dz@3x.png | Bin .../flags}/ec.imageset/Contents.json | 0 .../flags}/ec.imageset/ec@2x.png | Bin .../flags}/ec.imageset/ec@3x.png | Bin .../flags}/ee.imageset/Contents.json | 0 .../flags}/ee.imageset/ee@2x.png | Bin .../flags}/ee.imageset/ee@3x.png | Bin .../flags}/eg.imageset/Contents.json | 0 .../flags}/eg.imageset/eg@2x.png | Bin .../flags}/eg.imageset/eg@3x.png | Bin .../flags}/eh.imageset/Contents.json | 0 .../flags}/eh.imageset/eh@2x.png | Bin .../flags}/eh.imageset/eh@3x.png | Bin .../flags}/er.imageset/Contents.json | 0 .../flags}/er.imageset/er@2x.png | Bin .../flags}/er.imageset/er@3x.png | Bin .../flags}/es-ct.imageset/Contents.json | 0 .../flags}/es-ct.imageset/es-ct@2x.png | Bin .../flags}/es-ct.imageset/es-ct@3x.png | Bin .../flags}/es.imageset/Contents.json | 0 .../flags}/es.imageset/es@2x.png | Bin .../flags}/es.imageset/es@3x.png | Bin .../flags}/et.imageset/Contents.json | 0 .../flags}/et.imageset/et@2x.png | Bin .../flags}/et.imageset/et@3x.png | Bin .../flags}/eu.imageset/Contents.json | 0 .../flags}/eu.imageset/eu@2x.png | Bin .../flags}/eu.imageset/eu@3x.png | Bin .../flags}/fi.imageset/Contents.json | 0 .../flags}/fi.imageset/fi@2x.png | Bin .../flags}/fi.imageset/fi@3x.png | Bin .../flags}/fj.imageset/Contents.json | 0 .../flags}/fj.imageset/fj@2x.png | Bin .../flags}/fj.imageset/fj@3x.png | Bin .../flags}/fk.imageset/Contents.json | 0 .../flags}/fk.imageset/fk@2x.png | Bin .../flags}/fk.imageset/fk@3x.png | Bin .../flags}/fm.imageset/Contents.json | 0 .../flags}/fm.imageset/fm@2x.png | Bin .../flags}/fm.imageset/fm@3x.png | Bin .../flags}/fo.imageset/Contents.json | 0 .../flags}/fo.imageset/fo@2x.png | Bin .../flags}/fo.imageset/fo@3x.png | Bin .../flags}/fr.imageset/Contents.json | 0 .../flags}/fr.imageset/fr@2x.png | Bin .../flags}/fr.imageset/fr@3x.png | Bin .../flags}/ga.imageset/Contents.json | 0 .../flags}/ga.imageset/ga@2x.png | Bin .../flags}/ga.imageset/ga@3x.png | Bin .../flags}/gb-eng.imageset/Contents.json | 0 .../flags}/gb-eng.imageset/gb-eng@2x.png | Bin .../flags}/gb-eng.imageset/gb-eng@3x.png | Bin .../flags}/gb-nir.imageset/Contents.json | 0 .../flags}/gb-nir.imageset/gb-nir@2x.png | Bin .../flags}/gb-nir.imageset/gb-nir@3x.png | Bin .../flags}/gb-sct.imageset/Contents.json | 0 .../flags}/gb-sct.imageset/gb-sct@2x.png | Bin .../flags}/gb-sct.imageset/gb-sct@3x.png | Bin .../flags}/gb-wls.imageset/Contents.json | 0 .../flags}/gb-wls.imageset/gb-wls@2x.png | Bin .../flags}/gb-wls.imageset/gb-wls@3x.png | Bin .../flags}/gb.imageset/Contents.json | 0 .../flags}/gb.imageset/gb@2x.png | Bin .../flags}/gb.imageset/gb@3x.png | Bin .../flags}/gd.imageset/Contents.json | 0 .../flags}/gd.imageset/gd@2x.png | Bin .../flags}/gd.imageset/gd@3x.png | Bin .../flags}/ge.imageset/Contents.json | 0 .../flags}/ge.imageset/ge@2x.png | Bin .../flags}/ge.imageset/ge@3x.png | Bin .../flags}/gf.imageset/Contents.json | 0 .../flags}/gf.imageset/gf@2x.png | Bin .../flags}/gf.imageset/gf@3x.png | Bin .../flags}/gg.imageset/Contents.json | 0 .../flags}/gg.imageset/gg@2x.png | Bin .../flags}/gg.imageset/gg@3x.png | Bin .../flags}/gh.imageset/Contents.json | 0 .../flags}/gh.imageset/gh@2x.png | Bin .../flags}/gh.imageset/gh@3x.png | Bin .../flags}/gi.imageset/Contents.json | 0 .../flags}/gi.imageset/gi@2x.png | Bin .../flags}/gi.imageset/gi@3x.png | Bin .../flags}/gl.imageset/Contents.json | 0 .../flags}/gl.imageset/gl@2x.png | Bin .../flags}/gl.imageset/gl@3x.png | Bin .../flags}/gm.imageset/Contents.json | 0 .../flags}/gm.imageset/gm@2x.png | Bin .../flags}/gm.imageset/gm@3x.png | Bin .../flags}/gn.imageset/Contents.json | 0 .../flags}/gn.imageset/gn@2x.png | Bin .../flags}/gn.imageset/gn@3x.png | Bin .../flags}/gp.imageset/Contents.json | 0 .../flags}/gp.imageset/gp@2x.png | Bin .../flags}/gp.imageset/gp@3x.png | Bin .../flags}/gq.imageset/Contents.json | 0 .../flags}/gq.imageset/gq@2x.png | Bin .../flags}/gq.imageset/gq@3x.png | Bin .../flags}/gr.imageset/Contents.json | 0 .../flags}/gr.imageset/gr@2x.png | Bin .../flags}/gr.imageset/gr@3x.png | Bin .../flags}/gs.imageset/Contents.json | 0 .../flags}/gs.imageset/gs@2x.png | Bin .../flags}/gs.imageset/gs@3x.png | Bin .../flags}/gt.imageset/Contents.json | 0 .../flags}/gt.imageset/gt@2x.png | Bin .../flags}/gt.imageset/gt@3x.png | Bin .../flags}/gu.imageset/Contents.json | 0 .../flags}/gu.imageset/gu@2x.png | Bin .../flags}/gu.imageset/gu@3x.png | Bin .../flags}/gw.imageset/Contents.json | 0 .../flags}/gw.imageset/gw@2x.png | Bin .../flags}/gw.imageset/gw@3x.png | Bin .../flags}/gy.imageset/Contents.json | 0 .../flags}/gy.imageset/gy@2x.png | Bin .../flags}/gy.imageset/gy@3x.png | Bin .../flags}/hk.imageset/Contents.json | 0 .../flags}/hk.imageset/hk@2x.png | Bin .../flags}/hk.imageset/hk@3x.png | Bin .../flags}/hm.imageset/Contents.json | 0 .../flags}/hm.imageset/hm@2x.png | Bin .../flags}/hm.imageset/hm@3x.png | Bin .../flags}/hn.imageset/Contents.json | 0 .../flags}/hn.imageset/hn@2x.png | Bin .../flags}/hn.imageset/hn@3x.png | Bin .../flags}/hr.imageset/Contents.json | 0 .../flags}/hr.imageset/hr@2x.png | Bin .../flags}/hr.imageset/hr@3x.png | Bin .../flags}/ht.imageset/Contents.json | 0 .../flags}/ht.imageset/ht@2x.png | Bin .../flags}/ht.imageset/ht@3x.png | Bin .../flags}/hu.imageset/Contents.json | 0 .../flags}/hu.imageset/hu@2x.png | Bin .../flags}/hu.imageset/hu@3x.png | Bin .../flags}/id.imageset/Contents.json | 0 .../flags}/id.imageset/id@2x.png | Bin .../flags}/id.imageset/id@3x.png | Bin .../flags}/ie.imageset/Contents.json | 0 .../flags}/ie.imageset/ie@2x.png | Bin .../flags}/ie.imageset/ie@3x.png | Bin .../flags}/il.imageset/Contents.json | 0 .../flags}/il.imageset/il@2x.png | Bin .../flags}/il.imageset/il@3x.png | Bin .../flags}/im.imageset/Contents.json | 0 .../flags}/im.imageset/im@2x.png | Bin .../flags}/im.imageset/im@3x.png | Bin .../flags}/in.imageset/Contents.json | 0 .../flags}/in.imageset/in@2x.png | Bin .../flags}/in.imageset/in@3x.png | Bin .../flags}/io.imageset/Contents.json | 0 .../flags}/io.imageset/io@2x.png | Bin .../flags}/io.imageset/io@3x.png | Bin .../flags}/iq.imageset/Contents.json | 0 .../flags}/iq.imageset/iq@2x.png | Bin .../flags}/iq.imageset/iq@3x.png | Bin .../flags}/ir.imageset/Contents.json | 0 .../flags}/ir.imageset/ir@2x.png | Bin .../flags}/ir.imageset/ir@3x.png | Bin .../flags}/is.imageset/Contents.json | 0 .../flags}/is.imageset/is@2x.png | Bin .../flags}/is.imageset/is@3x.png | Bin .../flags}/it.imageset/Contents.json | 0 .../flags}/it.imageset/it@2x.png | Bin .../flags}/it.imageset/it@3x.png | Bin .../flags}/je.imageset/Contents.json | 0 .../flags}/je.imageset/je@2x.png | Bin .../flags}/je.imageset/je@3x.png | Bin .../flags}/jm.imageset/Contents.json | 0 .../flags}/jm.imageset/jm@2x.png | Bin .../flags}/jm.imageset/jm@3x.png | Bin .../flags}/jo.imageset/Contents.json | 0 .../flags}/jo.imageset/jo@2x.png | Bin .../flags}/jo.imageset/jo@3x.png | Bin .../flags}/jp.imageset/Contents.json | 0 .../flags}/jp.imageset/jp@2x.png | Bin .../flags}/jp.imageset/jp@3x.png | Bin .../flags}/ke.imageset/Contents.json | 0 .../flags}/ke.imageset/ke@2x.png | Bin .../flags}/ke.imageset/ke@3x.png | Bin .../flags}/kg.imageset/Contents.json | 0 .../flags}/kg.imageset/kg@2x.png | Bin .../flags}/kg.imageset/kg@3x.png | Bin .../flags}/kh.imageset/Contents.json | 0 .../flags}/kh.imageset/kh@2x.png | Bin .../flags}/kh.imageset/kh@3x.png | Bin .../flags}/ki.imageset/Contents.json | 0 .../flags}/ki.imageset/ki@2x.png | Bin .../flags}/ki.imageset/ki@3x.png | Bin .../flags}/km.imageset/Contents.json | 0 .../flags}/km.imageset/km@2x.png | Bin .../flags}/km.imageset/km@3x.png | Bin .../flags}/kn.imageset/Contents.json | 0 .../flags}/kn.imageset/kn@2x.png | Bin .../flags}/kn.imageset/kn@3x.png | Bin .../flags}/kp.imageset/Contents.json | 0 .../flags}/kp.imageset/kp@2x.png | Bin .../flags}/kp.imageset/kp@3x.png | Bin .../flags}/kr.imageset/Contents.json | 0 .../flags}/kr.imageset/kr@2x.png | Bin .../flags}/kr.imageset/kr@3x.png | Bin .../flags}/kw.imageset/Contents.json | 0 .../flags}/kw.imageset/kw@2x.png | Bin .../flags}/kw.imageset/kw@3x.png | Bin .../flags}/ky.imageset/Contents.json | 0 .../flags}/ky.imageset/ky@2x.png | Bin .../flags}/ky.imageset/ky@3x.png | Bin .../flags}/kz.imageset/Contents.json | 0 .../flags}/kz.imageset/kz@2x.png | Bin .../flags}/kz.imageset/kz@3x.png | Bin .../flags}/la.imageset/Contents.json | 0 .../flags}/la.imageset/la@2x.png | Bin .../flags}/la.imageset/la@3x.png | Bin .../flags}/lb.imageset/Contents.json | 0 .../flags}/lb.imageset/lb@2x.png | Bin .../flags}/lb.imageset/lb@3x.png | Bin .../flags}/lc.imageset/Contents.json | 0 .../flags}/lc.imageset/lc@2x.png | Bin .../flags}/lc.imageset/lc@3x.png | Bin .../flags}/li.imageset/Contents.json | 0 .../flags}/li.imageset/li@2x.png | Bin .../flags}/li.imageset/li@3x.png | Bin .../flags}/lk.imageset/Contents.json | 0 .../flags}/lk.imageset/lk@2x.png | Bin .../flags}/lk.imageset/lk@3x.png | Bin .../flags}/lr.imageset/Contents.json | 0 .../flags}/lr.imageset/lr@2x.png | Bin .../flags}/lr.imageset/lr@3x.png | Bin .../flags}/ls.imageset/Contents.json | 0 .../flags}/ls.imageset/ls@2x.png | Bin .../flags}/ls.imageset/ls@3x.png | Bin .../flags}/lt.imageset/Contents.json | 0 .../flags}/lt.imageset/lt@2x.png | Bin .../flags}/lt.imageset/lt@3x.png | Bin .../flags}/lu.imageset/Contents.json | 0 .../flags}/lu.imageset/lu@2x.png | Bin .../flags}/lu.imageset/lu@3x.png | Bin .../flags}/lv.imageset/Contents.json | 0 .../flags}/lv.imageset/lv@2x.png | Bin .../flags}/lv.imageset/lv@3x.png | Bin .../flags}/ly.imageset/Contents.json | 0 .../flags}/ly.imageset/ly@2x.png | Bin .../flags}/ly.imageset/ly@3x.png | Bin .../flags}/ma.imageset/Contents.json | 0 .../flags}/ma.imageset/ma@2x.png | Bin .../flags}/ma.imageset/ma@3x.png | Bin .../flags}/mc.imageset/Contents.json | 0 .../flags}/mc.imageset/mc@2x.png | Bin .../flags}/mc.imageset/mc@3x.png | Bin .../flags}/md.imageset/Contents.json | 0 .../flags}/md.imageset/md@2x.png | Bin .../flags}/md.imageset/md@3x.png | Bin .../flags}/me.imageset/Contents.json | 0 .../flags}/me.imageset/me@2x.png | Bin .../flags}/me.imageset/me@3x.png | Bin .../flags}/mf.imageset/Contents.json | 0 .../flags}/mf.imageset/mf@2x.png | Bin .../flags}/mf.imageset/mf@3x.png | Bin .../flags}/mg.imageset/Contents.json | 0 .../flags}/mg.imageset/mg@2x.png | Bin .../flags}/mg.imageset/mg@3x.png | Bin .../flags}/mh.imageset/Contents.json | 0 .../flags}/mh.imageset/mh@2x.png | Bin .../flags}/mh.imageset/mh@3x.png | Bin .../flags}/mk.imageset/Contents.json | 0 .../flags}/mk.imageset/mk@2x.png | Bin .../flags}/mk.imageset/mk@3x.png | Bin .../flags}/ml.imageset/Contents.json | 0 .../flags}/ml.imageset/ml@2x.png | Bin .../flags}/ml.imageset/ml@3x.png | Bin .../flags}/mm.imageset/Contents.json | 0 .../flags}/mm.imageset/mm@2x.png | Bin .../flags}/mm.imageset/mm@3x.png | Bin .../flags}/mn.imageset/Contents.json | 0 .../flags}/mn.imageset/mn@2x.png | Bin .../flags}/mn.imageset/mn@3x.png | Bin .../flags}/mo.imageset/Contents.json | 0 .../flags}/mo.imageset/mo@2x.png | Bin .../flags}/mo.imageset/mo@3x.png | Bin .../flags}/mp.imageset/Contents.json | 0 .../flags}/mp.imageset/mp@2x.png | Bin .../flags}/mp.imageset/mp@3x.png | Bin .../flags}/mq.imageset/Contents.json | 0 .../flags}/mq.imageset/mq@2x.png | Bin .../flags}/mq.imageset/mq@3x.png | Bin .../flags}/mr.imageset/Contents.json | 0 .../flags}/mr.imageset/mr@2x.png | Bin .../flags}/mr.imageset/mr@3x.png | Bin .../flags}/ms.imageset/Contents.json | 0 .../flags}/ms.imageset/ms@2x.png | Bin .../flags}/ms.imageset/ms@3x.png | Bin .../flags}/mt.imageset/Contents.json | 0 .../flags}/mt.imageset/mt@2x.png | Bin .../flags}/mt.imageset/mt@3x.png | Bin .../flags}/mu.imageset/Contents.json | 0 .../flags}/mu.imageset/mu@2x.png | Bin .../flags}/mu.imageset/mu@3x.png | Bin .../flags}/mv.imageset/Contents.json | 0 .../flags}/mv.imageset/mv@2x.png | Bin .../flags}/mv.imageset/mv@3x.png | Bin .../flags}/mw.imageset/Contents.json | 0 .../flags}/mw.imageset/mw@2x.png | Bin .../flags}/mw.imageset/mw@3x.png | Bin .../flags}/mx.imageset/Contents.json | 0 .../flags}/mx.imageset/mx@2x.png | Bin .../flags}/mx.imageset/mx@3x.png | Bin .../flags}/my.imageset/Contents.json | 0 .../flags}/my.imageset/my@2x.png | Bin .../flags}/my.imageset/my@3x.png | Bin .../flags}/mz.imageset/Contents.json | 0 .../flags}/mz.imageset/mz@2x.png | Bin .../flags}/mz.imageset/mz@3x.png | Bin .../flags}/na.imageset/Contents.json | 0 .../flags}/na.imageset/na@2x.png | Bin .../flags}/na.imageset/na@3x.png | Bin .../flags}/nc.imageset/Contents.json | 0 .../flags}/nc.imageset/nc@2x.png | Bin .../flags}/nc.imageset/nc@3x.png | Bin .../flags}/ne.imageset/Contents.json | 0 .../flags}/ne.imageset/ne@2x.png | Bin .../flags}/ne.imageset/ne@3x.png | Bin .../flags}/nf.imageset/Contents.json | 0 .../flags}/nf.imageset/nf@2x.png | Bin .../flags}/nf.imageset/nf@3x.png | Bin .../flags}/ng.imageset/Contents.json | 0 .../flags}/ng.imageset/ng@2x.png | Bin .../flags}/ng.imageset/ng@3x.png | Bin .../flags}/ni.imageset/Contents.json | 0 .../flags}/ni.imageset/ni@2x.png | Bin .../flags}/ni.imageset/ni@3x.png | Bin .../flags}/nl.imageset/Contents.json | 0 .../flags}/nl.imageset/nl@2x.png | Bin .../flags}/nl.imageset/nl@3x.png | Bin .../flags}/no.imageset/Contents.json | 0 .../flags}/no.imageset/no@2x.png | Bin .../flags}/no.imageset/no@3x.png | Bin .../flags}/np.imageset/Contents.json | 0 .../flags}/np.imageset/np@2x.png | Bin .../flags}/np.imageset/np@3x.png | Bin .../flags}/nr.imageset/Contents.json | 0 .../flags}/nr.imageset/nr@2x.png | Bin .../flags}/nr.imageset/nr@3x.png | Bin .../flags}/nu.imageset/Contents.json | 0 .../flags}/nu.imageset/nu@2x.png | Bin .../flags}/nu.imageset/nu@3x.png | Bin .../flags}/nz.imageset/Contents.json | 0 .../flags}/nz.imageset/nz@2x.png | Bin .../flags}/nz.imageset/nz@3x.png | Bin .../flags}/om.imageset/Contents.json | 0 .../flags}/om.imageset/om@2x.png | Bin .../flags}/om.imageset/om@3x.png | Bin .../flags}/pa.imageset/Contents.json | 0 .../flags}/pa.imageset/pa@2x.png | Bin .../flags}/pa.imageset/pa@3x.png | Bin .../flags}/pe.imageset/Contents.json | 0 .../flags}/pe.imageset/pe@2x.png | Bin .../flags}/pe.imageset/pe@3x.png | Bin .../flags}/pf.imageset/Contents.json | 0 .../flags}/pf.imageset/pf@2x.png | Bin .../flags}/pf.imageset/pf@3x.png | Bin .../flags}/pg.imageset/Contents.json | 0 .../flags}/pg.imageset/pg@2x.png | Bin .../flags}/pg.imageset/pg@3x.png | Bin .../flags}/ph.imageset/Contents.json | 0 .../flags}/ph.imageset/ph@2x.png | Bin .../flags}/ph.imageset/ph@3x.png | Bin .../flags}/pk.imageset/Contents.json | 0 .../flags}/pk.imageset/pk@2x.png | Bin .../flags}/pk.imageset/pk@3x.png | Bin .../flags}/pl.imageset/Contents.json | 0 .../flags}/pl.imageset/pl@2x.png | Bin .../flags}/pl.imageset/pl@3x.png | Bin .../flags}/pm.imageset/Contents.json | 0 .../flags}/pm.imageset/pm@2x.png | Bin .../flags}/pm.imageset/pm@3x.png | Bin .../flags}/pn.imageset/Contents.json | 0 .../flags}/pn.imageset/pn@2x.png | Bin .../flags}/pn.imageset/pn@3x.png | Bin .../flags}/pr.imageset/Contents.json | 0 .../flags}/pr.imageset/pr@2x.png | Bin .../flags}/pr.imageset/pr@3x.png | Bin .../flags}/ps.imageset/Contents.json | 0 .../flags}/ps.imageset/ps@2x.png | Bin .../flags}/ps.imageset/ps@3x.png | Bin .../flags}/pt.imageset/Contents.json | 0 .../flags}/pt.imageset/pt@2x.png | Bin .../flags}/pt.imageset/pt@3x.png | Bin .../flags}/pw.imageset/Contents.json | 0 .../flags}/pw.imageset/pw@2x.png | Bin .../flags}/pw.imageset/pw@3x.png | Bin .../flags}/py.imageset/Contents.json | 0 .../flags}/py.imageset/py@2x.png | Bin .../flags}/py.imageset/py@3x.png | Bin .../flags}/qa.imageset/Contents.json | 0 .../flags}/qa.imageset/qa@2x.png | Bin .../flags}/qa.imageset/qa@3x.png | Bin .../flags}/re.imageset/Contents.json | 0 .../flags}/re.imageset/re@2x.png | Bin .../flags}/re.imageset/re@3x.png | Bin .../flags}/ro.imageset/Contents.json | 0 .../flags}/ro.imageset/ro@2x.png | Bin .../flags}/ro.imageset/ro@3x.png | Bin .../flags}/rs.imageset/Contents.json | 0 .../flags}/rs.imageset/rs@2x.png | Bin .../flags}/rs.imageset/rs@3x.png | Bin .../flags}/ru.imageset/Contents.json | 0 .../flags}/ru.imageset/ru@2x.png | Bin .../flags}/ru.imageset/ru@3x.png | Bin .../flags}/rw.imageset/Contents.json | 0 .../flags}/rw.imageset/rw@2x.png | Bin .../flags}/rw.imageset/rw@3x.png | Bin .../flags}/sa.imageset/Contents.json | 0 .../flags}/sa.imageset/sa@2x.png | Bin .../flags}/sa.imageset/sa@3x.png | Bin .../flags}/sb.imageset/Contents.json | 0 .../flags}/sb.imageset/sb@2x.png | Bin .../flags}/sb.imageset/sb@3x.png | Bin .../flags}/sc.imageset/Contents.json | 0 .../flags}/sc.imageset/sc@2x.png | Bin .../flags}/sc.imageset/sc@3x.png | Bin .../flags}/sd.imageset/Contents.json | 0 .../flags}/sd.imageset/sd@2x.png | Bin .../flags}/sd.imageset/sd@3x.png | Bin .../flags}/se.imageset/Contents.json | 0 .../flags}/se.imageset/se@2x.png | Bin .../flags}/se.imageset/se@3x.png | Bin .../flags}/sg.imageset/Contents.json | 0 .../flags}/sg.imageset/sg@2x.png | Bin .../flags}/sg.imageset/sg@3x.png | Bin .../flags}/sh.imageset/Contents.json | 0 .../flags}/sh.imageset/sh@2x.png | Bin .../flags}/sh.imageset/sh@3x.png | Bin .../flags}/si.imageset/Contents.json | 0 .../flags}/si.imageset/si@2x.png | Bin .../flags}/si.imageset/si@3x.png | Bin .../flags}/sj.imageset/Contents.json | 0 .../flags}/sj.imageset/sj@2x.png | Bin .../flags}/sj.imageset/sj@3x.png | Bin .../flags}/sk.imageset/Contents.json | 0 .../flags}/sk.imageset/sk@2x.png | Bin .../flags}/sk.imageset/sk@3x.png | Bin .../flags}/sl.imageset/Contents.json | 0 .../flags}/sl.imageset/sl@2x.png | Bin .../flags}/sl.imageset/sl@3x.png | Bin .../flags}/sm.imageset/Contents.json | 0 .../flags}/sm.imageset/sm@2x.png | Bin .../flags}/sm.imageset/sm@3x.png | Bin .../flags}/sn.imageset/Contents.json | 0 .../flags}/sn.imageset/sn@2x.png | Bin .../flags}/sn.imageset/sn@3x.png | Bin .../flags}/so.imageset/Contents.json | 0 .../flags}/so.imageset/so@2x.png | Bin .../flags}/so.imageset/so@3x.png | Bin .../flags}/sr.imageset/Contents.json | 0 .../flags}/sr.imageset/sr@2x.png | Bin .../flags}/sr.imageset/sr@3x.png | Bin .../flags}/ss.imageset/Contents.json | 0 .../flags}/ss.imageset/ss@2x.png | Bin .../flags}/ss.imageset/ss@3x.png | Bin .../flags}/st.imageset/Contents.json | 0 .../flags}/st.imageset/st@2x.png | Bin .../flags}/st.imageset/st@3x.png | Bin .../flags}/sv.imageset/Contents.json | 0 .../flags}/sv.imageset/sv@2x.png | Bin .../flags}/sv.imageset/sv@3x.png | Bin .../flags}/sx.imageset/Contents.json | 0 .../flags}/sx.imageset/sx@2x.png | Bin .../flags}/sx.imageset/sx@3x.png | Bin .../flags}/sy.imageset/Contents.json | 0 .../flags}/sy.imageset/sy@2x.png | Bin .../flags}/sy.imageset/sy@3x.png | Bin .../flags}/sz.imageset/Contents.json | 0 .../flags}/sz.imageset/sz@2x.png | Bin .../flags}/sz.imageset/sz@3x.png | Bin .../flags}/tc.imageset/Contents.json | 0 .../flags}/tc.imageset/tc@2x.png | Bin .../flags}/tc.imageset/tc@3x.png | Bin .../flags}/td.imageset/Contents.json | 0 .../flags}/td.imageset/td@2x.png | Bin .../flags}/td.imageset/td@3x.png | Bin .../flags}/tf.imageset/Contents.json | 0 .../flags}/tf.imageset/tf@2x.png | Bin .../flags}/tf.imageset/tf@3x.png | Bin .../flags}/tg.imageset/Contents.json | 0 .../flags}/tg.imageset/tg@2x.png | Bin .../flags}/tg.imageset/tg@3x.png | Bin .../flags}/th.imageset/Contents.json | 0 .../flags}/th.imageset/th@2x.png | Bin .../flags}/th.imageset/th@3x.png | Bin .../flags}/tj.imageset/Contents.json | 0 .../flags}/tj.imageset/tj@2x.png | Bin .../flags}/tj.imageset/tj@3x.png | Bin .../flags}/tk.imageset/Contents.json | 0 .../flags}/tk.imageset/tk@2x.png | Bin .../flags}/tk.imageset/tk@3x.png | Bin .../flags}/tl.imageset/Contents.json | 0 .../flags}/tl.imageset/tl@2x.png | Bin .../flags}/tl.imageset/tl@3x.png | Bin .../flags}/tm.imageset/Contents.json | 0 .../flags}/tm.imageset/tm@2x.png | Bin .../flags}/tm.imageset/tm@3x.png | Bin .../flags}/tn.imageset/Contents.json | 0 .../flags}/tn.imageset/tn@2x.png | Bin .../flags}/tn.imageset/tn@3x.png | Bin .../flags}/to.imageset/Contents.json | 0 .../flags}/to.imageset/to@2x.png | Bin .../flags}/to.imageset/to@3x.png | Bin .../flags}/tr.imageset/Contents.json | 0 .../flags}/tr.imageset/tr@2x.png | Bin .../flags}/tr.imageset/tr@3x.png | Bin .../flags}/tt.imageset/Contents.json | 0 .../flags}/tt.imageset/tt@2x.png | Bin .../flags}/tt.imageset/tt@3x.png | Bin .../flags}/tv.imageset/Contents.json | 0 .../flags}/tv.imageset/tv@2x.png | Bin .../flags}/tv.imageset/tv@3x.png | Bin .../flags}/tw.imageset/Contents.json | 0 .../flags}/tw.imageset/tw@2x.png | Bin .../flags}/tw.imageset/tw@3x.png | Bin .../flags}/tz.imageset/Contents.json | 0 .../flags}/tz.imageset/tz@2x.png | Bin .../flags}/tz.imageset/tz@3x.png | Bin .../flags}/ua.imageset/Contents.json | 0 .../flags}/ua.imageset/ua@2x.png | Bin .../flags}/ua.imageset/ua@3x.png | Bin .../flags}/ug.imageset/Contents.json | 0 .../flags}/ug.imageset/ug@2x.png | Bin .../flags}/ug.imageset/ug@3x.png | Bin .../flags}/um.imageset/Contents.json | 0 .../flags}/um.imageset/um@2x.png | Bin .../flags}/um.imageset/um@3x.png | Bin .../flags}/un.imageset/Contents.json | 0 .../flags}/un.imageset/un@2x.png | Bin .../flags}/un.imageset/un@3x.png | Bin .../flags}/us.imageset/Contents.json | 0 .../flags}/us.imageset/us@2x.png | Bin .../flags}/us.imageset/us@3x.png | Bin .../flags}/uy.imageset/Contents.json | 0 .../flags}/uy.imageset/uy@2x.png | Bin .../flags}/uy.imageset/uy@3x.png | Bin .../flags}/uz.imageset/Contents.json | 0 .../flags}/uz.imageset/uz@2x.png | Bin .../flags}/uz.imageset/uz@3x.png | Bin .../flags}/va.imageset/Contents.json | 0 .../flags}/va.imageset/va@2x.png | Bin .../flags}/va.imageset/va@3x.png | Bin .../flags}/vc.imageset/Contents.json | 0 .../flags}/vc.imageset/vc@2x.png | Bin .../flags}/vc.imageset/vc@3x.png | Bin .../flags}/ve.imageset/Contents.json | 0 .../flags}/ve.imageset/ve@2x.png | Bin .../flags}/ve.imageset/ve@3x.png | Bin .../flags}/vg.imageset/Contents.json | 0 .../flags}/vg.imageset/vg@2x.png | Bin .../flags}/vg.imageset/vg@3x.png | Bin .../flags}/vi.imageset/Contents.json | 0 .../flags}/vi.imageset/vi@2x.png | Bin .../flags}/vi.imageset/vi@3x.png | Bin .../flags}/vn.imageset/Contents.json | 0 .../flags}/vn.imageset/vn@2x.png | Bin .../flags}/vn.imageset/vn@3x.png | Bin .../flags}/vu.imageset/Contents.json | 0 .../flags}/vu.imageset/vu@2x.png | Bin .../flags}/vu.imageset/vu@3x.png | Bin .../flags}/wf.imageset/Contents.json | 0 .../flags}/wf.imageset/wf@2x.png | Bin .../flags}/wf.imageset/wf@3x.png | Bin .../flags}/ws.imageset/Contents.json | 0 .../flags}/ws.imageset/ws@2x.png | Bin .../flags}/ws.imageset/ws@3x.png | Bin .../flags}/xk.imageset/Contents.json | 0 .../flags}/xk.imageset/xk@2x.png | Bin .../flags}/xk.imageset/xk@3x.png | Bin .../flags}/ye.imageset/Contents.json | 0 .../flags}/ye.imageset/ye@2x.png | Bin .../flags}/ye.imageset/ye@3x.png | Bin .../flags}/yt.imageset/Contents.json | 0 .../flags}/yt.imageset/yt@2x.png | Bin .../flags}/yt.imageset/yt@3x.png | Bin .../flags}/za.imageset/Contents.json | 0 .../flags}/za.imageset/za@2x.png | Bin .../flags}/za.imageset/za@3x.png | Bin .../flags}/zm.imageset/Contents.json | 0 .../flags}/zm.imageset/zm@2x.png | Bin .../flags}/zm.imageset/zm@3x.png | Bin .../flags}/zw.imageset/Contents.json | 0 .../flags}/zw.imageset/zw@2x.png | Bin .../flags}/zw.imageset/zw@3x.png | Bin .../App/Shared/InApp}/LocalProduct.swift | 96 +- .../App/Shared/InApp}/ProductManager.swift | 231 +-- Passepartout/App/{iOS => Shared}/Info.plist | 42 +- .../Base.lproj/Intents.intentdefinition | 70 +- .../Intents/IntentDispatcher+Activities.swift | 164 ++ .../App/Shared/Intents/IntentDispatcher.swift | 159 ++ .../App/Shared/Intents/IntentsManager.swift | 114 ++ .../Intents}/de.lproj/Intents.strings | 6 +- .../Intents}/el.lproj/Intents.strings | 6 +- .../Intents}/en.lproj/Intents.strings | 6 +- .../Intents}/es.lproj/Intents.strings | 6 +- .../Intents}/fr.lproj/Intents.strings | 6 +- .../Intents}/it.lproj/Intents.strings | 6 +- .../Intents}/nl.lproj/Intents.strings | 6 +- .../Intents}/pl.lproj/Intents.strings | 6 +- .../Intents}/pt.lproj/Intents.strings | 6 +- .../Intents}/ru.lproj/Intents.strings | 6 +- .../Intents}/sv.lproj/Intents.strings | 6 +- .../Intents}/zh-Hans.lproj/Intents.strings | 4 +- Passepartout/App/Shared/L10n/Core+L10n.swift | 106 + .../App/Shared/L10n/OpenVPN+L10n.swift | 122 ++ .../App/Shared/L10n/Providers+L10n.swift | 98 + .../App/Shared/L10n/TunnelKit+L10n.swift | 238 +++ .../App/Shared/L10n/Unlocalized.swift | 252 +++ .../App/Shared/L10n/WireGuard+L10n.swift | 27 + .../Shared/L10n/de.lproj/Localizable.strings | 344 ++++ .../Shared/L10n/el.lproj/Localizable.strings | 344 ++++ .../Shared/L10n/en.lproj/Localizable.strings | 344 ++++ .../Shared/L10n/es.lproj/Localizable.strings | 344 ++++ .../Shared/L10n/fr.lproj/Localizable.strings | 344 ++++ .../Shared/L10n/it.lproj/Localizable.strings | 344 ++++ .../Shared/L10n/nl.lproj/Localizable.strings | 344 ++++ .../Shared/L10n/pl.lproj/Localizable.strings | 344 ++++ .../Shared/L10n/pt.lproj/Localizable.strings | 344 ++++ .../Shared/L10n/ru.lproj/Localizable.strings | 344 ++++ .../Shared/L10n/sv.lproj/Localizable.strings | 344 ++++ .../L10n/zh-Hans.lproj/Localizable.strings | 344 ++++ Passepartout/App/Shared/PassepartoutApp.swift | 61 + .../Providers.xcassets}/Contents.json | 0 .../providers}/Contents.json | 8 +- .../providers}/hideme.imageset/Contents.json | 0 .../providers}/hideme.imageset/hideme@2x.png | Bin .../providers}/hideme.imageset/hideme@3x.png | Bin .../providers}/mullvad.imageset/Contents.json | 0 .../mullvad.imageset/mullvad@2x.png | Bin .../mullvad.imageset/mullvad@3x.png | Bin .../providers}/nordvpn.imageset/Contents.json | 0 .../nordvpn.imageset/nordvpn-dark@2x.png | Bin .../nordvpn.imageset/nordvpn-dark@3x.png | Bin .../nordvpn.imageset/nordvpn@2x.png | Bin .../nordvpn.imageset/nordvpn@3x.png | Bin .../providers}/oeck.imageset/Contents.json | 0 .../providers}/oeck.imageset/oeck-dark@2x.png | Bin .../providers}/oeck.imageset/oeck-dark@3x.png | Bin .../providers}/oeck.imageset/oeck@2x.png | Bin .../providers}/oeck.imageset/oeck@3x.png | Bin .../providers}/pia.imageset/Contents.json | 0 .../providers}/pia.imageset/pia@2x.png | Bin .../providers}/pia.imageset/pia@3x.png | Bin .../placeholder.imageset/Contents.json | 0 .../placeholder.imageset/placeholder@2x.png | Bin .../placeholder.imageset/placeholder@3x.png | Bin .../protonvpn.imageset/Contents.json | 0 .../protonvpn.imageset/protonvpn@2x.png | Bin .../protonvpn.imageset/protonvpn@3x.png | Bin .../surfshark.imageset/Contents.json | 0 .../surfshark.imageset/surfshark@2x.png | Bin .../surfshark.imageset/surfshark@3x.png | Bin .../torguard.imageset/Contents.json | 0 .../torguard.imageset/torguard@2x.png | Bin .../torguard.imageset/torguard@3x.png | Bin .../tunnelbear.imageset/Contents.json | 0 .../tunnelbear.imageset/tunnelbear@2x.png | Bin .../tunnelbear.imageset/tunnelbear@3x.png | Bin .../providers}/vyprvpn.imageset/Contents.json | 0 .../vyprvpn.imageset/vyprvpn@2x.png | Bin .../vyprvpn.imageset/vyprvpn@3x.png | Bin .../windscribe.imageset/Contents.json | 0 .../windscribe.imageset/windscribe@2x.png | Bin .../windscribe.imageset/windscribe@3x.png | Bin .../App/Shared/Reusable/AddingTextField.swift | 68 + .../Reusable/Binding+Extensions.swift} | 49 +- .../Shared/Reusable/CopySavingButton.swift | 80 + .../Shared/Reusable/EditableTextList.swift | 206 ++ .../Shared/Reusable/GenericCreditsView.swift | 175 ++ .../Shared/Reusable/GenericVersionView.swift | 56 + Passepartout/App/Shared/Reusable/InApp.swift | 225 ++ .../App/Shared/Reusable/IntentActivity.swift | 40 + .../App/Shared/Reusable/LongContentView.swift | 77 + .../Shared/Reusable/ReloadingSection.swift | 81 + .../Reusable/RevealingSecureField.swift | 72 + .../App/Shared/Reusable/Reviewer.swift | 96 + .../App/Shared/Reusable/Shortcut.swift | 55 + .../App/Shared/Reusable/StyledPicker.swift | 74 + .../App/Shared/Reusable/Validators.swift | 80 + .../App/Shared/Reusable/View+Extensions.swift | 132 ++ .../Settings.bundle/Root.plist | 0 .../Settings.bundle/en.lproj/Root.strings | Bin .../de.lproj/InfoPlist.strings | 0 .../el.lproj/InfoPlist.strings | 0 .../en.lproj/InfoPlist.strings | 0 .../es.lproj/InfoPlist.strings | 0 .../fr.lproj/InfoPlist.strings | 0 .../it.lproj/InfoPlist.strings | 0 .../nl.lproj/InfoPlist.strings | 0 .../pl.lproj/InfoPlist.strings | 0 .../pt.lproj/InfoPlist.strings | 0 .../ru.lproj/InfoPlist.strings | 0 .../sv.lproj/InfoPlist.strings | 0 Passepartout/App/Shared/swiftgen.yml | 15 + .../zh-Hans.lproj/InfoPlist.strings | 0 Passepartout/App/iOS/App.entitlements | 22 - Passepartout/App/iOS/AppDelegate.swift | 174 -- .../App/iOS/Assets.xcassets/Contents.json | 6 - .../App/iOS/Base.lproj/About.storyboard | 78 - .../iOS/Base.lproj/LaunchScreen.storyboard | 41 - .../App/iOS/Base.lproj/Main.storyboard | 686 ------- .../App/iOS/Base.lproj/Organizer.storyboard | 421 ---- .../App/iOS/Base.lproj/Purchase.storyboard | 104 - .../App/iOS/Base.lproj/Shortcuts.storyboard | 209 -- Passepartout/App/iOS/CHANGELOG.md | 10 + Passepartout/App/iOS/Cells/Cells.swift | 58 - .../iOS/Cells/DestructiveTableViewCell.swift | 75 - .../App/iOS/Cells/FieldTableViewCell.swift | 137 -- .../App/iOS/Cells/SettingTableViewCell.swift | 90 - .../App/iOS/Cells/ToggleTableViewCell.swift | 112 - .../App/iOS/Flags.xcassets/Contents.json | 6 - .../App/iOS/Global/HostImporter.swift | 169 -- .../App/iOS/Global/IssueReporter.swift | 111 - Passepartout/App/iOS/Global/Macros.swift | 94 - .../App/iOS/Global/SwiftGen+Assets.swift | 355 ---- .../App/iOS/Global/SwiftGen+Scenes.swift | 119 -- .../App/iOS/Global/SwiftGen+Segues.swift | 58 - Passepartout/App/iOS/Global/Theme+Cells.swift | 168 -- Passepartout/App/iOS/Global/Theme.swift | 229 --- .../App/iOS/Global/UITextView+Search.swift | 96 - .../App/iOS/Intents/IntentDispatcher.swift | 320 --- .../csv.imageset/Contents.json | 22 - .../csv.imageset/csv@2x.png | Bin 3435 -> 0 bytes .../csv.imageset/csv@3x.png | Bin 5407 -> 0 bytes .../App/iOS/Reusable/ActivityView.swift | 40 + .../App/iOS/Reusable/IntentAddView.swift | 46 + .../App/iOS/Reusable/IntentEditView.swift | 46 + .../App/iOS/Reusable/MailComposerView.swift | 79 + .../Scenes/About/AboutViewController.swift | 236 --- .../iOS/Scenes/AccountViewController.swift | 288 --- .../Scenes/ConfigurationViewController.swift | 449 ---- .../iOS/Scenes/DebugLogViewController.swift | 147 -- .../iOS/Scenes/EndpointViewController.swift | 302 --- .../NetworkSettingsViewController.swift | 798 -------- .../Organizer/DonationViewController.swift | 213 -- .../ImportedHostsViewController.swift | 89 - .../Organizer/OrganizerViewController.swift | 798 -------- .../Organizer/WizardHostViewController.swift | 269 --- .../WizardProviderViewController.swift | 246 --- .../Scenes/ProviderPoolViewController.swift | 287 --- .../Scenes/ProviderPresetViewController.swift | 131 -- .../Purchase/PurchaseTableViewCell.swift | 58 - .../Purchase/PurchaseViewController.swift | 261 --- .../Scenes/ServerNetworkViewController.swift | 291 --- .../iOS/Scenes/ServiceViewController.swift | 1536 -------------- .../ShortcutsAddViewController.swift | 208 -- .../ShortcutsConnectToViewController.swift | 186 -- .../Shortcuts/ShortcutsViewController.swift | 279 --- .../App/iOS/ViewModels/AddHostViewModel.swift | 105 + .../iOS/ViewModels/AddProviderViewModel.swift | 170 ++ Passepartout/App/iOS/Views/AboutView.swift | 143 ++ Passepartout/App/iOS/Views/AccountView.swift | 132 ++ .../iOS/Views/AddProfile/AddHostView.swift | 194 ++ .../iOS/Views/AddProfile/AddProfileView.swift | 116 ++ .../AddProfile/AddProviderView+Name.swift | 129 ++ .../Views/AddProfile/AddProviderView.swift | 181 ++ Passepartout/App/iOS/Views/CreditsView.swift | 39 + Passepartout/App/iOS/Views/DebugLogView.swift | 131 ++ .../iOS/Views/DiagnosticsView+OpenVPN.swift | 193 ++ .../iOS/Views/DiagnosticsView+WireGuard.swift | 63 + .../App/iOS/Views/DiagnosticsView.swift | 49 + Passepartout/App/iOS/Views/DonateView.swift | 153 ++ .../Views/EndpointAdvancedView+OpenVPN.swift | 310 +++ .../EndpointAdvancedView+WireGuard.swift | 99 + .../Views/EndpointAdvancedView.swift} | 11 +- .../App/iOS/Views/EndpointView+OpenVPN.swift | 274 +++ .../iOS/Views/EndpointView+WireGuard.swift | 146 ++ .../EndpointView.swift} | 41 +- Passepartout/App/iOS/Views/MainView.swift | 41 + .../App/iOS/Views/NetworkSettingsView.swift | 281 +++ .../App/iOS/Views/OnDemandView+SSID.swift | 134 ++ Passepartout/App/iOS/Views/OnDemandView.swift | 123 ++ .../Views/OrganizerView+AddProfileMenu.swift | 118 ++ .../iOS/Views/OrganizerView+Profiles.swift | 157 ++ .../App/iOS/Views/OrganizerView+Scene.swift | 91 + .../iOS/Views/OrganizerView+Shortcuts.swift | 70 + .../App/iOS/Views/OrganizerView+VPN.swift | 63 + .../App/iOS/Views/OrganizerView.swift | 275 +++ .../iOS/Views/Paywall/PaywallView+Beta.swift | 36 + .../Views/Paywall/PaywallView+Purchase.swift | 311 +++ .../App/iOS/Views/Paywall/PaywallView.swift | 47 + .../Views/Profile/ProfileView+Rename.swift | 104 + .../App/iOS/Views/ProfileHeaderRow.swift | 52 + .../iOS/Views/ProfileView+Configuration.swift | 105 + .../iOS/Views/ProfileView+Diagnostics.swift | 76 + .../App/iOS/Views/ProfileView+Extra.swift | 58 + .../App/iOS/Views/ProfileView+Provider.swift | 127 ++ .../App/iOS/Views/ProfileView+VPN.swift | 127 ++ .../App/iOS/Views/ProfileView+Welcome.swift | 35 + Passepartout/App/iOS/Views/ProfileView.swift | 217 ++ .../App/iOS/Views/ProviderLocationView.swift | 321 +++ .../App/iOS/Views/ProviderPresetView.swift | 104 + .../App/iOS/Views/ReportIssueView.swift | 90 + .../App/iOS/Views/ShortcutsView+Add.swift | 104 + .../iOS/Views/ShortcutsView+ConnectTo.swift | 135 ++ .../App/iOS/Views/ShortcutsView.swift | 140 ++ Passepartout/App/iOS/Views/VersionView.swift | 41 + Passepartout/App/iOS/swiftgen.yml | 21 - Passepartout/App/macOS/AppDelegate.swift | 144 -- .../AppIcon.appiconset/AppIcon-1024.png | Bin 63023 -> 0 bytes .../AppIcon.appiconset/AppIcon-256.png | Bin 9730 -> 0 bytes .../AppIcon.appiconset/AppIcon-32.png | Bin 1508 -> 0 bytes .../AppIcon.appiconset/AppIcon-512.png | Bin 25585 -> 0 bytes .../AppIcon.appiconset/AppIcon-64.png | Bin 2484 -> 0 bytes .../AppIcon.appiconset/Contents.json | 63 - .../StatusActive.imageset/Contents.json | 25 - .../StatusActive.imageset/StatusActive@2x.png | Bin 798 -> 0 bytes .../StatusActive.imageset/StatusActive@3x.png | Bin 1038 -> 0 bytes .../StatusPending.imageset/Contents.json | 25 - .../StatusPending@2x.png | Bin 1230 -> 0 bytes .../StatusPending@3x.png | Bin 1658 -> 0 bytes .../App/macOS/Base.lproj/Main.storyboard | 261 --- .../macOS/Base.lproj/Preferences.storyboard | 485 ----- .../App/macOS/Base.lproj/Purchase.storyboard | 196 -- .../App/macOS/Base.lproj/Service.storyboard | 1573 -------------- Passepartout/App/macOS/CHANGELOG.md | 6 + .../App/macOS/Flags.xcassets/Contents.json | 6 - .../Flags.xcassets/ad.imageset/Contents.json | 22 - .../Flags.xcassets/ad.imageset/ad@2x.png | Bin 560 -> 0 bytes .../Flags.xcassets/ad.imageset/ad@3x.png | Bin 994 -> 0 bytes .../Flags.xcassets/ae.imageset/Contents.json | 22 - .../Flags.xcassets/ae.imageset/ae@2x.png | Bin 249 -> 0 bytes .../Flags.xcassets/ae.imageset/ae@3x.png | Bin 260 -> 0 bytes .../Flags.xcassets/af.imageset/Contents.json | 22 - .../Flags.xcassets/af.imageset/af@2x.png | Bin 587 -> 0 bytes .../Flags.xcassets/af.imageset/af@3x.png | Bin 1026 -> 0 bytes .../Flags.xcassets/ag.imageset/Contents.json | 22 - .../Flags.xcassets/ag.imageset/ag@2x.png | Bin 919 -> 0 bytes .../Flags.xcassets/ag.imageset/ag@3x.png | Bin 1253 -> 0 bytes .../Flags.xcassets/ai.imageset/Contents.json | 22 - .../Flags.xcassets/ai.imageset/ai@2x.png | Bin 1160 -> 0 bytes .../Flags.xcassets/ai.imageset/ai@3x.png | Bin 1916 -> 0 bytes .../Flags.xcassets/al.imageset/Contents.json | 22 - .../Flags.xcassets/al.imageset/al@2x.png | Bin 740 -> 0 bytes .../Flags.xcassets/al.imageset/al@3x.png | Bin 1318 -> 0 bytes .../Flags.xcassets/am.imageset/Contents.json | 22 - .../Flags.xcassets/am.imageset/am@2x.png | Bin 191 -> 0 bytes .../Flags.xcassets/am.imageset/am@3x.png | Bin 194 -> 0 bytes .../Flags.xcassets/ao.imageset/Contents.json | 22 - .../Flags.xcassets/ao.imageset/ao@2x.png | Bin 522 -> 0 bytes .../Flags.xcassets/ao.imageset/ao@3x.png | Bin 804 -> 0 bytes .../Flags.xcassets/aq.imageset/Contents.json | 22 - .../Flags.xcassets/aq.imageset/aq@2x.png | Bin 624 -> 0 bytes .../Flags.xcassets/aq.imageset/aq@3x.png | Bin 908 -> 0 bytes .../Flags.xcassets/ar.imageset/Contents.json | 22 - .../Flags.xcassets/ar.imageset/ar@2x.png | Bin 391 -> 0 bytes .../Flags.xcassets/ar.imageset/ar@3x.png | Bin 595 -> 0 bytes .../Flags.xcassets/as.imageset/Contents.json | 22 - .../Flags.xcassets/as.imageset/as@2x.png | Bin 1097 -> 0 bytes .../Flags.xcassets/as.imageset/as@3x.png | Bin 1730 -> 0 bytes .../Flags.xcassets/at.imageset/Contents.json | 22 - .../Flags.xcassets/at.imageset/at@2x.png | Bin 185 -> 0 bytes .../Flags.xcassets/at.imageset/at@3x.png | Bin 192 -> 0 bytes .../Flags.xcassets/au.imageset/Contents.json | 22 - .../Flags.xcassets/au.imageset/au@2x.png | Bin 1250 -> 0 bytes .../Flags.xcassets/au.imageset/au@3x.png | Bin 1972 -> 0 bytes .../Flags.xcassets/aw.imageset/Contents.json | 22 - .../Flags.xcassets/aw.imageset/aw@2x.png | Bin 470 -> 0 bytes .../Flags.xcassets/aw.imageset/aw@3x.png | Bin 643 -> 0 bytes .../Flags.xcassets/ax.imageset/Contents.json | 22 - .../Flags.xcassets/ax.imageset/ax@2x.png | Bin 341 -> 0 bytes .../Flags.xcassets/ax.imageset/ax@3x.png | Bin 480 -> 0 bytes .../Flags.xcassets/az.imageset/Contents.json | 22 - .../Flags.xcassets/az.imageset/az@2x.png | Bin 490 -> 0 bytes .../Flags.xcassets/az.imageset/az@3x.png | Bin 614 -> 0 bytes .../Flags.xcassets/ba.imageset/Contents.json | 22 - .../Flags.xcassets/ba.imageset/ba@2x.png | Bin 951 -> 0 bytes .../Flags.xcassets/ba.imageset/ba@3x.png | Bin 1662 -> 0 bytes .../Flags.xcassets/bb.imageset/Contents.json | 22 - .../Flags.xcassets/bb.imageset/bb@2x.png | Bin 460 -> 0 bytes .../Flags.xcassets/bb.imageset/bb@3x.png | Bin 703 -> 0 bytes .../Flags.xcassets/bd.imageset/Contents.json | 22 - .../Flags.xcassets/bd.imageset/bd@2x.png | Bin 531 -> 0 bytes .../Flags.xcassets/bd.imageset/bd@3x.png | Bin 739 -> 0 bytes .../Flags.xcassets/be.imageset/Contents.json | 22 - .../Flags.xcassets/be.imageset/be@2x.png | Bin 169 -> 0 bytes .../Flags.xcassets/be.imageset/be@3x.png | Bin 217 -> 0 bytes .../Flags.xcassets/bf.imageset/Contents.json | 22 - .../Flags.xcassets/bf.imageset/bf@2x.png | Bin 529 -> 0 bytes .../Flags.xcassets/bf.imageset/bf@3x.png | Bin 700 -> 0 bytes .../Flags.xcassets/bg.imageset/Contents.json | 22 - .../Flags.xcassets/bg.imageset/bg@2x.png | Bin 205 -> 0 bytes .../Flags.xcassets/bg.imageset/bg@3x.png | Bin 201 -> 0 bytes .../Flags.xcassets/bh.imageset/Contents.json | 22 - .../Flags.xcassets/bh.imageset/bh@2x.png | Bin 601 -> 0 bytes .../Flags.xcassets/bh.imageset/bh@3x.png | Bin 677 -> 0 bytes .../Flags.xcassets/bi.imageset/Contents.json | 22 - .../Flags.xcassets/bi.imageset/bi@2x.png | Bin 956 -> 0 bytes .../Flags.xcassets/bi.imageset/bi@3x.png | Bin 1361 -> 0 bytes .../Flags.xcassets/bj.imageset/Contents.json | 22 - .../Flags.xcassets/bj.imageset/bj@2x.png | Bin 194 -> 0 bytes .../Flags.xcassets/bj.imageset/bj@3x.png | Bin 218 -> 0 bytes .../Flags.xcassets/bl.imageset/Contents.json | 22 - .../Flags.xcassets/bl.imageset/bl@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/bl.imageset/bl@3x.png | Bin 243 -> 0 bytes .../Flags.xcassets/bm.imageset/Contents.json | 22 - .../Flags.xcassets/bm.imageset/bm@2x.png | Bin 979 -> 0 bytes .../Flags.xcassets/bm.imageset/bm@3x.png | Bin 1525 -> 0 bytes .../Flags.xcassets/bn.imageset/Contents.json | 22 - .../Flags.xcassets/bn.imageset/bn@2x.png | Bin 1292 -> 0 bytes .../Flags.xcassets/bn.imageset/bn@3x.png | Bin 1845 -> 0 bytes .../Flags.xcassets/bo.imageset/Contents.json | 22 - .../Flags.xcassets/bo.imageset/bo@2x.png | Bin 467 -> 0 bytes .../Flags.xcassets/bo.imageset/bo@3x.png | Bin 679 -> 0 bytes .../Flags.xcassets/bq.imageset/Contents.json | 22 - .../Flags.xcassets/bq.imageset/bq@2x.png | Bin 213 -> 0 bytes .../Flags.xcassets/bq.imageset/bq@3x.png | Bin 210 -> 0 bytes .../Flags.xcassets/br.imageset/Contents.json | 22 - .../Flags.xcassets/br.imageset/br@2x.png | Bin 1312 -> 0 bytes .../Flags.xcassets/br.imageset/br@3x.png | Bin 1922 -> 0 bytes .../Flags.xcassets/bs.imageset/Contents.json | 22 - .../Flags.xcassets/bs.imageset/bs@2x.png | Bin 647 -> 0 bytes .../Flags.xcassets/bs.imageset/bs@3x.png | Bin 801 -> 0 bytes .../Flags.xcassets/bt.imageset/Contents.json | 22 - .../Flags.xcassets/bt.imageset/bt@2x.png | Bin 1139 -> 0 bytes .../Flags.xcassets/bt.imageset/bt@3x.png | Bin 2040 -> 0 bytes .../Flags.xcassets/bv.imageset/Contents.json | 22 - .../Flags.xcassets/bv.imageset/bv@2x.png | Bin 380 -> 0 bytes .../Flags.xcassets/bv.imageset/bv@3x.png | Bin 476 -> 0 bytes .../Flags.xcassets/bw.imageset/Contents.json | 22 - .../Flags.xcassets/bw.imageset/bw@2x.png | Bin 232 -> 0 bytes .../Flags.xcassets/bw.imageset/bw@3x.png | Bin 204 -> 0 bytes .../Flags.xcassets/by.imageset/Contents.json | 22 - .../Flags.xcassets/by.imageset/by@2x.png | Bin 717 -> 0 bytes .../Flags.xcassets/by.imageset/by@3x.png | Bin 1456 -> 0 bytes .../Flags.xcassets/bz.imageset/Contents.json | 22 - .../Flags.xcassets/bz.imageset/bz@2x.png | Bin 1505 -> 0 bytes .../Flags.xcassets/bz.imageset/bz@3x.png | Bin 2754 -> 0 bytes .../Flags.xcassets/ca.imageset/Contents.json | 22 - .../Flags.xcassets/ca.imageset/ca@2x.png | Bin 450 -> 0 bytes .../Flags.xcassets/ca.imageset/ca@3x.png | Bin 705 -> 0 bytes .../Flags.xcassets/cc.imageset/Contents.json | 22 - .../Flags.xcassets/cc.imageset/cc@2x.png | Bin 650 -> 0 bytes .../Flags.xcassets/cc.imageset/cc@3x.png | Bin 1014 -> 0 bytes .../Flags.xcassets/cd.imageset/Contents.json | 22 - .../Flags.xcassets/cd.imageset/cd@2x.png | Bin 910 -> 0 bytes .../Flags.xcassets/cd.imageset/cd@3x.png | Bin 1081 -> 0 bytes .../Flags.xcassets/cf.imageset/Contents.json | 22 - .../Flags.xcassets/cf.imageset/cf@2x.png | Bin 466 -> 0 bytes .../Flags.xcassets/cf.imageset/cf@3x.png | Bin 546 -> 0 bytes .../Flags.xcassets/cg.imageset/Contents.json | 22 - .../Flags.xcassets/cg.imageset/cg@2x.png | Bin 273 -> 0 bytes .../Flags.xcassets/cg.imageset/cg@3x.png | Bin 360 -> 0 bytes .../Flags.xcassets/ch.imageset/Contents.json | 22 - .../Flags.xcassets/ch.imageset/ch@2x.png | Bin 310 -> 0 bytes .../Flags.xcassets/ch.imageset/ch@3x.png | Bin 349 -> 0 bytes .../Flags.xcassets/ci.imageset/Contents.json | 22 - .../Flags.xcassets/ci.imageset/ci@2x.png | Bin 166 -> 0 bytes .../Flags.xcassets/ci.imageset/ci@3x.png | Bin 222 -> 0 bytes .../Flags.xcassets/ck.imageset/Contents.json | 22 - .../Flags.xcassets/ck.imageset/ck@2x.png | Bin 1326 -> 0 bytes .../Flags.xcassets/ck.imageset/ck@3x.png | Bin 2129 -> 0 bytes .../Flags.xcassets/cl.imageset/Contents.json | 22 - .../Flags.xcassets/cl.imageset/cl@2x.png | Bin 363 -> 0 bytes .../Flags.xcassets/cl.imageset/cl@3x.png | Bin 499 -> 0 bytes .../Flags.xcassets/cm.imageset/Contents.json | 22 - .../Flags.xcassets/cm.imageset/cm@2x.png | Bin 304 -> 0 bytes .../Flags.xcassets/cm.imageset/cm@3x.png | Bin 436 -> 0 bytes .../Flags.xcassets/cn.imageset/Contents.json | 22 - .../Flags.xcassets/cn.imageset/cn@2x.png | Bin 472 -> 0 bytes .../Flags.xcassets/cn.imageset/cn@3x.png | Bin 657 -> 0 bytes .../Flags.xcassets/co.imageset/Contents.json | 22 - .../Flags.xcassets/co.imageset/co@2x.png | Bin 208 -> 0 bytes .../Flags.xcassets/co.imageset/co@3x.png | Bin 196 -> 0 bytes .../Flags.xcassets/cr.imageset/Contents.json | 22 - .../Flags.xcassets/cr.imageset/cr@2x.png | Bin 253 -> 0 bytes .../Flags.xcassets/cr.imageset/cr@3x.png | Bin 218 -> 0 bytes .../Flags.xcassets/cu.imageset/Contents.json | 22 - .../Flags.xcassets/cu.imageset/cu@2x.png | Bin 766 -> 0 bytes .../Flags.xcassets/cu.imageset/cu@3x.png | Bin 1080 -> 0 bytes .../Flags.xcassets/cv.imageset/Contents.json | 22 - .../Flags.xcassets/cv.imageset/cv@2x.png | Bin 711 -> 0 bytes .../Flags.xcassets/cv.imageset/cv@3x.png | Bin 1023 -> 0 bytes .../Flags.xcassets/cw.imageset/Contents.json | 22 - .../Flags.xcassets/cw.imageset/cw@2x.png | Bin 410 -> 0 bytes .../Flags.xcassets/cw.imageset/cw@3x.png | Bin 567 -> 0 bytes .../Flags.xcassets/cx.imageset/Contents.json | 22 - .../Flags.xcassets/cx.imageset/cx@2x.png | Bin 882 -> 0 bytes .../Flags.xcassets/cx.imageset/cx@3x.png | Bin 1280 -> 0 bytes .../Flags.xcassets/cy.imageset/Contents.json | 22 - .../Flags.xcassets/cy.imageset/cy@2x.png | Bin 736 -> 0 bytes .../Flags.xcassets/cy.imageset/cy@3x.png | Bin 1149 -> 0 bytes .../Flags.xcassets/cz.imageset/Contents.json | 22 - .../Flags.xcassets/cz.imageset/cz@2x.png | Bin 415 -> 0 bytes .../Flags.xcassets/cz.imageset/cz@3x.png | Bin 525 -> 0 bytes .../Flags.xcassets/de.imageset/Contents.json | 22 - .../Flags.xcassets/de.imageset/de@2x.png | Bin 186 -> 0 bytes .../Flags.xcassets/de.imageset/de@3x.png | Bin 190 -> 0 bytes .../Flags.xcassets/dj.imageset/Contents.json | 22 - .../Flags.xcassets/dj.imageset/dj@2x.png | Bin 506 -> 0 bytes .../Flags.xcassets/dj.imageset/dj@3x.png | Bin 649 -> 0 bytes .../Flags.xcassets/dk.imageset/Contents.json | 22 - .../Flags.xcassets/dk.imageset/dk@2x.png | Bin 276 -> 0 bytes .../Flags.xcassets/dk.imageset/dk@3x.png | Bin 316 -> 0 bytes .../Flags.xcassets/dm.imageset/Contents.json | 22 - .../Flags.xcassets/dm.imageset/dm@2x.png | Bin 881 -> 0 bytes .../Flags.xcassets/dm.imageset/dm@3x.png | Bin 1384 -> 0 bytes .../Flags.xcassets/do.imageset/Contents.json | 22 - .../Flags.xcassets/do.imageset/do@2x.png | Bin 440 -> 0 bytes .../Flags.xcassets/do.imageset/do@3x.png | Bin 553 -> 0 bytes .../Flags.xcassets/dz.imageset/Contents.json | 22 - .../Flags.xcassets/dz.imageset/dz@2x.png | Bin 627 -> 0 bytes .../Flags.xcassets/dz.imageset/dz@3x.png | Bin 873 -> 0 bytes .../Flags.xcassets/ec.imageset/Contents.json | 22 - .../Flags.xcassets/ec.imageset/ec@2x.png | Bin 1253 -> 0 bytes .../Flags.xcassets/ec.imageset/ec@3x.png | Bin 2074 -> 0 bytes .../Flags.xcassets/ee.imageset/Contents.json | 22 - .../Flags.xcassets/ee.imageset/ee@2x.png | Bin 203 -> 0 bytes .../Flags.xcassets/ee.imageset/ee@3x.png | Bin 203 -> 0 bytes .../Flags.xcassets/eg.imageset/Contents.json | 22 - .../Flags.xcassets/eg.imageset/eg@2x.png | Bin 401 -> 0 bytes .../Flags.xcassets/eg.imageset/eg@3x.png | Bin 570 -> 0 bytes .../Flags.xcassets/eh.imageset/Contents.json | 22 - .../Flags.xcassets/eh.imageset/eh@2x.png | Bin 645 -> 0 bytes .../Flags.xcassets/eh.imageset/eh@3x.png | Bin 790 -> 0 bytes .../Flags.xcassets/er.imageset/Contents.json | 22 - .../Flags.xcassets/er.imageset/er@2x.png | Bin 1176 -> 0 bytes .../Flags.xcassets/er.imageset/er@3x.png | Bin 1845 -> 0 bytes .../es-ct.imageset/Contents.json | 22 - .../es-ct.imageset/es-ct@2x.png | Bin 330 -> 0 bytes .../es-ct.imageset/es-ct@3x.png | Bin 240 -> 0 bytes .../Flags.xcassets/es.imageset/Contents.json | 22 - .../Flags.xcassets/es.imageset/es@2x.png | Bin 579 -> 0 bytes .../Flags.xcassets/es.imageset/es@3x.png | Bin 885 -> 0 bytes .../Flags.xcassets/et.imageset/Contents.json | 22 - .../Flags.xcassets/et.imageset/et@2x.png | Bin 687 -> 0 bytes .../Flags.xcassets/et.imageset/et@3x.png | Bin 1051 -> 0 bytes .../Flags.xcassets/eu.imageset/Contents.json | 22 - .../Flags.xcassets/eu.imageset/eu@2x.png | Bin 661 -> 0 bytes .../Flags.xcassets/eu.imageset/eu@3x.png | Bin 956 -> 0 bytes .../Flags.xcassets/fi.imageset/Contents.json | 22 - .../Flags.xcassets/fi.imageset/fi@2x.png | Bin 266 -> 0 bytes .../Flags.xcassets/fi.imageset/fi@3x.png | Bin 318 -> 0 bytes .../Flags.xcassets/fj.imageset/Contents.json | 22 - .../Flags.xcassets/fj.imageset/fj@2x.png | Bin 1181 -> 0 bytes .../Flags.xcassets/fj.imageset/fj@3x.png | Bin 1972 -> 0 bytes .../Flags.xcassets/fk.imageset/Contents.json | 22 - .../Flags.xcassets/fk.imageset/fk@2x.png | Bin 1608 -> 0 bytes .../Flags.xcassets/fk.imageset/fk@3x.png | Bin 2921 -> 0 bytes .../Flags.xcassets/fm.imageset/Contents.json | 22 - .../Flags.xcassets/fm.imageset/fm@2x.png | Bin 556 -> 0 bytes .../Flags.xcassets/fm.imageset/fm@3x.png | Bin 862 -> 0 bytes .../Flags.xcassets/fo.imageset/Contents.json | 22 - .../Flags.xcassets/fo.imageset/fo@2x.png | Bin 406 -> 0 bytes .../Flags.xcassets/fo.imageset/fo@3x.png | Bin 469 -> 0 bytes .../Flags.xcassets/fr.imageset/Contents.json | 22 - .../Flags.xcassets/fr.imageset/fr@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/fr.imageset/fr@3x.png | Bin 243 -> 0 bytes .../Flags.xcassets/ga.imageset/Contents.json | 22 - .../Flags.xcassets/ga.imageset/ga@2x.png | Bin 206 -> 0 bytes .../Flags.xcassets/ga.imageset/ga@3x.png | Bin 210 -> 0 bytes .../gb-eng.imageset/Contents.json | 22 - .../gb-eng.imageset/gb-eng@2x.png | Bin 223 -> 0 bytes .../gb-eng.imageset/gb-eng@3x.png | Bin 226 -> 0 bytes .../gb-nir.imageset/Contents.json | 22 - .../gb-nir.imageset/gb-nir@2x.png | Bin 681 -> 0 bytes .../gb-nir.imageset/gb-nir@3x.png | Bin 1095 -> 0 bytes .../gb-sct.imageset/Contents.json | 22 - .../gb-sct.imageset/gb-sct@2x.png | Bin 892 -> 0 bytes .../gb-sct.imageset/gb-sct@3x.png | Bin 813 -> 0 bytes .../gb-wls.imageset/Contents.json | 22 - .../gb-wls.imageset/gb-wls@2x.png | Bin 1502 -> 0 bytes .../gb-wls.imageset/gb-wls@3x.png | Bin 2773 -> 0 bytes .../Flags.xcassets/gb.imageset/Contents.json | 22 - .../Flags.xcassets/gb.imageset/gb@2x.png | Bin 1250 -> 0 bytes .../Flags.xcassets/gb.imageset/gb@3x.png | Bin 1396 -> 0 bytes .../Flags.xcassets/gd.imageset/Contents.json | 22 - .../Flags.xcassets/gd.imageset/gd@2x.png | Bin 1114 -> 0 bytes .../Flags.xcassets/gd.imageset/gd@3x.png | Bin 1735 -> 0 bytes .../Flags.xcassets/ge.imageset/Contents.json | 22 - .../Flags.xcassets/ge.imageset/ge@2x.png | Bin 330 -> 0 bytes .../Flags.xcassets/ge.imageset/ge@3x.png | Bin 704 -> 0 bytes .../Flags.xcassets/gf.imageset/Contents.json | 22 - .../Flags.xcassets/gf.imageset/gf@2x.png | Bin 475 -> 0 bytes .../Flags.xcassets/gf.imageset/gf@3x.png | Bin 657 -> 0 bytes .../Flags.xcassets/gg.imageset/Contents.json | 22 - .../Flags.xcassets/gg.imageset/gg@2x.png | Bin 540 -> 0 bytes .../Flags.xcassets/gg.imageset/gg@3x.png | Bin 555 -> 0 bytes .../Flags.xcassets/gh.imageset/Contents.json | 22 - .../Flags.xcassets/gh.imageset/gh@2x.png | Bin 416 -> 0 bytes .../Flags.xcassets/gh.imageset/gh@3x.png | Bin 538 -> 0 bytes .../Flags.xcassets/gi.imageset/Contents.json | 22 - .../Flags.xcassets/gi.imageset/gi@2x.png | Bin 861 -> 0 bytes .../Flags.xcassets/gi.imageset/gi@3x.png | Bin 1279 -> 0 bytes .../Flags.xcassets/gl.imageset/Contents.json | 22 - .../Flags.xcassets/gl.imageset/gl@2x.png | Bin 593 -> 0 bytes .../Flags.xcassets/gl.imageset/gl@3x.png | Bin 797 -> 0 bytes .../Flags.xcassets/gm.imageset/Contents.json | 22 - .../Flags.xcassets/gm.imageset/gm@2x.png | Bin 235 -> 0 bytes .../Flags.xcassets/gm.imageset/gm@3x.png | Bin 214 -> 0 bytes .../Flags.xcassets/gn.imageset/Contents.json | 22 - .../Flags.xcassets/gn.imageset/gn@2x.png | Bin 162 -> 0 bytes .../Flags.xcassets/gn.imageset/gn@3x.png | Bin 208 -> 0 bytes .../Flags.xcassets/gp.imageset/Contents.json | 22 - .../Flags.xcassets/gp.imageset/gp@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/gp.imageset/gp@3x.png | Bin 243 -> 0 bytes .../Flags.xcassets/gq.imageset/Contents.json | 22 - .../Flags.xcassets/gq.imageset/gq@2x.png | Bin 621 -> 0 bytes .../Flags.xcassets/gq.imageset/gq@3x.png | Bin 941 -> 0 bytes .../Flags.xcassets/gr.imageset/Contents.json | 22 - .../Flags.xcassets/gr.imageset/gr@2x.png | Bin 535 -> 0 bytes .../Flags.xcassets/gr.imageset/gr@3x.png | Bin 526 -> 0 bytes .../Flags.xcassets/gs.imageset/Contents.json | 22 - .../Flags.xcassets/gs.imageset/gs@2x.png | Bin 1815 -> 0 bytes .../Flags.xcassets/gs.imageset/gs@3x.png | Bin 3131 -> 0 bytes .../Flags.xcassets/gt.imageset/Contents.json | 22 - .../Flags.xcassets/gt.imageset/gt@2x.png | Bin 405 -> 0 bytes .../Flags.xcassets/gt.imageset/gt@3x.png | Bin 676 -> 0 bytes .../Flags.xcassets/gu.imageset/Contents.json | 22 - .../Flags.xcassets/gu.imageset/gu@2x.png | Bin 949 -> 0 bytes .../Flags.xcassets/gu.imageset/gu@3x.png | Bin 1673 -> 0 bytes .../Flags.xcassets/gw.imageset/Contents.json | 22 - .../Flags.xcassets/gw.imageset/gw@2x.png | Bin 370 -> 0 bytes .../Flags.xcassets/gw.imageset/gw@3x.png | Bin 528 -> 0 bytes .../Flags.xcassets/gy.imageset/Contents.json | 22 - .../Flags.xcassets/gy.imageset/gy@2x.png | Bin 1143 -> 0 bytes .../Flags.xcassets/gy.imageset/gy@3x.png | Bin 1597 -> 0 bytes .../Flags.xcassets/hk.imageset/Contents.json | 22 - .../Flags.xcassets/hk.imageset/hk@2x.png | Bin 689 -> 0 bytes .../Flags.xcassets/hk.imageset/hk@3x.png | Bin 1135 -> 0 bytes .../Flags.xcassets/hm.imageset/Contents.json | 22 - .../Flags.xcassets/hm.imageset/hm@2x.png | Bin 1293 -> 0 bytes .../Flags.xcassets/hm.imageset/hm@3x.png | Bin 2018 -> 0 bytes .../Flags.xcassets/hn.imageset/Contents.json | 22 - .../Flags.xcassets/hn.imageset/hn@2x.png | Bin 467 -> 0 bytes .../Flags.xcassets/hn.imageset/hn@3x.png | Bin 545 -> 0 bytes .../Flags.xcassets/hr.imageset/Contents.json | 22 - .../Flags.xcassets/hr.imageset/hr@2x.png | Bin 760 -> 0 bytes .../Flags.xcassets/hr.imageset/hr@3x.png | Bin 1098 -> 0 bytes .../Flags.xcassets/ht.imageset/Contents.json | 22 - .../Flags.xcassets/ht.imageset/ht@2x.png | Bin 436 -> 0 bytes .../Flags.xcassets/ht.imageset/ht@3x.png | Bin 628 -> 0 bytes .../Flags.xcassets/hu.imageset/Contents.json | 22 - .../Flags.xcassets/hu.imageset/hu@2x.png | Bin 207 -> 0 bytes .../Flags.xcassets/hu.imageset/hu@3x.png | Bin 213 -> 0 bytes .../Flags.xcassets/id.imageset/Contents.json | 22 - .../Flags.xcassets/id.imageset/id@2x.png | Bin 164 -> 0 bytes .../Flags.xcassets/id.imageset/id@3x.png | Bin 171 -> 0 bytes .../Flags.xcassets/ie.imageset/Contents.json | 22 - .../Flags.xcassets/ie.imageset/ie@2x.png | Bin 171 -> 0 bytes .../Flags.xcassets/ie.imageset/ie@3x.png | Bin 237 -> 0 bytes .../Flags.xcassets/il.imageset/Contents.json | 22 - .../Flags.xcassets/il.imageset/il@2x.png | Bin 618 -> 0 bytes .../Flags.xcassets/il.imageset/il@3x.png | Bin 836 -> 0 bytes .../Flags.xcassets/im.imageset/Contents.json | 22 - .../Flags.xcassets/im.imageset/im@2x.png | Bin 853 -> 0 bytes .../Flags.xcassets/im.imageset/im@3x.png | Bin 1481 -> 0 bytes .../Flags.xcassets/in.imageset/Contents.json | 22 - .../Flags.xcassets/in.imageset/in@2x.png | Bin 411 -> 0 bytes .../Flags.xcassets/in.imageset/in@3x.png | Bin 561 -> 0 bytes .../Flags.xcassets/io.imageset/Contents.json | 22 - .../Flags.xcassets/io.imageset/io@2x.png | Bin 2580 -> 0 bytes .../Flags.xcassets/io.imageset/io@3x.png | Bin 4184 -> 0 bytes .../Flags.xcassets/iq.imageset/Contents.json | 22 - .../Flags.xcassets/iq.imageset/iq@2x.png | Bin 440 -> 0 bytes .../Flags.xcassets/iq.imageset/iq@3x.png | Bin 620 -> 0 bytes .../Flags.xcassets/ir.imageset/Contents.json | 22 - .../Flags.xcassets/ir.imageset/ir@2x.png | Bin 837 -> 0 bytes .../Flags.xcassets/ir.imageset/ir@3x.png | Bin 1453 -> 0 bytes .../Flags.xcassets/is.imageset/Contents.json | 22 - .../Flags.xcassets/is.imageset/is@2x.png | Bin 403 -> 0 bytes .../Flags.xcassets/is.imageset/is@3x.png | Bin 445 -> 0 bytes .../Flags.xcassets/it.imageset/Contents.json | 22 - .../Flags.xcassets/it.imageset/it@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/it.imageset/it@3x.png | Bin 242 -> 0 bytes .../Flags.xcassets/je.imageset/Contents.json | 22 - .../Flags.xcassets/je.imageset/je@2x.png | Bin 1257 -> 0 bytes .../Flags.xcassets/je.imageset/je@3x.png | Bin 1796 -> 0 bytes .../Flags.xcassets/jm.imageset/Contents.json | 22 - .../Flags.xcassets/jm.imageset/jm@2x.png | Bin 835 -> 0 bytes .../Flags.xcassets/jm.imageset/jm@3x.png | Bin 730 -> 0 bytes .../Flags.xcassets/jo.imageset/Contents.json | 22 - .../Flags.xcassets/jo.imageset/jo@2x.png | Bin 452 -> 0 bytes .../Flags.xcassets/jo.imageset/jo@3x.png | Bin 455 -> 0 bytes .../Flags.xcassets/jp.imageset/Contents.json | 22 - .../Flags.xcassets/jp.imageset/jp@2x.png | Bin 478 -> 0 bytes .../Flags.xcassets/jp.imageset/jp@3x.png | Bin 664 -> 0 bytes .../Flags.xcassets/ke.imageset/Contents.json | 22 - .../Flags.xcassets/ke.imageset/ke@2x.png | Bin 667 -> 0 bytes .../Flags.xcassets/ke.imageset/ke@3x.png | Bin 1003 -> 0 bytes .../Flags.xcassets/kg.imageset/Contents.json | 22 - .../Flags.xcassets/kg.imageset/kg@2x.png | Bin 829 -> 0 bytes .../Flags.xcassets/kg.imageset/kg@3x.png | Bin 1529 -> 0 bytes .../Flags.xcassets/kh.imageset/Contents.json | 22 - .../Flags.xcassets/kh.imageset/kh@2x.png | Bin 689 -> 0 bytes .../Flags.xcassets/kh.imageset/kh@3x.png | Bin 990 -> 0 bytes .../Flags.xcassets/ki.imageset/Contents.json | 22 - .../Flags.xcassets/ki.imageset/ki@2x.png | Bin 1613 -> 0 bytes .../Flags.xcassets/ki.imageset/ki@3x.png | Bin 2555 -> 0 bytes .../Flags.xcassets/km.imageset/Contents.json | 22 - .../Flags.xcassets/km.imageset/km@2x.png | Bin 892 -> 0 bytes .../Flags.xcassets/km.imageset/km@3x.png | Bin 1260 -> 0 bytes .../Flags.xcassets/kn.imageset/Contents.json | 22 - .../Flags.xcassets/kn.imageset/kn@2x.png | Bin 965 -> 0 bytes .../Flags.xcassets/kn.imageset/kn@3x.png | Bin 1526 -> 0 bytes .../Flags.xcassets/kp.imageset/Contents.json | 22 - .../Flags.xcassets/kp.imageset/kp@2x.png | Bin 693 -> 0 bytes .../Flags.xcassets/kp.imageset/kp@3x.png | Bin 937 -> 0 bytes .../Flags.xcassets/kr.imageset/Contents.json | 22 - .../Flags.xcassets/kr.imageset/kr@2x.png | Bin 1325 -> 0 bytes .../Flags.xcassets/kr.imageset/kr@3x.png | Bin 2065 -> 0 bytes .../Flags.xcassets/kw.imageset/Contents.json | 22 - .../Flags.xcassets/kw.imageset/kw@2x.png | Bin 409 -> 0 bytes .../Flags.xcassets/kw.imageset/kw@3x.png | Bin 441 -> 0 bytes .../Flags.xcassets/ky.imageset/Contents.json | 22 - .../Flags.xcassets/ky.imageset/ky@2x.png | Bin 1533 -> 0 bytes .../Flags.xcassets/ky.imageset/ky@3x.png | Bin 2681 -> 0 bytes .../Flags.xcassets/kz.imageset/Contents.json | 22 - .../Flags.xcassets/kz.imageset/kz@2x.png | Bin 1384 -> 0 bytes .../Flags.xcassets/kz.imageset/kz@3x.png | Bin 2649 -> 0 bytes .../Flags.xcassets/la.imageset/Contents.json | 22 - .../Flags.xcassets/la.imageset/la@2x.png | Bin 448 -> 0 bytes .../Flags.xcassets/la.imageset/la@3x.png | Bin 553 -> 0 bytes .../Flags.xcassets/lb.imageset/Contents.json | 22 - .../Flags.xcassets/lb.imageset/lb@2x.png | Bin 703 -> 0 bytes .../Flags.xcassets/lb.imageset/lb@3x.png | Bin 1117 -> 0 bytes .../Flags.xcassets/lc.imageset/Contents.json | 22 - .../Flags.xcassets/lc.imageset/lc@2x.png | Bin 659 -> 0 bytes .../Flags.xcassets/lc.imageset/lc@3x.png | Bin 1067 -> 0 bytes .../Flags.xcassets/li.imageset/Contents.json | 22 - .../Flags.xcassets/li.imageset/li@2x.png | Bin 425 -> 0 bytes .../Flags.xcassets/li.imageset/li@3x.png | Bin 680 -> 0 bytes .../Flags.xcassets/lk.imageset/Contents.json | 22 - .../Flags.xcassets/lk.imageset/lk@2x.png | Bin 861 -> 0 bytes .../Flags.xcassets/lk.imageset/lk@3x.png | Bin 1382 -> 0 bytes .../Flags.xcassets/lr.imageset/Contents.json | 22 - .../Flags.xcassets/lr.imageset/lr@2x.png | Bin 616 -> 0 bytes .../Flags.xcassets/lr.imageset/lr@3x.png | Bin 644 -> 0 bytes .../Flags.xcassets/ls.imageset/Contents.json | 22 - .../Flags.xcassets/ls.imageset/ls@2x.png | Bin 435 -> 0 bytes .../Flags.xcassets/ls.imageset/ls@3x.png | Bin 605 -> 0 bytes .../Flags.xcassets/lt.imageset/Contents.json | 22 - .../Flags.xcassets/lt.imageset/lt@2x.png | Bin 223 -> 0 bytes .../Flags.xcassets/lt.imageset/lt@3x.png | Bin 212 -> 0 bytes .../Flags.xcassets/lu.imageset/Contents.json | 22 - .../Flags.xcassets/lu.imageset/lu@2x.png | Bin 230 -> 0 bytes .../Flags.xcassets/lu.imageset/lu@3x.png | Bin 203 -> 0 bytes .../Flags.xcassets/lv.imageset/Contents.json | 22 - .../Flags.xcassets/lv.imageset/lv@2x.png | Bin 166 -> 0 bytes .../Flags.xcassets/lv.imageset/lv@3x.png | Bin 209 -> 0 bytes .../Flags.xcassets/ly.imageset/Contents.json | 22 - .../Flags.xcassets/ly.imageset/ly@2x.png | Bin 399 -> 0 bytes .../Flags.xcassets/ly.imageset/ly@3x.png | Bin 468 -> 0 bytes .../Flags.xcassets/ma.imageset/Contents.json | 22 - .../Flags.xcassets/ma.imageset/ma@2x.png | Bin 326 -> 0 bytes .../Flags.xcassets/ma.imageset/ma@3x.png | Bin 453 -> 0 bytes .../Flags.xcassets/mc.imageset/Contents.json | 22 - .../Flags.xcassets/mc.imageset/mc@2x.png | Bin 166 -> 0 bytes .../Flags.xcassets/mc.imageset/mc@3x.png | Bin 176 -> 0 bytes .../Flags.xcassets/md.imageset/Contents.json | 22 - .../Flags.xcassets/md.imageset/md@2x.png | Bin 954 -> 0 bytes .../Flags.xcassets/md.imageset/md@3x.png | Bin 1640 -> 0 bytes .../Flags.xcassets/me.imageset/Contents.json | 22 - .../Flags.xcassets/me.imageset/me@2x.png | Bin 832 -> 0 bytes .../Flags.xcassets/me.imageset/me@3x.png | Bin 1480 -> 0 bytes .../Flags.xcassets/mf.imageset/Contents.json | 22 - .../Flags.xcassets/mf.imageset/mf@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/mf.imageset/mf@3x.png | Bin 243 -> 0 bytes .../Flags.xcassets/mg.imageset/Contents.json | 22 - .../Flags.xcassets/mg.imageset/mg@2x.png | Bin 191 -> 0 bytes .../Flags.xcassets/mg.imageset/mg@3x.png | Bin 224 -> 0 bytes .../Flags.xcassets/mh.imageset/Contents.json | 22 - .../Flags.xcassets/mh.imageset/mh@2x.png | Bin 1394 -> 0 bytes .../Flags.xcassets/mh.imageset/mh@3x.png | Bin 2308 -> 0 bytes .../Flags.xcassets/mk.imageset/Contents.json | 22 - .../Flags.xcassets/mk.imageset/mk@2x.png | Bin 1099 -> 0 bytes .../Flags.xcassets/mk.imageset/mk@3x.png | Bin 1457 -> 0 bytes .../Flags.xcassets/ml.imageset/Contents.json | 22 - .../Flags.xcassets/ml.imageset/ml@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/ml.imageset/ml@3x.png | Bin 217 -> 0 bytes .../Flags.xcassets/mm.imageset/Contents.json | 22 - .../Flags.xcassets/mm.imageset/mm@2x.png | Bin 679 -> 0 bytes .../Flags.xcassets/mm.imageset/mm@3x.png | Bin 917 -> 0 bytes .../Flags.xcassets/mn.imageset/Contents.json | 22 - .../Flags.xcassets/mn.imageset/mn@2x.png | Bin 458 -> 0 bytes .../Flags.xcassets/mn.imageset/mn@3x.png | Bin 813 -> 0 bytes .../Flags.xcassets/mo.imageset/Contents.json | 22 - .../Flags.xcassets/mo.imageset/mo@2x.png | Bin 777 -> 0 bytes .../Flags.xcassets/mo.imageset/mo@3x.png | Bin 1203 -> 0 bytes .../Flags.xcassets/mp.imageset/Contents.json | 22 - .../Flags.xcassets/mp.imageset/mp@2x.png | Bin 1392 -> 0 bytes .../Flags.xcassets/mp.imageset/mp@3x.png | Bin 2462 -> 0 bytes .../Flags.xcassets/mq.imageset/Contents.json | 22 - .../Flags.xcassets/mq.imageset/mq@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/mq.imageset/mq@3x.png | Bin 243 -> 0 bytes .../Flags.xcassets/mr.imageset/Contents.json | 22 - .../Flags.xcassets/mr.imageset/mr@2x.png | Bin 674 -> 0 bytes .../Flags.xcassets/mr.imageset/mr@3x.png | Bin 947 -> 0 bytes .../Flags.xcassets/ms.imageset/Contents.json | 22 - .../Flags.xcassets/ms.imageset/ms@2x.png | Bin 1383 -> 0 bytes .../Flags.xcassets/ms.imageset/ms@3x.png | Bin 2193 -> 0 bytes .../Flags.xcassets/mt.imageset/Contents.json | 22 - .../Flags.xcassets/mt.imageset/mt@2x.png | Bin 449 -> 0 bytes .../Flags.xcassets/mt.imageset/mt@3x.png | Bin 632 -> 0 bytes .../Flags.xcassets/mu.imageset/Contents.json | 22 - .../Flags.xcassets/mu.imageset/mu@2x.png | Bin 235 -> 0 bytes .../Flags.xcassets/mu.imageset/mu@3x.png | Bin 214 -> 0 bytes .../Flags.xcassets/mv.imageset/Contents.json | 22 - .../Flags.xcassets/mv.imageset/mv@2x.png | Bin 388 -> 0 bytes .../Flags.xcassets/mv.imageset/mv@3x.png | Bin 551 -> 0 bytes .../Flags.xcassets/mw.imageset/Contents.json | 22 - .../Flags.xcassets/mw.imageset/mw@2x.png | Bin 574 -> 0 bytes .../Flags.xcassets/mw.imageset/mw@3x.png | Bin 848 -> 0 bytes .../Flags.xcassets/mx.imageset/Contents.json | 22 - .../Flags.xcassets/mx.imageset/mx@2x.png | Bin 454 -> 0 bytes .../Flags.xcassets/mx.imageset/mx@3x.png | Bin 742 -> 0 bytes .../Flags.xcassets/my.imageset/Contents.json | 22 - .../Flags.xcassets/my.imageset/my@2x.png | Bin 949 -> 0 bytes .../Flags.xcassets/my.imageset/my@3x.png | Bin 1111 -> 0 bytes .../Flags.xcassets/mz.imageset/Contents.json | 22 - .../Flags.xcassets/mz.imageset/mz@2x.png | Bin 656 -> 0 bytes .../Flags.xcassets/mz.imageset/mz@3x.png | Bin 891 -> 0 bytes .../Flags.xcassets/na.imageset/Contents.json | 22 - .../Flags.xcassets/na.imageset/na@2x.png | Bin 1104 -> 0 bytes .../Flags.xcassets/na.imageset/na@3x.png | Bin 1781 -> 0 bytes .../Flags.xcassets/nc.imageset/Contents.json | 22 - .../Flags.xcassets/nc.imageset/nc@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/nc.imageset/nc@3x.png | Bin 243 -> 0 bytes .../Flags.xcassets/ne.imageset/Contents.json | 22 - .../Flags.xcassets/ne.imageset/ne@2x.png | Bin 378 -> 0 bytes .../Flags.xcassets/ne.imageset/ne@3x.png | Bin 449 -> 0 bytes .../Flags.xcassets/nf.imageset/Contents.json | 22 - .../Flags.xcassets/nf.imageset/nf@2x.png | Bin 638 -> 0 bytes .../Flags.xcassets/nf.imageset/nf@3x.png | Bin 1109 -> 0 bytes .../Flags.xcassets/ng.imageset/Contents.json | 22 - .../Flags.xcassets/ng.imageset/ng@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/ng.imageset/ng@3x.png | Bin 239 -> 0 bytes .../Flags.xcassets/ni.imageset/Contents.json | 22 - .../Flags.xcassets/ni.imageset/ni@2x.png | Bin 432 -> 0 bytes .../Flags.xcassets/ni.imageset/ni@3x.png | Bin 611 -> 0 bytes .../Flags.xcassets/nl.imageset/Contents.json | 22 - .../Flags.xcassets/nl.imageset/nl@2x.png | Bin 216 -> 0 bytes .../Flags.xcassets/nl.imageset/nl@3x.png | Bin 214 -> 0 bytes .../Flags.xcassets/no.imageset/Contents.json | 22 - .../Flags.xcassets/no.imageset/no@2x.png | Bin 377 -> 0 bytes .../Flags.xcassets/no.imageset/no@3x.png | Bin 469 -> 0 bytes .../Flags.xcassets/np.imageset/Contents.json | 22 - .../Flags.xcassets/np.imageset/np@2x.png | Bin 944 -> 0 bytes .../Flags.xcassets/np.imageset/np@3x.png | Bin 1566 -> 0 bytes .../Flags.xcassets/nr.imageset/Contents.json | 22 - .../Flags.xcassets/nr.imageset/nr@2x.png | Bin 472 -> 0 bytes .../Flags.xcassets/nr.imageset/nr@3x.png | Bin 715 -> 0 bytes .../Flags.xcassets/nu.imageset/Contents.json | 22 - .../Flags.xcassets/nu.imageset/nu@2x.png | Bin 1478 -> 0 bytes .../Flags.xcassets/nu.imageset/nu@3x.png | Bin 2358 -> 0 bytes .../Flags.xcassets/nz.imageset/Contents.json | 22 - .../Flags.xcassets/nz.imageset/nz@2x.png | Bin 1080 -> 0 bytes .../Flags.xcassets/nz.imageset/nz@3x.png | Bin 1709 -> 0 bytes .../Flags.xcassets/om.imageset/Contents.json | 22 - .../Flags.xcassets/om.imageset/om@2x.png | Bin 405 -> 0 bytes .../Flags.xcassets/om.imageset/om@3x.png | Bin 646 -> 0 bytes .../Flags.xcassets/pa.imageset/Contents.json | 22 - .../Flags.xcassets/pa.imageset/pa@2x.png | Bin 577 -> 0 bytes .../Flags.xcassets/pa.imageset/pa@3x.png | Bin 792 -> 0 bytes .../Flags.xcassets/pe.imageset/Contents.json | 22 - .../Flags.xcassets/pe.imageset/pe@2x.png | Bin 838 -> 0 bytes .../Flags.xcassets/pe.imageset/pe@3x.png | Bin 1507 -> 0 bytes .../Flags.xcassets/pf.imageset/Contents.json | 22 - .../Flags.xcassets/pf.imageset/pf@2x.png | Bin 670 -> 0 bytes .../Flags.xcassets/pf.imageset/pf@3x.png | Bin 1154 -> 0 bytes .../Flags.xcassets/pg.imageset/Contents.json | 22 - .../Flags.xcassets/pg.imageset/pg@2x.png | Bin 780 -> 0 bytes .../Flags.xcassets/pg.imageset/pg@3x.png | Bin 1112 -> 0 bytes .../Flags.xcassets/ph.imageset/Contents.json | 22 - .../Flags.xcassets/ph.imageset/ph@2x.png | Bin 929 -> 0 bytes .../Flags.xcassets/ph.imageset/ph@3x.png | Bin 1382 -> 0 bytes .../Flags.xcassets/pk.imageset/Contents.json | 22 - .../Flags.xcassets/pk.imageset/pk@2x.png | Bin 552 -> 0 bytes .../Flags.xcassets/pk.imageset/pk@3x.png | Bin 773 -> 0 bytes .../Flags.xcassets/pl.imageset/Contents.json | 22 - .../Flags.xcassets/pl.imageset/pl@2x.png | Bin 177 -> 0 bytes .../Flags.xcassets/pl.imageset/pl@3x.png | Bin 177 -> 0 bytes .../Flags.xcassets/pm.imageset/Contents.json | 22 - .../Flags.xcassets/pm.imageset/pm@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/pm.imageset/pm@3x.png | Bin 243 -> 0 bytes .../Flags.xcassets/pn.imageset/Contents.json | 22 - .../Flags.xcassets/pn.imageset/pn@2x.png | Bin 1616 -> 0 bytes .../Flags.xcassets/pn.imageset/pn@3x.png | Bin 2673 -> 0 bytes .../Flags.xcassets/pr.imageset/Contents.json | 22 - .../Flags.xcassets/pr.imageset/pr@2x.png | Bin 770 -> 0 bytes .../Flags.xcassets/pr.imageset/pr@3x.png | Bin 1079 -> 0 bytes .../Flags.xcassets/ps.imageset/Contents.json | 22 - .../Flags.xcassets/ps.imageset/ps@2x.png | Bin 386 -> 0 bytes .../Flags.xcassets/ps.imageset/ps@3x.png | Bin 356 -> 0 bytes .../Flags.xcassets/pt.imageset/Contents.json | 22 - .../Flags.xcassets/pt.imageset/pt@2x.png | Bin 641 -> 0 bytes .../Flags.xcassets/pt.imageset/pt@3x.png | Bin 1046 -> 0 bytes .../Flags.xcassets/pw.imageset/Contents.json | 22 - .../Flags.xcassets/pw.imageset/pw@2x.png | Bin 515 -> 0 bytes .../Flags.xcassets/pw.imageset/pw@3x.png | Bin 699 -> 0 bytes .../Flags.xcassets/py.imageset/Contents.json | 22 - .../Flags.xcassets/py.imageset/py@2x.png | Bin 396 -> 0 bytes .../Flags.xcassets/py.imageset/py@3x.png | Bin 542 -> 0 bytes .../Flags.xcassets/qa.imageset/Contents.json | 22 - .../Flags.xcassets/qa.imageset/qa@2x.png | Bin 581 -> 0 bytes .../Flags.xcassets/qa.imageset/qa@3x.png | Bin 602 -> 0 bytes .../Flags.xcassets/re.imageset/Contents.json | 22 - .../Flags.xcassets/re.imageset/re@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/re.imageset/re@3x.png | Bin 243 -> 0 bytes .../Flags.xcassets/ro.imageset/Contents.json | 22 - .../Flags.xcassets/ro.imageset/ro@2x.png | Bin 173 -> 0 bytes .../Flags.xcassets/ro.imageset/ro@3x.png | Bin 221 -> 0 bytes .../Flags.xcassets/rs.imageset/Contents.json | 22 - .../Flags.xcassets/rs.imageset/rs@2x.png | Bin 1221 -> 0 bytes .../Flags.xcassets/rs.imageset/rs@3x.png | Bin 2058 -> 0 bytes .../Flags.xcassets/ru.imageset/Contents.json | 22 - .../Flags.xcassets/ru.imageset/ru@2x.png | Bin 205 -> 0 bytes .../Flags.xcassets/ru.imageset/ru@3x.png | Bin 204 -> 0 bytes .../Flags.xcassets/rw.imageset/Contents.json | 22 - .../Flags.xcassets/rw.imageset/rw@2x.png | Bin 439 -> 0 bytes .../Flags.xcassets/rw.imageset/rw@3x.png | Bin 644 -> 0 bytes .../Flags.xcassets/sa.imageset/Contents.json | 22 - .../Flags.xcassets/sa.imageset/sa@2x.png | Bin 792 -> 0 bytes .../Flags.xcassets/sa.imageset/sa@3x.png | Bin 1425 -> 0 bytes .../Flags.xcassets/sb.imageset/Contents.json | 22 - .../Flags.xcassets/sb.imageset/sb@2x.png | Bin 859 -> 0 bytes .../Flags.xcassets/sb.imageset/sb@3x.png | Bin 1109 -> 0 bytes .../Flags.xcassets/sc.imageset/Contents.json | 22 - .../Flags.xcassets/sc.imageset/sc@2x.png | Bin 880 -> 0 bytes .../Flags.xcassets/sc.imageset/sc@3x.png | Bin 1300 -> 0 bytes .../Flags.xcassets/sd.imageset/Contents.json | 22 - .../Flags.xcassets/sd.imageset/sd@2x.png | Bin 443 -> 0 bytes .../Flags.xcassets/sd.imageset/sd@3x.png | Bin 511 -> 0 bytes .../Flags.xcassets/se.imageset/Contents.json | 22 - .../Flags.xcassets/se.imageset/se@2x.png | Bin 220 -> 0 bytes .../Flags.xcassets/se.imageset/se@3x.png | Bin 353 -> 0 bytes .../Flags.xcassets/sg.imageset/Contents.json | 22 - .../Flags.xcassets/sg.imageset/sg@2x.png | Bin 490 -> 0 bytes .../Flags.xcassets/sg.imageset/sg@3x.png | Bin 717 -> 0 bytes .../Flags.xcassets/sh.imageset/Contents.json | 22 - .../Flags.xcassets/sh.imageset/sh@2x.png | Bin 1433 -> 0 bytes .../Flags.xcassets/sh.imageset/sh@3x.png | Bin 2388 -> 0 bytes .../Flags.xcassets/si.imageset/Contents.json | 22 - .../Flags.xcassets/si.imageset/si@2x.png | Bin 521 -> 0 bytes .../Flags.xcassets/si.imageset/si@3x.png | Bin 707 -> 0 bytes .../Flags.xcassets/sj.imageset/Contents.json | 22 - .../Flags.xcassets/sj.imageset/sj@2x.png | Bin 378 -> 0 bytes .../Flags.xcassets/sj.imageset/sj@3x.png | Bin 465 -> 0 bytes .../Flags.xcassets/sk.imageset/Contents.json | 22 - .../Flags.xcassets/sk.imageset/sk@2x.png | Bin 682 -> 0 bytes .../Flags.xcassets/sk.imageset/sk@3x.png | Bin 998 -> 0 bytes .../Flags.xcassets/sl.imageset/Contents.json | 22 - .../Flags.xcassets/sl.imageset/sl@2x.png | Bin 200 -> 0 bytes .../Flags.xcassets/sl.imageset/sl@3x.png | Bin 191 -> 0 bytes .../Flags.xcassets/sm.imageset/Contents.json | 22 - .../Flags.xcassets/sm.imageset/sm@2x.png | Bin 874 -> 0 bytes .../Flags.xcassets/sm.imageset/sm@3x.png | Bin 1461 -> 0 bytes .../Flags.xcassets/sn.imageset/Contents.json | 22 - .../Flags.xcassets/sn.imageset/sn@2x.png | Bin 419 -> 0 bytes .../Flags.xcassets/sn.imageset/sn@3x.png | Bin 616 -> 0 bytes .../Flags.xcassets/so.imageset/Contents.json | 22 - .../Flags.xcassets/so.imageset/so@2x.png | Bin 407 -> 0 bytes .../Flags.xcassets/so.imageset/so@3x.png | Bin 593 -> 0 bytes .../Flags.xcassets/sr.imageset/Contents.json | 22 - .../Flags.xcassets/sr.imageset/sr@2x.png | Bin 437 -> 0 bytes .../Flags.xcassets/sr.imageset/sr@3x.png | Bin 567 -> 0 bytes .../Flags.xcassets/ss.imageset/Contents.json | 22 - .../Flags.xcassets/ss.imageset/ss@2x.png | Bin 812 -> 0 bytes .../Flags.xcassets/ss.imageset/ss@3x.png | Bin 1008 -> 0 bytes .../Flags.xcassets/st.imageset/Contents.json | 22 - .../Flags.xcassets/st.imageset/st@2x.png | Bin 653 -> 0 bytes .../Flags.xcassets/st.imageset/st@3x.png | Bin 780 -> 0 bytes .../Flags.xcassets/sv.imageset/Contents.json | 22 - .../Flags.xcassets/sv.imageset/sv@2x.png | Bin 443 -> 0 bytes .../Flags.xcassets/sv.imageset/sv@3x.png | Bin 652 -> 0 bytes .../Flags.xcassets/sx.imageset/Contents.json | 22 - .../Flags.xcassets/sx.imageset/sx@2x.png | Bin 774 -> 0 bytes .../Flags.xcassets/sx.imageset/sx@3x.png | Bin 1176 -> 0 bytes .../Flags.xcassets/sy.imageset/Contents.json | 22 - .../Flags.xcassets/sy.imageset/sy@2x.png | Bin 379 -> 0 bytes .../Flags.xcassets/sy.imageset/sy@3x.png | Bin 514 -> 0 bytes .../Flags.xcassets/sz.imageset/Contents.json | 22 - .../Flags.xcassets/sz.imageset/sz@2x.png | Bin 1006 -> 0 bytes .../Flags.xcassets/sz.imageset/sz@3x.png | Bin 1554 -> 0 bytes .../Flags.xcassets/tc.imageset/Contents.json | 22 - .../Flags.xcassets/tc.imageset/tc@2x.png | Bin 1274 -> 0 bytes .../Flags.xcassets/tc.imageset/tc@3x.png | Bin 2107 -> 0 bytes .../Flags.xcassets/td.imageset/Contents.json | 22 - .../Flags.xcassets/td.imageset/td@2x.png | Bin 160 -> 0 bytes .../Flags.xcassets/td.imageset/td@3x.png | Bin 213 -> 0 bytes .../Flags.xcassets/tf.imageset/Contents.json | 22 - .../Flags.xcassets/tf.imageset/tf@2x.png | Bin 711 -> 0 bytes .../Flags.xcassets/tf.imageset/tf@3x.png | Bin 1065 -> 0 bytes .../Flags.xcassets/tg.imageset/Contents.json | 22 - .../Flags.xcassets/tg.imageset/tg@2x.png | Bin 448 -> 0 bytes .../Flags.xcassets/tg.imageset/tg@3x.png | Bin 586 -> 0 bytes .../Flags.xcassets/th.imageset/Contents.json | 22 - .../Flags.xcassets/th.imageset/th@2x.png | Bin 257 -> 0 bytes .../Flags.xcassets/th.imageset/th@3x.png | Bin 237 -> 0 bytes .../Flags.xcassets/tj.imageset/Contents.json | 22 - .../Flags.xcassets/tj.imageset/tj@2x.png | Bin 509 -> 0 bytes .../Flags.xcassets/tj.imageset/tj@3x.png | Bin 714 -> 0 bytes .../Flags.xcassets/tk.imageset/Contents.json | 22 - .../Flags.xcassets/tk.imageset/tk@2x.png | Bin 734 -> 0 bytes .../Flags.xcassets/tk.imageset/tk@3x.png | Bin 1039 -> 0 bytes .../Flags.xcassets/tl.imageset/Contents.json | 22 - .../Flags.xcassets/tl.imageset/tl@2x.png | Bin 831 -> 0 bytes .../Flags.xcassets/tl.imageset/tl@3x.png | Bin 1062 -> 0 bytes .../Flags.xcassets/tm.imageset/Contents.json | 22 - .../Flags.xcassets/tm.imageset/tm@2x.png | Bin 974 -> 0 bytes .../Flags.xcassets/tm.imageset/tm@3x.png | Bin 1823 -> 0 bytes .../Flags.xcassets/tn.imageset/Contents.json | 22 - .../Flags.xcassets/tn.imageset/tn@2x.png | Bin 691 -> 0 bytes .../Flags.xcassets/tn.imageset/tn@3x.png | Bin 1008 -> 0 bytes .../Flags.xcassets/to.imageset/Contents.json | 22 - .../Flags.xcassets/to.imageset/to@2x.png | Bin 369 -> 0 bytes .../Flags.xcassets/to.imageset/to@3x.png | Bin 425 -> 0 bytes .../Flags.xcassets/tr.imageset/Contents.json | 22 - .../Flags.xcassets/tr.imageset/tr@2x.png | Bin 693 -> 0 bytes .../Flags.xcassets/tr.imageset/tr@3x.png | Bin 973 -> 0 bytes .../Flags.xcassets/tt.imageset/Contents.json | 22 - .../Flags.xcassets/tt.imageset/tt@2x.png | Bin 963 -> 0 bytes .../Flags.xcassets/tt.imageset/tt@3x.png | Bin 1707 -> 0 bytes .../Flags.xcassets/tv.imageset/Contents.json | 22 - .../Flags.xcassets/tv.imageset/tv@2x.png | Bin 1345 -> 0 bytes .../Flags.xcassets/tv.imageset/tv@3x.png | Bin 2141 -> 0 bytes .../Flags.xcassets/tw.imageset/Contents.json | 22 - .../Flags.xcassets/tw.imageset/tw@2x.png | Bin 528 -> 0 bytes .../Flags.xcassets/tw.imageset/tw@3x.png | Bin 826 -> 0 bytes .../Flags.xcassets/tz.imageset/Contents.json | 22 - .../Flags.xcassets/tz.imageset/tz@2x.png | Bin 695 -> 0 bytes .../Flags.xcassets/tz.imageset/tz@3x.png | Bin 885 -> 0 bytes .../Flags.xcassets/ua.imageset/Contents.json | 22 - .../Flags.xcassets/ua.imageset/ua@2x.png | Bin 174 -> 0 bytes .../Flags.xcassets/ua.imageset/ua@3x.png | Bin 184 -> 0 bytes .../Flags.xcassets/ug.imageset/Contents.json | 22 - .../Flags.xcassets/ug.imageset/ug@2x.png | Bin 533 -> 0 bytes .../Flags.xcassets/ug.imageset/ug@3x.png | Bin 691 -> 0 bytes .../Flags.xcassets/um.imageset/Contents.json | 22 - .../Flags.xcassets/um.imageset/um@2x.png | Bin 1229 -> 0 bytes .../Flags.xcassets/um.imageset/um@3x.png | Bin 1642 -> 0 bytes .../Flags.xcassets/un.imageset/Contents.json | 22 - .../Flags.xcassets/un.imageset/un@2x.png | Bin 751 -> 0 bytes .../Flags.xcassets/un.imageset/un@3x.png | Bin 1396 -> 0 bytes .../Flags.xcassets/us.imageset/Contents.json | 22 - .../Flags.xcassets/us.imageset/us@2x.png | Bin 1167 -> 0 bytes .../Flags.xcassets/us.imageset/us@3x.png | Bin 1643 -> 0 bytes .../Flags.xcassets/uy.imageset/Contents.json | 22 - .../Flags.xcassets/uy.imageset/uy@2x.png | Bin 730 -> 0 bytes .../Flags.xcassets/uy.imageset/uy@3x.png | Bin 1026 -> 0 bytes .../Flags.xcassets/uz.imageset/Contents.json | 22 - .../Flags.xcassets/uz.imageset/uz@2x.png | Bin 462 -> 0 bytes .../Flags.xcassets/uz.imageset/uz@3x.png | Bin 648 -> 0 bytes .../Flags.xcassets/va.imageset/Contents.json | 22 - .../Flags.xcassets/va.imageset/va@2x.png | Bin 675 -> 0 bytes .../Flags.xcassets/va.imageset/va@3x.png | Bin 1150 -> 0 bytes .../Flags.xcassets/vc.imageset/Contents.json | 22 - .../Flags.xcassets/vc.imageset/vc@2x.png | Bin 483 -> 0 bytes .../Flags.xcassets/vc.imageset/vc@3x.png | Bin 745 -> 0 bytes .../Flags.xcassets/ve.imageset/Contents.json | 22 - .../Flags.xcassets/ve.imageset/ve@2x.png | Bin 466 -> 0 bytes .../Flags.xcassets/ve.imageset/ve@3x.png | Bin 623 -> 0 bytes .../Flags.xcassets/vg.imageset/Contents.json | 22 - .../Flags.xcassets/vg.imageset/vg@2x.png | Bin 1435 -> 0 bytes .../Flags.xcassets/vg.imageset/vg@3x.png | Bin 2455 -> 0 bytes .../Flags.xcassets/vi.imageset/Contents.json | 22 - .../Flags.xcassets/vi.imageset/vi@2x.png | Bin 1444 -> 0 bytes .../Flags.xcassets/vi.imageset/vi@3x.png | Bin 2544 -> 0 bytes .../Flags.xcassets/vn.imageset/Contents.json | 22 - .../Flags.xcassets/vn.imageset/vn@2x.png | Bin 470 -> 0 bytes .../Flags.xcassets/vn.imageset/vn@3x.png | Bin 668 -> 0 bytes .../Flags.xcassets/vu.imageset/Contents.json | 22 - .../Flags.xcassets/vu.imageset/vu@2x.png | Bin 926 -> 0 bytes .../Flags.xcassets/vu.imageset/vu@3x.png | Bin 1268 -> 0 bytes .../Flags.xcassets/wf.imageset/Contents.json | 22 - .../Flags.xcassets/wf.imageset/wf@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/wf.imageset/wf@3x.png | Bin 243 -> 0 bytes .../Flags.xcassets/ws.imageset/Contents.json | 22 - .../Flags.xcassets/ws.imageset/ws@2x.png | Bin 468 -> 0 bytes .../Flags.xcassets/ws.imageset/ws@3x.png | Bin 682 -> 0 bytes .../Flags.xcassets/xk.imageset/Contents.json | 22 - .../Flags.xcassets/xk.imageset/xk@2x.png | Bin 749 -> 0 bytes .../Flags.xcassets/xk.imageset/xk@3x.png | Bin 1181 -> 0 bytes .../Flags.xcassets/ye.imageset/Contents.json | 22 - .../Flags.xcassets/ye.imageset/ye@2x.png | Bin 205 -> 0 bytes .../Flags.xcassets/ye.imageset/ye@3x.png | Bin 193 -> 0 bytes .../Flags.xcassets/yt.imageset/Contents.json | 22 - .../Flags.xcassets/yt.imageset/yt@2x.png | Bin 172 -> 0 bytes .../Flags.xcassets/yt.imageset/yt@3x.png | Bin 243 -> 0 bytes .../Flags.xcassets/za.imageset/Contents.json | 22 - .../Flags.xcassets/za.imageset/za@2x.png | Bin 1112 -> 0 bytes .../Flags.xcassets/za.imageset/za@3x.png | Bin 1641 -> 0 bytes .../Flags.xcassets/zm.imageset/Contents.json | 22 - .../Flags.xcassets/zm.imageset/zm@2x.png | Bin 402 -> 0 bytes .../Flags.xcassets/zm.imageset/zm@3x.png | Bin 600 -> 0 bytes .../Flags.xcassets/zw.imageset/Contents.json | 22 - .../Flags.xcassets/zw.imageset/zw@2x.png | Bin 934 -> 0 bytes .../Flags.xcassets/zw.imageset/zw@3x.png | Bin 1319 -> 0 bytes Passepartout/App/macOS/Global/Credits.html | 24 - .../App/macOS/Global/HostImporter.swift | 238 --- .../App/macOS/Global/IssueReporter.swift | 122 -- Passepartout/App/macOS/Global/Macros.swift | 178 -- .../App/macOS/Global/SwiftGen+Assets.swift | 356 ---- .../App/macOS/Global/SwiftGen+Scenes.swift | 119 -- .../App/macOS/Global/SwiftGen+Segues.swift | 47 - .../Global/TextInputViewController.swift | 92 - .../App/macOS/Global/Theme+Views.swift | 98 - Passepartout/App/macOS/Global/Theme.swift | 84 - .../App/macOS/Global/WindowManager.swift | 86 - Passepartout/App/macOS/Info.plist | 75 - .../App/macOS/Launcher/AppDelegate.swift | 63 - .../AppIcon.appiconset/Contents.json | 58 - .../macOS/Launcher/Base.lproj/Main.storyboard | 684 ------- Passepartout/App/macOS/Launcher/Info.plist | 40 - .../App/macOS/Launcher/Launcher.entitlements | 10 - Passepartout/App/macOS/Menu/MainMenu.xib | 679 ------ Passepartout/App/macOS/Menu/StatusMenu.swift | 702 ------- .../csv.imageset/Contents.json | 22 - .../csv.imageset/csv@2x.png | Bin 3435 -> 0 bytes .../csv.imageset/csv@3x.png | Bin 5407 -> 0 bytes .../hideme.imageset/Contents.json | 22 - .../hideme.imageset/hideme@2x.png | Bin 3460 -> 0 bytes .../hideme.imageset/hideme@3x.png | Bin 5776 -> 0 bytes .../mullvad.imageset/Contents.json | 22 - .../mullvad.imageset/mullvad@2x.png | Bin 3282 -> 0 bytes .../mullvad.imageset/mullvad@3x.png | Bin 5168 -> 0 bytes .../nordvpn.imageset/Contents.json | 54 - .../nordvpn.imageset/nordvpn-dark@2x.png | Bin 1621 -> 0 bytes .../nordvpn.imageset/nordvpn-dark@3x.png | Bin 2583 -> 0 bytes .../nordvpn.imageset/nordvpn@2x.png | Bin 2211 -> 0 bytes .../nordvpn.imageset/nordvpn@3x.png | Bin 3309 -> 0 bytes .../oeck.imageset/Contents.json | 54 - .../oeck.imageset/oeck-dark@2x.png | Bin 1088 -> 0 bytes .../oeck.imageset/oeck-dark@3x.png | Bin 1706 -> 0 bytes .../oeck.imageset/oeck@2x.png | Bin 1648 -> 0 bytes .../oeck.imageset/oeck@3x.png | Bin 2498 -> 0 bytes .../pia.imageset/Contents.json | 22 - .../pia.imageset/pia@2x.png | Bin 6709 -> 0 bytes .../pia.imageset/pia@3x.png | Bin 9766 -> 0 bytes .../placeholder.imageset/Contents.json | 22 - .../placeholder.imageset/placeholder@2x.png | Bin 2362 -> 0 bytes .../placeholder.imageset/placeholder@3x.png | Bin 3468 -> 0 bytes .../protonvpn.imageset/Contents.json | 22 - .../protonvpn.imageset/protonvpn@2x.png | Bin 3179 -> 0 bytes .../protonvpn.imageset/protonvpn@3x.png | Bin 3603 -> 0 bytes .../surfshark.imageset/Contents.json | 22 - .../surfshark.imageset/surfshark@2x.png | Bin 2422 -> 0 bytes .../surfshark.imageset/surfshark@3x.png | Bin 3591 -> 0 bytes .../torguard.imageset/Contents.json | 22 - .../torguard.imageset/torguard@2x.png | Bin 4448 -> 0 bytes .../torguard.imageset/torguard@3x.png | Bin 7471 -> 0 bytes .../tunnelbear.imageset/Contents.json | 22 - .../tunnelbear.imageset/tunnelbear@2x.png | Bin 2949 -> 0 bytes .../tunnelbear.imageset/tunnelbear@3x.png | Bin 4755 -> 0 bytes .../vyprvpn.imageset/Contents.json | 22 - .../vyprvpn.imageset/vyprvpn@2x.png | Bin 3955 -> 0 bytes .../vyprvpn.imageset/vyprvpn@3x.png | Bin 6758 -> 0 bytes .../windscribe.imageset/Contents.json | 22 - .../windscribe.imageset/windscribe@2x.png | Bin 7386 -> 0 bytes .../windscribe.imageset/windscribe@3x.png | Bin 12273 -> 0 bytes .../Scenes/OrganizerProfileTableView.swift | 179 -- .../Scenes/OrganizerProfileTableView.xib | 190 -- .../Scenes/OrganizerViewController.swift | 394 ---- .../Preferences/DebugLogViewController.swift | 274 --- .../PreferencesGeneralViewController.swift | 91 - .../Scenes/Purchase/PurchaseProductView.swift | 49 - .../Purchase/PurchaseViewController.swift | 268 --- .../Service/AccountViewController.swift | 148 -- .../ConfigurationViewController.swift | 314 --- .../Customization/DNSViewController.swift | 222 -- .../DefaultGatewayViewController.swift | 136 -- .../EndpointViewController.swift | 123 -- .../Customization/MTUViewController.swift | 138 -- .../ProfileCustomizationViewController.swift | 275 --- .../Customization/ProxyViewController.swift | 154 -- .../TrustedNetworksAddViewController.swift | 59 - .../TrustedNetworksViewController.swift | 260 --- .../Scenes/Service/HostServiceView.swift | 77 - .../macOS/Scenes/Service/HostServiceView.xib | 87 - .../Scenes/Service/ProviderServiceView.swift | 326 --- .../Scenes/Service/ProviderServiceView.xib | 177 -- .../Service/ServiceViewController.swift | 386 ---- .../App/macOS/Tables/TextTableView.swift | 159 -- .../App/macOS/Tables/TextTableView.xib | 115 -- Passepartout/App/macOS/swiftgen.yml | 21 - Passepartout/Shared/Constants.swift | 38 + Passepartout/Tunnel/Info.plist | 6 +- .../Tunnel/OpenVPN/Constants+Tunnel.swift | 36 + .../{ => OpenVPN}/PacketTunnelProvider.swift | 11 +- Passepartout/Tunnel/Tunnel-iOS.entitlements | 18 - ...macOS.entitlements => Tunnel.entitlements} | 2 +- .../WireGuard/PacketTunnelProvider.swift} | 12 +- ....xcscheme => OpenVPNAppExtension.xcscheme} | 14 +- .../PassepartoutCore-Package.xcscheme | 289 +++ .../xcschemes/PassepartoutCore.xcscheme | 40 +- .../PassepartoutProfilesTests.xcscheme | 52 + .../PassepartoutProvidersTests.xcscheme | 58 + .../PassepartoutServicesTests.xcscheme | 52 + ...scheme => PassepartoutUtilsTests.xcscheme} | 8 +- .../xcschemes/WireGuardAppExtension.xcscheme | 67 + PassepartoutCore/Package.swift | 73 +- .../Exports.swift | 0 .../PassepartoutConstants/AppConstants.swift | 331 --- .../GroupConstants.swift | 77 - PassepartoutCore/Sources/PassepartoutCore/API | 1 - .../PassepartoutCore/AppConstants+Core.swift | 70 - .../Sources/PassepartoutCore/Exports.swift | 6 +- .../DebugLog+Decorated.swift} | 35 +- .../Extensions/OnDemand+Rules.swift | 65 + .../OpenVPNSettings+VPNConfiguration.swift | 137 ++ .../WireGuardSettings+VPNConfiguration.swift | 107 + .../Sources/PassepartoutCore/Issue.swift | 57 - .../Managers/AppManager+Migrations.swift | 328 +++ .../Managers/AppManager.swift | 220 ++ .../Managers/PersistenceManager.swift | 45 + .../Managers/VPNManager+Actions.swift | 123 ++ .../Managers/VPNManager+Configuration.swift | 102 + .../Managers/VPNManager.swift | 257 +++ .../Managers/VPNManagerStrategy+Mock.swift | 107 + .../VPNManagerStrategy+TunnelKit.swift | 278 +++ .../VPNManagerStrategy.swift} | 34 +- .../Model/ConnectionProfile.swift | 92 - .../ConnectionService+Configurations.swift | 70 - .../Model/ConnectionService+Migration.swift | 83 - .../Model/ConnectionService.swift | 683 ------- .../PassepartoutCore/Model/DataUnit.swift | 93 - .../PassepartoutCore/Model/GracefulVPN.swift | 140 -- .../Model/LocalProduct+Infrastructure.swift | 27 - .../Model/ProfileNetworkSettings.swift | 251 --- .../Profiles/HostConnectionProfile.swift | 157 -- .../PlaceholderConnectionProfile.swift | 79 - .../Model/Profiles/ProfileKey.swift | 78 - .../Profiles/ProviderConnectionProfile.swift | 246 --- .../Model/TransientStore.swift | 240 --- .../Models/AppPreferences.swift | 46 + .../PassepartoutCore/Models/DebugLog.swift | 19 +- .../Models/VPNConfiguration.swift | 65 + .../Models/VPNManager+ObservableState.swift | 64 + .../PassepartoutCore/PassepartoutCore.swift | 30 + .../Resources/de.lproj/Core.strings | 359 ---- .../Resources/el.lproj/Core.strings | 359 ---- .../Resources/en.lproj/Core.strings | 359 ---- .../Resources/es.lproj/Core.strings | 359 ---- .../Resources/fr.lproj/Core.strings | 359 ---- .../Resources/it.lproj/Core.strings | 359 ---- .../Resources/nl.lproj/Core.strings | 359 ---- .../Resources/pl.lproj/Core.strings | 359 ---- .../Resources/pt.lproj/Core.strings | 359 ---- .../Resources/ru.lproj/Core.strings | 359 ---- .../Resources/sv.lproj/Core.strings | 359 ---- .../Resources/zh-Hans.lproj/Core.strings | 359 ---- .../Services/Infrastructure+External.swift | 59 - .../Services/Infrastructure.swift | 94 - .../Services/InfrastructureFactory.swift | 363 ---- .../Services/InfrastructurePreset.swift | 238 --- .../PassepartoutCore/Services/Pool.swift | 185 -- .../PassepartoutCore/Services/PoolGroup.swift | 79 - .../Services/WebServices.swift | 96 - .../PassepartoutCore/SwiftGen+Strings.swift | 1289 ------------ .../UI/TrustedNetworksUI.swift | 219 -- .../Sources/PassepartoutCore/Utils.swift | 238 --- .../DataModels/CDProfile+CoreDataClass.swift | 16 + .../CDProfile+CoreDataProperties.swift | 30 + .../DataModels/Network.swift | 136 ++ .../DataModels/Profile+Account.swift | 35 +- .../DataModels/Profile+Header.swift | 71 + .../DataModels/Profile+Host.swift | 34 + .../DataModels/Profile+NetworkSettings.swift | 54 + .../DataModels/Profile+OnDemand.swift | 62 + .../DataModels/Profile+OpenVPNSettings.swift} | 25 +- .../DataModels/Profile+Provider.swift | 55 + .../Profile+WireGuardSettings.swift | 38 + .../DataModels/Profile.swift | 140 ++ .../Extensions/Host+Extensions.swift | 106 + .../NetworkSettings+Extensions.swift | 65 + .../Extensions/OnDemand+Extensions.swift | 59 + .../Extensions/OpenVPNSettings+Network.swift | 95 + .../PassepartoutProfiles+Logging.swift | 38 + ...ssepartoutProfiles+StrippableContent.swift | 61 + .../PassepartoutProviders+TunnelKit.swift | 108 + .../Extensions/Profile+Extensions.swift | 106 + .../Extensions/Provider+Extensions.swift | 153 ++ .../ProviderManager+Extensions.swift | 41 + .../Extensions/VPNProtocolType+Network.swift} | 45 +- .../WireGuardSettings+Network.swift | 56 + .../Managers/ProfileManager+Keychain.swift | 118 ++ .../Managers/ProfileManager+Processing.swift | 49 + .../Managers/ProfileManager.swift | 406 ++++ .../ProfileManagerStrategy+CoreData.swift | 66 + .../Managers/ProfileManagerStrategy.swift | 46 + .../Models/ObservableProfile.swift} | 20 +- .../PassepartoutProfiles.swift | 45 + .../Pickers/Picker+Network.swift | 44 + .../Pickers/Picker+OpenVPN.swift} | 2 +- .../Model.xcdatamodel/contents | 13 + .../Repositories/ProfileMapper.swift | 92 + .../Repositories/ProfileRepository.swift | 131 ++ .../CDInfrastructure+CoreDataClass.swift | 16 + .../CDInfrastructure+CoreDataProperties.swift | 47 + ...InfrastructureCategory+CoreDataClass.swift | 16 + ...structureCategory+CoreDataProperties.swift | 81 + ...ructureDefaultSettings+CoreDataClass.swift | 16 + ...reDefaultSettings+CoreDataProperties.swift | 28 + ...InfrastructureLocation+CoreDataClass.swift | 16 + ...structureLocation+CoreDataProperties.swift | 45 + ...CDInfrastructurePreset+CoreDataClass.swift | 16 + ...rastructurePreset+CoreDataProperties.swift | 48 + ...CDInfrastructureServer+CoreDataClass.swift | 16 + ...rastructureServer+CoreDataProperties.swift | 36 + .../DataModels/CDProvider+CoreDataClass.swift | 16 + .../CDProvider+CoreDataProperties.swift | 46 + .../PassepartoutProviders+Identifiable.swift | 46 + .../PassepartoutProviders+Logging.swift | 32 + .../ProviderName+Credentials.swift} | 17 +- .../VPNProtocolType+Extensions.swift | 32 + .../Managers/ProviderManager.swift | 268 +++ .../Models/CredentialsPurpose.swift} | 10 +- .../Models/ProviderCategory.swift} | 21 +- .../Models/ProviderLocation.swift | 39 + .../Models/ProviderMetadata.swift | 29 + .../Models/ProviderName.swift | 29 + .../Models/ProviderServer.swift | 86 + .../Models/VPNProtocolType.swift | 37 + .../PassepartoutProviders.swift | 37 + .../Model.xcdatamodel/contents | 63 + .../Repositories/CategoryMapper.swift | 80 + .../Repositories/DefaultSettingsMapper.swift | 44 + .../Repositories/InfrastructureMapper.swift | 111 + .../InfrastructureRepository.swift | 143 ++ .../Repositories/LocationMapper.swift | 77 + .../Repositories/PresetMapper.swift | 99 + .../Repositories/ProviderMapper.swift | 72 + .../Repositories/ProviderRepository.swift | 137 ++ .../Repositories/ServerMapper.swift | 202 ++ .../Repositories/ServerRepository.swift | 190 ++ .../Sources/PassepartoutServices/API | 1 + .../DataModels/WSProviderCategory.swift} | 34 +- .../DataModels/WSProviderInfrastructure.swift | 62 + .../DataModels/WSProviderLocation.swift | 43 + .../DataModels/WSProviderName.swift | 28 + .../DataModels/WSProviderPreset.swift | 61 + .../DataModels/WSProviderServer.swift | 69 + .../DataModels/WSProvidersIndex.swift} | 56 +- .../DataModels/WSVPNProtocol.swift | 44 + .../DefaultWebServices.swift | 106 + .../WSProviderName+Extensions.swift} | 6 +- .../PassepartoutServices/WebServices.swift | 63 + .../Sources/PassepartoutUtils/Exports.swift | 3 + .../PassepartoutDataModels.swift | 29 + .../PassepartoutUtils/PassepartoutError.swift | 38 + .../Reusable/FetchedValueHolder.swift | 85 + .../Reusable/GenericWebEndpoint.swift | 30 + .../Reusable/GenericWebParser.swift | 59 + .../Reusable/GenericWebResponse.swift} | 31 +- .../Reusable/GenericWebServices.swift | 122 ++ .../Reusable/KeyedCache.swift | 79 + .../PassepartoutUtils/Reusable/Mapper.swift | 42 + .../Reusable/Persistence.swift | 101 + .../Reusable/RateLimited.swift | 57 + .../Reusable/Repository.swift} | 16 +- .../Reusable/SSIDReader.swift | 68 + .../Reusable/StrippableContent.swift | 32 + .../Reusable/ValueHolder.swift | 32 + .../PassepartoutUtils/Utils/Utils+Async.swift | 68 + .../Utils/Utils+Codable.swift | 38 + .../Utils/Utils+CoreData.swift | 37 + .../PassepartoutUtils/Utils/Utils+Dates.swift | 54 + .../Utils/Utils+FileManager.swift | 51 + .../Utils/Utils+Logging.swift | 47 + .../Utils/Utils+Network.swift | 132 ++ .../Utils/Utils+Strings.swift | 92 + .../PassepartoutUtils/Utils/Utils+URL.swift | 105 + .../PassepartoutUtils/Utils/Utils.swift | 29 + .../WireGuardAppExtension/Exports.swift | 1 + .../ConnectionServiceTests.swift | 64 - .../PassepartoutCoreTests/CoreTests.swift | 36 + .../InfrastructureTests.swift | 130 -- .../Resources/ConnectionService.json | 1 - .../Resources/example.zip | Bin 794 -> 0 bytes .../ProfilesTests.swift | 39 + .../ProvidersTests.swift | 174 ++ .../ServicesTests.swift | 98 + .../Resources/Debug.log | 15 + .../UtilsTests.swift | 32 +- PassepartoutCore/swiftgen.yml | 8 - fastlane/Matchfile | 3 +- 2103 files changed, 27410 insertions(+), 40371 deletions(-) delete mode 100644 Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout-macOS.xcscheme rename Passepartout.xcodeproj/xcshareddata/xcschemes/{Passepartout-iOS.xcscheme => Passepartout.xcscheme} (61%) delete mode 100644 Passepartout/App/Descriptible.swift create mode 100755 Passepartout/App/Scripts/build_wireguard_go_bridge.sh create mode 100755 Passepartout/App/Scripts/copy_coredata_codegen.sh rename Passepartout/App/{macOS => Shared}/App.entitlements (57%) rename Passepartout/App/{iOS => Shared}/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png (100%) rename Passepartout/App/{iOS => Shared}/Assets.xcassets/AppIcon.appiconset/AppIcon-120.png (100%) rename Passepartout/App/{iOS => Shared}/Assets.xcassets/AppIcon.appiconset/AppIcon-152.png (100%) rename Passepartout/App/{iOS => Shared}/Assets.xcassets/AppIcon.appiconset/AppIcon-167.png (100%) rename Passepartout/App/{iOS => Shared}/Assets.xcassets/AppIcon.appiconset/AppIcon-180.png (100%) rename Passepartout/App/{iOS => Shared}/Assets.xcassets/AppIcon.appiconset/AppIcon-76.png (100%) rename Passepartout/App/{iOS => Shared}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Assets.xcassets}/Contents.json (100%) create mode 100644 Passepartout/App/Shared/Assets.xcassets/accentColor.colorset/Contents.json create mode 100644 Passepartout/App/Shared/Assets.xcassets/lightTextColor.colorset/Contents.json rename Passepartout/App/{iOS => Shared}/Assets.xcassets/logo.imageset/Contents.json (100%) rename Passepartout/App/{iOS => Shared}/Assets.xcassets/logo.imageset/logo@2x.png (100%) rename Passepartout/App/{iOS => Shared}/Assets.xcassets/logo.imageset/logo@3x.png (100%) create mode 100644 Passepartout/App/Shared/Assets.xcassets/primaryColor.colorset/Contents.json create mode 100644 Passepartout/App/Shared/Constants/AppContext.swift create mode 100644 Passepartout/App/Shared/Constants/Constants+Extensions.swift create mode 100644 Passepartout/App/Shared/Constants/SwiftGen+Assets.swift create mode 100644 Passepartout/App/Shared/Constants/SwiftGen+Strings.swift create mode 100644 Passepartout/App/Shared/Constants/Theme.swift create mode 100644 Passepartout/App/Shared/Extensions/DebugLog+Constants.swift create mode 100644 Passepartout/App/Shared/Extensions/PassepartoutProviders+Extensions.swift create mode 100644 Passepartout/App/Shared/Extensions/TunnelKit+Identifiable.swift create mode 100644 Passepartout/App/Shared/Extensions/VPNProtocolType+FileExtensions.swift rename Passepartout/App/{macOS/Assets.xcassets => Shared/Flags.xcassets}/Contents.json (100%) rename Passepartout/App/{macOS/Providers.xcassets => Shared/Flags.xcassets/flags}/Contents.json (52%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ad.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ad.imageset/ad@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ad.imageset/ad@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ae.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ae.imageset/ae@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ae.imageset/ae@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/af.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/af.imageset/af@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/af.imageset/af@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ag.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ag.imageset/ag@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ag.imageset/ag@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ai.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ai.imageset/ai@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ai.imageset/ai@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/al.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/al.imageset/al@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/al.imageset/al@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/am.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/am.imageset/am@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/am.imageset/am@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ao.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ao.imageset/ao@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ao.imageset/ao@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/aq.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/aq.imageset/aq@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/aq.imageset/aq@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ar.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ar.imageset/ar@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ar.imageset/ar@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/as.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/as.imageset/as@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/as.imageset/as@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/at.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/at.imageset/at@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/at.imageset/at@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/au.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/au.imageset/au@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/au.imageset/au@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/aw.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/aw.imageset/aw@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/aw.imageset/aw@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ax.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ax.imageset/ax@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ax.imageset/ax@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/az.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/az.imageset/az@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/az.imageset/az@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ba.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ba.imageset/ba@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ba.imageset/ba@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bb.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bb.imageset/bb@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bb.imageset/bb@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bd.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bd.imageset/bd@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bd.imageset/bd@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/be.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/be.imageset/be@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/be.imageset/be@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bf.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bf.imageset/bf@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bf.imageset/bf@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bg.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bg.imageset/bg@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bg.imageset/bg@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bh.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bh.imageset/bh@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bh.imageset/bh@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bi.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bi.imageset/bi@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bi.imageset/bi@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bj.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bj.imageset/bj@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bj.imageset/bj@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bl.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bl.imageset/bl@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bl.imageset/bl@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bm.imageset/bm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bm.imageset/bm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bn.imageset/bn@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bn.imageset/bn@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bo.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bo.imageset/bo@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bo.imageset/bo@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bq.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bq.imageset/bq@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bq.imageset/bq@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/br.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/br.imageset/br@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/br.imageset/br@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bs.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bs.imageset/bs@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bs.imageset/bs@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bt.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bt.imageset/bt@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bt.imageset/bt@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bv.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bv.imageset/bv@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bv.imageset/bv@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bw.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bw.imageset/bw@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bw.imageset/bw@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/by.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/by.imageset/by@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/by.imageset/by@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bz.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bz.imageset/bz@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/bz.imageset/bz@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ca.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ca.imageset/ca@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ca.imageset/ca@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cc.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cc.imageset/cc@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cc.imageset/cc@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cd.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cd.imageset/cd@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cd.imageset/cd@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cf.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cf.imageset/cf@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cf.imageset/cf@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cg.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cg.imageset/cg@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cg.imageset/cg@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ch.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ch.imageset/ch@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ch.imageset/ch@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ci.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ci.imageset/ci@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ci.imageset/ci@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ck.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ck.imageset/ck@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ck.imageset/ck@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cl.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cl.imageset/cl@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cl.imageset/cl@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cm.imageset/cm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cm.imageset/cm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cn.imageset/cn@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cn.imageset/cn@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/co.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/co.imageset/co@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/co.imageset/co@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cr.imageset/cr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cr.imageset/cr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cu.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cu.imageset/cu@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cu.imageset/cu@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cv.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cv.imageset/cv@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cv.imageset/cv@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cw.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cw.imageset/cw@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cw.imageset/cw@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cx.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cx.imageset/cx@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cx.imageset/cx@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cy.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cy.imageset/cy@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cy.imageset/cy@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cz.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cz.imageset/cz@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/cz.imageset/cz@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/de.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/de.imageset/de@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/de.imageset/de@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dj.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dj.imageset/dj@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dj.imageset/dj@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dk.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dk.imageset/dk@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dk.imageset/dk@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dm.imageset/dm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dm.imageset/dm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/do.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/do.imageset/do@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/do.imageset/do@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dz.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dz.imageset/dz@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/dz.imageset/dz@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ec.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ec.imageset/ec@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ec.imageset/ec@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ee.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ee.imageset/ee@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ee.imageset/ee@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/eg.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/eg.imageset/eg@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/eg.imageset/eg@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/eh.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/eh.imageset/eh@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/eh.imageset/eh@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/er.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/er.imageset/er@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/er.imageset/er@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/es-ct.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/es-ct.imageset/es-ct@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/es-ct.imageset/es-ct@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/es.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/es.imageset/es@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/es.imageset/es@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/et.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/et.imageset/et@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/et.imageset/et@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/eu.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/eu.imageset/eu@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/eu.imageset/eu@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fi.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fi.imageset/fi@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fi.imageset/fi@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fj.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fj.imageset/fj@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fj.imageset/fj@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fk.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fk.imageset/fk@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fk.imageset/fk@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fm.imageset/fm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fm.imageset/fm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fo.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fo.imageset/fo@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fo.imageset/fo@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fr.imageset/fr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/fr.imageset/fr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ga.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ga.imageset/ga@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ga.imageset/ga@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-eng.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-eng.imageset/gb-eng@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-eng.imageset/gb-eng@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-nir.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-nir.imageset/gb-nir@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-nir.imageset/gb-nir@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-sct.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-sct.imageset/gb-sct@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-sct.imageset/gb-sct@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-wls.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-wls.imageset/gb-wls@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb-wls.imageset/gb-wls@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb.imageset/gb@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gb.imageset/gb@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gd.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gd.imageset/gd@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gd.imageset/gd@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ge.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ge.imageset/ge@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ge.imageset/ge@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gf.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gf.imageset/gf@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gf.imageset/gf@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gg.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gg.imageset/gg@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gg.imageset/gg@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gh.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gh.imageset/gh@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gh.imageset/gh@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gi.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gi.imageset/gi@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gi.imageset/gi@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gl.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gl.imageset/gl@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gl.imageset/gl@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gm.imageset/gm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gm.imageset/gm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gn.imageset/gn@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gn.imageset/gn@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gp.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gp.imageset/gp@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gp.imageset/gp@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gq.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gq.imageset/gq@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gq.imageset/gq@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gr.imageset/gr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gr.imageset/gr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gs.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gs.imageset/gs@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gs.imageset/gs@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gt.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gt.imageset/gt@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gt.imageset/gt@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gu.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gu.imageset/gu@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gu.imageset/gu@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gw.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gw.imageset/gw@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gw.imageset/gw@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gy.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gy.imageset/gy@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/gy.imageset/gy@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hk.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hk.imageset/hk@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hk.imageset/hk@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hm.imageset/hm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hm.imageset/hm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hn.imageset/hn@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hn.imageset/hn@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hr.imageset/hr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hr.imageset/hr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ht.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ht.imageset/ht@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ht.imageset/ht@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hu.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hu.imageset/hu@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/hu.imageset/hu@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/id.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/id.imageset/id@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/id.imageset/id@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ie.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ie.imageset/ie@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ie.imageset/ie@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/il.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/il.imageset/il@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/il.imageset/il@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/im.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/im.imageset/im@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/im.imageset/im@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/in.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/in.imageset/in@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/in.imageset/in@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/io.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/io.imageset/io@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/io.imageset/io@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/iq.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/iq.imageset/iq@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/iq.imageset/iq@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ir.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ir.imageset/ir@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ir.imageset/ir@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/is.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/is.imageset/is@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/is.imageset/is@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/it.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/it.imageset/it@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/it.imageset/it@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/je.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/je.imageset/je@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/je.imageset/je@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/jm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/jm.imageset/jm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/jm.imageset/jm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/jo.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/jo.imageset/jo@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/jo.imageset/jo@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/jp.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/jp.imageset/jp@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/jp.imageset/jp@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ke.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ke.imageset/ke@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ke.imageset/ke@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kg.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kg.imageset/kg@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kg.imageset/kg@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kh.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kh.imageset/kh@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kh.imageset/kh@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ki.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ki.imageset/ki@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ki.imageset/ki@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/km.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/km.imageset/km@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/km.imageset/km@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kn.imageset/kn@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kn.imageset/kn@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kp.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kp.imageset/kp@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kp.imageset/kp@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kr.imageset/kr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kr.imageset/kr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kw.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kw.imageset/kw@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kw.imageset/kw@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ky.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ky.imageset/ky@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ky.imageset/ky@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kz.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kz.imageset/kz@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/kz.imageset/kz@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/la.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/la.imageset/la@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/la.imageset/la@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lb.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lb.imageset/lb@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lb.imageset/lb@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lc.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lc.imageset/lc@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lc.imageset/lc@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/li.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/li.imageset/li@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/li.imageset/li@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lk.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lk.imageset/lk@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lk.imageset/lk@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lr.imageset/lr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lr.imageset/lr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ls.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ls.imageset/ls@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ls.imageset/ls@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lt.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lt.imageset/lt@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lt.imageset/lt@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lu.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lu.imageset/lu@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lu.imageset/lu@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lv.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lv.imageset/lv@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/lv.imageset/lv@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ly.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ly.imageset/ly@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ly.imageset/ly@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ma.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ma.imageset/ma@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ma.imageset/ma@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mc.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mc.imageset/mc@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mc.imageset/mc@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/md.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/md.imageset/md@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/md.imageset/md@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/me.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/me.imageset/me@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/me.imageset/me@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mf.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mf.imageset/mf@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mf.imageset/mf@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mg.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mg.imageset/mg@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mg.imageset/mg@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mh.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mh.imageset/mh@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mh.imageset/mh@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mk.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mk.imageset/mk@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mk.imageset/mk@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ml.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ml.imageset/ml@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ml.imageset/ml@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mm.imageset/mm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mm.imageset/mm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mn.imageset/mn@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mn.imageset/mn@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mo.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mo.imageset/mo@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mo.imageset/mo@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mp.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mp.imageset/mp@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mp.imageset/mp@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mq.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mq.imageset/mq@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mq.imageset/mq@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mr.imageset/mr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mr.imageset/mr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ms.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ms.imageset/ms@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ms.imageset/ms@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mt.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mt.imageset/mt@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mt.imageset/mt@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mu.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mu.imageset/mu@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mu.imageset/mu@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mv.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mv.imageset/mv@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mv.imageset/mv@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mw.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mw.imageset/mw@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mw.imageset/mw@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mx.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mx.imageset/mx@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mx.imageset/mx@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/my.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/my.imageset/my@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/my.imageset/my@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mz.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mz.imageset/mz@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/mz.imageset/mz@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/na.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/na.imageset/na@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/na.imageset/na@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nc.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nc.imageset/nc@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nc.imageset/nc@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ne.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ne.imageset/ne@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ne.imageset/ne@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nf.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nf.imageset/nf@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nf.imageset/nf@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ng.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ng.imageset/ng@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ng.imageset/ng@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ni.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ni.imageset/ni@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ni.imageset/ni@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nl.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nl.imageset/nl@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nl.imageset/nl@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/no.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/no.imageset/no@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/no.imageset/no@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/np.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/np.imageset/np@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/np.imageset/np@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nr.imageset/nr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nr.imageset/nr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nu.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nu.imageset/nu@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nu.imageset/nu@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nz.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nz.imageset/nz@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/nz.imageset/nz@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/om.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/om.imageset/om@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/om.imageset/om@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pa.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pa.imageset/pa@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pa.imageset/pa@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pe.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pe.imageset/pe@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pe.imageset/pe@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pf.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pf.imageset/pf@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pf.imageset/pf@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pg.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pg.imageset/pg@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pg.imageset/pg@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ph.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ph.imageset/ph@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ph.imageset/ph@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pk.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pk.imageset/pk@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pk.imageset/pk@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pl.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pl.imageset/pl@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pl.imageset/pl@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pm.imageset/pm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pm.imageset/pm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pn.imageset/pn@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pn.imageset/pn@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pr.imageset/pr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pr.imageset/pr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ps.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ps.imageset/ps@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ps.imageset/ps@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pt.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pt.imageset/pt@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pt.imageset/pt@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pw.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pw.imageset/pw@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/pw.imageset/pw@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/py.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/py.imageset/py@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/py.imageset/py@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/qa.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/qa.imageset/qa@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/qa.imageset/qa@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/re.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/re.imageset/re@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/re.imageset/re@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ro.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ro.imageset/ro@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ro.imageset/ro@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/rs.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/rs.imageset/rs@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/rs.imageset/rs@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ru.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ru.imageset/ru@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ru.imageset/ru@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/rw.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/rw.imageset/rw@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/rw.imageset/rw@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sa.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sa.imageset/sa@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sa.imageset/sa@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sb.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sb.imageset/sb@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sb.imageset/sb@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sc.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sc.imageset/sc@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sc.imageset/sc@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sd.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sd.imageset/sd@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sd.imageset/sd@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/se.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/se.imageset/se@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/se.imageset/se@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sg.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sg.imageset/sg@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sg.imageset/sg@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sh.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sh.imageset/sh@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sh.imageset/sh@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/si.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/si.imageset/si@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/si.imageset/si@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sj.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sj.imageset/sj@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sj.imageset/sj@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sk.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sk.imageset/sk@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sk.imageset/sk@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sl.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sl.imageset/sl@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sl.imageset/sl@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sm.imageset/sm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sm.imageset/sm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sn.imageset/sn@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sn.imageset/sn@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/so.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/so.imageset/so@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/so.imageset/so@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sr.imageset/sr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sr.imageset/sr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ss.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ss.imageset/ss@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ss.imageset/ss@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/st.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/st.imageset/st@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/st.imageset/st@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sv.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sv.imageset/sv@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sv.imageset/sv@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sx.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sx.imageset/sx@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sx.imageset/sx@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sy.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sy.imageset/sy@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sy.imageset/sy@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sz.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sz.imageset/sz@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/sz.imageset/sz@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tc.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tc.imageset/tc@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tc.imageset/tc@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/td.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/td.imageset/td@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/td.imageset/td@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tf.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tf.imageset/tf@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tf.imageset/tf@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tg.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tg.imageset/tg@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tg.imageset/tg@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/th.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/th.imageset/th@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/th.imageset/th@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tj.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tj.imageset/tj@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tj.imageset/tj@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tk.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tk.imageset/tk@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tk.imageset/tk@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tl.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tl.imageset/tl@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tl.imageset/tl@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tm.imageset/tm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tm.imageset/tm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tn.imageset/tn@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tn.imageset/tn@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/to.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/to.imageset/to@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/to.imageset/to@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tr.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tr.imageset/tr@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tr.imageset/tr@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tt.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tt.imageset/tt@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tt.imageset/tt@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tv.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tv.imageset/tv@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tv.imageset/tv@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tw.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tw.imageset/tw@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tw.imageset/tw@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tz.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tz.imageset/tz@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/tz.imageset/tz@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ua.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ua.imageset/ua@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ua.imageset/ua@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ug.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ug.imageset/ug@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ug.imageset/ug@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/um.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/um.imageset/um@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/um.imageset/um@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/un.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/un.imageset/un@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/un.imageset/un@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/us.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/us.imageset/us@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/us.imageset/us@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/uy.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/uy.imageset/uy@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/uy.imageset/uy@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/uz.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/uz.imageset/uz@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/uz.imageset/uz@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/va.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/va.imageset/va@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/va.imageset/va@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vc.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vc.imageset/vc@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vc.imageset/vc@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ve.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ve.imageset/ve@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ve.imageset/ve@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vg.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vg.imageset/vg@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vg.imageset/vg@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vi.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vi.imageset/vi@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vi.imageset/vi@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vn.imageset/vn@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vn.imageset/vn@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vu.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vu.imageset/vu@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/vu.imageset/vu@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/wf.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/wf.imageset/wf@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/wf.imageset/wf@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ws.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ws.imageset/ws@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ws.imageset/ws@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/xk.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/xk.imageset/xk@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/xk.imageset/xk@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ye.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ye.imageset/ye@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/ye.imageset/ye@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/yt.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/yt.imageset/yt@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/yt.imageset/yt@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/za.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/za.imageset/za@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/za.imageset/za@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/zm.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/zm.imageset/zm@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/zm.imageset/zm@3x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/zw.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/zw.imageset/zw@2x.png (100%) rename Passepartout/App/{iOS/Flags.xcassets => Shared/Flags.xcassets/flags}/zw.imageset/zw@3x.png (100%) rename {PassepartoutCore/Sources/PassepartoutConstants => Passepartout/App/Shared/InApp}/LocalProduct.swift (53%) rename {PassepartoutCore/Sources/PassepartoutCore/Model => Passepartout/App/Shared/InApp}/ProductManager.swift (51%) rename Passepartout/App/{iOS => Shared}/Info.plist (84%) rename Passepartout/App/{iOS => Shared/Intents}/Base.lproj/Intents.intentdefinition (94%) create mode 100644 Passepartout/App/Shared/Intents/IntentDispatcher+Activities.swift create mode 100644 Passepartout/App/Shared/Intents/IntentDispatcher.swift create mode 100644 Passepartout/App/Shared/Intents/IntentsManager.swift rename Passepartout/App/{iOS => Shared/Intents}/de.lproj/Intents.strings (88%) rename Passepartout/App/{iOS => Shared/Intents}/el.lproj/Intents.strings (91%) rename Passepartout/App/{iOS => Shared/Intents}/en.lproj/Intents.strings (87%) rename Passepartout/App/{iOS => Shared/Intents}/es.lproj/Intents.strings (88%) rename Passepartout/App/{iOS => Shared/Intents}/fr.lproj/Intents.strings (90%) rename Passepartout/App/{iOS => Shared/Intents}/it.lproj/Intents.strings (88%) rename Passepartout/App/{iOS => Shared/Intents}/nl.lproj/Intents.strings (89%) rename Passepartout/App/{iOS => Shared/Intents}/pl.lproj/Intents.strings (89%) rename Passepartout/App/{iOS => Shared/Intents}/pt.lproj/Intents.strings (88%) rename Passepartout/App/{iOS => Shared/Intents}/ru.lproj/Intents.strings (90%) rename Passepartout/App/{iOS => Shared/Intents}/sv.lproj/Intents.strings (88%) rename Passepartout/App/{iOS => Shared/Intents}/zh-Hans.lproj/Intents.strings (90%) create mode 100644 Passepartout/App/Shared/L10n/Core+L10n.swift create mode 100644 Passepartout/App/Shared/L10n/OpenVPN+L10n.swift create mode 100644 Passepartout/App/Shared/L10n/Providers+L10n.swift create mode 100644 Passepartout/App/Shared/L10n/TunnelKit+L10n.swift create mode 100644 Passepartout/App/Shared/L10n/Unlocalized.swift create mode 100644 Passepartout/App/Shared/L10n/WireGuard+L10n.swift create mode 100644 Passepartout/App/Shared/L10n/de.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/el.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/en.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/es.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/fr.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/it.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/nl.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/pl.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/pt.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/ru.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/sv.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/L10n/zh-Hans.lproj/Localizable.strings create mode 100644 Passepartout/App/Shared/PassepartoutApp.swift rename Passepartout/App/{macOS/Launcher/Assets.xcassets => Shared/Providers.xcassets}/Contents.json (100%) rename Passepartout/App/{macOS/Launcher/Assets.xcassets/AccentColor.colorset => Shared/Providers.xcassets/providers}/Contents.json (51%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/hideme.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/hideme.imageset/hideme@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/hideme.imageset/hideme@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/mullvad.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/mullvad.imageset/mullvad@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/mullvad.imageset/mullvad@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/nordvpn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/nordvpn.imageset/nordvpn-dark@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/nordvpn.imageset/nordvpn-dark@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/nordvpn.imageset/nordvpn@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/nordvpn.imageset/nordvpn@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/oeck.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/oeck.imageset/oeck-dark@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/oeck.imageset/oeck-dark@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/oeck.imageset/oeck@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/oeck.imageset/oeck@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/pia.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/pia.imageset/pia@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/pia.imageset/pia@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/placeholder.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/placeholder.imageset/placeholder@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/placeholder.imageset/placeholder@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/protonvpn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/protonvpn.imageset/protonvpn@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/protonvpn.imageset/protonvpn@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/surfshark.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/surfshark.imageset/surfshark@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/surfshark.imageset/surfshark@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/torguard.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/torguard.imageset/torguard@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/torguard.imageset/torguard@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/tunnelbear.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/tunnelbear.imageset/tunnelbear@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/tunnelbear.imageset/tunnelbear@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/vyprvpn.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/vyprvpn.imageset/vyprvpn@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/vyprvpn.imageset/vyprvpn@3x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/windscribe.imageset/Contents.json (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/windscribe.imageset/windscribe@2x.png (100%) rename Passepartout/App/{iOS/Providers.xcassets => Shared/Providers.xcassets/providers}/windscribe.imageset/windscribe@3x.png (100%) create mode 100644 Passepartout/App/Shared/Reusable/AddingTextField.swift rename Passepartout/App/{iOS/Cells/ActivityTableViewCell.swift => Shared/Reusable/Binding+Extensions.swift} (50%) create mode 100644 Passepartout/App/Shared/Reusable/CopySavingButton.swift create mode 100644 Passepartout/App/Shared/Reusable/EditableTextList.swift create mode 100644 Passepartout/App/Shared/Reusable/GenericCreditsView.swift create mode 100644 Passepartout/App/Shared/Reusable/GenericVersionView.swift create mode 100644 Passepartout/App/Shared/Reusable/InApp.swift create mode 100644 Passepartout/App/Shared/Reusable/IntentActivity.swift create mode 100644 Passepartout/App/Shared/Reusable/LongContentView.swift create mode 100644 Passepartout/App/Shared/Reusable/ReloadingSection.swift create mode 100644 Passepartout/App/Shared/Reusable/RevealingSecureField.swift create mode 100644 Passepartout/App/Shared/Reusable/Reviewer.swift create mode 100644 Passepartout/App/Shared/Reusable/Shortcut.swift create mode 100644 Passepartout/App/Shared/Reusable/StyledPicker.swift create mode 100644 Passepartout/App/Shared/Reusable/Validators.swift create mode 100644 Passepartout/App/Shared/Reusable/View+Extensions.swift rename Passepartout/App/{iOS => Shared}/Settings.bundle/Root.plist (100%) rename Passepartout/App/{iOS => Shared}/Settings.bundle/en.lproj/Root.strings (100%) rename Passepartout/App/{iOS => Shared}/de.lproj/InfoPlist.strings (100%) rename Passepartout/App/{iOS => Shared}/el.lproj/InfoPlist.strings (100%) rename Passepartout/App/{iOS => Shared}/en.lproj/InfoPlist.strings (100%) rename Passepartout/App/{iOS => Shared}/es.lproj/InfoPlist.strings (100%) rename Passepartout/App/{iOS => Shared}/fr.lproj/InfoPlist.strings (100%) rename Passepartout/App/{iOS => Shared}/it.lproj/InfoPlist.strings (100%) rename Passepartout/App/{iOS => Shared}/nl.lproj/InfoPlist.strings (100%) rename Passepartout/App/{iOS => Shared}/pl.lproj/InfoPlist.strings (100%) rename Passepartout/App/{iOS => Shared}/pt.lproj/InfoPlist.strings (100%) rename Passepartout/App/{iOS => Shared}/ru.lproj/InfoPlist.strings (100%) rename Passepartout/App/{iOS => Shared}/sv.lproj/InfoPlist.strings (100%) create mode 100644 Passepartout/App/Shared/swiftgen.yml rename Passepartout/App/{iOS => Shared}/zh-Hans.lproj/InfoPlist.strings (100%) delete mode 100644 Passepartout/App/iOS/App.entitlements delete mode 100644 Passepartout/App/iOS/AppDelegate.swift delete mode 100644 Passepartout/App/iOS/Assets.xcassets/Contents.json delete mode 100644 Passepartout/App/iOS/Base.lproj/About.storyboard delete mode 100644 Passepartout/App/iOS/Base.lproj/LaunchScreen.storyboard delete mode 100644 Passepartout/App/iOS/Base.lproj/Main.storyboard delete mode 100644 Passepartout/App/iOS/Base.lproj/Organizer.storyboard delete mode 100644 Passepartout/App/iOS/Base.lproj/Purchase.storyboard delete mode 100644 Passepartout/App/iOS/Base.lproj/Shortcuts.storyboard delete mode 100644 Passepartout/App/iOS/Cells/Cells.swift delete mode 100644 Passepartout/App/iOS/Cells/DestructiveTableViewCell.swift delete mode 100644 Passepartout/App/iOS/Cells/FieldTableViewCell.swift delete mode 100644 Passepartout/App/iOS/Cells/SettingTableViewCell.swift delete mode 100644 Passepartout/App/iOS/Cells/ToggleTableViewCell.swift delete mode 100644 Passepartout/App/iOS/Flags.xcassets/Contents.json delete mode 100644 Passepartout/App/iOS/Global/HostImporter.swift delete mode 100644 Passepartout/App/iOS/Global/IssueReporter.swift delete mode 100644 Passepartout/App/iOS/Global/Macros.swift delete mode 100644 Passepartout/App/iOS/Global/SwiftGen+Assets.swift delete mode 100644 Passepartout/App/iOS/Global/SwiftGen+Scenes.swift delete mode 100644 Passepartout/App/iOS/Global/SwiftGen+Segues.swift delete mode 100644 Passepartout/App/iOS/Global/Theme+Cells.swift delete mode 100644 Passepartout/App/iOS/Global/Theme.swift delete mode 100644 Passepartout/App/iOS/Global/UITextView+Search.swift delete mode 100644 Passepartout/App/iOS/Intents/IntentDispatcher.swift delete mode 100644 Passepartout/App/iOS/Providers.xcassets/csv.imageset/Contents.json delete mode 100644 Passepartout/App/iOS/Providers.xcassets/csv.imageset/csv@2x.png delete mode 100644 Passepartout/App/iOS/Providers.xcassets/csv.imageset/csv@3x.png create mode 100644 Passepartout/App/iOS/Reusable/ActivityView.swift create mode 100644 Passepartout/App/iOS/Reusable/IntentAddView.swift create mode 100644 Passepartout/App/iOS/Reusable/IntentEditView.swift create mode 100644 Passepartout/App/iOS/Reusable/MailComposerView.swift delete mode 100644 Passepartout/App/iOS/Scenes/About/AboutViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/AccountViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/ConfigurationViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/DebugLogViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/EndpointViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/NetworkSettingsViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/Organizer/DonationViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/Organizer/ImportedHostsViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/Organizer/WizardHostViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/Organizer/WizardProviderViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/ProviderPoolViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/ProviderPresetViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/Purchase/PurchaseTableViewCell.swift delete mode 100644 Passepartout/App/iOS/Scenes/Purchase/PurchaseViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/ServerNetworkViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/ServiceViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsAddViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsConnectToViewController.swift delete mode 100644 Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsViewController.swift create mode 100644 Passepartout/App/iOS/ViewModels/AddHostViewModel.swift create mode 100644 Passepartout/App/iOS/ViewModels/AddProviderViewModel.swift create mode 100644 Passepartout/App/iOS/Views/AboutView.swift create mode 100644 Passepartout/App/iOS/Views/AccountView.swift create mode 100644 Passepartout/App/iOS/Views/AddProfile/AddHostView.swift create mode 100644 Passepartout/App/iOS/Views/AddProfile/AddProfileView.swift create mode 100644 Passepartout/App/iOS/Views/AddProfile/AddProviderView+Name.swift create mode 100644 Passepartout/App/iOS/Views/AddProfile/AddProviderView.swift create mode 100644 Passepartout/App/iOS/Views/CreditsView.swift create mode 100644 Passepartout/App/iOS/Views/DebugLogView.swift create mode 100644 Passepartout/App/iOS/Views/DiagnosticsView+OpenVPN.swift create mode 100644 Passepartout/App/iOS/Views/DiagnosticsView+WireGuard.swift create mode 100644 Passepartout/App/iOS/Views/DiagnosticsView.swift create mode 100644 Passepartout/App/iOS/Views/DonateView.swift create mode 100644 Passepartout/App/iOS/Views/EndpointAdvancedView+OpenVPN.swift create mode 100644 Passepartout/App/iOS/Views/EndpointAdvancedView+WireGuard.swift rename Passepartout/App/{macOS/Global/NSTextView+Search.swift => iOS/Views/EndpointAdvancedView.swift} (80%) create mode 100644 Passepartout/App/iOS/Views/EndpointView+OpenVPN.swift create mode 100644 Passepartout/App/iOS/Views/EndpointView+WireGuard.swift rename Passepartout/App/iOS/{Global/Theme+Titles.swift => Views/EndpointView.swift} (56%) create mode 100644 Passepartout/App/iOS/Views/MainView.swift create mode 100644 Passepartout/App/iOS/Views/NetworkSettingsView.swift create mode 100644 Passepartout/App/iOS/Views/OnDemandView+SSID.swift create mode 100644 Passepartout/App/iOS/Views/OnDemandView.swift create mode 100644 Passepartout/App/iOS/Views/OrganizerView+AddProfileMenu.swift create mode 100644 Passepartout/App/iOS/Views/OrganizerView+Profiles.swift create mode 100644 Passepartout/App/iOS/Views/OrganizerView+Scene.swift create mode 100644 Passepartout/App/iOS/Views/OrganizerView+Shortcuts.swift create mode 100644 Passepartout/App/iOS/Views/OrganizerView+VPN.swift create mode 100644 Passepartout/App/iOS/Views/OrganizerView.swift create mode 100644 Passepartout/App/iOS/Views/Paywall/PaywallView+Beta.swift create mode 100644 Passepartout/App/iOS/Views/Paywall/PaywallView+Purchase.swift create mode 100644 Passepartout/App/iOS/Views/Paywall/PaywallView.swift create mode 100644 Passepartout/App/iOS/Views/Profile/ProfileView+Rename.swift create mode 100644 Passepartout/App/iOS/Views/ProfileHeaderRow.swift create mode 100644 Passepartout/App/iOS/Views/ProfileView+Configuration.swift create mode 100644 Passepartout/App/iOS/Views/ProfileView+Diagnostics.swift create mode 100644 Passepartout/App/iOS/Views/ProfileView+Extra.swift create mode 100644 Passepartout/App/iOS/Views/ProfileView+Provider.swift create mode 100644 Passepartout/App/iOS/Views/ProfileView+VPN.swift create mode 100644 Passepartout/App/iOS/Views/ProfileView+Welcome.swift create mode 100644 Passepartout/App/iOS/Views/ProfileView.swift create mode 100644 Passepartout/App/iOS/Views/ProviderLocationView.swift create mode 100644 Passepartout/App/iOS/Views/ProviderPresetView.swift create mode 100644 Passepartout/App/iOS/Views/ReportIssueView.swift create mode 100644 Passepartout/App/iOS/Views/ShortcutsView+Add.swift create mode 100644 Passepartout/App/iOS/Views/ShortcutsView+ConnectTo.swift create mode 100644 Passepartout/App/iOS/Views/ShortcutsView.swift create mode 100644 Passepartout/App/iOS/Views/VersionView.swift delete mode 100644 Passepartout/App/iOS/swiftgen.yml delete mode 100644 Passepartout/App/macOS/AppDelegate.swift delete mode 100644 Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png delete mode 100644 Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png delete mode 100644 Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png delete mode 100644 Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png delete mode 100644 Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-64.png delete mode 100644 Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 Passepartout/App/macOS/Assets.xcassets/StatusActive.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Assets.xcassets/StatusActive.imageset/StatusActive@2x.png delete mode 100644 Passepartout/App/macOS/Assets.xcassets/StatusActive.imageset/StatusActive@3x.png delete mode 100644 Passepartout/App/macOS/Assets.xcassets/StatusPending.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Assets.xcassets/StatusPending.imageset/StatusPending@2x.png delete mode 100644 Passepartout/App/macOS/Assets.xcassets/StatusPending.imageset/StatusPending@3x.png delete mode 100644 Passepartout/App/macOS/Base.lproj/Main.storyboard delete mode 100644 Passepartout/App/macOS/Base.lproj/Preferences.storyboard delete mode 100644 Passepartout/App/macOS/Base.lproj/Purchase.storyboard delete mode 100644 Passepartout/App/macOS/Base.lproj/Service.storyboard delete mode 100644 Passepartout/App/macOS/Flags.xcassets/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ad.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ad.imageset/ad@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ad.imageset/ad@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ae.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ae.imageset/ae@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ae.imageset/ae@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/af.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/af.imageset/af@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/af.imageset/af@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ag.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ag.imageset/ag@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ag.imageset/ag@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ai.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ai.imageset/ai@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ai.imageset/ai@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/al.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/al.imageset/al@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/al.imageset/al@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/am.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/am.imageset/am@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/am.imageset/am@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ao.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ao.imageset/ao@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ao.imageset/ao@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/aq.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/aq.imageset/aq@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/aq.imageset/aq@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ar.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ar.imageset/ar@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ar.imageset/ar@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/as.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/as.imageset/as@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/as.imageset/as@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/at.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/at.imageset/at@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/at.imageset/at@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/au.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/au.imageset/au@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/au.imageset/au@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/aw.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/aw.imageset/aw@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/aw.imageset/aw@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ax.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ax.imageset/ax@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ax.imageset/ax@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/az.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/az.imageset/az@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/az.imageset/az@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ba.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ba.imageset/ba@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ba.imageset/ba@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bb.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bb.imageset/bb@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bb.imageset/bb@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bd.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bd.imageset/bd@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bd.imageset/bd@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/be.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/be.imageset/be@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/be.imageset/be@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bf.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bf.imageset/bf@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bf.imageset/bf@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bg.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bg.imageset/bg@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bg.imageset/bg@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bh.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bh.imageset/bh@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bh.imageset/bh@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bi.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bi.imageset/bi@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bi.imageset/bi@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bj.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bj.imageset/bj@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bj.imageset/bj@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bl.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bl.imageset/bl@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bl.imageset/bl@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bm.imageset/bm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bm.imageset/bm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bn.imageset/bn@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bn.imageset/bn@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bo.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bo.imageset/bo@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bo.imageset/bo@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bq.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bq.imageset/bq@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bq.imageset/bq@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/br.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/br.imageset/br@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/br.imageset/br@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bs.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bs.imageset/bs@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bs.imageset/bs@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bt.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bt.imageset/bt@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bt.imageset/bt@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bv.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bv.imageset/bv@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bv.imageset/bv@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bw.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bw.imageset/bw@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bw.imageset/bw@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/by.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/by.imageset/by@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/by.imageset/by@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bz.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bz.imageset/bz@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/bz.imageset/bz@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ca.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ca.imageset/ca@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ca.imageset/ca@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cc.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cc.imageset/cc@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cc.imageset/cc@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cd.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cd.imageset/cd@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cd.imageset/cd@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cf.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cf.imageset/cf@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cf.imageset/cf@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cg.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cg.imageset/cg@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cg.imageset/cg@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ch.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ch.imageset/ch@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ch.imageset/ch@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ci.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ci.imageset/ci@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ci.imageset/ci@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ck.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ck.imageset/ck@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ck.imageset/ck@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cl.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cl.imageset/cl@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cl.imageset/cl@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cm.imageset/cm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cm.imageset/cm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cn.imageset/cn@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cn.imageset/cn@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/co.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/co.imageset/co@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/co.imageset/co@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cr.imageset/cr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cr.imageset/cr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cu.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cu.imageset/cu@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cu.imageset/cu@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cv.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cv.imageset/cv@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cv.imageset/cv@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cw.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cw.imageset/cw@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cw.imageset/cw@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cx.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cx.imageset/cx@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cx.imageset/cx@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cy.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cy.imageset/cy@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cy.imageset/cy@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cz.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cz.imageset/cz@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/cz.imageset/cz@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/de.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/de.imageset/de@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/de.imageset/de@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dj.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dj.imageset/dj@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dj.imageset/dj@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dk.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dk.imageset/dk@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dk.imageset/dk@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dm.imageset/dm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dm.imageset/dm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/do.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/do.imageset/do@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/do.imageset/do@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dz.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dz.imageset/dz@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/dz.imageset/dz@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ec.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ec.imageset/ec@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ec.imageset/ec@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ee.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ee.imageset/ee@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ee.imageset/ee@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/eg.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/eg.imageset/eg@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/eg.imageset/eg@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/eh.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/eh.imageset/eh@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/eh.imageset/eh@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/er.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/er.imageset/er@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/er.imageset/er@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/es-ct.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/es-ct.imageset/es-ct@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/es-ct.imageset/es-ct@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/es.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/es.imageset/es@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/es.imageset/es@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/et.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/et.imageset/et@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/et.imageset/et@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/eu.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/eu.imageset/eu@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/eu.imageset/eu@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fi.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fi.imageset/fi@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fi.imageset/fi@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fj.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fj.imageset/fj@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fj.imageset/fj@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fk.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fk.imageset/fk@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fk.imageset/fk@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fm.imageset/fm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fm.imageset/fm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fo.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fo.imageset/fo@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fo.imageset/fo@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fr.imageset/fr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/fr.imageset/fr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ga.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ga.imageset/ga@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ga.imageset/ga@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-eng.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-eng.imageset/gb-eng@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-eng.imageset/gb-eng@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-nir.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-nir.imageset/gb-nir@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-nir.imageset/gb-nir@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-sct.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-sct.imageset/gb-sct@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-sct.imageset/gb-sct@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-wls.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-wls.imageset/gb-wls@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb-wls.imageset/gb-wls@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb.imageset/gb@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gb.imageset/gb@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gd.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gd.imageset/gd@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gd.imageset/gd@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ge.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ge.imageset/ge@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ge.imageset/ge@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gf.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gf.imageset/gf@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gf.imageset/gf@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gg.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gg.imageset/gg@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gg.imageset/gg@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gh.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gh.imageset/gh@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gh.imageset/gh@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gi.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gi.imageset/gi@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gi.imageset/gi@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gl.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gl.imageset/gl@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gl.imageset/gl@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gm.imageset/gm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gm.imageset/gm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gn.imageset/gn@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gn.imageset/gn@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gp.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gp.imageset/gp@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gp.imageset/gp@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gq.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gq.imageset/gq@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gq.imageset/gq@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gr.imageset/gr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gr.imageset/gr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gs.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gs.imageset/gs@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gs.imageset/gs@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gt.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gt.imageset/gt@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gt.imageset/gt@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gu.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gu.imageset/gu@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gu.imageset/gu@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gw.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gw.imageset/gw@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gw.imageset/gw@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gy.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gy.imageset/gy@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/gy.imageset/gy@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hk.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hk.imageset/hk@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hk.imageset/hk@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hm.imageset/hm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hm.imageset/hm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hn.imageset/hn@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hn.imageset/hn@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hr.imageset/hr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hr.imageset/hr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ht.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ht.imageset/ht@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ht.imageset/ht@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hu.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hu.imageset/hu@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/hu.imageset/hu@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/id.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/id.imageset/id@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/id.imageset/id@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ie.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ie.imageset/ie@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ie.imageset/ie@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/il.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/il.imageset/il@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/il.imageset/il@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/im.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/im.imageset/im@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/im.imageset/im@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/in.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/in.imageset/in@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/in.imageset/in@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/io.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/io.imageset/io@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/io.imageset/io@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/iq.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/iq.imageset/iq@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/iq.imageset/iq@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ir.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ir.imageset/ir@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ir.imageset/ir@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/is.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/is.imageset/is@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/is.imageset/is@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/it.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/it.imageset/it@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/it.imageset/it@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/je.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/je.imageset/je@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/je.imageset/je@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/jm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/jm.imageset/jm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/jm.imageset/jm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/jo.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/jo.imageset/jo@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/jo.imageset/jo@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/jp.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/jp.imageset/jp@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/jp.imageset/jp@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ke.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ke.imageset/ke@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ke.imageset/ke@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kg.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kg.imageset/kg@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kg.imageset/kg@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kh.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kh.imageset/kh@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kh.imageset/kh@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ki.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ki.imageset/ki@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ki.imageset/ki@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/km.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/km.imageset/km@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/km.imageset/km@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kn.imageset/kn@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kn.imageset/kn@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kp.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kp.imageset/kp@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kp.imageset/kp@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kr.imageset/kr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kr.imageset/kr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kw.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kw.imageset/kw@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kw.imageset/kw@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ky.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ky.imageset/ky@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ky.imageset/ky@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kz.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kz.imageset/kz@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/kz.imageset/kz@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/la.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/la.imageset/la@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/la.imageset/la@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lb.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lb.imageset/lb@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lb.imageset/lb@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lc.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lc.imageset/lc@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lc.imageset/lc@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/li.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/li.imageset/li@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/li.imageset/li@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lk.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lk.imageset/lk@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lk.imageset/lk@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lr.imageset/lr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lr.imageset/lr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ls.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ls.imageset/ls@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ls.imageset/ls@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lt.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lt.imageset/lt@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lt.imageset/lt@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lu.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lu.imageset/lu@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lu.imageset/lu@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lv.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lv.imageset/lv@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/lv.imageset/lv@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ly.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ly.imageset/ly@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ly.imageset/ly@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ma.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ma.imageset/ma@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ma.imageset/ma@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mc.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mc.imageset/mc@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mc.imageset/mc@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/md.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/md.imageset/md@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/md.imageset/md@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/me.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/me.imageset/me@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/me.imageset/me@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mf.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mf.imageset/mf@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mf.imageset/mf@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mg.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mg.imageset/mg@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mg.imageset/mg@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mh.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mh.imageset/mh@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mh.imageset/mh@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mk.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mk.imageset/mk@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mk.imageset/mk@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ml.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ml.imageset/ml@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ml.imageset/ml@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mm.imageset/mm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mm.imageset/mm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mn.imageset/mn@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mn.imageset/mn@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mo.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mo.imageset/mo@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mo.imageset/mo@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mp.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mp.imageset/mp@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mp.imageset/mp@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mq.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mq.imageset/mq@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mq.imageset/mq@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mr.imageset/mr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mr.imageset/mr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ms.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ms.imageset/ms@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ms.imageset/ms@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mt.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mt.imageset/mt@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mt.imageset/mt@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mu.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mu.imageset/mu@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mu.imageset/mu@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mv.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mv.imageset/mv@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mv.imageset/mv@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mw.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mw.imageset/mw@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mw.imageset/mw@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mx.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mx.imageset/mx@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mx.imageset/mx@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/my.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/my.imageset/my@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/my.imageset/my@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mz.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mz.imageset/mz@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/mz.imageset/mz@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/na.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/na.imageset/na@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/na.imageset/na@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nc.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nc.imageset/nc@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nc.imageset/nc@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ne.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ne.imageset/ne@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ne.imageset/ne@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nf.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nf.imageset/nf@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nf.imageset/nf@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ng.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ng.imageset/ng@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ng.imageset/ng@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ni.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ni.imageset/ni@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ni.imageset/ni@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nl.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nl.imageset/nl@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nl.imageset/nl@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/no.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/no.imageset/no@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/no.imageset/no@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/np.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/np.imageset/np@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/np.imageset/np@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nr.imageset/nr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nr.imageset/nr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nu.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nu.imageset/nu@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nu.imageset/nu@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nz.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nz.imageset/nz@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/nz.imageset/nz@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/om.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/om.imageset/om@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/om.imageset/om@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pa.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pa.imageset/pa@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pa.imageset/pa@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pe.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pe.imageset/pe@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pe.imageset/pe@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pf.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pf.imageset/pf@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pf.imageset/pf@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pg.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pg.imageset/pg@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pg.imageset/pg@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ph.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ph.imageset/ph@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ph.imageset/ph@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pk.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pk.imageset/pk@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pk.imageset/pk@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pl.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pl.imageset/pl@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pl.imageset/pl@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pm.imageset/pm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pm.imageset/pm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pn.imageset/pn@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pn.imageset/pn@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pr.imageset/pr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pr.imageset/pr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ps.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ps.imageset/ps@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ps.imageset/ps@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pt.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pt.imageset/pt@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pt.imageset/pt@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pw.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pw.imageset/pw@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/pw.imageset/pw@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/py.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/py.imageset/py@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/py.imageset/py@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/qa.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/qa.imageset/qa@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/qa.imageset/qa@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/re.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/re.imageset/re@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/re.imageset/re@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ro.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ro.imageset/ro@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ro.imageset/ro@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/rs.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/rs.imageset/rs@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/rs.imageset/rs@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ru.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ru.imageset/ru@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ru.imageset/ru@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/rw.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/rw.imageset/rw@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/rw.imageset/rw@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sa.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sa.imageset/sa@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sa.imageset/sa@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sb.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sb.imageset/sb@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sb.imageset/sb@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sc.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sc.imageset/sc@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sc.imageset/sc@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sd.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sd.imageset/sd@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sd.imageset/sd@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/se.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/se.imageset/se@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/se.imageset/se@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sg.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sg.imageset/sg@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sg.imageset/sg@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sh.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sh.imageset/sh@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sh.imageset/sh@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/si.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/si.imageset/si@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/si.imageset/si@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sj.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sj.imageset/sj@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sj.imageset/sj@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sk.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sk.imageset/sk@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sk.imageset/sk@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sl.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sl.imageset/sl@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sl.imageset/sl@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sm.imageset/sm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sm.imageset/sm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sn.imageset/sn@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sn.imageset/sn@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/so.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/so.imageset/so@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/so.imageset/so@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sr.imageset/sr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sr.imageset/sr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ss.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ss.imageset/ss@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ss.imageset/ss@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/st.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/st.imageset/st@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/st.imageset/st@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sv.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sv.imageset/sv@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sv.imageset/sv@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sx.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sx.imageset/sx@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sx.imageset/sx@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sy.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sy.imageset/sy@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sy.imageset/sy@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sz.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sz.imageset/sz@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/sz.imageset/sz@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tc.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tc.imageset/tc@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tc.imageset/tc@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/td.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/td.imageset/td@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/td.imageset/td@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tf.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tf.imageset/tf@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tf.imageset/tf@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tg.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tg.imageset/tg@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tg.imageset/tg@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/th.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/th.imageset/th@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/th.imageset/th@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tj.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tj.imageset/tj@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tj.imageset/tj@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tk.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tk.imageset/tk@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tk.imageset/tk@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tl.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tl.imageset/tl@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tl.imageset/tl@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tm.imageset/tm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tm.imageset/tm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tn.imageset/tn@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tn.imageset/tn@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/to.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/to.imageset/to@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/to.imageset/to@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tr.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tr.imageset/tr@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tr.imageset/tr@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tt.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tt.imageset/tt@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tt.imageset/tt@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tv.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tv.imageset/tv@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tv.imageset/tv@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tw.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tw.imageset/tw@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tw.imageset/tw@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tz.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tz.imageset/tz@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/tz.imageset/tz@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ua.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ua.imageset/ua@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ua.imageset/ua@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ug.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ug.imageset/ug@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ug.imageset/ug@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/um.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/um.imageset/um@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/um.imageset/um@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/un.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/un.imageset/un@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/un.imageset/un@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/us.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/us.imageset/us@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/us.imageset/us@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/uy.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/uy.imageset/uy@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/uy.imageset/uy@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/uz.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/uz.imageset/uz@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/uz.imageset/uz@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/va.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/va.imageset/va@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/va.imageset/va@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vc.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vc.imageset/vc@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vc.imageset/vc@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ve.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ve.imageset/ve@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ve.imageset/ve@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vg.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vg.imageset/vg@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vg.imageset/vg@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vi.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vi.imageset/vi@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vi.imageset/vi@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vn.imageset/vn@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vn.imageset/vn@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vu.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vu.imageset/vu@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/vu.imageset/vu@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/wf.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/wf.imageset/wf@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/wf.imageset/wf@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ws.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ws.imageset/ws@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ws.imageset/ws@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/xk.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/xk.imageset/xk@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/xk.imageset/xk@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ye.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ye.imageset/ye@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/ye.imageset/ye@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/yt.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/yt.imageset/yt@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/yt.imageset/yt@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/za.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/za.imageset/za@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/za.imageset/za@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/zm.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/zm.imageset/zm@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/zm.imageset/zm@3x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/zw.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Flags.xcassets/zw.imageset/zw@2x.png delete mode 100644 Passepartout/App/macOS/Flags.xcassets/zw.imageset/zw@3x.png delete mode 100644 Passepartout/App/macOS/Global/Credits.html delete mode 100644 Passepartout/App/macOS/Global/HostImporter.swift delete mode 100644 Passepartout/App/macOS/Global/IssueReporter.swift delete mode 100644 Passepartout/App/macOS/Global/Macros.swift delete mode 100644 Passepartout/App/macOS/Global/SwiftGen+Assets.swift delete mode 100644 Passepartout/App/macOS/Global/SwiftGen+Scenes.swift delete mode 100644 Passepartout/App/macOS/Global/SwiftGen+Segues.swift delete mode 100644 Passepartout/App/macOS/Global/TextInputViewController.swift delete mode 100644 Passepartout/App/macOS/Global/Theme+Views.swift delete mode 100644 Passepartout/App/macOS/Global/Theme.swift delete mode 100644 Passepartout/App/macOS/Global/WindowManager.swift delete mode 100644 Passepartout/App/macOS/Info.plist delete mode 100644 Passepartout/App/macOS/Launcher/AppDelegate.swift delete mode 100644 Passepartout/App/macOS/Launcher/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 Passepartout/App/macOS/Launcher/Base.lproj/Main.storyboard delete mode 100644 Passepartout/App/macOS/Launcher/Info.plist delete mode 100644 Passepartout/App/macOS/Launcher/Launcher.entitlements delete mode 100644 Passepartout/App/macOS/Menu/MainMenu.xib delete mode 100644 Passepartout/App/macOS/Menu/StatusMenu.swift delete mode 100644 Passepartout/App/macOS/Providers.xcassets/csv.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/csv.imageset/csv@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/csv.imageset/csv@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/hideme.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/hideme.imageset/hideme@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/hideme.imageset/hideme@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/mullvad.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/mullvad.imageset/mullvad@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/mullvad.imageset/mullvad@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/nordvpn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/nordvpn.imageset/nordvpn-dark@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/nordvpn.imageset/nordvpn-dark@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/nordvpn.imageset/nordvpn@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/nordvpn.imageset/nordvpn@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/oeck.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/oeck.imageset/oeck-dark@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/oeck.imageset/oeck-dark@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/oeck.imageset/oeck@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/oeck.imageset/oeck@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/pia.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/pia.imageset/pia@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/pia.imageset/pia@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/placeholder.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/placeholder.imageset/placeholder@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/placeholder.imageset/placeholder@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/protonvpn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/protonvpn.imageset/protonvpn@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/protonvpn.imageset/protonvpn@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/surfshark.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/surfshark.imageset/surfshark@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/surfshark.imageset/surfshark@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/torguard.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/torguard.imageset/torguard@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/torguard.imageset/torguard@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/tunnelbear.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/tunnelbear.imageset/tunnelbear@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/tunnelbear.imageset/tunnelbear@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/vyprvpn.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/vyprvpn.imageset/vyprvpn@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/vyprvpn.imageset/vyprvpn@3x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/windscribe.imageset/Contents.json delete mode 100644 Passepartout/App/macOS/Providers.xcassets/windscribe.imageset/windscribe@2x.png delete mode 100644 Passepartout/App/macOS/Providers.xcassets/windscribe.imageset/windscribe@3x.png delete mode 100644 Passepartout/App/macOS/Scenes/OrganizerProfileTableView.swift delete mode 100644 Passepartout/App/macOS/Scenes/OrganizerProfileTableView.xib delete mode 100644 Passepartout/App/macOS/Scenes/OrganizerViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Preferences/DebugLogViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Preferences/PreferencesGeneralViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Purchase/PurchaseProductView.swift delete mode 100644 Passepartout/App/macOS/Scenes/Purchase/PurchaseViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/AccountViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/Customization/ConfigurationViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/Customization/DNSViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/Customization/DefaultGatewayViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/Customization/EndpointViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/Customization/MTUViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/Customization/ProfileCustomizationViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/Customization/ProxyViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/Customization/TrustedNetworksAddViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/Customization/TrustedNetworksViewController.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/HostServiceView.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/HostServiceView.xib delete mode 100644 Passepartout/App/macOS/Scenes/Service/ProviderServiceView.swift delete mode 100644 Passepartout/App/macOS/Scenes/Service/ProviderServiceView.xib delete mode 100644 Passepartout/App/macOS/Scenes/Service/ServiceViewController.swift delete mode 100644 Passepartout/App/macOS/Tables/TextTableView.swift delete mode 100644 Passepartout/App/macOS/Tables/TextTableView.xib delete mode 100644 Passepartout/App/macOS/swiftgen.yml create mode 100644 Passepartout/Shared/Constants.swift create mode 100644 Passepartout/Tunnel/OpenVPN/Constants+Tunnel.swift rename Passepartout/Tunnel/{ => OpenVPN}/PacketTunnelProvider.swift (78%) delete mode 100644 Passepartout/Tunnel/Tunnel-iOS.entitlements rename Passepartout/Tunnel/{Tunnel-macOS.entitlements => Tunnel.entitlements} (88%) rename Passepartout/{App/macOS/Menu/MainMenu.swift => Tunnel/WireGuard/PacketTunnelProvider.swift} (79%) rename PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/{PassepartoutOpenVPNTunnel.xcscheme => OpenVPNAppExtension.xcscheme} (83%) create mode 100644 PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCore-Package.xcscheme create mode 100644 PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutProfilesTests.xcscheme create mode 100644 PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutProvidersTests.xcscheme create mode 100644 PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutServicesTests.xcscheme rename PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/{PassepartoutCoreTests.xcscheme => PassepartoutUtilsTests.xcscheme} (88%) create mode 100644 PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/WireGuardAppExtension.xcscheme rename PassepartoutCore/Sources/{PassepartoutOpenVPNTunnel => OpenVPNAppExtension}/Exports.swift (100%) delete mode 100644 PassepartoutCore/Sources/PassepartoutConstants/AppConstants.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutConstants/GroupConstants.swift delete mode 160000 PassepartoutCore/Sources/PassepartoutCore/API delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/AppConstants+Core.swift rename PassepartoutCore/Sources/PassepartoutCore/{Model/DebugLog.swift => Extensions/DebugLog+Decorated.swift} (70%) create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Extensions/OnDemand+Rules.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Extensions/OpenVPNSettings+VPNConfiguration.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Extensions/WireGuardSettings+VPNConfiguration.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Issue.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Managers/AppManager+Migrations.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Managers/AppManager.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Managers/PersistenceManager.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager+Actions.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager+Configuration.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy+Mock.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy+TunnelKit.swift rename PassepartoutCore/Sources/PassepartoutCore/{Model/Preferences.swift => Managers/VPNManagerStrategy.swift} (62%) delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionProfile.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService+Configurations.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService+Migration.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/DataUnit.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/GracefulVPN.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/LocalProduct+Infrastructure.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/ProfileNetworkSettings.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/HostConnectionProfile.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/PlaceholderConnectionProfile.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/ProfileKey.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/ProviderConnectionProfile.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Model/TransientStore.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Models/AppPreferences.swift rename Passepartout/App/iOS/Scenes/ConfigurationModificationDelegate.swift => PassepartoutCore/Sources/PassepartoutCore/Models/DebugLog.swift (74%) create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Models/VPNConfiguration.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/Models/VPNManager+ObservableState.swift create mode 100644 PassepartoutCore/Sources/PassepartoutCore/PassepartoutCore.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/de.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/el.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/en.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/es.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/fr.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/it.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/nl.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/pl.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/pt.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/ru.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/sv.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Resources/zh-Hans.lproj/Core.strings delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure+External.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Services/InfrastructureFactory.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Services/InfrastructurePreset.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Services/Pool.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Services/PoolGroup.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Services/WebServices.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/SwiftGen+Strings.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/UI/TrustedNetworksUI.swift delete mode 100644 PassepartoutCore/Sources/PassepartoutCore/Utils.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataClass.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataProperties.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Network.swift rename Passepartout/App/macOS/Scenes/Preferences/PreferencesViewController.swift => PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Account.swift (60%) create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Header.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Host.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+NetworkSettings.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+OnDemand.swift rename PassepartoutCore/Sources/{PassepartoutCore/VPN+Shared.swift => PassepartoutProfiles/DataModels/Profile+OpenVPNSettings.swift} (65%) create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Provider.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+WireGuardSettings.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Host+Extensions.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/NetworkSettings+Extensions.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/OnDemand+Extensions.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/OpenVPNSettings+Network.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+Logging.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+StrippableContent.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProviders+TunnelKit.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Profile+Extensions.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Provider+Extensions.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/ProviderManager+Extensions.swift rename PassepartoutCore/Sources/{PassepartoutCore/Model/SessionProxy+Communication.swift => PassepartoutProfiles/Extensions/VPNProtocolType+Network.swift} (55%) create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Extensions/WireGuardSettings+Network.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager+Keychain.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager+Processing.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy+CoreData.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift rename PassepartoutCore/Sources/{PassepartoutCore/Model/TrustedNetworks.swift => PassepartoutProfiles/Models/ObservableProfile.swift} (73%) create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/PassepartoutProfiles.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Pickers/Picker+Network.swift rename PassepartoutCore/Sources/{PassepartoutCore/Model/OpenVPNOptions.swift => PassepartoutProfiles/Pickers/Picker+OpenVPN.swift} (98%) create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Profiles.xcdatamodeld/Model.xcdatamodel/contents create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Repositories/ProfileMapper.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataClass.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataProperties.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataClass.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataProperties.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataClass.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataProperties.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataClass.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataProperties.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataClass.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataProperties.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataClass.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataProperties.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataClass.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataProperties.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Identifiable.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Logging.swift rename PassepartoutCore/Sources/{PassepartoutCore/Services/Infrastructure+CredentialsPurpose.swift => PassepartoutProviders/Extensions/ProviderName+Credentials.swift} (75%) create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Extensions/VPNProtocolType+Extensions.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Managers/ProviderManager.swift rename PassepartoutCore/Sources/{PassepartoutCore/Model/TrustPolicy.swift => PassepartoutProviders/Models/CredentialsPurpose.swift} (85%) rename PassepartoutCore/Sources/{PassepartoutCore/Model/Profiles/PoolCategory.swift => PassepartoutProviders/Models/ProviderCategory.swift} (72%) create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderLocation.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderMetadata.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderName.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderServer.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Models/VPNProtocolType.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/PassepartoutProviders.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Providers.xcdatamodeld/Model.xcdatamodel/contents create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Repositories/CategoryMapper.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Repositories/DefaultSettingsMapper.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Repositories/InfrastructureMapper.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Repositories/InfrastructureRepository.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Repositories/LocationMapper.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Repositories/PresetMapper.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Repositories/ProviderMapper.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Repositories/ProviderRepository.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Repositories/ServerMapper.swift create mode 100644 PassepartoutCore/Sources/PassepartoutProviders/Repositories/ServerRepository.swift create mode 160000 PassepartoutCore/Sources/PassepartoutServices/API rename PassepartoutCore/Sources/{PassepartoutCore/Model/Credentials.swift => PassepartoutServices/DataModels/WSProviderCategory.swift} (63%) create mode 100644 PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderInfrastructure.swift create mode 100644 PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderLocation.swift create mode 100644 PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderName.swift create mode 100644 PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderPreset.swift create mode 100644 PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderServer.swift rename PassepartoutCore/Sources/{PassepartoutCore/Services/Infrastructure+Metadata.swift => PassepartoutServices/DataModels/WSProvidersIndex.swift} (54%) create mode 100644 PassepartoutCore/Sources/PassepartoutServices/DataModels/WSVPNProtocol.swift create mode 100644 PassepartoutCore/Sources/PassepartoutServices/DefaultWebServices.swift rename PassepartoutCore/Sources/{PassepartoutConstants/InfrastructureName.swift => PassepartoutServices/Extensions/WSProviderName+Extensions.swift} (92%) create mode 100644 PassepartoutCore/Sources/PassepartoutServices/WebServices.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Exports.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/PassepartoutDataModels.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/PassepartoutError.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/FetchedValueHolder.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebEndpoint.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebParser.swift rename PassepartoutCore/Sources/{PassepartoutCore/Model/EndpointDataSource.swift => PassepartoutUtils/Reusable/GenericWebResponse.swift} (61%) create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebServices.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/KeyedCache.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/Mapper.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/Persistence.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/RateLimited.swift rename PassepartoutCore/Sources/{PassepartoutCore/ApplicationError.swift => PassepartoutUtils/Reusable/Repository.swift} (78%) create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/SSIDReader.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/StrippableContent.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Reusable/ValueHolder.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Async.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Codable.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+CoreData.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Dates.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+FileManager.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Logging.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Network.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Strings.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+URL.swift create mode 100644 PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils.swift create mode 100644 PassepartoutCore/Sources/WireGuardAppExtension/Exports.swift delete mode 100644 PassepartoutCore/Tests/PassepartoutCoreTests/ConnectionServiceTests.swift create mode 100644 PassepartoutCore/Tests/PassepartoutCoreTests/CoreTests.swift delete mode 100644 PassepartoutCore/Tests/PassepartoutCoreTests/InfrastructureTests.swift delete mode 100644 PassepartoutCore/Tests/PassepartoutCoreTests/Resources/ConnectionService.json delete mode 100644 PassepartoutCore/Tests/PassepartoutCoreTests/Resources/example.zip create mode 100644 PassepartoutCore/Tests/PassepartoutProfilesTests/ProfilesTests.swift create mode 100644 PassepartoutCore/Tests/PassepartoutProvidersTests/ProvidersTests.swift create mode 100644 PassepartoutCore/Tests/PassepartoutServicesTests/ServicesTests.swift create mode 100644 PassepartoutCore/Tests/PassepartoutUtilsTests/Resources/Debug.log rename PassepartoutCore/Tests/{PassepartoutCoreTests => PassepartoutUtilsTests}/UtilsTests.swift (70%) delete mode 100644 PassepartoutCore/swiftgen.yml diff --git a/.env.ios b/.env.ios index 6efce560..06f020ae 100644 --- a/.env.ios +++ b/.env.ios @@ -1,6 +1,6 @@ APP_ROOT="Passepartout/App/iOS" MATCH_PLATFORM="ios" -GYM_SCHEME="Passepartout-iOS" +GYM_SCHEME="Passepartout" SCAN_DEVICE="iPhone 12" DELIVER_PLATFORM="ios" PILOT_PLATFORM="ios" diff --git a/.env.mac b/.env.mac index e0c2151f..52bb83f9 100644 --- a/.env.mac +++ b/.env.mac @@ -1,7 +1,9 @@ APP_ROOT="Passepartout/App/macOS" -MATCH_PLATFORM="macos" +MATCH_PLATFORM="catalyst" MATCH_ADDITIONAL_CERT_TYPES="mac_installer_distribution" -GYM_SCHEME="Passepartout-macOS" -SCAN_DESTINATION="platform=macOS" +GYM_SCHEME="Passepartout" + +# not sure about these +SCAN_DESTINATION="platform=iOS" DELIVER_PLATFORM="osx" PILOT_PLATFORM="osx" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 00edfb5c..23648c20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: publish_to_app_store: name: Publish to App Store runs-on: ubuntu-latest - needs: [build_upload, submit_for_app_review] + needs: submit_for_app_review environment: name: app_store env: diff --git a/.gitignore b/.gitignore index cce41cc9..a2204cc7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ iap/ templates/ .env.secret* Preview.html -l10n passepartout-translations.zip default.profraw asc-key.json diff --git a/.gitmodules b/.gitmodules index cbac6047..36ce8f46 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "Submodules/fastlane-ci-templates"] path = Submodules/fastlane-ci-templates url = https://github.com/keeshux/fastlane-ci-templates -[submodule "PassepartoutCore/Sources/PassepartoutCore/API"] - path = PassepartoutCore/Sources/PassepartoutCore/API +[submodule "PassepartoutCore/Sources/PassepartoutServices/API"] + path = PassepartoutCore/Sources/PassepartoutServices/API url = https://github.com/passepartoutvpn/api diff --git a/Config.xcconfig b/Config.xcconfig index f03322c3..afc3a282 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -33,4 +33,6 @@ CFG_GROUP_ID = com.algoritmico.Passepartout CFG_APPSTORE_ID = 1433648537 CFG_COPYRIGHT = Copyright © 2022 Davide De Rosa. All rights reserved. +PATH = $(PATH):/opt/homebrew/bin:/usr/local/bin:/usr/local/go/bin + #include? "Secret.xcconfig" diff --git a/Gemfile.lock b/Gemfile.lock index 2c572048..c3f64b0b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,7 @@ GEM specs: CFPropertyList (3.0.5) rexml - activesupport (6.1.4.4) + activesupport (6.1.5) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -17,27 +17,27 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.552.0) - aws-sdk-core (3.126.0) + aws-partitions (1.570.0) + aws-sdk-core (3.130.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.54.0) - aws-sdk-core (~> 3, >= 3.126.0) + aws-sdk-kms (1.55.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.112.0) - aws-sdk-core (~> 3, >= 3.126.0) + aws-sdk-s3 (1.113.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) - cocoapods (1.11.2) + cocoapods (1.11.3) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.2) + cocoapods-core (= 1.11.3) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -52,7 +52,7 @@ GEM nap (~> 1.0) ruby-macho (>= 1.0, < 3.0) xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.2) + cocoapods-core (1.11.3) activesupport (>= 5.0, < 7) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -63,7 +63,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.5.1) + cocoapods-downloader (1.6.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -75,7 +75,7 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.1.9) + concurrent-ruby (1.1.10) declarative (0.0.20) digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) @@ -86,8 +86,8 @@ GEM escape (0.0.4) ethon (0.15.0) ffi (>= 1.15.0) - excon (0.91.0) - faraday (1.9.3) + excon (0.92.1) + faraday (1.10.0) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -116,7 +116,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.204.2) + fastlane (2.205.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -179,10 +179,10 @@ GEM google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.5.0) - faraday (>= 0.17.3, < 2.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.2.0) - google-cloud-storage (1.36.0) + google-cloud-storage (1.36.1) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) @@ -190,8 +190,8 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.1.0) - faraday (>= 0.17.3, < 2.0) + googleauth (1.1.2) + faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) @@ -201,9 +201,9 @@ GEM http-cookie (1.0.4) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.9.1) + i18n (1.10.0) concurrent-ruby (~> 1.0) - jmespath (1.5.0) + jmespath (1.6.1) json (2.6.1) jwt (2.3.0) memoist (0.16.2) @@ -233,9 +233,9 @@ GEM ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.16.0) + signet (0.16.1) addressable (~> 2.8) - faraday (>= 0.17.3, < 2.0) + faraday (>= 0.17.5, < 3.0) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simctl (1.6.8) @@ -256,7 +256,7 @@ GEM uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8) + unf_ext (0.0.8.1) unicode-display_width (1.8.0) webrick (1.7.0) word_wrap (1.0.0) diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index c4156423..5c466a2a 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -7,133 +7,122 @@ objects = { /* Begin PBXBuildFile section */ - 0E05C5D420D1645F006EE732 /* FieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E05C5CE20D139AF006EE732 /* FieldTableViewCell.swift */; }; - 0E05C5D520D1645F006EE732 /* SettingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED824CD20D12DBE00F2FE9E /* SettingTableViewCell.swift */; }; - 0E05C5D620D1645F006EE732 /* SwiftGen+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE8DE320C89028004C739C /* SwiftGen+Scenes.swift */; }; - 0E05C5D720D1645F006EE732 /* ToggleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED824C920D12B8700F2FE9E /* ToggleTableViewCell.swift */; }; - 0E05C61D20D27C82006EE732 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E05C61C20D27C82006EE732 /* Theme.swift */; }; + 0E0AD49027BD53CB00FBB520 /* ProfileView+Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0AD48F27BD53CB00FBB520 /* ProfileView+Welcome.swift */; }; + 0E0BD27327B2EA2C00583AC5 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27227B2EA2C00583AC5 /* MainView.swift */; }; + 0E0BD27627B2EB2200583AC5 /* DonateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27527B2EB2200583AC5 /* DonateView.swift */; }; + 0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27827B2EBE500583AC5 /* ShortcutsView.swift */; }; 0E0C0729236087A100155AAC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E0C072B236087A100155AAC /* InfoPlist.strings */; }; - 0E1066C920E0F84A004F98B7 /* Cells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1066C820E0F84A004F98B7 /* Cells.swift */; }; - 0E158ADA20E11B0B00C85A82 /* EndpointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E158AD920E11B0B00C85A82 /* EndpointViewController.swift */; }; - 0E1D72B2213BFFCF00BA1586 /* ProviderPresetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B1213BFFCF00BA1586 /* ProviderPresetViewController.swift */; }; - 0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */; }; - 0E24273A225950450064A1A3 /* About.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E24273C225950450064A1A3 /* About.storyboard */; }; - 0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E242741225956AC0064A1A3 /* DonationViewController.swift */; }; - 0E294AA125AE2B0A00CB4908 /* Descriptible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E294A8225AE29D100CB4908 /* Descriptible.swift */; }; - 0E294AA225AE2B0B00CB4908 /* Descriptible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E294A8225AE29D100CB4908 /* Descriptible.swift */; }; + 0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12BC8E27F62C8500B2F912 /* Validators.swift */; }; + 0E2A8D4927ADF87F00207D04 /* PassepartoutApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */; }; + 0E2A8D4F27B04BBA00207D04 /* OrganizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */; }; 0E2AC24522EC3AC10037B4B0 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */; }; - 0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B493F20FCFF990094784C /* Theme+Titles.swift */; }; - 0E3262D9235EE8DA00B5E470 /* HostImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3262D8235EE8DA00B5E470 /* HostImporter.swift */; }; - 0E3586FE225BD34800509A4D /* ActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3586FD225BD34800509A4D /* ActivityTableViewCell.swift */; }; - 0E36D24D2240234B006AF062 /* ShortcutsAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */; }; - 0E36D25822403469006AF062 /* Shortcuts.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E36D25A22403469006AF062 /* Shortcuts.storyboard */; }; - 0E36D25C224034AD006AF062 /* ShortcutsConnectToViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E36D25B224034AD006AF062 /* ShortcutsConnectToViewController.swift */; }; - 0E4B0D6B2366E3C100C890B4 /* PurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4B0D6A2366E3C000C890B4 /* PurchaseViewController.swift */; }; - 0E4B0D742366E6C800C890B4 /* Purchase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E4B0D762366E6C800C890B4 /* Purchase.storyboard */; }; - 0E4C9CBB20DCF0D600A0C59C /* DestructiveTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4C9CBA20DCF0D600A0C59C /* DestructiveTableViewCell.swift */; }; - 0E4FD7F120D58618002221FF /* Macros.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4FD7F020D58618002221FF /* Macros.swift */; }; - 0E52031D259F58BF00CBAB56 /* Providers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F7D259F41690022DFB8 /* Providers.xcassets */; }; - 0E52031E259F58BF00CBAB56 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F7F259F41690022DFB8 /* Assets.xcassets */; }; - 0E52031F259F58BF00CBAB56 /* Flags.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E569FA7259F41690022DFB8 /* Flags.xcassets */; }; - 0E520320259F58BF00CBAB56 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F8B259F41690022DFB8 /* AppDelegate.swift */; }; - 0E52032B259F58DD00CBAB56 /* TextTableView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F5A259F41690022DFB8 /* TextTableView.xib */; }; - 0E52032C259F58DD00CBAB56 /* TextTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F5B259F41690022DFB8 /* TextTableView.swift */; }; - 0E520333259F58F500CBAB56 /* OrganizerProfileTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F69259F41690022DFB8 /* OrganizerProfileTableView.swift */; }; - 0E520334259F58F500CBAB56 /* OrganizerProfileTableView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F6B259F41690022DFB8 /* OrganizerProfileTableView.xib */; }; - 0E520335259F58F500CBAB56 /* HostServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F7A259F41690022DFB8 /* HostServiceView.swift */; }; - 0E520336259F58F500CBAB56 /* HostServiceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F7B259F41690022DFB8 /* HostServiceView.xib */; }; - 0E520337259F58F500CBAB56 /* ProviderServiceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F79259F41690022DFB8 /* ProviderServiceView.xib */; }; - 0E520338259F58F500CBAB56 /* ProviderServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F6E259F41690022DFB8 /* ProviderServiceView.swift */; }; - 0E520339259F58F500CBAB56 /* ServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F6D259F41690022DFB8 /* ServiceViewController.swift */; }; - 0E52033A259F58F500CBAB56 /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F7C259F41690022DFB8 /* AccountViewController.swift */; }; - 0E52033B259F58F500CBAB56 /* OrganizerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F6A259F41690022DFB8 /* OrganizerViewController.swift */; }; - 0E520342259F58FE00CBAB56 /* EndpointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F71259F41690022DFB8 /* EndpointViewController.swift */; }; - 0E520343259F58FE00CBAB56 /* ProfileCustomizationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F73259F41690022DFB8 /* ProfileCustomizationViewController.swift */; }; - 0E520344259F58FE00CBAB56 /* TrustedNetworksAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F75259F41690022DFB8 /* TrustedNetworksAddViewController.swift */; }; - 0E520345259F58FE00CBAB56 /* DNSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F76259F41690022DFB8 /* DNSViewController.swift */; }; - 0E520346259F58FE00CBAB56 /* TrustedNetworksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F78259F41690022DFB8 /* TrustedNetworksViewController.swift */; }; - 0E520347259F58FE00CBAB56 /* ProxyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F74259F41690022DFB8 /* ProxyViewController.swift */; }; - 0E520348259F58FE00CBAB56 /* MTUViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F70259F41690022DFB8 /* MTUViewController.swift */; }; - 0E520349259F58FE00CBAB56 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F72259F41690022DFB8 /* ConfigurationViewController.swift */; }; - 0E52034A259F58FE00CBAB56 /* DefaultGatewayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F77259F41690022DFB8 /* DefaultGatewayViewController.swift */; }; - 0E520354259F590600CBAB56 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F67259F41690022DFB8 /* PreferencesViewController.swift */; }; - 0E520355259F590600CBAB56 /* DebugLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F66259F41690022DFB8 /* DebugLogViewController.swift */; }; - 0E520356259F590600CBAB56 /* PreferencesGeneralViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F68259F41690022DFB8 /* PreferencesGeneralViewController.swift */; }; - 0E52035D259F591300CBAB56 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F8A259F41690022DFB8 /* MainMenu.xib */; }; - 0E52035E259F591300CBAB56 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F89259F41690022DFB8 /* StatusMenu.swift */; }; - 0E52035F259F591300CBAB56 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F88259F41690022DFB8 /* MainMenu.swift */; }; - 0E520379259F593B00CBAB56 /* Macros.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569FA6259F41690022DFB8 /* Macros.swift */; }; - 0E52037A259F593B00CBAB56 /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F92259F41690022DFB8 /* WindowManager.swift */; }; - 0E52037B259F593B00CBAB56 /* IssueReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F8F259F41690022DFB8 /* IssueReporter.swift */; }; - 0E52037C259F593B00CBAB56 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569FA1259F41690022DFB8 /* Theme.swift */; }; - 0E52037E259F593B00CBAB56 /* SwiftGen+Segues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569FA5259F41690022DFB8 /* SwiftGen+Segues.swift */; }; - 0E52037F259F593B00CBAB56 /* Theme+Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F9A259F41690022DFB8 /* Theme+Views.swift */; }; - 0E520381259F593B00CBAB56 /* NSTextView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F94259F41690022DFB8 /* NSTextView+Search.swift */; }; - 0E520382259F593B00CBAB56 /* SwiftGen+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F9D259F41690022DFB8 /* SwiftGen+Scenes.swift */; }; - 0E520383259F593B00CBAB56 /* SwiftGen+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F98259F41690022DFB8 /* SwiftGen+Assets.swift */; }; - 0E520385259F593B00CBAB56 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = 0E569FA4259F41690022DFB8 /* Credits.html */; }; - 0E520387259F593B00CBAB56 /* TextInputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F97259F41690022DFB8 /* TextInputViewController.swift */; }; - 0E520388259F593B00CBAB56 /* HostImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F93259F41690022DFB8 /* HostImporter.swift */; }; - 0E5203B6259F5F3F00CBAB56 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ED31C3920CF39510027975F /* NetworkExtension.framework */; }; - 0E5203BE259F5F3F00CBAB56 /* PassepartoutTunnel.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0E5203B5259F5F3F00CBAB56 /* PassepartoutTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 0E52047B259F642600CBAB56 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F85259F41690022DFB8 /* Main.storyboard */; }; - 0E52047C259F642600CBAB56 /* Service.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F83259F41690022DFB8 /* Service.storyboard */; }; - 0E52047D259F642600CBAB56 /* Preferences.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F81259F41690022DFB8 /* Preferences.storyboard */; }; - 0E57F63C20C83FC5008323CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E57F63B20C83FC5008323CF /* AppDelegate.swift */; }; - 0E57F63E20C83FC5008323CF /* ServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E57F63D20C83FC5008323CF /* ServiceViewController.swift */; }; - 0E57F64120C83FC5008323CF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E57F63F20C83FC5008323CF /* Main.storyboard */; }; - 0E57F64320C83FC7008323CF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E57F64220C83FC7008323CF /* Assets.xcassets */; }; - 0E57F64620C83FC7008323CF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E57F64420C83FC7008323CF /* LaunchScreen.storyboard */; }; - 0E6268942369AD0600355F75 /* PurchaseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6268932369AD0600355F75 /* PurchaseTableViewCell.swift */; }; - 0E6BA54B25C9EE3A000CDFAC /* Purchase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E6BA54D25C9EE3A000CDFAC /* Purchase.storyboard */; }; - 0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6BE13E20CFBAB300A6DD36 /* DebugLogViewController.swift */; }; - 0E773BF8224BF37600CDDC8E /* ShortcutsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E773BF7224BF37600CDDC8E /* ShortcutsViewController.swift */; }; - 0E79D2C825C9F1B300D12964 /* PurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D2C725C9F1B300D12964 /* PurchaseViewController.swift */; }; - 0E79D31E25CC0CF600D12964 /* PurchaseProductView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D31D25CC0CF600D12964 /* PurchaseProductView.swift */; }; - 0E89DFCE213EEDFA00741BA1 /* WizardProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E89DFCD213EEDFA00741BA1 /* WizardProviderViewController.swift */; }; + 0E2C171B27CB5A3B007E8488 /* GenericCreditsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2C171A27CB5A3A007E8488 /* GenericCreditsView.swift */; }; + 0E2C172B27CB63F9007E8488 /* Reviewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2C172A27CB63F9007E8488 /* Reviewer.swift */; }; + 0E2DE71C27DCCFE80067B9E1 /* TunnelKit+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2DE71B27DCCFE80067B9E1 /* TunnelKit+Identifiable.swift */; }; + 0E2DE71F27DCD0290067B9E1 /* TunnelKit+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2DE71E27DCD0290067B9E1 /* TunnelKit+L10n.swift */; }; + 0E2DE72527DCDF550067B9E1 /* WireGuard+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2DE72427DCDF550067B9E1 /* WireGuard+L10n.swift */; }; + 0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */; }; + 0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */; }; + 0E34A2CF27CADA6300C73B67 /* GenericVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */; }; + 0E34AC7627F83FE20042F2AB /* OrganizerView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7527F83FE20042F2AB /* OrganizerView+VPN.swift */; }; + 0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */; }; + 0E34AC7A27F8431D0042F2AB /* OrganizerView+Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7927F8431D0042F2AB /* OrganizerView+Shortcuts.swift */; }; + 0E34AC7C27F845510042F2AB /* OrganizerView+Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7B27F845510042F2AB /* OrganizerView+Profiles.swift */; }; + 0E34AC7E27F849050042F2AB /* OrganizerView+AddProfileMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7D27F849050042F2AB /* OrganizerView+AddProfileMenu.swift */; }; + 0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; }; + 0E3B7FCD27E47B3700C66F13 /* AddHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FCC27E47B3700C66F13 /* AddHostView.swift */; }; + 0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */; }; + 0E3B7FDA27E51A0200C66F13 /* ProfileView+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */; }; + 0E44689627B051C300A14CE4 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E44689527B051C300A14CE4 /* ProfileView.swift */; }; + 0E44689C27B11B5300A14CE4 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E44689B27B11B5300A14CE4 /* AboutView.swift */; }; + 0E49F6BB27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49F6BA27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift */; }; + 0E49F6BD27D7639000385834 /* EndpointAdvancedView+WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49F6BC27D7639000385834 /* EndpointAdvancedView+WireGuard.swift */; }; + 0E49F6BF27D764AF00385834 /* EndpointAdvancedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49F6BE27D764AF00385834 /* EndpointAdvancedView.swift */; }; + 0E53249927D26B51002565C3 /* ProductManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E53249627D26B51002565C3 /* ProductManager.swift */; }; + 0E53249D27D28FC7002565C3 /* Kvitto in Frameworks */ = {isa = PBXBuildFile; productRef = 0E53249C27D28FC7002565C3 /* Kvitto */; }; + 0E5324A627D297BB002565C3 /* InApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5324A527D297BB002565C3 /* InApp.swift */; }; + 0E5324A927D2AC55002565C3 /* LongContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5324A827D2AC55002565C3 /* LongContentView.swift */; }; + 0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */; }; + 0E5349C627C176C200C71BB3 /* EndpointView+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */; }; + 0E5349C827C176D100C71BB3 /* EndpointView+WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5349C727C176D100C71BB3 /* EndpointView+WireGuard.swift */; }; + 0E53E63727E34FE2001D4902 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E53E63627E34FE2001D4902 /* AppContext.swift */; }; + 0E5683B927C2825D00EAF1CD /* DiagnosticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5683B827C2825D00EAF1CD /* DiagnosticsView.swift */; }; + 0E6059CB27FCC5DE003F4063 /* Flags.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E6059C827FCC5DD003F4063 /* Flags.xcassets */; }; + 0E6059CC27FCC5DE003F4063 /* Providers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E6059C927FCC5DE003F4063 /* Providers.xcassets */; }; + 0E6059CD27FCC5DE003F4063 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E6059CA27FCC5DE003F4063 /* Assets.xcassets */; }; + 0E6059CF27FCC618003F4063 /* SwiftGen+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6059CE27FCC618003F4063 /* SwiftGen+Assets.swift */; }; + 0E71ACDD27C0295C00F85C4B /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACDC27C0295B00F85C4B /* View+Extensions.swift */; }; + 0E71ACE327C0F2E400F85C4B /* Providers+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACE227C0F2E300F85C4B /* Providers+L10n.swift */; }; + 0E71ACE927C1055300F85C4B /* NetworkSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACE827C1055200F85C4B /* NetworkSettingsView.swift */; }; + 0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACEA27C1060D00F85C4B /* EndpointView.swift */; }; + 0E71ACEF27C106B500F85C4B /* ProviderPresetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACEE27C106B400F85C4B /* ProviderPresetView.swift */; }; + 0E71ACF127C1073800F85C4B /* ProviderLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACF027C1073800F85C4B /* ProviderLocationView.swift */; }; + 0E71ACF727C107CA00F85C4B /* DebugLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACF627C107C900F85C4B /* DebugLogView.swift */; }; + 0E71ACF927C12E4800F85C4B /* CreditsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACF827C12E4800F85C4B /* CreditsView.swift */; }; + 0E71ACFB27C12E5300F85C4B /* VersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACFA27C12E5300F85C4B /* VersionView.swift */; }; + 0E71ACFD27C1321A00F85C4B /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACFC27C1321A00F85C4B /* ActivityView.swift */; }; + 0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */; }; + 0E92D7C627F103300033CB7B /* ProfileView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */; }; + 0E92D7C927F1042A0033CB7B /* ProfileView+Extra.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */; }; + 0E92D7F427F104B80033CB7B /* ProfileView+Diagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7F327F104B80033CB7B /* ProfileView+Diagnostics.swift */; }; 0E9AA978259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */; }; - 0E9AA979259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */; }; - 0E9AAA7F259F7DB7003FAFF1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F5E259F41690022DFB8 /* Assets.xcassets */; }; - 0E9AAA80259F7DB7003FAFF1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E569F62259F41690022DFB8 /* AppDelegate.swift */; }; - 0E9AAA89259F7DCA003FAFF1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E569F5F259F41690022DFB8 /* Main.storyboard */; }; - 0E9AAABE259F7FFF003FAFF1 /* PassepartoutLauncher.app in Resources */ = {isa = PBXBuildFile; fileRef = 0E9AAA61259F7D7E003FAFF1 /* PassepartoutLauncher.app */; }; - 0E9AAACB259F808B003FAFF1 /* PassepartoutLauncher.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0E9AAA61259F7D7E003FAFF1 /* PassepartoutLauncher.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 0E9CD7872257462800D033B4 /* Providers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E9CD7862257462800D033B4 /* Providers.xcassets */; }; - 0E9CD789225746B300D033B4 /* Flags.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E9CD788225746B300D033B4 /* Flags.xcassets */; }; - 0E9CDB6723604AD5006733B4 /* ServerNetworkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9CDB6623604AD5006733B4 /* ServerNetworkViewController.swift */; }; - 0EA591132733DD4F0096F796 /* IntentDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA591122733DD4E0096F796 /* IntentDispatcher.swift */; }; + 0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9C232F27F47032007D5FC7 /* IntentsManager.swift */; }; + 0E9C233327F47E95007D5FC7 /* IntentDispatcher+Activities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9C233227F47E95007D5FC7 /* IntentDispatcher+Activities.swift */; }; + 0E9C3B6C27FB3A9C00D0F02E /* ReloadingSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9C3B6B27FB3A9C00D0F02E /* ReloadingSection.swift */; }; + 0E9C3B6F27FC573E00D0F02E /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E9C3B6E27FC573E00D0F02E /* CloudKit.framework */; }; + 0E9E5AEF27B44CF1008C95DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E9E5AE227B44CF1008C95DA /* Localizable.strings */; }; + 0E9ED48127FD9BAE003B2316 /* CopySavingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9ED48027FD9BAE003B2316 /* CopySavingButton.swift */; }; 0EA591162733DDDA0096F796 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 0EA591142733DDDA0096F796 /* Intents.intentdefinition */; }; - 0EA591302733E1420096F796 /* PassepartoutOpenVPNTunnel in Frameworks */ = {isa = PBXBuildFile; productRef = 0EA5912F2733E1420096F796 /* PassepartoutOpenVPNTunnel */; }; - 0EA591322733E1490096F796 /* PassepartoutOpenVPNTunnel in Frameworks */ = {isa = PBXBuildFile; productRef = 0EA591312733E1490096F796 /* PassepartoutOpenVPNTunnel */; }; - 0EB2B1482733FB6F007705AB /* PassepartoutTunnel.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0EDE8DBF20C86910004C739C /* PassepartoutTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */; }; - 0EB67D6B2184581E00BA6200 /* ImportedHostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */; }; - 0EBE3A79213C4E5500BFA2F5 /* OrganizerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBE3A78213C4E5400BFA2F5 /* OrganizerViewController.swift */; }; - 0ECC60DE2256B68A0020BEAC /* SwiftGen+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60DD2256B6890020BEAC /* SwiftGen+Assets.swift */; }; - 0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */; }; - 0ED31C2920CF2A340027975F /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED31C2820CF2A340027975F /* AccountViewController.swift */; }; - 0ED31C2C20CF2D6F0027975F /* ProviderPoolViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED31C2B20CF2D6F0027975F /* ProviderPoolViewController.swift */; }; + 0EB17EA727D226B400D473B5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA127D2263700D473B5 /* Constants.swift */; }; + 0EB17EA927D226C900D473B5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA127D2263700D473B5 /* Constants.swift */; }; + 0EB17EAA27D226C900D473B5 /* Constants+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA527D2263700D473B5 /* Constants+Extensions.swift */; }; + 0EB17EAE27D226CF00D473B5 /* LocalProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA327D2263700D473B5 /* LocalProduct.swift */; }; + 0EB17EBA27D2560300D473B5 /* PassepartoutProviders+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EB927D2560300D473B5 /* PassepartoutProviders+Extensions.swift */; }; + 0EB2B1482733FB6F007705AB /* PassepartoutOpenVPNTunnel.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0EDE8DBF20C86910004C739C /* PassepartoutOpenVPNTunnel.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 0EB3413027C7761A00483410 /* Binding+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB3412F27C7761A00483410 /* Binding+Extensions.swift */; }; + 0EB34BCA27C6A70200B126DA /* OnDemandView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB34BC927C6A70200B126DA /* OnDemandView.swift */; }; + 0EB34BCC27C6F41D00B126DA /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB34BCB27C6F41D00B126DA /* Theme.swift */; }; + 0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB4042B27CA0E8B00378B1A /* Unlocalized.swift */; }; + 0EB4042E27CA136300378B1A /* AddingTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB4042D27CA136200378B1A /* AddingTextField.swift */; }; + 0EBC074C27EB673C00208AD9 /* ProfileView+Rename.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC074B27EB673C00208AD9 /* ProfileView+Rename.swift */; }; + 0EBC075527EBC83800208AD9 /* MailComposerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC075427EBC83800208AD9 /* MailComposerView.swift */; }; + 0EBC075B27EC4FFF00208AD9 /* ReportIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC075A27EC4FFF00208AD9 /* ReportIssueView.swift */; }; + 0EBC075D27EC529000208AD9 /* DebugLog+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC075C27EC529000208AD9 /* DebugLog+Constants.swift */; }; + 0EBC076027EC587900208AD9 /* SwiftGen+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC075F27EC587900208AD9 /* SwiftGen+Strings.swift */; }; + 0ECF71EE27B6A99300CDB528 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECF71ED27B6A99300CDB528 /* AccountView.swift */; }; + 0ED1D6DC27DBA41700983466 /* DiagnosticsView+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED1D6DB27DBA41700983466 /* DiagnosticsView+OpenVPN.swift */; }; + 0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED1D6DD27DBA42100983466 /* DiagnosticsView+WireGuard.swift */; }; + 0ED2B33927D3C49800FD8EA9 /* OpenVPNAppExtension in Frameworks */ = {isa = PBXBuildFile; productRef = 0ED2B33827D3C49800FD8EA9 /* OpenVPNAppExtension */; }; + 0ED2B34527D3C77800FD8EA9 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ED31C3920CF39510027975F /* NetworkExtension.framework */; }; + 0ED2B35B27D3C94F00FD8EA9 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED2B35A27D3C94F00FD8EA9 /* PacketTunnelProvider.swift */; }; + 0ED2B36027D3C99100FD8EA9 /* PassepartoutWireGuardTunnel.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0ED2B34A27D3C77800FD8EA9 /* PassepartoutWireGuardTunnel.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 0ED2B36727D3C9A300FD8EA9 /* WireGuardAppExtension in Frameworks */ = {isa = PBXBuildFile; productRef = 0ED2B36627D3C9A300FD8EA9 /* WireGuardAppExtension */; }; + 0ED30DCC27EA197D0057D8A3 /* RevealingSecureField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED30DCB27EA197C0057D8A3 /* RevealingSecureField.swift */; }; + 0ED30DCF27EA1EF80057D8A3 /* PaywallView+Beta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED30DCE27EA1EF80057D8A3 /* PaywallView+Beta.swift */; }; + 0ED30DD227EA1F650057D8A3 /* PaywallView+Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED30DD127EA1F650057D8A3 /* PaywallView+Purchase.swift */; }; + 0ED30DDB27EA351C0057D8A3 /* Constants+Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED30DDA27EA351C0057D8A3 /* Constants+Tunnel.swift */; }; + 0ED30DDD27EA35230057D8A3 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA127D2263700D473B5 /* Constants.swift */; }; 0ED31C3A20CF39510027975F /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ED31C3920CF39510027975F /* NetworkExtension.framework */; }; - 0ED38AD9213F33150004D387 /* WizardHostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED38AD8213F33150004D387 /* WizardHostViewController.swift */; }; - 0ED38ADA213F44D00004D387 /* Organizer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0ED38ADC213F44D00004D387 /* Organizer.storyboard */; }; - 0ED38AEC2141260D0004D387 /* ConfigurationModificationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED38AEB2141260D0004D387 /* ConfigurationModificationDelegate.swift */; }; - 0EE3BBB2215ED3A900F30952 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE3BBB1215ED3A900F30952 /* AboutViewController.swift */; }; + 0ED89C1527DE0A0C008B36D6 /* Shortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1427DE0A0C008B36D6 /* Shortcut.swift */; }; + 0ED89C1727DE0E05008B36D6 /* IntentEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1627DE0E05008B36D6 /* IntentEditView.swift */; }; + 0ED89C1C27DE3ABC008B36D6 /* ShortcutsView+Add.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1B27DE3ABC008B36D6 /* ShortcutsView+Add.swift */; }; + 0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */; }; + 0ED89C2027DE423B008B36D6 /* ShortcutsView+ConnectTo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1F27DE423B008B36D6 /* ShortcutsView+ConnectTo.swift */; }; + 0ED89C2527DE45A3008B36D6 /* ProfileHeaderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */; }; + 0EDE02C227F61C79000FBE3C /* EditableTextList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */; }; + 0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE8B7E227FF340F00B68621 /* VPNProtocolType+FileExtensions.swift */; }; 0EED0BB92733CEDA00C9FC68 /* PassepartoutCore in Frameworks */ = {isa = PBXBuildFile; productRef = 0EED0BB82733CEDA00C9FC68 /* PassepartoutCore */; }; - 0EED0BBB2733CEE000C9FC68 /* PassepartoutCore in Frameworks */ = {isa = PBXBuildFile; productRef = 0EED0BBA2733CEE000C9FC68 /* PassepartoutCore */; }; - 0EF56BBB2185AC8500B0C8AB /* SwiftGen+Segues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */; }; - 0EFB901A2276D7F1006405E4 /* NetworkSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */; }; - 0EFD943E215BE10800529B64 /* IssueReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFD943D215BE10800529B64 /* IssueReporter.swift */; }; + 0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF0FAF527DD0211007EB181 /* PaywallView.swift */; }; + 0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA591122733DD4E0096F796 /* IntentDispatcher.swift */; }; + 0EF0FAF927DD212C007EB181 /* IntentActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF0FAF827DD212C007EB181 /* IntentActivity.swift */; }; + 0EF2212B27E667EA001D0BD7 /* AddProviderView+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212A27E667EA001D0BD7 /* AddProviderView+Name.swift */; }; + 0EF2212D27E66EB5001D0BD7 /* AddProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */; }; + 0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */; }; + 0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 0E5203BC259F5F3F00CBAB56 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 0E57F63020C83FC5008323CF /* Project object */; - proxyType = 1; - remoteGlobalIDString = 0E5203B4259F5F3F00CBAB56; - remoteInfo = "Passepartout-macOS-Tunnel"; - }; - 0E9AAABF259F7FFF003FAFF1 /* PBXContainerItemProxy */ = { + 0E6059C627FCC33D003F4063 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0E57F63020C83FC5008323CF /* Project object */; proxyType = 1; @@ -147,6 +136,27 @@ remoteGlobalIDString = 0EDE8DBE20C86910004C739C; remoteInfo = "PassepartoutTunnel-iOS"; }; + 0ECF71FB27B6DA6700CDB528 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0E57F63020C83FC5008323CF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0ECF71F327B6D9CD00CDB528; + remoteInfo = "PassepartoutWireGuard-iOS"; + }; + 0ED2B36127D3C99100FD8EA9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0E57F63020C83FC5008323CF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0ED2B33E27D3C77800FD8EA9; + remoteInfo = "WireGuardTunnel-iOS"; + }; + 0ED2B36A27D3CAB100FD8EA9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0E57F63020C83FC5008323CF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0ECF71F327B6D9CD00CDB528; + remoteInfo = "WireGuardGo-iOS"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -160,44 +170,14 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - 0E5203C2259F5F3F00CBAB56 /* Embed App Extensions */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 13; - files = ( - 0E5203BE259F5F3F00CBAB56 /* PassepartoutTunnel.appex in Embed App Extensions */, - ); - name = "Embed App Extensions"; - runOnlyForDeploymentPostprocessing = 0; - }; - 0E5203F6259F60D600CBAB56 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - 0E9AAACA259F806B003FAFF1 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = Contents/Library/LoginItems; - dstSubfolderSpec = 1; - files = ( - 0E9AAACB259F808B003FAFF1 /* PassepartoutLauncher.app in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 0EB2B14B2733FB6F007705AB /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( - 0EB2B1482733FB6F007705AB /* PassepartoutTunnel.appex in Embed App Extensions */, + 0ED2B36027D3C99100FD8EA9 /* PassepartoutWireGuardTunnel.appex in Embed App Extensions */, + 0EB2B1482733FB6F007705AB /* PassepartoutOpenVPNTunnel.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -205,101 +185,86 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0E05C5CE20D139AF006EE732 /* FieldTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldTableViewCell.swift; sourceTree = ""; }; - 0E05C61C20D27C82006EE732 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; + 0E0AD48F27BD53CB00FBB520 /* ProfileView+Welcome.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ProfileView+Welcome.swift"; sourceTree = ""; }; + 0E0BD27227B2EA2C00583AC5 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + 0E0BD27527B2EB2200583AC5 /* DonateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonateView.swift; sourceTree = ""; }; + 0E0BD27827B2EBE500583AC5 /* ShortcutsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsView.swift; sourceTree = ""; }; 0E0C072A236087A100155AAC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 0E0C072C236087C800155AAC /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 0E1066C820E0F84A004F98B7 /* Cells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cells.swift; sourceTree = ""; }; - 0E158AD920E11B0B00C85A82 /* EndpointViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EndpointViewController.swift; sourceTree = ""; }; + 0E12BC8E27F62C8500B2F912 /* Validators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = ""; }; 0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 0E1D72B1213BFFCF00BA1586 /* ProviderPresetViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProviderPresetViewController.swift; sourceTree = ""; }; - 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationViewController.swift; sourceTree = ""; }; 0E23B4A12298559800304C30 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; - 0E24273B225950450064A1A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/About.storyboard; sourceTree = ""; }; - 0E242741225956AC0064A1A3 /* DonationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationViewController.swift; sourceTree = ""; }; - 0E294A8225AE29D100CB4908 /* Descriptible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Descriptible.swift; sourceTree = ""; }; + 0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassepartoutApp.swift; sourceTree = ""; }; + 0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizerView.swift; sourceTree = ""; }; 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; - 0E2B493F20FCFF990094784C /* Theme+Titles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Titles.swift"; sourceTree = ""; }; - 0E3262D8235EE8DA00B5E470 /* HostImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostImporter.swift; sourceTree = ""; }; - 0E3586FD225BD34800509A4D /* ActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTableViewCell.swift; sourceTree = ""; }; - 0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsAddViewController.swift; sourceTree = ""; }; - 0E36D25B224034AD006AF062 /* ShortcutsConnectToViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConnectToViewController.swift; sourceTree = ""; }; - 0E4B0D6A2366E3C000C890B4 /* PurchaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseViewController.swift; sourceTree = ""; }; - 0E4B0D752366E6C800C890B4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Purchase.storyboard; sourceTree = ""; }; - 0E4C9CBA20DCF0D600A0C59C /* DestructiveTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestructiveTableViewCell.swift; sourceTree = ""; }; - 0E4FD7F020D58618002221FF /* Macros.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Macros.swift; sourceTree = ""; }; - 0E5202F7259F573500CBAB56 /* Passepartout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Passepartout.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 0E5203B5259F5F3F00CBAB56 /* PassepartoutTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - 0E569F5A259F41690022DFB8 /* TextTableView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TextTableView.xib; sourceTree = ""; }; - 0E569F5B259F41690022DFB8 /* TextTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextTableView.swift; sourceTree = ""; }; - 0E569F5E259F41690022DFB8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 0E569F60259F41690022DFB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 0E569F61259F41690022DFB8 /* Launcher.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Launcher.entitlements; sourceTree = ""; }; - 0E569F62259F41690022DFB8 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 0E569F63259F41690022DFB8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 0E569F66259F41690022DFB8 /* DebugLogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugLogViewController.swift; sourceTree = ""; }; - 0E569F67259F41690022DFB8 /* PreferencesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = ""; }; - 0E569F68259F41690022DFB8 /* PreferencesGeneralViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesGeneralViewController.swift; sourceTree = ""; }; - 0E569F69259F41690022DFB8 /* OrganizerProfileTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganizerProfileTableView.swift; sourceTree = ""; }; - 0E569F6A259F41690022DFB8 /* OrganizerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganizerViewController.swift; sourceTree = ""; }; - 0E569F6B259F41690022DFB8 /* OrganizerProfileTableView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OrganizerProfileTableView.xib; sourceTree = ""; }; - 0E569F6D259F41690022DFB8 /* ServiceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceViewController.swift; sourceTree = ""; }; - 0E569F6E259F41690022DFB8 /* ProviderServiceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProviderServiceView.swift; sourceTree = ""; }; - 0E569F70259F41690022DFB8 /* MTUViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTUViewController.swift; sourceTree = ""; }; - 0E569F71259F41690022DFB8 /* EndpointViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EndpointViewController.swift; sourceTree = ""; }; - 0E569F72259F41690022DFB8 /* ConfigurationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationViewController.swift; sourceTree = ""; }; - 0E569F73259F41690022DFB8 /* ProfileCustomizationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileCustomizationViewController.swift; sourceTree = ""; }; - 0E569F74259F41690022DFB8 /* ProxyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxyViewController.swift; sourceTree = ""; }; - 0E569F75259F41690022DFB8 /* TrustedNetworksAddViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrustedNetworksAddViewController.swift; sourceTree = ""; }; - 0E569F76259F41690022DFB8 /* DNSViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSViewController.swift; sourceTree = ""; }; - 0E569F77259F41690022DFB8 /* DefaultGatewayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultGatewayViewController.swift; sourceTree = ""; }; - 0E569F78259F41690022DFB8 /* TrustedNetworksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrustedNetworksViewController.swift; sourceTree = ""; }; - 0E569F79259F41690022DFB8 /* ProviderServiceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ProviderServiceView.xib; sourceTree = ""; }; - 0E569F7A259F41690022DFB8 /* HostServiceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostServiceView.swift; sourceTree = ""; }; - 0E569F7B259F41690022DFB8 /* HostServiceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HostServiceView.xib; sourceTree = ""; }; - 0E569F7C259F41690022DFB8 /* AccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = ""; }; - 0E569F7D259F41690022DFB8 /* Providers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Providers.xcassets; sourceTree = ""; }; - 0E569F7E259F41690022DFB8 /* App.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = ""; }; - 0E569F7F259F41690022DFB8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 0E569F82259F41690022DFB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Preferences.storyboard; sourceTree = ""; }; - 0E569F84259F41690022DFB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Service.storyboard; sourceTree = ""; }; - 0E569F86259F41690022DFB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 0E569F88259F41690022DFB8 /* MainMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = ""; }; - 0E569F89259F41690022DFB8 /* StatusMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = ""; }; - 0E569F8A259F41690022DFB8 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; - 0E569F8B259F41690022DFB8 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 0E569F8F259F41690022DFB8 /* IssueReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueReporter.swift; sourceTree = ""; }; - 0E569F92259F41690022DFB8 /* WindowManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = ""; }; - 0E569F93259F41690022DFB8 /* HostImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostImporter.swift; sourceTree = ""; }; - 0E569F94259F41690022DFB8 /* NSTextView+Search.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextView+Search.swift"; sourceTree = ""; }; - 0E569F97259F41690022DFB8 /* TextInputViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextInputViewController.swift; sourceTree = ""; }; - 0E569F98259F41690022DFB8 /* SwiftGen+Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Assets.swift"; sourceTree = ""; }; - 0E569F9A259F41690022DFB8 /* Theme+Views.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Theme+Views.swift"; sourceTree = ""; }; - 0E569F9D259F41690022DFB8 /* SwiftGen+Scenes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Scenes.swift"; sourceTree = ""; }; - 0E569FA1259F41690022DFB8 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; - 0E569FA4259F41690022DFB8 /* Credits.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Credits.html; sourceTree = ""; }; - 0E569FA5259F41690022DFB8 /* SwiftGen+Segues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Segues.swift"; sourceTree = ""; }; - 0E569FA6259F41690022DFB8 /* Macros.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Macros.swift; sourceTree = ""; }; - 0E569FA7259F41690022DFB8 /* Flags.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Flags.xcassets; sourceTree = ""; }; - 0E569FA8259F41690022DFB8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 0E569FAA259F41690022DFB8 /* Tunnel-macOS.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "Tunnel-macOS.entitlements"; sourceTree = ""; }; + 0E2C171A27CB5A3A007E8488 /* GenericCreditsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCreditsView.swift; sourceTree = ""; }; + 0E2C172A27CB63F9007E8488 /* Reviewer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reviewer.swift; sourceTree = ""; }; + 0E2DE71B27DCCFE80067B9E1 /* TunnelKit+Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelKit+Identifiable.swift"; sourceTree = ""; }; + 0E2DE71E27DCD0290067B9E1 /* TunnelKit+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelKit+L10n.swift"; sourceTree = ""; }; + 0E2DE72427DCDF550067B9E1 /* WireGuard+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WireGuard+L10n.swift"; sourceTree = ""; }; + 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenVPN+L10n.swift"; sourceTree = ""; }; + 0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Core+L10n.swift"; sourceTree = ""; }; + 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericVersionView.swift; sourceTree = ""; }; + 0E34AC7527F83FE20042F2AB /* OrganizerView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+VPN.swift"; sourceTree = ""; }; + 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Scene.swift"; sourceTree = ""; }; + 0E34AC7927F8431D0042F2AB /* OrganizerView+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Shortcuts.swift"; sourceTree = ""; }; + 0E34AC7B27F845510042F2AB /* OrganizerView+Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Profiles.swift"; sourceTree = ""; }; + 0E34AC7D27F849050042F2AB /* OrganizerView+AddProfileMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+AddProfileMenu.swift"; sourceTree = ""; }; + 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnDemandView+SSID.swift"; sourceTree = ""; }; + 0E3B7FCC27E47B3700C66F13 /* AddHostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddHostView.swift; sourceTree = ""; }; + 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = ""; }; + 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Provider.swift"; sourceTree = ""; }; + 0E44689527B051C300A14CE4 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; + 0E44689B27B11B5300A14CE4 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; + 0E49F6BA27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointAdvancedView+OpenVPN.swift"; sourceTree = ""; }; + 0E49F6BC27D7639000385834 /* EndpointAdvancedView+WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointAdvancedView+WireGuard.swift"; sourceTree = ""; }; + 0E49F6BE27D764AF00385834 /* EndpointAdvancedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointAdvancedView.swift; sourceTree = ""; }; + 0E53249627D26B51002565C3 /* ProductManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductManager.swift; sourceTree = ""; }; + 0E5324A527D297BB002565C3 /* InApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InApp.swift; sourceTree = ""; }; + 0E5324A827D2AC55002565C3 /* LongContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongContentView.swift; sourceTree = ""; }; + 0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyledPicker.swift; sourceTree = ""; }; + 0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointView+OpenVPN.swift"; sourceTree = ""; }; + 0E5349C727C176D100C71BB3 /* EndpointView+WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointView+WireGuard.swift"; sourceTree = ""; }; + 0E53E63627E34FE2001D4902 /* AppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = ""; }; + 0E5683B827C2825D00EAF1CD /* DiagnosticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsView.swift; sourceTree = ""; }; 0E57F63820C83FC5008323CF /* Passepartout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Passepartout.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 0E57F63B20C83FC5008323CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 0E57F63D20C83FC5008323CF /* ServiceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceViewController.swift; sourceTree = ""; }; - 0E57F64220C83FC7008323CF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0E57F64720C83FC7008323CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 0E6268932369AD0600355F75 /* PurchaseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseTableViewCell.swift; sourceTree = ""; }; - 0E6BA54C25C9EE3A000CDFAC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Purchase.storyboard; sourceTree = ""; }; - 0E6BE13E20CFBAB300A6DD36 /* DebugLogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugLogViewController.swift; sourceTree = ""; }; - 0E773BF7224BF37600CDDC8E /* ShortcutsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsViewController.swift; sourceTree = ""; }; - 0E79D2C725C9F1B300D12964 /* PurchaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseViewController.swift; sourceTree = ""; }; - 0E79D31D25CC0CF600D12964 /* PurchaseProductView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseProductView.swift; sourceTree = ""; }; - 0E89DFCD213EEDFA00741BA1 /* WizardProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardProviderViewController.swift; sourceTree = ""; }; + 0E6059C827FCC5DD003F4063 /* Flags.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Flags.xcassets; sourceTree = ""; }; + 0E6059C927FCC5DE003F4063 /* Providers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Providers.xcassets; sourceTree = ""; }; + 0E6059CA27FCC5DE003F4063 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 0E6059CE27FCC618003F4063 /* SwiftGen+Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Assets.swift"; sourceTree = ""; }; + 0E71ACDC27C0295B00F85C4B /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; + 0E71ACE227C0F2E300F85C4B /* Providers+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Providers+L10n.swift"; sourceTree = ""; }; + 0E71ACE827C1055200F85C4B /* NetworkSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettingsView.swift; sourceTree = ""; }; + 0E71ACEA27C1060D00F85C4B /* EndpointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointView.swift; sourceTree = ""; }; + 0E71ACEE27C106B400F85C4B /* ProviderPresetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderPresetView.swift; sourceTree = ""; }; + 0E71ACF027C1073800F85C4B /* ProviderLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderLocationView.swift; sourceTree = ""; }; + 0E71ACF627C107C900F85C4B /* DebugLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLogView.swift; sourceTree = ""; }; + 0E71ACF827C12E4800F85C4B /* CreditsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditsView.swift; sourceTree = ""; }; + 0E71ACFA27C12E5300F85C4B /* VersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionView.swift; sourceTree = ""; }; + 0E71ACFC27C1321A00F85C4B /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; + 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddHostViewModel.swift; sourceTree = ""; }; + 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Configuration.swift"; sourceTree = ""; }; + 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Extra.swift"; sourceTree = ""; }; + 0E92D7F327F104B80033CB7B /* ProfileView+Diagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Diagnostics.swift"; sourceTree = ""; }; 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; - 0E9AAA61259F7D7E003FAFF1 /* PassepartoutLauncher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PassepartoutLauncher.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 0E9CD7862257462800D033B4 /* Providers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Providers.xcassets; sourceTree = ""; }; - 0E9CD788225746B300D033B4 /* Flags.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Flags.xcassets; sourceTree = ""; }; - 0E9CDB6623604AD5006733B4 /* ServerNetworkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerNetworkViewController.swift; sourceTree = ""; }; + 0E9C232F27F47032007D5FC7 /* IntentsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentsManager.swift; sourceTree = ""; }; + 0E9C233227F47E95007D5FC7 /* IntentDispatcher+Activities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntentDispatcher+Activities.swift"; sourceTree = ""; }; + 0E9C3B6B27FB3A9C00D0F02E /* ReloadingSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadingSection.swift; sourceTree = ""; }; + 0E9C3B6E27FC573E00D0F02E /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; + 0E9E5AE327B44CF1008C95DA /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 0E9E5AE427B44CF1008C95DA /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; + 0E9E5AE527B44CF1008C95DA /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 0E9E5AE627B44CF1008C95DA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 0E9E5AE727B44CF1008C95DA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 0E9E5AE827B44CF1008C95DA /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + 0E9E5AE927B44CF1008C95DA /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 0E9E5AEA27B44CF1008C95DA /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; + 0E9E5AEB27B44CF1008C95DA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 0E9E5AEC27B44CF1008C95DA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 0E9E5AED27B44CF1008C95DA /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 0E9E5AEE27B44CF1008C95DA /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; + 0E9ED48027FD9BAE003B2316 /* CopySavingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopySavingButton.swift; sourceTree = ""; }; 0EA26DC827353020000F251A /* PassepartoutCore */ = {isa = PBXFileReference; lastKnownFileType = folder; path = PassepartoutCore; sourceTree = ""; }; 0EA591122733DD4E0096F796 /* IntentDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentDispatcher.swift; sourceTree = ""; }; 0EA591152733DDDA0096F796 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; @@ -315,8 +280,20 @@ 0EA5912A2733DDFC0096F796 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Intents.strings; sourceTree = ""; }; 0EA5912C2733DDFC0096F796 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Intents.strings; sourceTree = ""; }; 0EA5912E2733DDFD0096F796 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Intents.strings; sourceTree = ""; }; - 0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+Search.swift"; sourceTree = ""; }; - 0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportedHostsViewController.swift; sourceTree = ""; }; + 0EB17EA127D2263700D473B5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 0EB17EA327D2263700D473B5 /* LocalProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalProduct.swift; sourceTree = ""; }; + 0EB17EA527D2263700D473B5 /* Constants+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Constants+Extensions.swift"; sourceTree = ""; }; + 0EB17EB927D2560300D473B5 /* PassepartoutProviders+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PassepartoutProviders+Extensions.swift"; sourceTree = ""; }; + 0EB3412F27C7761A00483410 /* Binding+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Binding+Extensions.swift"; sourceTree = ""; }; + 0EB34BC927C6A70200B126DA /* OnDemandView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnDemandView.swift; sourceTree = ""; }; + 0EB34BCB27C6F41D00B126DA /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; + 0EB4042B27CA0E8B00378B1A /* Unlocalized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Unlocalized.swift; sourceTree = ""; }; + 0EB4042D27CA136200378B1A /* AddingTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddingTextField.swift; sourceTree = ""; }; + 0EBC074B27EB673C00208AD9 /* ProfileView+Rename.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Rename.swift"; sourceTree = ""; }; + 0EBC075427EBC83800208AD9 /* MailComposerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MailComposerView.swift; sourceTree = ""; }; + 0EBC075A27EC4FFF00208AD9 /* ReportIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportIssueView.swift; sourceTree = ""; }; + 0EBC075C27EC529000208AD9 /* DebugLog+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DebugLog+Constants.swift"; sourceTree = ""; }; + 0EBC075F27EC587900208AD9 /* SwiftGen+Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Strings.swift"; sourceTree = ""; }; 0EBE2FD02360F88C00F0D5AB /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 0EBE2FD12360F88E00F0D5AB /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; 0EBE2FD22360F88F00F0D5AB /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -326,62 +303,54 @@ 0EBE2FD62360F89500F0D5AB /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 0EBE2FD72360F89600F0D5AB /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 0EBE2FD82360F89600F0D5AB /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = ""; }; - 0EBE3A78213C4E5400BFA2F5 /* OrganizerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizerViewController.swift; sourceTree = ""; }; - 0ECC60DD2256B6890020BEAC /* SwiftGen+Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Assets.swift"; sourceTree = ""; }; - 0ECEB105224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 0ECEB106224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Organizer.storyboard; sourceTree = ""; }; - 0ECEB107224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Shortcuts.storyboard; sourceTree = ""; }; - 0ECEB108224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Cells.swift"; sourceTree = ""; }; - 0ED31C2820CF2A340027975F /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = ""; }; - 0ED31C2B20CF2D6F0027975F /* ProviderPoolViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderPoolViewController.swift; sourceTree = ""; }; + 0ECF71ED27B6A99300CDB528 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; + 0ED1D6DB27DBA41700983466 /* DiagnosticsView+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiagnosticsView+OpenVPN.swift"; sourceTree = ""; }; + 0ED1D6DD27DBA42100983466 /* DiagnosticsView+WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiagnosticsView+WireGuard.swift"; sourceTree = ""; }; + 0ED2B34A27D3C77800FD8EA9 /* PassepartoutWireGuardTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutWireGuardTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 0ED2B35A27D3C94F00FD8EA9 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; + 0ED30DCB27EA197C0057D8A3 /* RevealingSecureField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevealingSecureField.swift; sourceTree = ""; }; + 0ED30DCE27EA1EF80057D8A3 /* PaywallView+Beta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaywallView+Beta.swift"; sourceTree = ""; }; + 0ED30DD127EA1F650057D8A3 /* PaywallView+Purchase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaywallView+Purchase.swift"; sourceTree = ""; }; + 0ED30DDA27EA351C0057D8A3 /* Constants+Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Constants+Tunnel.swift"; sourceTree = ""; }; 0ED31C3920CF39510027975F /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; - 0ED31C3B20CF39510027975F /* Tunnel-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Tunnel-iOS.entitlements"; sourceTree = ""; }; - 0ED38AD8213F33150004D387 /* WizardHostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardHostViewController.swift; sourceTree = ""; }; - 0ED38AEB2141260D0004D387 /* ConfigurationModificationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationModificationDelegate.swift; sourceTree = ""; }; - 0ED824C920D12B8700F2FE9E /* ToggleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleTableViewCell.swift; sourceTree = ""; }; - 0ED824CD20D12DBE00F2FE9E /* SettingTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingTableViewCell.swift; sourceTree = ""; }; - 0EDE8DBF20C86910004C739C /* PassepartoutTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 0ED31C3B20CF39510027975F /* Tunnel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Tunnel.entitlements; sourceTree = ""; }; + 0ED89C1427DE0A0C008B36D6 /* Shortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcut.swift; sourceTree = ""; }; + 0ED89C1627DE0E05008B36D6 /* IntentEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentEditView.swift; sourceTree = ""; }; + 0ED89C1B27DE3ABC008B36D6 /* ShortcutsView+Add.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShortcutsView+Add.swift"; sourceTree = ""; }; + 0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentAddView.swift; sourceTree = ""; }; + 0ED89C1F27DE423B008B36D6 /* ShortcutsView+ConnectTo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShortcutsView+ConnectTo.swift"; sourceTree = ""; }; + 0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderRow.swift; sourceTree = ""; }; + 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextList.swift; sourceTree = ""; }; + 0EDE8DBF20C86910004C739C /* PassepartoutOpenVPNTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutOpenVPNTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 0EDE8DC320C86910004C739C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0EDE8DD220C86978004C739C /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; 0EDE8DE220C86A13004C739C /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = ""; }; - 0EDE8DE320C89028004C739C /* SwiftGen+Scenes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Scenes.swift"; sourceTree = ""; }; - 0EE3BBB1215ED3A900F30952 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; - 0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Segues.swift"; sourceTree = ""; }; - 0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettingsViewController.swift; sourceTree = ""; }; - 0EFD943D215BE10800529B64 /* IssueReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReporter.swift; sourceTree = ""; }; + 0EE8B7E227FF340F00B68621 /* VPNProtocolType+FileExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VPNProtocolType+FileExtensions.swift"; sourceTree = ""; }; + 0EF0FAF527DD0211007EB181 /* PaywallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallView.swift; sourceTree = ""; }; + 0EF0FAF827DD212C007EB181 /* IntentActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentActivity.swift; sourceTree = ""; }; + 0EF2212A27E667EA001D0BD7 /* AddProviderView+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddProviderView+Name.swift"; sourceTree = ""; }; + 0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProviderView.swift; sourceTree = ""; }; + 0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProfileView.swift; sourceTree = ""; }; + 0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProviderViewModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 0E5202F4259F573500CBAB56 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 0EED0BBB2733CEE000C9FC68 /* PassepartoutCore in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 0E5203B2259F5F3F00CBAB56 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 0EA591302733E1420096F796 /* PassepartoutOpenVPNTunnel in Frameworks */, - 0E5203B6259F5F3F00CBAB56 /* NetworkExtension.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 0E57F63520C83FC5008323CF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0E9C3B6F27FC573E00D0F02E /* CloudKit.framework in Frameworks */, 0EED0BB92733CEDA00C9FC68 /* PassepartoutCore in Frameworks */, + 0E53249D27D28FC7002565C3 /* Kvitto in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - 0E9AAA5E259F7D7E003FAFF1 /* Frameworks */ = { + 0ED2B34327D3C77800FD8EA9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0ED2B34527D3C77800FD8EA9 /* NetworkExtension.framework in Frameworks */, + 0ED2B36727D3C9A300FD8EA9 /* WireGuardAppExtension in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -389,7 +358,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 0EA591322733E1490096F796 /* PassepartoutOpenVPNTunnel in Frameworks */, + 0ED2B33927D3C49800FD8EA9 /* OpenVPNAppExtension in Frameworks */, 0ED31C3A20CF39510027975F /* NetworkExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -397,169 +366,122 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0E1066CA20E0F85C004F98B7 /* Cells */ = { + 0E0BD25527B130AD00583AC5 /* Views */ = { isa = PBXGroup; children = ( - 0E3586FD225BD34800509A4D /* ActivityTableViewCell.swift */, - 0E1066C820E0F84A004F98B7 /* Cells.swift */, - 0E4C9CBA20DCF0D600A0C59C /* DestructiveTableViewCell.swift */, - 0E05C5CE20D139AF006EE732 /* FieldTableViewCell.swift */, - 0ED824CD20D12DBE00F2FE9E /* SettingTableViewCell.swift */, - 0ED824C920D12B8700F2FE9E /* ToggleTableViewCell.swift */, + 0EC54E1927F214C9002522D0 /* AddProfile */, + 0EC54E1A27F214E7002522D0 /* Paywall */, + 0EC54E1B27F214F1002522D0 /* Profile */, + 0E44689B27B11B5300A14CE4 /* AboutView.swift */, + 0ECF71ED27B6A99300CDB528 /* AccountView.swift */, + 0E71ACF827C12E4800F85C4B /* CreditsView.swift */, + 0E71ACF627C107C900F85C4B /* DebugLogView.swift */, + 0E5683B827C2825D00EAF1CD /* DiagnosticsView.swift */, + 0ED1D6DB27DBA41700983466 /* DiagnosticsView+OpenVPN.swift */, + 0ED1D6DD27DBA42100983466 /* DiagnosticsView+WireGuard.swift */, + 0E0BD27527B2EB2200583AC5 /* DonateView.swift */, + 0E71ACEA27C1060D00F85C4B /* EndpointView.swift */, + 0E49F6BE27D764AF00385834 /* EndpointAdvancedView.swift */, + 0E49F6BA27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift */, + 0E49F6BC27D7639000385834 /* EndpointAdvancedView+WireGuard.swift */, + 0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */, + 0E5349C727C176D100C71BB3 /* EndpointView+WireGuard.swift */, + 0E0BD27227B2EA2C00583AC5 /* MainView.swift */, + 0E71ACE827C1055200F85C4B /* NetworkSettingsView.swift */, + 0EB34BC927C6A70200B126DA /* OnDemandView.swift */, + 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */, + 0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */, + 0E34AC7D27F849050042F2AB /* OrganizerView+AddProfileMenu.swift */, + 0E34AC7B27F845510042F2AB /* OrganizerView+Profiles.swift */, + 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */, + 0E34AC7927F8431D0042F2AB /* OrganizerView+Shortcuts.swift */, + 0E34AC7527F83FE20042F2AB /* OrganizerView+VPN.swift */, + 0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */, + 0E44689527B051C300A14CE4 /* ProfileView.swift */, + 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */, + 0E92D7F327F104B80033CB7B /* ProfileView+Diagnostics.swift */, + 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */, + 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */, + 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */, + 0E0AD48F27BD53CB00FBB520 /* ProfileView+Welcome.swift */, + 0E71ACF027C1073800F85C4B /* ProviderLocationView.swift */, + 0E71ACEE27C106B400F85C4B /* ProviderPresetView.swift */, + 0EBC075A27EC4FFF00208AD9 /* ReportIssueView.swift */, + 0E0BD27827B2EBE500583AC5 /* ShortcutsView.swift */, + 0ED89C1B27DE3ABC008B36D6 /* ShortcutsView+Add.swift */, + 0ED89C1F27DE423B008B36D6 /* ShortcutsView+ConnectTo.swift */, + 0E71ACFA27C12E5300F85C4B /* VersionView.swift */, ); - path = Cells; + path = Views; sourceTree = ""; }; - 0E24273D225950CC0064A1A3 /* About */ = { + 0E0BD26427B25EC800583AC5 /* Shared */ = { isa = PBXGroup; children = ( - 0EE3BBB1215ED3A900F30952 /* AboutViewController.swift */, + 0EB17EA027D2263700D473B5 /* Constants */, + 0E49F6C927DB398100385834 /* Extensions */, + 0E92781227E7CD530057BB81 /* InApp */, + 0EA591112733DD4E0096F796 /* Intents */, + 0E34A2B827CAA8EA00C73B67 /* L10n */, + 0E2C171C27CB6307007E8488 /* Reusable */, + 0E6059CA27FCC5DE003F4063 /* Assets.xcassets */, + 0E6059C827FCC5DD003F4063 /* Flags.xcassets */, + 0E6059C927FCC5DE003F4063 /* Providers.xcassets */, + 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */, + 0EDE8DE220C86A13004C739C /* App.entitlements */, + 0E57F64720C83FC7008323CF /* Info.plist */, + 0E0C072B236087A100155AAC /* InfoPlist.strings */, + 0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */, ); - path = About; + path = Shared; sourceTree = ""; }; - 0E24273E225950ED0064A1A3 /* Shortcuts */ = { + 0E2C171C27CB6307007E8488 /* Reusable */ = { isa = PBXGroup; children = ( - 0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */, - 0E36D25B224034AD006AF062 /* ShortcutsConnectToViewController.swift */, - 0E773BF7224BF37600CDDC8E /* ShortcutsViewController.swift */, + 0EB4042D27CA136200378B1A /* AddingTextField.swift */, + 0EB3412F27C7761A00483410 /* Binding+Extensions.swift */, + 0E9ED48027FD9BAE003B2316 /* CopySavingButton.swift */, + 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */, + 0E2C171A27CB5A3A007E8488 /* GenericCreditsView.swift */, + 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */, + 0E5324A527D297BB002565C3 /* InApp.swift */, + 0EF0FAF827DD212C007EB181 /* IntentActivity.swift */, + 0E5324A827D2AC55002565C3 /* LongContentView.swift */, + 0E9C3B6B27FB3A9C00D0F02E /* ReloadingSection.swift */, + 0ED30DCB27EA197C0057D8A3 /* RevealingSecureField.swift */, + 0E2C172A27CB63F9007E8488 /* Reviewer.swift */, + 0ED89C1427DE0A0C008B36D6 /* Shortcut.swift */, + 0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */, + 0E12BC8E27F62C8500B2F912 /* Validators.swift */, + 0E71ACDC27C0295B00F85C4B /* View+Extensions.swift */, ); - path = Shortcuts; + path = Reusable; sourceTree = ""; }; - 0E4B0D6C2366E53C00C890B4 /* Purchase */ = { + 0E34A2B827CAA8EA00C73B67 /* L10n */ = { isa = PBXGroup; children = ( - 0E6268932369AD0600355F75 /* PurchaseTableViewCell.swift */, - 0E4B0D6A2366E3C000C890B4 /* PurchaseViewController.swift */, + 0E9E5AE227B44CF1008C95DA /* Localizable.strings */, + 0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */, + 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */, + 0E71ACE227C0F2E300F85C4B /* Providers+L10n.swift */, + 0E2DE71E27DCD0290067B9E1 /* TunnelKit+L10n.swift */, + 0EB4042B27CA0E8B00378B1A /* Unlocalized.swift */, + 0E2DE72427DCDF550067B9E1 /* WireGuard+L10n.swift */, ); - path = Purchase; + path = L10n; sourceTree = ""; }; - 0E569F58259F41690022DFB8 /* macOS */ = { + 0E49F6C927DB398100385834 /* Extensions */ = { isa = PBXGroup; children = ( - 0E569F8C259F41690022DFB8 /* Global */, - 0E569F5C259F41690022DFB8 /* Launcher */, - 0E569F87259F41690022DFB8 /* Menu */, - 0E569F64259F41690022DFB8 /* Scenes */, - 0E569F59259F41690022DFB8 /* Tables */, - 0E569F7E259F41690022DFB8 /* App.entitlements */, - 0E569FA8259F41690022DFB8 /* Info.plist */, - 0E569F8B259F41690022DFB8 /* AppDelegate.swift */, - 0E569F7F259F41690022DFB8 /* Assets.xcassets */, - 0E569FA7259F41690022DFB8 /* Flags.xcassets */, - 0E569F7D259F41690022DFB8 /* Providers.xcassets */, - 0E569F85259F41690022DFB8 /* Main.storyboard */, - 0E569F81259F41690022DFB8 /* Preferences.storyboard */, - 0E6BA54D25C9EE3A000CDFAC /* Purchase.storyboard */, - 0E569F83259F41690022DFB8 /* Service.storyboard */, + 0EBC075C27EC529000208AD9 /* DebugLog+Constants.swift */, + 0EB17EB927D2560300D473B5 /* PassepartoutProviders+Extensions.swift */, + 0E2DE71B27DCCFE80067B9E1 /* TunnelKit+Identifiable.swift */, + 0EE8B7E227FF340F00B68621 /* VPNProtocolType+FileExtensions.swift */, ); - path = macOS; - sourceTree = ""; - }; - 0E569F59259F41690022DFB8 /* Tables */ = { - isa = PBXGroup; - children = ( - 0E569F5B259F41690022DFB8 /* TextTableView.swift */, - 0E569F5A259F41690022DFB8 /* TextTableView.xib */, - ); - path = Tables; - sourceTree = ""; - }; - 0E569F5C259F41690022DFB8 /* Launcher */ = { - isa = PBXGroup; - children = ( - 0E569F61259F41690022DFB8 /* Launcher.entitlements */, - 0E569F63259F41690022DFB8 /* Info.plist */, - 0E569F62259F41690022DFB8 /* AppDelegate.swift */, - 0E569F5E259F41690022DFB8 /* Assets.xcassets */, - 0E569F5F259F41690022DFB8 /* Main.storyboard */, - ); - path = Launcher; - sourceTree = ""; - }; - 0E569F64259F41690022DFB8 /* Scenes */ = { - isa = PBXGroup; - children = ( - 0E569F65259F41690022DFB8 /* Preferences */, - 0E6BA54125C9ED91000CDFAC /* Purchase */, - 0E569F6C259F41690022DFB8 /* Service */, - 0E569F69259F41690022DFB8 /* OrganizerProfileTableView.swift */, - 0E569F6A259F41690022DFB8 /* OrganizerViewController.swift */, - 0E569F6B259F41690022DFB8 /* OrganizerProfileTableView.xib */, - ); - path = Scenes; - sourceTree = ""; - }; - 0E569F65259F41690022DFB8 /* Preferences */ = { - isa = PBXGroup; - children = ( - 0E569F66259F41690022DFB8 /* DebugLogViewController.swift */, - 0E569F68259F41690022DFB8 /* PreferencesGeneralViewController.swift */, - 0E569F67259F41690022DFB8 /* PreferencesViewController.swift */, - ); - path = Preferences; - sourceTree = ""; - }; - 0E569F6C259F41690022DFB8 /* Service */ = { - isa = PBXGroup; - children = ( - 0E569F6F259F41690022DFB8 /* Customization */, - 0E569F7C259F41690022DFB8 /* AccountViewController.swift */, - 0E569F7A259F41690022DFB8 /* HostServiceView.swift */, - 0E569F6E259F41690022DFB8 /* ProviderServiceView.swift */, - 0E569F6D259F41690022DFB8 /* ServiceViewController.swift */, - 0E569F7B259F41690022DFB8 /* HostServiceView.xib */, - 0E569F79259F41690022DFB8 /* ProviderServiceView.xib */, - ); - path = Service; - sourceTree = ""; - }; - 0E569F6F259F41690022DFB8 /* Customization */ = { - isa = PBXGroup; - children = ( - 0E569F72259F41690022DFB8 /* ConfigurationViewController.swift */, - 0E569F77259F41690022DFB8 /* DefaultGatewayViewController.swift */, - 0E569F76259F41690022DFB8 /* DNSViewController.swift */, - 0E569F71259F41690022DFB8 /* EndpointViewController.swift */, - 0E569F70259F41690022DFB8 /* MTUViewController.swift */, - 0E569F73259F41690022DFB8 /* ProfileCustomizationViewController.swift */, - 0E569F74259F41690022DFB8 /* ProxyViewController.swift */, - 0E569F75259F41690022DFB8 /* TrustedNetworksAddViewController.swift */, - 0E569F78259F41690022DFB8 /* TrustedNetworksViewController.swift */, - ); - path = Customization; - sourceTree = ""; - }; - 0E569F87259F41690022DFB8 /* Menu */ = { - isa = PBXGroup; - children = ( - 0E569F88259F41690022DFB8 /* MainMenu.swift */, - 0E569F8A259F41690022DFB8 /* MainMenu.xib */, - 0E569F89259F41690022DFB8 /* StatusMenu.swift */, - ); - path = Menu; - sourceTree = ""; - }; - 0E569F8C259F41690022DFB8 /* Global */ = { - isa = PBXGroup; - children = ( - 0E569FA4259F41690022DFB8 /* Credits.html */, - 0E569F93259F41690022DFB8 /* HostImporter.swift */, - 0E569F8F259F41690022DFB8 /* IssueReporter.swift */, - 0E569FA6259F41690022DFB8 /* Macros.swift */, - 0E569F94259F41690022DFB8 /* NSTextView+Search.swift */, - 0E569F98259F41690022DFB8 /* SwiftGen+Assets.swift */, - 0E569F9D259F41690022DFB8 /* SwiftGen+Scenes.swift */, - 0E569FA5259F41690022DFB8 /* SwiftGen+Segues.swift */, - 0E569F97259F41690022DFB8 /* TextInputViewController.swift */, - 0E569FA1259F41690022DFB8 /* Theme.swift */, - 0E569F9A259F41690022DFB8 /* Theme+Views.swift */, - 0E569F92259F41690022DFB8 /* WindowManager.swift */, - ); - path = Global; + path = Extensions; sourceTree = ""; }; 0E57F62F20C83FC5008323CF = { @@ -577,10 +499,8 @@ isa = PBXGroup; children = ( 0E57F63820C83FC5008323CF /* Passepartout.app */, - 0EDE8DBF20C86910004C739C /* PassepartoutTunnel.appex */, - 0E5202F7259F573500CBAB56 /* Passepartout.app */, - 0E5203B5259F5F3F00CBAB56 /* PassepartoutTunnel.appex */, - 0E9AAA61259F7D7E003FAFF1 /* PassepartoutLauncher.app */, + 0EDE8DBF20C86910004C739C /* PassepartoutOpenVPNTunnel.appex */, + 0ED2B34A27D3C77800FD8EA9 /* PassepartoutWireGuardTunnel.appex */, ); name = Products; sourceTree = ""; @@ -588,54 +508,27 @@ 0E57F63A20C83FC5008323CF /* iOS */ = { isa = PBXGroup; children = ( - 0E1066CA20E0F85C004F98B7 /* Cells */, - 0EDE8DEC20C93E3B004C739C /* Global */, - 0EA591112733DD4E0096F796 /* Intents */, - 0EDE8DF120C93ED8004C739C /* Scenes */, - 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */, - 0EDE8DE220C86A13004C739C /* App.entitlements */, - 0E57F64720C83FC7008323CF /* Info.plist */, - 0E0C072B236087A100155AAC /* InfoPlist.strings */, - 0E57F63B20C83FC5008323CF /* AppDelegate.swift */, - 0E57F64220C83FC7008323CF /* Assets.xcassets */, - 0E9CD788225746B300D033B4 /* Flags.xcassets */, - 0E9CD7862257462800D033B4 /* Providers.xcassets */, - 0E24273C225950450064A1A3 /* About.storyboard */, - 0E57F64420C83FC7008323CF /* LaunchScreen.storyboard */, - 0E57F63F20C83FC5008323CF /* Main.storyboard */, - 0ED38ADC213F44D00004D387 /* Organizer.storyboard */, - 0E4B0D762366E6C800C890B4 /* Purchase.storyboard */, - 0E36D25A22403469006AF062 /* Shortcuts.storyboard */, - 0EA591142733DDDA0096F796 /* Intents.intentdefinition */, + 0EBC075327EBC7FC00208AD9 /* Reusable */, + 0ECF71E227B64FFC00CDB528 /* ViewModels */, + 0E0BD25527B130AD00583AC5 /* Views */, ); path = iOS; sourceTree = ""; }; - 0E6BA54125C9ED91000CDFAC /* Purchase */ = { + 0E92781227E7CD530057BB81 /* InApp */ = { isa = PBXGroup; children = ( - 0E79D31D25CC0CF600D12964 /* PurchaseProductView.swift */, - 0E79D2C725C9F1B300D12964 /* PurchaseViewController.swift */, + 0EB17EA327D2263700D473B5 /* LocalProduct.swift */, + 0E53249627D26B51002565C3 /* ProductManager.swift */, ); - path = Purchase; - sourceTree = ""; - }; - 0E89DFCC213EEDE700741BA1 /* Organizer */ = { - isa = PBXGroup; - children = ( - 0E242741225956AC0064A1A3 /* DonationViewController.swift */, - 0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */, - 0EBE3A78213C4E5400BFA2F5 /* OrganizerViewController.swift */, - 0ED38AD8213F33150004D387 /* WizardHostViewController.swift */, - 0E89DFCD213EEDFA00741BA1 /* WizardProviderViewController.swift */, - ); - path = Organizer; + path = InApp; sourceTree = ""; }; 0E9AA982259F7674003FAFF1 /* Passepartout */ = { isa = PBXGroup; children = ( 0E9AA983259F76C5003FAFF1 /* App */, + 0ED30DD627EA33220057D8A3 /* Shared */, 0EDE8DC020C86910004C739C /* Tunnel */, ); path = Passepartout; @@ -644,9 +537,8 @@ 0E9AA983259F76C5003FAFF1 /* App */ = { isa = PBXGroup; children = ( + 0E0BD26427B25EC800583AC5 /* Shared */, 0E57F63A20C83FC5008323CF /* iOS */, - 0E569F58259F41690022DFB8 /* macOS */, - 0E294A8225AE29D100CB4908 /* Descriptible.swift */, ); path = App; sourceTree = ""; @@ -654,60 +546,111 @@ 0EA591112733DD4E0096F796 /* Intents */ = { isa = PBXGroup; children = ( + 0EA591142733DDDA0096F796 /* Intents.intentdefinition */, 0EA591122733DD4E0096F796 /* IntentDispatcher.swift */, + 0E9C233227F47E95007D5FC7 /* IntentDispatcher+Activities.swift */, + 0E9C232F27F47032007D5FC7 /* IntentsManager.swift */, ); path = Intents; sourceTree = ""; }; + 0EB17EA027D2263700D473B5 /* Constants */ = { + isa = PBXGroup; + children = ( + 0E53E63627E34FE2001D4902 /* AppContext.swift */, + 0EB17EA527D2263700D473B5 /* Constants+Extensions.swift */, + 0E6059CE27FCC618003F4063 /* SwiftGen+Assets.swift */, + 0EBC075F27EC587900208AD9 /* SwiftGen+Strings.swift */, + 0EB34BCB27C6F41D00B126DA /* Theme.swift */, + ); + path = Constants; + sourceTree = ""; + }; + 0EBC075327EBC7FC00208AD9 /* Reusable */ = { + isa = PBXGroup; + children = ( + 0E71ACFC27C1321A00F85C4B /* ActivityView.swift */, + 0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */, + 0ED89C1627DE0E05008B36D6 /* IntentEditView.swift */, + 0EBC075427EBC83800208AD9 /* MailComposerView.swift */, + ); + path = Reusable; + sourceTree = ""; + }; + 0EC54E1927F214C9002522D0 /* AddProfile */ = { + isa = PBXGroup; + children = ( + 0E3B7FCC27E47B3700C66F13 /* AddHostView.swift */, + 0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */, + 0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */, + 0EF2212A27E667EA001D0BD7 /* AddProviderView+Name.swift */, + ); + path = AddProfile; + sourceTree = ""; + }; + 0EC54E1A27F214E7002522D0 /* Paywall */ = { + isa = PBXGroup; + children = ( + 0EF0FAF527DD0211007EB181 /* PaywallView.swift */, + 0ED30DCE27EA1EF80057D8A3 /* PaywallView+Beta.swift */, + 0ED30DD127EA1F650057D8A3 /* PaywallView+Purchase.swift */, + ); + path = Paywall; + sourceTree = ""; + }; + 0EC54E1B27F214F1002522D0 /* Profile */ = { + isa = PBXGroup; + children = ( + 0EBC074B27EB673C00208AD9 /* ProfileView+Rename.swift */, + ); + path = Profile; + sourceTree = ""; + }; + 0ECF71E227B64FFC00CDB528 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */, + 0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 0ED2B33C27D3C52900FD8EA9 /* OpenVPN */ = { + isa = PBXGroup; + children = ( + 0ED30DDA27EA351C0057D8A3 /* Constants+Tunnel.swift */, + 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */, + ); + path = OpenVPN; + sourceTree = ""; + }; + 0ED2B33D27D3C53400FD8EA9 /* WireGuard */ = { + isa = PBXGroup; + children = ( + 0ED2B35A27D3C94F00FD8EA9 /* PacketTunnelProvider.swift */, + ); + path = WireGuard; + sourceTree = ""; + }; + 0ED30DD627EA33220057D8A3 /* Shared */ = { + isa = PBXGroup; + children = ( + 0EB17EA127D2263700D473B5 /* Constants.swift */, + ); + path = Shared; + sourceTree = ""; + }; 0EDE8DC020C86910004C739C /* Tunnel */ = { isa = PBXGroup; children = ( - 0ED31C3B20CF39510027975F /* Tunnel-iOS.entitlements */, - 0E569FAA259F41690022DFB8 /* Tunnel-macOS.entitlements */, + 0ED2B33C27D3C52900FD8EA9 /* OpenVPN */, + 0ED2B33D27D3C53400FD8EA9 /* WireGuard */, + 0ED31C3B20CF39510027975F /* Tunnel.entitlements */, 0EDE8DC320C86910004C739C /* Info.plist */, - 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */, ); path = Tunnel; sourceTree = ""; }; - 0EDE8DEC20C93E3B004C739C /* Global */ = { - isa = PBXGroup; - children = ( - 0E3262D8235EE8DA00B5E470 /* HostImporter.swift */, - 0EFD943D215BE10800529B64 /* IssueReporter.swift */, - 0E4FD7F020D58618002221FF /* Macros.swift */, - 0ECC60DD2256B6890020BEAC /* SwiftGen+Assets.swift */, - 0EDE8DE320C89028004C739C /* SwiftGen+Scenes.swift */, - 0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */, - 0E05C61C20D27C82006EE732 /* Theme.swift */, - 0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */, - 0E2B493F20FCFF990094784C /* Theme+Titles.swift */, - 0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */, - ); - path = Global; - sourceTree = ""; - }; - 0EDE8DF120C93ED8004C739C /* Scenes */ = { - isa = PBXGroup; - children = ( - 0E24273D225950CC0064A1A3 /* About */, - 0E89DFCC213EEDE700741BA1 /* Organizer */, - 0E4B0D6C2366E53C00C890B4 /* Purchase */, - 0E24273E225950ED0064A1A3 /* Shortcuts */, - 0ED31C2820CF2A340027975F /* AccountViewController.swift */, - 0ED38AEB2141260D0004D387 /* ConfigurationModificationDelegate.swift */, - 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */, - 0E6BE13E20CFBAB300A6DD36 /* DebugLogViewController.swift */, - 0E158AD920E11B0B00C85A82 /* EndpointViewController.swift */, - 0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */, - 0ED31C2B20CF2D6F0027975F /* ProviderPoolViewController.swift */, - 0E1D72B1213BFFCF00BA1586 /* ProviderPresetViewController.swift */, - 0E9CDB6623604AD5006733B4 /* ServerNetworkViewController.swift */, - 0E57F63D20C83FC5008323CF /* ServiceViewController.swift */, - ); - path = Scenes; - sourceTree = ""; - }; 0EE315DB2733104700F5D461 /* Packages */ = { isa = PBXGroup; children = ( @@ -719,6 +662,7 @@ 374B9F085E1148C37CF9117A /* Frameworks */ = { isa = PBXGroup; children = ( + 0E9C3B6E27FC573E00D0F02E /* CloudKit.framework */, 0ED31C3920CF39510027975F /* NetworkExtension.framework */, 0EDE8DD220C86978004C739C /* NotificationCenter.framework */, ); @@ -727,57 +671,30 @@ }; /* End PBXGroup section */ +/* Begin PBXLegacyTarget section */ + 0ECF71F327B6D9CD00CDB528 /* WireGuardGo */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "$(ACTION)"; + buildConfigurationList = 0ECF71F427B6D9CD00CDB528 /* Build configuration list for PBXLegacyTarget "WireGuardGo" */; + buildPhases = ( + ); + buildToolPath = "$(PROJECT_DIR)/Passepartout/App/Scripts/build_wireguard_go_bridge.sh"; + buildWorkingDirectory = ""; + dependencies = ( + ); + name = WireGuardGo; + passBuildSettingsInEnvironment = 1; + productName = "PassepartoutWireGuard-iOS"; + }; +/* End PBXLegacyTarget section */ + /* Begin PBXNativeTarget section */ - 0E5202F6259F573500CBAB56 /* Passepartout-macOS */ = { + 0E57F63720C83FC5008323CF /* Passepartout */ = { isa = PBXNativeTarget; - buildConfigurationList = 0E520304259F573800CBAB56 /* Build configuration list for PBXNativeTarget "Passepartout-macOS" */; - buildPhases = ( - 0E5202F3259F573500CBAB56 /* Sources */, - 0E5202F4259F573500CBAB56 /* Frameworks */, - 0E5202F5259F573500CBAB56 /* Resources */, - 0E9AAACA259F806B003FAFF1 /* CopyFiles */, - 0E5203C2259F5F3F00CBAB56 /* Embed App Extensions */, - 0E5203F6259F60D600CBAB56 /* Embed Frameworks */, - 0EBEF139274E4DAE00EAC689 /* Drop Extra Frameworks In Extensions */, - ); - buildRules = ( - ); - dependencies = ( - 0E5203BD259F5F3F00CBAB56 /* PBXTargetDependency */, - 0E9AAAC0259F7FFF003FAFF1 /* PBXTargetDependency */, - ); - name = "Passepartout-macOS"; - packageProductDependencies = ( - 0EED0BBA2733CEE000C9FC68 /* PassepartoutCore */, - ); - productName = "Passepartout-macOS"; - productReference = 0E5202F7259F573500CBAB56 /* Passepartout.app */; - productType = "com.apple.product-type.application"; - }; - 0E5203B4259F5F3F00CBAB56 /* PassepartoutTunnel-macOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 0E5203BF259F5F3F00CBAB56 /* Build configuration list for PBXNativeTarget "PassepartoutTunnel-macOS" */; - buildPhases = ( - 0E5203B1259F5F3F00CBAB56 /* Sources */, - 0E5203B2259F5F3F00CBAB56 /* Frameworks */, - 0E5203B3259F5F3F00CBAB56 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "PassepartoutTunnel-macOS"; - packageProductDependencies = ( - 0EA5912F2733E1420096F796 /* PassepartoutOpenVPNTunnel */, - ); - productName = "Passepartout-macOS-Tunnel"; - productReference = 0E5203B5259F5F3F00CBAB56 /* PassepartoutTunnel.appex */; - productType = "com.apple.product-type.app-extension"; - }; - 0E57F63720C83FC5008323CF /* Passepartout-iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 0E57F65520C83FC7008323CF /* Build configuration list for PBXNativeTarget "Passepartout-iOS" */; + buildConfigurationList = 0E57F65520C83FC7008323CF /* Build configuration list for PBXNativeTarget "Passepartout" */; buildPhases = ( + 0EADDC7227F0677F0093E303 /* Copy Core Data codegen */, + 0E9E5AF427B44E59008C95DA /* Refresh Localizations */, 0E57F63420C83FC5008323CF /* Sources */, 0E57F63520C83FC5008323CF /* Frameworks */, 0E57F63620C83FC5008323CF /* Resources */, @@ -788,36 +705,44 @@ buildRules = ( ); dependencies = ( + 0ECF71FC27B6DA6700CDB528 /* PBXTargetDependency */, 0EB2B14A2733FB6F007705AB /* PBXTargetDependency */, + 0ED2B36227D3C99100FD8EA9 /* PBXTargetDependency */, + 0E6059C727FCC33D003F4063 /* PBXTargetDependency */, ); - name = "Passepartout-iOS"; + name = Passepartout; packageProductDependencies = ( 0EED0BB82733CEDA00C9FC68 /* PassepartoutCore */, + 0E53249C27D28FC7002565C3 /* Kvitto */, ); productName = Passepartout; productReference = 0E57F63820C83FC5008323CF /* Passepartout.app */; productType = "com.apple.product-type.application"; }; - 0E9AAA60259F7D7E003FAFF1 /* PassepartoutLauncher-macOS */ = { + 0ED2B33E27D3C77800FD8EA9 /* WireGuardTunnel */ = { isa = PBXNativeTarget; - buildConfigurationList = 0E9AAA70259F7D81003FAFF1 /* Build configuration list for PBXNativeTarget "PassepartoutLauncher-macOS" */; + buildConfigurationList = 0ED2B34727D3C77800FD8EA9 /* Build configuration list for PBXNativeTarget "WireGuardTunnel" */; buildPhases = ( - 0E9AAA5D259F7D7E003FAFF1 /* Sources */, - 0E9AAA5E259F7D7E003FAFF1 /* Frameworks */, - 0E9AAA5F259F7D7E003FAFF1 /* Resources */, + 0ED2B34027D3C77800FD8EA9 /* Sources */, + 0ED2B34327D3C77800FD8EA9 /* Frameworks */, + 0ED2B34627D3C77800FD8EA9 /* Resources */, ); buildRules = ( ); dependencies = ( + 0ED2B36B27D3CAB100FD8EA9 /* PBXTargetDependency */, ); - name = "PassepartoutLauncher-macOS"; - productName = "PassepartoutLauncher-macOS"; - productReference = 0E9AAA61259F7D7E003FAFF1 /* PassepartoutLauncher.app */; - productType = "com.apple.product-type.application"; + name = WireGuardTunnel; + packageProductDependencies = ( + 0ED2B36627D3C9A300FD8EA9 /* WireGuardAppExtension */, + ); + productName = "Passepartout-iOS-Tunnel"; + productReference = 0ED2B34A27D3C77800FD8EA9 /* PassepartoutWireGuardTunnel.appex */; + productType = "com.apple.product-type.app-extension"; }; - 0EDE8DBE20C86910004C739C /* PassepartoutTunnel-iOS */ = { + 0EDE8DBE20C86910004C739C /* OpenVPNTunnel */ = { isa = PBXNativeTarget; - buildConfigurationList = 0EDE8DC920C86910004C739C /* Build configuration list for PBXNativeTarget "PassepartoutTunnel-iOS" */; + buildConfigurationList = 0EDE8DC920C86910004C739C /* Build configuration list for PBXNativeTarget "OpenVPNTunnel" */; buildPhases = ( 0EDE8DBB20C86910004C739C /* Sources */, 0EDE8DBC20C86910004C739C /* Frameworks */, @@ -827,12 +752,12 @@ ); dependencies = ( ); - name = "PassepartoutTunnel-iOS"; + name = OpenVPNTunnel; packageProductDependencies = ( - 0EA591312733E1490096F796 /* PassepartoutOpenVPNTunnel */, + 0ED2B33827D3C49800FD8EA9 /* OpenVPNAppExtension */, ); productName = "Passepartout-iOS-Tunnel"; - productReference = 0EDE8DBF20C86910004C739C /* PassepartoutTunnel.appex */; + productReference = 0EDE8DBF20C86910004C739C /* PassepartoutOpenVPNTunnel.appex */; productType = "com.apple.product-type.app-extension"; }; /* End PBXNativeTarget section */ @@ -845,12 +770,6 @@ LastUpgradeCheck = 1240; ORGANIZATIONNAME = "Davide De Rosa"; TargetAttributes = { - 0E5202F6259F573500CBAB56 = { - CreatedOnToolsVersion = 12.3; - }; - 0E5203B4259F5F3F00CBAB56 = { - CreatedOnToolsVersion = 12.3; - }; 0E57F63720C83FC5008323CF = { CreatedOnToolsVersion = 9.4; LastSwiftMigration = 1020; @@ -869,8 +788,8 @@ }; }; }; - 0E9AAA60259F7D7E003FAFF1 = { - CreatedOnToolsVersion = 12.3; + 0ECF71F327B6D9CD00CDB528 = { + CreatedOnToolsVersion = 13.2; }; 0EDE8DBE20C86910004C739C = { CreatedOnToolsVersion = 10.0; @@ -907,73 +826,38 @@ ); mainGroup = 0E57F62F20C83FC5008323CF; packageReferences = ( + 0E53249B27D28FC7002565C3 /* XCRemoteSwiftPackageReference "Kvitto" */, ); productRefGroup = 0E57F63920C83FC5008323CF /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 0EDE8DBE20C86910004C739C /* PassepartoutTunnel-iOS */, - 0E57F63720C83FC5008323CF /* Passepartout-iOS */, - 0E9AAA60259F7D7E003FAFF1 /* PassepartoutLauncher-macOS */, - 0E5203B4259F5F3F00CBAB56 /* PassepartoutTunnel-macOS */, - 0E5202F6259F573500CBAB56 /* Passepartout-macOS */, + 0E57F63720C83FC5008323CF /* Passepartout */, + 0EDE8DBE20C86910004C739C /* OpenVPNTunnel */, + 0ECF71F327B6D9CD00CDB528 /* WireGuardGo */, + 0ED2B33E27D3C77800FD8EA9 /* WireGuardTunnel */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 0E5202F5259F573500CBAB56 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 0E520337259F58F500CBAB56 /* ProviderServiceView.xib in Resources */, - 0E520336259F58F500CBAB56 /* HostServiceView.xib in Resources */, - 0E52031D259F58BF00CBAB56 /* Providers.xcassets in Resources */, - 0E52047D259F642600CBAB56 /* Preferences.storyboard in Resources */, - 0E6BA54B25C9EE3A000CDFAC /* Purchase.storyboard in Resources */, - 0E520385259F593B00CBAB56 /* Credits.html in Resources */, - 0E52047C259F642600CBAB56 /* Service.storyboard in Resources */, - 0E52032B259F58DD00CBAB56 /* TextTableView.xib in Resources */, - 0E52047B259F642600CBAB56 /* Main.storyboard in Resources */, - 0E520334259F58F500CBAB56 /* OrganizerProfileTableView.xib in Resources */, - 0E9AAABE259F7FFF003FAFF1 /* PassepartoutLauncher.app in Resources */, - 0E52031E259F58BF00CBAB56 /* Assets.xcassets in Resources */, - 0E52031F259F58BF00CBAB56 /* Flags.xcassets in Resources */, - 0E52035D259F591300CBAB56 /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 0E5203B3259F5F3F00CBAB56 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 0E57F63620C83FC5008323CF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E36D25822403469006AF062 /* Shortcuts.storyboard in Resources */, + 0E6059CB27FCC5DE003F4063 /* Flags.xcassets in Resources */, 0E0C0729236087A100155AAC /* InfoPlist.strings in Resources */, - 0ED38ADA213F44D00004D387 /* Organizer.storyboard in Resources */, - 0E24273A225950450064A1A3 /* About.storyboard in Resources */, - 0E57F64620C83FC7008323CF /* LaunchScreen.storyboard in Resources */, - 0E57F64320C83FC7008323CF /* Assets.xcassets in Resources */, - 0E9CD7872257462800D033B4 /* Providers.xcassets in Resources */, - 0E9CD789225746B300D033B4 /* Flags.xcassets in Resources */, - 0E57F64120C83FC5008323CF /* Main.storyboard in Resources */, - 0E4B0D742366E6C800C890B4 /* Purchase.storyboard in Resources */, + 0E6059CC27FCC5DE003F4063 /* Providers.xcassets in Resources */, + 0E6059CD27FCC5DE003F4063 /* Assets.xcassets in Resources */, + 0E9E5AEF27B44CF1008C95DA /* Localizable.strings in Resources */, 0E2AC24522EC3AC10037B4B0 /* Settings.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 0E9AAA5F259F7D7E003FAFF1 /* Resources */ = { + 0ED2B34627D3C77800FD8EA9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E9AAA7F259F7DB7003FAFF1 /* Assets.xcassets in Resources */, - 0E9AAA89259F7DCA003FAFF1 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -987,6 +871,43 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 0E9E5AF427B44E59008C95DA /* Refresh Localizations */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Refresh Localizations"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "PATH=${PATH}:/opt/homebrew/bin\ncd \"${PROJECT_DIR}/Passepartout/App/Shared\"\nswiftgen\n"; + }; + 0EADDC7227F0677F0093E303 /* Copy Core Data codegen */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Copy Core Data codegen"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "Passepartout/App/Scripts/copy_coredata_codegen.sh\n"; + showEnvVarsInLog = 0; + }; 0EBEF138274E4C7F00EAC689 /* Drop Extra Frameworks In Extensions */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1005,131 +926,114 @@ shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nrm -rf ${BUILT_PRODUCTS_DIR}/${PLUGINS_FOLDER_PATH}/*.appex/Frameworks\n"; }; - 0EBEF139274E4DAE00EAC689 /* Drop Extra Frameworks In Extensions */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Drop Extra Frameworks In Extensions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nrm -rf ${BUILT_PRODUCTS_DIR}/${PLUGINS_FOLDER_PATH}/*.appex/Contents/Frameworks\n"; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 0E5202F3259F573500CBAB56 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 0E520388259F593B00CBAB56 /* HostImporter.swift in Sources */, - 0E520379259F593B00CBAB56 /* Macros.swift in Sources */, - 0E520387259F593B00CBAB56 /* TextInputViewController.swift in Sources */, - 0E52033B259F58F500CBAB56 /* OrganizerViewController.swift in Sources */, - 0E520382259F593B00CBAB56 /* SwiftGen+Scenes.swift in Sources */, - 0E52032C259F58DD00CBAB56 /* TextTableView.swift in Sources */, - 0E520355259F590600CBAB56 /* DebugLogViewController.swift in Sources */, - 0E52034A259F58FE00CBAB56 /* DefaultGatewayViewController.swift in Sources */, - 0E520345259F58FE00CBAB56 /* DNSViewController.swift in Sources */, - 0E520320259F58BF00CBAB56 /* AppDelegate.swift in Sources */, - 0E520344259F58FE00CBAB56 /* TrustedNetworksAddViewController.swift in Sources */, - 0E52033A259F58F500CBAB56 /* AccountViewController.swift in Sources */, - 0E294AA225AE2B0B00CB4908 /* Descriptible.swift in Sources */, - 0E520356259F590600CBAB56 /* PreferencesGeneralViewController.swift in Sources */, - 0E520348259F58FE00CBAB56 /* MTUViewController.swift in Sources */, - 0E79D31E25CC0CF600D12964 /* PurchaseProductView.swift in Sources */, - 0E52037E259F593B00CBAB56 /* SwiftGen+Segues.swift in Sources */, - 0E52037C259F593B00CBAB56 /* Theme.swift in Sources */, - 0E52035E259F591300CBAB56 /* StatusMenu.swift in Sources */, - 0E52035F259F591300CBAB56 /* MainMenu.swift in Sources */, - 0E520342259F58FE00CBAB56 /* EndpointViewController.swift in Sources */, - 0E520339259F58F500CBAB56 /* ServiceViewController.swift in Sources */, - 0E520383259F593B00CBAB56 /* SwiftGen+Assets.swift in Sources */, - 0E52037A259F593B00CBAB56 /* WindowManager.swift in Sources */, - 0E520381259F593B00CBAB56 /* NSTextView+Search.swift in Sources */, - 0E520349259F58FE00CBAB56 /* ConfigurationViewController.swift in Sources */, - 0E520354259F590600CBAB56 /* PreferencesViewController.swift in Sources */, - 0E520343259F58FE00CBAB56 /* ProfileCustomizationViewController.swift in Sources */, - 0E520346259F58FE00CBAB56 /* TrustedNetworksViewController.swift in Sources */, - 0E79D2C825C9F1B300D12964 /* PurchaseViewController.swift in Sources */, - 0E520338259F58F500CBAB56 /* ProviderServiceView.swift in Sources */, - 0E520347259F58FE00CBAB56 /* ProxyViewController.swift in Sources */, - 0E52037B259F593B00CBAB56 /* IssueReporter.swift in Sources */, - 0E520335259F58F500CBAB56 /* HostServiceView.swift in Sources */, - 0E520333259F58F500CBAB56 /* OrganizerProfileTableView.swift in Sources */, - 0E52037F259F593B00CBAB56 /* Theme+Views.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 0E5203B1259F5F3F00CBAB56 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 0E9AA979259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 0E57F63420C83FC5008323CF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0ED38AD9213F33150004D387 /* WizardHostViewController.swift in Sources */, - 0EE3BBB2215ED3A900F30952 /* AboutViewController.swift in Sources */, 0EA591162733DDDA0096F796 /* Intents.intentdefinition in Sources */, - 0EBE3A79213C4E5500BFA2F5 /* OrganizerViewController.swift in Sources */, - 0E4FD7F120D58618002221FF /* Macros.swift in Sources */, - 0E05C5D720D1645F006EE732 /* ToggleTableViewCell.swift in Sources */, - 0EFB901A2276D7F1006405E4 /* NetworkSettingsViewController.swift in Sources */, - 0E05C5D420D1645F006EE732 /* FieldTableViewCell.swift in Sources */, - 0E36D25C224034AD006AF062 /* ShortcutsConnectToViewController.swift in Sources */, - 0E05C61D20D27C82006EE732 /* Theme.swift in Sources */, - 0ECC60DE2256B68A0020BEAC /* SwiftGen+Assets.swift in Sources */, - 0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */, - 0ED38AEC2141260D0004D387 /* ConfigurationModificationDelegate.swift in Sources */, - 0E294AA125AE2B0A00CB4908 /* Descriptible.swift in Sources */, - 0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */, - 0E6268942369AD0600355F75 /* PurchaseTableViewCell.swift in Sources */, - 0E1066C920E0F84A004F98B7 /* Cells.swift in Sources */, - 0E4B0D6B2366E3C100C890B4 /* PurchaseViewController.swift in Sources */, - 0EF56BBB2185AC8500B0C8AB /* SwiftGen+Segues.swift in Sources */, - 0EA591132733DD4F0096F796 /* IntentDispatcher.swift in Sources */, - 0E05C5D620D1645F006EE732 /* SwiftGen+Scenes.swift in Sources */, - 0E773BF8224BF37600CDDC8E /* ShortcutsViewController.swift in Sources */, - 0E9CDB6723604AD5006733B4 /* ServerNetworkViewController.swift in Sources */, - 0E3262D9235EE8DA00B5E470 /* HostImporter.swift in Sources */, - 0ED31C2C20CF2D6F0027975F /* ProviderPoolViewController.swift in Sources */, - 0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */, - 0E05C5D520D1645F006EE732 /* SettingTableViewCell.swift in Sources */, - 0E89DFCE213EEDFA00741BA1 /* WizardProviderViewController.swift in Sources */, - 0E1D72B2213BFFCF00BA1586 /* ProviderPresetViewController.swift in Sources */, - 0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */, - 0EFD943E215BE10800529B64 /* IssueReporter.swift in Sources */, - 0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */, - 0E3586FE225BD34800509A4D /* ActivityTableViewCell.swift in Sources */, - 0EB67D6B2184581E00BA6200 /* ImportedHostsViewController.swift in Sources */, - 0E57F63E20C83FC5008323CF /* ServiceViewController.swift in Sources */, - 0E36D24D2240234B006AF062 /* ShortcutsAddViewController.swift in Sources */, - 0E57F63C20C83FC5008323CF /* AppDelegate.swift in Sources */, - 0ED31C2920CF2A340027975F /* AccountViewController.swift in Sources */, - 0E158ADA20E11B0B00C85A82 /* EndpointViewController.swift in Sources */, - 0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */, - 0E4C9CBB20DCF0D600A0C59C /* DestructiveTableViewCell.swift in Sources */, + 0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */, + 0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */, + 0E92D7C627F103300033CB7B /* ProfileView+Configuration.swift in Sources */, + 0E34AC7A27F8431D0042F2AB /* OrganizerView+Shortcuts.swift in Sources */, + 0E34AC7E27F849050042F2AB /* OrganizerView+AddProfileMenu.swift in Sources */, + 0E2DE71C27DCCFE80067B9E1 /* TunnelKit+Identifiable.swift in Sources */, + 0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */, + 0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */, + 0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */, + 0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */, + 0E9C3B6C27FB3A9C00D0F02E /* ReloadingSection.swift in Sources */, + 0E5324A627D297BB002565C3 /* InApp.swift in Sources */, + 0E3B7FCD27E47B3700C66F13 /* AddHostView.swift in Sources */, + 0EF2212D27E66EB5001D0BD7 /* AddProviderView.swift in Sources */, + 0E5349C827C176D100C71BB3 /* EndpointView+WireGuard.swift in Sources */, + 0EBC076027EC587900208AD9 /* SwiftGen+Strings.swift in Sources */, + 0E5683B927C2825D00EAF1CD /* DiagnosticsView.swift in Sources */, + 0E71ACFD27C1321A00F85C4B /* ActivityView.swift in Sources */, + 0E44689627B051C300A14CE4 /* ProfileView.swift in Sources */, + 0EDE02C227F61C79000FBE3C /* EditableTextList.swift in Sources */, + 0E92D7C927F1042A0033CB7B /* ProfileView+Extra.swift in Sources */, + 0EBC074C27EB673C00208AD9 /* ProfileView+Rename.swift in Sources */, + 0E49F6BD27D7639000385834 /* EndpointAdvancedView+WireGuard.swift in Sources */, + 0EB34BCC27C6F41D00B126DA /* Theme.swift in Sources */, + 0EB17EA927D226C900D473B5 /* Constants.swift in Sources */, + 0E5324A927D2AC55002565C3 /* LongContentView.swift in Sources */, + 0ED89C1C27DE3ABC008B36D6 /* ShortcutsView+Add.swift in Sources */, + 0E34A2CF27CADA6300C73B67 /* GenericVersionView.swift in Sources */, + 0E9C233327F47E95007D5FC7 /* IntentDispatcher+Activities.swift in Sources */, + 0EBC075D27EC529000208AD9 /* DebugLog+Constants.swift in Sources */, + 0EB17EAA27D226C900D473B5 /* Constants+Extensions.swift in Sources */, + 0E53E63727E34FE2001D4902 /* AppContext.swift in Sources */, + 0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */, + 0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */, + 0ED30DCF27EA1EF80057D8A3 /* PaywallView+Beta.swift in Sources */, + 0ECF71EE27B6A99300CDB528 /* AccountView.swift in Sources */, + 0E71ACF727C107CA00F85C4B /* DebugLogView.swift in Sources */, + 0EF0FAF927DD212C007EB181 /* IntentActivity.swift in Sources */, + 0EBC075B27EC4FFF00208AD9 /* ReportIssueView.swift in Sources */, + 0ED89C1727DE0E05008B36D6 /* IntentEditView.swift in Sources */, + 0E71ACE927C1055300F85C4B /* NetworkSettingsView.swift in Sources */, + 0EB34BCA27C6A70200B126DA /* OnDemandView.swift in Sources */, + 0E0BD27327B2EA2C00583AC5 /* MainView.swift in Sources */, + 0E34AC7C27F845510042F2AB /* OrganizerView+Profiles.swift in Sources */, + 0EB17EBA27D2560300D473B5 /* PassepartoutProviders+Extensions.swift in Sources */, + 0E3B7FDA27E51A0200C66F13 /* ProfileView+Provider.swift in Sources */, + 0E71ACE327C0F2E400F85C4B /* Providers+L10n.swift in Sources */, + 0E71ACF127C1073800F85C4B /* ProviderLocationView.swift in Sources */, + 0E2A8D4F27B04BBA00207D04 /* OrganizerView.swift in Sources */, + 0E49F6BB27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift in Sources */, + 0E71ACEF27C106B500F85C4B /* ProviderPresetView.swift in Sources */, + 0E0AD49027BD53CB00FBB520 /* ProfileView+Welcome.swift in Sources */, + 0E34AC7627F83FE20042F2AB /* OrganizerView+VPN.swift in Sources */, + 0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */, + 0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */, + 0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */, + 0ED89C2527DE45A3008B36D6 /* ProfileHeaderRow.swift in Sources */, + 0E2C172B27CB63F9007E8488 /* Reviewer.swift in Sources */, + 0E71ACDD27C0295C00F85C4B /* View+Extensions.swift in Sources */, + 0ED89C2027DE423B008B36D6 /* ShortcutsView+ConnectTo.swift in Sources */, + 0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */, + 0E6059CF27FCC618003F4063 /* SwiftGen+Assets.swift in Sources */, + 0E2A8D4927ADF87F00207D04 /* PassepartoutApp.swift in Sources */, + 0EBC075527EBC83800208AD9 /* MailComposerView.swift in Sources */, + 0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */, + 0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */, + 0E9ED48127FD9BAE003B2316 /* CopySavingButton.swift in Sources */, + 0E44689C27B11B5300A14CE4 /* AboutView.swift in Sources */, + 0E71ACF927C12E4800F85C4B /* CreditsView.swift in Sources */, + 0ED89C1527DE0A0C008B36D6 /* Shortcut.swift in Sources */, + 0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */, + 0EB17EAE27D226CF00D473B5 /* LocalProduct.swift in Sources */, + 0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */, + 0E53249927D26B51002565C3 /* ProductManager.swift in Sources */, + 0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */, + 0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */, + 0EB4042E27CA136300378B1A /* AddingTextField.swift in Sources */, + 0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */, + 0EF2212B27E667EA001D0BD7 /* AddProviderView+Name.swift in Sources */, + 0E2DE71F27DCD0290067B9E1 /* TunnelKit+L10n.swift in Sources */, + 0E49F6BF27D764AF00385834 /* EndpointAdvancedView.swift in Sources */, + 0E0BD27627B2EB2200583AC5 /* DonateView.swift in Sources */, + 0E2C171B27CB5A3B007E8488 /* GenericCreditsView.swift in Sources */, + 0ED30DD227EA1F650057D8A3 /* PaywallView+Purchase.swift in Sources */, + 0EB3413027C7761A00483410 /* Binding+Extensions.swift in Sources */, + 0E2DE72527DCDF550067B9E1 /* WireGuard+L10n.swift in Sources */, + 0E71ACFB27C12E5300F85C4B /* VersionView.swift in Sources */, + 0ED1D6DC27DBA41700983466 /* DiagnosticsView+OpenVPN.swift in Sources */, + 0ED30DCC27EA197D0057D8A3 /* RevealingSecureField.swift in Sources */, + 0E92D7F427F104B80033CB7B /* ProfileView+Diagnostics.swift in Sources */, + 0E5349C627C176C200C71BB3 /* EndpointView+OpenVPN.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 0E9AAA5D259F7D7E003FAFF1 /* Sources */ = { + 0ED2B34027D3C77800FD8EA9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E9AAA80259F7DB7003FAFF1 /* AppDelegate.swift in Sources */, + 0ED2B35B27D3C94F00FD8EA9 /* PacketTunnelProvider.swift in Sources */, + 0ED30DDD27EA35230057D8A3 /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1138,27 +1042,41 @@ buildActionMask = 2147483647; files = ( 0E9AA978259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */, + 0EB17EA727D226B400D473B5 /* Constants.swift in Sources */, + 0ED30DDB27EA351C0057D8A3 /* Constants+Tunnel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 0E5203BD259F5F3F00CBAB56 /* PBXTargetDependency */ = { + 0E6059C727FCC33D003F4063 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 0E5203B4259F5F3F00CBAB56 /* PassepartoutTunnel-macOS */; - targetProxy = 0E5203BC259F5F3F00CBAB56 /* PBXContainerItemProxy */; - }; - 0E9AAAC0259F7FFF003FAFF1 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 0E9AAA60259F7D7E003FAFF1 /* PassepartoutLauncher-macOS */; - targetProxy = 0E9AAABF259F7FFF003FAFF1 /* PBXContainerItemProxy */; + platformFilter = ios; + targetProxy = 0E6059C627FCC33D003F4063 /* PBXContainerItemProxy */; }; 0EB2B14A2733FB6F007705AB /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 0EDE8DBE20C86910004C739C /* PassepartoutTunnel-iOS */; + platformFilter = ios; + target = 0EDE8DBE20C86910004C739C /* OpenVPNTunnel */; targetProxy = 0EB2B1492733FB6F007705AB /* PBXContainerItemProxy */; }; + 0ECF71FC27B6DA6700CDB528 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0ECF71F327B6D9CD00CDB528 /* WireGuardGo */; + targetProxy = 0ECF71FB27B6DA6700CDB528 /* PBXContainerItemProxy */; + }; + 0ED2B36227D3C99100FD8EA9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + platformFilter = ios; + target = 0ED2B33E27D3C77800FD8EA9 /* WireGuardTunnel */; + targetProxy = 0ED2B36127D3C99100FD8EA9 /* PBXContainerItemProxy */; + }; + 0ED2B36B27D3CAB100FD8EA9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0ECF71F327B6D9CD00CDB528 /* WireGuardGo */; + targetProxy = 0ED2B36A27D3CAB100FD8EA9 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1181,84 +1099,23 @@ name = InfoPlist.strings; sourceTree = ""; }; - 0E24273C225950450064A1A3 /* About.storyboard */ = { + 0E9E5AE227B44CF1008C95DA /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - 0E24273B225950450064A1A3 /* Base */, + 0E9E5AE327B44CF1008C95DA /* de */, + 0E9E5AE427B44CF1008C95DA /* el */, + 0E9E5AE527B44CF1008C95DA /* zh-Hans */, + 0E9E5AE627B44CF1008C95DA /* en */, + 0E9E5AE727B44CF1008C95DA /* es */, + 0E9E5AE827B44CF1008C95DA /* it */, + 0E9E5AE927B44CF1008C95DA /* sv */, + 0E9E5AEA27B44CF1008C95DA /* pl */, + 0E9E5AEB27B44CF1008C95DA /* ru */, + 0E9E5AEC27B44CF1008C95DA /* fr */, + 0E9E5AED27B44CF1008C95DA /* nl */, + 0E9E5AEE27B44CF1008C95DA /* pt */, ); - name = About.storyboard; - sourceTree = ""; - }; - 0E36D25A22403469006AF062 /* Shortcuts.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 0ECEB107224FE51400E9E551 /* Base */, - ); - name = Shortcuts.storyboard; - sourceTree = ""; - }; - 0E4B0D762366E6C800C890B4 /* Purchase.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 0E4B0D752366E6C800C890B4 /* Base */, - ); - name = Purchase.storyboard; - sourceTree = ""; - }; - 0E569F5F259F41690022DFB8 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 0E569F60259F41690022DFB8 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 0E569F81259F41690022DFB8 /* Preferences.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 0E569F82259F41690022DFB8 /* Base */, - ); - name = Preferences.storyboard; - sourceTree = ""; - }; - 0E569F83259F41690022DFB8 /* Service.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 0E569F84259F41690022DFB8 /* Base */, - ); - name = Service.storyboard; - sourceTree = ""; - }; - 0E569F85259F41690022DFB8 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 0E569F86259F41690022DFB8 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 0E57F63F20C83FC5008323CF /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 0ECEB105224FE51400E9E551 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 0E57F64420C83FC7008323CF /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 0ECEB108224FE51400E9E551 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; - 0E6BA54D25C9EE3A000CDFAC /* Purchase.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 0E6BA54C25C9EE3A000CDFAC /* Base */, - ); - name = Purchase.storyboard; + name = Localizable.strings; sourceTree = ""; }; 0EA591142733DDDA0096F796 /* Intents.intentdefinition */ = { @@ -1281,115 +1138,9 @@ name = Intents.intentdefinition; sourceTree = ""; }; - 0ED38ADC213F44D00004D387 /* Organizer.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 0ECEB106224FE51400E9E551 /* Base */, - ); - name = Organizer.storyboard; - sourceTree = ""; - }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 0E520305259F573800CBAB56 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = Passepartout/App/macOS/App.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - DEVELOPMENT_TEAM = DTDYD63ZX9; - ENABLE_HARDENED_RUNTIME = YES; - INFOPLIST_FILE = Passepartout/App/macOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID)"; - PRODUCT_NAME = Passepartout; - PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout macos"; - SDKROOT = macosx; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 0E520306259F573800CBAB56 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = Passepartout/App/macOS/App.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - DEVELOPMENT_TEAM = DTDYD63ZX9; - ENABLE_HARDENED_RUNTIME = YES; - INFOPLIST_FILE = Passepartout/App/macOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID)"; - PRODUCT_NAME = Passepartout; - PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout macos"; - SDKROOT = macosx; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 0E5203C0259F5F3F00CBAB56 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = "Passepartout/Tunnel/Tunnel-macOS.entitlements"; - CODE_SIGN_IDENTITY = "Mac Developer"; - CODE_SIGN_STYLE = Manual; - COPY_PHASE_STRIP = NO; - DEVELOPMENT_TEAM = DTDYD63ZX9; - ENABLE_HARDENED_RUNTIME = YES; - INFOPLIST_FILE = Passepartout/Tunnel/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@executable_path/../../../../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).Tunnel"; - PRODUCT_NAME = PassepartoutTunnel; - PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.Tunnel macos"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 0E5203C1259F5F3F00CBAB56 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = "Passepartout/Tunnel/Tunnel-macOS.entitlements"; - CODE_SIGN_IDENTITY = "Mac Developer"; - CODE_SIGN_STYLE = Manual; - COPY_PHASE_STRIP = NO; - DEVELOPMENT_TEAM = DTDYD63ZX9; - ENABLE_HARDENED_RUNTIME = YES; - INFOPLIST_FILE = Passepartout/Tunnel/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@executable_path/../../../../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).Tunnel"; - PRODUCT_NAME = PassepartoutTunnel; - PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.Tunnel macos"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; 0E57F65320C83FC7008323CF /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 0E23B4A12298559800304C30 /* Config.xcconfig */; @@ -1426,7 +1177,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2984; + CURRENT_PROJECT_VERSION = 3000; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1445,8 +1196,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MACOSX_DEPLOYMENT_TARGET = 10.14; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -1492,7 +1243,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2984; + CURRENT_PROJECT_VERSION = 3000; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_BITCODE = NO; ENABLE_NS_ASSERTIONS = NO; @@ -1505,8 +1256,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MACOSX_DEPLOYMENT_TARGET = 10.14; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -1521,18 +1272,24 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Passepartout/App/iOS/App.entitlements; + CODE_SIGN_ENTITLEMENTS = Passepartout/App/Shared/App.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 3000; DEVELOPMENT_TEAM = DTDYD63ZX9; - INFOPLIST_FILE = Passepartout/App/iOS/Info.plist; + INFOPLIST_FILE = Passepartout/App/Shared/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID)"; PRODUCT_NAME = Passepartout; PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout catalyst"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1543,79 +1300,69 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Passepartout/App/iOS/App.entitlements; + CODE_SIGN_ENTITLEMENTS = Passepartout/App/Shared/App.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 3000; DEVELOPMENT_TEAM = DTDYD63ZX9; - INFOPLIST_FILE = Passepartout/App/iOS/Info.plist; + INFOPLIST_FILE = Passepartout/App/Shared/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID)"; PRODUCT_NAME = Passepartout; PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout catalyst"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; - 0E9AAA6E259F7D81003FAFF1 /* Debug */ = { + 0ECF71F527B6D9CD00CDB528 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = Passepartout/App/macOS/Launcher/Launcher.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; + DEBUGGING_SYMBOLS = YES; + DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = DTDYD63ZX9; - ENABLE_HARDENED_RUNTIME = YES; - INFOPLIST_FILE = Passepartout/App/macOS/Launcher/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_LAUNCHER_ID)"; - PRODUCT_NAME = PassepartoutLauncher; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; - 0E9AAA6F259F7D81003FAFF1 /* Release */ = { + 0ECF71F627B6D9CD00CDB528 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = Passepartout/App/macOS/Launcher/Launcher.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = DTDYD63ZX9; - ENABLE_HARDENED_RUNTIME = YES; - INFOPLIST_FILE = Passepartout/App/macOS/Launcher/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_LAUNCHER_ID)"; - PRODUCT_NAME = PassepartoutLauncher; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; - 0EDE8DCA20C86910004C739C /* Debug */ = { + 0ED2B34827D3C77800FD8EA9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_ENTITLEMENTS = "Passepartout/Tunnel/Tunnel-iOS.entitlements"; + CODE_SIGN_ENTITLEMENTS = Passepartout/Tunnel/Tunnel.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = DTDYD63ZX9; INFOPLIST_FILE = Passepartout/Tunnel/Info.plist; @@ -1624,10 +1371,60 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).Tunnel"; - PRODUCT_NAME = PassepartoutTunnel; - PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.Tunnel"; + PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).Tunnel.WireGuard"; + PRODUCT_NAME = PassepartoutWireGuardTunnel; + PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.Tunnel.WireGuard"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.Tunnel.WireGuard catalyst"; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0ED2B34927D3C77800FD8EA9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = Passepartout/Tunnel/Tunnel.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = DTDYD63ZX9; + INFOPLIST_FILE = Passepartout/Tunnel/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).Tunnel.WireGuard"; + PRODUCT_NAME = PassepartoutWireGuardTunnel; + PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.Tunnel.WireGuard"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.Tunnel.WireGuard catalyst"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 0EDE8DCA20C86910004C739C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = Passepartout/Tunnel/Tunnel.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = DTDYD63ZX9; + INFOPLIST_FILE = Passepartout/Tunnel/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).Tunnel.OpenVPN"; + PRODUCT_NAME = PassepartoutOpenVPNTunnel; + PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.Tunnel.OpenVPN"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.Tunnel.OpenVPN catalyst"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -1635,8 +1432,9 @@ 0EDE8DCB20C86910004C739C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_ENTITLEMENTS = "Passepartout/Tunnel/Tunnel-iOS.entitlements"; + CODE_SIGN_ENTITLEMENTS = Passepartout/Tunnel/Tunnel.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = DTDYD63ZX9; INFOPLIST_FILE = Passepartout/Tunnel/Info.plist; @@ -1645,10 +1443,12 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).Tunnel"; - PRODUCT_NAME = PassepartoutTunnel; - PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.Tunnel"; + PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).Tunnel.OpenVPN"; + PRODUCT_NAME = PassepartoutOpenVPNTunnel; + PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.Tunnel.OpenVPN"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.Tunnel.OpenVPN catalyst"; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -1656,24 +1456,6 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 0E520304259F573800CBAB56 /* Build configuration list for PBXNativeTarget "Passepartout-macOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 0E520305259F573800CBAB56 /* Debug */, - 0E520306259F573800CBAB56 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 0E5203BF259F5F3F00CBAB56 /* Build configuration list for PBXNativeTarget "PassepartoutTunnel-macOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 0E5203C0259F5F3F00CBAB56 /* Debug */, - 0E5203C1259F5F3F00CBAB56 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 0E57F63320C83FC5008323CF /* Build configuration list for PBXProject "Passepartout" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1683,7 +1465,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 0E57F65520C83FC7008323CF /* Build configuration list for PBXNativeTarget "Passepartout-iOS" */ = { + 0E57F65520C83FC7008323CF /* Build configuration list for PBXNativeTarget "Passepartout" */ = { isa = XCConfigurationList; buildConfigurations = ( 0E57F65620C83FC7008323CF /* Debug */, @@ -1692,16 +1474,25 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 0E9AAA70259F7D81003FAFF1 /* Build configuration list for PBXNativeTarget "PassepartoutLauncher-macOS" */ = { + 0ECF71F427B6D9CD00CDB528 /* Build configuration list for PBXLegacyTarget "WireGuardGo" */ = { isa = XCConfigurationList; buildConfigurations = ( - 0E9AAA6E259F7D81003FAFF1 /* Debug */, - 0E9AAA6F259F7D81003FAFF1 /* Release */, + 0ECF71F527B6D9CD00CDB528 /* Debug */, + 0ECF71F627B6D9CD00CDB528 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 0EDE8DC920C86910004C739C /* Build configuration list for PBXNativeTarget "PassepartoutTunnel-iOS" */ = { + 0ED2B34727D3C77800FD8EA9 /* Build configuration list for PBXNativeTarget "WireGuardTunnel" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0ED2B34827D3C77800FD8EA9 /* Debug */, + 0ED2B34927D3C77800FD8EA9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0EDE8DC920C86910004C739C /* Build configuration list for PBXNativeTarget "OpenVPNTunnel" */ = { isa = XCConfigurationList; buildConfigurations = ( 0EDE8DCA20C86910004C739C /* Debug */, @@ -1712,23 +1503,35 @@ }; /* End XCConfigurationList section */ -/* Begin XCSwiftPackageProductDependency section */ - 0EA5912F2733E1420096F796 /* PassepartoutOpenVPNTunnel */ = { - isa = XCSwiftPackageProductDependency; - productName = PassepartoutOpenVPNTunnel; +/* Begin XCRemoteSwiftPackageReference section */ + 0E53249B27D28FC7002565C3 /* XCRemoteSwiftPackageReference "Kvitto" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Cocoanetics/Kvitto"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; }; - 0EA591312733E1490096F796 /* PassepartoutOpenVPNTunnel */ = { +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 0E53249C27D28FC7002565C3 /* Kvitto */ = { isa = XCSwiftPackageProductDependency; - productName = PassepartoutOpenVPNTunnel; + package = 0E53249B27D28FC7002565C3 /* XCRemoteSwiftPackageReference "Kvitto" */; + productName = Kvitto; + }; + 0ED2B33827D3C49800FD8EA9 /* OpenVPNAppExtension */ = { + isa = XCSwiftPackageProductDependency; + productName = OpenVPNAppExtension; + }; + 0ED2B36627D3C9A300FD8EA9 /* WireGuardAppExtension */ = { + isa = XCSwiftPackageProductDependency; + productName = WireGuardAppExtension; }; 0EED0BB82733CEDA00C9FC68 /* PassepartoutCore */ = { isa = XCSwiftPackageProductDependency; productName = PassepartoutCore; }; - 0EED0BBA2733CEE000C9FC68 /* PassepartoutCore */ = { - isa = XCSwiftPackageProductDependency; - productName = PassepartoutCore; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0E57F63020C83FC5008323CF /* Project object */; diff --git a/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8b466ef7..288e57c4 100644 --- a/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,15 +1,6 @@ { "object": { "pins": [ - { - "package": "Convenience", - "repositoryURL": "https://github.com/keeshux/convenience", - "state": { - "branch": null, - "revision": "347105ec0ce27cd4255acf9875fd60ad1f213801", - "version": null - } - }, { "package": "DTFoundation", "repositoryURL": "https://github.com/Cocoanetics/DTFoundation.git", @@ -20,12 +11,12 @@ } }, { - "package": "FontAwesome", - "repositoryURL": "https://github.com/thii/FontAwesome.swift", + "package": "GenericJSON", + "repositoryURL": "https://github.com/zoul/generic-json-swift", "state": { "branch": null, - "revision": "07883a32d49dfc7bdedbeea115067b53dfbeeb43", - "version": "1.9.1" + "revision": "a137894b2a217abe489cdf1ea287e6f7819b1051", + "version": "2.0.1" } }, { @@ -37,15 +28,6 @@ "version": "1.0.6" } }, - { - "package": "MBProgressHUD", - "repositoryURL": "https://github.com/jdg/MBProgressHUD", - "state": { - "branch": null, - "revision": "bca42b801100b2b3a4eda0ba8dd33d858c780b0d", - "version": "1.2.0" - } - }, { "package": "openssl-apple", "repositoryURL": "https://github.com/passepartoutvpn/openssl-apple", @@ -64,22 +46,13 @@ "version": "1.9.5" } }, - { - "package": "TunnelKit", - "repositoryURL": "https://github.com/passepartoutvpn/tunnelkit", - "state": { - "branch": null, - "revision": "88544e4877f00990ef699873f3505fff606ab64b", - "version": "4.1.0" - } - }, { "package": "WireGuardKit", - "repositoryURL": "https://git.zx2c4.com/wireguard-apple", + "repositoryURL": "https://github.com/passepartoutvpn/wireguard-apple", "state": { "branch": null, - "revision": "10da5cfdef362889b438cfbeff867a74e6d717fd", - "version": "1.0.15-26" + "revision": "d3b8f1ac6f3361d69bd3daf8aee3c43012c6ec0b", + "version": "1.0.16" } } ] diff --git a/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout-macOS.xcscheme b/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout-macOS.xcscheme deleted file mode 100644 index 823c0b6e..00000000 --- a/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout-macOS.xcscheme +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout-iOS.xcscheme b/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme similarity index 61% rename from Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout-iOS.xcscheme rename to Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme index d48d07d9..8649b99f 100644 --- a/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout-iOS.xcscheme +++ b/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme @@ -16,7 +16,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "0E57F63720C83FC5008323CF" BuildableName = "Passepartout.app" - BlueprintName = "Passepartout-iOS" + BlueprintName = "Passepartout" ReferencedContainer = "container:Passepartout.xcodeproj"> @@ -29,8 +29,36 @@ + + + + + + + + @@ -46,21 +74,11 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "0E57F63720C83FC5008323CF" BuildableName = "Passepartout.app" - BlueprintName = "Passepartout-iOS" + BlueprintName = "Passepartout" ReferencedContainer = "container:Passepartout.xcodeproj"> - - - - + + + + + + + + + + + + + + @@ -103,7 +148,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "0E57F63720C83FC5008323CF" BuildableName = "Passepartout.app" - BlueprintName = "Passepartout-iOS" + BlueprintName = "Passepartout" ReferencedContainer = "container:Passepartout.xcodeproj"> diff --git a/Passepartout/App/Descriptible.swift b/Passepartout/App/Descriptible.swift deleted file mode 100644 index 2afd4add..00000000 --- a/Passepartout/App/Descriptible.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// Descriptible.swift -// Passepartout -// -// Created by Davide De Rosa on 1/12/21. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import PassepartoutCore - -public protocol UIDescriptible { - var uiDescription: String { get } -} - -extension OpenVPN.Cipher: UIDescriptible { - public var uiDescription: String { - return description - } -} - -extension OpenVPN.Digest: UIDescriptible { - public var uiDescription: String { - return description - } -} - -extension OpenVPN.CompressionFraming: UIDescriptible { - public var uiDescription: String { - let V = L10n.Configuration.Cells.self - switch self { - case .disabled: - return L10n.Global.Values.disabled - - case .compLZO: - return V.CompressionFraming.Value.lzo - - case .compress, .compressV2: - return V.CompressionFraming.Value.compress - } - } -} - -extension OpenVPN.CompressionAlgorithm: UIDescriptible { - public var uiDescription: String { - let V = L10n.Configuration.Cells.self - switch self { - case .disabled: - return L10n.Global.Values.disabled - - case .LZO: - return V.CompressionAlgorithm.Value.lzo - - case .other: - return V.CompressionAlgorithm.Value.other - } - } -} - -extension OpenVPN.ConfigurationBuilder { - public var uiDescriptionForTLSWrap: String { - let V = L10n.Configuration.Cells.self - if let strategy = tlsWrap?.strategy { - switch strategy { - case .auth: - return V.TlsWrapping.Value.auth - - case .crypt: - return V.TlsWrapping.Value.crypt - } - } else { - return L10n.Global.Values.disabled - } - } - - public var uiDescriptionForKeepAlive: String { - let V = L10n.Configuration.Cells.self - if let keepAlive = keepAliveInterval, keepAlive > 0 { - return V.KeepAlive.Value.seconds(Int(keepAlive)) - } else { - return L10n.Global.Values.disabled - } - } - - public var uiDescriptionForClientCertificate: String { - let V = L10n.Configuration.Cells.Client.Value.self - return (clientCertificate != nil) ? V.enabled : V.disabled - } - - public var uiDescriptionForEKU: String { - let V = L10n.Global.Values.self - return (checksEKU ?? false) ? V.enabled : V.disabled - } - - public var uiDescriptionForRenegotiatesAfter: String { - let V = L10n.Configuration.Cells.self - if let reneg = renegotiatesAfter, reneg > 0 { - return V.RenegotiationSeconds.Value.after(TimeInterval(reneg).localized) - } else { - return L10n.Global.Values.disabled - } - } - - public var uiDescriptionForRandomizeEndpoint: String { - let V = L10n.Global.Values.self - return (randomizeEndpoint ?? false) ? V.enabled : V.disabled - } - - public var uiDescriptionForXOR: String { - let V = L10n.Global.Values.self - guard let mask = xorMask, mask != 0 else { - return V.disabled - } - - return String(format: "0x%02x", UInt8(mask)) - } -} - -extension NetworkChoice: CustomStringConvertible { - public var description: String { - switch self { - case .client: - return L10n.NetworkChoice.client - - case .server: - return L10n.NetworkChoice.server - - case .manual: - return L10n.Global.Values.manual - } - } -} - -extension DNSProtocol: CustomStringConvertible { - public var description: String { - switch self { - case .plain: - return "Cleartext" - - case .https: - return "HTTPS" - - case .tls: - return "TLS" - } - } -} - -extension VPNStatus: UIDescriptible { - public var uiDescription: String { - switch self { - case .connecting: - return L10n.Vpn.connecting - - case .connected: - return L10n.Vpn.active - - case .disconnecting: - return L10n.Vpn.disconnecting - - case .disconnected: - return L10n.Vpn.inactive - } - } -} diff --git a/Passepartout/App/Scripts/build_wireguard_go_bridge.sh b/Passepartout/App/Scripts/build_wireguard_go_bridge.sh new file mode 100755 index 00000000..ac097f81 --- /dev/null +++ b/Passepartout/App/Scripts/build_wireguard_go_bridge.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +# build_wireguard_go_bridge.sh - Builds WireGuardKitGo +# +# Figures out the directory where the wireguard-apple SPM package +# is checked out by Xcode (so that it works when building as well as +# archiving), then cd-s to the WireGuardKitGo directory +# and runs make there. + +project_data_dir="$BUILD_DIR" + +# The wireguard-apple README suggests using ${BUILD_DIR%Build/*}, which +# doesn't seem to work. So here, we do the equivalent in script. + +while true; do + parent_dir=$(dirname "$project_data_dir") + basename=$(basename "$project_data_dir") + project_data_dir="$parent_dir" + if [ "$basename" = "Build" ]; then + break + fi +done + +# The wireguard-apple README looks into +# SourcePackages/checkouts/wireguard-apple, but Xcode seems to place the +# sources in SourcePackages/checkouts/ so just playing it safe and +# trying both. + +checkouts_dir="$project_data_dir"/SourcePackages/checkouts +if [ -e "$checkouts_dir"/wireguard-apple ]; then + checkouts_dir="$checkouts_dir"/wireguard-apple +fi + +wireguard_go_dir="$checkouts_dir"/Sources/WireGuardKitGo + +# To ensure we have Go in our path, we add where +# Homebrew generally installs executables +export PATH=${PATH}:/opt/homebrew/bin:/usr/local/bin:/usr/local/go/bin + +cd "$wireguard_go_dir" && /usr/bin/make diff --git a/Passepartout/App/Scripts/copy_coredata_codegen.sh b/Passepartout/App/Scripts/copy_coredata_codegen.sh new file mode 100755 index 00000000..b9e2130c --- /dev/null +++ b/Passepartout/App/Scripts/copy_coredata_codegen.sh @@ -0,0 +1,9 @@ +# Type a script or drag a script file from your workspace to insert its path. +CD_PROVIDERS_DIR="$PROJECT_TEMP_DIR/../PassepartoutCore.build/Debug-iphonesimulator/PassepartoutProviders.build/DerivedSources/CoreDataGenerated/Providers" +CD_CORE_DIR="$PROJECT_TEMP_DIR/../PassepartoutCore.build/Debug-iphonesimulator/PassepartoutCore.build/DerivedSources/CoreDataGenerated/Core" +if [ -d "$CD_PROVIDERS_DIR" ]; then + cp "$CD_PROVIDERS_DIR"/* "$PROJECT_DIR/PassepartoutCore/Sources/PassepartoutProviders/DataModels" +fi +if [ -d "$CD_CORE_DIR" ]; then + cp "$CD_CORE_DIR"/* "$PROJECT_DIR/PassepartoutCore/Sources/PassepartoutCore/DataModels" +fi diff --git a/Passepartout/App/macOS/App.entitlements b/Passepartout/App/Shared/App.entitlements similarity index 57% rename from Passepartout/App/macOS/App.entitlements rename to Passepartout/App/Shared/App.entitlements index 5a9e4913..1320c473 100644 --- a/Passepartout/App/macOS/App.entitlements +++ b/Passepartout/App/Shared/App.entitlements @@ -2,20 +2,36 @@ + aps-environment + development + com.apple.developer.icloud-container-identifiers + + iCloud.com.algoritmico.Passepartout + + com.apple.developer.icloud-services + + CloudKit + com.apple.developer.networking.networkextension packet-tunnel-provider + com.apple.developer.networking.wifi-info + + com.apple.developer.siri + com.apple.security.app-sandbox com.apple.security.application-groups - $(TeamIdentifierPrefix)group.$(CFG_GROUP_ID) + group.$(CFG_GROUP_ID) com.apple.security.files.user-selected.read-only com.apple.security.network.client + com.apple.security.personal-information.location + keychain-access-groups $(AppIdentifierPrefix)group.com.algoritmico.Passepartout diff --git a/Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png b/Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png similarity index 100% rename from Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png rename to Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png diff --git a/Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-120.png b/Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-120.png similarity index 100% rename from Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-120.png rename to Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-120.png diff --git a/Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-152.png b/Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-152.png similarity index 100% rename from Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-152.png rename to Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-152.png diff --git a/Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-167.png b/Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-167.png similarity index 100% rename from Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-167.png rename to Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-167.png diff --git a/Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-180.png b/Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-180.png similarity index 100% rename from Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-180.png rename to Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-180.png diff --git a/Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-76.png b/Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-76.png similarity index 100% rename from Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-76.png rename to Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/AppIcon-76.png diff --git a/Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Passepartout/App/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/Contents.json b/Passepartout/App/Shared/Assets.xcassets/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/Contents.json rename to Passepartout/App/Shared/Assets.xcassets/Contents.json diff --git a/Passepartout/App/Shared/Assets.xcassets/accentColor.colorset/Contents.json b/Passepartout/App/Shared/Assets.xcassets/accentColor.colorset/Contents.json new file mode 100644 index 00000000..06515a89 --- /dev/null +++ b/Passepartout/App/Shared/Assets.xcassets/accentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x68", + "green" : "0x9C", + "red" : "0xD6" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Passepartout/App/Shared/Assets.xcassets/lightTextColor.colorset/Contents.json b/Passepartout/App/Shared/Assets.xcassets/lightTextColor.colorset/Contents.json new file mode 100644 index 00000000..97650a1a --- /dev/null +++ b/Passepartout/App/Shared/Assets.xcassets/lightTextColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Passepartout/App/iOS/Assets.xcassets/logo.imageset/Contents.json b/Passepartout/App/Shared/Assets.xcassets/logo.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Assets.xcassets/logo.imageset/Contents.json rename to Passepartout/App/Shared/Assets.xcassets/logo.imageset/Contents.json diff --git a/Passepartout/App/iOS/Assets.xcassets/logo.imageset/logo@2x.png b/Passepartout/App/Shared/Assets.xcassets/logo.imageset/logo@2x.png similarity index 100% rename from Passepartout/App/iOS/Assets.xcassets/logo.imageset/logo@2x.png rename to Passepartout/App/Shared/Assets.xcassets/logo.imageset/logo@2x.png diff --git a/Passepartout/App/iOS/Assets.xcassets/logo.imageset/logo@3x.png b/Passepartout/App/Shared/Assets.xcassets/logo.imageset/logo@3x.png similarity index 100% rename from Passepartout/App/iOS/Assets.xcassets/logo.imageset/logo@3x.png rename to Passepartout/App/Shared/Assets.xcassets/logo.imageset/logo@3x.png diff --git a/Passepartout/App/Shared/Assets.xcassets/primaryColor.colorset/Contents.json b/Passepartout/App/Shared/Assets.xcassets/primaryColor.colorset/Contents.json new file mode 100644 index 00000000..546e2723 --- /dev/null +++ b/Passepartout/App/Shared/Assets.xcassets/primaryColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.443", + "green" : "0.365", + "red" : "0.318" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Passepartout/App/Shared/Constants/AppContext.swift b/Passepartout/App/Shared/Constants/AppContext.swift new file mode 100644 index 00000000..dff6eed9 --- /dev/null +++ b/Passepartout/App/Shared/Constants/AppContext.swift @@ -0,0 +1,208 @@ +// +// AppContext.swift +// Passepartout +// +// Created by Davide De Rosa on 3/17/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import Combine +import PassepartoutCore +import PassepartoutServices + +@MainActor +class AppContext { + static let shared = AppContext() + + private let persistenceManager: PersistenceManager + + let appManager: AppManager + + let providerManager: ProviderManager + + let profileManager: ProfileManager + + let vpnManager: VPNManager + + let productManager: ProductManager + + let intentsManager: IntentsManager + + let reviewer: Reviewer + + private var cancellables: Set = [] + + private init() { + + // core + + appManager = AppManager() + appManager.logLevel = Constants.Log.logLevel + appManager.logFile = Constants.Log.appFileURL + appManager.logFormat = Constants.Log.appLogFormat + appManager.tunnelLogFormat = Constants.Log.tunnelLogFormat + appManager.configureLogging() + pp_log.info("Logging to: \(appManager.logFile!)") + + persistenceManager = PersistenceManager(author: appManager.persistenceAuthor) + + providerManager = ProviderManager( + appBuild: Constants.Global.appBuildNumber, + bundleServices: DefaultWebServices.bundledServices( + withVersion: Constants.Services.version + ), + webServices: DefaultWebServices( + Constants.Services.version, + Constants.Repos.api, + timeout: Constants.Services.connectivityTimeout + ), + persistence: persistenceManager.providersPersistence( + withName: Constants.Persistence.providersContainerName + ) + ) + + profileManager = ProfileManager( + providerManager: providerManager, + appGroup: Constants.App.appGroupId, + keychainLabel: Unlocalized.Keychain.passwordLabel, + strategy: ProfileManager.CoreDataStrategy( + persistence: persistenceManager.profilesPersistence( + withName: Constants.Persistence.profilesContainerName + ) + ) + ) + + #if targetEnvironment(simulator) + vpnManager = VPNManager( + appManager: appManager, + profileManager: profileManager, + providerManager: providerManager, + strategy: VPNManager.MockStrategy() + ) + #else + vpnManager = VPNManager( + appManager: appManager, + profileManager: profileManager, + providerManager: providerManager, + strategy: VPNManager.TunnelKitStrategy( + appGroup: Constants.App.appGroupId, + tunnelBundleIdentifier: Constants.App.tunnelBundleId + ) + ) + #endif + + // app + + productManager = ProductManager(.init( + appType: Constants.InApp.appType, + lastFullVersionBuild: Constants.InApp.lastFullVersionBuild + )) + intentsManager = IntentsManager() + reviewer = Reviewer() + + // post + + configureObjects() + } + + private func configureObjects() { + + // core + + profileManager.availabilityFilter = { + self.isEligibleProfile(withHeader: $0) + } + profileManager.activeProfileId = appManager.activeProfileId + providerManager.rateLimitMilliseconds = Constants.RateLimit.providerManager + vpnManager.rateLimitMilliseconds = Constants.RateLimit.vpnManager + vpnManager.isOnDemandSupported = { + self.isEligibleForOnDemand() + } + + // app + + reviewer.eventCountBeforeRating = Constants.Rating.eventCount + vpnManager.currentState.$vpnStatus + .removeDuplicates() + .sink { + if $0 == .connected { + pp_log.info("VPN successful connection, report to Reviewer") + self.reviewer.reportEvent() + } + }.store(in: &cancellables) + } + + // eligibility: hide providers not found or not purchased + private func isEligibleProfile(withHeader header: Profile.Header) -> Bool { + guard let providerName = header.providerName else { + return true // always eligible for non-provider profiles + } + guard productManager.isEligible(forProvider: providerName) else { +// pp_log.debug("Not eligible for provider \(metadata.name)") + return false + } + return true + } + + // eligibility: reset on-demand rules if no trusted networks + private func isEligibleForOnDemand() -> Bool { + guard productManager.isEligible(forFeature: .trustedNetworks) else { + pp_log.warning("Ignore on-demand rules, not eligible for trusted networks") + return false + } + return true + } +} + +extension AppManager { + static let shared = AppContext.shared.appManager +} + +extension ProfileManager { + static let shared = AppContext.shared.profileManager +} + +extension ProviderManager { + static let shared = AppContext.shared.providerManager +} + +extension VPNManager { + static let shared = AppContext.shared.vpnManager +} + +extension ProductManager { + static let shared = AppContext.shared.productManager +} + +extension IntentsManager { + static let shared = AppContext.shared.intentsManager +} + +extension Reviewer { + static let shared = AppContext.shared.reviewer +} + +extension VPNManager.ObservableState { + + @MainActor + static let shared = AppContext.shared.vpnManager.currentState +} diff --git a/Passepartout/App/Shared/Constants/Constants+Extensions.swift b/Passepartout/App/Shared/Constants/Constants+Extensions.swift new file mode 100644 index 00000000..44f4e13b --- /dev/null +++ b/Passepartout/App/Shared/Constants/Constants+Extensions.swift @@ -0,0 +1,282 @@ +// +// Constants+Extensions.swift +// Passepartout +// +// Created by Davide De Rosa on 9/15/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import UniformTypeIdentifiers +import PassepartoutCore +import SwiftyBeaver + +extension Constants { + private static let bundleConfig = Bundle.main.infoDictionary?["com.algoritmico.Passepartout.config"] as? [String: Any] +} + +extension Constants { + enum App { + static let appLauncherId = bundleConfig?["app_launcher_id"] as? String ?? "DUMMY_app_launcher_id" + + static let appStoreId = bundleConfig?["appstore_id"] as? String ?? "DUMMY_appstore_id" + + static let appGroupId = bundleConfig?["group_id"] as? String ?? "DUMMY_group_id" + + static func tunnelBundleId(_ vpnProtocol: VPNProtocolType) -> String { + guard let identifier = Bundle.main.infoDictionary?[kCFBundleIdentifierKey as String] as? String else { + fatalError("Missing kCFBundleIdentifierKey from Info.plist") + } + let prefix = "\(identifier).Tunnel" + switch vpnProtocol { + case .openVPN: + return "\(prefix).OpenVPN" + + case .wireGuard: + return "\(prefix).WireGuard" + } + } + } + + enum InApp { + static var appType: ProductManager.AppType { + if let envString = ProcessInfo.processInfo.environment["APP_TYPE"], + let envValue = Int(envString), + let testAppType = ProductManager.AppType(rawValue: envValue) { + + return testAppType + } + if let infoValue = bundleConfig?["app_type"] as? Int, + let testAppType = ProductManager.AppType(rawValue: infoValue) { + + return testAppType + } + return isBeta ? .beta : .freemium + } + + #if os(iOS) + static let lastFullVersionBuild: (Int, LocalProduct) = (2016, .fullVersion_iOS) + #else + static let lastFullVersionBuild: (Int, LocalProduct) = (0, .fullVersion_macOS) + #endif + + private static var isBeta: Bool { + #if os(iOS) + #if targetEnvironment(simulator) + return true + #else + return Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" + #endif + #else + // TODO: production, skip TestFlight on macOS until beta condition is clearly determined + return false + #endif + } + } +} + +extension Constants { + enum Activities { + static let enableVPN = "EnableVPNIntent" + + static let disableVPN = "DisableVPNIntent" + + static let connectVPN = "ConnectVPNIntent" + + static let moveToLocation = "MoveToLocationIntent" + + static let trustCellularNetwork = "TrustCellularNetworkIntent" + + static let trustCurrentNetwork = "TrustCurrentNetworkIntent" + + static let untrustCellularNetwork = "UntrustCellularNetworkIntent" + + static let untrustCurrentNetwork = "UntrustCurrentNetworkIntent" + } +} + +extension Constants { + enum Domain { + static let name = "passepartoutvpn.app" + } + + enum Services { + static let version = "v5" + + private static let connectivityStrings: [String] = [ + "https://www.amazon.com", + "https://www.google.com", + "https://www.twitter.com", + "https://www.facebook.com", + "https://www.instagram.com" + ] + + static let connectivityURL = URL(string: connectivityStrings.randomElement()!)! + + static let connectivityTimeout: TimeInterval = 10.0 + } + + enum Persistence { + static let profilesContainerName = "Profiles" + + static let providersContainerName = "Providers" + } + + // milliseconds + enum RateLimit { + static let providerManager = 10000 + + static let vpnManager = 500 + } + + enum Log { + static let logLevel: SwiftyBeaver.Level = { + guard let levelString = ProcessInfo.processInfo.environment["LOG_LEVEL"], let levelNum = Int(levelString) else { + return .info + } + return .init(rawValue: levelNum) ?? .info + }() + + static let appLogFormat = "$DHH:mm:ss.SSS$d $C$L$c $N.$F:$l - $M" + + private static let appFileName = "Debug.log" + + static var appFileURL: URL { + return Files.cachesURL.appendingPathComponent(appFileName) + } + + static let tunnelLogFormat = "$DHH:mm:ss$d - $M" + + static let tunnelLogMaxBytes = 15000 + + static let tunnelLogRefreshInterval: TimeInterval = 5.0 + } + + enum URLs { + static let readme = Repos.apple.appendingPathComponent("blob/master/README.md") + + enum iOS { + static let changelog = Repos.apple.appendingPathComponent("blob/master/Passepartout/App/iOS/CHANGELOG.md") + } + + enum macOS { + static let changelog = Repos.apple.appendingPathComponent("blob/master/Passepartout/App/macOS/CHANGELOG.md") + } + + static let filetypes: [UTType] = [.item] + + static let website = URL(string: "https://\(Domain.name)")! + + static let faq = website.appendingPathComponent("faq") + + static let disclaimer = website.appendingPathComponent("disclaimer") + + static let privacyPolicy = website.appendingPathComponent("privacy") + + static let donate = website.appendingPathComponent("donate") + + static let subreddit = URL(string: "https://www.reddit.com/r/passepartout")! + + static let twitch = URL(string: "twitch://stream/keeshux")! + + static let twitchFallback = URL(string: "https://twitch.tv/keeshux")! + + static let githubSponsors = URL(string: "https://www.github.com/sponsors/passepartoutvpn")! + + static let alternativeTo = URL(string: "https://alternativeto.net/software/passepartout-vpn/about/")! + + static let openVPNGuidances: [ProviderName: String] = [ + .protonvpn: "https://account.protonvpn.com/settings", + .surfshark: "https://my.surfshark.com/vpn/manual-setup/main", + .torguard: "https://torguard.net/clientarea.php?action=changepw", + .windscribe: "https://windscribe.com/getconfig/openvpn" + ] + + static let referrals: [ProviderName: String] = [ + .hideme: "https://member.hide.me/en/checkout?plan=new_default_prices&coupon=6CB-BDB-802&duration=24", + .mullvad: "https://mullvad.net/en/account/create/", + .nordvpn: "https://go.nordvpn.net/SH21Z", + .pia: "https://www.privateinternetaccess.com/pages/buy-vpn/", + .protonvpn: "https://proton.go2cloud.org/SHZ", + .torguard: "https://torguard.net/", + .tunnelbear: "https://www.tunnelbear.com/", + .vyprvpn: "https://www.vyprvpn.com/", + .windscribe: "https://secure.link/kCsD0prd" + ] + } + + enum Repos { + private static let githubRoot = URL(string: "https://github.com/passepartoutvpn/")! + + private static let githubRawRoot = URL(string: "https://\(Domain.name)/")! + + private static func github(repo: String) -> URL { + return githubRoot.appendingPathComponent(repo) + } + + private static func githubRaw(repo: String) -> URL { + return githubRawRoot.appendingPathComponent(repo) + } + + static let apple = github(repo: "passepartout-apple") + + static let api = githubRaw(repo: "api") + } + + // milliseconds + enum Delays { + static let scrolling = 100 + +// @available(*, deprecated, message: "for weird animation when using withAnimation() in View.onAppear") + static let xxxAnimateOnAppear = 200 + +// @available(*, deprecated, message: "file importer stops showing again after closing with swipe down") + static let xxxPresentFileImporter = 200 + +// @available(*, deprecated, message: "edited shortcut is outdated in delegate") + static let xxxReloadEditedShortcut = 200 + } + + enum Rating { + #if os(iOS) + static let eventCount = 3 + #else + static let eventCount = 10 + #endif + } +} + +extension Constants { + enum Files { + private static var containerURL: URL { + guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: App.appGroupId) else { + print("Unable to access App Group container") + return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + } + return url + } + + static let cachesURL: URL = { + let url = containerURL.appendingPathComponent("Library/Caches", isDirectory: true) + try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) + return url + }() + } +} diff --git a/Passepartout/App/Shared/Constants/SwiftGen+Assets.swift b/Passepartout/App/Shared/Constants/SwiftGen+Assets.swift new file mode 100644 index 00000000..0fee27f5 --- /dev/null +++ b/Passepartout/App/Shared/Constants/SwiftGen+Assets.swift @@ -0,0 +1,418 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +#if os(macOS) + import AppKit +#elseif os(iOS) + import UIKit +#elseif os(tvOS) || os(watchOS) + import UIKit +#endif + +// Deprecated typealiases +@available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0") +internal typealias AssetColorTypeAlias = ColorAsset.Color +@available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0") +internal typealias AssetImageTypeAlias = ImageAsset.Image + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Asset Catalogs + +// swiftlint:disable identifier_name line_length nesting type_body_length type_name +internal enum Asset { + internal enum Assets { + internal static let accentColor = ColorAsset(name: "accentColor") + internal static let lightTextColor = ColorAsset(name: "lightTextColor") + internal static let logo = ImageAsset(name: "logo") + internal static let primaryColor = ColorAsset(name: "primaryColor") + } + internal enum Flags { + internal enum Flags { + internal static let ad = ImageAsset(name: "flags/ad") + internal static let ae = ImageAsset(name: "flags/ae") + internal static let af = ImageAsset(name: "flags/af") + internal static let ag = ImageAsset(name: "flags/ag") + internal static let ai = ImageAsset(name: "flags/ai") + internal static let al = ImageAsset(name: "flags/al") + internal static let am = ImageAsset(name: "flags/am") + internal static let ao = ImageAsset(name: "flags/ao") + internal static let aq = ImageAsset(name: "flags/aq") + internal static let ar = ImageAsset(name: "flags/ar") + internal static let `as` = ImageAsset(name: "flags/as") + internal static let at = ImageAsset(name: "flags/at") + internal static let au = ImageAsset(name: "flags/au") + internal static let aw = ImageAsset(name: "flags/aw") + internal static let ax = ImageAsset(name: "flags/ax") + internal static let az = ImageAsset(name: "flags/az") + internal static let ba = ImageAsset(name: "flags/ba") + internal static let bb = ImageAsset(name: "flags/bb") + internal static let bd = ImageAsset(name: "flags/bd") + internal static let be = ImageAsset(name: "flags/be") + internal static let bf = ImageAsset(name: "flags/bf") + internal static let bg = ImageAsset(name: "flags/bg") + internal static let bh = ImageAsset(name: "flags/bh") + internal static let bi = ImageAsset(name: "flags/bi") + internal static let bj = ImageAsset(name: "flags/bj") + internal static let bl = ImageAsset(name: "flags/bl") + internal static let bm = ImageAsset(name: "flags/bm") + internal static let bn = ImageAsset(name: "flags/bn") + internal static let bo = ImageAsset(name: "flags/bo") + internal static let bq = ImageAsset(name: "flags/bq") + internal static let br = ImageAsset(name: "flags/br") + internal static let bs = ImageAsset(name: "flags/bs") + internal static let bt = ImageAsset(name: "flags/bt") + internal static let bv = ImageAsset(name: "flags/bv") + internal static let bw = ImageAsset(name: "flags/bw") + internal static let by = ImageAsset(name: "flags/by") + internal static let bz = ImageAsset(name: "flags/bz") + internal static let ca = ImageAsset(name: "flags/ca") + internal static let cc = ImageAsset(name: "flags/cc") + internal static let cd = ImageAsset(name: "flags/cd") + internal static let cf = ImageAsset(name: "flags/cf") + internal static let cg = ImageAsset(name: "flags/cg") + internal static let ch = ImageAsset(name: "flags/ch") + internal static let ci = ImageAsset(name: "flags/ci") + internal static let ck = ImageAsset(name: "flags/ck") + internal static let cl = ImageAsset(name: "flags/cl") + internal static let cm = ImageAsset(name: "flags/cm") + internal static let cn = ImageAsset(name: "flags/cn") + internal static let co = ImageAsset(name: "flags/co") + internal static let cr = ImageAsset(name: "flags/cr") + internal static let cu = ImageAsset(name: "flags/cu") + internal static let cv = ImageAsset(name: "flags/cv") + internal static let cw = ImageAsset(name: "flags/cw") + internal static let cx = ImageAsset(name: "flags/cx") + internal static let cy = ImageAsset(name: "flags/cy") + internal static let cz = ImageAsset(name: "flags/cz") + internal static let de = ImageAsset(name: "flags/de") + internal static let dj = ImageAsset(name: "flags/dj") + internal static let dk = ImageAsset(name: "flags/dk") + internal static let dm = ImageAsset(name: "flags/dm") + internal static let `do` = ImageAsset(name: "flags/do") + internal static let dz = ImageAsset(name: "flags/dz") + internal static let ec = ImageAsset(name: "flags/ec") + internal static let ee = ImageAsset(name: "flags/ee") + internal static let eg = ImageAsset(name: "flags/eg") + internal static let eh = ImageAsset(name: "flags/eh") + internal static let er = ImageAsset(name: "flags/er") + internal static let esCt = ImageAsset(name: "flags/es-ct") + internal static let es = ImageAsset(name: "flags/es") + internal static let et = ImageAsset(name: "flags/et") + internal static let eu = ImageAsset(name: "flags/eu") + internal static let fi = ImageAsset(name: "flags/fi") + internal static let fj = ImageAsset(name: "flags/fj") + internal static let fk = ImageAsset(name: "flags/fk") + internal static let fm = ImageAsset(name: "flags/fm") + internal static let fo = ImageAsset(name: "flags/fo") + internal static let fr = ImageAsset(name: "flags/fr") + internal static let ga = ImageAsset(name: "flags/ga") + internal static let gbEng = ImageAsset(name: "flags/gb-eng") + internal static let gbNir = ImageAsset(name: "flags/gb-nir") + internal static let gbSct = ImageAsset(name: "flags/gb-sct") + internal static let gbWls = ImageAsset(name: "flags/gb-wls") + internal static let gb = ImageAsset(name: "flags/gb") + internal static let gd = ImageAsset(name: "flags/gd") + internal static let ge = ImageAsset(name: "flags/ge") + internal static let gf = ImageAsset(name: "flags/gf") + internal static let gg = ImageAsset(name: "flags/gg") + internal static let gh = ImageAsset(name: "flags/gh") + internal static let gi = ImageAsset(name: "flags/gi") + internal static let gl = ImageAsset(name: "flags/gl") + internal static let gm = ImageAsset(name: "flags/gm") + internal static let gn = ImageAsset(name: "flags/gn") + internal static let gp = ImageAsset(name: "flags/gp") + internal static let gq = ImageAsset(name: "flags/gq") + internal static let gr = ImageAsset(name: "flags/gr") + internal static let gs = ImageAsset(name: "flags/gs") + internal static let gt = ImageAsset(name: "flags/gt") + internal static let gu = ImageAsset(name: "flags/gu") + internal static let gw = ImageAsset(name: "flags/gw") + internal static let gy = ImageAsset(name: "flags/gy") + internal static let hk = ImageAsset(name: "flags/hk") + internal static let hm = ImageAsset(name: "flags/hm") + internal static let hn = ImageAsset(name: "flags/hn") + internal static let hr = ImageAsset(name: "flags/hr") + internal static let ht = ImageAsset(name: "flags/ht") + internal static let hu = ImageAsset(name: "flags/hu") + internal static let id = ImageAsset(name: "flags/id") + internal static let ie = ImageAsset(name: "flags/ie") + internal static let il = ImageAsset(name: "flags/il") + internal static let im = ImageAsset(name: "flags/im") + internal static let `in` = ImageAsset(name: "flags/in") + internal static let io = ImageAsset(name: "flags/io") + internal static let iq = ImageAsset(name: "flags/iq") + internal static let ir = ImageAsset(name: "flags/ir") + internal static let `is` = ImageAsset(name: "flags/is") + internal static let it = ImageAsset(name: "flags/it") + internal static let je = ImageAsset(name: "flags/je") + internal static let jm = ImageAsset(name: "flags/jm") + internal static let jo = ImageAsset(name: "flags/jo") + internal static let jp = ImageAsset(name: "flags/jp") + internal static let ke = ImageAsset(name: "flags/ke") + internal static let kg = ImageAsset(name: "flags/kg") + internal static let kh = ImageAsset(name: "flags/kh") + internal static let ki = ImageAsset(name: "flags/ki") + internal static let km = ImageAsset(name: "flags/km") + internal static let kn = ImageAsset(name: "flags/kn") + internal static let kp = ImageAsset(name: "flags/kp") + internal static let kr = ImageAsset(name: "flags/kr") + internal static let kw = ImageAsset(name: "flags/kw") + internal static let ky = ImageAsset(name: "flags/ky") + internal static let kz = ImageAsset(name: "flags/kz") + internal static let la = ImageAsset(name: "flags/la") + internal static let lb = ImageAsset(name: "flags/lb") + internal static let lc = ImageAsset(name: "flags/lc") + internal static let li = ImageAsset(name: "flags/li") + internal static let lk = ImageAsset(name: "flags/lk") + internal static let lr = ImageAsset(name: "flags/lr") + internal static let ls = ImageAsset(name: "flags/ls") + internal static let lt = ImageAsset(name: "flags/lt") + internal static let lu = ImageAsset(name: "flags/lu") + internal static let lv = ImageAsset(name: "flags/lv") + internal static let ly = ImageAsset(name: "flags/ly") + internal static let ma = ImageAsset(name: "flags/ma") + internal static let mc = ImageAsset(name: "flags/mc") + internal static let md = ImageAsset(name: "flags/md") + internal static let me = ImageAsset(name: "flags/me") + internal static let mf = ImageAsset(name: "flags/mf") + internal static let mg = ImageAsset(name: "flags/mg") + internal static let mh = ImageAsset(name: "flags/mh") + internal static let mk = ImageAsset(name: "flags/mk") + internal static let ml = ImageAsset(name: "flags/ml") + internal static let mm = ImageAsset(name: "flags/mm") + internal static let mn = ImageAsset(name: "flags/mn") + internal static let mo = ImageAsset(name: "flags/mo") + internal static let mp = ImageAsset(name: "flags/mp") + internal static let mq = ImageAsset(name: "flags/mq") + internal static let mr = ImageAsset(name: "flags/mr") + internal static let ms = ImageAsset(name: "flags/ms") + internal static let mt = ImageAsset(name: "flags/mt") + internal static let mu = ImageAsset(name: "flags/mu") + internal static let mv = ImageAsset(name: "flags/mv") + internal static let mw = ImageAsset(name: "flags/mw") + internal static let mx = ImageAsset(name: "flags/mx") + internal static let my = ImageAsset(name: "flags/my") + internal static let mz = ImageAsset(name: "flags/mz") + internal static let na = ImageAsset(name: "flags/na") + internal static let nc = ImageAsset(name: "flags/nc") + internal static let ne = ImageAsset(name: "flags/ne") + internal static let nf = ImageAsset(name: "flags/nf") + internal static let ng = ImageAsset(name: "flags/ng") + internal static let ni = ImageAsset(name: "flags/ni") + internal static let nl = ImageAsset(name: "flags/nl") + internal static let no = ImageAsset(name: "flags/no") + internal static let np = ImageAsset(name: "flags/np") + internal static let nr = ImageAsset(name: "flags/nr") + internal static let nu = ImageAsset(name: "flags/nu") + internal static let nz = ImageAsset(name: "flags/nz") + internal static let om = ImageAsset(name: "flags/om") + internal static let pa = ImageAsset(name: "flags/pa") + internal static let pe = ImageAsset(name: "flags/pe") + internal static let pf = ImageAsset(name: "flags/pf") + internal static let pg = ImageAsset(name: "flags/pg") + internal static let ph = ImageAsset(name: "flags/ph") + internal static let pk = ImageAsset(name: "flags/pk") + internal static let pl = ImageAsset(name: "flags/pl") + internal static let pm = ImageAsset(name: "flags/pm") + internal static let pn = ImageAsset(name: "flags/pn") + internal static let pr = ImageAsset(name: "flags/pr") + internal static let ps = ImageAsset(name: "flags/ps") + internal static let pt = ImageAsset(name: "flags/pt") + internal static let pw = ImageAsset(name: "flags/pw") + internal static let py = ImageAsset(name: "flags/py") + internal static let qa = ImageAsset(name: "flags/qa") + internal static let re = ImageAsset(name: "flags/re") + internal static let ro = ImageAsset(name: "flags/ro") + internal static let rs = ImageAsset(name: "flags/rs") + internal static let ru = ImageAsset(name: "flags/ru") + internal static let rw = ImageAsset(name: "flags/rw") + internal static let sa = ImageAsset(name: "flags/sa") + internal static let sb = ImageAsset(name: "flags/sb") + internal static let sc = ImageAsset(name: "flags/sc") + internal static let sd = ImageAsset(name: "flags/sd") + internal static let se = ImageAsset(name: "flags/se") + internal static let sg = ImageAsset(name: "flags/sg") + internal static let sh = ImageAsset(name: "flags/sh") + internal static let si = ImageAsset(name: "flags/si") + internal static let sj = ImageAsset(name: "flags/sj") + internal static let sk = ImageAsset(name: "flags/sk") + internal static let sl = ImageAsset(name: "flags/sl") + internal static let sm = ImageAsset(name: "flags/sm") + internal static let sn = ImageAsset(name: "flags/sn") + internal static let so = ImageAsset(name: "flags/so") + internal static let sr = ImageAsset(name: "flags/sr") + internal static let ss = ImageAsset(name: "flags/ss") + internal static let st = ImageAsset(name: "flags/st") + internal static let sv = ImageAsset(name: "flags/sv") + internal static let sx = ImageAsset(name: "flags/sx") + internal static let sy = ImageAsset(name: "flags/sy") + internal static let sz = ImageAsset(name: "flags/sz") + internal static let tc = ImageAsset(name: "flags/tc") + internal static let td = ImageAsset(name: "flags/td") + internal static let tf = ImageAsset(name: "flags/tf") + internal static let tg = ImageAsset(name: "flags/tg") + internal static let th = ImageAsset(name: "flags/th") + internal static let tj = ImageAsset(name: "flags/tj") + internal static let tk = ImageAsset(name: "flags/tk") + internal static let tl = ImageAsset(name: "flags/tl") + internal static let tm = ImageAsset(name: "flags/tm") + internal static let tn = ImageAsset(name: "flags/tn") + internal static let to = ImageAsset(name: "flags/to") + internal static let tr = ImageAsset(name: "flags/tr") + internal static let tt = ImageAsset(name: "flags/tt") + internal static let tv = ImageAsset(name: "flags/tv") + internal static let tw = ImageAsset(name: "flags/tw") + internal static let tz = ImageAsset(name: "flags/tz") + internal static let ua = ImageAsset(name: "flags/ua") + internal static let ug = ImageAsset(name: "flags/ug") + internal static let um = ImageAsset(name: "flags/um") + internal static let un = ImageAsset(name: "flags/un") + internal static let us = ImageAsset(name: "flags/us") + internal static let uy = ImageAsset(name: "flags/uy") + internal static let uz = ImageAsset(name: "flags/uz") + internal static let va = ImageAsset(name: "flags/va") + internal static let vc = ImageAsset(name: "flags/vc") + internal static let ve = ImageAsset(name: "flags/ve") + internal static let vg = ImageAsset(name: "flags/vg") + internal static let vi = ImageAsset(name: "flags/vi") + internal static let vn = ImageAsset(name: "flags/vn") + internal static let vu = ImageAsset(name: "flags/vu") + internal static let wf = ImageAsset(name: "flags/wf") + internal static let ws = ImageAsset(name: "flags/ws") + internal static let xk = ImageAsset(name: "flags/xk") + internal static let ye = ImageAsset(name: "flags/ye") + internal static let yt = ImageAsset(name: "flags/yt") + internal static let za = ImageAsset(name: "flags/za") + internal static let zm = ImageAsset(name: "flags/zm") + internal static let zw = ImageAsset(name: "flags/zw") + } + } + internal enum Providers { + internal enum Providers { + internal static let hideme = ImageAsset(name: "providers/hideme") + internal static let mullvad = ImageAsset(name: "providers/mullvad") + internal static let nordvpn = ImageAsset(name: "providers/nordvpn") + internal static let oeck = ImageAsset(name: "providers/oeck") + internal static let pia = ImageAsset(name: "providers/pia") + internal static let placeholder = ImageAsset(name: "providers/placeholder") + internal static let protonvpn = ImageAsset(name: "providers/protonvpn") + internal static let surfshark = ImageAsset(name: "providers/surfshark") + internal static let torguard = ImageAsset(name: "providers/torguard") + internal static let tunnelbear = ImageAsset(name: "providers/tunnelbear") + internal static let vyprvpn = ImageAsset(name: "providers/vyprvpn") + internal static let windscribe = ImageAsset(name: "providers/windscribe") + } + } +} +// swiftlint:enable identifier_name line_length nesting type_body_length type_name + +// MARK: - Implementation Details + +internal final class ColorAsset { + internal fileprivate(set) var name: String + + #if os(macOS) + internal typealias Color = NSColor + #elseif os(iOS) || os(tvOS) || os(watchOS) + internal typealias Color = UIColor + #endif + + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + internal private(set) lazy var color: Color = Color(asset: self) + + #if os(iOS) || os(tvOS) + @available(iOS 11.0, tvOS 11.0, *) + internal func color(compatibleWith traitCollection: UITraitCollection) -> Color { + let bundle = BundleToken.bundle + guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load color asset named \(name).") + } + return color + } + #endif + + fileprivate init(name: String) { + self.name = name + } +} + +internal extension ColorAsset.Color { + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + convenience init!(asset: ColorAsset) { + let bundle = BundleToken.bundle + #if os(iOS) || os(tvOS) + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSColor.Name(asset.name), bundle: bundle) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} + +internal struct ImageAsset { + internal fileprivate(set) var name: String + + #if os(macOS) + internal typealias Image = NSImage + #elseif os(iOS) || os(tvOS) || os(watchOS) + internal typealias Image = UIImage + #endif + + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) + internal var image: Image { + let bundle = BundleToken.bundle + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + let name = NSImage.Name(self.name) + let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + + #if os(iOS) || os(tvOS) + @available(iOS 8.0, tvOS 9.0, *) + internal func image(compatibleWith traitCollection: UITraitCollection) -> Image { + let bundle = BundleToken.bundle + guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + #endif +} + +internal extension ImageAsset.Image { + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) + @available(macOS, deprecated, + message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") + convenience init!(asset: ImageAsset) { + #if os(iOS) || os(tvOS) + let bundle = BundleToken.bundle + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSImage.Name(asset.name)) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/Passepartout/App/Shared/Constants/SwiftGen+Strings.swift b/Passepartout/App/Shared/Constants/SwiftGen+Strings.swift new file mode 100644 index 00000000..a53909ed --- /dev/null +++ b/Passepartout/App/Shared/Constants/SwiftGen+Strings.swift @@ -0,0 +1,1114 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +internal enum L10n { + + internal enum About { + /// About + internal static let title = L10n.tr("Localizable", "about.title") + internal enum Items { + internal enum Credits { + /// Credits + internal static let caption = L10n.tr("Localizable", "about.items.credits.caption") + } + internal enum Disclaimer { + /// Disclaimer + internal static let caption = L10n.tr("Localizable", "about.items.disclaimer.caption") + } + internal enum PrivacyPolicy { + /// Privacy policy + internal static let caption = L10n.tr("Localizable", "about.items.privacy_policy.caption") + } + internal enum ShareGeneric { + /// Invite a friend + internal static let caption = L10n.tr("Localizable", "about.items.share_generic.caption") + } + internal enum ShareTwitter { + /// Tweet about it! + internal static let caption = L10n.tr("Localizable", "about.items.share_twitter.caption") + } + internal enum Website { + /// Home page + internal static let caption = L10n.tr("Localizable", "about.items.website.caption") + } + } + internal enum Sections { + internal enum Share { + /// Share + internal static let header = L10n.tr("Localizable", "about.sections.share.header") + } + internal enum Web { + /// Web + internal static let header = L10n.tr("Localizable", "about.sections.web.header") + } + } + } + + internal enum Account { + /// Account + internal static let title = L10n.tr("Localizable", "account.title") + internal enum Items { + internal enum OpenGuide { + /// See your credentials + internal static let caption = L10n.tr("Localizable", "account.items.open_guide.caption") + } + internal enum Password { + /// Password + internal static let caption = L10n.tr("Localizable", "account.items.password.caption") + /// secret + internal static let placeholder = L10n.tr("Localizable", "account.items.password.placeholder") + } + internal enum Signup { + /// Register with %@ + internal static func caption(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.items.signup.caption", String(describing: p1)) + } + } + internal enum Username { + /// Username + internal static let caption = L10n.tr("Localizable", "account.items.username.caption") + /// username + internal static let placeholder = L10n.tr("Localizable", "account.items.username.placeholder") + } + } + internal enum Sections { + internal enum Credentials { + /// Credentials + internal static let header = L10n.tr("Localizable", "account.sections.credentials.header") + } + internal enum Guidance { + internal enum Footer { + internal enum Infrastructure { + /// Use your %@ website credentials. Your username is usually numeric (without spaces). + internal static func mullvad(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.sections.guidance.footer.infrastructure.mullvad", String(describing: p1)) + } + /// Use your %@ website credentials. Your username is usually your e-mail. + internal static func nordvpn(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.sections.guidance.footer.infrastructure.nordvpn", String(describing: p1)) + } + /// Use your %@ website credentials. Your username is usually numeric with a "p" prefix. + internal static func pia(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.sections.guidance.footer.infrastructure.pia", String(describing: p1)) + } + /// Find your %@ credentials in the "Account > OpenVPN / IKEv2 Username" section of the website. + internal static func protonvpn(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.sections.guidance.footer.infrastructure.protonvpn", String(describing: p1)) + } + /// Use your %@ website credentials. Your username is usually your e-mail. + internal static func tunnelbear(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.sections.guidance.footer.infrastructure.tunnelbear", String(describing: p1)) + } + /// Use your %@ website credentials. Your username is usually your e-mail. + internal static func vyprvpn(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.sections.guidance.footer.infrastructure.vyprvpn", String(describing: p1)) + } + /// Find your %@ credentials in the OpenVPN Config Generator on the website. + internal static func windscribe(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.sections.guidance.footer.infrastructure.windscribe", String(describing: p1)) + } + internal enum Default { + /// Use your %@ service credentials, which may differ from website credentials. + internal static func specific(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.sections.guidance.footer.infrastructure.default.specific", String(describing: p1)) + } + /// Use your %@ website credentials. + internal static func web(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.sections.guidance.footer.infrastructure.default.web", String(describing: p1)) + } + } + } + } + } + internal enum Registration { + /// Go get an account on the %@ website. + internal static func footer(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.sections.registration.footer", String(describing: p1)) + } + } + } + } + + internal enum AddProfile { + internal enum Host { + internal enum Sections { + internal enum Encryption { + /// Enter passphrase + internal static let footer = L10n.tr("Localizable", "add_profile.host.sections.encryption.footer") + } + } + } + internal enum Provider { + internal enum Errors { + /// Could not find any server. + internal static let noDefaultServer = L10n.tr("Localizable", "add_profile.provider.errors.no_default_server") + } + internal enum Items { + /// Update list + internal static let updateList = L10n.tr("Localizable", "add_profile.provider.items.update_list") + } + internal enum Sections { + internal enum Providers { + /// Providers + internal static let header = L10n.tr("Localizable", "add_profile.provider.sections.providers.header") + } + internal enum Vpn { + /// Here you find a few providers with preset configuration profiles. + internal static let footer = L10n.tr("Localizable", "add_profile.provider.sections.vpn.footer") + } + } + } + internal enum Shared { + /// New profile + internal static let title = L10n.tr("Localizable", "add_profile.shared.title") + internal enum Alerts { + internal enum Overwrite { + /// A profile with the same name already exists. Replace it? + internal static let message = L10n.tr("Localizable", "add_profile.shared.alerts.overwrite.message") + } + } + internal enum Views { + internal enum Existing { + /// Existing profiles + internal static let header = L10n.tr("Localizable", "add_profile.shared.views.existing.header") + } + } + } + } + + internal enum Credits { + /// Credits + internal static let title = L10n.tr("Localizable", "credits.title") + internal enum Sections { + internal enum Licenses { + /// Licenses + internal static let header = L10n.tr("Localizable", "credits.sections.licenses.header") + } + internal enum Notices { + /// Notices + internal static let header = L10n.tr("Localizable", "credits.sections.notices.header") + } + } + } + + internal enum DebugLog { + /// Debug log + internal static let title = L10n.tr("Localizable", "debug_log.title") + internal enum Buttons { + /// Copy + internal static let copy = L10n.tr("Localizable", "debug_log.buttons.copy") + } + } + + internal enum Diagnostics { + /// Diagnostics + internal static let title = L10n.tr("Localizable", "diagnostics.title") + internal enum Alerts { + internal enum MasksPrivateData { + internal enum Messages { + /// In order to safely reset the current debug log and apply the new masking preference, you must reconnect to the VPN now. + internal static let mustReconnect = L10n.tr("Localizable", "diagnostics.alerts.masks_private_data.messages.must_reconnect") + } + } + } + internal enum Items { + internal enum MasksPrivateData { + /// Mask network data + internal static let caption = L10n.tr("Localizable", "diagnostics.items.masks_private_data.caption") + } + internal enum ReportIssue { + /// Report connectivity issue + internal static let caption = L10n.tr("Localizable", "diagnostics.items.report_issue.caption") + } + internal enum ServerConfiguration { + /// Server configuration + internal static let caption = L10n.tr("Localizable", "diagnostics.items.server_configuration.caption") + } + } + internal enum Sections { + internal enum DebugLog { + /// Masking status will be effective after reconnecting. Network data are hostnames, IP addresses, routing, SSID. Credentials and private keys are not logged regardless. + internal static let footer = L10n.tr("Localizable", "diagnostics.sections.debug_log.footer") + } + } + } + + internal enum Donate { + /// Donate + internal static let title = L10n.tr("Localizable", "donate.title") + internal enum Alerts { + internal enum Purchase { + internal enum Failure { + /// Unable to perform the donation. %@ + internal static func message(_ p1: Any) -> String { + return L10n.tr("Localizable", "donate.alerts.purchase.failure.message", String(describing: p1)) + } + } + internal enum Success { + /// This means a lot to me and I really hope you keep using and promoting this app. + internal static let message = L10n.tr("Localizable", "donate.alerts.purchase.success.message") + /// Thank you + internal static let title = L10n.tr("Localizable", "donate.alerts.purchase.success.title") + } + } + } + internal enum Items { + internal enum Loading { + /// Loading donations + internal static let caption = L10n.tr("Localizable", "donate.items.loading.caption") + } + internal enum Purchasing { + /// Performing donation + internal static let caption = L10n.tr("Localizable", "donate.items.purchasing.caption") + } + } + internal enum Sections { + internal enum OneTime { + /// If you want to display gratitude for my free work, here are a couple amounts you can donate instantly. + /// + /// You will only be charged once per donation, and you can donate multiple times. + internal static let footer = L10n.tr("Localizable", "donate.sections.one_time.footer") + /// One time + internal static let header = L10n.tr("Localizable", "donate.sections.one_time.header") + } + } + } + + internal enum Endpoint { + internal enum Advanced { + /// Technical details + internal static let title = L10n.tr("Localizable", "endpoint.advanced.title") + internal enum Openvpn { + internal enum Items { + internal enum Cipher { + /// Cipher + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.cipher.caption") + } + internal enum Client { + /// Certificate + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.client.caption") + internal enum Value { + /// Not verified + internal static let disabled = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.client.value.disabled") + /// Verified + internal static let enabled = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.client.value.enabled") + } + } + internal enum ClientKey { + /// Key + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.client_key.caption") + } + internal enum CompressionAlgorithm { + /// Algorithm + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.compression_algorithm.caption") + internal enum Value { + /// Unsupported + internal static let other = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.compression_algorithm.value.other") + } + } + internal enum CompressionFraming { + /// Framing + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.compression_framing.caption") + } + internal enum Digest { + /// Authentication + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.digest.caption") + internal enum Value { + /// Embedded + internal static let embedded = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.digest.value.embedded") + } + } + internal enum Eku { + /// Extended verification + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.eku.caption") + } + internal enum KeepAlive { + internal enum Value { + /// %d seconds + internal static func seconds(_ p1: Int) -> String { + return L10n.tr("Localizable", "endpoint.advanced.openvpn.items.keep_alive.value.seconds", p1) + } + } + } + internal enum RandomEndpoint { + /// Randomize endpoint + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.random_endpoint.caption") + } + internal enum RenegotiationSeconds { + /// Renegotiation + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.renegotiation_seconds.caption") + internal enum Value { + /// after %@ + internal static func after(_ p1: Any) -> String { + return L10n.tr("Localizable", "endpoint.advanced.openvpn.items.renegotiation_seconds.value.after", String(describing: p1)) + } + } + } + internal enum ResetOriginal { + /// Reset configuration + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.reset_original.caption") + } + internal enum Route { + /// Route + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.route.caption") + } + internal enum TlsWrapping { + /// Wrapping + internal static let caption = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.tls_wrapping.caption") + internal enum Value { + /// Authentication + internal static let auth = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.tls_wrapping.value.auth") + /// Encryption + internal static let crypt = L10n.tr("Localizable", "endpoint.advanced.openvpn.items.tls_wrapping.value.crypt") + } + } + } + internal enum Sections { + internal enum Communication { + /// Communication + internal static let header = L10n.tr("Localizable", "endpoint.advanced.openvpn.sections.communication.header") + } + internal enum Compression { + /// Compression + internal static let header = L10n.tr("Localizable", "endpoint.advanced.openvpn.sections.compression.header") + } + internal enum Network { + /// Network + internal static let header = L10n.tr("Localizable", "endpoint.advanced.openvpn.sections.network.header") + } + internal enum Other { + /// Other + internal static let header = L10n.tr("Localizable", "endpoint.advanced.openvpn.sections.other.header") + } + internal enum Reset { + /// If you ended up with broken connectivity after changing the communication parameters, tap to revert to the original configuration. + internal static let footer = L10n.tr("Localizable", "endpoint.advanced.openvpn.sections.reset.footer") + } + } + } + } + internal enum Wireguard { + internal enum Items { + internal enum AllowedIp { + /// Allowed IP + internal static let caption = L10n.tr("Localizable", "endpoint.wireguard.items.allowed_ip.caption") + } + internal enum Peer { + /// Peer + internal static let caption = L10n.tr("Localizable", "endpoint.wireguard.items.peer.caption") + } + internal enum PresharedKey { + /// Preshared key + internal static let caption = L10n.tr("Localizable", "endpoint.wireguard.items.preshared_key.caption") + } + } + } + } + + internal enum Global { + internal enum Errors { + /// Missing account + internal static let missingAccount = L10n.tr("Localizable", "global.errors.missing_account") + /// Missing profile + internal static let missingProfile = L10n.tr("Localizable", "global.errors.missing_profile") + /// Missing preset + internal static let missingProviderPreset = L10n.tr("Localizable", "global.errors.missing_provider_preset") + /// Missing server + internal static let missingProviderServer = L10n.tr("Localizable", "global.errors.missing_provider_server") + } + internal enum Messages { + /// No e-mail account is configured. + internal static let emailNotConfigured = L10n.tr("Localizable", "global.messages.email_not_configured") + /// Passepartout is an user-friendly, open source OpenVPN client for iOS and macOS + internal static let share = L10n.tr("Localizable", "global.messages.share") + } + internal enum Placeholders { + /// My profile + internal static let profileName = L10n.tr("Localizable", "global.placeholders.profile_name") + } + internal enum Strings { + /// Add + internal static let add = L10n.tr("Localizable", "global.strings.add") + /// Address + internal static let address = L10n.tr("Localizable", "global.strings.address") + /// Addresses + internal static let addresses = L10n.tr("Localizable", "global.strings.addresses") + /// Advanced + internal static let advanced = L10n.tr("Localizable", "global.strings.advanced") + /// Automatic + internal static let automatic = L10n.tr("Localizable", "global.strings.automatic") + /// Bytes + internal static let bytes = L10n.tr("Localizable", "global.strings.bytes") + /// Cancel + internal static let cancel = L10n.tr("Localizable", "global.strings.cancel") + /// Default + internal static let `default` = L10n.tr("Localizable", "global.strings.default") + /// Disabled + internal static let disabled = L10n.tr("Localizable", "global.strings.disabled") + /// Domain + internal static let domain = L10n.tr("Localizable", "global.strings.domain") + /// Domains + internal static let domains = L10n.tr("Localizable", "global.strings.domains") + /// Enabled + internal static let enabled = L10n.tr("Localizable", "global.strings.enabled") + /// Encryption + internal static let encryption = L10n.tr("Localizable", "global.strings.encryption") + /// Endpoint + internal static let endpoint = L10n.tr("Localizable", "global.strings.endpoint") + /// Interface + internal static let interface = L10n.tr("Localizable", "global.strings.interface") + /// Keep-alive + internal static let keepalive = L10n.tr("Localizable", "global.strings.keepalive") + /// Manual + internal static let manual = L10n.tr("Localizable", "global.strings.manual") + /// Name + internal static let name = L10n.tr("Localizable", "global.strings.name") + /// Next + internal static let next = L10n.tr("Localizable", "global.strings.next") + /// None + internal static let `none` = L10n.tr("Localizable", "global.strings.none") + /// OK + internal static let ok = L10n.tr("Localizable", "global.strings.ok") + /// Port + internal static let port = L10n.tr("Localizable", "global.strings.port") + /// Private key + internal static let privateKey = L10n.tr("Localizable", "global.strings.private_key") + /// Protocol + internal static let `protocol` = L10n.tr("Localizable", "global.strings.protocol") + /// Protocols + internal static let protocols = L10n.tr("Localizable", "global.strings.protocols") + /// Proxy + internal static let proxy = L10n.tr("Localizable", "global.strings.proxy") + /// Public key + internal static let publicKey = L10n.tr("Localizable", "global.strings.public_key") + /// Reconnect + internal static let reconnect = L10n.tr("Localizable", "global.strings.reconnect") + /// Rename + internal static let rename = L10n.tr("Localizable", "global.strings.rename") + /// Save + internal static let save = L10n.tr("Localizable", "global.strings.save") + /// Servers + internal static let servers = L10n.tr("Localizable", "global.strings.servers") + /// Translations + internal static let translations = L10n.tr("Localizable", "global.strings.translations") + } + } + + internal enum Menu { + internal enum ActiveProfile { + internal enum Items { + internal enum Customize { + /// Customize... + internal static let title = L10n.tr("Localizable", "menu.active_profile.items.customize.title") + } + } + internal enum Messages { + /// No account configured + internal static let missingCredentials = L10n.tr("Localizable", "menu.active_profile.messages.missing_credentials") + } + internal enum Title { + /// No active profile + internal static let `none` = L10n.tr("Localizable", "menu.active_profile.title.none") + } + } + internal enum Organizer { + /// Organizer + internal static let title = L10n.tr("Localizable", "menu.organizer.title") + } + internal enum Preferences { + /// Preferences + internal static let title = L10n.tr("Localizable", "menu.preferences.title") + } + internal enum Quit { + /// Quit %@ + internal static func title(_ p1: Any) -> String { + return L10n.tr("Localizable", "menu.quit.title", String(describing: p1)) + } + internal enum Messages { + /// The VPN, if enabled, will still run in the background. Do you want to quit? + internal static let confirm = L10n.tr("Localizable", "menu.quit.messages.confirm") + } + } + internal enum Show { + /// Show + internal static let title = L10n.tr("Localizable", "menu.show.title") + } + internal enum Support { + /// Support + internal static let title = L10n.tr("Localizable", "menu.support.title") + } + internal enum SwitchProfile { + /// Active profile + internal static let title = L10n.tr("Localizable", "menu.switch_profile.title") + } + } + + internal enum NetworkSettings { + /// Network settings + internal static let title = L10n.tr("Localizable", "network_settings.title") + internal enum Gateway { + /// Default gateway + internal static let title = L10n.tr("Localizable", "network_settings.gateway.title") + } + internal enum Items { + internal enum AddDnsDomain { + /// Add search domain + internal static let caption = L10n.tr("Localizable", "network_settings.items.add_dns_domain.caption") + } + internal enum AddDnsServer { + /// Add address + internal static let caption = L10n.tr("Localizable", "network_settings.items.add_dns_server.caption") + } + internal enum AddProxyBypass { + /// Add bypass domain + internal static let caption = L10n.tr("Localizable", "network_settings.items.add_proxy_bypass.caption") + } + internal enum ProxyBypass { + /// Bypass domain + internal static let caption = L10n.tr("Localizable", "network_settings.items.proxy_bypass.caption") + } + } + internal enum Proxy { + internal enum Items { + internal enum BypassDomains { + /// Bypass domains + internal static let caption = L10n.tr("Localizable", "network_settings.proxy.items.bypass_domains.caption") + } + } + } + internal enum Sections { + internal enum Choices { + /// Override + internal static let header = L10n.tr("Localizable", "network_settings.sections.choices.header") + } + } + } + + internal enum OnDemand { + /// Trusted networks + internal static let title = L10n.tr("Localizable", "on_demand.title") + internal enum Items { + internal enum Active { + /// Trust + internal static let caption = L10n.tr("Localizable", "on_demand.items.active.caption") + } + internal enum AddSsid { + /// Add Wi-Fi + internal static let caption = L10n.tr("Localizable", "on_demand.items.add_ssid.caption") + } + internal enum Ethernet { + /// Trust wired connections + internal static let caption = L10n.tr("Localizable", "on_demand.items.ethernet.caption") + /// Check to trust any wired cable connection. + internal static let description = L10n.tr("Localizable", "on_demand.items.ethernet.description") + } + internal enum Mobile { + /// Cellular network + internal static let caption = L10n.tr("Localizable", "on_demand.items.mobile.caption") + } + internal enum Policy { + /// Trust disables VPN + internal static let caption = L10n.tr("Localizable", "on_demand.items.policy.caption") + } + } + internal enum Sections { + internal enum Policy { + /// When entering a trusted network, the VPN is normally shut down and kept disconnected. Disable this option to not enforce such behavior. + internal static let footer = L10n.tr("Localizable", "on_demand.sections.policy.footer") + } + } + } + + internal enum Organizer { + internal enum Alerts { + internal enum Reddit { + /// Did you know that Passepartout has a subreddit? Subscribe for updates or to discuss issues, features, new platforms or whatever you like. + /// + /// It's also a great way to show you care about this project. + internal static let message = L10n.tr("Localizable", "organizer.alerts.reddit.message") + internal enum Buttons { + /// Don't ask again + internal static let never = L10n.tr("Localizable", "organizer.alerts.reddit.buttons.never") + /// Remind me later + internal static let remind = L10n.tr("Localizable", "organizer.alerts.reddit.buttons.remind") + /// Subscribe now! + internal static let subscribe = L10n.tr("Localizable", "organizer.alerts.reddit.buttons.subscribe") + } + } + internal enum RemoveProfile { + /// Are you sure you want to delete profile %@? + internal static func message(_ p1: Any) -> String { + return L10n.tr("Localizable", "organizer.alerts.remove_profile.message", String(describing: p1)) + } + /// Remove profile + internal static let title = L10n.tr("Localizable", "organizer.alerts.remove_profile.title") + } + internal enum UninstallVpn { + /// Do you really want to erase the VPN configuration from your device settings? This may fix some broken VPN states and will not affect your provider and host profiles. + internal static let message = L10n.tr("Localizable", "organizer.alerts.uninstall_vpn.message") + } + } + internal enum Items { + internal enum About { + /// About %@ + internal static func caption(_ p1: Any) -> String { + return L10n.tr("Localizable", "organizer.items.about.caption", String(describing: p1)) + } + } + internal enum AddHost { + /// Add from Files + internal static let caption = L10n.tr("Localizable", "organizer.items.add_host.caption") + } + internal enum AddProvider { + /// Add new provider + internal static let caption = L10n.tr("Localizable", "organizer.items.add_provider.caption") + } + internal enum Donate { + /// Make a donation + internal static let caption = L10n.tr("Localizable", "organizer.items.donate.caption") + } + internal enum FollowTwitch { + /// Watch Passepartout on Twitch + internal static let caption = L10n.tr("Localizable", "organizer.items.follow_twitch.caption") + } + internal enum GithubSponsors { + /// Support me on GitHub + internal static let caption = L10n.tr("Localizable", "organizer.items.github_sponsors.caption") + } + internal enum JoinCommunity { + /// Join community + internal static let caption = L10n.tr("Localizable", "organizer.items.join_community.caption") + } + internal enum Profile { + internal enum Value { + /// In use + internal static let current = L10n.tr("Localizable", "organizer.items.profile.value.current") + } + } + internal enum SiriShortcuts { + /// Manage shortcuts + internal static let caption = L10n.tr("Localizable", "organizer.items.siri_shortcuts.caption") + } + internal enum Translate { + /// Offer to translate + internal static let caption = L10n.tr("Localizable", "organizer.items.translate.caption") + } + internal enum Uninstall { + /// Remove VPN configuration + internal static let caption = L10n.tr("Localizable", "organizer.items.uninstall.caption") + } + internal enum WriteReview { + /// Write a review + internal static let caption = L10n.tr("Localizable", "organizer.items.write_review.caption") + } + } + internal enum Menus { + internal enum AddProfile { + /// Add %@ + internal static func imported(_ p1: Any) -> String { + return L10n.tr("Localizable", "organizer.menus.add_profile.imported", String(describing: p1)) + } + } + } + internal enum Sections { + internal enum Siri { + /// Get help from Siri to speed up your most common interactions with the app. + internal static let footer = L10n.tr("Localizable", "organizer.sections.siri.footer") + } + internal enum Support { + /// Support + internal static let header = L10n.tr("Localizable", "organizer.sections.support.header") + } + internal enum Twitch { + /// Come watch me make Passepartout live on Twitch, join the chat to interact and contribute! + internal static let footer = L10n.tr("Localizable", "organizer.sections.twitch.footer") + } + } + } + + internal enum Paywall { + /// Purchase + internal static let title = L10n.tr("Localizable", "paywall.title") + internal enum Items { + internal enum FullVersion { + /// All providers (including future ones) + /// %@ + internal static func extraDescription(_ p1: Any) -> String { + return L10n.tr("Localizable", "paywall.items.full_version.extra_description", String(describing: p1)) + } + } + internal enum Loading { + /// Loading products + internal static let caption = L10n.tr("Localizable", "paywall.items.loading.caption") + } + internal enum Restore { + /// If you bought this app or feature in the past, you can restore your purchases and this screen won't show again. + internal static let description = L10n.tr("Localizable", "paywall.items.restore.description") + /// Restore purchases + internal static let title = L10n.tr("Localizable", "paywall.items.restore.title") + } + } + internal enum Sections { + internal enum Products { + /// Every product is a one-time purchase. Provider purchases do not include a VPN subscription. + internal static let footer = L10n.tr("Localizable", "paywall.sections.products.footer") + } + } + } + + internal enum Preferences { + /// Preferences + internal static let title = L10n.tr("Localizable", "preferences.title") + internal enum Items { + internal enum ConfirmQuit { + /// Confirm quit + internal static let caption = L10n.tr("Localizable", "preferences.items.confirm_quit.caption") + /// Check to present a quit confirmation alert. + internal static let footer = L10n.tr("Localizable", "preferences.items.confirm_quit.footer") + } + internal enum LaunchesOnLogin { + /// Launch on login + internal static let caption = L10n.tr("Localizable", "preferences.items.launches_on_login.caption") + /// Check to automatically launch the app on boot or login. + internal static let footer = L10n.tr("Localizable", "preferences.items.launches_on_login.footer") + } + } + internal enum Sections { + internal enum General { + /// General + internal static let header = L10n.tr("Localizable", "preferences.sections.general.header") + } + } + } + + internal enum Profile { + internal enum Alerts { + internal enum ReconnectVpn { + /// Do you want to reconnect to the VPN? + internal static let message = L10n.tr("Localizable", "profile.alerts.reconnect_vpn.message") + } + internal enum Rename { + /// Rename profile + internal static let title = L10n.tr("Localizable", "profile.alerts.rename.title") + } + internal enum TestConnectivity { + /// Connectivity + internal static let title = L10n.tr("Localizable", "profile.alerts.test_connectivity.title") + internal enum Messages { + /// Your device has no Internet connectivity, please review your profile parameters. + internal static let failure = L10n.tr("Localizable", "profile.alerts.test_connectivity.messages.failure") + /// Your device is connected to the Internet! + internal static let success = L10n.tr("Localizable", "profile.alerts.test_connectivity.messages.success") + } + } + } + internal enum Items { + internal enum Category { + /// Category + internal static let caption = L10n.tr("Localizable", "profile.items.category.caption") + } + internal enum ConnectionStatus { + /// Status + internal static let caption = L10n.tr("Localizable", "profile.items.connection_status.caption") + } + internal enum DataCount { + /// Exchanged data + internal static let caption = L10n.tr("Localizable", "profile.items.data_count.caption") + } + internal enum OnlyShowsFavorites { + /// Only show favorite locations + internal static let caption = L10n.tr("Localizable", "profile.items.only_shows_favorites.caption") + } + internal enum Provider { + internal enum Refresh { + /// Refresh infrastructure + internal static let caption = L10n.tr("Localizable", "profile.items.provider.refresh.caption") + } + } + internal enum Reconnect { + /// Reconnect + internal static let caption = L10n.tr("Localizable", "profile.items.reconnect.caption") + } + internal enum UseProfile { + /// Use this profile + internal static let caption = L10n.tr("Localizable", "profile.items.use_profile.caption") + } + internal enum Vpn { + internal enum TurnOff { + /// Disable VPN + internal static let caption = L10n.tr("Localizable", "profile.items.vpn.turn_off.caption") + } + internal enum TurnOn { + /// Enable VPN + internal static let caption = L10n.tr("Localizable", "profile.items.vpn.turn_on.caption") + } + } + internal enum VpnResolvesHostname { + /// Resolve provider hostname + internal static let caption = L10n.tr("Localizable", "profile.items.vpn_resolves_hostname.caption") + } + internal enum VpnService { + /// Enabled + internal static let caption = L10n.tr("Localizable", "profile.items.vpn_service.caption") + } + internal enum VpnSurvivesSleep { + /// Keep alive on sleep + internal static let caption = L10n.tr("Localizable", "profile.items.vpn_survives_sleep.caption") + } + } + internal enum Sections { + internal enum Configuration { + /// Configuration + internal static let header = L10n.tr("Localizable", "profile.sections.configuration.header") + } + internal enum Feedback { + /// Feedback + internal static let header = L10n.tr("Localizable", "profile.sections.feedback.header") + } + internal enum Provider { + /// Provider + internal static let header = L10n.tr("Localizable", "profile.sections.provider.header") + } + internal enum ProviderInfrastructure { + /// Last updated on %@. + internal static func footer(_ p1: Any) -> String { + return L10n.tr("Localizable", "profile.sections.provider_infrastructure.footer", String(describing: p1)) + } + } + internal enum Status { + /// Connection + internal static let header = L10n.tr("Localizable", "profile.sections.status.header") + } + internal enum Vpn { + /// The connection will be established whenever necessary. + internal static let footer = L10n.tr("Localizable", "profile.sections.vpn.footer") + } + internal enum VpnResolvesHostname { + /// Preferred in most networks and required in some IPv6 networks. Disable where DNS is blocked, or to speed up negotiation when DNS is slow to respond. + internal static let footer = L10n.tr("Localizable", "profile.sections.vpn_resolves_hostname.footer") + } + internal enum VpnSurvivesSleep { + /// Disable to improve battery usage, at the expense of occasional slowdowns due to wake-up reconnections. + internal static let footer = L10n.tr("Localizable", "profile.sections.vpn_survives_sleep.footer") + } + } + internal enum Welcome { + /// Welcome to Passepartout! + /// + /// Use the organizer to add a new profile. + internal static let message = L10n.tr("Localizable", "profile.welcome.message") + } + } + + internal enum Provider { + internal enum Location { + /// Location + internal static let title = L10n.tr("Localizable", "provider.location.title") + internal enum Actions { + /// Favorite + internal static let favorite = L10n.tr("Localizable", "provider.location.actions.favorite") + /// Unfavorite + internal static let unfavorite = L10n.tr("Localizable", "provider.location.actions.unfavorite") + } + internal enum Sections { + internal enum EmptyFavorites { + /// Swipe left on a location to add or remove it from Favorites. + internal static let footer = L10n.tr("Localizable", "provider.location.sections.empty_favorites.footer") + } + } + } + internal enum Preset { + /// Preset + internal static let title = L10n.tr("Localizable", "provider.preset.title") + } + } + + internal enum ReportIssue { + internal enum Alert { + /// Report issue + internal static let title = L10n.tr("Localizable", "report_issue.alert.title") + } + } + + internal enum Shortcuts { + internal enum Add { + /// Add shortcut + internal static let title = L10n.tr("Localizable", "shortcuts.add.title") + internal enum Alerts { + internal enum NoProfiles { + /// There is no profile to connect to. + internal static let message = L10n.tr("Localizable", "shortcuts.add.alerts.no_profiles.message") + } + } + internal enum Items { + internal enum Connect { + /// Connect to + internal static let caption = L10n.tr("Localizable", "shortcuts.add.items.connect.caption") + } + internal enum DisableVpn { + /// Disable VPN + internal static let caption = L10n.tr("Localizable", "shortcuts.add.items.disable_vpn.caption") + } + internal enum EnableVpn { + /// Enable VPN + internal static let caption = L10n.tr("Localizable", "shortcuts.add.items.enable_vpn.caption") + } + internal enum TrustCellular { + /// Trust cellular network + internal static let caption = L10n.tr("Localizable", "shortcuts.add.items.trust_cellular.caption") + } + internal enum TrustCurrentWifi { + /// Trust current Wi-Fi + internal static let caption = L10n.tr("Localizable", "shortcuts.add.items.trust_current_wifi.caption") + } + internal enum UntrustCellular { + /// Untrust cellular network + internal static let caption = L10n.tr("Localizable", "shortcuts.add.items.untrust_cellular.caption") + } + internal enum UntrustCurrentWifi { + /// Untrust current Wi-Fi + internal static let caption = L10n.tr("Localizable", "shortcuts.add.items.untrust_current_wifi.caption") + } + } + internal enum Sections { + internal enum Cellular { + /// Cellular + internal static let header = L10n.tr("Localizable", "shortcuts.add.sections.cellular.header") + } + internal enum Wifi { + /// Wi-Fi + internal static let header = L10n.tr("Localizable", "shortcuts.add.sections.wifi.header") + } + } + } + internal enum Edit { + /// Manage shortcuts + internal static let title = L10n.tr("Localizable", "shortcuts.edit.title") + internal enum Items { + internal enum AddShortcut { + /// Add shortcut + internal static let caption = L10n.tr("Localizable", "shortcuts.edit.items.add_shortcut.caption") + } + } + internal enum Sections { + internal enum All { + /// Existing shortcuts + internal static let header = L10n.tr("Localizable", "shortcuts.edit.sections.all.header") + } + } + } + } + + internal enum Tunnelkit { + internal enum Errors { + /// Unable to parse the provided configuration file (%@). + internal static func parsing(_ p1: Any) -> String { + return L10n.tr("Localizable", "tunnelkit.errors.parsing", String(describing: p1)) + } + internal enum Openvpn { + /// Unable to decrypt private key. + internal static let decryption = L10n.tr("Localizable", "tunnelkit.errors.openvpn.decryption") + /// The configuration file contains a malformed option (%@). + internal static func malformed(_ p1: Any) -> String { + return L10n.tr("Localizable", "tunnelkit.errors.openvpn.malformed", String(describing: p1)) + } + /// Please enter the encryption passphrase. + internal static let passphraseRequired = L10n.tr("Localizable", "tunnelkit.errors.openvpn.passphrase_required") + /// The configuration file is correct but contains a potentially unsupported option (%@). + /// + /// Connectivity may break depending on server settings. + internal static func potentiallyUnsupportedOption(_ p1: Any) -> String { + return L10n.tr("Localizable", "tunnelkit.errors.openvpn.potentially_unsupported_option", String(describing: p1)) + } + /// The configuration file lacks a required option (%@). + internal static func requiredOption(_ p1: Any) -> String { + return L10n.tr("Localizable", "tunnelkit.errors.openvpn.required_option", String(describing: p1)) + } + /// The configuration file contains an unsupported option (%@). + internal static func unsupportedOption(_ p1: Any) -> String { + return L10n.tr("Localizable", "tunnelkit.errors.openvpn.unsupported_option", String(describing: p1)) + } + } + internal enum Vpn { + /// Auth failed + internal static let auth = L10n.tr("Localizable", "tunnelkit.errors.vpn.auth") + /// Compression unsupported + internal static let compression = L10n.tr("Localizable", "tunnelkit.errors.vpn.compression") + /// DNS failed + internal static let dns = L10n.tr("Localizable", "tunnelkit.errors.vpn.dns") + /// Encryption failed + internal static let encryption = L10n.tr("Localizable", "tunnelkit.errors.vpn.encryption") + /// No gateway + internal static let gateway = L10n.tr("Localizable", "tunnelkit.errors.vpn.gateway") + /// Network changed + internal static let network = L10n.tr("Localizable", "tunnelkit.errors.vpn.network") + /// Missing routing + internal static let routing = L10n.tr("Localizable", "tunnelkit.errors.vpn.routing") + /// Server shutdown + internal static let shutdown = L10n.tr("Localizable", "tunnelkit.errors.vpn.shutdown") + /// Timeout + internal static let timeout = L10n.tr("Localizable", "tunnelkit.errors.vpn.timeout") + /// TLS failed + internal static let tls = L10n.tr("Localizable", "tunnelkit.errors.vpn.tls") + } + } + internal enum Vpn { + /// Active + internal static let active = L10n.tr("Localizable", "tunnelkit.vpn.active") + /// Connecting + internal static let connecting = L10n.tr("Localizable", "tunnelkit.vpn.connecting") + /// Disabled + internal static let disabled = L10n.tr("Localizable", "tunnelkit.vpn.disabled") + /// Disconnecting + internal static let disconnecting = L10n.tr("Localizable", "tunnelkit.vpn.disconnecting") + /// Inactive + internal static let inactive = L10n.tr("Localizable", "tunnelkit.vpn.inactive") + /// Off + internal static let unused = L10n.tr("Localizable", "tunnelkit.vpn.unused") + } + } + + internal enum Version { + /// Version + internal static let title = L10n.tr("Localizable", "version.title") + internal enum Labels { + /// Passepartout and TunnelKit are written and maintained by Davide De Rosa (keeshux). + /// + /// Source code for Passepartout and TunnelKit is publicly available on GitHub under the GPLv3, you can find links in the home page. + /// + /// Passepartout is a non-official client and is in no way affiliated with OpenVPN Inc. + internal static let intro = L10n.tr("Localizable", "version.labels.intro") + } + } +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension L10n { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + let format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table) + return String(format: format, locale: Locale.current, arguments: args) + } +} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/Passepartout/App/Shared/Constants/Theme.swift b/Passepartout/App/Shared/Constants/Theme.swift new file mode 100644 index 00000000..7d561488 --- /dev/null +++ b/Passepartout/App/Shared/Constants/Theme.swift @@ -0,0 +1,394 @@ +// +// Theme.swift +// Passepartout +// +// Created by Davide De Rosa on 2/24/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension Color { + init(red: Double, green: Double, blue: Double, brightness: Double) { + self.init( + red: red * brightness, + green: green * brightness, + blue: blue * brightness + ) + } +} + +extension View { + + @available(iOS 14, *) + func themeConfigureNavigationBarAppearance() { + let navBackgroundColor = Asset.Assets.primaryColor.color + let titleAttributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: Asset.Assets.lightTextColor.color + ] + + let navBarAppearance = UINavigationBarAppearance() + navBarAppearance.configureWithOpaqueBackground() + navBarAppearance.backgroundColor = navBackgroundColor + navBarAppearance.titleTextAttributes = titleAttributes + navBarAppearance.largeTitleTextAttributes = titleAttributes + + let navBar = UINavigationBar.appearance() + navBar.standardAppearance = navBarAppearance + navBar.compactAppearance = navBarAppearance + navBar.scrollEdgeAppearance = navBarAppearance + +// UITableView.appearance().backgroundColor = .clear + } + + @available(iOS 14, *) + var themeIdiom: UIUserInterfaceIdiom { + UIDevice.current.userInterfaceIdiom + } +} + +// MARK: Styles + +extension View { + func themeGlobal() -> some View { + let color = themeAccentColor + return accentColor(color) + .toggleStyle(SwitchToggleStyle(tint: color)) + .listStyle(.insetGrouped) + .themeNavigationViewStyle() + } + + @ViewBuilder + private func themeNavigationViewStyle() -> some View { + switch UIDevice.current.userInterfaceIdiom { + case .phone: + navigationViewStyle(.stack) + + default: + navigationViewStyle(.automatic) + } + } + + @ViewBuilder + func themePrimaryView() -> some View { + switch UIDevice.current.userInterfaceIdiom { + case .phone: + navigationBarTitleDisplayMode(.large) + + default: + themeSecondaryView() + } + } + + func themeSecondaryView() -> some View { + navigationBarTitleDisplayMode(.inline) + } + + func themeLongText() -> some View { + lineLimit(1) + .truncationMode(.middle) + } +} + +// MARK: Colors + +extension View { + var themeAccentColor: Color { + Color(Asset.Assets.accentColor.color) + } + + var themePrimaryBackgroundColor: Color { + Color(Asset.Assets.primaryColor.color) + } + + var themePrimaryBackground: some View { + themePrimaryBackgroundColor + .ignoresSafeArea() + } + + var themeSecondaryColor: Color { + .secondary + } + + var themeLightTextColor: Color { + Color(Asset.Assets.lightTextColor.color) + } + + var themeErrorColor: Color { + .red + } + + private func themeColor(_ string: String?, validator: (String) throws -> Void) -> Color? { + guard let string = string else { + return nil + } + do { + try validator(string) + return nil + } catch { + return themeErrorColor + } + } +} + +// MARK: Fonts + +extension View { + func themeDebugLogFont() -> some View { + font(.system(size: 13, weight: .medium, design: .monospaced)) + } +} + +// MARK: Images + +extension View { + var themeAssetsLogoImage: String { + "logo" + } + + func themeAssetsProviderImage(_ providerName: ProviderName) -> String { + "providers/\(providerName)" + } + + func themeAssetsCountryImage(_ countryCode: String) -> String { + "flags/\(countryCode.lowercased())" + } + + var themeHostImage: String { + "folder.fill" + } + + var themeProviderImage: String { + "externaldrive.connected.to.line.below.fill" + } + + var themeAddProfileImage: String { + "plus" + } + + var themeCheckmarkImage: String { + "checkmark" + } + + var themeStatusImage: String { + "network" + } + + var themeShortcutsImage: String { + "mic.fill" + } + + var themeDonateImage: String { + "giftcard.fill" + } + + var themeRedditImage: String { + "person.3.fill" + } + + var themeWriteReviewImage: String { + "heart.fill" + } + + var themeAboutImage: String { + "info.circle" + } + + var themeDeleteImage: String { + "trash.fill" + } + + var themeRenameProfileImage: String { + "highlighter" +// "character.cursor.ibeam" + } + + var themeVPNProtocolImage: String { + "bolt.fill" +// "waveform.path.ecg" +// "message.and.waveform.fill" +// "pc" +// "captions.bubble.fill" + } + + var themeEndpointImage: String { + "link" + } + + var themeAccountImage: String { + "person.fill" + } + + var themeProviderLocationImage: String { + "location.fill" + } + + var themeProviderPresetImage: String { + "slider.horizontal.3" + } + + var themeProviderRefreshImage: String { + "arrow.clockwise" + } + + var themeNetworkSettingsImage: String { +// "network" + "globe" + } + + var themeOnDemandImage: String { + "wifi" + } + + var themeDiagnosticsImage: String { + "bandage.fill" + } + + var themeFAQImage: String { + "questionmark.diamond.fill" + } + + var themeConceilImage: String { + "eye.slash.fill" + } + + var themeRevealImage: String { + "eye.fill" + } + + var themeShareImage: String { + "square.and.arrow.up" + } + + func themeFavoritesImage(_ active: Bool) -> String { + active ? "bookmark.fill" : "bookmark" + } + + func themeFavoriteActionImage(_ doFavorite: Bool) -> String { + doFavorite ? "bookmark" : "bookmark.slash.fill" + } +} + +extension String { + var asAssetImage: Image { + Image(self) + } + + var asSystemImage: Image { + Image(systemName: self) + } +} + +// MARK: Shortcuts + +extension View { + func themeSaveButtonLabel() -> some View { +// themeCheckmarkImage.asSystemImage + Text(L10n.Global.Strings.save) + } + + func themeDoneButtonLabel() -> some View { +// themeCheckmarkImage.asSystemImage + Text(L10n.Global.Strings.ok) + } + + func themeTextPicker(_ title: String, selection: Binding, values: [T], description: @escaping (T) -> String) -> some View { + StyledPicker(title: title, selection: selection, values: values) { + Text(description($0)) + } selectionLabel: { + Text(description($0)) + .foregroundColor(themeSecondaryColor) + } listStyle: { + .insetGrouped + } + } + + func themeLongContentLink(_ title: String, content: Binding, withPreview preview: String? = nil) -> some View { + LongContentLink(title, content: content, preview: preview) { + Text(preview != nil ? $0 : "") + .foregroundColor(themeSecondaryColor) + } + } + + @ViewBuilder + func themeErrorMessage(_ message: String?) -> some View { + if let message = message { + if message.last != "." { + Text("\(message).") + .foregroundColor(themeErrorColor) + } else { + Text(message) + .foregroundColor(themeErrorColor) + } + } else { + EmptyView() + } + } +} + +// MARK: Validation + +extension View { + func themeURL(_ urlString: String?) -> some View { + themeValidating(urlString, validator: Validators.url) + .keyboardType(.asciiCapable) + .autocapitalization(.none) + .disableAutocorrection(true) + } + + func themeIPAddress(_ ipAddress: String?) -> some View { + themeValidating(ipAddress, validator: Validators.ipAddress) + .keyboardType(.numbersAndPunctuation) + .autocapitalization(.none) + .disableAutocorrection(true) + } + + func themeSocketPort() -> some View { + keyboardType(.numberPad) + } + + func themeDomainName(_ domainName: String?) -> some View { + themeValidating(domainName, validator: Validators.domainName) + .keyboardType(.asciiCapable) + .autocapitalization(.none) + .disableAutocorrection(true) + } + + func themeSSID(_ text: String?) -> some View { + themeValidating(text, validator: Validators.notEmpty) + .keyboardType(.asciiCapable) + .autocapitalization(.none) + .disableAutocorrection(true) + } + + private func themeValidating(_ string: String?, validator: (String) throws -> Void) -> some View { + foregroundColor(themeColor(string, validator: validator)) + } +} + +// MARK: Hacks + +extension View { +// @available(*, deprecated, message: "mitigates multiline text truncation (1.0 does not work though)") + func xxxThemeTruncation() -> some View { + minimumScaleFactor(0.5) + } +} diff --git a/Passepartout/App/Shared/Extensions/DebugLog+Constants.swift b/Passepartout/App/Shared/Extensions/DebugLog+Constants.swift new file mode 100644 index 00000000..d67291e3 --- /dev/null +++ b/Passepartout/App/Shared/Extensions/DebugLog+Constants.swift @@ -0,0 +1,37 @@ +// +// DebugLog+Constants.swift +// Passepartout +// +// Created by Davide De Rosa on 3/24/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore + +extension DebugLog { + func decoratedString() -> String { + return decoratedString(Constants.Global.appName, Constants.Global.appVersionString) + } + + func decoratedData() -> Data { + return decoratedData(Constants.Global.appName, Constants.Global.appVersionString) + } +} diff --git a/Passepartout/App/Shared/Extensions/PassepartoutProviders+Extensions.swift b/Passepartout/App/Shared/Extensions/PassepartoutProviders+Extensions.swift new file mode 100644 index 00000000..a14b97a8 --- /dev/null +++ b/Passepartout/App/Shared/Extensions/PassepartoutProviders+Extensions.swift @@ -0,0 +1,106 @@ +// +// PassepartoutProviders+Extensions.swift +// Passepartout +// +// Created by Davide De Rosa on 11/4/21. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore + +extension ProviderMetadata: Identifiable, Comparable, Hashable { + public var id: String { + return name + } + + public static func ==(lhs: Self, rhs: Self) -> Bool { + return lhs.name == rhs.name + } + + public static func <(lhs: Self, rhs: Self) -> Bool { + return lhs.fullName.lowercased() < rhs.fullName.lowercased() + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + } +} + +extension ProviderCategory: Comparable { + public static func ==(lhs: Self, rhs: Self) -> Bool { + return lhs.name.lowercased() == rhs.name.lowercased() + } + + public static func <(lhs: Self, rhs: Self) -> Bool { + return lhs.name.lowercased() < rhs.name.lowercased() + } +} + +extension ProviderLocation: Comparable { + public static func ==(lhs: Self, rhs: Self) -> Bool { + return lhs.countryCode == rhs.countryCode + } + + public static func <(lhs: Self, rhs: Self) -> Bool { + return lhs.localizedCountry < rhs.localizedCountry + } +} + +extension ProviderServer: Comparable { + public static func ==(lhs: Self, rhs: Self) -> Bool { + return lhs.id == rhs.id + } + + // "Default" comes first + // sorts by serverIndex first, see ProtonVPN > Germany (currently "Frankfurt #203" comes before "#3") + public static func <(lhs: Self, rhs: Self) -> Bool { + if let li = lhs.serverIndex, let ri = rhs.serverIndex { + return li < ri + } + return lhs.localizedDetails < rhs.localizedDetails + } +} + +extension ProviderServer.Preset: Comparable { + public static func ==(lhs: Self, rhs: Self) -> Bool { + return lhs.name == rhs.name + } + + public static func <(lhs: Self, rhs: Self) -> Bool { + return lhs.name < rhs.name + } +} + +extension ProviderMetadata { + var openVPNGuidanceURL: URL? { + guard let string = Constants.URLs.openVPNGuidances[name] else { + return nil + } + return URL(string: string) + } + + var referralURL: URL? { + guard let string = Constants.URLs.referrals[name] else { + return nil + } + return URL(string: string) + } +} diff --git a/Passepartout/App/Shared/Extensions/TunnelKit+Identifiable.swift b/Passepartout/App/Shared/Extensions/TunnelKit+Identifiable.swift new file mode 100644 index 00000000..c148273d --- /dev/null +++ b/Passepartout/App/Shared/Extensions/TunnelKit+Identifiable.swift @@ -0,0 +1,45 @@ +// +// TunnelKit+Identifiable.swift +// Passepartout +// +// Created by Davide De Rosa on 3/12/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitCore + +extension Endpoint: Identifiable { + public var id: String { + return "\(address):\(proto.port):\(proto.socketType.rawValue)" + } +} + +extension IPv4Settings.Route: Identifiable { + public var id: String { + return "\(destination):\(mask):\(gateway)" + } +} + +extension IPv6Settings.Route: Identifiable { + public var id: String { + return "\(destination):\(prefixLength):\(gateway)" + } +} diff --git a/Passepartout/App/Shared/Extensions/VPNProtocolType+FileExtensions.swift b/Passepartout/App/Shared/Extensions/VPNProtocolType+FileExtensions.swift new file mode 100644 index 00000000..2cf0e95e --- /dev/null +++ b/Passepartout/App/Shared/Extensions/VPNProtocolType+FileExtensions.swift @@ -0,0 +1,44 @@ +// +// VPNProtocolType+FileExtensions.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore + +extension VPNProtocolType { + static let knownFileExtensions: [String] = { + let protos: [Self] = [.openVPN, .wireGuard] + return protos.map(\.fileExtension) + }() + + var fileExtension: String { + switch self { + case .openVPN: + return "ovpn" + + case .wireGuard: + return "wg" + } + } +} diff --git a/Passepartout/App/macOS/Assets.xcassets/Contents.json b/Passepartout/App/Shared/Flags.xcassets/Contents.json similarity index 100% rename from Passepartout/App/macOS/Assets.xcassets/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/Contents.json diff --git a/Passepartout/App/macOS/Providers.xcassets/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/Contents.json similarity index 52% rename from Passepartout/App/macOS/Providers.xcassets/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/Contents.json index 73c00596..6e965652 100644 --- a/Passepartout/App/macOS/Providers.xcassets/Contents.json +++ b/Passepartout/App/Shared/Flags.xcassets/flags/Contents.json @@ -2,5 +2,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "provides-namespace" : true } } diff --git a/Passepartout/App/iOS/Flags.xcassets/ad.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ad.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ad.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ad.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ad.imageset/ad@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ad.imageset/ad@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ad.imageset/ad@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ad.imageset/ad@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ad.imageset/ad@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ad.imageset/ad@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ad.imageset/ad@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ad.imageset/ad@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ae.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ae.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ae.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ae.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ae.imageset/ae@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ae.imageset/ae@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ae.imageset/ae@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ae.imageset/ae@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ae.imageset/ae@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ae.imageset/ae@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ae.imageset/ae@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ae.imageset/ae@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/af.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/af.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/af.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/af.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/af.imageset/af@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/af.imageset/af@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/af.imageset/af@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/af.imageset/af@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/af.imageset/af@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/af.imageset/af@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/af.imageset/af@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/af.imageset/af@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ag.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ag.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ag.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ag.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ag.imageset/ag@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ag.imageset/ag@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ag.imageset/ag@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ag.imageset/ag@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ag.imageset/ag@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ag.imageset/ag@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ag.imageset/ag@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ag.imageset/ag@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ai.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ai.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ai.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ai.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ai.imageset/ai@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ai.imageset/ai@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ai.imageset/ai@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ai.imageset/ai@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ai.imageset/ai@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ai.imageset/ai@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ai.imageset/ai@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ai.imageset/ai@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/al.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/al.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/al.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/al.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/al.imageset/al@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/al.imageset/al@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/al.imageset/al@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/al.imageset/al@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/al.imageset/al@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/al.imageset/al@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/al.imageset/al@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/al.imageset/al@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/am.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/am.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/am.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/am.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/am.imageset/am@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/am.imageset/am@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/am.imageset/am@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/am.imageset/am@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/am.imageset/am@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/am.imageset/am@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/am.imageset/am@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/am.imageset/am@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ao.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ao.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ao.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ao.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ao.imageset/ao@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ao.imageset/ao@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ao.imageset/ao@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ao.imageset/ao@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ao.imageset/ao@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ao.imageset/ao@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ao.imageset/ao@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ao.imageset/ao@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/aq.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/aq.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/aq.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/aq.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/aq.imageset/aq@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/aq.imageset/aq@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/aq.imageset/aq@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/aq.imageset/aq@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/aq.imageset/aq@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/aq.imageset/aq@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/aq.imageset/aq@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/aq.imageset/aq@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ar.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ar.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ar.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ar.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ar.imageset/ar@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ar.imageset/ar@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ar.imageset/ar@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ar.imageset/ar@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ar.imageset/ar@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ar.imageset/ar@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ar.imageset/ar@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ar.imageset/ar@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/as.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/as.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/as.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/as.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/as.imageset/as@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/as.imageset/as@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/as.imageset/as@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/as.imageset/as@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/as.imageset/as@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/as.imageset/as@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/as.imageset/as@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/as.imageset/as@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/at.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/at.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/at.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/at.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/at.imageset/at@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/at.imageset/at@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/at.imageset/at@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/at.imageset/at@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/at.imageset/at@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/at.imageset/at@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/at.imageset/at@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/at.imageset/at@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/au.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/au.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/au.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/au.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/au.imageset/au@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/au.imageset/au@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/au.imageset/au@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/au.imageset/au@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/au.imageset/au@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/au.imageset/au@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/au.imageset/au@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/au.imageset/au@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/aw.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/aw.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/aw.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/aw.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/aw.imageset/aw@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/aw.imageset/aw@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/aw.imageset/aw@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/aw.imageset/aw@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/aw.imageset/aw@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/aw.imageset/aw@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/aw.imageset/aw@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/aw.imageset/aw@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ax.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ax.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ax.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ax.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ax.imageset/ax@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ax.imageset/ax@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ax.imageset/ax@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ax.imageset/ax@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ax.imageset/ax@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ax.imageset/ax@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ax.imageset/ax@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ax.imageset/ax@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/az.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/az.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/az.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/az.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/az.imageset/az@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/az.imageset/az@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/az.imageset/az@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/az.imageset/az@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/az.imageset/az@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/az.imageset/az@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/az.imageset/az@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/az.imageset/az@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ba.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ba.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ba.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ba.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ba.imageset/ba@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ba.imageset/ba@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ba.imageset/ba@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ba.imageset/ba@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ba.imageset/ba@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ba.imageset/ba@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ba.imageset/ba@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ba.imageset/ba@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bb.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bb.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bb.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bb.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bb.imageset/bb@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bb.imageset/bb@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bb.imageset/bb@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bb.imageset/bb@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bb.imageset/bb@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bb.imageset/bb@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bb.imageset/bb@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bb.imageset/bb@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bd.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bd.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bd.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bd.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bd.imageset/bd@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bd.imageset/bd@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bd.imageset/bd@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bd.imageset/bd@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bd.imageset/bd@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bd.imageset/bd@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bd.imageset/bd@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bd.imageset/bd@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/be.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/be.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/be.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/be.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/be.imageset/be@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/be.imageset/be@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/be.imageset/be@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/be.imageset/be@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/be.imageset/be@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/be.imageset/be@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/be.imageset/be@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/be.imageset/be@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bf.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bf.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bf.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bf.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bf.imageset/bf@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bf.imageset/bf@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bf.imageset/bf@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bf.imageset/bf@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bf.imageset/bf@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bf.imageset/bf@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bf.imageset/bf@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bf.imageset/bf@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bg.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bg.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bg.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bg.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bg.imageset/bg@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bg.imageset/bg@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bg.imageset/bg@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bg.imageset/bg@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bg.imageset/bg@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bg.imageset/bg@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bg.imageset/bg@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bg.imageset/bg@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bh.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bh.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bh.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bh.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bh.imageset/bh@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bh.imageset/bh@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bh.imageset/bh@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bh.imageset/bh@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bh.imageset/bh@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bh.imageset/bh@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bh.imageset/bh@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bh.imageset/bh@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bi.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bi.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bi.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bi.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bi.imageset/bi@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bi.imageset/bi@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bi.imageset/bi@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bi.imageset/bi@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bi.imageset/bi@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bi.imageset/bi@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bi.imageset/bi@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bi.imageset/bi@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bj.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bj.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bj.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bj.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bj.imageset/bj@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bj.imageset/bj@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bj.imageset/bj@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bj.imageset/bj@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bj.imageset/bj@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bj.imageset/bj@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bj.imageset/bj@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bj.imageset/bj@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bl.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bl.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bl.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bl.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bl.imageset/bl@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bl.imageset/bl@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bl.imageset/bl@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bl.imageset/bl@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bl.imageset/bl@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bl.imageset/bl@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bl.imageset/bl@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bl.imageset/bl@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bm.imageset/bm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bm.imageset/bm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bm.imageset/bm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bm.imageset/bm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bm.imageset/bm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bm.imageset/bm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bm.imageset/bm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bm.imageset/bm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bn.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bn.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bn.imageset/bn@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bn.imageset/bn@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bn.imageset/bn@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bn.imageset/bn@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bn.imageset/bn@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bn.imageset/bn@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bn.imageset/bn@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bn.imageset/bn@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bo.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bo.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bo.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bo.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bo.imageset/bo@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bo.imageset/bo@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bo.imageset/bo@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bo.imageset/bo@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bo.imageset/bo@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bo.imageset/bo@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bo.imageset/bo@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bo.imageset/bo@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bq.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bq.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bq.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bq.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bq.imageset/bq@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bq.imageset/bq@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bq.imageset/bq@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bq.imageset/bq@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bq.imageset/bq@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bq.imageset/bq@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bq.imageset/bq@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bq.imageset/bq@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/br.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/br.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/br.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/br.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/br.imageset/br@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/br.imageset/br@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/br.imageset/br@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/br.imageset/br@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/br.imageset/br@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/br.imageset/br@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/br.imageset/br@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/br.imageset/br@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bs.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bs.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bs.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bs.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bs.imageset/bs@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bs.imageset/bs@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bs.imageset/bs@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bs.imageset/bs@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bs.imageset/bs@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bs.imageset/bs@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bs.imageset/bs@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bs.imageset/bs@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bt.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bt.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bt.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bt.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bt.imageset/bt@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bt.imageset/bt@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bt.imageset/bt@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bt.imageset/bt@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bt.imageset/bt@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bt.imageset/bt@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bt.imageset/bt@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bt.imageset/bt@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bv.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bv.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bv.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bv.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bv.imageset/bv@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bv.imageset/bv@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bv.imageset/bv@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bv.imageset/bv@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bv.imageset/bv@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bv.imageset/bv@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bv.imageset/bv@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bv.imageset/bv@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bw.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bw.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bw.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bw.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bw.imageset/bw@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bw.imageset/bw@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bw.imageset/bw@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bw.imageset/bw@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bw.imageset/bw@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bw.imageset/bw@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bw.imageset/bw@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bw.imageset/bw@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/by.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/by.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/by.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/by.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/by.imageset/by@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/by.imageset/by@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/by.imageset/by@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/by.imageset/by@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/by.imageset/by@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/by.imageset/by@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/by.imageset/by@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/by.imageset/by@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bz.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/bz.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bz.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/bz.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/bz.imageset/bz@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bz.imageset/bz@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bz.imageset/bz@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bz.imageset/bz@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/bz.imageset/bz@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/bz.imageset/bz@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/bz.imageset/bz@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/bz.imageset/bz@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ca.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ca.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ca.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ca.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ca.imageset/ca@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ca.imageset/ca@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ca.imageset/ca@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ca.imageset/ca@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ca.imageset/ca@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ca.imageset/ca@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ca.imageset/ca@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ca.imageset/ca@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cc.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cc.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cc.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cc.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cc.imageset/cc@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cc.imageset/cc@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cc.imageset/cc@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cc.imageset/cc@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cc.imageset/cc@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cc.imageset/cc@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cc.imageset/cc@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cc.imageset/cc@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cd.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cd.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cd.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cd.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cd.imageset/cd@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cd.imageset/cd@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cd.imageset/cd@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cd.imageset/cd@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cd.imageset/cd@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cd.imageset/cd@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cd.imageset/cd@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cd.imageset/cd@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cf.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cf.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cf.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cf.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cf.imageset/cf@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cf.imageset/cf@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cf.imageset/cf@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cf.imageset/cf@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cf.imageset/cf@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cf.imageset/cf@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cf.imageset/cf@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cf.imageset/cf@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cg.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cg.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cg.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cg.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cg.imageset/cg@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cg.imageset/cg@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cg.imageset/cg@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cg.imageset/cg@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cg.imageset/cg@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cg.imageset/cg@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cg.imageset/cg@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cg.imageset/cg@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ch.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ch.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ch.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ch.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ch.imageset/ch@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ch.imageset/ch@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ch.imageset/ch@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ch.imageset/ch@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ch.imageset/ch@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ch.imageset/ch@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ch.imageset/ch@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ch.imageset/ch@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ci.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ci.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ci.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ci.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ci.imageset/ci@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ci.imageset/ci@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ci.imageset/ci@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ci.imageset/ci@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ci.imageset/ci@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ci.imageset/ci@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ci.imageset/ci@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ci.imageset/ci@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ck.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ck.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ck.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ck.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ck.imageset/ck@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ck.imageset/ck@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ck.imageset/ck@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ck.imageset/ck@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ck.imageset/ck@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ck.imageset/ck@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ck.imageset/ck@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ck.imageset/ck@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cl.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cl.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cl.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cl.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cl.imageset/cl@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cl.imageset/cl@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cl.imageset/cl@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cl.imageset/cl@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cl.imageset/cl@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cl.imageset/cl@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cl.imageset/cl@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cl.imageset/cl@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cm.imageset/cm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cm.imageset/cm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cm.imageset/cm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cm.imageset/cm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cm.imageset/cm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cm.imageset/cm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cm.imageset/cm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cm.imageset/cm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cn.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cn.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cn.imageset/cn@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cn.imageset/cn@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cn.imageset/cn@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cn.imageset/cn@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cn.imageset/cn@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cn.imageset/cn@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cn.imageset/cn@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cn.imageset/cn@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/co.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/co.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/co.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/co.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/co.imageset/co@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/co.imageset/co@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/co.imageset/co@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/co.imageset/co@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/co.imageset/co@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/co.imageset/co@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/co.imageset/co@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/co.imageset/co@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cr.imageset/cr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cr.imageset/cr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cr.imageset/cr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cr.imageset/cr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cr.imageset/cr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cr.imageset/cr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cr.imageset/cr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cr.imageset/cr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cu.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cu.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cu.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cu.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cu.imageset/cu@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cu.imageset/cu@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cu.imageset/cu@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cu.imageset/cu@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cu.imageset/cu@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cu.imageset/cu@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cu.imageset/cu@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cu.imageset/cu@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cv.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cv.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cv.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cv.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cv.imageset/cv@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cv.imageset/cv@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cv.imageset/cv@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cv.imageset/cv@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cv.imageset/cv@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cv.imageset/cv@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cv.imageset/cv@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cv.imageset/cv@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cw.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cw.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cw.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cw.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cw.imageset/cw@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cw.imageset/cw@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cw.imageset/cw@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cw.imageset/cw@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cw.imageset/cw@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cw.imageset/cw@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cw.imageset/cw@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cw.imageset/cw@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cx.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cx.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cx.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cx.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cx.imageset/cx@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cx.imageset/cx@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cx.imageset/cx@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cx.imageset/cx@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cx.imageset/cx@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cx.imageset/cx@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cx.imageset/cx@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cx.imageset/cx@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cy.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cy.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cy.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cy.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cy.imageset/cy@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cy.imageset/cy@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cy.imageset/cy@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cy.imageset/cy@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cy.imageset/cy@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cy.imageset/cy@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cy.imageset/cy@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cy.imageset/cy@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cz.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/cz.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cz.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/cz.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/cz.imageset/cz@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cz.imageset/cz@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cz.imageset/cz@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cz.imageset/cz@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/cz.imageset/cz@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/cz.imageset/cz@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/cz.imageset/cz@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/cz.imageset/cz@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/de.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/de.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/de.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/de.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/de.imageset/de@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/de.imageset/de@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/de.imageset/de@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/de.imageset/de@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/de.imageset/de@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/de.imageset/de@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/de.imageset/de@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/de.imageset/de@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/dj.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/dj.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dj.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/dj.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/dj.imageset/dj@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/dj.imageset/dj@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dj.imageset/dj@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/dj.imageset/dj@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/dj.imageset/dj@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/dj.imageset/dj@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dj.imageset/dj@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/dj.imageset/dj@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/dk.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/dk.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dk.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/dk.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/dk.imageset/dk@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/dk.imageset/dk@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dk.imageset/dk@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/dk.imageset/dk@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/dk.imageset/dk@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/dk.imageset/dk@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dk.imageset/dk@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/dk.imageset/dk@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/dm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/dm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/dm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/dm.imageset/dm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/dm.imageset/dm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dm.imageset/dm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/dm.imageset/dm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/dm.imageset/dm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/dm.imageset/dm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dm.imageset/dm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/dm.imageset/dm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/do.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/do.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/do.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/do.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/do.imageset/do@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/do.imageset/do@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/do.imageset/do@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/do.imageset/do@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/do.imageset/do@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/do.imageset/do@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/do.imageset/do@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/do.imageset/do@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/dz.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/dz.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dz.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/dz.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/dz.imageset/dz@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/dz.imageset/dz@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dz.imageset/dz@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/dz.imageset/dz@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/dz.imageset/dz@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/dz.imageset/dz@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/dz.imageset/dz@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/dz.imageset/dz@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ec.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ec.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ec.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ec.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ec.imageset/ec@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ec.imageset/ec@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ec.imageset/ec@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ec.imageset/ec@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ec.imageset/ec@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ec.imageset/ec@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ec.imageset/ec@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ec.imageset/ec@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ee.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ee.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ee.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ee.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ee.imageset/ee@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ee.imageset/ee@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ee.imageset/ee@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ee.imageset/ee@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ee.imageset/ee@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ee.imageset/ee@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ee.imageset/ee@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ee.imageset/ee@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/eg.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/eg.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/eg.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/eg.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/eg.imageset/eg@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/eg.imageset/eg@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/eg.imageset/eg@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/eg.imageset/eg@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/eg.imageset/eg@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/eg.imageset/eg@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/eg.imageset/eg@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/eg.imageset/eg@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/eh.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/eh.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/eh.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/eh.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/eh.imageset/eh@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/eh.imageset/eh@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/eh.imageset/eh@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/eh.imageset/eh@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/eh.imageset/eh@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/eh.imageset/eh@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/eh.imageset/eh@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/eh.imageset/eh@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/er.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/er.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/er.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/er.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/er.imageset/er@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/er.imageset/er@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/er.imageset/er@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/er.imageset/er@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/er.imageset/er@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/er.imageset/er@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/er.imageset/er@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/er.imageset/er@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/es-ct.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/es-ct.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/es-ct.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/es-ct.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/es-ct.imageset/es-ct@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/es-ct.imageset/es-ct@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/es-ct.imageset/es-ct@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/es-ct.imageset/es-ct@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/es-ct.imageset/es-ct@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/es-ct.imageset/es-ct@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/es-ct.imageset/es-ct@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/es-ct.imageset/es-ct@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/es.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/es.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/es.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/es.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/es.imageset/es@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/es.imageset/es@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/es.imageset/es@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/es.imageset/es@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/es.imageset/es@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/es.imageset/es@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/es.imageset/es@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/es.imageset/es@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/et.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/et.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/et.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/et.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/et.imageset/et@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/et.imageset/et@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/et.imageset/et@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/et.imageset/et@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/et.imageset/et@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/et.imageset/et@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/et.imageset/et@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/et.imageset/et@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/eu.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/eu.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/eu.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/eu.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/eu.imageset/eu@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/eu.imageset/eu@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/eu.imageset/eu@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/eu.imageset/eu@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/eu.imageset/eu@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/eu.imageset/eu@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/eu.imageset/eu@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/eu.imageset/eu@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fi.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/fi.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fi.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/fi.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/fi.imageset/fi@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fi.imageset/fi@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fi.imageset/fi@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fi.imageset/fi@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fi.imageset/fi@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fi.imageset/fi@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fi.imageset/fi@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fi.imageset/fi@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fj.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/fj.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fj.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/fj.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/fj.imageset/fj@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fj.imageset/fj@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fj.imageset/fj@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fj.imageset/fj@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fj.imageset/fj@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fj.imageset/fj@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fj.imageset/fj@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fj.imageset/fj@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fk.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/fk.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fk.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/fk.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/fk.imageset/fk@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fk.imageset/fk@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fk.imageset/fk@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fk.imageset/fk@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fk.imageset/fk@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fk.imageset/fk@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fk.imageset/fk@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fk.imageset/fk@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/fm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/fm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/fm.imageset/fm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fm.imageset/fm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fm.imageset/fm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fm.imageset/fm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fm.imageset/fm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fm.imageset/fm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fm.imageset/fm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fm.imageset/fm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fo.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/fo.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fo.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/fo.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/fo.imageset/fo@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fo.imageset/fo@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fo.imageset/fo@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fo.imageset/fo@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fo.imageset/fo@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fo.imageset/fo@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fo.imageset/fo@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fo.imageset/fo@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/fr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/fr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/fr.imageset/fr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fr.imageset/fr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fr.imageset/fr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fr.imageset/fr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/fr.imageset/fr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/fr.imageset/fr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/fr.imageset/fr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/fr.imageset/fr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ga.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ga.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ga.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ga.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ga.imageset/ga@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ga.imageset/ga@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ga.imageset/ga@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ga.imageset/ga@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ga.imageset/ga@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ga.imageset/ga@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ga.imageset/ga@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ga.imageset/ga@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-eng.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gb-eng.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-eng.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-eng.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-eng.imageset/gb-eng@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gb-eng.imageset/gb-eng@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-eng.imageset/gb-eng@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-eng.imageset/gb-eng@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-eng.imageset/gb-eng@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gb-eng.imageset/gb-eng@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-eng.imageset/gb-eng@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-eng.imageset/gb-eng@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-nir.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gb-nir.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-nir.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-nir.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-nir.imageset/gb-nir@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gb-nir.imageset/gb-nir@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-nir.imageset/gb-nir@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-nir.imageset/gb-nir@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-nir.imageset/gb-nir@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gb-nir.imageset/gb-nir@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-nir.imageset/gb-nir@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-nir.imageset/gb-nir@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-sct.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gb-sct.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-sct.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-sct.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-sct.imageset/gb-sct@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gb-sct.imageset/gb-sct@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-sct.imageset/gb-sct@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-sct.imageset/gb-sct@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-sct.imageset/gb-sct@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gb-sct.imageset/gb-sct@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-sct.imageset/gb-sct@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-sct.imageset/gb-sct@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-wls.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gb-wls.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-wls.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-wls.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-wls.imageset/gb-wls@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gb-wls.imageset/gb-wls@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-wls.imageset/gb-wls@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-wls.imageset/gb-wls@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gb-wls.imageset/gb-wls@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gb-wls.imageset/gb-wls@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb-wls.imageset/gb-wls@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gb-wls.imageset/gb-wls@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gb.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gb.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gb.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gb.imageset/gb@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gb.imageset/gb@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb.imageset/gb@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gb.imageset/gb@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gb.imageset/gb@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gb.imageset/gb@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gb.imageset/gb@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gb.imageset/gb@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gd.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gd.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gd.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gd.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gd.imageset/gd@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gd.imageset/gd@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gd.imageset/gd@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gd.imageset/gd@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gd.imageset/gd@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gd.imageset/gd@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gd.imageset/gd@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gd.imageset/gd@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ge.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ge.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ge.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ge.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ge.imageset/ge@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ge.imageset/ge@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ge.imageset/ge@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ge.imageset/ge@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ge.imageset/ge@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ge.imageset/ge@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ge.imageset/ge@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ge.imageset/ge@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gf.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gf.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gf.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gf.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gf.imageset/gf@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gf.imageset/gf@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gf.imageset/gf@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gf.imageset/gf@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gf.imageset/gf@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gf.imageset/gf@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gf.imageset/gf@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gf.imageset/gf@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gg.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gg.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gg.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gg.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gg.imageset/gg@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gg.imageset/gg@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gg.imageset/gg@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gg.imageset/gg@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gg.imageset/gg@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gg.imageset/gg@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gg.imageset/gg@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gg.imageset/gg@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gh.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gh.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gh.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gh.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gh.imageset/gh@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gh.imageset/gh@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gh.imageset/gh@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gh.imageset/gh@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gh.imageset/gh@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gh.imageset/gh@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gh.imageset/gh@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gh.imageset/gh@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gi.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gi.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gi.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gi.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gi.imageset/gi@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gi.imageset/gi@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gi.imageset/gi@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gi.imageset/gi@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gi.imageset/gi@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gi.imageset/gi@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gi.imageset/gi@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gi.imageset/gi@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gl.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gl.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gl.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gl.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gl.imageset/gl@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gl.imageset/gl@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gl.imageset/gl@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gl.imageset/gl@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gl.imageset/gl@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gl.imageset/gl@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gl.imageset/gl@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gl.imageset/gl@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gm.imageset/gm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gm.imageset/gm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gm.imageset/gm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gm.imageset/gm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gm.imageset/gm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gm.imageset/gm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gm.imageset/gm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gm.imageset/gm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gn.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gn.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gn.imageset/gn@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gn.imageset/gn@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gn.imageset/gn@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gn.imageset/gn@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gn.imageset/gn@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gn.imageset/gn@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gn.imageset/gn@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gn.imageset/gn@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gp.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gp.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gp.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gp.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gp.imageset/gp@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gp.imageset/gp@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gp.imageset/gp@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gp.imageset/gp@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gp.imageset/gp@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gp.imageset/gp@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gp.imageset/gp@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gp.imageset/gp@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gq.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gq.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gq.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gq.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gq.imageset/gq@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gq.imageset/gq@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gq.imageset/gq@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gq.imageset/gq@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gq.imageset/gq@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gq.imageset/gq@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gq.imageset/gq@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gq.imageset/gq@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gr.imageset/gr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gr.imageset/gr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gr.imageset/gr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gr.imageset/gr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gr.imageset/gr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gr.imageset/gr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gr.imageset/gr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gr.imageset/gr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gs.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gs.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gs.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gs.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gs.imageset/gs@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gs.imageset/gs@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gs.imageset/gs@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gs.imageset/gs@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gs.imageset/gs@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gs.imageset/gs@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gs.imageset/gs@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gs.imageset/gs@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gt.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gt.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gt.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gt.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gt.imageset/gt@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gt.imageset/gt@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gt.imageset/gt@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gt.imageset/gt@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gt.imageset/gt@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gt.imageset/gt@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gt.imageset/gt@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gt.imageset/gt@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gu.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gu.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gu.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gu.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gu.imageset/gu@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gu.imageset/gu@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gu.imageset/gu@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gu.imageset/gu@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gu.imageset/gu@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gu.imageset/gu@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gu.imageset/gu@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gu.imageset/gu@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gw.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gw.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gw.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gw.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gw.imageset/gw@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gw.imageset/gw@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gw.imageset/gw@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gw.imageset/gw@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gw.imageset/gw@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gw.imageset/gw@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gw.imageset/gw@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gw.imageset/gw@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gy.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/gy.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gy.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/gy.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/gy.imageset/gy@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gy.imageset/gy@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gy.imageset/gy@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gy.imageset/gy@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/gy.imageset/gy@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/gy.imageset/gy@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/gy.imageset/gy@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/gy.imageset/gy@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/hk.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/hk.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hk.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/hk.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/hk.imageset/hk@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/hk.imageset/hk@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hk.imageset/hk@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/hk.imageset/hk@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/hk.imageset/hk@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/hk.imageset/hk@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hk.imageset/hk@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/hk.imageset/hk@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/hm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/hm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/hm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/hm.imageset/hm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/hm.imageset/hm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hm.imageset/hm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/hm.imageset/hm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/hm.imageset/hm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/hm.imageset/hm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hm.imageset/hm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/hm.imageset/hm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/hn.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/hn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hn.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/hn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/hn.imageset/hn@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/hn.imageset/hn@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hn.imageset/hn@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/hn.imageset/hn@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/hn.imageset/hn@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/hn.imageset/hn@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hn.imageset/hn@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/hn.imageset/hn@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/hr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/hr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/hr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/hr.imageset/hr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/hr.imageset/hr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hr.imageset/hr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/hr.imageset/hr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/hr.imageset/hr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/hr.imageset/hr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hr.imageset/hr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/hr.imageset/hr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ht.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ht.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ht.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ht.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ht.imageset/ht@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ht.imageset/ht@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ht.imageset/ht@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ht.imageset/ht@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ht.imageset/ht@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ht.imageset/ht@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ht.imageset/ht@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ht.imageset/ht@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/hu.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/hu.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hu.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/hu.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/hu.imageset/hu@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/hu.imageset/hu@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hu.imageset/hu@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/hu.imageset/hu@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/hu.imageset/hu@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/hu.imageset/hu@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/hu.imageset/hu@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/hu.imageset/hu@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/id.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/id.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/id.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/id.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/id.imageset/id@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/id.imageset/id@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/id.imageset/id@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/id.imageset/id@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/id.imageset/id@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/id.imageset/id@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/id.imageset/id@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/id.imageset/id@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ie.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ie.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ie.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ie.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ie.imageset/ie@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ie.imageset/ie@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ie.imageset/ie@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ie.imageset/ie@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ie.imageset/ie@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ie.imageset/ie@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ie.imageset/ie@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ie.imageset/ie@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/il.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/il.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/il.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/il.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/il.imageset/il@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/il.imageset/il@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/il.imageset/il@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/il.imageset/il@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/il.imageset/il@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/il.imageset/il@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/il.imageset/il@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/il.imageset/il@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/im.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/im.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/im.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/im.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/im.imageset/im@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/im.imageset/im@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/im.imageset/im@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/im.imageset/im@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/im.imageset/im@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/im.imageset/im@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/im.imageset/im@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/im.imageset/im@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/in.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/in.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/in.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/in.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/in.imageset/in@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/in.imageset/in@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/in.imageset/in@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/in.imageset/in@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/in.imageset/in@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/in.imageset/in@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/in.imageset/in@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/in.imageset/in@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/io.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/io.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/io.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/io.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/io.imageset/io@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/io.imageset/io@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/io.imageset/io@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/io.imageset/io@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/io.imageset/io@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/io.imageset/io@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/io.imageset/io@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/io.imageset/io@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/iq.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/iq.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/iq.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/iq.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/iq.imageset/iq@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/iq.imageset/iq@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/iq.imageset/iq@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/iq.imageset/iq@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/iq.imageset/iq@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/iq.imageset/iq@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/iq.imageset/iq@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/iq.imageset/iq@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ir.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ir.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ir.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ir.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ir.imageset/ir@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ir.imageset/ir@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ir.imageset/ir@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ir.imageset/ir@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ir.imageset/ir@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ir.imageset/ir@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ir.imageset/ir@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ir.imageset/ir@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/is.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/is.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/is.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/is.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/is.imageset/is@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/is.imageset/is@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/is.imageset/is@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/is.imageset/is@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/is.imageset/is@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/is.imageset/is@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/is.imageset/is@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/is.imageset/is@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/it.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/it.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/it.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/it.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/it.imageset/it@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/it.imageset/it@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/it.imageset/it@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/it.imageset/it@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/it.imageset/it@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/it.imageset/it@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/it.imageset/it@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/it.imageset/it@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/je.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/je.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/je.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/je.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/je.imageset/je@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/je.imageset/je@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/je.imageset/je@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/je.imageset/je@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/je.imageset/je@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/je.imageset/je@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/je.imageset/je@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/je.imageset/je@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/jm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/jm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/jm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/jm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/jm.imageset/jm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/jm.imageset/jm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/jm.imageset/jm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/jm.imageset/jm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/jm.imageset/jm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/jm.imageset/jm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/jm.imageset/jm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/jm.imageset/jm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/jo.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/jo.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/jo.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/jo.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/jo.imageset/jo@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/jo.imageset/jo@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/jo.imageset/jo@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/jo.imageset/jo@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/jo.imageset/jo@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/jo.imageset/jo@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/jo.imageset/jo@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/jo.imageset/jo@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/jp.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/jp.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/jp.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/jp.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/jp.imageset/jp@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/jp.imageset/jp@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/jp.imageset/jp@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/jp.imageset/jp@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/jp.imageset/jp@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/jp.imageset/jp@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/jp.imageset/jp@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/jp.imageset/jp@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ke.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ke.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ke.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ke.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ke.imageset/ke@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ke.imageset/ke@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ke.imageset/ke@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ke.imageset/ke@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ke.imageset/ke@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ke.imageset/ke@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ke.imageset/ke@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ke.imageset/ke@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kg.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/kg.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kg.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/kg.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/kg.imageset/kg@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kg.imageset/kg@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kg.imageset/kg@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kg.imageset/kg@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kg.imageset/kg@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kg.imageset/kg@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kg.imageset/kg@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kg.imageset/kg@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kh.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/kh.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kh.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/kh.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/kh.imageset/kh@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kh.imageset/kh@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kh.imageset/kh@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kh.imageset/kh@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kh.imageset/kh@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kh.imageset/kh@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kh.imageset/kh@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kh.imageset/kh@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ki.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ki.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ki.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ki.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ki.imageset/ki@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ki.imageset/ki@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ki.imageset/ki@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ki.imageset/ki@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ki.imageset/ki@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ki.imageset/ki@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ki.imageset/ki@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ki.imageset/ki@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/km.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/km.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/km.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/km.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/km.imageset/km@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/km.imageset/km@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/km.imageset/km@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/km.imageset/km@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/km.imageset/km@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/km.imageset/km@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/km.imageset/km@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/km.imageset/km@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kn.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/kn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kn.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/kn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/kn.imageset/kn@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kn.imageset/kn@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kn.imageset/kn@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kn.imageset/kn@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kn.imageset/kn@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kn.imageset/kn@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kn.imageset/kn@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kn.imageset/kn@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kp.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/kp.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kp.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/kp.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/kp.imageset/kp@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kp.imageset/kp@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kp.imageset/kp@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kp.imageset/kp@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kp.imageset/kp@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kp.imageset/kp@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kp.imageset/kp@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kp.imageset/kp@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/kr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/kr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/kr.imageset/kr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kr.imageset/kr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kr.imageset/kr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kr.imageset/kr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kr.imageset/kr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kr.imageset/kr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kr.imageset/kr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kr.imageset/kr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kw.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/kw.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kw.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/kw.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/kw.imageset/kw@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kw.imageset/kw@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kw.imageset/kw@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kw.imageset/kw@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kw.imageset/kw@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kw.imageset/kw@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kw.imageset/kw@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kw.imageset/kw@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ky.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ky.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ky.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ky.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ky.imageset/ky@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ky.imageset/ky@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ky.imageset/ky@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ky.imageset/ky@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ky.imageset/ky@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ky.imageset/ky@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ky.imageset/ky@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ky.imageset/ky@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kz.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/kz.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kz.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/kz.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/kz.imageset/kz@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kz.imageset/kz@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kz.imageset/kz@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kz.imageset/kz@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/kz.imageset/kz@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/kz.imageset/kz@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/kz.imageset/kz@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/kz.imageset/kz@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/la.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/la.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/la.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/la.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/la.imageset/la@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/la.imageset/la@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/la.imageset/la@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/la.imageset/la@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/la.imageset/la@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/la.imageset/la@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/la.imageset/la@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/la.imageset/la@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lb.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/lb.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lb.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/lb.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/lb.imageset/lb@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lb.imageset/lb@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lb.imageset/lb@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lb.imageset/lb@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lb.imageset/lb@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lb.imageset/lb@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lb.imageset/lb@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lb.imageset/lb@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lc.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/lc.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lc.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/lc.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/lc.imageset/lc@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lc.imageset/lc@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lc.imageset/lc@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lc.imageset/lc@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lc.imageset/lc@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lc.imageset/lc@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lc.imageset/lc@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lc.imageset/lc@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/li.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/li.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/li.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/li.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/li.imageset/li@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/li.imageset/li@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/li.imageset/li@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/li.imageset/li@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/li.imageset/li@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/li.imageset/li@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/li.imageset/li@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/li.imageset/li@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lk.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/lk.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lk.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/lk.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/lk.imageset/lk@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lk.imageset/lk@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lk.imageset/lk@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lk.imageset/lk@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lk.imageset/lk@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lk.imageset/lk@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lk.imageset/lk@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lk.imageset/lk@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/lr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/lr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/lr.imageset/lr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lr.imageset/lr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lr.imageset/lr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lr.imageset/lr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lr.imageset/lr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lr.imageset/lr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lr.imageset/lr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lr.imageset/lr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ls.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ls.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ls.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ls.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ls.imageset/ls@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ls.imageset/ls@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ls.imageset/ls@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ls.imageset/ls@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ls.imageset/ls@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ls.imageset/ls@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ls.imageset/ls@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ls.imageset/ls@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lt.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/lt.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lt.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/lt.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/lt.imageset/lt@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lt.imageset/lt@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lt.imageset/lt@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lt.imageset/lt@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lt.imageset/lt@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lt.imageset/lt@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lt.imageset/lt@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lt.imageset/lt@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lu.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/lu.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lu.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/lu.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/lu.imageset/lu@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lu.imageset/lu@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lu.imageset/lu@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lu.imageset/lu@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lu.imageset/lu@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lu.imageset/lu@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lu.imageset/lu@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lu.imageset/lu@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lv.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/lv.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lv.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/lv.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/lv.imageset/lv@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lv.imageset/lv@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lv.imageset/lv@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lv.imageset/lv@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/lv.imageset/lv@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/lv.imageset/lv@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/lv.imageset/lv@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/lv.imageset/lv@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ly.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ly.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ly.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ly.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ly.imageset/ly@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ly.imageset/ly@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ly.imageset/ly@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ly.imageset/ly@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ly.imageset/ly@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ly.imageset/ly@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ly.imageset/ly@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ly.imageset/ly@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ma.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ma.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ma.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ma.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ma.imageset/ma@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ma.imageset/ma@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ma.imageset/ma@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ma.imageset/ma@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ma.imageset/ma@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ma.imageset/ma@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ma.imageset/ma@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ma.imageset/ma@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mc.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mc.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mc.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mc.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mc.imageset/mc@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mc.imageset/mc@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mc.imageset/mc@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mc.imageset/mc@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mc.imageset/mc@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mc.imageset/mc@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mc.imageset/mc@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mc.imageset/mc@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/md.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/md.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/md.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/md.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/md.imageset/md@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/md.imageset/md@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/md.imageset/md@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/md.imageset/md@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/md.imageset/md@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/md.imageset/md@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/md.imageset/md@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/md.imageset/md@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/me.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/me.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/me.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/me.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/me.imageset/me@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/me.imageset/me@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/me.imageset/me@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/me.imageset/me@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/me.imageset/me@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/me.imageset/me@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/me.imageset/me@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/me.imageset/me@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mf.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mf.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mf.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mf.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mf.imageset/mf@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mf.imageset/mf@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mf.imageset/mf@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mf.imageset/mf@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mf.imageset/mf@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mf.imageset/mf@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mf.imageset/mf@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mf.imageset/mf@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mg.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mg.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mg.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mg.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mg.imageset/mg@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mg.imageset/mg@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mg.imageset/mg@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mg.imageset/mg@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mg.imageset/mg@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mg.imageset/mg@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mg.imageset/mg@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mg.imageset/mg@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mh.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mh.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mh.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mh.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mh.imageset/mh@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mh.imageset/mh@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mh.imageset/mh@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mh.imageset/mh@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mh.imageset/mh@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mh.imageset/mh@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mh.imageset/mh@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mh.imageset/mh@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mk.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mk.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mk.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mk.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mk.imageset/mk@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mk.imageset/mk@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mk.imageset/mk@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mk.imageset/mk@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mk.imageset/mk@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mk.imageset/mk@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mk.imageset/mk@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mk.imageset/mk@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ml.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ml.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ml.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ml.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ml.imageset/ml@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ml.imageset/ml@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ml.imageset/ml@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ml.imageset/ml@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ml.imageset/ml@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ml.imageset/ml@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ml.imageset/ml@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ml.imageset/ml@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mm.imageset/mm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mm.imageset/mm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mm.imageset/mm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mm.imageset/mm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mm.imageset/mm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mm.imageset/mm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mm.imageset/mm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mm.imageset/mm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mn.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mn.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mn.imageset/mn@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mn.imageset/mn@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mn.imageset/mn@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mn.imageset/mn@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mn.imageset/mn@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mn.imageset/mn@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mn.imageset/mn@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mn.imageset/mn@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mo.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mo.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mo.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mo.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mo.imageset/mo@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mo.imageset/mo@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mo.imageset/mo@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mo.imageset/mo@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mo.imageset/mo@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mo.imageset/mo@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mo.imageset/mo@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mo.imageset/mo@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mp.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mp.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mp.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mp.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mp.imageset/mp@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mp.imageset/mp@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mp.imageset/mp@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mp.imageset/mp@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mp.imageset/mp@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mp.imageset/mp@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mp.imageset/mp@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mp.imageset/mp@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mq.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mq.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mq.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mq.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mq.imageset/mq@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mq.imageset/mq@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mq.imageset/mq@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mq.imageset/mq@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mq.imageset/mq@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mq.imageset/mq@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mq.imageset/mq@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mq.imageset/mq@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mr.imageset/mr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mr.imageset/mr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mr.imageset/mr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mr.imageset/mr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mr.imageset/mr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mr.imageset/mr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mr.imageset/mr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mr.imageset/mr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ms.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ms.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ms.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ms.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ms.imageset/ms@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ms.imageset/ms@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ms.imageset/ms@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ms.imageset/ms@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ms.imageset/ms@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ms.imageset/ms@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ms.imageset/ms@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ms.imageset/ms@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mt.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mt.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mt.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mt.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mt.imageset/mt@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mt.imageset/mt@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mt.imageset/mt@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mt.imageset/mt@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mt.imageset/mt@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mt.imageset/mt@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mt.imageset/mt@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mt.imageset/mt@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mu.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mu.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mu.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mu.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mu.imageset/mu@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mu.imageset/mu@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mu.imageset/mu@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mu.imageset/mu@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mu.imageset/mu@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mu.imageset/mu@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mu.imageset/mu@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mu.imageset/mu@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mv.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mv.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mv.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mv.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mv.imageset/mv@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mv.imageset/mv@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mv.imageset/mv@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mv.imageset/mv@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mv.imageset/mv@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mv.imageset/mv@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mv.imageset/mv@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mv.imageset/mv@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mw.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mw.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mw.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mw.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mw.imageset/mw@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mw.imageset/mw@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mw.imageset/mw@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mw.imageset/mw@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mw.imageset/mw@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mw.imageset/mw@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mw.imageset/mw@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mw.imageset/mw@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mx.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mx.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mx.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mx.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mx.imageset/mx@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mx.imageset/mx@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mx.imageset/mx@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mx.imageset/mx@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mx.imageset/mx@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mx.imageset/mx@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mx.imageset/mx@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mx.imageset/mx@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/my.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/my.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/my.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/my.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/my.imageset/my@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/my.imageset/my@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/my.imageset/my@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/my.imageset/my@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/my.imageset/my@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/my.imageset/my@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/my.imageset/my@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/my.imageset/my@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mz.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/mz.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mz.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/mz.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/mz.imageset/mz@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mz.imageset/mz@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mz.imageset/mz@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mz.imageset/mz@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/mz.imageset/mz@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/mz.imageset/mz@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/mz.imageset/mz@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/mz.imageset/mz@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/na.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/na.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/na.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/na.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/na.imageset/na@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/na.imageset/na@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/na.imageset/na@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/na.imageset/na@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/na.imageset/na@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/na.imageset/na@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/na.imageset/na@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/na.imageset/na@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nc.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/nc.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nc.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/nc.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/nc.imageset/nc@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nc.imageset/nc@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nc.imageset/nc@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nc.imageset/nc@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nc.imageset/nc@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nc.imageset/nc@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nc.imageset/nc@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nc.imageset/nc@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ne.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ne.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ne.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ne.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ne.imageset/ne@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ne.imageset/ne@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ne.imageset/ne@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ne.imageset/ne@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ne.imageset/ne@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ne.imageset/ne@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ne.imageset/ne@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ne.imageset/ne@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nf.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/nf.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nf.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/nf.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/nf.imageset/nf@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nf.imageset/nf@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nf.imageset/nf@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nf.imageset/nf@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nf.imageset/nf@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nf.imageset/nf@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nf.imageset/nf@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nf.imageset/nf@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ng.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ng.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ng.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ng.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ng.imageset/ng@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ng.imageset/ng@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ng.imageset/ng@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ng.imageset/ng@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ng.imageset/ng@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ng.imageset/ng@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ng.imageset/ng@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ng.imageset/ng@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ni.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ni.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ni.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ni.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ni.imageset/ni@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ni.imageset/ni@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ni.imageset/ni@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ni.imageset/ni@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ni.imageset/ni@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ni.imageset/ni@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ni.imageset/ni@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ni.imageset/ni@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nl.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/nl.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nl.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/nl.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/nl.imageset/nl@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nl.imageset/nl@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nl.imageset/nl@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nl.imageset/nl@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nl.imageset/nl@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nl.imageset/nl@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nl.imageset/nl@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nl.imageset/nl@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/no.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/no.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/no.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/no.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/no.imageset/no@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/no.imageset/no@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/no.imageset/no@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/no.imageset/no@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/no.imageset/no@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/no.imageset/no@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/no.imageset/no@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/no.imageset/no@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/np.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/np.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/np.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/np.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/np.imageset/np@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/np.imageset/np@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/np.imageset/np@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/np.imageset/np@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/np.imageset/np@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/np.imageset/np@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/np.imageset/np@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/np.imageset/np@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/nr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/nr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/nr.imageset/nr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nr.imageset/nr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nr.imageset/nr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nr.imageset/nr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nr.imageset/nr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nr.imageset/nr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nr.imageset/nr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nr.imageset/nr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nu.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/nu.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nu.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/nu.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/nu.imageset/nu@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nu.imageset/nu@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nu.imageset/nu@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nu.imageset/nu@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nu.imageset/nu@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nu.imageset/nu@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nu.imageset/nu@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nu.imageset/nu@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nz.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/nz.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nz.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/nz.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/nz.imageset/nz@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nz.imageset/nz@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nz.imageset/nz@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nz.imageset/nz@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/nz.imageset/nz@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/nz.imageset/nz@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/nz.imageset/nz@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/nz.imageset/nz@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/om.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/om.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/om.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/om.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/om.imageset/om@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/om.imageset/om@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/om.imageset/om@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/om.imageset/om@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/om.imageset/om@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/om.imageset/om@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/om.imageset/om@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/om.imageset/om@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pa.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pa.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pa.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pa.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pa.imageset/pa@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pa.imageset/pa@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pa.imageset/pa@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pa.imageset/pa@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pa.imageset/pa@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pa.imageset/pa@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pa.imageset/pa@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pa.imageset/pa@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pe.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pe.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pe.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pe.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pe.imageset/pe@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pe.imageset/pe@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pe.imageset/pe@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pe.imageset/pe@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pe.imageset/pe@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pe.imageset/pe@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pe.imageset/pe@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pe.imageset/pe@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pf.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pf.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pf.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pf.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pf.imageset/pf@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pf.imageset/pf@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pf.imageset/pf@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pf.imageset/pf@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pf.imageset/pf@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pf.imageset/pf@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pf.imageset/pf@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pf.imageset/pf@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pg.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pg.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pg.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pg.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pg.imageset/pg@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pg.imageset/pg@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pg.imageset/pg@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pg.imageset/pg@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pg.imageset/pg@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pg.imageset/pg@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pg.imageset/pg@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pg.imageset/pg@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ph.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ph.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ph.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ph.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ph.imageset/ph@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ph.imageset/ph@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ph.imageset/ph@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ph.imageset/ph@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ph.imageset/ph@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ph.imageset/ph@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ph.imageset/ph@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ph.imageset/ph@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pk.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pk.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pk.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pk.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pk.imageset/pk@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pk.imageset/pk@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pk.imageset/pk@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pk.imageset/pk@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pk.imageset/pk@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pk.imageset/pk@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pk.imageset/pk@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pk.imageset/pk@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pl.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pl.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pl.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pl.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pl.imageset/pl@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pl.imageset/pl@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pl.imageset/pl@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pl.imageset/pl@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pl.imageset/pl@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pl.imageset/pl@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pl.imageset/pl@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pl.imageset/pl@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pm.imageset/pm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pm.imageset/pm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pm.imageset/pm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pm.imageset/pm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pm.imageset/pm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pm.imageset/pm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pm.imageset/pm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pm.imageset/pm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pn.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pn.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pn.imageset/pn@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pn.imageset/pn@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pn.imageset/pn@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pn.imageset/pn@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pn.imageset/pn@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pn.imageset/pn@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pn.imageset/pn@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pn.imageset/pn@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pr.imageset/pr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pr.imageset/pr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pr.imageset/pr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pr.imageset/pr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pr.imageset/pr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pr.imageset/pr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pr.imageset/pr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pr.imageset/pr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ps.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ps.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ps.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ps.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ps.imageset/ps@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ps.imageset/ps@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ps.imageset/ps@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ps.imageset/ps@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ps.imageset/ps@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ps.imageset/ps@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ps.imageset/ps@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ps.imageset/ps@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pt.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pt.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pt.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pt.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pt.imageset/pt@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pt.imageset/pt@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pt.imageset/pt@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pt.imageset/pt@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pt.imageset/pt@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pt.imageset/pt@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pt.imageset/pt@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pt.imageset/pt@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pw.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/pw.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pw.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/pw.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/pw.imageset/pw@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pw.imageset/pw@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pw.imageset/pw@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pw.imageset/pw@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/pw.imageset/pw@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/pw.imageset/pw@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/pw.imageset/pw@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/pw.imageset/pw@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/py.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/py.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/py.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/py.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/py.imageset/py@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/py.imageset/py@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/py.imageset/py@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/py.imageset/py@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/py.imageset/py@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/py.imageset/py@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/py.imageset/py@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/py.imageset/py@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/qa.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/qa.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/qa.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/qa.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/qa.imageset/qa@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/qa.imageset/qa@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/qa.imageset/qa@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/qa.imageset/qa@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/qa.imageset/qa@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/qa.imageset/qa@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/qa.imageset/qa@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/qa.imageset/qa@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/re.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/re.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/re.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/re.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/re.imageset/re@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/re.imageset/re@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/re.imageset/re@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/re.imageset/re@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/re.imageset/re@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/re.imageset/re@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/re.imageset/re@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/re.imageset/re@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ro.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ro.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ro.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ro.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ro.imageset/ro@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ro.imageset/ro@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ro.imageset/ro@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ro.imageset/ro@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ro.imageset/ro@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ro.imageset/ro@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ro.imageset/ro@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ro.imageset/ro@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/rs.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/rs.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/rs.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/rs.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/rs.imageset/rs@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/rs.imageset/rs@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/rs.imageset/rs@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/rs.imageset/rs@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/rs.imageset/rs@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/rs.imageset/rs@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/rs.imageset/rs@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/rs.imageset/rs@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ru.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ru.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ru.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ru.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ru.imageset/ru@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ru.imageset/ru@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ru.imageset/ru@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ru.imageset/ru@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ru.imageset/ru@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ru.imageset/ru@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ru.imageset/ru@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ru.imageset/ru@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/rw.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/rw.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/rw.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/rw.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/rw.imageset/rw@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/rw.imageset/rw@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/rw.imageset/rw@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/rw.imageset/rw@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/rw.imageset/rw@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/rw.imageset/rw@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/rw.imageset/rw@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/rw.imageset/rw@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sa.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sa.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sa.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sa.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sa.imageset/sa@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sa.imageset/sa@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sa.imageset/sa@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sa.imageset/sa@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sa.imageset/sa@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sa.imageset/sa@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sa.imageset/sa@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sa.imageset/sa@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sb.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sb.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sb.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sb.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sb.imageset/sb@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sb.imageset/sb@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sb.imageset/sb@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sb.imageset/sb@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sb.imageset/sb@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sb.imageset/sb@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sb.imageset/sb@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sb.imageset/sb@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sc.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sc.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sc.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sc.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sc.imageset/sc@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sc.imageset/sc@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sc.imageset/sc@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sc.imageset/sc@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sc.imageset/sc@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sc.imageset/sc@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sc.imageset/sc@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sc.imageset/sc@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sd.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sd.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sd.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sd.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sd.imageset/sd@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sd.imageset/sd@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sd.imageset/sd@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sd.imageset/sd@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sd.imageset/sd@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sd.imageset/sd@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sd.imageset/sd@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sd.imageset/sd@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/se.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/se.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/se.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/se.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/se.imageset/se@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/se.imageset/se@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/se.imageset/se@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/se.imageset/se@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/se.imageset/se@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/se.imageset/se@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/se.imageset/se@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/se.imageset/se@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sg.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sg.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sg.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sg.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sg.imageset/sg@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sg.imageset/sg@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sg.imageset/sg@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sg.imageset/sg@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sg.imageset/sg@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sg.imageset/sg@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sg.imageset/sg@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sg.imageset/sg@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sh.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sh.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sh.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sh.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sh.imageset/sh@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sh.imageset/sh@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sh.imageset/sh@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sh.imageset/sh@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sh.imageset/sh@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sh.imageset/sh@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sh.imageset/sh@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sh.imageset/sh@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/si.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/si.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/si.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/si.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/si.imageset/si@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/si.imageset/si@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/si.imageset/si@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/si.imageset/si@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/si.imageset/si@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/si.imageset/si@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/si.imageset/si@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/si.imageset/si@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sj.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sj.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sj.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sj.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sj.imageset/sj@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sj.imageset/sj@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sj.imageset/sj@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sj.imageset/sj@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sj.imageset/sj@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sj.imageset/sj@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sj.imageset/sj@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sj.imageset/sj@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sk.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sk.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sk.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sk.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sk.imageset/sk@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sk.imageset/sk@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sk.imageset/sk@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sk.imageset/sk@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sk.imageset/sk@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sk.imageset/sk@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sk.imageset/sk@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sk.imageset/sk@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sl.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sl.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sl.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sl.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sl.imageset/sl@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sl.imageset/sl@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sl.imageset/sl@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sl.imageset/sl@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sl.imageset/sl@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sl.imageset/sl@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sl.imageset/sl@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sl.imageset/sl@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sm.imageset/sm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sm.imageset/sm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sm.imageset/sm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sm.imageset/sm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sm.imageset/sm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sm.imageset/sm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sm.imageset/sm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sm.imageset/sm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sn.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sn.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sn.imageset/sn@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sn.imageset/sn@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sn.imageset/sn@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sn.imageset/sn@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sn.imageset/sn@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sn.imageset/sn@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sn.imageset/sn@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sn.imageset/sn@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/so.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/so.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/so.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/so.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/so.imageset/so@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/so.imageset/so@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/so.imageset/so@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/so.imageset/so@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/so.imageset/so@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/so.imageset/so@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/so.imageset/so@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/so.imageset/so@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sr.imageset/sr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sr.imageset/sr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sr.imageset/sr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sr.imageset/sr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sr.imageset/sr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sr.imageset/sr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sr.imageset/sr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sr.imageset/sr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ss.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ss.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ss.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ss.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ss.imageset/ss@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ss.imageset/ss@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ss.imageset/ss@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ss.imageset/ss@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ss.imageset/ss@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ss.imageset/ss@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ss.imageset/ss@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ss.imageset/ss@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/st.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/st.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/st.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/st.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/st.imageset/st@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/st.imageset/st@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/st.imageset/st@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/st.imageset/st@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/st.imageset/st@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/st.imageset/st@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/st.imageset/st@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/st.imageset/st@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sv.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sv.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sv.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sv.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sv.imageset/sv@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sv.imageset/sv@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sv.imageset/sv@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sv.imageset/sv@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sv.imageset/sv@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sv.imageset/sv@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sv.imageset/sv@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sv.imageset/sv@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sx.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sx.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sx.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sx.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sx.imageset/sx@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sx.imageset/sx@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sx.imageset/sx@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sx.imageset/sx@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sx.imageset/sx@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sx.imageset/sx@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sx.imageset/sx@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sx.imageset/sx@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sy.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sy.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sy.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sy.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sy.imageset/sy@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sy.imageset/sy@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sy.imageset/sy@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sy.imageset/sy@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sy.imageset/sy@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sy.imageset/sy@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sy.imageset/sy@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sy.imageset/sy@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sz.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/sz.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sz.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/sz.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/sz.imageset/sz@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sz.imageset/sz@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sz.imageset/sz@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sz.imageset/sz@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/sz.imageset/sz@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/sz.imageset/sz@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/sz.imageset/sz@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/sz.imageset/sz@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tc.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tc.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tc.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tc.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tc.imageset/tc@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tc.imageset/tc@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tc.imageset/tc@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tc.imageset/tc@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tc.imageset/tc@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tc.imageset/tc@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tc.imageset/tc@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tc.imageset/tc@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/td.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/td.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/td.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/td.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/td.imageset/td@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/td.imageset/td@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/td.imageset/td@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/td.imageset/td@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/td.imageset/td@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/td.imageset/td@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/td.imageset/td@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/td.imageset/td@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tf.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tf.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tf.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tf.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tf.imageset/tf@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tf.imageset/tf@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tf.imageset/tf@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tf.imageset/tf@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tf.imageset/tf@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tf.imageset/tf@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tf.imageset/tf@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tf.imageset/tf@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tg.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tg.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tg.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tg.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tg.imageset/tg@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tg.imageset/tg@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tg.imageset/tg@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tg.imageset/tg@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tg.imageset/tg@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tg.imageset/tg@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tg.imageset/tg@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tg.imageset/tg@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/th.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/th.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/th.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/th.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/th.imageset/th@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/th.imageset/th@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/th.imageset/th@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/th.imageset/th@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/th.imageset/th@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/th.imageset/th@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/th.imageset/th@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/th.imageset/th@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tj.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tj.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tj.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tj.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tj.imageset/tj@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tj.imageset/tj@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tj.imageset/tj@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tj.imageset/tj@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tj.imageset/tj@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tj.imageset/tj@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tj.imageset/tj@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tj.imageset/tj@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tk.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tk.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tk.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tk.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tk.imageset/tk@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tk.imageset/tk@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tk.imageset/tk@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tk.imageset/tk@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tk.imageset/tk@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tk.imageset/tk@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tk.imageset/tk@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tk.imageset/tk@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tl.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tl.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tl.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tl.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tl.imageset/tl@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tl.imageset/tl@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tl.imageset/tl@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tl.imageset/tl@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tl.imageset/tl@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tl.imageset/tl@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tl.imageset/tl@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tl.imageset/tl@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tm.imageset/tm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tm.imageset/tm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tm.imageset/tm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tm.imageset/tm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tm.imageset/tm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tm.imageset/tm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tm.imageset/tm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tm.imageset/tm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tn.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tn.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tn.imageset/tn@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tn.imageset/tn@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tn.imageset/tn@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tn.imageset/tn@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tn.imageset/tn@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tn.imageset/tn@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tn.imageset/tn@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tn.imageset/tn@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/to.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/to.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/to.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/to.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/to.imageset/to@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/to.imageset/to@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/to.imageset/to@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/to.imageset/to@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/to.imageset/to@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/to.imageset/to@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/to.imageset/to@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/to.imageset/to@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tr.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tr.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tr.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tr.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tr.imageset/tr@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tr.imageset/tr@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tr.imageset/tr@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tr.imageset/tr@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tr.imageset/tr@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tr.imageset/tr@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tr.imageset/tr@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tr.imageset/tr@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tt.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tt.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tt.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tt.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tt.imageset/tt@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tt.imageset/tt@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tt.imageset/tt@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tt.imageset/tt@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tt.imageset/tt@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tt.imageset/tt@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tt.imageset/tt@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tt.imageset/tt@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tv.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tv.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tv.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tv.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tv.imageset/tv@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tv.imageset/tv@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tv.imageset/tv@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tv.imageset/tv@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tv.imageset/tv@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tv.imageset/tv@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tv.imageset/tv@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tv.imageset/tv@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tw.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tw.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tw.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tw.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tw.imageset/tw@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tw.imageset/tw@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tw.imageset/tw@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tw.imageset/tw@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tw.imageset/tw@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tw.imageset/tw@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tw.imageset/tw@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tw.imageset/tw@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tz.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/tz.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tz.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/tz.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/tz.imageset/tz@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tz.imageset/tz@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tz.imageset/tz@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tz.imageset/tz@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/tz.imageset/tz@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/tz.imageset/tz@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/tz.imageset/tz@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/tz.imageset/tz@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ua.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ua.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ua.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ua.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ua.imageset/ua@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ua.imageset/ua@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ua.imageset/ua@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ua.imageset/ua@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ua.imageset/ua@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ua.imageset/ua@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ua.imageset/ua@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ua.imageset/ua@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ug.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ug.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ug.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ug.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ug.imageset/ug@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ug.imageset/ug@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ug.imageset/ug@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ug.imageset/ug@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ug.imageset/ug@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ug.imageset/ug@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ug.imageset/ug@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ug.imageset/ug@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/um.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/um.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/um.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/um.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/um.imageset/um@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/um.imageset/um@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/um.imageset/um@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/um.imageset/um@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/um.imageset/um@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/um.imageset/um@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/um.imageset/um@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/um.imageset/um@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/un.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/un.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/un.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/un.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/un.imageset/un@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/un.imageset/un@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/un.imageset/un@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/un.imageset/un@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/un.imageset/un@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/un.imageset/un@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/un.imageset/un@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/un.imageset/un@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/us.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/us.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/us.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/us.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/us.imageset/us@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/us.imageset/us@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/us.imageset/us@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/us.imageset/us@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/us.imageset/us@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/us.imageset/us@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/us.imageset/us@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/us.imageset/us@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/uy.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/uy.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/uy.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/uy.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/uy.imageset/uy@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/uy.imageset/uy@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/uy.imageset/uy@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/uy.imageset/uy@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/uy.imageset/uy@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/uy.imageset/uy@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/uy.imageset/uy@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/uy.imageset/uy@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/uz.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/uz.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/uz.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/uz.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/uz.imageset/uz@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/uz.imageset/uz@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/uz.imageset/uz@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/uz.imageset/uz@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/uz.imageset/uz@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/uz.imageset/uz@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/uz.imageset/uz@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/uz.imageset/uz@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/va.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/va.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/va.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/va.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/va.imageset/va@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/va.imageset/va@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/va.imageset/va@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/va.imageset/va@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/va.imageset/va@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/va.imageset/va@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/va.imageset/va@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/va.imageset/va@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/vc.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/vc.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vc.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/vc.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/vc.imageset/vc@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/vc.imageset/vc@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vc.imageset/vc@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/vc.imageset/vc@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/vc.imageset/vc@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/vc.imageset/vc@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vc.imageset/vc@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/vc.imageset/vc@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ve.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ve.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ve.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ve.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ve.imageset/ve@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ve.imageset/ve@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ve.imageset/ve@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ve.imageset/ve@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ve.imageset/ve@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ve.imageset/ve@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ve.imageset/ve@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ve.imageset/ve@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/vg.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/vg.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vg.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/vg.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/vg.imageset/vg@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/vg.imageset/vg@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vg.imageset/vg@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/vg.imageset/vg@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/vg.imageset/vg@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/vg.imageset/vg@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vg.imageset/vg@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/vg.imageset/vg@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/vi.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/vi.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vi.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/vi.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/vi.imageset/vi@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/vi.imageset/vi@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vi.imageset/vi@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/vi.imageset/vi@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/vi.imageset/vi@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/vi.imageset/vi@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vi.imageset/vi@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/vi.imageset/vi@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/vn.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/vn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vn.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/vn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/vn.imageset/vn@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/vn.imageset/vn@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vn.imageset/vn@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/vn.imageset/vn@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/vn.imageset/vn@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/vn.imageset/vn@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vn.imageset/vn@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/vn.imageset/vn@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/vu.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/vu.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vu.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/vu.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/vu.imageset/vu@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/vu.imageset/vu@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vu.imageset/vu@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/vu.imageset/vu@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/vu.imageset/vu@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/vu.imageset/vu@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/vu.imageset/vu@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/vu.imageset/vu@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/wf.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/wf.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/wf.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/wf.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/wf.imageset/wf@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/wf.imageset/wf@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/wf.imageset/wf@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/wf.imageset/wf@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/wf.imageset/wf@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/wf.imageset/wf@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/wf.imageset/wf@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/wf.imageset/wf@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ws.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ws.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ws.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ws.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ws.imageset/ws@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ws.imageset/ws@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ws.imageset/ws@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ws.imageset/ws@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ws.imageset/ws@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ws.imageset/ws@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ws.imageset/ws@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ws.imageset/ws@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/xk.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/xk.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/xk.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/xk.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/xk.imageset/xk@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/xk.imageset/xk@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/xk.imageset/xk@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/xk.imageset/xk@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/xk.imageset/xk@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/xk.imageset/xk@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/xk.imageset/xk@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/xk.imageset/xk@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ye.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/ye.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ye.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/ye.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/ye.imageset/ye@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ye.imageset/ye@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ye.imageset/ye@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ye.imageset/ye@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/ye.imageset/ye@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/ye.imageset/ye@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/ye.imageset/ye@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/ye.imageset/ye@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/yt.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/yt.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/yt.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/yt.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/yt.imageset/yt@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/yt.imageset/yt@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/yt.imageset/yt@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/yt.imageset/yt@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/yt.imageset/yt@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/yt.imageset/yt@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/yt.imageset/yt@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/yt.imageset/yt@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/za.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/za.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/za.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/za.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/za.imageset/za@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/za.imageset/za@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/za.imageset/za@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/za.imageset/za@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/za.imageset/za@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/za.imageset/za@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/za.imageset/za@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/za.imageset/za@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/zm.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/zm.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/zm.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/zm.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/zm.imageset/zm@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/zm.imageset/zm@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/zm.imageset/zm@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/zm.imageset/zm@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/zm.imageset/zm@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/zm.imageset/zm@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/zm.imageset/zm@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/zm.imageset/zm@3x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/zw.imageset/Contents.json b/Passepartout/App/Shared/Flags.xcassets/flags/zw.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/zw.imageset/Contents.json rename to Passepartout/App/Shared/Flags.xcassets/flags/zw.imageset/Contents.json diff --git a/Passepartout/App/iOS/Flags.xcassets/zw.imageset/zw@2x.png b/Passepartout/App/Shared/Flags.xcassets/flags/zw.imageset/zw@2x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/zw.imageset/zw@2x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/zw.imageset/zw@2x.png diff --git a/Passepartout/App/iOS/Flags.xcassets/zw.imageset/zw@3x.png b/Passepartout/App/Shared/Flags.xcassets/flags/zw.imageset/zw@3x.png similarity index 100% rename from Passepartout/App/iOS/Flags.xcassets/zw.imageset/zw@3x.png rename to Passepartout/App/Shared/Flags.xcassets/flags/zw.imageset/zw@3x.png diff --git a/PassepartoutCore/Sources/PassepartoutConstants/LocalProduct.swift b/Passepartout/App/Shared/InApp/LocalProduct.swift similarity index 53% rename from PassepartoutCore/Sources/PassepartoutConstants/LocalProduct.swift rename to Passepartout/App/Shared/InApp/LocalProduct.swift index 554d22e5..3ae9592d 100644 --- a/PassepartoutCore/Sources/PassepartoutConstants/LocalProduct.swift +++ b/Passepartout/App/Shared/InApp/LocalProduct.swift @@ -25,8 +25,9 @@ import Foundation import StoreKit +import PassepartoutCore -public struct LocalProduct: RawRepresentable, Equatable, Hashable { +struct LocalProduct: RawRepresentable, Equatable, Hashable { private static let bundleSubdomain = "ios" private static let bundle = "com.algoritmico.\(bundleSubdomain).Passepartout" @@ -35,23 +36,23 @@ public struct LocalProduct: RawRepresentable, Equatable, Hashable { private static let featuresBundle = "\(bundle).features" - public static let providersBundle = "\(bundle).providers" + static let providersBundle = "\(bundle).providers" // MARK: Donations - public static let tinyDonation = LocalProduct(donationDescription: "Tiny") + static let tinyDonation = LocalProduct(donationDescription: "Tiny") - public static let smallDonation = LocalProduct(donationDescription: "Small") + static let smallDonation = LocalProduct(donationDescription: "Small") - public static let mediumDonation = LocalProduct(donationDescription: "Medium") + static let mediumDonation = LocalProduct(donationDescription: "Medium") - public static let bigDonation = LocalProduct(donationDescription: "Big") + static let bigDonation = LocalProduct(donationDescription: "Big") - public static let hugeDonation = LocalProduct(donationDescription: "Huge") + static let hugeDonation = LocalProduct(donationDescription: "Huge") - public static let maxiDonation = LocalProduct(donationDescription: "Maxi") + static let maxiDonation = LocalProduct(donationDescription: "Maxi") - public static let allDonations: [LocalProduct] = [ + static let allDonations: [LocalProduct] = [ .tinyDonation, .smallDonation, .mediumDonation, @@ -66,19 +67,19 @@ public struct LocalProduct: RawRepresentable, Equatable, Hashable { // MARK: Features - public static let allProviders = LocalProduct(featureId: "all_providers") + static let allProviders = LocalProduct(featureId: "all_providers") - public static let trustedNetworks = LocalProduct(featureId: "trusted_networks") + static let trustedNetworks = LocalProduct(featureId: "trusted_networks") - public static let siriShortcuts = LocalProduct(featureId: "siri") + static let siriShortcuts = LocalProduct(featureId: "siri") - public static let fullVersion_iOS = LocalProduct(featureId: "full_version") + static let fullVersion_iOS = LocalProduct(featureId: "full_version") - public static let fullVersion_macOS = LocalProduct(featureId: "full_mac_version") + static let fullVersion_macOS = LocalProduct(featureId: "full_mac_version") - public static let fullVersion = LocalProduct(featureId: "full_multi_version") + static let fullVersion = LocalProduct(featureId: "full_multi_version") - public static let allFeatures: [LocalProduct] = [ + static let allFeatures: [LocalProduct] = [ .allProviders, .trustedNetworks, .siriShortcuts, @@ -93,45 +94,70 @@ public struct LocalProduct: RawRepresentable, Equatable, Hashable { // MARK: All - public static var all: [LocalProduct] { + static var all: [LocalProduct] { return allDonations + allFeatures// + allProviders } - public var isDonation: Bool { + var isDonation: Bool { return rawValue.hasPrefix(LocalProduct.donationsBundle) } - public var isFeature: Bool { + var isFeature: Bool { return rawValue.hasPrefix(LocalProduct.featuresBundle) } - public var isProvider: Bool { + var isProvider: Bool { return rawValue.hasPrefix(LocalProduct.providersBundle) } // MARK: RawRepresentable - public let rawValue: String + let rawValue: String - public init?(rawValue: String) { + init?(rawValue: String) { self.rawValue = rawValue } - - // MARK: Equatable - - public static func ==(lhs: LocalProduct, rhs: LocalProduct) -> Bool { - return lhs.rawValue == rhs.rawValue - } - - // MARK: Hashable - - public func hash(into hasher: inout Hasher) { - rawValue.hash(into: &hasher) - } } -public extension LocalProduct { +extension LocalProduct { func matchesStoreKitProduct(_ skProduct: SKProduct) -> Bool { return skProduct.productIdentifier == rawValue } } + +extension ProviderName { + var product: LocalProduct { + .init(rawValue: "\(LocalProduct.providersBundle).\(inApp)")! + } +} + +// legacy in-app products +private extension ProviderName { + var inApp: String { + switch self { + case .mullvad: + return "Mullvad" + + case .nordvpn: + return "NordVPN" + + case .pia: + return "PIA" + + case .protonvpn: + return "ProtonVPN" + + case .tunnelbear: + return "TunnelBear" + + case .vyprvpn: + return "VyprVPN" + + case .windscribe: + return "Windscribe" + + default: + return self + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/ProductManager.swift b/Passepartout/App/Shared/InApp/ProductManager.swift similarity index 51% rename from PassepartoutCore/Sources/PassepartoutCore/Model/ProductManager.swift rename to Passepartout/App/Shared/InApp/ProductManager.swift index 734f75ba..d8f63107 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/ProductManager.swift +++ b/Passepartout/App/Shared/InApp/ProductManager.swift @@ -24,46 +24,50 @@ // import Foundation +import PassepartoutCore import StoreKit -import Convenience -import SwiftyBeaver import Kvitto -import TunnelKit -import PassepartoutConstants -private let log = SwiftyBeaver.self - -public enum ProductError: Error { +enum ProductError: Error { case uneligible case beta } -public class ProductManager: NSObject { - public struct Configuration { - public let locksBetaFeatures: Bool - - public let isBetaFullVersion: Bool +@MainActor +class ProductManager: NSObject, ObservableObject { + enum AppType: Int { + case freemium = 0 - public let lastFullVersionBuild: (Int, LocalProduct) + case beta = 1 - public init( - locksBetaFeatures: Bool, - isBetaFullVersion: Bool, + case fullVersion = 2 + } + + struct Configuration { + let appType: AppType + + let lastFullVersionBuild: (Int, LocalProduct) + + init( + appType: AppType, lastFullVersionBuild: (Int, LocalProduct) ) { - self.locksBetaFeatures = locksBetaFeatures - self.isBetaFullVersion = isBetaFullVersion + self.appType = appType self.lastFullVersionBuild = lastFullVersionBuild } } - public static let didReloadReceipt = Notification.Name("ProductManagerDidReloadReceipt") + static let didReloadReceipt = Notification.Name("ProductManagerDidReloadReceipt") - public static let didReviewPurchases = Notification.Name("ProductManagerDidReviewPurchases") + let cfg: Configuration - public let cfg: Configuration + @Published private(set) var isRefreshingProducts = false + @Published private(set) var products: [SKProduct] + + // + private let inApp: InApp private var purchasedAppBuild: Int? @@ -78,8 +82,9 @@ public class ProductManager: NSObject { private var restoreCompletionHandler: ((Error?) -> Void)? - public init(_ cfg: Configuration) { + init(_ cfg: Configuration) { self.cfg = cfg + products = [] inApp = InApp() purchasedAppBuild = nil purchasedFeatures = [] @@ -90,45 +95,36 @@ public class ProductManager: NSObject { reloadReceipt() SKPaymentQueue.default().add(self) + + refreshProducts() } deinit { SKPaymentQueue.default().remove(self) } - public var isBeta: Bool { - #if os(iOS) - #if targetEnvironment(simulator) - return true - #else - return Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" - #endif - #else - // FIXME: skip TestFlight on macOS until beta condition is clearly determined - return false - #endif - } - - public func listProducts(completionHandler: (([SKProduct]?, Error?) -> Void)?) { - let products = LocalProduct.all - guard !products.isEmpty else { - completionHandler?(nil, nil) + func refreshProducts() { + let ids = LocalProduct.all + guard !ids.isEmpty else { return } - inApp.requestProducts(withIdentifiers: products, completionHandler: { _ in - log.debug("In-app products: \(self.inApp.products.map { $0.productIdentifier })") + isRefreshingProducts = true + inApp.requestProducts(withIdentifiers: ids, completionHandler: { _ in + pp_log.debug("In-app products: \(self.inApp.products.map { $0.productIdentifier })") - completionHandler?(self.inApp.products, nil) + self.products = self.inApp.products + self.isRefreshingProducts = false }, failureHandler: { - completionHandler?(nil, $0) + pp_log.warning("Unable to list products: \($0)") + self.isRefreshingProducts = false }) } - public func product(withIdentifier identifier: LocalProduct) -> SKProduct? { + func product(withIdentifier identifier: LocalProduct) -> SKProduct? { return inApp.product(withIdentifier: identifier) } - public func featureProducts(including: [LocalProduct]) -> [SKProduct] { + func featureProducts(including: [LocalProduct]) -> [SKProduct] { return inApp.products.filter { guard let p = LocalProduct(rawValue: $0.productIdentifier) else { return false @@ -143,7 +139,7 @@ public class ProductManager: NSObject { } } - public func featureProducts(excluding: [LocalProduct]) -> [SKProduct] { + func featureProducts(excluding: [LocalProduct]) -> [SKProduct] { return inApp.products.filter { guard let p = LocalProduct(rawValue: $0.productIdentifier) else { return false @@ -158,16 +154,16 @@ public class ProductManager: NSObject { } } - public func purchase(_ product: SKProduct, completionHandler: @escaping (InAppPurchaseResult, Error?) -> Void) { + func purchase(_ product: SKProduct, completionHandler: @escaping (Result) -> Void) { inApp.purchase(product: product) { - if $0 == .success { + if case .success = $0 { self.reloadReceipt() } - completionHandler($0, $1) + completionHandler($0) } } - public func restorePurchases(completionHandler: @escaping (Error?) -> Void) { + func restorePurchases(completionHandler: @escaping (Error?) -> Void) { restoreCompletionHandler = completionHandler refreshRequest = SKReceiptRefreshRequest() refreshRequest?.delegate = self @@ -185,7 +181,7 @@ public class ProductManager: NSObject { } private func isFullVersion() -> Bool { - if isBeta && cfg.isBetaFullVersion { + if cfg.appType == .fullVersion { return true } if isCurrentPlatformVersion() { @@ -194,7 +190,7 @@ public class ProductManager: NSObject { return purchasedFeatures.contains(.fullVersion) } - private func isEligible(forFeature feature: LocalProduct) -> Bool { + func isEligible(forFeature feature: LocalProduct) -> Bool { #if os(iOS) return isFullVersion() || purchasedFeatures.contains(feature) #else @@ -202,60 +198,33 @@ public class ProductManager: NSObject { #endif } - public func isEligibleForFeedback() -> Bool { - return isBeta || !purchasedFeatures.isEmpty + func isEligible(forProvider providerName: ProviderName) -> Bool { + isEligible(forFeature: providerName.product) + } + + func isEligibleForFeedback() -> Bool { + return cfg.appType == .beta || !purchasedFeatures.isEmpty } - public func verifyEligible(forFeature feature: LocalProduct) throws { - if isBeta { - if cfg.isBetaFullVersion { - return - } - guard !cfg.locksBetaFeatures else { - throw ProductError.beta - } - } - guard isEligible(forFeature: feature) else { - throw ProductError.uneligible - } - } - - public func verifyEligible(forProvider metadata: Infrastructure.Metadata) throws { - if isBeta { - if cfg.isBetaFullVersion { - return - } - guard !cfg.locksBetaFeatures else { - throw ProductError.beta - } - } - guard metadata.name != .oeck else { - return - } - guard isEligible(forFeature: metadata.product) else { - throw ProductError.uneligible - } - } - - public func hasPurchased(_ product: LocalProduct) -> Bool { + func hasPurchased(_ product: LocalProduct) -> Bool { return purchasedFeatures.contains(product) } - public func isCancelledPurchase(_ product: LocalProduct) -> Bool { + func isCancelledPurchase(_ product: LocalProduct) -> Bool { return cancelledPurchases.contains(product) } - public func purchaseDate(forProduct product: LocalProduct) -> Date? { + func purchaseDate(forProduct product: LocalProduct) -> Date? { return purchaseDates[product] } - public func reloadReceipt(andNotify: Bool = true) { + func reloadReceipt(andNotify: Bool = true) { guard let url = Bundle.main.appStoreReceiptURL else { - log.warning("No App Store receipt found!") + pp_log.warning("No App Store receipt found!") return } guard let receipt = Receipt(contentsOfURL: url) else { - log.error("Could not parse App Store receipt!") + pp_log.error("Could not parse App Store receipt!") return } @@ -266,7 +235,7 @@ public class ProductManager: NSObject { cancelledPurchases.removeAll() if let buildNumber = purchasedAppBuild { - log.debug("Original purchased build: \(buildNumber)") + pp_log.debug("Original purchased build: \(buildNumber)") // treat former purchases as full versions if buildNumber <= cfg.lastFullVersionBuild.0 { @@ -276,24 +245,25 @@ public class ProductManager: NSObject { if let iapReceipts = receipt.inAppPurchaseReceipts { purchaseDates.removeAll() - log.debug("In-app receipts:") + pp_log.debug("In-app receipts:") iapReceipts.forEach { guard let pid = $0.productIdentifier, let product = LocalProduct(rawValue: pid) else { return } if let cancellationDate = $0.cancellationDate { - log.debug("\t\(pid) [cancelled on: \(cancellationDate)]") + pp_log.debug("\t\(pid) [cancelled on: \(cancellationDate)]") cancelledPurchases.insert(product) return } if let purchaseDate = $0.originalPurchaseDate { - log.debug("\t\(pid) [purchased on: \(purchaseDate)]") + pp_log.debug("\t\(pid) [purchased on: \(purchaseDate)]") purchaseDates[product] = purchaseDate } purchasedFeatures.insert(product) } } - log.info("Purchased features: \(purchasedFeatures)") + pp_log.info("Purchased features: \(purchasedFeatures)") + objectWillChange.send() if andNotify { NotificationCenter.default.post(name: ProductManager.didReloadReceipt, object: nil) @@ -302,7 +272,7 @@ public class ProductManager: NSObject { } extension ProductManager: SKPaymentTransactionObserver { - public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { + func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { DispatchQueue.main.async { [weak self] in self?.reloadReceipt() } @@ -310,7 +280,7 @@ extension ProductManager: SKPaymentTransactionObserver { } extension ProductManager: SKRequestDelegate { - public func requestDidFinish(_ request: SKRequest) { + func requestDidFinish(_ request: SKRequest) { DispatchQueue.main.async { [weak self] in self?.reloadReceipt() } @@ -325,7 +295,7 @@ extension ProductManager: SKRequestDelegate { } } - public func request(_ request: SKRequest, didFailWithError error: Error) { + func request(_ request: SKRequest, didFailWithError error: Error) { DispatchQueue.main.async { [weak self] in self?.restoreCompletionHandler?(error) self?.restoreCompletionHandler = nil @@ -334,21 +304,11 @@ extension ProductManager: SKRequestDelegate { } extension ProductManager { - public static let shared = ProductManager( - Configuration( - locksBetaFeatures: AppConstants.InApp.locksBetaFeatures, - isBetaFullVersion: AppConstants.InApp.isBetaFullVersion, - lastFullVersionBuild: AppConstants.InApp.lastFullVersionBuild - ) - ) - - public func reviewPurchases() { - let service = TransientStore.shared.service + func hasRefunded() -> Bool { reloadReceipt(andNotify: false) let isEligibleForFullVersion = isFullVersion() let hasCancelledFullVersion: Bool let hasCancelledTrustedNetworks: Bool - var anyRefund = false #if os(iOS) hasCancelledFullVersion = !isEligibleForFullVersion && (isCancelledPurchase(.fullVersion) || isCancelledPurchase(.fullVersion_iOS)) @@ -359,55 +319,6 @@ extension ProductManager { #endif // review features and potentially revert them if they were used (Siri is handled in AppDelegate) - - log.debug("Checking 'Trusted networks'") - if hasCancelledFullVersion || hasCancelledTrustedNetworks { - - // reset trusted networks for ALL profiles (must load first) - for key in service.allProfileKeys() { - guard let profile = service.profile(withKey: key) else { - continue - } - #if os(iOS) - if profile.trustedNetworks.includesMobile || !profile.trustedNetworks.includedWiFis.isEmpty { - profile.trustedNetworks.includesMobile = false - profile.trustedNetworks.includedWiFis.removeAll() - anyRefund = true - } - #else - if !profile.trustedNetworks.includedWiFis.isEmpty { - profile.trustedNetworks.includedWiFis.removeAll() - anyRefund = true - } - #endif - } - if anyRefund { - log.debug("\tRefunded") - } - } - - log.debug("Checking providers") - for name in service.providerNames() { - guard let metadata = InfrastructureFactory.shared.metadata(forName: name) else { - continue - } - if hasCancelledFullVersion || (!isEligibleForFullVersion && isCancelledPurchase(metadata.product)) { - service.removeProfile(ProfileKey(name)) - log.debug("\tRefunded provider: \(name)") - anyRefund = true - } - } - - guard anyRefund else { - return - } - - // - - // save reverts and remove fraud VPN profile - TransientStore.shared.serialize(withProfiles: true) - VPN.shared.uninstall(completionHandler: nil) - - NotificationCenter.default.post(name: ProductManager.didReviewPurchases, object: nil) + return hasCancelledFullVersion || hasCancelledTrustedNetworks } } diff --git a/Passepartout/App/iOS/Info.plist b/Passepartout/App/Shared/Info.plist similarity index 84% rename from Passepartout/App/iOS/Info.plist rename to Passepartout/App/Shared/Info.plist index 71b8d2cf..8ffaffc8 100644 --- a/Passepartout/App/iOS/Info.plist +++ b/Passepartout/App/Shared/Info.plist @@ -14,11 +14,10 @@ CFBundleTypeRole Viewer LSHandlerRank - Alternate + Default LSItemContentTypes - public.content - public.data + public.item @@ -33,15 +32,20 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.18.0 + 2.0.0 CFBundleVersion - 2984 + 3000 ITSAppUsesNonExemptEncryption + LSApplicationCategoryType + public.app-category.utilities + LSApplicationQueriesSchemes + + mailto + twitch + LSRequiresIPhoneOS - LSSupportsOpeningDocumentsInPlace - NSHumanReadableCopyright $(CFG_COPYRIGHT) NSLocationWhenInUseUsageDescription @@ -57,12 +61,19 @@ UntrustCellularNetworkIntent UntrustCurrentNetworkIntent + UIBackgroundModes + + remote-notification + UIFileSharingEnabled - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main + UILaunchScreen + + UIColorName + primaryColor + UIImageName + logo + UIRequiredDeviceCapabilities arm64 @@ -72,8 +83,6 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad @@ -82,13 +91,10 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UISupportsDocumentBrowser + UIViewControllerBasedStatusBarAppearance - LSApplicationQueriesSchemes - - mailto - twitch - com.algoritmico.Passepartout.config appstore_id diff --git a/Passepartout/App/iOS/Base.lproj/Intents.intentdefinition b/Passepartout/App/Shared/Intents/Base.lproj/Intents.intentdefinition similarity index 94% rename from Passepartout/App/iOS/Base.lproj/Intents.intentdefinition rename to Passepartout/App/Shared/Intents/Base.lproj/Intents.intentdefinition index 58ceccf9..53383964 100644 --- a/Passepartout/App/iOS/Base.lproj/Intents.intentdefinition +++ b/Passepartout/App/Shared/Intents/Base.lproj/Intents.intentdefinition @@ -5,15 +5,15 @@ INEnums INIntentDefinitionModelVersion - 1.1 + 1.2 INIntentDefinitionNamespace CM6KGi INIntentDefinitionSystemVersion - 19D76 + 21E230 INIntentDefinitionToolsBuildVersion - 11C504 + 13E113 INIntentDefinitionToolsVersion - 11.3.1 + 13.3 INIntents @@ -29,14 +29,14 @@ ConnectVPN INIntentParameterCombinations - profileId,profileTitle,context + profileId,profileName INIntentParameterCombinationIsPrimary INIntentParameterCombinationSupportsBackgroundExecution INIntentParameterCombinationTitle - Connect to ${profileTitle} + Connect to ${profileName} INIntentParameterCombinationTitleID U6o81V @@ -52,9 +52,9 @@ Sentences INIntentParameterName - context + profileId INIntentParameterTag - 5 + 4 INIntentParameterType String @@ -67,22 +67,7 @@ Sentences INIntentParameterName - profileId - INIntentParameterTag - 4 - INIntentParameterType - String - - - INIntentParameterDisplayPriority - 3 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - - INIntentParameterName - profileTitle + profileName INIntentParameterTag 6 INIntentParameterType @@ -362,23 +347,23 @@ INIntentDescriptionID KjkCfU INIntentLastParameterTag - 3 + 4 INIntentName MoveToLocation INIntentParameterCombinations - providerId,poolName,poolId + serverName,providerFullName,serverId,profileId INIntentParameterCombinationSubtitle - With ${providerId} provider + With ${providerFullName} provider INIntentParameterCombinationSubtitleID - 66bZBE + OeVNIO INIntentParameterCombinationSupportsBackgroundExecution INIntentParameterCombinationTitle - Connect to ${poolName} + Connect to ${serverName} INIntentParameterCombinationTitleID - WnTPFg + nzeF6m INIntentParameters @@ -392,7 +377,7 @@ Sentences INIntentParameterName - providerId + profileId INIntentParameterTag 2 INIntentParameterType @@ -405,11 +390,13 @@ INIntentParameterMetadataCapitalization Sentences + INIntentParameterMetadataDefaultValueID + Cf6h1r INIntentParameterName - poolId + providerFullName INIntentParameterTag - 3 + 4 INIntentParameterType String @@ -422,7 +409,22 @@ Sentences INIntentParameterName - poolName + serverId + INIntentParameterTag + 3 + INIntentParameterType + String + + + INIntentParameterDisplayPriority + 4 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + + INIntentParameterName + serverName INIntentParameterTag 1 INIntentParameterType diff --git a/Passepartout/App/Shared/Intents/IntentDispatcher+Activities.swift b/Passepartout/App/Shared/Intents/IntentDispatcher+Activities.swift new file mode 100644 index 00000000..26edafd0 --- /dev/null +++ b/Passepartout/App/Shared/Intents/IntentDispatcher+Activities.swift @@ -0,0 +1,164 @@ +// +// IntentDispatcher+Activities.swift +// Passepartout +// +// Created by Davide De Rosa on 3/30/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Intents +import PassepartoutCore + +@MainActor +extension IntentDispatcher { + private enum IntentError: Error { + case notProvider(UUID) + + case serverNotFound(UUID) + + case activeAndConnected(UUID) + } + + typealias VPNIntentActivity = IntentActivity + + static let enableVPN = VPNIntentActivity(name: Constants.Activities.enableVPN) { activity, vpnManager in + pp_log.info("Enabling VPN...") + + Task { + do { + try await vpnManager.connectWithActiveProfile() + } catch { + pp_log.error("Unable to connect with active profile: \(error)") + } + } + } + + static let disableVPN = VPNIntentActivity(name: Constants.Activities.disableVPN) { activity, vpnManager in + pp_log.info("Disabling VPN...") + + Task { + await vpnManager.disable() + } + } + + static let connectVPN = VPNIntentActivity(name: Constants.Activities.connectVPN) { activity, vpnManager in + pp_log.info("Connecting VPN...") + + guard let intent = activity.interaction?.intent as? ConnectVPNIntent else { + assertionFailure("Not a ConnectVPNIntent?") + return + } + guard let uuid = intent.profileId, let profileId = UUID(uuidString: uuid) else { + assertionFailure("Profile id is not valid") + if let interactionIdentifier = activity.interaction?.identifier { + INInteraction.delete(with: [interactionIdentifier], completion: nil) + } + return + } + Task { + do { + try await vpnManager.connect(with: profileId) + } catch { + pp_log.error("Unable to connect with profile \(profileId): \(error)") + } + } + } + + static let moveToLocation = VPNIntentActivity(name: Constants.Activities.moveToLocation) { activity, vpnManager in + pp_log.info("Moving to VPN location...") + + guard let intent = activity.interaction?.intent as? MoveToLocationIntent else { + assertionFailure("Not a MoveToLocationIntent?") + return + } + guard let uuid = intent.profileId, let profileId = UUID(uuidString: uuid) else { + if let interactionIdentifier = activity.interaction?.identifier { + INInteraction.delete(with: [interactionIdentifier], completion: nil) + } + return + } + guard let newServerId = intent.serverId else { + assertionFailure("Missing serverId") + if let interactionIdentifier = activity.interaction?.identifier { + INInteraction.delete(with: [interactionIdentifier], completion: nil) + } + return + } + Task { + do { + try await vpnManager.connect(with: profileId, toServer: newServerId) + } catch { + pp_log.error("Unable to connect with profile \(profileId): \(error)") + } + } + } + + static let trustCellularNetwork = VPNIntentActivity(name: Constants.Activities.trustCellularNetwork) { activity, vpnManager in + pp_log.info("Trusting mobile network...") + handleCellularNetwork(true, vpnManager) + } + + static let trustCurrentNetwork = VPNIntentActivity(name: Constants.Activities.trustCurrentNetwork) { activity, vpnManager in + pp_log.info("Trusting current Wi-Fi...") + handleCurrentNetwork(true, vpnManager) + } + + static let untrustCellularNetwork = VPNIntentActivity(name: Constants.Activities.untrustCellularNetwork) { activity, vpnManager in + pp_log.info("Untrusting mobile network...") + handleCellularNetwork(false, vpnManager) + } + + static let untrustCurrentNetwork = VPNIntentActivity(name: Constants.Activities.untrustCurrentNetwork) { activity, vpnManager in + pp_log.info("Untrusting current Wi-Fi...") + handleCurrentNetwork(false, vpnManager) + } + + private static func handleCellularNetwork(_ trust: Bool, _ vpnManager: VPNManager) { + #if os(iOS) + Task { + do { + try await vpnManager.modifyActiveProfile { + $0.onDemand.withMobileNetwork = trust + } + } catch { + pp_log.error("Unable to modify cellular trust: \(error)") + } + } + #endif + } + + private static func handleCurrentNetwork(_ trust: Bool, _ vpnManager: VPNManager) { + guard let ssid = Utils.currentWifiNetworkName() else { + pp_log.warning("Not connected to any Wi-Fi or no permission to read location (needs 'While Using' or 'Always')") + return + } + Task { + do { + try await vpnManager.modifyActiveProfile { + pp_log.info("Wi-Fi SSID: \(ssid)") + $0.onDemand.withSSIDs[ssid] = trust + } + } catch { + pp_log.error("Unable to modify Wi-Fi trust: \(error)") + } + } + } +} diff --git a/Passepartout/App/Shared/Intents/IntentDispatcher.swift b/Passepartout/App/Shared/Intents/IntentDispatcher.swift new file mode 100644 index 00000000..52101a0d --- /dev/null +++ b/Passepartout/App/Shared/Intents/IntentDispatcher.swift @@ -0,0 +1,159 @@ +// +// IntentDispatcher.swift +// Passepartout +// +// Created by Davide De Rosa on 3/8/19. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Intents +import PassepartoutCore + +class IntentDispatcher { + private struct Groups { + static let vpn = "VPN" + + static let trust = "Trust" + } + + // MARK: Intents + + static func intentConnect(header: Profile.Header) -> ConnectVPNIntent { + let intent = ConnectVPNIntent() + intent.profileId = header.id.uuidString + intent.profileName = header.name + return intent + } + + static func intentMoveTo(header: Profile.Header, providerFullName: String, server: ProviderServer) -> MoveToLocationIntent { + let intent = MoveToLocationIntent() + intent.profileId = header.id.uuidString + intent.providerFullName = providerFullName + intent.serverId = server.id + intent.serverName = server.localizedDescription + return intent + } + + static func intentEnable() -> EnableVPNIntent { + return EnableVPNIntent() + } + + static func intentDisable() -> DisableVPNIntent { + return DisableVPNIntent() + } + + static func intentTrustWiFi() -> TrustCurrentNetworkIntent { + return TrustCurrentNetworkIntent() + } + + static func intentUntrustWiFi() -> UntrustCurrentNetworkIntent { + return UntrustCurrentNetworkIntent() + } + + static func intentTrustCellular() -> TrustCellularNetworkIntent { + return TrustCellularNetworkIntent() + } + + static func intentUntrustCellular() -> UntrustCellularNetworkIntent { + return UntrustCellularNetworkIntent() + } + + // MARK: Donations + + @MainActor + static func donateConnection(with profile: Profile, providerManager: ProviderManager) { + let genericIntent: INIntent + if let providerName = profile.header.providerName { + guard let provider = providerManager.provider(withName: providerName) else { + pp_log.warning("Intent provider not found") + return + } + guard let server = profile.providerServer(providerManager) else { + pp_log.warning("Intent server not found") + return + } + genericIntent = intentMoveTo(header: profile.header, providerFullName: provider.fullName, server: server) + } else { + genericIntent = intentConnect(header: profile.header) + } + + let interaction = INInteraction(intent: genericIntent, response: nil) + interaction.groupIdentifier = profile.id.uuidString + interaction.donateAndLog() + } + + static func donateEnableVPN() { + let interaction = INInteraction(intent: intentEnable(), response: nil) + interaction.groupIdentifier = Groups.vpn + interaction.donateAndLog() + } + + static func donateDisableVPN() { + let interaction = INInteraction(intent: intentDisable(), response: nil) + interaction.groupIdentifier = Groups.vpn + interaction.donateAndLog() + } + + static func donateTrustCurrentNetwork() { + let interaction = INInteraction(intent: intentTrustWiFi(), response: nil) + interaction.groupIdentifier = Groups.trust + interaction.donateAndLog() + } + + static func donateUntrustCurrentNetwork() { + let interaction = INInteraction(intent: intentUntrustWiFi(), response: nil) + interaction.groupIdentifier = Groups.trust + interaction.donateAndLog() + } + + static func donateTrustCellularNetwork() { + let interaction = INInteraction(intent: intentTrustCellular(), response: nil) + interaction.groupIdentifier = Groups.trust + interaction.donateAndLog() + } + + static func donateUntrustCellularNetwork() { + let interaction = INInteraction(intent: intentUntrustCellular(), response: nil) + interaction.groupIdentifier = Groups.trust + interaction.donateAndLog() + } + + static func forgetProfile(withHeader header: Profile.Header) { + INInteraction.delete(with: header.id.uuidString) { (error) in + if let error = error { + pp_log.warning("Unable to forget interactions: \(error)") + return + } + pp_log.debug("Removed profile \(header.name) interactions") + } + } +} + +private extension INInteraction { + func donateAndLog() { + donate { (error) in + if let error = error { + pp_log.error("Unable to donate interaction: \(error)") + } + pp_log.debug("Donated \(self.intent)") + } + } +} diff --git a/Passepartout/App/Shared/Intents/IntentsManager.swift b/Passepartout/App/Shared/Intents/IntentsManager.swift new file mode 100644 index 00000000..f73fdf20 --- /dev/null +++ b/Passepartout/App/Shared/Intents/IntentsManager.swift @@ -0,0 +1,114 @@ +// +// IntentsManager.swift +// Passepartout +// +// Created by Davide De Rosa on 3/30/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Intents +#if canImport(IntentsUI) +import IntentsUI +#endif +import Combine + +@MainActor +class IntentsManager: NSObject, ObservableObject { + @Published private(set) var isReloadingShortcuts = false + + @Published private(set) var shortcuts: [UUID: Shortcut] = [:] + + let shouldDismissIntentView = PassthroughSubject() + + @MainActor + override init() { + super.init() + reloadShortcuts() + } + + func reloadShortcuts() { + isReloadingShortcuts = true + INVoiceShortcutCenter.shared.getAllVoiceShortcuts { vs, error in + if let error = error { + assertionFailure("Unable to fetch existing shortcuts: \(error)") + DispatchQueue.main.async { + self.isReloadingShortcuts = false + } + return + } + let shortcuts = (vs ?? []).reduce(into: [UUID: Shortcut]()) { + $0[$1.identifier] = Shortcut($1) + } + DispatchQueue.main.async { + self.shortcuts = shortcuts + self.isReloadingShortcuts = false + } + } + } +} + +@available(iOS 12, macOS 12, *) +extension IntentsManager: INUIAddVoiceShortcutViewControllerDelegate { + func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) { + guard let vs = voiceShortcut else { + shouldDismissIntentView.send() + return + } + shortcuts[vs.identifier] = Shortcut(vs) + shouldDismissIntentView.send() + } + + func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) { + shouldDismissIntentView.send() + } +} + +@available(iOS 12, macOS 12, *) +extension IntentsManager: INUIEditVoiceShortcutViewControllerDelegate { + func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) { + guard let vs = voiceShortcut else { + return + } + + shortcuts[vs.identifier] = Shortcut(vs) + shouldDismissIntentView.send() + + // XXX: iOS bug, vs.invocationPhrase here is still the old one before edit + // + // additionally, back from edit view controller does not trigger either onAppear or + // scenePhase .active FFS + // + // so damn it, reload manually after a delay + Task { + await Task.maybeWait(forMilliseconds: Constants.Delays.xxxReloadEditedShortcut) + reloadShortcuts() + } + } + + func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) { + shortcuts.removeValue(forKey: deletedVoiceShortcutIdentifier) + shouldDismissIntentView.send() + } + + func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) { + shouldDismissIntentView.send() + } +} diff --git a/Passepartout/App/iOS/de.lproj/Intents.strings b/Passepartout/App/Shared/Intents/de.lproj/Intents.strings similarity index 88% rename from Passepartout/App/iOS/de.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/de.lproj/Intents.strings index 3e07dfa8..ae672575 100644 --- a/Passepartout/App/iOS/de.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/de.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "VPN deaktivieren"; "1ZRTCZ" = "VPN deaktivieren"; -"66bZBE" = "Mit Anbieter ${providerId}"; +"66bZBE" = "Mit Anbieter ${providerFullName}"; "7eoAss" = "Entferne aktuelles WLAN von vertrauten Netzwerken"; @@ -18,9 +18,9 @@ "LA99yM" = "Verbinde mit VPN"; -"U6o81V" = "Verbinde mit ${profileTitle}"; +"U6o81V" = "Verbinde mit ${profileName}"; -"WnTPFg" = "Verbinde mit ${poolName}"; +"WnTPFg" = "Verbinde mit ${serverName}"; "eQ1yzr" = "Deaktiviert den VPN-Dienst"; diff --git a/Passepartout/App/iOS/el.lproj/Intents.strings b/Passepartout/App/Shared/Intents/el.lproj/Intents.strings similarity index 91% rename from Passepartout/App/iOS/el.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/el.lproj/Intents.strings index 2e57fa92..e67fbc67 100644 --- a/Passepartout/App/iOS/el.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/el.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "Απενεργοποίηση VPN"; "1ZRTCZ" = "Απενεργοποίηση VPN"; -"66bZBE" = "Με ${providerId} πάροχο"; +"66bZBE" = "Με ${providerFullName} πάροχο"; "7eoAss" = "Αφαίρεση συνδεδεμένου Wi-Fi από τα έμπιστα δίκτυα"; @@ -18,9 +18,9 @@ "LA99yM" = "Σύνδεση VPN"; -"U6o81V" = "Σύνδεση στο ${profileTitle}"; +"U6o81V" = "Σύνδεση στο ${profileName}"; -"WnTPFg" = "Σύνδεση σε ${poolName}"; +"WnTPFg" = "Σύνδεση σε ${serverName}"; "eQ1yzr" = "Απενεργοποίηση υπηρεσίας VPN"; diff --git a/Passepartout/App/iOS/en.lproj/Intents.strings b/Passepartout/App/Shared/Intents/en.lproj/Intents.strings similarity index 87% rename from Passepartout/App/iOS/en.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/en.lproj/Intents.strings index 8afbd82a..0a13a9cf 100644 --- a/Passepartout/App/iOS/en.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/en.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "Disable VPN"; "1ZRTCZ" = "Disable VPN"; -"66bZBE" = "With ${providerId} provider"; +"66bZBE" = "With ${providerFullName} provider"; "7eoAss" = "Removes current Wi-Fi from trusted networks"; @@ -18,9 +18,9 @@ "LA99yM" = "Connect to VPN"; -"U6o81V" = "Connect to ${profileTitle}"; +"U6o81V" = "Connect to ${profileName}"; -"WnTPFg" = "Connect to ${poolName}"; +"WnTPFg" = "Connect to ${serverName}"; "eQ1yzr" = "Disables the VPN service"; diff --git a/Passepartout/App/iOS/es.lproj/Intents.strings b/Passepartout/App/Shared/Intents/es.lproj/Intents.strings similarity index 88% rename from Passepartout/App/iOS/es.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/es.lproj/Intents.strings index 95b880e8..e38bd40e 100644 --- a/Passepartout/App/iOS/es.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/es.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "Deshabilitar VPN"; "1ZRTCZ" = "Deshabilitar VPN"; -"66bZBE" = "Con el proveedor ${providerId}"; +"66bZBE" = "Con el proveedor ${providerFullName}"; "7eoAss" = "Borra el Wi-Fi en uso de las redes de confianza"; @@ -18,9 +18,9 @@ "LA99yM" = "Conectar con el VPN"; -"U6o81V" = "Conectar con ${profileTitle}"; +"U6o81V" = "Conectar con ${profileName}"; -"WnTPFg" = "Conectar con ${poolName}"; +"WnTPFg" = "Conectar con ${serverName}"; "eQ1yzr" = "Deshabilita el servicio VPN"; diff --git a/Passepartout/App/iOS/fr.lproj/Intents.strings b/Passepartout/App/Shared/Intents/fr.lproj/Intents.strings similarity index 90% rename from Passepartout/App/iOS/fr.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/fr.lproj/Intents.strings index db08e72e..8e5af442 100755 --- a/Passepartout/App/iOS/fr.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/fr.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "Désactive VPN"; "1ZRTCZ" = "Désactive VPN"; -"66bZBE" = "Avec ${providerId} fournisseur"; +"66bZBE" = "Avec ${providerFullName} fournisseur"; "7eoAss" = "Supprime le présent réseaux Wi-Fi des réseaux de confiance "; @@ -18,9 +18,9 @@ "LA99yM" = "Se connecter au VPN"; -"U6o81V" = "Se connecter à ${profileTitle}"; +"U6o81V" = "Se connecter à ${profileName}"; -"WnTPFg" = "Se connecter à ${poolName}"; +"WnTPFg" = "Se connecter à ${serverName}"; "eQ1yzr" = "Désactives le service VPN"; diff --git a/Passepartout/App/iOS/it.lproj/Intents.strings b/Passepartout/App/Shared/Intents/it.lproj/Intents.strings similarity index 88% rename from Passepartout/App/iOS/it.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/it.lproj/Intents.strings index 2aeadfdb..4748e6ef 100644 --- a/Passepartout/App/iOS/it.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/it.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "Disabilita VPN"; "1ZRTCZ" = "Disabilita VPN"; -"66bZBE" = "Con il provider ${providerId}"; +"66bZBE" = "Con il provider ${providerFullName}"; "7eoAss" = "Rimuove la Wi-Fi corrente dalle reti sicure"; @@ -18,9 +18,9 @@ "LA99yM" = "Connetti alla VPN"; -"U6o81V" = "Connettiti a ${profileTitle}"; +"U6o81V" = "Connettiti a ${profileName}"; -"WnTPFg" = "Connettiti in ${poolName}"; +"WnTPFg" = "Connettiti in ${serverName}"; "eQ1yzr" = "Disabilita il servizio VPN"; diff --git a/Passepartout/App/iOS/nl.lproj/Intents.strings b/Passepartout/App/Shared/Intents/nl.lproj/Intents.strings similarity index 89% rename from Passepartout/App/iOS/nl.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/nl.lproj/Intents.strings index f055e0f9..174b79a3 100644 --- a/Passepartout/App/iOS/nl.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/nl.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "Disable VPN"; "1ZRTCZ" = "Disable VPN"; -"66bZBE" = "Met ${providerId} aanbieder"; +"66bZBE" = "Met ${providerFullName} aanbieder"; "7eoAss" = "Verwijder huidige Wi-Fi van vertrouwde netwerken"; @@ -18,9 +18,9 @@ "LA99yM" = "Verbind VPN"; -"U6o81V" = "Verbind met ${profileTitle}"; +"U6o81V" = "Verbind met ${profileName}"; -"WnTPFg" = "Verbind met ${poolName}"; +"WnTPFg" = "Verbind met ${serverName}"; "eQ1yzr" = "Schakel VPN service uit"; diff --git a/Passepartout/App/iOS/pl.lproj/Intents.strings b/Passepartout/App/Shared/Intents/pl.lproj/Intents.strings similarity index 89% rename from Passepartout/App/iOS/pl.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/pl.lproj/Intents.strings index a364221a..ddb5aae7 100644 --- a/Passepartout/App/iOS/pl.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/pl.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "Wyłącz VPN"; "1ZRTCZ" = "Wyłącz VPN"; -"66bZBE" = "Z usługodawcą ${providerId}"; +"66bZBE" = "Z usługodawcą ${providerFullName}"; "7eoAss" = "Usuwa obecnie połączoną sieć Wi-Fi z zaufanych sieci"; @@ -18,9 +18,9 @@ "LA99yM" = "Połącz z VPN"; -"U6o81V" = "Połącz z ${profileTitle}"; +"U6o81V" = "Połącz z ${profileName}"; -"WnTPFg" = "Połącz z ${poolName}"; +"WnTPFg" = "Połącz z ${serverName}"; "eQ1yzr" = "Wyłącza VPN"; diff --git a/Passepartout/App/iOS/pt.lproj/Intents.strings b/Passepartout/App/Shared/Intents/pt.lproj/Intents.strings similarity index 88% rename from Passepartout/App/iOS/pt.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/pt.lproj/Intents.strings index c8375784..a8ae762e 100644 --- a/Passepartout/App/iOS/pt.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/pt.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "Desativar VPN"; "1ZRTCZ" = "Desativar VPN"; -"66bZBE" = "Com o provedor ${providerId}"; +"66bZBE" = "Com o provedor ${providerFullName}"; "7eoAss" = "Remover Wi-Fi atual de conexões seguras"; @@ -18,9 +18,9 @@ "LA99yM" = "Conectar VPN"; -"U6o81V" = "Conectar ${profileTitle}"; +"U6o81V" = "Conectar ${profileName}"; -"WnTPFg" = "Conectar ${poolName}"; +"WnTPFg" = "Conectar ${serverName}"; "eQ1yzr" = "Desabilitar serviço de VPN"; diff --git a/Passepartout/App/iOS/ru.lproj/Intents.strings b/Passepartout/App/Shared/Intents/ru.lproj/Intents.strings similarity index 90% rename from Passepartout/App/iOS/ru.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/ru.lproj/Intents.strings index 9b860a2c..6979c8ed 100644 --- a/Passepartout/App/iOS/ru.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/ru.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "Отключить VPN"; "1ZRTCZ" = "Отключить VPN"; -"66bZBE" = "С ${providerId} провайдером"; +"66bZBE" = "С ${providerFullName} провайдером"; "7eoAss" = "Удаляет текущий Wi-Fi из доверенных подключений"; @@ -18,9 +18,9 @@ "LA99yM" = "Подключиться к VPN"; -"U6o81V" = "Подключиться к ${profileTitle}"; +"U6o81V" = "Подключиться к ${profileName}"; -"WnTPFg" = "Подключиться к ${poolName}"; +"WnTPFg" = "Подключиться к ${serverName}"; "eQ1yzr" = "Отключить этот VPN сервис"; diff --git a/Passepartout/App/iOS/sv.lproj/Intents.strings b/Passepartout/App/Shared/Intents/sv.lproj/Intents.strings similarity index 88% rename from Passepartout/App/iOS/sv.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/sv.lproj/Intents.strings index 36da334b..9feaa326 100644 --- a/Passepartout/App/iOS/sv.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/sv.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "Avstäng VPN"; "1ZRTCZ" = "Avstäng VPN"; -"66bZBE" = "Med ${providerId} leverantör"; +"66bZBE" = "Med ${providerFullName} leverantör"; "7eoAss" = "Tar bort nuvarande Wi-Fi från betrodda nätverk"; @@ -18,9 +18,9 @@ "LA99yM" = "Koppla till VPN"; -"U6o81V" = "Koppla till ${profileTitle}"; +"U6o81V" = "Koppla till ${profileName}"; -"WnTPFg" = "Koppla till ${poolName}"; +"WnTPFg" = "Koppla till ${serverName}"; "eQ1yzr" = "Avstäng VPN service"; diff --git a/Passepartout/App/iOS/zh-Hans.lproj/Intents.strings b/Passepartout/App/Shared/Intents/zh-Hans.lproj/Intents.strings similarity index 90% rename from Passepartout/App/iOS/zh-Hans.lproj/Intents.strings rename to Passepartout/App/Shared/Intents/zh-Hans.lproj/Intents.strings index 49285ef3..be6068f3 100644 --- a/Passepartout/App/iOS/zh-Hans.lproj/Intents.strings +++ b/Passepartout/App/Shared/Intents/zh-Hans.lproj/Intents.strings @@ -3,7 +3,7 @@ "IeGsEq" = "禁用VPN"; "1ZRTCZ" = "禁用VPN"; -"66bZBE" = "使用 ${providerId} 提供商配置"; +"66bZBE" = "使用 ${providerFullName} 提供商配置"; "7eoAss" = "从可信网络中移除当前Wi-Fi"; @@ -20,7 +20,7 @@ "U6o81V" = "连接到${profileId}"; -"WnTPFg" = "连接到${poolName}"; +"WnTPFg" = "连接到${serverName}"; "eQ1yzr" = "禁用VPN服务"; diff --git a/Passepartout/App/Shared/L10n/Core+L10n.swift b/Passepartout/App/Shared/L10n/Core+L10n.swift new file mode 100644 index 00000000..48c6ade9 --- /dev/null +++ b/Passepartout/App/Shared/L10n/Core+L10n.swift @@ -0,0 +1,106 @@ +// +// Core+L10n.swift +// Passepartout +// +// Created by Davide De Rosa on 2/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore + +extension PassepartoutError { + var localizedAppDescription: String? { + let V = L10n.Global.Errors.self + switch self { + case .missingProfile: + return V.missingProfile + + case .missingAccount: + return V.missingAccount + + case .missingProviderServer: + return V.missingProviderServer + + case .missingProviderPreset: + return V.missingProviderPreset + + default: + return nil + } + } +} + +extension VPNManager.ObservableState { + func localizedStatusDescription(withErrors: Bool, withDataCount: Bool) -> String { + guard isEnabled else { + + // report application errors even if VPN is disabled + if withErrors { + if let errorDescription = (lastError as? PassepartoutError)?.localizedAppDescription, !errorDescription.isEmpty { + return errorDescription + } + } + + return L10n.Tunnelkit.Vpn.disabled + } + if withErrors { + if let errorDescription = lastError?.localizedVPNDescription, !errorDescription.isEmpty { + return errorDescription + } + } + if withDataCount { + if vpnStatus == .connected, let dataCount = dataCount { + return dataCount.localizedDescription + } + } + return vpnStatus.localizedDescription + } +} + +extension Profile.Header: Comparable { + public static func <(lhs: Self, rhs: Self) -> Bool { + return lhs.name.lowercased() < rhs.name.lowercased() + } +} + +extension Profile.OpenVPNSettings { + var endpointDescription: String? { + return customEndpoint?.address ?? configuration.remotes?.first?.address + } +} + +extension Profile.WireGuardSettings { + var endpointDescription: String? { + return configuration.tunnelConfiguration.peers.first?.endpoint?.stringRepresentation + } +} + +extension Network.Choice { + var localizedDescription: String { + switch self { + case .automatic: + return L10n.Global.Strings.automatic + + case .manual: + return L10n.Global.Strings.manual + } + } +} diff --git a/Passepartout/App/Shared/L10n/OpenVPN+L10n.swift b/Passepartout/App/Shared/L10n/OpenVPN+L10n.swift new file mode 100644 index 00000000..386c5993 --- /dev/null +++ b/Passepartout/App/Shared/L10n/OpenVPN+L10n.swift @@ -0,0 +1,122 @@ +// +// OpenVPN+L10n.swift +// Passepartout +// +// Created by Davide De Rosa on 2/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitOpenVPN + +extension OpenVPN.Cipher { + var localizedDescription: String { + return description + } +} + +extension OpenVPN.Digest { + var localizedDescription: String { + return description + } +} + +extension UInt8 { + var localizedDescriptionAsXOR: String { + let V = L10n.Global.Strings.self + guard self != 0 else { + return V.disabled + } + return String(format: "0x%02x", UInt8(self)) + } +} + +extension OpenVPN.CompressionFraming { + var localizedDescription: String { + switch self { + case .disabled: + return L10n.Global.Strings.disabled + + case .compLZO: + return Unlocalized.OpenVPN.compLZO + + case .compress, .compressV2: + return Unlocalized.OpenVPN.compress + } + } +} + +extension OpenVPN.CompressionAlgorithm { + var localizedDescription: String { + let V = L10n.Endpoint.Advanced.Openvpn.Items.self + switch self { + case .disabled: + return L10n.Global.Strings.disabled + + case .LZO: + return Unlocalized.OpenVPN.lzo + + case .other: + return V.CompressionAlgorithm.Value.other + } + } +} + +extension Optional where Wrapped == OpenVPN.TLSWrap { + var localizedDescription: String { + let V = L10n.Endpoint.Advanced.Openvpn.Items.self + if let strategy = self?.strategy { + switch strategy { + case .auth: + return V.TlsWrapping.Value.auth + + case .crypt: + return V.TlsWrapping.Value.crypt + } + } else { + return L10n.Global.Strings.disabled + } + } +} + +extension Optional where Wrapped == Bool { + var localizedDescriptionAsEKU: String { + let V = L10n.Global.Strings.self + return (self ?? false) ? V.enabled : V.disabled + } +} + +extension TimeInterval { + var localizedDescriptionAsRenegotiatesAfter: String { + let V = L10n.Endpoint.Advanced.Openvpn.Items.self + if self > 0 { + return V.RenegotiationSeconds.Value.after(TimeInterval(self).localizedDescription) + } else { + return L10n.Global.Strings.disabled + } + } +} + +extension Bool { + var localizedDescriptionAsRandomizeEndpoint: String { + let V = L10n.Global.Strings.self + return self ? V.enabled : V.disabled + } +} diff --git a/Passepartout/App/Shared/L10n/Providers+L10n.swift b/Passepartout/App/Shared/L10n/Providers+L10n.swift new file mode 100644 index 00000000..0991e484 --- /dev/null +++ b/Passepartout/App/Shared/L10n/Providers+L10n.swift @@ -0,0 +1,98 @@ +// +// Providers+L10n.swift +// Passepartout +// +// Created by Davide De Rosa on 2/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore + +extension ProviderManager { +// func localizedLocation(forProfile profile: Profile) -> String? { +// return profile.providerServer(self)?.localizedDescription +// } + + func localizedPreset(forProfile profile: Profile) -> String? { + guard let server = profile.providerServer(self) else { + return nil + } + return profile.providerPreset(server)?.localizedDescription + } + + func localizedInfrastructureUpdate(forProfile profile: Profile) -> String? { + guard let providerName = profile.header.providerName else { + return nil + } + return lastUpdate(providerName, vpnProtocol: profile.currentVPNProtocol)?.timestamp + } +} + +extension ProviderMetadata { + var localizedGuidanceString: String? { + let prefix = "account.sections.guidance.footer.infrastructure" + let key = "\(prefix).\(name)" + var format = NSLocalizedString(key, bundle: .main, comment: "") + + // i.e. key not found + if format == key { + let purpose = name.credentialsPurpose + let defaultKey = "\(prefix).default.\(purpose)" + format = NSLocalizedString(defaultKey, bundle: .main, comment: "") + } + + return String(format: format, locale: .current, description) + } +} + +extension ProviderLocation { + var localizedCountry: String { + return countryCode.localizedAsCountryCode + } +} + +extension ProviderServer { + var localizedCountry: String { + return countryCode.localizedAsCountryCode + } + + var localizedDescription: String { + var comps: [String] = [localizedCountry] + details.map { + comps.append($0) + } + return comps.joined(separator: " - ") + } + + var localizedDetails: String { + return details ?? "" + } + + var localizedDetailsWithDefault: String { + return details ?? L10n.Global.Strings.default + } +} + +extension ProviderServer.Preset { + var localizedDescription: String { + return name + } +} diff --git a/Passepartout/App/Shared/L10n/TunnelKit+L10n.swift b/Passepartout/App/Shared/L10n/TunnelKit+L10n.swift new file mode 100644 index 00000000..d85fcebd --- /dev/null +++ b/Passepartout/App/Shared/L10n/TunnelKit+L10n.swift @@ -0,0 +1,238 @@ +// +// TunnelKit+L10n.swift +// Passepartout +// +// Created by Davide De Rosa on 3/12/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitManager +import TunnelKitOpenVPN +import TunnelKitWireGuard +import NetworkExtension +import PassepartoutUtils + +extension VPNStatus { + var localizedDescription: String { + switch self { + case .connecting: + return L10n.Tunnelkit.Vpn.connecting + + case .connected: + return L10n.Tunnelkit.Vpn.active + + case .disconnecting: + return L10n.Tunnelkit.Vpn.disconnecting + + case .disconnected: + return L10n.Tunnelkit.Vpn.inactive + } + } +} + +extension DNSProtocol { + var localizedDescription: String { + switch self { + case .plain: + return Unlocalized.DNS.plain + + case .https: + return Unlocalized.Network.https + + case .tls: + return Unlocalized.Network.tls + } + } +} + +extension DataCount { + var localizedDescription: String { + let down = received.descriptionAsDataUnit + let up = sent.descriptionAsDataUnit + return "↓\(down) / ↑\(up)" + } +} + +extension Int { + var localizedDescriptionAsMTU: String { + guard self != 0 else { + return L10n.Global.Strings.default + } + return description + } +} + +extension TimeInterval { + var localizedDescriptionAsKeepAlive: String { + let V = L10n.Endpoint.Advanced.Openvpn.Items.self + if self > 0 { + return V.KeepAlive.Value.seconds(Int(self)) + } else { + return L10n.Global.Strings.disabled + } + } +} + +extension Optional where Wrapped == IPv4Settings { + var localizedAddress: String { + if let ipv4 = self { + return "\(ipv4.address)/\(ipv4.addressMask)" + } else { + return L10n.Global.Strings.none + } + } + + var localizedDefaultGateway: String { + return self?.defaultGateway ?? L10n.Global.Strings.none + } +} + +extension Optional where Wrapped == IPv6Settings { + var localizedAddress: String { + if let ipv6 = self { + return "\(ipv6.address)/\(ipv6.addressPrefixLength)" + } else { + return L10n.Global.Strings.none + } + } + + var localizedDefaultGateway: String { + return self?.defaultGateway ?? L10n.Global.Strings.none + } +} + +extension IPv4Settings.Route { + var localizedDescription: String { + return "\(destination)/\(mask) -> \(gateway)" + } +} + +extension IPv6Settings.Route { + var localizedDescription: String { + return "\(destination)/\(prefixLength) -> \(gateway)" + } +} + +extension Error { + var localizedVPNDescription: String? { + if let ovpnError = self as? OpenVPNProviderError { + return ovpnErrorDescription(ovpnError) + } + if let wgError = self as? WireGuardProviderError { + return wgErrorDescription(wgError) + } + if let neError = self as? NEVPNError { + return neErrorDescription(neError) + } + return localizedDescription + } + + private func ovpnErrorDescription(_ error: OpenVPNProviderError) -> String? { + let V = L10n.Tunnelkit.Errors.Vpn.self + switch error { + case .socketActivity, .timeout: + return V.timeout + + case .dnsFailure: + return V.dns + + case .tlsInitialization, .tlsServerVerification, .tlsHandshake: + return V.tls + + case .authentication: + return V.auth + + case .encryptionInitialization, .encryptionData: + return V.encryption + + case .serverCompression, .lzo: + return V.compression + + case .networkChanged: + return V.network + + case .routing: + return V.routing + + case .gatewayUnattainable: + return V.gateway + + case .serverShutdown: + return V.shutdown + + default: + return nil + } + } + + private func wgErrorDescription(_ error: WireGuardProviderError) -> String? { + let V = L10n.Tunnelkit.Errors.Vpn.self + switch error { + case .dnsResolutionFailure: + return V.dns + + default: + return nil + } + } + + private func neErrorDescription(_ error: NEVPNError) -> String? { + return error.localizedDescription.capitalized + } +} + +extension Error { + var localizedVPNParsingDescription: String? { + if let ovpnError = self as? OpenVPN.ConfigurationError { + return ovpnErrorDescription(ovpnError) + } + pp_log.error("Could not parse configuration URL: \(localizedDescription)") + return L10n.Tunnelkit.Errors.parsing(localizedDescription) + } + + private func ovpnErrorDescription(_ error: OpenVPN.ConfigurationError) -> String { + let V = L10n.Tunnelkit.Errors.Openvpn.self + switch error { + case .encryptionPassphrase: + pp_log.error("Could not parse configuration URL: unable to decrypt, \(error.localizedDescription)") + return V.passphraseRequired + + case .unableToDecrypt(let error): + pp_log.error("Could not parse configuration URL: unable to decrypt, \(error.localizedDescription)") + return V.decryption + + case .malformed(let option): + pp_log.error("Could not parse configuration URL: malformed option, \(option)") + return V.malformed(option) + + case .missingConfiguration(let option): + pp_log.error("Could not parse configuration URL: missing configuration, \(option)") + return V.requiredOption(option) + + case .unsupportedConfiguration(var option): + if option.contains("external") { + option.append(" (see FAQ)") + } + pp_log.error("Could not parse configuration URL: unsupported configuration, \(option)") + return V.unsupportedOption(option) + } + } +} diff --git a/Passepartout/App/Shared/L10n/Unlocalized.swift b/Passepartout/App/Shared/L10n/Unlocalized.swift new file mode 100644 index 00000000..a18345fb --- /dev/null +++ b/Passepartout/App/Shared/L10n/Unlocalized.swift @@ -0,0 +1,252 @@ +// +// Unlocalized.swift +// Passepartout +// +// Created by Davide De Rosa on 2/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore + +enum Unlocalized { + static let appName = Constants.Global.appName + + enum Placeholders { + static let empty = "" + + static let address = "0.0.0.0" + + static let port = "8080" + + static let hostname = "example.com" + + static let dohURL = "https://example.com/dns-query" + + static let dotServerName = hostname + + static let dnsAddress = address + + static let dnsDomain = hostname + + static let pacURL = "https://proxy/auto-conf" + + static let proxyBypassDomain = hostname + } + + enum DNS { + static let plain = "Cleartext" + } + + enum Keychain { + static func passwordLabel(_ profileName: String, vpnProtocol: VPNProtocolType) -> String { + return "\(Constants.Global.appName): \(profileName) (\(vpnProtocol.description))" + } + } + + enum Issues { + static let recipient = "issues@\(Constants.Domain.name)" + + static let subject = "\(appName) - Report issue" + + static func body(_ description: String, _ metadata: String) -> String { + return "Hi,\n\n\(description)\n\n\(metadata)\n\nRegards" + } + + static let template = "description of the issue: " + + static let maxLogBytes = UInt64(20000) + + enum Filenames { + static var debugLog: String { + let fmt = DateFormatter() + fmt.dateFormat = "yyyyMMdd-HHmmss" + let iso = fmt.string(from: Date()) + return "debug-\(iso).txt" + } + + static let configuration = "profile.ovpn" +// static let configuration = "profile.ovpn.txt" + + static let template = "description of the issue: " + } + + enum MIME { + static let debugLog = "text/plain" + +// static let configuration = "application/x-openvpn-profile" + static let configuration = "text/plain" + } + } + + enum Social { + static let reddit = "Reddit" + + private static let twitterHashtags = ["OpenVPN", "iOS", "macOS"] + + static func twitterIntent(withMessage message: String) -> URL { + var text = message + for ht in twitterHashtags { + text = text.replacingOccurrences(of: ht, with: "#\(ht)") + } + var comps = URLComponents(string: "https://twitter.com/intent/tweet")! + comps.queryItems = [ + URLQueryItem(name: "url", value: Constants.URLs.website.absoluteString), + URLQueryItem(name: "via", value: "keeshux"), + URLQueryItem(name: "text", value: text) + ] + return comps.url! + } + } + + enum Translations { + enum Email { + static let recipient = "translate@\(Constants.Domain.name)" + + static let subject = "\(appName) - Translations" + + static func body(_ description: String) -> String { + return "Hi,\n\n\(description)\n\nRegards" + } + + static let template = "I offer to translate to: " + } + + static let translators: [String: String] = [ + "de": "Christian Lederer, Theodor Tietze", + "el": "Konstantinos Koukoulakis", + "en-US": "Davide De Rosa", + "es": "Davide De Rosa, Elena Vivó", + "fr-FR": "Julien Laniel", + "it": "Davide De Rosa", + "nl": "Norbert de Vreede", + "pl": "Piotr Książek", + "pt-BR": "Helder Santana", + "ru": "Alexander Korobynikov", + "sv": "Henry Gross-Hellsen", + "zh-Hans": "OnlyThen" + ] + } + + enum Credits { + typealias License = (String, String, URL) + + typealias Notice = (String, String) + + static let author = "Davide De Rosa" + + static let licenses: [License] = [( + "Kvitto", + "BSD", + URL(string: "https://raw.githubusercontent.com/Cocoanetics/Kvitto/develop/LICENSE")! + ), ( + "lzo", + "GPLv2", + URL(string: "https://www.gnu.org/licenses/gpl-2.0.txt")! + ), ( + "MBProgressHUD", + "MIT", + URL(string: "https://raw.githubusercontent.com/jdg/MBProgressHUD/master/LICENSE")! + ), ( + "OpenSSL", + "OpenSSL", + URL(string: "https://raw.githubusercontent.com/openssl/openssl/master/LICENSE.txt")! + ), ( + "PIATunnel", + "MIT", + URL(string: "https://raw.githubusercontent.com/pia-foss/tunnel-apple/master/LICENSE")! + ), ( + "SSZipArchive", + "MIT", + URL(string: "https://raw.githubusercontent.com/samsoffes/ssziparchive/master/LICENSE")! + ), ( + "SwiftGen", + "MIT", + URL(string: "https://raw.githubusercontent.com/SwiftGen/SwiftGen/master/LICENCE")! + ), ( + "SwiftyBeaver", + "MIT", + URL(string: "https://raw.githubusercontent.com/SwiftyBeaver/SwiftyBeaver/master/LICENSE")! + )] + + static let notices: [Notice] = [( + "Circle Icons", + "The logo is taken from the awesome Circle Icons set by Nick Roach." + ), ( + "Country flags", + "The country flags are taken from: https://github.com/lipis/flag-icon-css/" + ), ( + "OpenVPN", + "© 2002-2018 OpenVPN Inc. - OpenVPN is a registered trademark of OpenVPN Inc." + )] + } + + enum About { + static let github = "GitHub" + + static let readme = "README" + + static let changelog = "CHANGELOG" + + static let faq = "FAQ" + + static let alternativeTo = "AlternativeTo" + } + + enum VPN { + static let vpn = "VPN" + + static let certificateAuthority = "CA" + + static let xor = "XOR" + } + + enum OpenVPN { + static let compLZO = "--comp-lzo" + + static let compress = "--compress" + + static let lzo = "LZO" + } + + enum Network { + static let dns = "DNS" + + static let tls = "TLS" + + static let https = "HTTPS" + + static let url = "URL" + + static let mtu = "MTU" + + static let ipv4 = "IPv4" + + static let ipv6 = "IPv6" + + static let ssid = "SSID" + + static let proxyAutoConfiguration = "PAC" + } + + enum Other { + static let siri = "Siri" + } +} diff --git a/Passepartout/App/Shared/L10n/WireGuard+L10n.swift b/Passepartout/App/Shared/L10n/WireGuard+L10n.swift new file mode 100644 index 00000000..042e348c --- /dev/null +++ b/Passepartout/App/Shared/L10n/WireGuard+L10n.swift @@ -0,0 +1,27 @@ +// +// WireGuard+L10n.swift +// Passepartout +// +// Created by Davide De Rosa on 3/12/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitWireGuardCore diff --git a/Passepartout/App/Shared/L10n/de.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/de.lproj/Localizable.strings new file mode 100644 index 00000000..e4127597 --- /dev/null +++ b/Passepartout/App/Shared/L10n/de.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Abbrechen"; +"global.strings.next" = "Weiter"; +/* MARK: Global */ +"global.strings.ok" = "OK"; +"global.strings.save" = "Speichern"; +"global.strings.rename" = "Umbenennen"; +"global.strings.add" = "Hinzufügen"; +"global.strings.default" = "Default"; +"global.strings.name" = "Name"; +"global.strings.address" = "Adresse"; +"global.strings.addresses" = "Adressen"; +"global.strings.port" = "Port"; +"global.strings.protocol" = "Protokoll"; +"global.strings.protocols" = "Protokolle"; +"global.strings.enabled" = "Aktiviert"; +"global.strings.disabled" = "Deaktiviert"; +"global.strings.none" = "Keine"; +"global.strings.automatic" = "Automatisch"; +"global.strings.manual" = "Manuell"; +"global.strings.encryption" = "Verschlüsselung"; +"global.strings.reconnect" = "Erneut verbinden"; +"global.strings.servers" = "Server"; +"global.strings.domain" = "Domäne"; +"global.strings.domains" = "Domänen"; +"global.strings.proxy" = "Proxy"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "Interface"; +"global.strings.private_key" = "Privater Key"; +"global.strings.public_key" = "Öffentlicher Key"; +"global.strings.endpoint" = "Endpunkt"; +"global.strings.keepalive" = "Keep-alive"; +"global.strings.advanced" = "Erweitert"; +"global.strings.translations" = "Übersetzungen"; + +"global.messages.email_not_configured" = "Es wurde kein Email-Account konfiguriert."; +"global.messages.share" = "Passepartout ist ein Benutzerfreundlicher, Open Source OpenVPN client für iOS und macOS"; + +"global.placeholders.profile_name" = "Mein Profil"; + +"global.errors.missing_profile" = "Fehlendes Profil"; +"global.errors.missing_account" = "Fehlender Account"; +"global.errors.missing_provider_server" = "Fehlender Server"; +"global.errors.missing_provider_preset" = "Fehlende Voreinstellung"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Verbinde"; +"tunnelkit.vpn.active" = "Aktiv"; +"tunnelkit.vpn.disconnecting" = "Trenne"; +"tunnelkit.vpn.inactive" = "Inaktiv"; +"tunnelkit.vpn.disabled" = "Deaktiviert"; +"tunnelkit.vpn.unused" = "Aus"; + +"tunnelkit.errors.vpn.timeout" = "Timeout"; +"tunnelkit.errors.vpn.dns" = "DNS fehlgeschlagen"; +"tunnelkit.errors.vpn.auth" = "Authentifizierung fehlgeschlagen"; +"tunnelkit.errors.vpn.tls" = "TLS fehlgeschlagen"; +"tunnelkit.errors.vpn.encryption" = "Verschlüsselung fehlgeschlagen"; +"tunnelkit.errors.vpn.compression" = "Komprimierung nicht unterstützt"; +"tunnelkit.errors.vpn.network" = "Netzwerk geändert"; +"tunnelkit.errors.vpn.routing" = "Kein Routing"; +"tunnelkit.errors.vpn.gateway" = "Kein Gateway"; +"tunnelkit.errors.vpn.shutdown" = "Server heruntergefahren"; + +"tunnelkit.errors.parsing" = "Fehler beim Verarbeiten der Konfigurationsdatei (%@)."; +"tunnelkit.errors.openvpn.malformed" = "Die Konfigurations-Datei enthält eine ungültige Option (%@)."; +"tunnelkit.errors.openvpn.required_option" = "Die Konfigurations-Datei enthält eine benötigte Option nicht (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "Die Konfigurations-Datei enthält eine nicht unterstützte Option (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "Die Konfigurations-Datei ist korrekt, enthält aber möglicherweise eine nicht unterstützte Option (%@).\n\nDie Verbindung kann, abhängig von den Server-Einstellungen, unterbrochen werden."; +"tunnelkit.errors.openvpn.passphrase_required" = "Bitte die Verschlüsselungs-Passphrase eingeben."; +"tunnelkit.errors.openvpn.decryption" = "Die Konfiguration enthält einen verschlüsselten Private Key und konnte nicht entschlüsselt werden. Bitte überprüfe ob du die Passphrase eingegeben hast."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "Come watch me make Passepartout live on Twitch, join the chat to interact and contribute!"; +"organizer.sections.siri.footer" = "Erhalte Hilfe von Siri um deine üblichen Interaktionen mit der App zu beschleunigen."; +"organizer.sections.support.header" = "Support"; +"organizer.items.follow_twitch.caption" = "Passepartout auf Twitch ansehen"; +"organizer.items.profile.value.current" = "In Benutzung"; +"organizer.items.siri_shortcuts.caption" = "Kurzbefehle verwalten"; +"organizer.items.join_community.caption" = "Community beitreten"; +"organizer.items.write_review.caption" = "Rezension schreiben"; +"organizer.items.donate.caption" = "Spenden"; +"organizer.items.github_sponsors.caption" = "Unterstütze mich bei GitHub"; +"organizer.items.translate.caption" = "Übersetzung anbieten"; +"organizer.items.about.caption" = "Über %@"; +"organizer.items.uninstall.caption" = "VPN-Konfiguration entfernen"; +"organizer.items.add_provider.caption" = "Neuen Anbieter hinzufügen"; +"organizer.items.add_host.caption" = "Aus Dateien hinzufügen"; + +"organizer.alerts.reddit.message" = "Wusstest du, daß Passepartout einen Subreddit hat? Abonniere ihn für Updates oder um Features, Probleme, neue Plattformen zu diskutieren - oder was auch immer du möchtest.\n\nDies ist auch ein guter Weg zu zeigen dass dir dieses Projekt etwas bedeutet."; +"organizer.alerts.reddit.buttons.subscribe" = "Jetzt abbonnieren!"; +"organizer.alerts.reddit.buttons.remind" = "Später erinnern"; +"organizer.alerts.reddit.buttons.never" = "Nicht erneut fragen"; + +"organizer.alerts.uninstall_vpn.message" = "Möchtest du wirklich die VPN-Konfiguration aus deinen Geräte-Einstellungen löschen? Dies behebt möglicherweise manche kaputten VPN-Zustände und beeinflusst nicht deine Anbieter und Hosts-Profile."; +"organizer.alerts.remove_profile.title" = "Profil entfernen"; +"organizer.alerts.remove_profile.message" = "Bist du sicher, dass du das Profil %@ löschen möchtest?"; + +"organizer.menus.add_profile.imported" = "%@ hinzufügen"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "Neues Profil"; +"add_profile.shared.views.existing.header" = "Bestehende Profile"; +"add_profile.shared.alerts.overwrite.message" = "Ein Profil mit identischem Namen existiert bereits. Ersetzen?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Passphrase eingeben"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Anbieter"; +"add_profile.provider.sections.vpn.footer" = "Hier findest du einige Anbieter mit voreingestellten Konfigurationsprofilen."; +"add_profile.provider.items.update_list" = "Aktualisiere Liste"; +"add_profile.provider.errors.no_default_server" = "Keine Server gefunden."; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Anbieter"; + +"profile.welcome.message" = "Willkommen bei Passepartout!\n\nBenutze den Organizer um ein neues Profil hinzuzufügen."; + +"profile.sections.vpn.footer" = "Die Verbindung wird immer aufgebaut wenn notwendig."; +"profile.sections.status.header" = "Verbindung"; +"profile.sections.configuration.header" = "Konfiguration"; +"profile.sections.provider_infrastructure.footer" = "Zuletzt aktualisiert am %@."; +"profile.sections.vpn_survives_sleep.footer" = "Deaktivieren um die Batterielaufzeit zu verbessern, allerdings verzögert sich der Verbindungsaufbau beim Aufwachen."; +"profile.sections.vpn_resolves_hostname.footer" = "Bevorzugt in den meisten Netzwerken und benötigt in manchen IPv6 Netzwerken. Deaktivieren wo DNS geblockt ist oder um die Aushandlung zu beschleunigen bei langsam antwortenden DNS."; +"profile.sections.feedback.header" = "Feedback"; +"profile.items.use_profile.caption" = "Dieses Profil verwenden"; +"profile.items.vpn_service.caption" = "Aktiviert"; +"profile.items.vpn.turn_on.caption" = "Aktiviere VPN"; +"profile.items.vpn.turn_off.caption" = "Deaktiviere VPN"; +"profile.items.connection_status.caption" = "Status"; +"profile.items.data_count.caption" = "Ausgetauschte Datenmenge"; +"profile.items.provider.refresh.caption" = "Infrastruktur neu laden"; +"profile.items.category.caption" = "Kategorie"; +"profile.items.only_shows_favorites.caption" = "Nur favorisierte Standorte anzeigen"; +"profile.items.vpn_survives_sleep.caption" = "Verbindung aktiv halten trotz Schlafmodus"; +"profile.items.vpn_resolves_hostname.caption" = "Server Hostname auflösen"; +"profile.items.reconnect.caption" = "Erneut verbinden"; + +"profile.alerts.rename.title" = "Profil umbenennen"; +"profile.alerts.reconnect_vpn.message" = "Möchtest du erneut zum VPN verbinden?"; +"profile.alerts.test_connectivity.title" = "Konnektivität"; +"profile.alerts.test_connectivity.messages.success" = "Dein Gerät ist mit dem Internet verbunden!"; +"profile.alerts.test_connectivity.messages.failure" = "Dein Gerät hat keine Verbindung mit dem Internet, bitte prüfe deine Profil-Parameter."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Ort"; +"provider.location.sections.empty_favorites.footer" = "Wische nach Links um einen Standort zu den Favoriten hinzuzufügen oder zu entfernen."; +"provider.location.actions.favorite" = "Favorit hinzuzufügen"; +"provider.location.actions.unfavorite" = "Favorit entfernen"; + +"provider.preset.title" = "Voreinstellung"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Account"; +"account.sections.credentials.header" = "Zugangsdaten"; +"account.sections.registration.footer" = "Beantrage einen Account auf der %@ Webseite."; +"account.items.username.caption" = "Benutzername"; +"account.items.username.placeholder" = "Benutzername"; +"account.items.password.caption" = "Passwort"; +"account.items.password.placeholder" = "Geheim"; +"account.items.open_guide.caption" = "Siehe deine Zugangsdaten"; +"account.items.signup.caption" = "Registrieren bei %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Benutze deine %@ Web-Zugangsdaten."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Verwenden Sie Ihre %@ service-Anmeldeinformationen, die von den Website-Anmeldeinformationen abweichen können."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Benutze deine %@ Web-Zugangsdaten. Dein Benutzername ist üblicherweise numerischt (ohne Zwischenraum)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Benutze deine %@ Web-Zugangsdaten. Dein Benutzername ist üblicherweise deine Email."; +"account.sections.guidance.footer.infrastructure.pia" = "Benutze deine %@ Web-Zugangsdaten. Dein Benutzername ist üblicherweise numerischt mit einem \"p\" Präfix."; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Deine Zugangsdaten für %@ findest du unter \"Account > OpenVPN / IKEv2 Username\" auf der Webseite."; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Benutze deine %@ Web-Zugangsdaten. Dein Benutzername ist üblicherweise deine Email."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Benutze deine %@ Web-Zugangsdaten. Dein Benutzername ist üblicherweise deine Email."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Deine Zugangsdaten für %@ findest du im OpenVPN Config Generator auf der Webseite."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Peer"; +"endpoint.wireguard.items.preshared_key.caption" = "Vorinstallierter Key"; +"endpoint.wireguard.items.allowed_ip.caption" = "Zulässige IP"; + +"endpoint.advanced.title" = "Technische Details"; +"endpoint.advanced.openvpn.sections.communication.header" = "Kommunikation"; +"endpoint.advanced.openvpn.sections.reset.footer" = "Wenn du nach einer Änderung der Kommunikations-Parameter dich nicht mehr verbinden kannst, hier tippen um zur originalen Konfiguration zurückzukehren."; +"endpoint.advanced.openvpn.sections.compression.header" = "Komprimierung"; +"endpoint.advanced.openvpn.sections.network.header" = "Netzwerk"; +"endpoint.advanced.openvpn.sections.other.header" = "Andere"; +"endpoint.advanced.openvpn.items.route.caption" = "Route"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Chiffre"; +"endpoint.advanced.openvpn.items.digest.caption" = "Authentifizierung"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Eingebettet"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Framing"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Algorithmus"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "Nicht unterstützt"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Konfiguration zurücksetzen"; +"endpoint.advanced.openvpn.items.client.caption" = "Zertifikat"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Key"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Geprüft"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "Nicht geprüft"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Wrapping"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Authentifizierung"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Verschlüsselung"; +"endpoint.advanced.openvpn.items.eku.caption" = "Erweiterte Verifizierung"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d Sekunden"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "erneute Aushandlung"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "nach %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Endpunkt zufällig wählen"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Netzwerk-Einstellungen"; +"network_settings.sections.choices.header" = "Überschreiben"; +"network_settings.gateway.title" = "Standard-Gateway"; +"network_settings.proxy.items.bypass_domains.caption" = "Domänen umgehen"; +"network_settings.items.add_dns_server.caption" = "Adresse hinzufügen"; +"network_settings.items.add_dns_domain.caption" = "Domäne hinzufügen"; +"network_settings.items.proxy_bypass.caption" = "Domäne umgehen"; +"network_settings.items.add_proxy_bypass.caption" = "Zu umgehende Domäne hinzufügen"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Vertrauenswürdige Netzwerke"; +"on_demand.sections.policy.footer" = "Wenn ein vertrauenswürdiges Netzwerk verbunden wird, wird normalerweise die VPN-Verbindung beendet und bleibt deaktiviert. Deaktiviere diese Option um dieses Verhalten zu unterbinden."; +"on_demand.items.add_ssid.caption" = "WLAN hinzufügen"; +"on_demand.items.active.caption" = "Vertrauen"; +"on_demand.items.mobile.caption" = "Mobilfunknetz"; +"on_demand.items.ethernet.caption" = "Kabelverbindungen vertrauen"; +"on_demand.items.ethernet.description" = "Hier ein Häkchen setzen, um jeder Kabelverbindung zu vertrauen."; +"on_demand.items.policy.caption" = "Vertrauen deaktiviert VPN"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Diagnose"; +"diagnostics.sections.debug_log.footer" = "Zensier-Status wird aktiv nach erneutem Verbinden. Netzwerk-Daten sind Hostnamen, IP-Adressen, Routingtabellen, SSID. Zugangsdaten und Private Keys werden nie gelogged."; +"diagnostics.items.server_configuration.caption" = "Serverkonfiguration"; +"diagnostics.items.masks_private_data.caption" = "Netzwerkdaten zensieren"; +"diagnostics.items.report_issue.caption" = "Verbindungsproblem melden"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "Um das aktuelle Debug-Log sicher zurückzusetzen und die neuen Zensier-Paramenter anzuwenden, musst du das VPN jetzt erneut verbinden."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Debug log"; +"debug_log.buttons.copy" = "Kopieren"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Problem melden"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Füge Kurzbefehl hinzu"; +"shortcuts.add.sections.wifi.header" = "WLAN"; +"shortcuts.add.sections.cellular.header" = "Mobilfunknetz"; +"shortcuts.add.items.connect.caption" = "Verbinde mit"; +"shortcuts.add.items.enable_vpn.caption" = "Aktiviere VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Deaktiviere VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Vertraue aktivem WLAN"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Misstraue aktivem WLAN"; +"shortcuts.add.items.trust_cellular.caption" = "Vertraue Mobilfunknetz"; +"shortcuts.add.items.untrust_cellular.caption" = "Misstraue Mobilfunknetz"; +"shortcuts.add.alerts.no_profiles.message" = "Es gibt kein Profil mit dem eine Verbindung hergestellt werden kann."; + +"shortcuts.edit.title" = "Kurzbefehle bearbeiten"; +"shortcuts.edit.sections.all.header" = "Existierende Kurzbefehle"; +"shortcuts.edit.items.add_shortcut.caption" = "Kurzbefehl hinzufügen"; + +// MARK: PaywallView + +"paywall.title" = "Kaufen"; +"paywall.sections.products.footer" = "Jedes Produkt ist ein einmaliger Kauf. Der Kauf eines Providers beinhaltet kein VPN-Abonnement."; +"paywall.items.loading.caption" = "Produkte werden geladen"; +"paywall.items.full_version.extra_description" = "Alle Anbieter (inklusive Zukünftige)\n%@"; +"paywall.items.restore.title" = "Einkäufe wiederherstellen"; +"paywall.items.restore.description" = "Wenn Sie diese App oder Funktion in der Vergangenheit gekauft haben, können Sie Ihre Einkäufe wiederherstellen und dieser Bildschirm wird nicht mehr angezeigt."; + +// MARK: DonateView + +"donate.title" = "Spenden"; +"donate.sections.one_time.header" = "Einmalig"; +"donate.sections.one_time.footer" = "Wenn du dich erkenntlich zeigen möchtest für meine Arbeit, gibt es hier ein paar Beträge die du direkt spenden kannst.\n\nDu bezahlst pro Spende nur einmal und kannst mehrmals spenden wenn du möchtest."; +"donate.items.loading.caption" = "Lade Spenden"; +"donate.items.purchasing.caption" = "Führe Spende durch"; +"donate.alerts.purchase.success.title" = "Danke"; +"donate.alerts.purchase.success.message" = "Das bedeutet mir viel und ich hoffe wirklich dass du die App weiterhin benutzt und unterstützt."; +"donate.alerts.purchase.failure.message" = "Konnte Spende nicht durchführen. %@"; + +// MARK: AboutView + +"about.title" = "Über"; +"about.sections.web.header" = "Web"; +"about.sections.share.header" = "Teilen"; +"about.items.credits.caption" = "Credits"; +"about.items.website.caption" = "Homepage"; +"about.items.disclaimer.caption" = "Haftungsausschluss"; +"about.items.privacy_policy.caption" = "Datenschutzrichtlinie"; +"about.items.share_twitter.caption" = "Darüber Twittern!"; +"about.items.share_generic.caption" = "Freund einladen"; + +// MARK: AboutView -> VersionView + +"version.title" = "Version"; +"version.labels.intro" = "Passepartout und TunnelKit sind geschrieben und gewartet von Davide De Rosa (keeshux).\n\nQuellcode für Passepartout und TunnelKit ist öffentlich auf GitHub unter GPLv3 verfügbar, du findest die Links auf der Homepage.\n\nPassepartout ist ein inoffizieller client und auf keine Art und Weise mit OpenVPN Inc. verbunden."; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Credits"; +"credits.sections.licenses.header" = "Lizenzen"; +"credits.sections.notices.header" = "Notizen"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Einstellungen"; +"preferences.sections.general.header" = "Allgemein"; +"preferences.items.launches_on_login.caption" = "Bei Anmeldung starten"; +"preferences.items.launches_on_login.footer" = "Hier ein Häkchen setzen, um die App beim Systemstart oder der Anmeldung automatisch zu starten."; +"preferences.items.confirm_quit.caption" = "Beenden bestätigen"; +"preferences.items.confirm_quit.footer" = "Hier ein Häkchen setzen, um einen Hinweis zur Bestätigung des Beendens anzuzeigen."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Anzeigen"; +"menu.switch_profile.title" = "Aktives Profil"; +"menu.active_profile.title.none" = "Keine aktiven Profile"; +"menu.active_profile.items.customize.title" = "Anpassen..."; +"menu.active_profile.messages.missing_credentials" = "Es wurde keine Konto konfiguriert"; +"menu.organizer.title" = "Organizer"; +"menu.preferences.title" = "Einstellungen"; +"menu.support.title" = "Support"; +"menu.quit.title" = "%@ beenden"; +"menu.quit.messages.confirm" = "Wenn das VPN aktiviert wurde, läuft es weiter im Hintergrund. Möchtest du beenden?"; diff --git a/Passepartout/App/Shared/L10n/el.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/el.lproj/Localizable.strings new file mode 100644 index 00000000..2b5ebf89 --- /dev/null +++ b/Passepartout/App/Shared/L10n/el.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Ακύρωση"; +"global.strings.next" = "Επόμενο"; +/* MARK: Global */ +"global.strings.ok" = "OK"; +"global.strings.save" = "Αποθήκευση"; +"global.strings.rename" = "Επανονομασία"; +"global.strings.add" = "Προσθήκη"; +"global.strings.default" = "Default"; +"global.strings.name" = "Όνομα"; +"global.strings.address" = "Διεύθυνση"; +"global.strings.addresses" = "Διεθύνσεις"; +"global.strings.port" = "Θύρα"; +"global.strings.protocol" = "Πρωτόκολλο"; +"global.strings.protocols" = "Πρωτόκολλα"; +"global.strings.enabled" = "Ενεργοποιήθηκε"; +"global.strings.disabled" = "Απενεργοποιήθηκε"; +"global.strings.none" = "Κανένα"; +"global.strings.automatic" = "Αυτόματο"; +"global.strings.manual" = "Χειροκίνητο"; +"global.strings.encryption" = "Κρυπτογράφηση"; +"global.strings.reconnect" = "Επανασύνδεση"; +"global.strings.servers" = "Διακομιστές"; +"global.strings.domain" = "Domain"; +"global.strings.domains" = "Τομείς"; +"global.strings.proxy" = "Proxy"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "Διεπαφή"; +"global.strings.private_key" = "Ιδιωτικό κλειδί"; +"global.strings.public_key" = "Δημόσιο κλειδί"; +"global.strings.endpoint" = "Τελικό σημείο"; +"global.strings.keepalive" = "Διατηρήστε ζωντανή"; +"global.strings.advanced" = "Προηγμένα"; +"global.strings.translations" = "Μεταφράσεις"; + +"global.messages.email_not_configured" = "Δεν έχει ρυθμιστεί λογαριασμός ηλεκτρονικού ταχυδρομείου."; +"global.messages.share" = "Το Passepartout είναι φιλικό προς το χρήστη, ανοιχτού κώδικα OpenVPN πρόγραμμα για iOS και macOS"; + +"global.placeholders.profile_name" = "Το προφίλ μου"; + +"global.errors.missing_profile" = "Λείπει προφίλ"; +"global.errors.missing_account" = "Λείπει λογαριασμός"; +"global.errors.missing_provider_server" = "Λείπει διακομιστής"; +"global.errors.missing_provider_preset" = "Λείπει προεπιλογή"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Προσπάθεια Σύνδεσης"; +"tunnelkit.vpn.active" = "Ενεργό"; +"tunnelkit.vpn.disconnecting" = "Αποσυνδέετε"; +"tunnelkit.vpn.inactive" = "Μη ενεργό"; +"tunnelkit.vpn.disabled" = "Απενεργοποιημένο"; +"tunnelkit.vpn.unused" = "Ανενεργό"; + +"tunnelkit.errors.vpn.timeout" = "Τέλος χρονικού Ορίου"; +"tunnelkit.errors.vpn.dns" = "Το DNS απέτυχε"; +"tunnelkit.errors.vpn.auth" = "Το Auth απέτυχε"; +"tunnelkit.errors.vpn.tls" = "Το TLS απέτυχε"; +"tunnelkit.errors.vpn.encryption" = "Η Κρυπτογράφηση απέτυχε"; +"tunnelkit.errors.vpn.compression" = "Η συμπίεση δεν υποστηρίζεται"; +"tunnelkit.errors.vpn.network" = "Το δίκτυο άλλαξε"; +"tunnelkit.errors.vpn.routing" = "Λείπει η δρομολόγηση"; +"tunnelkit.errors.vpn.gateway" = "Δεν υπάρχει πύλη"; +"tunnelkit.errors.vpn.shutdown" = "Ο διακομιστής έκλεισε"; + +"tunnelkit.errors.parsing" = "Δεν είναι δυνατή η ανάλυση του παρεχόμενου αρχείου ρύθμισης παραμέτρων (%@)."; +"tunnelkit.errors.openvpn.malformed" = "Το αρχείο ρυθμίσεων περιέχει μια ακατάλληλη επιλογή (%@)."; +"tunnelkit.errors.openvpn.required_option" = "Το αρχείο διαμόρφωσης δεν διαθέτει την απαιτούμενη επιλογή (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "Το αρχείο διαμόρφωσης περιέχει μια επιλογή που δεν υποστηρίζεται (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "Το αρχείο ρυθμίσεων είναι σωστό, αλλά περιέχει μια δυνητικά μη υποστηριζόμενη επιλογή (%@).\n\nΗ δυνατότητα σύνδεσης μπορεί να διακοπεί ανάλογα με τις ρυθμίσεις του διακομιστή."; +"tunnelkit.errors.openvpn.passphrase_required" = "Εισαγάγετε το κωδικό κρυπτογράφησης."; +"tunnelkit.errors.openvpn.decryption" = "Η διαμόρφωση περιέχει κρυπτογραφημένο ιδιωτικό κλειδί και δεν ήταν δυνατό να αποκρυπτογραφηθεί. Δείτε πάλι το κωδικό που καταχωρίσατε."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "Ελάτε να με παρακολουθήσετε ζωντανά το Passepartout στο Twitch, εγγραφείτε στη συνομιλία για να αλληλεπιδράσετε και να συνεισφέρετε!"; +"organizer.sections.siri.footer" = "Get help from Siri to speed up your most common interactions with the app."; +"organizer.sections.support.header" = "Υποστήριξη"; +"organizer.items.follow_twitch.caption" = "Παρακολουθήστε το Passepartout στο Twitch"; +"organizer.items.profile.value.current" = "Σε χρήση"; +"organizer.items.siri_shortcuts.caption" = "Διαχείριση Συντομεύσεων"; +"organizer.items.join_community.caption" = "Συμμετοχή στην κοινότητα"; +"organizer.items.write_review.caption" = "Γράψτε μια κριτική"; +"organizer.items.donate.caption" = "Κάντε μια δωρεά"; +"organizer.items.github_sponsors.caption" = "Υποστηρίξτε με στο GitHub"; +"organizer.items.translate.caption" = "Βοηθήστε στη μετάφραση"; +"organizer.items.about.caption" = "Σχετικά με %@"; +"organizer.items.uninstall.caption" = "Αφαίρεση ρύθμισης VPN"; +"organizer.items.add_provider.caption" = "Προσθήκη νέου παρόχου"; +"organizer.items.add_host.caption" = "Προσθήκη από αρχεία"; + +"organizer.alerts.reddit.message" = "Γνωρίζατε ότι το Passepartout έχει subreddit? Εγγραφείτε για ενημερώσεις ή για να συζητήσετε προβλήματα της εφαρμογές, νέες δυνατότητες και άλλα.\n\nΕίναι επίσης ένας ωραίος τρόπος να δείξετε ότι ενδιαφέρεστε για τη προσπάθεια αυτή."; +"organizer.alerts.reddit.buttons.subscribe" = "Εγγραφή τώρα!"; +"organizer.alerts.reddit.buttons.remind" = "Υπενθύμιση Αργότερα"; +"organizer.alerts.reddit.buttons.never" = "Μη με ρωτήσεις ξανά"; + +"organizer.alerts.uninstall_vpn.message" = "Θέλετε πραγματικά να διαγράψετε τη διαμόρφωση VPN από τις ρυθμίσεις της συσκευής σας; Αυτό μπορεί να διορθώσει κάποιες καταστραμμένες καταστάσεις VPN και δεν θα επηρεάσει τα προφίλ του παροχέα και του διακομιστή σας."; +"organizer.alerts.remove_profile.title" = "Κατάργηση προφίλ"; +"organizer.alerts.remove_profile.message" = "Είστε βέβαιοι ότι θέλετε να διαγράψετε το προφίλ %@;"; + +"organizer.menus.add_profile.imported" = "Προσθήκη %@"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "Νέο προφίλ"; +"add_profile.shared.views.existing.header" = "Υπάρχον Προφίλ"; +"add_profile.shared.alerts.overwrite.message" = "Ένα προφίλ με το ίδιο όνομα υπάρχει ήδη. Αντικατάσταση;"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Εισαγωγή φράσης εισόδου"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Πάροχοι"; +"add_profile.provider.sections.vpn.footer" = "Εδώ θα βρείτε ορισμένους παρόχους με προκαθορισμένες ρυθμίσεις προφίλ."; +"add_profile.provider.items.update_list" = "Αναβάθμιση Λίστας"; +"add_profile.provider.errors.no_default_server" = "Δεν βρέθηκε διακομιστής"; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Πάροχος"; + +"profile.welcome.message" = "Καλώς Ήλθατε στο Passepartout!\n\nΧρησιμοποιήστε τον διοργανωτή για να προσθέσετε ένα νέο προφίλ."; + +"profile.sections.vpn.footer" = "Η σύνδεση θα πραγματοποιηθεί όποτε είναι απαραίτητο."; +"profile.sections.status.header" = "Σύνδεση"; +"profile.sections.configuration.header" = "Ρύθμιση"; +"profile.sections.provider_infrastructure.footer" = "Τελευταία ενημέρωση στις %@."; +"profile.sections.vpn_survives_sleep.footer" = "Απενεργοποιήστε για να βελτιώσετε τη χρήση της μπαταρίας, εις βάρος των περιστασιακών επιβραδύνσεων που οφείλονται σε επανασύνδεση αφύπνισης."; +"profile.sections.vpn_resolves_hostname.footer" = "Προτιμάται στα περισσότερα δίκτυα και απαιτείται σε ορισμένα δίκτυα IPv6. Απενεργοποιήστε το εκεί που μπλοκάρεται το DNS ή για να επιταχύνετε τη επικοινωνία όταν το DNS είναι αργό για να ανταποκριθεί."; +"profile.sections.feedback.header" = "Ανατροφοδότηση"; +"profile.items.use_profile.caption" = "Χρησιμοποιήστε αυτό το προφίλ"; +"profile.items.vpn_service.caption" = "Ενεργοποιήθηκε"; +"profile.items.vpn.turn_on.caption" = "Ενεργοποίηση VPN"; +"profile.items.vpn.turn_off.caption" = "Απενεργοποίηση VPN"; +"profile.items.connection_status.caption" = "Κατάσταση"; +"profile.items.data_count.caption" = "Ανταλλαγή δεδομένων"; +"profile.items.provider.refresh.caption" = "Ανανέωση της υποδομής"; +"profile.items.category.caption" = "Κατηγορία"; +"profile.items.only_shows_favorites.caption" = "Προβολή αγαπημένων τοποθεσιών μόνο"; +"profile.items.vpn_survives_sleep.caption" = "Κρατήστε ζωντανό στον ύπνο"; +"profile.items.vpn_resolves_hostname.caption" = "Επίλυση του ονόματος σέρβερ διακομιστή"; +"profile.items.reconnect.caption" = "Επανασύνδεση"; + +"profile.alerts.rename.title" = "Μετονομασία προφίλ"; +"profile.alerts.reconnect_vpn.message" = "Θέλετε να συνδεθείτε ξανά με το VPN;"; +"profile.alerts.test_connectivity.title" = "Συνδεσιμότητα"; +"profile.alerts.test_connectivity.messages.success" = "Η συσκευή σας είναι συνδεδεμένη στο Διαδίκτυο!"; +"profile.alerts.test_connectivity.messages.failure" = "Η συσκευή σας δεν διαθέτει σύνδεση στο Internet, παρακαλούμε να ελέγξετε τις παραμέτρους του προφίλ σας."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Τοποθεσία"; +"provider.location.sections.empty_favorites.footer" = "Σείρετε αριστερά για να προσθέσετε ή να αφαιρέσεται από τα αγαπημένα."; +"provider.location.actions.favorite" = "Αγαπημένο"; +"provider.location.actions.unfavorite" = "Δεν προτιμάται"; + +"provider.preset.title" = "Προεπιλογή"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Λογαριασμός"; +"account.sections.credentials.header" = "Διαπιστευτήρια"; +"account.sections.registration.footer" = "Πηγαίνετε να αποκτήσετε λογαριασμό στον ιστότοπο %@."; +"account.items.username.caption" = "Όνομα χρήστη"; +"account.items.username.placeholder" = "χρήστης"; +"account.items.password.caption" = "Κωδικός"; +"account.items.password.placeholder" = "κωδικός"; +"account.items.open_guide.caption" = "Δείτε τα διαπιστευτήρια σας"; +"account.items.signup.caption" = "Εγγραφείτε με %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Χρησιμοποιήστε τα διαπιστευτήρια της υπηρεσίας %@, τα οποία ενδέχεται να διαφέρουν από τα διαπιστευτήρια του ιστότοπου."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@. Το όνομα χρήστη είναι συνήθως αριθμητικό (χωρίς διαστήματα)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@. Το όνομα χρήστη είναι συνήθως το ηλεκτρονικό σας ταχυδρομείο."; +"account.sections.guidance.footer.infrastructure.pia" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@. Το όνομα χρήστη είναι συνήθως αριθμητικό με πρόθεμα \"p\"."; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Βρείτε τα διαπιστευτήριά σας %@ στην ενότητα \"Λογαριασμός> OpenVPN / IKEv2 Username \" της ιστοσελίδας."; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@. Το όνομα χρήστη είναι συνήθως το ηλεκτρονικό σας ταχυδρομείο."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@. Το όνομα χρήστη είναι συνήθως το ηλεκτρονικό σας ταχυδρομείο."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Βρείτε τα διαπιστευτήριά σας %@ στο OpenVPN Config Generator στον ιστότοπο."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Ομότιμο"; +"endpoint.wireguard.items.preshared_key.caption" = "Κλειδί προδιαμοιρασμού"; +"endpoint.wireguard.items.allowed_ip.caption" = "Επιτρεπόμενη IP"; + +"endpoint.advanced.title" = "Τεχνικές Λεπτομέρειες"; +"endpoint.advanced.openvpn.sections.communication.header" = "Επικοινωνία"; +"endpoint.advanced.openvpn.sections.reset.footer" = "Αν καταλήξατε σε κατεστραμένη συνδεσιμότητα μετά την αλλαγή των παραμέτρων επικοινωνίας, πατήστε για να επανέλθετε στην αρχική διαμόρφωση."; +"endpoint.advanced.openvpn.sections.compression.header" = "Συμπίεση"; +"endpoint.advanced.openvpn.sections.network.header" = "Δίκτυο"; +"endpoint.advanced.openvpn.sections.other.header" = "Άλλο"; +"endpoint.advanced.openvpn.items.route.caption" = "Διαδρομή"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Cipher"; +"endpoint.advanced.openvpn.items.digest.caption" = "Αυθεντικοποίηση"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Ενσωματωμένο"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Framing"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Αλγόρυθμος"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "Δεν υποστηρίζεται"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Επαναφορά ρυθμίσεων"; +"endpoint.advanced.openvpn.items.client.caption" = "Πιστοποιητικό"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Κλειδί"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Επαληθεύτηκε"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "Δεν επαληθεύτηκε"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Wrapping"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Αυθεντικοποίηση"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Κρυπτογράφηση"; +"endpoint.advanced.openvpn.items.eku.caption" = "Εκτεταμένη επαλήθευση"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d δευτερόλεπτα"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "Επαναδιαπραγμάτευση"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "μετά από %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Τυχαίο τελικό σημείο"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Ρυθμίσεις Δικτύου"; +"network_settings.sections.choices.header" = "Παράκαμψη"; +"network_settings.gateway.title" = "Προεπιλεγμένη πύλη"; +"network_settings.proxy.items.bypass_domains.caption" = "Παράκαμψη τομέων"; +"network_settings.items.add_dns_server.caption" = "Προσθήκη Διεύθυνσης"; +"network_settings.items.add_dns_domain.caption" = "Προσθήκη τομέα αναζήτησης"; +"network_settings.items.proxy_bypass.caption" = "Παράκαμψη Τομέα"; +"network_settings.items.add_proxy_bypass.caption" = "Προσθήκη τομέα παράκαμψης"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Αξιόπιστα δίκτυα"; +"on_demand.sections.policy.footer" = "Κατά την είσοδο σε ένα αξιόπιστο δίκτυο, το VPN απενεργοποιείται κανονικά και διατηρείται αποσυνδεδεμένο. Απενεργοποιήστε αυτήν την επιλογή για να μην έχετε μια τέτοια συμπεριφορά."; +"on_demand.items.add_ssid.caption" = "Προσθέστε Wi-Fi"; +"on_demand.items.active.caption" = "Εμπιστευθείτε"; +"on_demand.items.mobile.caption" = "Δίκτυο Κινητής"; +"on_demand.items.ethernet.caption" = "Εμπιστευθείτε τις ενσύρματες συνδέσεις"; +"on_demand.items.ethernet.description" = "Επιλέξτε για να εμπιστευθείτε οποιαδήποτε ενσύρματη σύνδεση καλωδίου."; +"on_demand.items.policy.caption" = "Τα αξιόπιστα δίκτυα απενεργοποιούν το VPN"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Διαγνωστικά"; +"diagnostics.sections.debug_log.footer" = "Η κατάσταση κάλυψης θα είναι αποτελεσματική μετά την επανασύνδεση. Τα δεδομένα δικτύου είναι του διακομιστή, διευθύνσεις IP, δρομολόγηση και SSID. Τα διαπιστευτήρια και τα ιδιωτικά κλειδιά δεν καταγράφονται ανεξάρτητα."; +"diagnostics.items.server_configuration.caption" = "Ρυθμίσεις Διακομιστή"; +"diagnostics.items.masks_private_data.caption" = "Μάσκα δεδομένα δικτύου"; +"diagnostics.items.report_issue.caption" = "Αναφορά ζητήματος συνδεσιμότητας"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "Για να επαναφέρετε με ασφάλεια την τρέχουσα καταγραφή εντοπισμού σφαλμάτων και να εφαρμόσετε τη νέα προτίμηση κάλυψης, πρέπει να συνδεθείτε ξανά με το VPN."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Μητρώο εντοπισμού σφαλμάτων"; +"debug_log.buttons.copy" = "Αντιγραφή"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Αναφορά Προβλήματος"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Προσθήκη Συντόμευσης"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "Δίκτυο Κινητής"; +"shortcuts.add.items.connect.caption" = "Σύνδεση σε"; +"shortcuts.add.items.enable_vpn.caption" = "Ενεργοποίηση VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Απενεργοποίηση VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Εμπιστέψου το τρέχον Wi-Fi"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Μην εμπιστευθείτε το τρέχον Wi-Fi"; +"shortcuts.add.items.trust_cellular.caption" = "Εμπιστοσύνη δικτύου κινητής τηλεφωνίας"; +"shortcuts.add.items.untrust_cellular.caption" = "Μην εμπιστευθείτε το δίκτυο κινητής τηλεφωνίας"; +"shortcuts.add.alerts.no_profiles.message" = "Δεν υπάρχει προφίλ για σύνδεση."; + +"shortcuts.edit.title" = "Διαχείριση συντομεύσεων"; +"shortcuts.edit.sections.all.header" = "Υπάρχουσες συντομεύσεις"; +"shortcuts.edit.items.add_shortcut.caption" = "Προσθήκη Συντόμευσης"; + +// MARK: PaywallView + +"paywall.title" = "Αγορά"; +"paywall.sections.products.footer" = "Κάθε προϊόν είναι μια αγορά. Οι αγορές παρόχων δεν περιλαμβάνουν τη συνδρομή VPN."; +"paywall.items.loading.caption" = "Φόρτωση προϊόντων"; +"paywall.items.full_version.extra_description" = "Όλοι οι πάροχοι (περιλαμβάνονται και οι μελλοντικοί)\n%@"; +"paywall.items.restore.title" = "Επαναφορά Αγορών"; +"paywall.items.restore.description" = "Εαν αγοράσατε την εφαρμογή στο παρελθόν, μπορείτε να κάνετε επαναφορά αγορών και αυτή η οθόνη δε θα εμφανιστεί ξανά."; + +// MARK: DonateView + +"donate.title" = "Δωρεά"; +"donate.sections.one_time.header" = "Μια Φορά"; +"donate.sections.one_time.footer" = "Αν είστε χαρούμενη με τη δουλειά μου, εδώ είναι λίγα ποσά που μπορείτε να δώσετε αμέσως.\n\nΘα χρεωθείτε μόνο μία φορά και μπορείτε να δώσετε πολλές φορές."; +"donate.items.loading.caption" = "Φόρτωση δωρεών"; +"donate.items.purchasing.caption" = "Εκτέλεση δωρεάς"; +"donate.alerts.purchase.success.title" = "Ευχαριστώ"; +"donate.alerts.purchase.success.message" = "Αυτό σημαίνει πολλά για μένα και πραγματικά ελπίζω να συνεχίσετε να χρησιμοποιείτε και να προωθείτε αυτήν την εφαρμογή."; +"donate.alerts.purchase.failure.message" = "Δεν είναι δυνατή η εκτέλεση της δωρεάς. %@"; + +// MARK: AboutView + +"about.title" = "Περι"; +"about.sections.web.header" = "Web"; +"about.sections.share.header" = "Διαμοιράστε"; +"about.items.credits.caption" = "Συντελεστές"; +"about.items.website.caption" = "Αρχική Σελίδα"; +"about.items.disclaimer.caption" = "Άρνηση Ευθύνης"; +"about.items.privacy_policy.caption" = "Πολιτική Απορρήτου"; +"about.items.share_twitter.caption" = "Tweet γι 'αυτό!"; +"about.items.share_generic.caption" = "Πρόσκληση Φίλου"; + +// MARK: AboutView -> VersionView + +"version.title" = "Έκδοση"; +"version.labels.intro" = "Το Passepartout και το TunnelKit γράφονται και συντηρούνται από τον Davide De Rosa (keeshux).\n\nΟ πηγαίος κώδικας για το Passepartout και το TunnelKit είναι δημόσια διαθέσιμε στο GitHub υπό το GPLv3, μπορείτε να βρείτε συνδέσμους στην αρχική σελίδα.\n\nΤο Passepartout είναι ένας μη επίσημος πελάτης και δεν είναι συνδεδεμένος με το OpenVPN Inc."; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Συντελεστές"; +"credits.sections.licenses.header" = "Άδειες"; +"credits.sections.notices.header" = "Σημειώσεις"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Προτιμήσεις"; +"preferences.sections.general.header" = "Γενικός"; +"preferences.items.launches_on_login.caption" = "Εκκίνηση κατά τη σύνδεση"; +"preferences.items.launches_on_login.footer" = "Επιλέξτε για αυτόματη εκκίνηση της εφαρμογής κατά την εκκίνηση ή τη σύνδεση."; +"preferences.items.confirm_quit.caption" = "Επιβεβαίωση διακοπής"; +"preferences.items.confirm_quit.footer" = "Επιλέξτε για παρουσίαση ειδοποίησης ότι επιβεβαιώνεται η διακοπή."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Προβολή"; +"menu.switch_profile.title" = "Ενεργό προφίλ"; +"menu.active_profile.title.none" = "Δεν υπάρχει ενεργό προφίλ"; +"menu.active_profile.items.customize.title" = "Προσαρμογή..."; +"menu.active_profile.messages.missing_credentials" = "Δεν έχει διαμορφωθεί λογαριασμός"; +"menu.organizer.title" = "Διοργανωτής"; +"menu.preferences.title" = "Προτιμήσεις"; +"menu.support.title" = "Υποστήριξη"; +"menu.quit.title" = "Διακοπή %@"; +"menu.quit.messages.confirm" = "Το VPN, αν είναι ενεργοποιημένο, θα εξακολουθεί να εκτελείται στο παρασκήνιο. Θέλετε να το διακόψετε;"; diff --git a/Passepartout/App/Shared/L10n/en.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/en.lproj/Localizable.strings new file mode 100644 index 00000000..50bbbfd1 --- /dev/null +++ b/Passepartout/App/Shared/L10n/en.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Cancel"; +"global.strings.next" = "Next"; +/* MARK: Global */ +"global.strings.ok" = "OK"; +"global.strings.save" = "Save"; +"global.strings.rename" = "Rename"; +"global.strings.add" = "Add"; +"global.strings.default" = "Default"; +"global.strings.name" = "Name"; +"global.strings.address" = "Address"; +"global.strings.addresses" = "Addresses"; +"global.strings.port" = "Port"; +"global.strings.protocol" = "Protocol"; +"global.strings.protocols" = "Protocols"; +"global.strings.enabled" = "Enabled"; +"global.strings.disabled" = "Disabled"; +"global.strings.none" = "None"; +"global.strings.automatic" = "Automatic"; +"global.strings.manual" = "Manual"; +"global.strings.encryption" = "Encryption"; +"global.strings.reconnect" = "Reconnect"; +"global.strings.servers" = "Servers"; +"global.strings.domain" = "Domain"; +"global.strings.domains" = "Domains"; +"global.strings.proxy" = "Proxy"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "Interface"; +"global.strings.private_key" = "Private key"; +"global.strings.public_key" = "Public key"; +"global.strings.endpoint" = "Endpoint"; +"global.strings.keepalive" = "Keep-alive"; +"global.strings.advanced" = "Advanced"; +"global.strings.translations" = "Translations"; + +"global.messages.email_not_configured" = "No e-mail account is configured."; +"global.messages.share" = "Passepartout is an user-friendly, open source OpenVPN client for iOS and macOS"; + +"global.placeholders.profile_name" = "My profile"; + +"global.errors.missing_profile" = "Missing profile"; +"global.errors.missing_account" = "Missing account"; +"global.errors.missing_provider_server" = "Missing server"; +"global.errors.missing_provider_preset" = "Missing preset"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Connecting"; +"tunnelkit.vpn.active" = "Active"; +"tunnelkit.vpn.disconnecting" = "Disconnecting"; +"tunnelkit.vpn.inactive" = "Inactive"; +"tunnelkit.vpn.disabled" = "Disabled"; +"tunnelkit.vpn.unused" = "Off"; + +"tunnelkit.errors.vpn.timeout" = "Timeout"; +"tunnelkit.errors.vpn.dns" = "DNS failed"; +"tunnelkit.errors.vpn.auth" = "Auth failed"; +"tunnelkit.errors.vpn.tls" = "TLS failed"; +"tunnelkit.errors.vpn.encryption" = "Encryption failed"; +"tunnelkit.errors.vpn.compression" = "Compression unsupported"; +"tunnelkit.errors.vpn.network" = "Network changed"; +"tunnelkit.errors.vpn.routing" = "Missing routing"; +"tunnelkit.errors.vpn.gateway" = "No gateway"; +"tunnelkit.errors.vpn.shutdown" = "Server shutdown"; + +"tunnelkit.errors.parsing" = "Unable to parse the provided configuration file (%@)."; +"tunnelkit.errors.openvpn.malformed" = "The configuration file contains a malformed option (%@)."; +"tunnelkit.errors.openvpn.required_option" = "The configuration file lacks a required option (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "The configuration file contains an unsupported option (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "The configuration file is correct but contains a potentially unsupported option (%@).\n\nConnectivity may break depending on server settings."; +"tunnelkit.errors.openvpn.passphrase_required" = "Please enter the encryption passphrase."; +"tunnelkit.errors.openvpn.decryption" = "Unable to decrypt private key."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "Come watch me make Passepartout live on Twitch, join the chat to interact and contribute!"; +"organizer.sections.siri.footer" = "Get help from Siri to speed up your most common interactions with the app."; +"organizer.sections.support.header" = "Support"; +"organizer.items.follow_twitch.caption" = "Watch Passepartout on Twitch"; +"organizer.items.profile.value.current" = "In use"; +"organizer.items.siri_shortcuts.caption" = "Manage shortcuts"; +"organizer.items.join_community.caption" = "Join community"; +"organizer.items.write_review.caption" = "Write a review"; +"organizer.items.donate.caption" = "Make a donation"; +"organizer.items.github_sponsors.caption" = "Support me on GitHub"; +"organizer.items.translate.caption" = "Offer to translate"; +"organizer.items.about.caption" = "About %@"; +"organizer.items.uninstall.caption" = "Remove VPN configuration"; +"organizer.items.add_provider.caption" = "Add new provider"; +"organizer.items.add_host.caption" = "Add from Files"; + +"organizer.alerts.reddit.message" = "Did you know that Passepartout has a subreddit? Subscribe for updates or to discuss issues, features, new platforms or whatever you like.\n\nIt's also a great way to show you care about this project."; +"organizer.alerts.reddit.buttons.subscribe" = "Subscribe now!"; +"organizer.alerts.reddit.buttons.remind" = "Remind me later"; +"organizer.alerts.reddit.buttons.never" = "Don't ask again"; + +"organizer.alerts.uninstall_vpn.message" = "Do you really want to erase the VPN configuration from your device settings? This may fix some broken VPN states and will not affect your provider and host profiles."; +"organizer.alerts.remove_profile.title" = "Remove profile"; +"organizer.alerts.remove_profile.message" = "Are you sure you want to delete profile %@?"; + +"organizer.menus.add_profile.imported" = "Add %@"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "New profile"; +"add_profile.shared.views.existing.header" = "Existing profiles"; +"add_profile.shared.alerts.overwrite.message" = "A profile with the same name already exists. Replace it?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Enter passphrase"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Providers"; +"add_profile.provider.sections.vpn.footer" = "Here you find a few providers with preset configuration profiles."; +"add_profile.provider.items.update_list" = "Update list"; +"add_profile.provider.errors.no_default_server" = "Could not find any server."; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Provider"; + +"profile.welcome.message" = "Welcome to Passepartout!\n\nUse the organizer to add a new profile."; + +"profile.sections.vpn.footer" = "The connection will be established whenever necessary."; +"profile.sections.status.header" = "Connection"; +"profile.sections.configuration.header" = "Configuration"; +"profile.sections.provider_infrastructure.footer" = "Last updated on %@."; +"profile.sections.vpn_survives_sleep.footer" = "Disable to improve battery usage, at the expense of occasional slowdowns due to wake-up reconnections."; +"profile.sections.vpn_resolves_hostname.footer" = "Preferred in most networks and required in some IPv6 networks. Disable where DNS is blocked, or to speed up negotiation when DNS is slow to respond."; +"profile.sections.feedback.header" = "Feedback"; +"profile.items.use_profile.caption" = "Use this profile"; +"profile.items.vpn_service.caption" = "Enabled"; +"profile.items.vpn.turn_on.caption" = "Enable VPN"; +"profile.items.vpn.turn_off.caption" = "Disable VPN"; +"profile.items.connection_status.caption" = "Status"; +"profile.items.data_count.caption" = "Exchanged data"; +"profile.items.provider.refresh.caption" = "Refresh infrastructure"; +"profile.items.category.caption" = "Category"; +"profile.items.only_shows_favorites.caption" = "Only show favorite locations"; +"profile.items.vpn_survives_sleep.caption" = "Keep alive on sleep"; +"profile.items.vpn_resolves_hostname.caption" = "Resolve provider hostname"; +"profile.items.reconnect.caption" = "Reconnect"; + +"profile.alerts.rename.title" = "Rename profile"; +"profile.alerts.reconnect_vpn.message" = "Do you want to reconnect to the VPN?"; +"profile.alerts.test_connectivity.title" = "Connectivity"; +"profile.alerts.test_connectivity.messages.success" = "Your device is connected to the Internet!"; +"profile.alerts.test_connectivity.messages.failure" = "Your device has no Internet connectivity, please review your profile parameters."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Location"; +"provider.location.sections.empty_favorites.footer" = "Swipe left on a location to add or remove it from Favorites."; +"provider.location.actions.favorite" = "Favorite"; +"provider.location.actions.unfavorite" = "Unfavorite"; + +"provider.preset.title" = "Preset"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Account"; +"account.sections.credentials.header" = "Credentials"; +"account.sections.registration.footer" = "Go get an account on the %@ website."; +"account.items.username.caption" = "Username"; +"account.items.username.placeholder" = "username"; +"account.items.password.caption" = "Password"; +"account.items.password.placeholder" = "secret"; +"account.items.open_guide.caption" = "See your credentials"; +"account.items.signup.caption" = "Register with %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Use your %@ website credentials."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Use your %@ service credentials, which may differ from website credentials."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Use your %@ website credentials. Your username is usually numeric (without spaces)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Use your %@ website credentials. Your username is usually your e-mail."; +"account.sections.guidance.footer.infrastructure.pia" = "Use your %@ website credentials. Your username is usually numeric with a \"p\" prefix."; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Find your %@ credentials in the \"Account > OpenVPN / IKEv2 Username\" section of the website."; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Use your %@ website credentials. Your username is usually your e-mail."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Use your %@ website credentials. Your username is usually your e-mail."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Find your %@ credentials in the OpenVPN Config Generator on the website."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Peer"; +"endpoint.wireguard.items.preshared_key.caption" = "Preshared key"; +"endpoint.wireguard.items.allowed_ip.caption" = "Allowed IP"; + +"endpoint.advanced.title" = "Technical details"; +"endpoint.advanced.openvpn.sections.communication.header" = "Communication"; +"endpoint.advanced.openvpn.sections.reset.footer" = "If you ended up with broken connectivity after changing the communication parameters, tap to revert to the original configuration."; +"endpoint.advanced.openvpn.sections.compression.header" = "Compression"; +"endpoint.advanced.openvpn.sections.network.header" = "Network"; +"endpoint.advanced.openvpn.sections.other.header" = "Other"; +"endpoint.advanced.openvpn.items.route.caption" = "Route"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Cipher"; +"endpoint.advanced.openvpn.items.digest.caption" = "Authentication"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Embedded"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Framing"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Algorithm"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "Unsupported"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Reset configuration"; +"endpoint.advanced.openvpn.items.client.caption" = "Certificate"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Key"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Verified"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "Not verified"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Wrapping"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Authentication"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Encryption"; +"endpoint.advanced.openvpn.items.eku.caption" = "Extended verification"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d seconds"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "Renegotiation"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "after %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Randomize endpoint"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Network settings"; +"network_settings.sections.choices.header" = "Override"; +"network_settings.gateway.title" = "Default gateway"; +"network_settings.proxy.items.bypass_domains.caption" = "Bypass domains"; +"network_settings.items.add_dns_server.caption" = "Add address"; +"network_settings.items.add_dns_domain.caption" = "Add search domain"; +"network_settings.items.proxy_bypass.caption" = "Bypass domain"; +"network_settings.items.add_proxy_bypass.caption" = "Add bypass domain"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Trusted networks"; +"on_demand.sections.policy.footer" = "When entering a trusted network, the VPN is normally shut down and kept disconnected. Disable this option to not enforce such behavior."; +"on_demand.items.add_ssid.caption" = "Add Wi-Fi"; +"on_demand.items.active.caption" = "Trust"; +"on_demand.items.mobile.caption" = "Cellular network"; +"on_demand.items.ethernet.caption" = "Trust wired connections"; +"on_demand.items.ethernet.description" = "Check to trust any wired cable connection."; +"on_demand.items.policy.caption" = "Trust disables VPN"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Diagnostics"; +"diagnostics.sections.debug_log.footer" = "Masking status will be effective after reconnecting. Network data are hostnames, IP addresses, routing, SSID. Credentials and private keys are not logged regardless."; +"diagnostics.items.server_configuration.caption" = "Server configuration"; +"diagnostics.items.masks_private_data.caption" = "Mask network data"; +"diagnostics.items.report_issue.caption" = "Report connectivity issue"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "In order to safely reset the current debug log and apply the new masking preference, you must reconnect to the VPN now."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Debug log"; +"debug_log.buttons.copy" = "Copy"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Report issue"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Add shortcut"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "Cellular"; +"shortcuts.add.items.connect.caption" = "Connect to"; +"shortcuts.add.items.enable_vpn.caption" = "Enable VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Disable VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Trust current Wi-Fi"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Untrust current Wi-Fi"; +"shortcuts.add.items.trust_cellular.caption" = "Trust cellular network"; +"shortcuts.add.items.untrust_cellular.caption" = "Untrust cellular network"; +"shortcuts.add.alerts.no_profiles.message" = "There is no profile to connect to."; + +"shortcuts.edit.title" = "Manage shortcuts"; +"shortcuts.edit.sections.all.header" = "Existing shortcuts"; +"shortcuts.edit.items.add_shortcut.caption" = "Add shortcut"; + +// MARK: PaywallView + +"paywall.title" = "Purchase"; +"paywall.sections.products.footer" = "Every product is a one-time purchase. Provider purchases do not include a VPN subscription."; +"paywall.items.loading.caption" = "Loading products"; +"paywall.items.full_version.extra_description" = "All providers (including future ones)\n%@"; +"paywall.items.restore.title" = "Restore purchases"; +"paywall.items.restore.description" = "If you bought this app or feature in the past, you can restore your purchases and this screen won't show again."; + +// MARK: DonateView + +"donate.title" = "Donate"; +"donate.sections.one_time.header" = "One time"; +"donate.sections.one_time.footer" = "If you want to display gratitude for my free work, here are a couple amounts you can donate instantly.\n\nYou will only be charged once per donation, and you can donate multiple times."; +"donate.items.loading.caption" = "Loading donations"; +"donate.items.purchasing.caption" = "Performing donation"; +"donate.alerts.purchase.success.title" = "Thank you"; +"donate.alerts.purchase.success.message" = "This means a lot to me and I really hope you keep using and promoting this app."; +"donate.alerts.purchase.failure.message" = "Unable to perform the donation. %@"; + +// MARK: AboutView + +"about.title" = "About"; +"about.sections.web.header" = "Web"; +"about.sections.share.header" = "Share"; +"about.items.credits.caption" = "Credits"; +"about.items.website.caption" = "Home page"; +"about.items.disclaimer.caption" = "Disclaimer"; +"about.items.privacy_policy.caption" = "Privacy policy"; +"about.items.share_twitter.caption" = "Tweet about it!"; +"about.items.share_generic.caption" = "Invite a friend"; + +// MARK: AboutView -> VersionView + +"version.title" = "Version"; +"version.labels.intro" = "Passepartout and TunnelKit are written and maintained by Davide De Rosa (keeshux).\n\nSource code for Passepartout and TunnelKit is publicly available on GitHub under the GPLv3, you can find links in the home page.\n\nPassepartout is a non-official client and is in no way affiliated with OpenVPN Inc."; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Credits"; +"credits.sections.licenses.header" = "Licenses"; +"credits.sections.notices.header" = "Notices"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Preferences"; +"preferences.sections.general.header" = "General"; +"preferences.items.launches_on_login.caption" = "Launch on login"; +"preferences.items.launches_on_login.footer" = "Check to automatically launch the app on boot or login."; +"preferences.items.confirm_quit.caption" = "Confirm quit"; +"preferences.items.confirm_quit.footer" = "Check to present a quit confirmation alert."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Show"; +"menu.switch_profile.title" = "Active profile"; +"menu.active_profile.title.none" = "No active profile"; +"menu.active_profile.items.customize.title" = "Customize..."; +"menu.active_profile.messages.missing_credentials" = "No account configured"; +"menu.organizer.title" = "Organizer"; +"menu.preferences.title" = "Preferences"; +"menu.support.title" = "Support"; +"menu.quit.title" = "Quit %@"; +"menu.quit.messages.confirm" = "The VPN, if enabled, will still run in the background. Do you want to quit?"; diff --git a/Passepartout/App/Shared/L10n/es.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/es.lproj/Localizable.strings new file mode 100644 index 00000000..f64e988c --- /dev/null +++ b/Passepartout/App/Shared/L10n/es.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Cancelar"; +"global.strings.next" = "Siguiente"; +/* MARK: Global */ +"global.strings.ok" = "OK"; +"global.strings.save" = "Guardar"; +"global.strings.rename" = "Cambiar nombre"; +"global.strings.add" = "Añadir"; +"global.strings.default" = "Default"; +"global.strings.name" = "Nombre"; +"global.strings.address" = "Dirección"; +"global.strings.addresses" = "Direcciones"; +"global.strings.port" = "Puerta"; +"global.strings.protocol" = "Protocolo"; +"global.strings.protocols" = "Protocolos"; +"global.strings.enabled" = "Habilitado"; +"global.strings.disabled" = "Deshabilitado"; +"global.strings.none" = "Ninguno"; +"global.strings.automatic" = "Automático"; +"global.strings.manual" = "Manual"; +"global.strings.encryption" = "Cifrado"; +"global.strings.reconnect" = "Reconectar"; +"global.strings.servers" = "Servidores"; +"global.strings.domain" = "Dominio"; +"global.strings.domains" = "Dominios"; +"global.strings.proxy" = "Proxy"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "Interfaz"; +"global.strings.private_key" = "Clave privada"; +"global.strings.public_key" = "Clave pública"; +"global.strings.endpoint" = "Extremo"; +"global.strings.keepalive" = "Keep-alive"; +"global.strings.advanced" = "Avanzado"; +"global.strings.translations" = "Traducciones"; + +"global.messages.email_not_configured" = "Ningún e-mail configurado."; +"global.messages.share" = "Passepartout es un cliente OpenVPN intuitivo, de código abierto para iOS y macOS"; + +"global.placeholders.profile_name" = "Mi perfil"; + +"global.errors.missing_profile" = "Sin perfil"; +"global.errors.missing_account" = "Sin cuenta"; +"global.errors.missing_provider_server" = "Sin servidor"; +"global.errors.missing_provider_preset" = "Sin ajuste"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Conectando"; +"tunnelkit.vpn.active" = "Activo"; +"tunnelkit.vpn.disconnecting" = "Desconectando"; +"tunnelkit.vpn.inactive" = "Inactivo"; +"tunnelkit.vpn.disabled" = "Deshabilitado"; +"tunnelkit.vpn.unused" = "Desactivada"; + +"tunnelkit.errors.vpn.timeout" = "Timeout"; +"tunnelkit.errors.vpn.dns" = "DNS fallido"; +"tunnelkit.errors.vpn.auth" = "Autentificación fallida"; +"tunnelkit.errors.vpn.tls" = "TLS fallido"; +"tunnelkit.errors.vpn.encryption" = "Cifrado fallido"; +"tunnelkit.errors.vpn.compression" = "Compresión no soportada"; +"tunnelkit.errors.vpn.network" = "Cambio de red"; +"tunnelkit.errors.vpn.routing" = "Sin rutas"; +"tunnelkit.errors.vpn.gateway" = "Sin puerta de enlace"; +"tunnelkit.errors.vpn.shutdown" = "Servidor apagado"; + +"tunnelkit.errors.parsing" = "Imposible importar el fichero de configuración proporcionado (%@)."; +"tunnelkit.errors.openvpn.malformed" = "El fichero de configuración contiene una opción mal formada (%@)."; +"tunnelkit.errors.openvpn.required_option" = "El fichero de configuración falta de una opción necesaria (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "El fichero de configuración contiene una opción no soportada (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "El fichero de configuración es correcto pero contiene una opción potencialmente no soportada (%@).\n\nLa conectividad podría fallar según los parámetros del servidor."; +"tunnelkit.errors.openvpn.passphrase_required" = "Por favor introducir la contraseña de cifrado."; +"tunnelkit.errors.openvpn.decryption" = "La configuración contiene una clave privada cifrada que no ha podido ser descifrada. Por favor revisa la contraseña introducida."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "¡Ven a ver cómo hago Passepartout en vivo en Twitch, únete al chat para interactuar y contribuir!"; +"organizer.sections.siri.footer" = "Déjate ayudar por Siri para acelerar tus interacciones más frecuentes con la aplicación."; +"organizer.sections.support.header" = "Soporte"; +"organizer.items.follow_twitch.caption" = "Ve Passepartout en Twitch"; +"organizer.items.profile.value.current" = "En uso"; +"organizer.items.siri_shortcuts.caption" = "Gestionar atajos"; +"organizer.items.join_community.caption" = "Apuntarse a la comunidad"; +"organizer.items.write_review.caption" = "Escribir una reseña"; +"organizer.items.donate.caption" = "Hacer una donación"; +"organizer.items.github_sponsors.caption" = "Apoyar en GitHub"; +"organizer.items.translate.caption" = "Ofrecer una traducción"; +"organizer.items.about.caption" = "Sobre %@"; +"organizer.items.uninstall.caption" = "Borrar configuración VPN"; +"organizer.items.add_provider.caption" = "Añadir proveedor"; +"organizer.items.add_host.caption" = "Añadir desde Ficheros"; + +"organizer.alerts.reddit.message" = "Sabías que Passepartout tiene un subreddit? Suscríbete para actualizaciones o comentar problemas, funciones, nuevas plataformas o todo lo que se te ocurra.\n\nTambién es la manera ideal de mostrar interés en este proyecto."; +"organizer.alerts.reddit.buttons.subscribe" = "Suscribir ahora!"; +"organizer.alerts.reddit.buttons.remind" = "Recordar más tarde"; +"organizer.alerts.reddit.buttons.never" = "No preguntar más"; + +"organizer.alerts.uninstall_vpn.message" = "Realmente quieres eliminar la configuración VPN de tu dispositivo? Ésto puede corregir algunos estados incorrectos del VPN y no afectará tus perfiles."; +"organizer.alerts.remove_profile.title" = "Quitar perfil"; +"organizer.alerts.remove_profile.message" = "¿Seguro que deseas eliminar el perfil %@?"; + +"organizer.menus.add_profile.imported" = "Añadir %@"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "Nuevo perfil"; +"add_profile.shared.views.existing.header" = "Perfiles existentes"; +"add_profile.shared.alerts.overwrite.message" = "Ya existe un perfil con el mismo nombre. Reemplazar?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Introduce la contraseña de cifrado"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Proveedores"; +"add_profile.provider.sections.vpn.footer" = "Aquí encuentras algunos proveedores con ajustes preconfigurados."; +"add_profile.provider.items.update_list" = "Actualizar lista"; +"add_profile.provider.errors.no_default_server" = "No se ha encontrado ningún servidor."; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Proveedor"; + +"profile.welcome.message" = "Bienvenid@ a Passepartout!\n\nUsa el organizador para añadir un nuevo perfil."; + +"profile.sections.vpn.footer" = "La conexión se establecerá siempre y cuando sea necesario."; +"profile.sections.status.header" = "Conexión"; +"profile.sections.configuration.header" = "Configuración"; +"profile.sections.provider_infrastructure.footer" = "Última actualización: %@."; +"profile.sections.vpn_survives_sleep.footer" = "Deshabilitar para mejorar el uso de la batería, a costa de ralentizaciones ocasionales por las reconexiones al despertar el dispositivo."; +"profile.sections.vpn_resolves_hostname.footer" = "Preferido en la mayoría de las redes y necesario en algunas redes IPv6. Deshabilitar donde el DNS esté bloqueado, o para acelerar la negociación cuando el DNS sea lento en responder."; +"profile.sections.feedback.header" = "Feedback"; +"profile.items.use_profile.caption" = "Usar este perfil"; +"profile.items.vpn_service.caption" = "Habilitado"; +"profile.items.vpn.turn_on.caption" = "Habilitar VPN"; +"profile.items.vpn.turn_off.caption" = "Deshabilitar VPN"; +"profile.items.connection_status.caption" = "Estado"; +"profile.items.data_count.caption" = "Datos intercambiados"; +"profile.items.provider.refresh.caption" = "Refrescar infraestructura"; +"profile.items.category.caption" = "Categoría"; +"profile.items.only_shows_favorites.caption" = "Mostrar solo ubicaciones favoritas"; +"profile.items.vpn_survives_sleep.caption" = "Mantener en modo inactivo"; +"profile.items.vpn_resolves_hostname.caption" = "Resolver hostname del servidor"; +"profile.items.reconnect.caption" = "Reconectar"; + +"profile.alerts.rename.title" = "Renombrar perfil"; +"profile.alerts.reconnect_vpn.message" = "Quieres reconectarte al VPN?"; +"profile.alerts.test_connectivity.title" = "Conectividad"; +"profile.alerts.test_connectivity.messages.success" = "Tu dispositivo está conectado en Internet!"; +"profile.alerts.test_connectivity.messages.failure" = "Tu dispositivo no tiene conectividad Internet, por favor revisa los parámetros de tu perfil."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Ubicación"; +"provider.location.sections.empty_favorites.footer" = "Desliza a la izquierda de una ubicación para agregarla o quitarla de los Favoritos."; +"provider.location.actions.favorite" = "Favorita"; +"provider.location.actions.unfavorite" = "No favorita"; + +"provider.preset.title" = "Ajuste"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Cuenta"; +"account.sections.credentials.header" = "Credenciales"; +"account.sections.registration.footer" = "Obten una cuenta en la web de %@."; +"account.items.username.caption" = "Usuario"; +"account.items.username.placeholder" = "usuario"; +"account.items.password.caption" = "Contraseña"; +"account.items.password.placeholder" = "secreto"; +"account.items.open_guide.caption" = "Mira tus credenciales"; +"account.items.signup.caption" = "Registrarse con %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Usa tus credenciales de la web %@."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Usa tus credenciales de servicio %@, que pueden diferir de las credenciales de la web."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Usa tus credenciales de la web %@. Normalmente tu usuario es numérico (sin espacios)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Usa tus credenciales de la web %@. Normalmente tu usuario es tu e-mail."; +"account.sections.guidance.footer.infrastructure.pia" = "Usa tus credenciales de la web %@. Normalmente tu usuario es numérico con un prefijo \"p\"."; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Encuentra tus credenciales %@ en la sección \"Account > OpenVPN / IKEv2 Username\" de la web."; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Usa tus credenciales de la web %@. Normalmente tu usuario es tu e-mail."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Usa tus credenciales de la web %@. Normalmente tu usuario es tu e-mail."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Encuentra tus credenciales %@ en el \"OpenVPN Config Generator\" en la web."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Peer"; +"endpoint.wireguard.items.preshared_key.caption" = "Clave previamente compartida"; +"endpoint.wireguard.items.allowed_ip.caption" = "IP permitida"; + +"endpoint.advanced.title" = "Detalles técnicos"; +"endpoint.advanced.openvpn.sections.communication.header" = "Comunicación"; +"endpoint.advanced.openvpn.sections.reset.footer" = "Si acabaste estropeando tu conectividad tras cambiar los parámetros de comunicación, pulsa para volver a la configuración inicial."; +"endpoint.advanced.openvpn.sections.compression.header" = "Compresión"; +"endpoint.advanced.openvpn.sections.network.header" = "Red"; +"endpoint.advanced.openvpn.sections.other.header" = "Otro"; +"endpoint.advanced.openvpn.items.route.caption" = "Ruta"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Cifrado"; +"endpoint.advanced.openvpn.items.digest.caption" = "Autentificación"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Incluida"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Marco"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Algoritmo"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "No soportado"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Resetear configuración"; +"endpoint.advanced.openvpn.items.client.caption" = "Certificado"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Clave"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Verificado"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "No verificado"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Envoltorio"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Autentificado"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Cifrado"; +"endpoint.advanced.openvpn.items.eku.caption" = "Verificación extendida"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d segundos"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "Renegociación"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "después de %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Aleatorizar destino"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Ajustes de red"; +"network_settings.sections.choices.header" = "Customizar"; +"network_settings.gateway.title" = "Puerta de enlace"; +"network_settings.proxy.items.bypass_domains.caption" = "Dominios ignorados"; +"network_settings.items.add_dns_server.caption" = "Añadir dirección"; +"network_settings.items.add_dns_domain.caption" = "Añadir dominio"; +"network_settings.items.proxy_bypass.caption" = "Dominio ignorado"; +"network_settings.items.add_proxy_bypass.caption" = "Añadir dominio ignorado"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Redes de confianza"; +"on_demand.sections.policy.footer" = "Entrando en una red de confianza, normalmente el VPN es cerrado y mantenido desconectado. Deshabilitar esta opción para no forzar este modo."; +"on_demand.items.add_ssid.caption" = "Añadir Wi-Fi"; +"on_demand.items.active.caption" = "Confianza"; +"on_demand.items.mobile.caption" = "Red móvil"; +"on_demand.items.ethernet.caption" = "Confiar en conexiones cableadas"; +"on_demand.items.ethernet.description" = "Activa esta opción para confiar en cualquier conexión cableada."; +"on_demand.items.policy.caption" = "Red de confianza deshabilita el VPN"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Diagnósticos"; +"diagnostics.sections.debug_log.footer" = "El estado de ocultación será efectivo tras reconectar. Los datos de red son hostnames, direcciones IP, routing, SSID. Las credenciales y las claves privadas no son registrados a pesar."; +"diagnostics.items.server_configuration.caption" = "Configuración del servidor"; +"diagnostics.items.masks_private_data.caption" = "Ocultar datos de red"; +"diagnostics.items.report_issue.caption" = "Reportar problema de conectividad"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "Para resetear el registro de debug y aplicar la nueva preferencia de ocultación, debes reconectarte al VPN."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Registro de debug"; +"debug_log.buttons.copy" = "Copiar"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Reportar incidencia"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Añadir atajo"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "Móvil"; +"shortcuts.add.items.connect.caption" = "Conectar a"; +"shortcuts.add.items.enable_vpn.caption" = "Habilitar VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Deshabilitar VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Añadir Wi-Fi de confianza"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Borrar Wi-Fi de confianza"; +"shortcuts.add.items.trust_cellular.caption" = "Añadir red móvil de confianza"; +"shortcuts.add.items.untrust_cellular.caption" = "Borrar red móvil de confianza"; +"shortcuts.add.alerts.no_profiles.message" = "No hay ningún perfil al que conectarse."; + +"shortcuts.edit.title" = "Gestionar atajos"; +"shortcuts.edit.sections.all.header" = "Atajos existentes"; +"shortcuts.edit.items.add_shortcut.caption" = "Añadir atajo"; + +// MARK: PaywallView + +"paywall.title" = "Comprar"; +"paywall.sections.products.footer" = "Cada producto es una compra única y no recurrente. La compra de un proveedor no incluye una suscripción al servicio."; +"paywall.items.loading.caption" = "Cargando productos"; +"paywall.items.full_version.extra_description" = "Todos los proveedores (incluye los futuros)\n%@"; +"paywall.items.restore.title" = "Restaurar compras"; +"paywall.items.restore.description" = "Si compraste esta aplicación o funcionalidad anteriormente, puedes restaurar tus compras y esta pantalla no volverá a aparecer."; + +// MARK: DonateView + +"donate.title" = "Donar"; +"donate.sections.one_time.header" = "Única"; +"donate.sections.one_time.footer" = "Si te gusta mi trabajo, aquí puedes colaborar con una donación.\n\nSólo se te cobrará una vez por donación, y puedes donar las veces que quieras."; +"donate.items.loading.caption" = "Cargando donaciones"; +"donate.items.purchasing.caption" = "Efectuando donación"; +"donate.alerts.purchase.success.title" = "Muchas gracias"; +"donate.alerts.purchase.success.message" = "Ésto significa mucho para mí y espero sinceramente que sigas usando y promoviendo esta aplicación."; +"donate.alerts.purchase.failure.message" = "Imposible completar la donación, por favor vuelve a intentarlo. %@"; + +// MARK: AboutView + +"about.title" = "Información"; +"about.sections.web.header" = "Web"; +"about.sections.share.header" = "Compartir"; +"about.items.credits.caption" = "Créditos"; +"about.items.website.caption" = "Página de inicio"; +"about.items.disclaimer.caption" = "Aviso legal"; +"about.items.privacy_policy.caption" = "Política de privacidad"; +"about.items.share_twitter.caption" = "Enviar un Tweet!"; +"about.items.share_generic.caption" = "Invitar a un amig@"; + +// MARK: AboutView -> VersionView + +"version.title" = "Versión"; +"version.labels.intro" = "Passepartout y TunnelKit están escritos y son mantenidos por Davide De Rosa (keeshux).\n\nEl código de Passepartout y TunnelKit es público y está disponible en GitHub bajo la GPLv3, encontrarás enlaces en la página de inicio.\n\nPassepartout es un cliente no oficial y no es afiliado de OpenVPN Inc."; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Créditos"; +"credits.sections.licenses.header" = "Licencias"; +"credits.sections.notices.header" = "Avisos"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Preferencias"; +"preferences.sections.general.header" = "General"; +"preferences.items.launches_on_login.caption" = "Iniciar al iniciar sesión"; +"preferences.items.launches_on_login.footer" = "Activa esta opción para que la aplicación se inicie automáticamente al iniciar o al iniciar sesión."; +"preferences.items.confirm_quit.caption" = "Confirmar salir"; +"preferences.items.confirm_quit.footer" = "Activa esta opción para que se muestre una alerta de confirmación al salir."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Mostrar"; +"menu.switch_profile.title" = "Perfil activo"; +"menu.active_profile.title.none" = "Ningún perfil activo"; +"menu.active_profile.items.customize.title" = "Personalizar..."; +"menu.active_profile.messages.missing_credentials" = "Ninguna cuenta configurada"; +"menu.organizer.title" = "Organizador"; +"menu.preferences.title" = "Preferencias"; +"menu.support.title" = "Soporte"; +"menu.quit.title" = "Salir de %@"; +"menu.quit.messages.confirm" = "Si la VPN está habilitada, seguirá funcionando en segundo plano. ¿Deseas salir?"; diff --git a/Passepartout/App/Shared/L10n/fr.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/fr.lproj/Localizable.strings new file mode 100644 index 00000000..63ee6bbd --- /dev/null +++ b/Passepartout/App/Shared/L10n/fr.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Annuler"; +"global.strings.next" = "Suivant"; +/* MARK: Global */ +"global.strings.ok" = "OK"; +"global.strings.save" = "Enregistrer"; +"global.strings.rename" = "Renommer"; +"global.strings.add" = "Ajouter"; +"global.strings.default" = "Default"; +"global.strings.name" = "Nom"; +"global.strings.address" = "Adresse"; +"global.strings.addresses" = "Adresses"; +"global.strings.port" = "Port"; +"global.strings.protocol" = "Protocole"; +"global.strings.protocols" = "Protocols"; +"global.strings.enabled" = "Activer"; +"global.strings.disabled" = "Désactiver"; +"global.strings.none" = "Aucun"; +"global.strings.automatic" = "Automatique"; +"global.strings.manual" = "Manuel"; +"global.strings.encryption" = "Cryptage"; +"global.strings.reconnect" = "Reconnecter"; +"global.strings.servers" = "Serveurs"; +"global.strings.domain" = "Domaine"; +"global.strings.domains" = "Domaines"; +"global.strings.proxy" = "Proxy"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "Interface"; +"global.strings.private_key" = "Clé privée"; +"global.strings.public_key" = "Clé publique"; +"global.strings.endpoint" = "Point terminal"; +"global.strings.keepalive" = "Garder actif"; +"global.strings.advanced" = "Avancé"; +"global.strings.translations" = "Traductions"; + +"global.messages.email_not_configured" = "Aucun compte courriel n'est configuré."; +"global.messages.share" = "Passepartout est un client OpenVPN simple d'utilisation et open source pour iOS et macOS"; + +"global.placeholders.profile_name" = "Mon profile"; + +"global.errors.missing_profile" = "Profil manquant"; +"global.errors.missing_account" = "Compte manquant"; +"global.errors.missing_provider_server" = "Serveur manquant"; +"global.errors.missing_provider_preset" = "Préréglage manquant"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Connection..."; +"tunnelkit.vpn.active" = "Actif"; +"tunnelkit.vpn.disconnecting" = "Déconnection..."; +"tunnelkit.vpn.inactive" = "Inactif"; +"tunnelkit.vpn.disabled" = "Désactivé"; +"tunnelkit.vpn.unused" = "Désactivé"; + +"tunnelkit.errors.vpn.timeout" = "Délais dépassé"; +"tunnelkit.errors.vpn.dns" = "Échec DNS"; +"tunnelkit.errors.vpn.auth" = "Échec Auth"; +"tunnelkit.errors.vpn.tls" = "Échec TLS"; +"tunnelkit.errors.vpn.encryption" = "Échec du cryptage"; +"tunnelkit.errors.vpn.compression" = "Compression non supportée"; +"tunnelkit.errors.vpn.network" = "Réseau modifié"; +"tunnelkit.errors.vpn.routing" = "Routage manquant"; +"tunnelkit.errors.vpn.gateway" = "Aucune passerelle"; +"tunnelkit.errors.vpn.shutdown" = "Arrêt du serveur"; + +"tunnelkit.errors.parsing" = "Incapable d'analyser le fichier de configuration fournis. (%@)."; +"tunnelkit.errors.openvpn.malformed" = "Le fichier de configuration contient une mauvaise option.(%@)."; +"tunnelkit.errors.openvpn.required_option" = "Le fichier de configuration ne contient pas une option requise. (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "Le fichier de configuration contient une option non supportée (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "Le fichier de configuration est adéquat, mais contient une option potentiellement non supportée. (%@).\n\nLa connection peut être perdue selon les paramètres du serveur."; +"tunnelkit.errors.openvpn.passphrase_required" = "Veuillez entrer le mot de passe d'encryption."; +"tunnelkit.errors.openvpn.decryption" = "Le fichier de configuration contient une clé privée encryptée et n'a pas été décryptée. Veuillez revérifier votre mot de passe."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "Venez me regarder faire passer Passepartout en direct sur Twitch, rejoignez le chat pour interagir et contribuer!"; +"organizer.sections.siri.footer" = "Obtenez de l'aide de Siri pour accélérer vos intéractions les plus courantes avec l'app."; +"organizer.sections.support.header" = "Support"; +"organizer.items.follow_twitch.caption" = "Regardez Passepartout sur Twitch"; +"organizer.items.profile.value.current" = "En utilisation"; +"organizer.items.siri_shortcuts.caption" = "Gérer les raccourcis"; +"organizer.items.join_community.caption" = "Rejoindre la communauté"; +"organizer.items.write_review.caption" = "Écrire un avis"; +"organizer.items.donate.caption" = "Faire un don"; +"organizer.items.github_sponsors.caption" = "Me parrainer chez GitHub"; +"organizer.items.translate.caption" = "Offre de traduction"; +"organizer.items.about.caption" = "À propos %@"; +"organizer.items.uninstall.caption" = "Supprimer la configuration VPN"; +"organizer.items.add_provider.caption" = "Ajouter un nouveau fournisseur"; +"organizer.items.add_host.caption" = "Ajouter de Fichiers"; + +"organizer.alerts.reddit.message" = "Saviez-vous que Passepartout a un subreddit? Souscrivez pour les mises à jour ou discuter des problèmes, caractéristiques, nouvelles plateformes ou quoi que ce soit.\n\nC'est aussi une très bonne façon de démontrer votre enthousiasme envers le projet."; +"organizer.alerts.reddit.buttons.subscribe" = "Souscrivez maintenant!"; +"organizer.alerts.reddit.buttons.remind" = "Me rappeler plus tard"; +"organizer.alerts.reddit.buttons.never" = "Ne pas me redemander"; + +"organizer.alerts.uninstall_vpn.message" = "Voulez-vous vraiment effacer la configuration VPN de vos paramètres? Ceci peux fixer certains VPN en arrêt et n'affectera pas vos profiles de fournisseurs et hôtes."; +"organizer.alerts.remove_profile.title" = "Supprimer le profil"; +"organizer.alerts.remove_profile.message" = "Voulez-vous vraiment supprimer le profil %@ ?"; + +"organizer.menus.add_profile.imported" = "Ajouter %@"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "Nouveau profil"; +"add_profile.shared.views.existing.header" = "Profiles existants"; +"add_profile.shared.alerts.overwrite.message" = "Un profile avec ce même nom existe déjà. Le remplacer ?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Saisir le mot de passe"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Fournisseurs"; +"add_profile.provider.sections.vpn.footer" = "Ici vous pouvez trousers certains fournisseurs avec des profiles déjà configurés."; +"add_profile.provider.items.update_list" = "Actualiser la liste"; +"add_profile.provider.errors.no_default_server" = "Aucun serveur trouvé"; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Fournisseur"; + +"profile.welcome.message" = "Bienvenue à Passepartout!\n\nUtilisez l'organiseur pour ajouter un nouveau profile."; + +"profile.sections.vpn.footer" = "La connection sera établie lorsque nécessaire."; +"profile.sections.status.header" = "Connection"; +"profile.sections.configuration.header" = "Configuration"; +"profile.sections.provider_infrastructure.footer" = "Mis à jour : %@."; +"profile.sections.vpn_survives_sleep.footer" = "Désactiver pour augmenter l'autonomie de la batterie, au dépends de la rapidité au réveil pour la reconnection."; +"profile.sections.vpn_resolves_hostname.footer" = "Préféré dans la plus part des réseaux et requis dans certains réseaux IPv6. Désactiver lorsque le DNS est bloqué ou pour augmenter la rapidité des négociations lorsque le DNS est lent à répondre."; +"profile.sections.feedback.header" = "Commentaires"; +"profile.items.use_profile.caption" = "Utiliser ce profile"; +"profile.items.vpn_service.caption" = "Activer"; +"profile.items.vpn.turn_on.caption" = "Activer VPN"; +"profile.items.vpn.turn_off.caption" = "Désactiver VPN"; +"profile.items.connection_status.caption" = "Statut"; +"profile.items.data_count.caption" = "Échanger les données"; +"profile.items.provider.refresh.caption" = "Rafraîchir l'infrastructure"; +"profile.items.category.caption" = "Catégorie"; +"profile.items.only_shows_favorites.caption" = "Afficher uniquement les emplacements favoris"; +"profile.items.vpn_survives_sleep.caption" = "Garder actif lors de la veille"; +"profile.items.vpn_resolves_hostname.caption" = "Résoudre le nom d'hôte du serveur"; +"profile.items.reconnect.caption" = "Reconnecter"; + +"profile.alerts.rename.title" = "Renommer le profile"; +"profile.alerts.reconnect_vpn.message" = "Voulez-vous reconnecter le VPN?"; +"profile.alerts.test_connectivity.title" = "Connections"; +"profile.alerts.test_connectivity.messages.success" = "Votre appareil est connecté à Internet!"; +"profile.alerts.test_connectivity.messages.failure" = "Votre appareil n'a aucune connection Invernet, veuillez vérifier vos paramètres de profile."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Locallisation"; +"provider.location.sections.empty_favorites.footer" = "Glissez vers la gauche d'un item pour l'ajouter ou le retirer des Favoris."; +"provider.location.actions.favorite" = "Favoris"; +"provider.location.actions.unfavorite" = "Retirer des Favoris"; + +"provider.preset.title" = "Préréglage"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Compte"; +"account.sections.credentials.header" = "Indetifiants"; +"account.sections.registration.footer" = "Allez créer un compte sur le site %@."; +"account.items.username.caption" = "Nom d'utilisateur"; +"account.items.username.placeholder" = "nom d'utilisateur"; +"account.items.password.caption" = "Mot de passe"; +"account.items.password.placeholder" = "secret"; +"account.items.open_guide.caption" = "Voir vos identifiants"; +"account.items.signup.caption" = "S'inscrire avec %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Utilisez votre identifiants web de %@."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Utilisez vos informations d'identification de service %@, qui peuvent différer des informations d'identification du web."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Utilisez votre identifiants web de %@. Votre nom d'utilisateur est normalement numérique (sans espaces)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Utilisez votre identifiants web de %@. Votre nom d'utilisateur est normalement votre courriel."; +"account.sections.guidance.footer.infrastructure.pia" = "Utilisez votre identifiants web de %@. Votre nom d'utilisateur est normalement numérique avec le préfixe \"p\" "; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Trouvez votre identifiant web %@ dans la section du site web \"Account > OpenVPN / IKEv2 nom d'utilisateur\" "; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Utilisez votre identifiants web de %@. Votre nom d'utilisateur est normalement votre courriel."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Utilisez votre identifiants web de %@. Votre nom d'utilisateur est normalement votre courriel."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Trouver votre identifiant %@ dans la section web Générateur de configuration OpenVPN."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Peer"; +"endpoint.wireguard.items.preshared_key.caption" = "Clé pré-partagée"; +"endpoint.wireguard.items.allowed_ip.caption" = "IP autorisée"; + +"endpoint.advanced.title" = "Détails techniques"; +"endpoint.advanced.openvpn.sections.communication.header" = "Communications"; +"endpoint.advanced.openvpn.sections.reset.footer" = "Si vous obtenez une connection erronnée après le changement des parameters de communication, tapotez pour revenir à la configuration initiale."; +"endpoint.advanced.openvpn.sections.compression.header" = "Compression"; +"endpoint.advanced.openvpn.sections.network.header" = "Réseau"; +"endpoint.advanced.openvpn.sections.other.header" = "Autre"; +"endpoint.advanced.openvpn.items.route.caption" = "Routage"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Cryptogramme"; +"endpoint.advanced.openvpn.items.digest.caption" = "Authentification"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Intégré"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Framing"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Algorithme"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "Non supporté"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Réinitialiser la configuration"; +"endpoint.advanced.openvpn.items.client.caption" = "Certificat"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Clé"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Verifié"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "Non vérifié"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Wrapping"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Authentification"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Cryptage"; +"endpoint.advanced.openvpn.items.eku.caption" = "Vérification étendue"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d secondes"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "Renégociation"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "aprè %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Extrémité aléatoire"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Paramètres réseaux"; +"network_settings.sections.choices.header" = "Écraser"; +"network_settings.gateway.title" = "Gateway"; +"network_settings.proxy.items.bypass_domains.caption" = "Outrepasser le domaine"; +"network_settings.items.add_dns_server.caption" = "Ajouter une adresse"; +"network_settings.items.add_dns_domain.caption" = "Ajouter un domaine"; +"network_settings.items.proxy_bypass.caption" = "Outrepasser le domaine"; +"network_settings.items.add_proxy_bypass.caption" = "Ajouter outrepasser le domaine"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Réseaux de confiance"; +"on_demand.sections.policy.footer" = "Lors d'une connection à un réseau de confiance, le VPN est normalement fermé. Désactivez cette option pour ne pas autoriser ce comportement."; +"on_demand.items.add_ssid.caption" = "Ajouter Wi-Fi"; +"on_demand.items.active.caption" = "Fiables"; +"on_demand.items.mobile.caption" = "Réseau cellulaire"; +"on_demand.items.ethernet.caption" = "Faire confiance aux connexions filaires"; +"on_demand.items.ethernet.description" = "Cochez pour faire confiance à toutes les connexions filaires."; +"on_demand.items.policy.caption" = "La confiance désactive le VPN"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Diagnostiques"; +"diagnostics.sections.debug_log.footer" = "Camouflage du status sera effectif après la reconnection. Les données réseaux sont les noms d'hôtes, adresses IP, routage, SSID. Les identifiants et clés privés ne sont pas enregistrés."; +"diagnostics.items.server_configuration.caption" = "Configuration serveur"; +"diagnostics.items.masks_private_data.caption" = "Masquer les données de réseau"; +"diagnostics.items.report_issue.caption" = "Rapporter un problème de connection"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "Pour bien réinitialiser le registre de diagnostique et appliquer les préférences de camouflage, vous devez vous reconnecter au VPN maintenant."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Journal de débogage"; +"debug_log.buttons.copy" = "Copier"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Rapporter un problème"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Ajouter un raccourcis"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "Cellulaire"; +"shortcuts.add.items.connect.caption" = "Connecter à"; +"shortcuts.add.items.enable_vpn.caption" = "Activer VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Désactiver VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Faire confiance au présent réseau Wi-Fi"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Retirer le présent réseau Wi-Fi des réseaux de confiance."; +"shortcuts.add.items.trust_cellular.caption" = "Faire confiance au présent réseau cellulaire"; +"shortcuts.add.items.untrust_cellular.caption" = "Retirer le présent réseau cellulaire des réseaux de confiance."; +"shortcuts.add.alerts.no_profiles.message" = "Il n'y a aucun profile pour se connecter."; + +"shortcuts.edit.title" = "Gérer les raccourcis"; +"shortcuts.edit.sections.all.header" = "Raccourcis existants"; +"shortcuts.edit.items.add_shortcut.caption" = "Ajouter un raccourcis"; + +// MARK: PaywallView + +"paywall.title" = "Acheter"; +"paywall.sections.products.footer" = "Chaque produit est un achat unique. Les achats n'incluent pas une souscription à un service de VPN."; +"paywall.items.loading.caption" = "Chargement des produits"; +"paywall.items.full_version.extra_description" = "Tous les fournisseurs (incluant les prochains)\n%@"; +"paywall.items.restore.title" = "Restaurer les achats"; +"paywall.items.restore.description" = "Si vous avez acheté l'application ou une fonctionnalité dans le passé, vous pouvez restaurer les achats et ce message ne s'affichera plus."; + +// MARK: DonateView + +"donate.title" = "Faire un don"; +"donate.sections.one_time.header" = "Une seule fois"; +"donate.sections.one_time.footer" = "Si vous voulez manifester votre gratitude envers mon travail bénévole, voici certains montants pour faire un don instantanément.\n\n Vous n'allez être chargé qu'une seule fois par don et vous pouvez faire un don plus d'une fois."; +"donate.items.loading.caption" = "Chargement des dons"; +"donate.items.purchasing.caption" = "Don en cours"; +"donate.alerts.purchase.success.title" = "Merci"; +"donate.alerts.purchase.success.message" = "Ceci signifie beaucoup pour moi et j'espère sincèrement que vous continuerez d'utiliser et de promouvoir cette app."; +"donate.alerts.purchase.failure.message" = "Impossible de faire le don. %@"; + +// MARK: AboutView + +"about.title" = "À propos"; +"about.sections.web.header" = "Web"; +"about.sections.share.header" = "Partager"; +"about.items.credits.caption" = "Crédits"; +"about.items.website.caption" = "Page d'accueil"; +"about.items.disclaimer.caption" = "Avis de non-responsabilité"; +"about.items.privacy_policy.caption" = "Politique de la vie privée"; +"about.items.share_twitter.caption" = "Tweetez!"; +"about.items.share_generic.caption" = "Inviter un amis"; + +// MARK: AboutView -> VersionView + +"version.title" = "Version"; +"version.labels.intro" = "Passepartout et TunnelKit sont codés et maintenu par Davide De Rosa (keeshux).\n\nLe code source de Passepartout et TunnelKit est publiquement disponible sur GitHub sous license GPLv3, vous pouvez trouver les liens sur la page d'accueil.\n\nPassepartout est un client non-officiel et n'est aucunement affilié avec OpenVPN Inc."; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Crédits"; +"credits.sections.licenses.header" = "Licenses"; +"credits.sections.notices.header" = "Préavis"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Préférences"; +"preferences.sections.general.header" = "Général"; +"preferences.items.launches_on_login.caption" = "Lancer au démarrage"; +"preferences.items.launches_on_login.footer" = "Cochez pour lancer automatiquement l'application à la connexion ou au démarrage."; +"preferences.items.confirm_quit.caption" = "Notification de sortie"; +"preferences.items.confirm_quit.footer" = "Cochez pour recevoir une demande de confirmation lorsque vous quittez."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Afficher"; +"menu.switch_profile.title" = "Profil actif"; +"menu.active_profile.title.none" = "Pas de profil actif"; +"menu.active_profile.items.customize.title" = "Personnaliser..."; +"menu.active_profile.messages.missing_credentials" = "Pas de compte configuré"; +"menu.organizer.title" = "Organisateur"; +"menu.preferences.title" = "Préférences"; +"menu.support.title" = "Assistance"; +"menu.quit.title" = "Quitter %@"; +"menu.quit.messages.confirm" = "S'il est activé, le VPN fonctionnera en tâche de fond. Voulez-vous quitter ?"; diff --git a/Passepartout/App/Shared/L10n/it.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/it.lproj/Localizable.strings new file mode 100644 index 00000000..02a94b40 --- /dev/null +++ b/Passepartout/App/Shared/L10n/it.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Annulla"; +"global.strings.next" = "Avanti"; +/* MARK: Global */ +"global.strings.ok" = "OK"; +"global.strings.save" = "Salva"; +"global.strings.rename" = "Rinomina"; +"global.strings.add" = "Aggiungi"; +"global.strings.default" = "Default"; +"global.strings.name" = "Nome"; +"global.strings.address" = "Indirizzo"; +"global.strings.addresses" = "Indirizzi"; +"global.strings.port" = "Porta"; +"global.strings.protocol" = "Protocollo"; +"global.strings.protocols" = "Protocolli"; +"global.strings.enabled" = "Abilitato"; +"global.strings.disabled" = "Disabilitato"; +"global.strings.none" = "Nessuno"; +"global.strings.automatic" = "Automatico"; +"global.strings.manual" = "Manuale"; +"global.strings.encryption" = "Crittografia"; +"global.strings.reconnect" = "Riconnetti"; +"global.strings.servers" = "Server"; +"global.strings.domain" = "Dominio"; +"global.strings.domains" = "Dominii"; +"global.strings.proxy" = "Proxy"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "Interfaccia"; +"global.strings.private_key" = "Chiave privata"; +"global.strings.public_key" = "Chiave pubblica"; +"global.strings.endpoint" = "Endpoint"; +"global.strings.keepalive" = "Keep-alive"; +"global.strings.advanced" = "Avanzate"; +"global.strings.translations" = "Traduzioni"; + +"global.messages.email_not_configured" = "Nessun account e-mail configurato."; +"global.messages.share" = "Passepartout è un client OpenVPN user-friendly ed open source per iOS e macOS"; + +"global.placeholders.profile_name" = "Il mio profilo"; + +"global.errors.missing_profile" = "Profilo mancante"; +"global.errors.missing_account" = "Credenziali mancanti"; +"global.errors.missing_provider_server" = "Server mancante"; +"global.errors.missing_provider_preset" = "Preset mancante"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Connettendo"; +"tunnelkit.vpn.active" = "Attiva"; +"tunnelkit.vpn.disconnecting" = "Disconnettendo"; +"tunnelkit.vpn.inactive" = "Inattiva"; +"tunnelkit.vpn.disabled" = "Disabilitata"; +"tunnelkit.vpn.unused" = "Spento"; + +"tunnelkit.errors.vpn.timeout" = "Timeout"; +"tunnelkit.errors.vpn.dns" = "DNS fallito"; +"tunnelkit.errors.vpn.auth" = "Autenticazione fallita"; +"tunnelkit.errors.vpn.tls" = "TLS fallito"; +"tunnelkit.errors.vpn.encryption" = "Crittografia fallita"; +"tunnelkit.errors.vpn.compression" = "Compressione non supportata"; +"tunnelkit.errors.vpn.network" = "Rete cambiata"; +"tunnelkit.errors.vpn.routing" = "Routing mancante"; +"tunnelkit.errors.vpn.gateway" = "Nessun gateway"; +"tunnelkit.errors.vpn.shutdown" = "Server arrestato"; + +"tunnelkit.errors.parsing" = "Impossibile processare il file di configurazione specificato (%@)."; +"tunnelkit.errors.openvpn.malformed" = "La configurazione contiene un'opzione malformata (%@)."; +"tunnelkit.errors.openvpn.required_option" = "La configurazione non contiene un'opzione obbligatoria (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "La configurazione contiene un'opzione non supportata (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "La configurazione è corretto ma contiene un'opzione potenzialmente non supportata (%@).\n\nLa connettività potrebbe fallire a seconda delle impostazioni del server."; +"tunnelkit.errors.openvpn.passphrase_required" = "Per favore inserisci la passphrase di criptazione."; +"tunnelkit.errors.openvpn.decryption" = "La configurazione contiene una chiave privata criptata e non è stato possibile decriptarla. Controlla la tua passphrase."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "Vieni a vedermi creare Passepartout in diretta su Twitch, unisciti alla chat per interagire e contribuire!"; +"organizer.sections.siri.footer" = "Chiedi aiuto a Siri per velocizzare le tue interazioni più frequenti con l'app."; +"organizer.sections.support.header" = "Supporto"; +"organizer.items.follow_twitch.caption" = "Guarda Passepartout su Twitch"; +"organizer.items.profile.value.current" = "In uso"; +"organizer.items.siri_shortcuts.caption" = "Gestisci comandi rapidi"; +"organizer.items.join_community.caption" = "Entra nella community"; +"organizer.items.write_review.caption" = "Scrivi una recensione"; +"organizer.items.donate.caption" = "Fai una donazione"; +"organizer.items.github_sponsors.caption" = "Supportami su GitHub"; +"organizer.items.translate.caption" = "Offri una traduzione"; +"organizer.items.about.caption" = "Informazioni su %@"; +"organizer.items.uninstall.caption" = "Rimuovi configurazione VPN"; +"organizer.items.add_provider.caption" = "Aggiungi provider"; +"organizer.items.add_host.caption" = "Aggiungi da Files"; + +"organizer.alerts.reddit.message" = "Sapevi che Passepartout ha un subreddit? Iscriviti per aggiornamenti o per discutere problemi, aggiunte, nuove piattaforme o qualunque cosa tu voglia.\n\nÈ anche un ottimo modo per dimostrare che hai a cuore questo progetto."; +"organizer.alerts.reddit.buttons.subscribe" = "Iscriviti ora!"; +"organizer.alerts.reddit.buttons.remind" = "Ricordami più tardi"; +"organizer.alerts.reddit.buttons.never" = "Non chiedere più"; + +"organizer.alerts.uninstall_vpn.message" = "Vuoi veramente cancellare la configurazione VPN dalle impostazioni del tuo dispositivo? Quest'azione potrebbe risolvere alcuni stati erronei della VPN e non altererà i tuoi provider e i tuoi host."; +"organizer.alerts.remove_profile.title" = "Cancella profilo"; +"organizer.alerts.remove_profile.message" = "Sei sicuro di voler cancellare il profilo %@?"; + +"organizer.menus.add_profile.imported" = "Aggiungi %@"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "Nuovo profilo"; +"add_profile.shared.views.existing.header" = "Profili esistenti"; +"add_profile.shared.alerts.overwrite.message" = "Esiste già un profilo con lo stesso nome. Sostituire?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Inserisci passphrase"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Provider"; +"add_profile.provider.sections.vpn.footer" = "Qui trovi alcuni provider con configurazioni precompilate."; +"add_profile.provider.items.update_list" = "Aggiorna lista"; +"add_profile.provider.errors.no_default_server" = "Nessun server disponibile."; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Provider"; + +"profile.welcome.message" = "Benvenuto in Passepartout!\n\nUsa il menu per aggiungere un nuovo profilo."; + +"profile.sections.vpn.footer" = "La connessione sarà stabilita ogni volta che è necessario."; +"profile.sections.status.header" = "Connessione"; +"profile.sections.configuration.header" = "Configurazione"; +"profile.sections.provider_infrastructure.footer" = "Ultimo aggiornamento: %@."; +"profile.sections.vpn_survives_sleep.footer" = "Disabilita per migliorare il consumo della batteria, a discapito di rallentamenti occasionali causati dalle riconnessioni."; +"profile.sections.vpn_resolves_hostname.footer" = "Preferibile nella maggior parte delle reti e necessario in alcune reti IPv6. Disabilita dove il DNS è bloccato, o per velocizzare la negoziazione quando il DNS tarda a rispondere."; +"profile.sections.feedback.header" = "Feedback"; +"profile.items.use_profile.caption" = "Usa questo profilo"; +"profile.items.vpn_service.caption" = "Abilitato"; +"profile.items.vpn.turn_on.caption" = "Abilita VPN"; +"profile.items.vpn.turn_off.caption" = "Disabilita VPN"; +"profile.items.connection_status.caption" = "Stato"; +"profile.items.data_count.caption" = "Dati scambiati"; +"profile.items.provider.refresh.caption" = "Aggiorna infrastruttura"; +"profile.items.category.caption" = "Categoria"; +"profile.items.only_shows_favorites.caption" = "Mostra solo le posizioni preferite"; +"profile.items.vpn_survives_sleep.caption" = "Mantieni attivo in sleep"; +"profile.items.vpn_resolves_hostname.caption" = "Risolvi hostname del server"; +"profile.items.reconnect.caption" = "Riconnetti"; + +"profile.alerts.rename.title" = "Rinomina profilo"; +"profile.alerts.reconnect_vpn.message" = "Vuoi riconnetterti alla VPN?"; +"profile.alerts.test_connectivity.title" = "Connettività"; +"profile.alerts.test_connectivity.messages.success" = "Il tuo dispositivo è connesso a Internet!"; +"profile.alerts.test_connectivity.messages.failure" = "Il tuo dispositivo non è connesso a Internet, per favore controlla i parametri del tuo profilo."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Regione"; +"provider.location.sections.empty_favorites.footer" = "Scorri a sinistra su una regione per aggiungerla o rimuoverla dai Preferiti."; +"provider.location.actions.favorite" = "Preferita"; +"provider.location.actions.unfavorite" = "Non preferita"; + +"provider.preset.title" = "Preset"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Account"; +"account.sections.credentials.header" = "Credenziali"; +"account.sections.registration.footer" = "Registra un account sul sito di %@."; +"account.items.username.caption" = "Username"; +"account.items.username.placeholder" = "username"; +"account.items.password.caption" = "Password"; +"account.items.password.placeholder" = "segreto"; +"account.items.open_guide.caption" = "Vedi le tue credenziali"; +"account.items.signup.caption" = "Registrati con %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Usa le credenziali del sito di %@."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Usa le tue credenziali del servizio %@, che potrebbero differire dalle credenziali del sito web."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Usa le credenziali del sito di %@. Il tuo username è generalmente numerico (senza spazi)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Usa le credenziali del sito di %@. Il tuo username è generalmente la tua e-mail."; +"account.sections.guidance.footer.infrastructure.pia" = "Usa le credenziali del sito di %@. Il tuo username è generalmente numerico con un prefisso \"p\"."; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Trova le tue credenziali nella sezione \"Account > OpenVPN / IKEv2 Username\" del sito di %@."; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Usa le credenziali del sito di %@. Il tuo username è generalmente la tua e-mail."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Usa le credenziali del sito di %@. Il tuo username è generalmente la tua e-mail."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Trova le tue credenziali nell'OpenVPN Config Generator sul sito di %@."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Peer"; +"endpoint.wireguard.items.preshared_key.caption" = "Chiave condivisa"; +"endpoint.wireguard.items.allowed_ip.caption" = "IP ammesso"; + +"endpoint.advanced.title" = "Dettagli tecnici"; +"endpoint.advanced.openvpn.sections.communication.header" = "Comunicazione"; +"endpoint.advanced.openvpn.sections.reset.footer" = "Se ti trovi con una connettività compromessa dopo aver cambiato i parametri di comunicazione, tocca per tornare alla configurazione originale."; +"endpoint.advanced.openvpn.sections.compression.header" = "Compressione"; +"endpoint.advanced.openvpn.sections.network.header" = "Rete"; +"endpoint.advanced.openvpn.sections.other.header" = "Altro"; +"endpoint.advanced.openvpn.items.route.caption" = "Rotta"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Cifratura"; +"endpoint.advanced.openvpn.items.digest.caption" = "Autenticazione"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Incorporata"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Framing"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Algoritmo"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "Non supportato"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Ripristina configurazione"; +"endpoint.advanced.openvpn.items.client.caption" = "Certificato"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Chiave"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Verificato"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "Non verificato"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Wrapping"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Autenticazione"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Criptazione"; +"endpoint.advanced.openvpn.items.eku.caption" = "Verifica estesa"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d secondi"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "Rinegoziazione"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "dopo %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Endpoint casuale"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Impostazioni di rete"; +"network_settings.sections.choices.header" = "Personalizza"; +"network_settings.gateway.title" = "Gateway predefinito"; +"network_settings.proxy.items.bypass_domains.caption" = "Dominii ignorati"; +"network_settings.items.add_dns_server.caption" = "Aggiungi indirizzo"; +"network_settings.items.add_dns_domain.caption" = "Aggiungi dominio"; +"network_settings.items.proxy_bypass.caption" = "Dominio ignorato"; +"network_settings.items.add_proxy_bypass.caption" = "Aggiungi dominio ignorato"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Reti sicure"; +"on_demand.sections.policy.footer" = "Entrando in una rete sicura, normalmente la VPN viene spenta e mantenuta disconnessa. Disabilita quest'opzione per non imporre questo comportamento."; +"on_demand.items.add_ssid.caption" = "Aggiungi Wi-Fi"; +"on_demand.items.active.caption" = "Sicura"; +"on_demand.items.mobile.caption" = "Rete cellulare"; +"on_demand.items.ethernet.caption" = "Connessioni cablate sicure"; +"on_demand.items.ethernet.description" = "Seleziona per considerare sicura qualsiasi rete cablata."; +"on_demand.items.policy.caption" = "Spegni VPN in rete sicura"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Diagnostica"; +"diagnostics.sections.debug_log.footer" = "Il mascheramento sarà effettivo dopo una riconnessione. I dati di rete sono hostname, indirizzi IP, routing, SSID. Credenziali e chiavi private non sono registrati in ogni caso."; +"diagnostics.items.server_configuration.caption" = "Configurazione del server"; +"diagnostics.items.masks_private_data.caption" = "Maschera dati rete"; +"diagnostics.items.report_issue.caption" = "Segnala problema connettività"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "Per azzerare il debug log ed applicare la nuova preferenza di mascheramento, devi riconnetterti alla VPN."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Debug log"; +"debug_log.buttons.copy" = "Copia"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Segnala problema"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Aggiungi comando rapido"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "Cellulare"; +"shortcuts.add.items.connect.caption" = "Connetti a"; +"shortcuts.add.items.enable_vpn.caption" = "Abilita VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Disabilita VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Aggiungi Wi-Fi sicura"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Rimuovi Wi-Fi sicura"; +"shortcuts.add.items.trust_cellular.caption" = "Aggiungi rete mobile sicura"; +"shortcuts.add.items.untrust_cellular.caption" = "Rimuovi rete mobile sicura"; +"shortcuts.add.alerts.no_profiles.message" = "Non c'è nessun profilo a cui connettersi."; + +"shortcuts.edit.title" = "Gestisci comandi rapidi"; +"shortcuts.edit.sections.all.header" = "Comandi esistenti"; +"shortcuts.edit.items.add_shortcut.caption" = "Aggiungi comando rapido"; + +// MARK: PaywallView + +"paywall.title" = "Acquista"; +"paywall.sections.products.footer" = "Ogni prodotto è un acquisto unico e non ricorrente. L'acquisto di un provider non include una sottoscrizione."; +"paywall.items.loading.caption" = "Caricando prodotti"; +"paywall.items.full_version.extra_description" = "Tutti i provider (inclusi quelli futuri)\n%@"; +"paywall.items.restore.title" = "Ripristina acquisti"; +"paywall.items.restore.description" = "Se hai comprato quest'applicazione o funzionalità in precedenza, puoi ripristinare i tuoi acquisti in modo che questa schermata non compaia più."; + +// MARK: DonateView + +"donate.title" = "Donazione"; +"donate.sections.one_time.header" = "Unica"; +"donate.sections.one_time.footer" = "Se vuoi mostrare gratitudine per il mio lavoro a titolo gratuito, qui trovi varie somme da donare all'istante.\n\nLa donazione ti sarà addebitata solo una volta, e puoi effettuare più donazioni."; +"donate.items.loading.caption" = "Caricando donazioni"; +"donate.items.purchasing.caption" = "Effettuando donazione"; +"donate.alerts.purchase.success.title" = "Grazie"; +"donate.alerts.purchase.success.message" = "Questo significa molto per me e spero vivamente che tu continui ad usare e promuovere quest'applicazione."; +"donate.alerts.purchase.failure.message" = "Impossibile effettuare la donazione. %@"; + +// MARK: AboutView + +"about.title" = "Informazioni su"; +"about.sections.web.header" = "Web"; +"about.sections.share.header" = "Condividi"; +"about.items.credits.caption" = "Credits"; +"about.items.website.caption" = "Home page"; +"about.items.disclaimer.caption" = "Disclaimer"; +"about.items.privacy_policy.caption" = "Privacy policy"; +"about.items.share_twitter.caption" = "Manda un Tweet!"; +"about.items.share_generic.caption" = "Invita un amico"; + +// MARK: AboutView -> VersionView + +"version.title" = "Versione"; +"version.labels.intro" = "Passepartout e TunnelKit sono scritti e mantenuti da Davide De Rosa (keeshux).\n\nIl codice sorgente di Passepartout e TunnelKit è pubblicamente disponibile su GitHub in accordo con la GPLv3, puoi trovare i link nella home page.\n\nPassepartout è un client non ufficiale e non è affiliato ad OpenVPN Inc. in alcuna maniera."; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Credits"; +"credits.sections.licenses.header" = "Licenze"; +"credits.sections.notices.header" = "Notice"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Preferenze"; +"preferences.sections.general.header" = "Generale"; +"preferences.items.launches_on_login.caption" = "Apri al login"; +"preferences.items.launches_on_login.footer" = "Seleziona per aprire automaticamente l'app all'avvio o al login."; +"preferences.items.confirm_quit.caption" = "Conferma uscita"; +"preferences.items.confirm_quit.footer" = "Seleziona per confermare l'uscita dall'applicazione."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Mostra"; +"menu.switch_profile.title" = "Profilo attivo"; +"menu.active_profile.title.none" = "Nessun profilo attivo"; +"menu.active_profile.items.customize.title" = "Personalizza..."; +"menu.active_profile.messages.missing_credentials" = "Nessun account configurato"; +"menu.organizer.title" = "Organizer"; +"menu.preferences.title" = "Preferenze"; +"menu.support.title" = "Supporto"; +"menu.quit.title" = "Esci da %@"; +"menu.quit.messages.confirm" = "La VPN, se abilitata, continuerà ad essere attiva in background. Vuoi comunque uscire?"; diff --git a/Passepartout/App/Shared/L10n/nl.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/nl.lproj/Localizable.strings new file mode 100644 index 00000000..1d75ef4c --- /dev/null +++ b/Passepartout/App/Shared/L10n/nl.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Afbreken"; +"global.strings.next" = "Volgende"; +/* MARK: Global */ +"global.strings.ok" = "OK"; +"global.strings.save" = "Opslaan"; +"global.strings.rename" = "Hernoemen"; +"global.strings.add" = "Toevoegen"; +"global.strings.default" = "Default"; +"global.strings.name" = "Naam"; +"global.strings.address" = "Adress"; +"global.strings.addresses" = "Adressen"; +"global.strings.port" = "Port"; +"global.strings.protocol" = "Protocol"; +"global.strings.protocols" = "Protocollen"; +"global.strings.enabled" = "Ingeschakeld"; +"global.strings.disabled" = "Uitgeschakeld"; +"global.strings.none" = "Geen"; +"global.strings.automatic" = "Automatisch"; +"global.strings.manual" = "Handmatig"; +"global.strings.encryption" = "Versleuteling"; +"global.strings.reconnect" = "Opnieuw verbinden"; +"global.strings.servers" = "Servers"; +"global.strings.domain" = "Domein"; +"global.strings.domains" = "Domeinen"; +"global.strings.proxy" = "Proxy"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "Interface"; +"global.strings.private_key" = "Persoonlijke sleutel"; +"global.strings.public_key" = "Openbare sleutel"; +"global.strings.endpoint" = "Eindpunt"; +"global.strings.keepalive" = "Keep-alive"; +"global.strings.advanced" = "Geavanceerd"; +"global.strings.translations" = "Vertalingen"; + +"global.messages.email_not_configured" = "Er is geen email adres geconfigureerd."; +"global.messages.share" = "Passepartout is een gebruiksvriendelijke open source OpenVPN-client voor iOS en macOS"; + +"global.placeholders.profile_name" = "Mijn Profiel"; + +"global.errors.missing_profile" = "Ontbrekend profiel"; +"global.errors.missing_account" = "Ontbrekend account"; +"global.errors.missing_provider_server" = "Ontbrekende server"; +"global.errors.missing_provider_preset" = "Ontbrekende voorkeur"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Verbinden"; +"tunnelkit.vpn.active" = "Actief"; +"tunnelkit.vpn.disconnecting" = "Verbinding verbreken"; +"tunnelkit.vpn.inactive" = "Inactief"; +"tunnelkit.vpn.disabled" = "Uitgeschakeld"; +"tunnelkit.vpn.unused" = "Uit"; + +"tunnelkit.errors.vpn.timeout" = "Time-out"; +"tunnelkit.errors.vpn.dns" = "DNS niet gelukt"; +"tunnelkit.errors.vpn.auth" = "Auth niet gelukt"; +"tunnelkit.errors.vpn.tls" = "TLS niet gelukt"; +"tunnelkit.errors.vpn.encryption" = "Versleuteling mislukt"; +"tunnelkit.errors.vpn.compression" = "Compressie wordt niet ondersteund"; +"tunnelkit.errors.vpn.network" = "Netwerk veranderd"; +"tunnelkit.errors.vpn.routing" = "Ontbrekende routering"; +"tunnelkit.errors.vpn.gateway" = "Geen gateway"; +"tunnelkit.errors.vpn.shutdown" = "Server is afgesloten"; + +"tunnelkit.errors.parsing" = "Kan het opgegeven configuratiebestand niet verwerken (%@)."; +"tunnelkit.errors.openvpn.malformed" = "Het configuratie betand bevat ongeldige optie(s) (%@)."; +"tunnelkit.errors.openvpn.required_option" = "Het configuratiebestand mist een vereiste optie (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "Het configuratiebestand bevat een niet-ondersteunde optie (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "Het configuratiebestand is correct maar bevat mogelijk een niet-ondersteunde optie (%@).\n\nConnectiviteit kan hier door niet werken, afhankelijk van de serverinstellingen."; +"tunnelkit.errors.openvpn.passphrase_required" = "Voer een coderingswachtwoord in"; +"tunnelkit.errors.openvpn.decryption" = "De configuratie bevat een gecodeerde privésleutel en deze kan niet worden gedecodeerd. Controleer de ingevoerde wachtwoordzin nogmaals."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "Kom kijken hoe ik Passepartout live maak op Twitch, doe mee aan de chat om te communiceren en bij te dragen!"; +"organizer.sections.siri.footer" = "Krijg hulp van Siri en versnel de meest gebruikte interacties binnen de app."; +"organizer.sections.support.header" = "Ondersteuning"; +"organizer.items.follow_twitch.caption" = "Bekijk Passepartout op Twitch"; +"organizer.items.profile.value.current" = "In gebruik"; +"organizer.items.siri_shortcuts.caption" = "Beheer snelkoppelingen"; +"organizer.items.join_community.caption" = "Word lid van de gemeenschap"; +"organizer.items.write_review.caption" = "Schrijf een beoordeling"; +"organizer.items.donate.caption" = "Doneer een gift"; +"organizer.items.github_sponsors.caption" = "Steun me op GitHub"; +"organizer.items.translate.caption" = "Help met vertalen"; +"organizer.items.about.caption" = "Over %@"; +"organizer.items.uninstall.caption" = "Verwijder VPN configuratie"; +"organizer.items.add_provider.caption" = "Voeg nieuwe aanbieder toe"; +"organizer.items.add_host.caption" = "Toevoegen vanuit Bestanden"; + +"organizer.alerts.reddit.message" = "Wist je dat Passepartout een eigen subreddit heeft? Schrijf je in voor updates, of discussiëren over problemen, (nieuwe) mogelijkheden, nieuwe platformen of wat je maar wil.\n\nHet is ook een goede manier om te laten zien dat je om dit project geeft."; +"organizer.alerts.reddit.buttons.subscribe" = "Schfijf je nu in!"; +"organizer.alerts.reddit.buttons.remind" = "Herinner me later"; +"organizer.alerts.reddit.buttons.never" = "Vraag dit niet meer"; + +"organizer.alerts.uninstall_vpn.message" = "Wilt u de VPN-configuratie van uw apparaatinstellingen verwijderen? Dit kan enkele problemen met VPN oplossen en heeft geen invloed op uw provider- en hostprofielen."; +"organizer.alerts.remove_profile.title" = "Profiel verwijderen"; +"organizer.alerts.remove_profile.message" = "Weet u zeker dat u profiel %@ wilt verwijderen?"; + +"organizer.menus.add_profile.imported" = "%@ toevoegen"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "Nieuw profiel"; +"add_profile.shared.views.existing.header" = "Bestaande profielen"; +"add_profile.shared.alerts.overwrite.message" = "Er bestaat al een profiel met deze naam, wil je hem vervangen?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Voer wachtwoordzin in"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Aanbieders"; +"add_profile.provider.sections.vpn.footer" = "Hier vind je aan aantal aanbieders met configuratie profielen."; +"add_profile.provider.items.update_list" = "Lijst bijwerken"; +"add_profile.provider.errors.no_default_server" = "Geen server gevonden."; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Aanbieder"; + +"profile.welcome.message" = "Welkom bij Passepartout!\n\nGebruik de organizer om een nieuw profiel toe te voegen."; + +"profile.sections.vpn.footer" = "De verbinding zal worden gestart wanneer nodig."; +"profile.sections.status.header" = "Verbinding"; +"profile.sections.configuration.header" = "Configuratie"; +"profile.sections.provider_infrastructure.footer" = "Laatste update was op %@."; +"profile.sections.vpn_survives_sleep.footer" = "Uitschakelen om het batterijverbruik te verbeteren, ten koste van incidentele vertragingen als gevolg van het opnieuw opstarten na wake-up."; +"profile.sections.vpn_resolves_hostname.footer" = "Voorkeur om dit aan te zetten voor de meeste netwerken en vereist in sommige IPv6-netwerken. Uitschakelen waar DNS wordt geblokkeerd, of om de onderhandelingen te versnellen wanneer DNS traag reageert."; +"profile.sections.feedback.header" = "Terugkoppeling"; +"profile.items.use_profile.caption" = "Gebruik dit profiel"; +"profile.items.vpn_service.caption" = "Ingeschakeld"; +"profile.items.vpn.turn_on.caption" = "VPN activeren"; +"profile.items.vpn.turn_off.caption" = "VPN deactiveren"; +"profile.items.connection_status.caption" = "Status"; +"profile.items.data_count.caption" = "Gegegevens uitgewisseld"; +"profile.items.provider.refresh.caption" = "Vernieuw de infrastructuur"; +"profile.items.category.caption" = "Categorie"; +"profile.items.only_shows_favorites.caption" = "Alleen favoriete locaties weergeven"; +"profile.items.vpn_survives_sleep.caption" = "Actief tijdens slaapstand"; +"profile.items.vpn_resolves_hostname.caption" = "Haal de naam van de host op"; +"profile.items.reconnect.caption" = "Opnieuw verbinden"; + +"profile.alerts.rename.title" = "Profiel hernoemen"; +"profile.alerts.reconnect_vpn.message" = "Opnieuw verbinding maken met de VPN?"; +"profile.alerts.test_connectivity.title" = "Connectiviteit"; +"profile.alerts.test_connectivity.messages.success" = "Apparaat is verbonden met internet!"; +"profile.alerts.test_connectivity.messages.failure" = "Uw apparaat heeft geen internetverbinding. Controleer uw profielparameters."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Locatie"; +"provider.location.sections.empty_favorites.footer" = "Veeg naar links op een locatie om deze toe te voegen of te verwijderen aan Favorieten."; +"provider.location.actions.favorite" = "Favoriet"; +"provider.location.actions.unfavorite" = "Geen favoriet"; + +"provider.preset.title" = "Voorkeur"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Account"; +"account.sections.credentials.header" = "Inloggegevens"; +"account.sections.registration.footer" = "Registreer voor een %@ account op de website."; +"account.items.username.caption" = "Gebruikersnaam"; +"account.items.username.placeholder" = "gebruikersnaam"; +"account.items.password.caption" = "Wachtwoord"; +"account.items.password.placeholder" = "geheim"; +"account.items.open_guide.caption" = "Bekijk de inloggegevens"; +"account.items.signup.caption" = "Registreer bij %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Gebruik de inloggegevens van %@."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Gebruik uw %@ service-gegevens, die kunnen verschillen van de gegevens van de website."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Gebruik de inloggegevens van %@. Uw gebruikersnaam is meestal numeriek (zonder ruimte)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Gebruik de inloggegevens van %@. Uw gebruikersnaam is meestal uw e-mailadres."; +"account.sections.guidance.footer.infrastructure.pia" = "Gebruik de inloggegevens van %@. Uw gebruikersnaam is meestal numeriek met \"p\" als voorvoegsel."; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Vind de inloggegevens van %@ in \"Account > OpenVPN / IKEv2 Username\" onderdeel van de website."; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Gebruik de inloggegevens van %@. Uw gebruikersnaam is meestal uw e-mailadres."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Gebruik de inloggegevens van %@ Uw gebruikersnaam is meestal uw e-mailadres."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Vind de inloggegevens van %@ in de OpenVPN Config Generator op de website."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Peer"; +"endpoint.wireguard.items.preshared_key.caption" = "Vooraf gedeelde sleutel"; +"endpoint.wireguard.items.allowed_ip.caption" = "Toegestane IP"; + +"endpoint.advanced.title" = "Technische details"; +"endpoint.advanced.openvpn.sections.communication.header" = "Communicatie"; +"endpoint.advanced.openvpn.sections.reset.footer" = "Tik hier als de connectiviteit niet meer werkt na het aanpassen van instellingen, om terug te gaan naar de originele configuratie."; +"endpoint.advanced.openvpn.sections.compression.header" = "Compressie"; +"endpoint.advanced.openvpn.sections.network.header" = "Netwerk"; +"endpoint.advanced.openvpn.sections.other.header" = "Ander"; +"endpoint.advanced.openvpn.items.route.caption" = "Route"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Cipher"; +"endpoint.advanced.openvpn.items.digest.caption" = "Authenticatie"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Embedded"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Framing"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Algoritme"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "Niet ondersteund"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Reset configuratie"; +"endpoint.advanced.openvpn.items.client.caption" = "Certificaat"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Sleutel"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Geverifieerd"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "Niet geverifieerd"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Wrapping"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Authenticatie"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Versleuteling"; +"endpoint.advanced.openvpn.items.eku.caption" = "Uitgebreide verificatie"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d seconden"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "Renegotiation"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "na %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Willekeurig eindpunt"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Netwerk instellingen"; +"network_settings.sections.choices.header" = "Overschrijven"; +"network_settings.gateway.title" = "Standaard gateway"; +"network_settings.proxy.items.bypass_domains.caption" = "Domeinen omzeilen"; +"network_settings.items.add_dns_server.caption" = "Voeg adress toe"; +"network_settings.items.add_dns_domain.caption" = "Zoekdomein toevoegen"; +"network_settings.items.proxy_bypass.caption" = "Omzeil domein"; +"network_settings.items.add_proxy_bypass.caption" = "Voeg omzeil optie voor domein toe"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Vertrouwde netwerken"; +"on_demand.sections.policy.footer" = "Bij het invoeren van een vertrouwd netwerk wordt de VPN uitgeschakeld en niet verbonden gehouden. Schakel deze optie uit om dergelijk gedrag niet af te dwingen."; +"on_demand.items.add_ssid.caption" = "Wi-Fi toevoegen"; +"on_demand.items.active.caption" = "Vertrouwen"; +"on_demand.items.mobile.caption" = "Mobiel netwerk"; +"on_demand.items.ethernet.caption" = "Bekabelde verbindingen vertrouwen"; +"on_demand.items.ethernet.description" = "Vink aan om alle bekabelde verbindingen te vertrouwen."; +"on_demand.items.policy.caption" = "Trust disables VPN"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Diagnose"; +"diagnostics.sections.debug_log.footer" = "De maskeerstatus is effectief na opnieuw verbinden. Netwerkgegevens zijn hostnamen, IP-adressen, routing, SSID's. Inloggegevens en privésleutels worden niet geregistreerd."; +"diagnostics.items.server_configuration.caption" = "Server configuratie"; +"diagnostics.items.masks_private_data.caption" = "Netwerkgegevens maskeren"; +"diagnostics.items.report_issue.caption" = "Probleem met connectiviteit melden"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "Om het huidige foutopsporingslogboek veilig opnieuw in te stellen en de nieuwe maskeervoorkeur toe te passen, moet u nu opnieuw verbinding maken met VPN."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Foutopsporingslogboek"; +"debug_log.buttons.copy" = "Kopiëren"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Meld een probleem"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Voeg snelkoppeling toe"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "Mobiel"; +"shortcuts.add.items.connect.caption" = "Verbind met"; +"shortcuts.add.items.enable_vpn.caption" = "Activeer VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Deactiveer VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Vertrouw huidig Wi-Fi netwerk"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Untrust current Wi-Fi"; +"shortcuts.add.items.trust_cellular.caption" = "Vertouw mobiel netwerk"; +"shortcuts.add.items.untrust_cellular.caption" = "Untrust mobiel netwerk"; +"shortcuts.add.alerts.no_profiles.message" = "Er is geen profiel om verbinding mee te maken."; + +"shortcuts.edit.title" = "Beheer snelkoppelingen"; +"shortcuts.edit.sections.all.header" = "Bestaande snelkoppelingen"; +"shortcuts.edit.items.add_shortcut.caption" = "Voeg snelkoppeling toe"; + +// MARK: PaywallView + +"paywall.title" = "Aanschaffen"; +"paywall.sections.products.footer" = "Elk product is een eenmalige aankoop. Aankopen van providers bevatten geen VPN-abonnement."; +"paywall.items.loading.caption" = "Producten laden"; +"paywall.items.full_version.extra_description" = "Alle providers (inclusief toekomstige)\n%@"; +"paywall.items.restore.title" = "Herstel Aankopen"; +"paywall.items.restore.description" = "Als u deze app of functie in het verleden heeft gekocht, kunt u uw aankopen herstellen en wordt dit scherm niet meer getoond."; + +// MARK: DonateView + +"donate.title" = "Donatie"; +"donate.sections.one_time.header" = "Eenmalig"; +"donate.sections.one_time.footer" = "Als je dankbaarheid wilt tonen voor mijn gratis werk, zijn hier een paar bedragen die je direct kunt doneren.\n\nHet bedrag wordt slechts één keer per donatie in rekening gebracht en u kunt meerdere keren doneren."; +"donate.items.loading.caption" = "Ophalen donaties"; +"donate.items.purchasing.caption" = "Doneren"; +"donate.alerts.purchase.success.title" = "Hartelijk dank"; +"donate.alerts.purchase.success.message" = "Dit betekent veel voor mij en ik hoop echt dat je deze app blijft gebruiken en promoten."; +"donate.alerts.purchase.failure.message" = "Donatie mislukt. %@"; + +// MARK: AboutView + +"about.title" = "Over"; +"about.sections.web.header" = "Web"; +"about.sections.share.header" = "Delen"; +"about.items.credits.caption" = "Credits"; +"about.items.website.caption" = "Home page"; +"about.items.disclaimer.caption" = "Vrijwaring"; +"about.items.privacy_policy.caption" = "Privacybeleid"; +"about.items.share_twitter.caption" = "Tweet about it!"; +"about.items.share_generic.caption" = "Nodig een vriend uit"; + +// MARK: AboutView -> VersionView + +"version.title" = "Versie"; +"version.labels.intro" = "Passepartout en TunnelKit zijn geschreven en worden onderhouden door Davide De Rosa (keeshux).\n\nDe broncode voor Passepartout en TunnelKit is openbaar beschikbaar op GitHub onder de GPLv3, je kunt links op de startpagina vinden.\n\nPassepartout is een niet-officiële client en is op geen enkele manier verbonden aan OpenVPN Inc."; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Credits"; +"credits.sections.licenses.header" = "Licenties"; +"credits.sections.notices.header" = "Mededelingen"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Voorkeuren"; +"preferences.sections.general.header" = "Algemeen"; +"preferences.items.launches_on_login.caption" = "Lanceren bij aanmelden"; +"preferences.items.launches_on_login.footer" = "Vink aan als u wilt dat de app automatisch wordt gelanceerd bij opstarten of aanmelden."; +"preferences.items.confirm_quit.caption" = "Sluiten bevestigen"; +"preferences.items.confirm_quit.footer" = "Vink aan om een bevestigingsmelding te sluiten."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Weergeven"; +"menu.switch_profile.title" = "Actief profiel"; +"menu.active_profile.title.none" = "Geen actief profiel"; +"menu.active_profile.items.customize.title" = "Aanpassen..."; +"menu.active_profile.messages.missing_credentials" = "Geen account geconfigureerd"; +"menu.organizer.title" = "Organisator"; +"menu.preferences.title" = "Voorkeuren"; +"menu.support.title" = "Ondersteuning"; +"menu.quit.title" = "%@ afsluiten"; +"menu.quit.messages.confirm" = "De VPN zal, indien geactiveerd, op de achtergrond blijven draaien. Wilt u sluiten?"; diff --git a/Passepartout/App/Shared/L10n/pl.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/pl.lproj/Localizable.strings new file mode 100644 index 00000000..90e18fcc --- /dev/null +++ b/Passepartout/App/Shared/L10n/pl.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Anuluj"; +"global.strings.next" = "Następny"; +/* MARK: Global */ +"global.strings.ok" = "OK"; +"global.strings.save" = "Zapisz"; +"global.strings.rename" = "Zmień nazwę"; +"global.strings.add" = "Dodaj"; +"global.strings.default" = "Default"; +"global.strings.name" = "Nazwa"; +"global.strings.address" = "Adres"; +"global.strings.addresses" = "Adresy"; +"global.strings.port" = "Port"; +"global.strings.protocol" = "Protokół"; +"global.strings.protocols" = "Protokoły"; +"global.strings.enabled" = "Włączony"; +"global.strings.disabled" = "Wyłączony"; +"global.strings.none" = "Brak"; +"global.strings.automatic" = "Automatycznie"; +"global.strings.manual" = "Ręcznie"; +"global.strings.encryption" = "Szyfrowanie"; +"global.strings.reconnect" = "Połącz ponownie"; +"global.strings.servers" = "Serwery"; +"global.strings.domain" = "Domena"; +"global.strings.domains" = "Domeny"; +"global.strings.proxy" = "Proxy"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "Interfejs"; +"global.strings.private_key" = "Klucz prywatny"; +"global.strings.public_key" = "Klucz publiczny"; +"global.strings.endpoint" = "Punkt końcowy"; +"global.strings.keepalive" = "Utrzymuj połączenie"; +"global.strings.advanced" = "Zaawansowane"; +"global.strings.translations" = "Tłumaczenia"; + +"global.messages.email_not_configured" = "Adres e-mail nie jest skonfigurowany."; +"global.messages.share" = "Passepartout to klient OpenVPN, przyjazny użytkownikowi, open-source, stworzony dla iOS i macOS"; + +"global.placeholders.profile_name" = "Mój profil"; + +"global.errors.missing_profile" = "Brakujący profil"; +"global.errors.missing_account" = "Brakujące konto"; +"global.errors.missing_provider_server" = "Brakujący serwer"; +"global.errors.missing_provider_preset" = "Brakujący preset"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Łączenie"; +"tunnelkit.vpn.active" = "Aktywne"; +"tunnelkit.vpn.disconnecting" = "Rozłączanie"; +"tunnelkit.vpn.inactive" = "Nieaktywne"; +"tunnelkit.vpn.disabled" = "Wyłączone"; +"tunnelkit.vpn.unused" = "Wył"; + +"tunnelkit.errors.vpn.timeout" = "Upłynąl limit czasu połączenia"; +"tunnelkit.errors.vpn.dns" = "Błąd DNS"; +"tunnelkit.errors.vpn.auth" = "Błąd autoryzacji"; +"tunnelkit.errors.vpn.tls" = "Błąd TLS"; +"tunnelkit.errors.vpn.encryption" = "Błąd szyfrowania"; +"tunnelkit.errors.vpn.compression" = "Niewspierana kompresja"; +"tunnelkit.errors.vpn.network" = "Sieć zmieniona"; +"tunnelkit.errors.vpn.routing" = "Brak routingu"; +"tunnelkit.errors.vpn.gateway" = "Brak bramy domyślnej"; +"tunnelkit.errors.vpn.shutdown" = "Serwer został zamknięty"; + +"tunnelkit.errors.parsing" = "Błąd w przetwarzaniu pliku konfiguracyjnego (%@)."; +"tunnelkit.errors.openvpn.malformed" = "Plik konfiguracyjny zawiera źle zformułowaną opcję (%@)."; +"tunnelkit.errors.openvpn.required_option" = "Plik konfiguracyjny nie nawiera potrzebnej opcji (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "Plik konfiguracyjny posiada niewspieraną opcję (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "Plik konfiguracyjny jest poprawny, ale zawiera potencjalnie niewspieraną opcję (%@).\n\nPołączenie może zostać przerwane przez ustawienia serwera."; +"tunnelkit.errors.openvpn.passphrase_required" = "Proszę wpisać frazę szyfrującą."; +"tunnelkit.errors.openvpn.decryption" = "Konfiguracja zawiera zaszyfrowany klucz prywatny który nie może zostać odszyfrowany. Sprawdź frazę szyfrującą."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "Przyjdź i zobacz, jak tworzę Passepartout na żywo na Twitchu, dołącz do czatu, aby współdziałać i udzielać się!"; +"organizer.sections.siri.footer" = "Użyj Siri żeby przyspieszyć najczęstsze akcje w aplikacji."; +"organizer.sections.support.header" = "Wsparcie"; +"organizer.items.follow_twitch.caption" = "Oglądaj Passepartout na Twitchu"; +"organizer.items.profile.value.current" = "W użyciu"; +"organizer.items.siri_shortcuts.caption" = "Zarządzaj skrótami"; +"organizer.items.join_community.caption" = "Dołącz do społeczności"; +"organizer.items.write_review.caption" = "Napisz recenzję"; +"organizer.items.donate.caption" = "Wyślij dotację"; +"organizer.items.github_sponsors.caption" = "Wesprzyj mnie na GitHub"; +"organizer.items.translate.caption" = "Zaproponuj tłumaczenie"; +"organizer.items.about.caption" = "O %@"; +"organizer.items.uninstall.caption" = "Usuń konfiguracje VPN"; +"organizer.items.add_provider.caption" = "Dodaj nowego usługodawcę"; +"organizer.items.add_host.caption" = "Dodaj z Plików"; + +"organizer.alerts.reddit.message" = "Wiedziałeś/łaś, że Passepartout ma swój subreddit? Subskrybuj dla aktualizacji, dyskusji o funkcjonalności, nowych platformach lub o czymkolwiek zechcesz.\n\nTo również świetny sposób na okazanie zainteresowania projektem."; +"organizer.alerts.reddit.buttons.subscribe" = "Subskrybuj!"; +"organizer.alerts.reddit.buttons.remind" = "Przypomnij mi później"; +"organizer.alerts.reddit.buttons.never" = "Nie przypominaj"; + +"organizer.alerts.uninstall_vpn.message" = "Na pewno chcesz usunąć konfigurację VPN z urządzenia? Może to naprawić błędy z statusem VPN i nie będzie miało wpływu na konfigurację usługodawców/hostów."; +"organizer.alerts.remove_profile.title" = "Usuń profil"; +"organizer.alerts.remove_profile.message" = "Na pewno chcesz usunąć profil %@?"; + +"organizer.menus.add_profile.imported" = "Dodaj %@"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "Nowy profil"; +"add_profile.shared.views.existing.header" = "Istniejące profile"; +"add_profile.shared.alerts.overwrite.message" = "Profil hosta z taką nazwą już istnieje. Nadpisać profil?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Wpisz frazę szyfrującą"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Usługodawcy"; +"add_profile.provider.sections.vpn.footer" = "Tutaj znajdziesz kilku usługodawców z profilami konfiguracyjnymi."; +"add_profile.provider.items.update_list" = "Zaktualizuj listę"; +"add_profile.provider.errors.no_default_server" = "Nie znaleziono żadnego serwera."; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Dostawca"; + +"profile.welcome.message" = "Witaj w Passepartout!\n\nUżyj organizera by utworzyć nowy profil."; + +"profile.sections.vpn.footer" = "Połączenie zostanie nawiązane zgodnie z ustawieniami."; +"profile.sections.status.header" = "Połączenie"; +"profile.sections.configuration.header" = "Konfiguracja"; +"profile.sections.provider_infrastructure.footer" = "Ostatnio aktualizowane %@."; +"profile.sections.vpn_survives_sleep.footer" = "Wyłącz dla mniejszego zużycia baterii kosztem wolniejszego działania spowodowanego ponownym połączeniem przy wybudzeniu urządzenia."; +"profile.sections.vpn_resolves_hostname.footer" = "Preferowane w większości sieci i potrzebne w niektórych sieciach IPv6. Wyłącz kiedy DNS jest zablokowane, lub żeby przyspieszyć ustanawianie połączenia gdy DNS jest zbyt wolne."; +"profile.sections.feedback.header" = "Wyraź opinię"; +"profile.items.use_profile.caption" = "Używaj tego profilu"; +"profile.items.vpn_service.caption" = "Włączone"; +"profile.items.vpn.turn_on.caption" = "Włącz VPN"; +"profile.items.vpn.turn_off.caption" = "Wyłącz VPN"; +"profile.items.connection_status.caption" = "Status"; +"profile.items.data_count.caption" = "Pobrane/wysłane dane"; +"profile.items.provider.refresh.caption" = "Odśwież infrastrukturę"; +"profile.items.category.caption" = "Kategoria"; +"profile.items.only_shows_favorites.caption" = "Pokazuj tylko ulubione lokalizacje"; +"profile.items.vpn_survives_sleep.caption" = "Utrzymuj połączenie przy zablokowanym ekranie"; +"profile.items.vpn_resolves_hostname.caption" = "Rozwiązuj nazwy hostów usługodawcy"; +"profile.items.reconnect.caption" = "Połącz ponownie"; + +"profile.alerts.rename.title" = "Zmień nazwę profilu"; +"profile.alerts.reconnect_vpn.message" = "Czy chcesz połączyć się ponownie z VPN?"; +"profile.alerts.test_connectivity.title" = "Połączenie"; +"profile.alerts.test_connectivity.messages.success" = "Twoje urządzenie jest połączone z internetem!"; +"profile.alerts.test_connectivity.messages.failure" = "Twoje urządzenie nie jest połączone z internetem, sprawdź ustawienia profilu."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Lokalizacja"; +"provider.location.sections.empty_favorites.footer" = "Aby usunąć zakładkę, przesuń w lewo."; +"provider.location.actions.favorite" = "Dodaj do ulubionych"; +"provider.location.actions.unfavorite" = "Usuń z ulubionych"; + +"provider.preset.title" = "Preset"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Konto"; +"account.sections.credentials.header" = "Dane logowania"; +"account.sections.registration.footer" = "Utwórz konto na stronie: %@"; +"account.items.username.caption" = "Nazwa użytkownika"; +"account.items.username.placeholder" = "Nazwa użytkownika"; +"account.items.password.caption" = "Hasło"; +"account.items.password.placeholder" = "Ukryte"; +"account.items.open_guide.caption" = "Zobacz swoje login/hasło"; +"account.items.signup.caption" = "Zarejstruj się w %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Użyj loginu do %@."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Użyj poświadczeń usługi %@, które mogą różnić się od poświadczeń witryny."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Użyj loginu do %@. Twoja nazwa użytkownika jest najczęściej ciągiem liczb (bez przestrzeni)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Użyj loginu do %@. Twoja nazwa użytkownika to najczęściej e-mail."; +"account.sections.guidance.footer.infrastructure.pia" = "Użyj loginu do %@. Twoja nazwa użytkownika jesy najczęściej ciągiem liczb poprzedonym prefiksem \"p\"."; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Użyj loginu do %@. Zajdziesz go w sekcji \"Account > OpenVPN / IKEv2 Username\"."; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Użyj loginu do %@. Twoja nazwa użytkownika to najczęściej e-mail."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Użyj loginu do %@. Twoja nazwa użytkownika to najczęściej e-mail."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Użyj loginu do %@ z generatora konfiguracji OpenVPN dostępnego na stronie."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Peer"; +"endpoint.wireguard.items.preshared_key.caption" = "Wcześniej udostępniony klucz"; +"endpoint.wireguard.items.allowed_ip.caption" = "IP z zezwoleniem"; + +"endpoint.advanced.title" = "Dane techniczne"; +"endpoint.advanced.openvpn.sections.communication.header" = "Komunikacja"; +"endpoint.advanced.openvpn.sections.reset.footer" = "Jeśli masz problemy z połączeniem po zmianie konfiguracji komunikacji, kliknij żeby przywrócić domyślną konfigurację."; +"endpoint.advanced.openvpn.sections.compression.header" = "Kompresja"; +"endpoint.advanced.openvpn.sections.network.header" = "Sieć"; +"endpoint.advanced.openvpn.sections.other.header" = "Inne"; +"endpoint.advanced.openvpn.items.route.caption" = "Trasowanie"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Szyfr"; +"endpoint.advanced.openvpn.items.digest.caption" = "Uwierzytelnienie"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Osadzony"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Struktura"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Algorytm"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "Nieobsługiwane"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Zresetuj konfigurację"; +"endpoint.advanced.openvpn.items.client.caption" = "Certyfikat"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Klucz"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Zweryfikowany"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "Niezweryfikowany"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Wrapping"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Uwierzytelnienie"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Szyfrowanie"; +"endpoint.advanced.openvpn.items.eku.caption" = "Rozszerzona weryfikacja"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d sekund"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "Ponowna negocjacja"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "po %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Losowy host końcowy"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Ustawienia sieci"; +"network_settings.sections.choices.header" = "Nadpisz"; +"network_settings.gateway.title" = "Domyślna brama sieciowa"; +"network_settings.proxy.items.bypass_domains.caption" = "Pomiń domeny"; +"network_settings.items.add_dns_server.caption" = "Dodaj adres"; +"network_settings.items.add_dns_domain.caption" = "Dodaj domenę wyszukiwania"; +"network_settings.items.proxy_bypass.caption" = "Pomiń domenę"; +"network_settings.items.add_proxy_bypass.caption" = "Dodaj domenę"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Zaufane sieci"; +"on_demand.sections.policy.footer" = "Kiedy urządzenie łączy się z zaufaną siecią, VPN jest wyłączane. Wyłącz tę opcję żeby nie wymuszać takiego zachowania."; +"on_demand.items.add_ssid.caption" = "Dodaj Wi-Fi"; +"on_demand.items.active.caption" = "Ufaj"; +"on_demand.items.mobile.caption" = "Sieć komórkowa"; +"on_demand.items.ethernet.caption" = "Ufaj połączeniom przewodowym"; +"on_demand.items.ethernet.description" = "Zaznacz, aby traktować każde przewodowe połączenie kablowe jako zaufane."; +"on_demand.items.policy.caption" = "Wyłącz VPN dla zaufanych sieci"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Diagnostyka"; +"diagnostics.sections.debug_log.footer" = "Status maskowania będzie widoczny po ponownym połączeniu. Dane połączenia to nazwy hostów, adresy IP, routing, SSID. Loginy i klucze prywatne nie są zapisywane."; +"diagnostics.items.server_configuration.caption" = "Konfiguracja serwera"; +"diagnostics.items.masks_private_data.caption" = "Maskuj dane sieci"; +"diagnostics.items.report_issue.caption" = "Zgłoś problemy z połączeniem"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "Aby bezpiecznie zresetować rejestr debugowania i zastosować nowe ustawienia maskowania, musisz połączyć się z VPN ponownie."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Debugowanie"; +"debug_log.buttons.copy" = "Kopiuj"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Zgłoś błąd"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Dodaj skrót"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "Dane komórkowe"; +"shortcuts.add.items.connect.caption" = "Połącz z"; +"shortcuts.add.items.enable_vpn.caption" = "Włącz VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Wyłącz VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Zaufaj obecnie połączonej sieci Wi-Fi"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Przestań ufać obecnie połączonej sieci Wi-Fi"; +"shortcuts.add.items.trust_cellular.caption" = "Ufaj danym komórkowym"; +"shortcuts.add.items.untrust_cellular.caption" = "Przestań ufać danym komórkowym"; +"shortcuts.add.alerts.no_profiles.message" = "Brak wybranego profilu połączenia."; + +"shortcuts.edit.title" = "Zarządzaj skrótami"; +"shortcuts.edit.sections.all.header" = "Istniejące skróty"; +"shortcuts.edit.items.add_shortcut.caption" = "Dodaj skrót"; + +// MARK: PaywallView + +"paywall.title" = "Kup"; +"paywall.sections.products.footer" = "Każdy produkt to zakup jednorazowy. Kuipno usługodawcy nie zawiera subskrypcji VPN."; +"paywall.items.loading.caption" = "Ładowanie produktów"; +"paywall.items.full_version.extra_description" = "Wszyscy usługodawcy (włączając przyszłych)\n%@"; +"paywall.items.restore.title" = "Przywróć zakup"; +"paywall.items.restore.description" = "Jeśli kupiłeś tą aplikację lub funkcję wcześniej, możesz przywrócić swoje zakupy i ten ekran nie będzie wyświetlony ponownie."; + +// MARK: DonateView + +"donate.title" = "Dotacja"; +"donate.sections.one_time.header" = "Jeden raz"; +"donate.sections.one_time.footer" = "Jeśli chcesz docenić moją pracę, poniżej znajdziesz kilka kwot do wyboru dotacji.\n\nTwoje konto zostanie obciążone tylko raz na jedną dotację, możesz wysłać dotację kilka razy."; +"donate.items.loading.caption" = "Ładowanie dotacji"; +"donate.items.purchasing.caption" = "Wykonywanie dotacji"; +"donate.alerts.purchase.success.title" = "Dziękuję"; +"donate.alerts.purchase.success.message" = "To dla mnie dużo znaczy, mam nadzięję że będziesz używać aplikacji i przyczynisz się do jej rozpowrzechnienia."; +"donate.alerts.purchase.failure.message" = "Nie można dokonać dotacji. %@"; + +// MARK: AboutView + +"about.title" = "O programie"; +"about.sections.web.header" = "Strona WWW"; +"about.sections.share.header" = "Udostępnij"; +"about.items.credits.caption" = "Twórcy"; +"about.items.website.caption" = "Strona domowa"; +"about.items.disclaimer.caption" = "Disclaimer"; +"about.items.privacy_policy.caption" = "Polityka prywatności"; +"about.items.share_twitter.caption" = "Wyślij tweeta!"; +"about.items.share_generic.caption" = "Zaproś znajomego"; + +// MARK: AboutView -> VersionView + +"version.title" = "Wersja"; +"version.labels.intro" = "Passepartout i TunnelKit są stworzone i utrzymywane przez Davide De Rosa (keeshux).\n\nKod źródłowy Passepartout i TunnelKit jest publicznie dostępny na licencji GPLv3, linki możesz znaleźć na stronie domowej.\n\nPassepartout nie jest oficjanlnym klientem i nie jest powiązany z OpenVPN Inc."; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Twórcy"; +"credits.sections.licenses.header" = "Licencje"; +"credits.sections.notices.header" = "Dodatki"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Preferencje"; +"preferences.sections.general.header" = "Ogólne"; +"preferences.items.launches_on_login.caption" = "Uruchom po zalogowaniu"; +"preferences.items.launches_on_login.footer" = "Zaznacz, aby automatycznie uruchamiać aplikację przy restarcie systemu lub logowaniu."; +"preferences.items.confirm_quit.caption" = "Potwierdź zakończenie pracy"; +"preferences.items.confirm_quit.footer" = "Zaznacz, aby wyświetlić monit o potwierdzeniu zakończenia."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Pokaż"; +"menu.switch_profile.title" = "Aktywny profil"; +"menu.active_profile.title.none" = "Brak aktywnych profili"; +"menu.active_profile.items.customize.title" = "Personalizuj"; +"menu.active_profile.messages.missing_credentials" = "Brak skonfigurowanych kont"; +"menu.organizer.title" = "Organzator"; +"menu.preferences.title" = "Preferencje"; +"menu.support.title" = "Obsługa techniczna"; +"menu.quit.title" = "Zakończ %@"; +"menu.quit.messages.confirm" = "Jeśli sieć VPN, jest włączona, będzie nadal działać w tle. Chcesz zakończyć?"; diff --git a/Passepartout/App/Shared/L10n/pt.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/pt.lproj/Localizable.strings new file mode 100644 index 00000000..579bfaf1 --- /dev/null +++ b/Passepartout/App/Shared/L10n/pt.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Cancelar"; +"global.strings.next" = "Próximo"; +/* MARK: Global */ +"global.strings.ok" = "OK"; +"global.strings.save" = "Guardar"; +"global.strings.rename" = "Renomear"; +"global.strings.add" = "Adicionar"; +"global.strings.default" = "Default"; +"global.strings.name" = "Nome"; +"global.strings.address" = "Endereço"; +"global.strings.addresses" = "Endereços"; +"global.strings.port" = "Porta"; +"global.strings.protocol" = "Protocolo"; +"global.strings.protocols" = "Protocolos"; +"global.strings.enabled" = "Ativado"; +"global.strings.disabled" = "Desativado"; +"global.strings.none" = "Nenhum"; +"global.strings.automatic" = "Automático"; +"global.strings.manual" = "Manual"; +"global.strings.encryption" = "Criptografia"; +"global.strings.reconnect" = "Reconectar"; +"global.strings.servers" = "Servidores"; +"global.strings.domain" = "Domínio"; +"global.strings.domains" = "Domínios"; +"global.strings.proxy" = "Proxy"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "Interface"; +"global.strings.private_key" = "Chave privada"; +"global.strings.public_key" = "Chave pública"; +"global.strings.endpoint" = "Destino"; +"global.strings.keepalive" = "Manter ativo"; +"global.strings.advanced" = "Avançado"; +"global.strings.translations" = "Traduções"; + +"global.messages.email_not_configured" = "Nenhuma conta de email configurada."; +"global.messages.share" = "Passepartout é um cliente OpenVPN fácil e open-source para iOS e macOS"; + +"global.placeholders.profile_name" = "Meu perfil"; + +"global.errors.missing_profile" = "Perfil em falta"; +"global.errors.missing_account" = "Conta em falta"; +"global.errors.missing_provider_server" = "Servidor em falta"; +"global.errors.missing_provider_preset" = "Pré-definição em falta"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Conectando"; +"tunnelkit.vpn.active" = "Ativa"; +"tunnelkit.vpn.disconnecting" = "Desconectando"; +"tunnelkit.vpn.inactive" = "Inativo"; +"tunnelkit.vpn.disabled" = "Desativado"; +"tunnelkit.vpn.unused" = "Desativado"; + +"tunnelkit.errors.vpn.timeout" = "Timeout"; +"tunnelkit.errors.vpn.dns" = "Falha no DNS"; +"tunnelkit.errors.vpn.auth" = "Falha na autenticação"; +"tunnelkit.errors.vpn.tls" = "Falha no TLS"; +"tunnelkit.errors.vpn.encryption" = "Falha na criptografia"; +"tunnelkit.errors.vpn.compression" = "Compressão não suportada"; +"tunnelkit.errors.vpn.network" = "Rede alterada"; +"tunnelkit.errors.vpn.routing" = "Rota necessária"; +"tunnelkit.errors.vpn.gateway" = "Sem gateway"; +"tunnelkit.errors.vpn.shutdown" = "Servidor desligado"; + +"tunnelkit.errors.parsing" = "Não foi possível processar as configurações do arquivo (%@)."; +"tunnelkit.errors.openvpn.malformed" = "O arquivo de configuração possui uma opção não formatada corretamente (%@)."; +"tunnelkit.errors.openvpn.required_option" = "O arquivo não possui todas configurações requeridas (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "O arquivo de configuração possui uma opção não suportada (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "O arquivo de configuração está correto, mas provavelmente possui uma opção não suportada (%@).\n\nSua conexão poderá ser instável dependendo as configurações do servidor."; +"tunnelkit.errors.openvpn.passphrase_required" = "Por favor, digite sua senha de criptografia."; +"tunnelkit.errors.openvpn.decryption" = "Sua configiração possui uma chave privada criptografada que talvez não possa ser descriptografada. Verifique novamente sua senha de criptografia."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "Venha me ver fazer o Passepartout ao vivo no Twitch, entre no chat para interagir e contribuir!"; +"organizer.sections.siri.footer" = "Peça ajuda para Siri para agilar tarefas comum do aplicativo."; +"organizer.sections.support.header" = "Suporte"; +"organizer.items.follow_twitch.caption" = "Assistir Passepartout no Twitch"; +"organizer.items.profile.value.current" = "Ativo"; +"organizer.items.siri_shortcuts.caption" = "Gerenciar atalhos"; +"organizer.items.join_community.caption" = "Participar da comunidade"; +"organizer.items.write_review.caption" = "Escrever avaliação"; +"organizer.items.donate.caption" = "Fazer doação"; +"organizer.items.github_sponsors.caption" = "Contribuir no GitHub"; +"organizer.items.translate.caption" = "Ajudar-nos na tradução"; +"organizer.items.about.caption" = "Sobre %@"; +"organizer.items.uninstall.caption" = "Remover configuração VPN"; +"organizer.items.add_provider.caption" = "Adicionar novo perfil"; +"organizer.items.add_host.caption" = "Adicionar dos Arquivos"; + +"organizer.alerts.reddit.message" = "Você sabia que Passepartout tem um subreddit? Siga-nos para atualizações ou para discutir problemas, novas funcionalidades, ou qualquer outro tópico.\n\nÉ uma boa maneira de mostrar seu interesse pelo projeto."; +"organizer.alerts.reddit.buttons.subscribe" = "Seguir!"; +"organizer.alerts.reddit.buttons.remind" = "Lembrar-me depois"; +"organizer.alerts.reddit.buttons.never" = "Não perguntar novamente"; + +"organizer.alerts.uninstall_vpn.message" = "Tem certeza que deseja remover as configurações de VPN do seu dispositivo? Isso poderá corrigir problemas com o estado atual, sem afetar seu provedor e perfis do host."; +"organizer.alerts.remove_profile.title" = "Remover perfil"; +"organizer.alerts.remove_profile.message" = "Tem a certeza de que pretende eliminar o perfil %@?"; + +"organizer.menus.add_profile.imported" = "Adicionar %@"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "Perfil novo"; +"add_profile.shared.views.existing.header" = "Perfis existentes"; +"add_profile.shared.alerts.overwrite.message" = "Já existe um perfil com o mesmo nome. Deseja substituí-lo?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Introduza a sua senha de criptografia"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Provedores"; +"add_profile.provider.sections.vpn.footer" = "Aqui você encontra um provedor com o perfil pré-configurado."; +"add_profile.provider.items.update_list" = "Atualizar lista"; +"add_profile.provider.errors.no_default_server" = "Impossível encontrar um servidor."; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Fornecedor"; + +"profile.welcome.message" = "Bem-vindo ao Passepartout!\n\nUse o organizador para adicionar um novo perfil."; + +"profile.sections.vpn.footer" = "A conexão será estabelecida assim que necessária."; +"profile.sections.status.header" = "Conexão"; +"profile.sections.configuration.header" = "Configuração"; +"profile.sections.provider_infrastructure.footer" = "Última atualização em %@."; +"profile.sections.vpn_survives_sleep.footer" = "Desative para melhorar o consumo de bateria, o que poderá ocasionar queda de performance quando o restabelecimento de conexão for realizado."; +"profile.sections.vpn_resolves_hostname.footer" = "Recomendado para maioria das redes e requirido em algumas redes IPv6. Desative se o DNS estiver bloqueado, ou para acelerar o DNS quando o mesmo está devagar."; +"profile.sections.feedback.header" = "Feedback"; +"profile.items.use_profile.caption" = "Usar esse perfil"; +"profile.items.vpn_service.caption" = "Ativado"; +"profile.items.vpn.turn_on.caption" = "Ativar VPN"; +"profile.items.vpn.turn_off.caption" = "Desativar VPN"; +"profile.items.connection_status.caption" = "Status"; +"profile.items.data_count.caption" = "Dados transferidos"; +"profile.items.provider.refresh.caption" = "Atualizar infraestrutura"; +"profile.items.category.caption" = "Categoria"; +"profile.items.only_shows_favorites.caption" = "Mostrar apenas os locais preferidos"; +"profile.items.vpn_survives_sleep.caption" = "Manter ativo em modo descanço"; +"profile.items.vpn_resolves_hostname.caption" = "Resolver hostname do servidor"; +"profile.items.reconnect.caption" = "Reconectar"; + +"profile.alerts.rename.title" = "Renomear perfil"; +"profile.alerts.reconnect_vpn.message" = "Deseja reconectar à VPN?"; +"profile.alerts.test_connectivity.title" = "Conectividade"; +"profile.alerts.test_connectivity.messages.success" = "Seu dispositivo está conectado à Internet!"; +"profile.alerts.test_connectivity.messages.failure" = "Seu dispositivo não está conectado à Internet, por favor, verifique sua configurações."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Localização"; +"provider.location.sections.empty_favorites.footer" = "Deslize para a esquerda em um local para adicioná-lo ou removê-lo dos Favoritos."; +"provider.location.actions.favorite" = "Favorito"; +"provider.location.actions.unfavorite" = "Não favorito"; + +"provider.preset.title" = "Pré-definição"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Conta"; +"account.sections.credentials.header" = "Credenciais"; +"account.sections.registration.footer" = "Registrar em %@ website."; +"account.items.username.caption" = "Usuário"; +"account.items.username.placeholder" = "usuário"; +"account.items.password.caption" = "Senha"; +"account.items.password.placeholder" = "senha secreta"; +"account.items.open_guide.caption" = "Ver sua credenciais"; +"account.items.signup.caption" = "Registrar com %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Utilize %@ credenciais do site."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Utilize suas credenciais de serviço %@, que podem diferir das credenciais do site."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Utilize %@ credenciais do site. Seu usuário é normalmente numérico (sem espaços)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Utilize %@ credenciais do site. Seu usuário é normalmente o seu email."; +"account.sections.guidance.footer.infrastructure.pia" = "Utilize %@ credenciais do site. Seu usuário é normalmente numérico com prefixo \"p\"."; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Encontre %@ credenciais na sessão \"Account > OpenVPN / IKEv2 Username\" do site."; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Utilize %@ credenciais do site. Seu usuário é normalmente o seu email."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Utilize %@ credenciais do site. Seu usuário é normalmente o seu email."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Encontre %@ credenciais no gerador de configuração OpenVPN do site."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Ponto"; +"endpoint.wireguard.items.preshared_key.caption" = "Chave pré-partilhada"; +"endpoint.wireguard.items.allowed_ip.caption" = "IP Permitido"; + +"endpoint.advanced.title" = "Detalhes técnicos"; +"endpoint.advanced.openvpn.sections.communication.header" = "Comunicação"; +"endpoint.advanced.openvpn.sections.reset.footer" = "Se você foi desconectado após mudar parâmetros de comunicação, toque para restaurar a configuração original."; +"endpoint.advanced.openvpn.sections.compression.header" = "Compressão"; +"endpoint.advanced.openvpn.sections.network.header" = "Rede"; +"endpoint.advanced.openvpn.sections.other.header" = "Outro"; +"endpoint.advanced.openvpn.items.route.caption" = "Rota"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Criptografada"; +"endpoint.advanced.openvpn.items.digest.caption" = "Autenticação"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Agregado"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Framing"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Algorítimo"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "Não suportado"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Restaurar configuração"; +"endpoint.advanced.openvpn.items.client.caption" = "Certificado"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Chave"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Verificado"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "Não verificado"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Wrapping"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Autenticação"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Criptografia"; +"endpoint.advanced.openvpn.items.eku.caption" = "Verificação extendida"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d segundos"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "Renegociando"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "depois de %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Destino randômico"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Configurações de rede"; +"network_settings.sections.choices.header" = "Substituir"; +"network_settings.gateway.title" = "Gateway padrão"; +"network_settings.proxy.items.bypass_domains.caption" = "Fazer um bypass aos domínios"; +"network_settings.items.add_dns_server.caption" = "Adicionar endereço"; +"network_settings.items.add_dns_domain.caption" = "Adicionar domínio"; +"network_settings.items.proxy_bypass.caption" = "Domínio ignorado"; +"network_settings.items.add_proxy_bypass.caption" = "Adicionar domínio ignorado"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Redes seguras"; +"on_demand.sections.policy.footer" = "Ao entrar em uma rede segura, a VPN é normalmente é desconectada e mantido inativa. Desative essa opção para não forçar esse comportamento."; +"on_demand.items.add_ssid.caption" = "Adicionar Wi-Fi"; +"on_demand.items.active.caption" = "Confiar"; +"on_demand.items.mobile.caption" = "Rede celular"; +"on_demand.items.ethernet.caption" = "Confiar em ligações com fios"; +"on_demand.items.ethernet.description" = "Assinale para confiar em qualquer ligação com cabo."; +"on_demand.items.policy.caption" = "Trust disables VPN"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Diagnóstico"; +"diagnostics.sections.debug_log.footer" = "O status será escondido após reconectado. Os dados da rede são hostnames, endereços de IP, rotas, SSID. Credenciais e chaves privadas não será logadas em nenhum dos casos."; +"diagnostics.items.server_configuration.caption" = "Configuração do servidor"; +"diagnostics.items.masks_private_data.caption" = "Esconder dados da rede"; +"diagnostics.items.report_issue.caption" = "Reportar problemas de conexão"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "Para garantir uma restauração segura do seu log de debug, você precisa reconectar à VPN."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Log de Debug"; +"debug_log.buttons.copy" = "Copiar"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Reportar problema"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Adicionar atalho"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "Celular"; +"shortcuts.add.items.connect.caption" = "Conectar à"; +"shortcuts.add.items.enable_vpn.caption" = "Ativar VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Desativar VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Confiar na Wi-Fi atual"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Não confiar na Wi-Fi atual"; +"shortcuts.add.items.trust_cellular.caption" = "Confiar em rede celular"; +"shortcuts.add.items.untrust_cellular.caption" = "Não confiar em rede celular"; +"shortcuts.add.alerts.no_profiles.message" = "Ainda não existe nenhum perfil para se conectar."; + +"shortcuts.edit.title" = "Configuração de atalhos"; +"shortcuts.edit.sections.all.header" = "Atalhos existentes"; +"shortcuts.edit.items.add_shortcut.caption" = "Adicionar atalho"; + +// MARK: PaywallView + +"paywall.title" = "Comprar"; +"paywall.sections.products.footer" = "Todo produto é uma compra única. As compras do fornecedor não incluem uma assinatura VPN."; +"paywall.items.loading.caption" = "A carregar produtos"; +"paywall.items.full_version.extra_description" = "Todos os provedores (incluindo os futuros)\n%@"; +"paywall.items.restore.title" = "Restaurar compras"; +"paywall.items.restore.description" = "Se você comprou este aplicativo ou recurso no passado, pode restaurar suas compras e essa tela não será exibida novamente."; + +// MARK: DonateView + +"donate.title" = "Doar"; +"donate.sections.one_time.header" = "Uma vez"; +"donate.sections.one_time.footer" = "Se você deseja mostrar gratidão pelo meu trabalho, aqui estão alguns valores do qual você pode contribuir.\n\nVocé só será cobrado uma única vez, ou doar mais vezes caso desejar."; +"donate.items.loading.caption" = "Carregando doações"; +"donate.items.purchasing.caption" = "Efetuando doação"; +"donate.alerts.purchase.success.title" = "Obrigado"; +"donate.alerts.purchase.success.message" = "Isso significa muito para mim! Espero que você continue usando e promovendo esse aplicativo."; +"donate.alerts.purchase.failure.message" = "Não foi possível realizar doação. %@"; + +// MARK: AboutView + +"about.title" = "Sobre"; +"about.sections.web.header" = "Web"; +"about.sections.share.header" = "Compartilhar"; +"about.items.credits.caption" = "Créditos"; +"about.items.website.caption" = "Home page"; +"about.items.disclaimer.caption" = "Disclaimer"; +"about.items.privacy_policy.caption" = "Política de privacidade"; +"about.items.share_twitter.caption" = "Tweet sobre isso!"; +"about.items.share_generic.caption" = "Convide um amigo"; + +// MARK: AboutView -> VersionView + +"version.title" = "Versão"; +"version.labels.intro" = "Passepartout e TunnelKit são desenvolvidos e mantidos por Davide De Rosa (keeshux).\n\nO código de fonte está disponível no GitHub sobre a licença GPLv3, você pode encontrar links na home page.\n\nPassepartout não é um cliente oficial e não possui nenhuma ligação com a OpenVPN Inc."; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Créditos"; +"credits.sections.licenses.header" = "Licenças"; +"credits.sections.notices.header" = "Notices"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Preferências"; +"preferences.sections.general.header" = "Geral"; +"preferences.items.launches_on_login.caption" = "Iniciar ao iniciar a sessão"; +"preferences.items.launches_on_login.footer" = "Assinale para executar automaticamente a aplicação ao arrancar ou com o início de sessão."; +"preferences.items.confirm_quit.caption" = "Confirmar a saída"; +"preferences.items.confirm_quit.footer" = "Assinale para apresentar um alerta de confirmação da saída."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Mostrar"; +"menu.switch_profile.title" = "Perfil ativo"; +"menu.active_profile.title.none" = "Sem perfil ativo"; +"menu.active_profile.items.customize.title" = "Personalizar..."; +"menu.active_profile.messages.missing_credentials" = "Não há uma conta configurada"; +"menu.organizer.title" = "Organizador"; +"menu.preferences.title" = "Preferências"; +"menu.support.title" = "Apoio"; +"menu.quit.title" = "Sair %@"; +"menu.quit.messages.confirm" = "A VPN, se ativa, ainda vai ser executada em segundo plano. Quer sair?"; diff --git a/Passepartout/App/Shared/L10n/ru.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/ru.lproj/Localizable.strings new file mode 100644 index 00000000..e5c09e33 --- /dev/null +++ b/Passepartout/App/Shared/L10n/ru.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Отменить"; +"global.strings.next" = "Далее"; +/* MARK: Global */ +"global.strings.ok" = "Ок"; +"global.strings.save" = "Сохранить"; +"global.strings.rename" = "Переименовать"; +"global.strings.add" = "Добавить"; +"global.strings.default" = "По умолчанию"; +"global.strings.name" = "Имя"; +"global.strings.address" = "Адрес"; +"global.strings.addresses" = "Адреса"; +"global.strings.port" = "порт"; +"global.strings.protocol" = "Протокол"; +"global.strings.protocols" = "Протоколы"; +"global.strings.enabled" = "Включен"; +"global.strings.disabled" = "Выключен"; +"global.strings.none" = "Нет"; +"global.strings.automatic" = "Автоматически"; +"global.strings.manual" = "Вручную"; +"global.strings.encryption" = "Шифрование"; +"global.strings.reconnect" = "Переподключить"; +"global.strings.servers" = "Серверы"; +"global.strings.domain" = "Домен"; +"global.strings.domains" = "Домены"; +"global.strings.proxy" = "Прокси"; +"global.strings.bytes" = "байты"; +"global.strings.interface" = "Интерфейс"; +"global.strings.private_key" = "Закрытый ключ"; +"global.strings.public_key" = "Открытый ключ"; +"global.strings.endpoint" = "Конечная точка"; +"global.strings.keepalive" = "Поддерживаем"; +"global.strings.advanced" = "Расширенные"; +"global.strings.translations" = "Переводы"; + +"global.messages.email_not_configured" = "E-mail аккаунт не создан."; +"global.messages.share" = "Passepartout - это простой в использовании OpenVPN клиент для iOS и macOS, с открытым исходным кодом"; + +"global.placeholders.profile_name" = "Мой профиль"; + +"global.errors.missing_profile" = "Отсутствует профиль"; +"global.errors.missing_account" = "Отсутствует учетная запись"; +"global.errors.missing_provider_server" = "Отсутствует сервер"; +"global.errors.missing_provider_preset" = "Отсутствует пресет"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Подключается"; +"tunnelkit.vpn.active" = "Активен"; +"tunnelkit.vpn.disconnecting" = "Отключается"; +"tunnelkit.vpn.inactive" = "Не активен"; +"tunnelkit.vpn.disabled" = "Отключен"; +"tunnelkit.vpn.unused" = "Выкл"; + +"tunnelkit.errors.vpn.timeout" = "Тайм-аут"; +"tunnelkit.errors.vpn.dns" = "Ошибка DNS"; +"tunnelkit.errors.vpn.auth" = "Ошибка аутентификации"; +"tunnelkit.errors.vpn.tls" = "Ошибка TSL"; +"tunnelkit.errors.vpn.encryption" = "Ошибка расшифровки"; +"tunnelkit.errors.vpn.compression" = "Сжатие не поддерживается"; +"tunnelkit.errors.vpn.network" = "Изменение сети"; +"tunnelkit.errors.vpn.routing" = "Отсутствует маршрутизация"; +"tunnelkit.errors.vpn.gateway" = "Нет шлюза"; +"tunnelkit.errors.vpn.shutdown" = "Сервер выключен"; + +"tunnelkit.errors.parsing" = "Не получается разобрать предоставленный файл конфигурации (%@)."; +"tunnelkit.errors.openvpn.malformed" = "Файл конфигурации содержит неверную опцию (%@)."; +"tunnelkit.errors.openvpn.required_option" = "Файл конфигурации не содержит необходимую опцию (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "Файл конфигурации содержит неподдерживаемую опцию (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "Файл конфигурации верный, но возможно содержит неподдерживаемую опцию (%@).\n\nСоединение может прерваться - зависит от настроек сервера."; +"tunnelkit.errors.openvpn.passphrase_required" = "Пожалуйста, введите кодовую фразу шифрования"; +"tunnelkit.errors.openvpn.decryption" = "Конфигурация содержит зашифрованный приватный ключ, он не может быть расшифрован. Перепроверьте кодовую фразу."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "Приходите посмотреть, как я делаю Passepartout в прямом эфире на Twitch, присоединяйтесь к чату, чтобы общаться и вносить свой вклад!"; +"organizer.sections.siri.footer" = "Получить помощь Сири, чтобы ускорить частые действия с приложением."; +"organizer.sections.support.header" = "Поддержка"; +"organizer.items.follow_twitch.caption" = "Смотрите Паспарту на Twitch"; +"organizer.items.profile.value.current" = "Используется"; +"organizer.items.siri_shortcuts.caption" = "Управлять коммандами"; +"organizer.items.join_community.caption" = "Вступить в сообщество"; +"organizer.items.write_review.caption" = "Написать отзыв"; +"organizer.items.donate.caption" = "Сделать пожертвование"; +"organizer.items.github_sponsors.caption" = "Поддержите меня на GitHub"; +"organizer.items.translate.caption" = "Помощь с переводом"; +"organizer.items.about.caption" = "Об %@"; +"organizer.items.uninstall.caption" = "Удалить VPN конфигурацию"; +"organizer.items.add_provider.caption" = "Добавить нового провайдера"; +"organizer.items.add_host.caption" = "Добавить из файлов"; + +"organizer.alerts.reddit.message" = "А Вы знали, что Passepartout имеет свой сабреддит? Подписывайтесь для получения обновлений, обсуждения проблем, функций, новых платформ или чего угодно.\n\nЭто также отличный способ показать поддержку проекта."; +"organizer.alerts.reddit.buttons.subscribe" = "Подписаться сейчас!"; +"organizer.alerts.reddit.buttons.remind" = "Напомнить позже"; +"organizer.alerts.reddit.buttons.never" = "Больше не спрашивать"; + +"organizer.alerts.uninstall_vpn.message" = "Вы действительно хотите убрать VPN конфигурацию из настроек устройства? Это может исправить несколько VPN ошибок, но не изменит установки приложения."; +"organizer.alerts.remove_profile.title" = "Удалить профиль"; +"organizer.alerts.remove_profile.message" = "Вы точно хотите удалить профиль %@?"; + +"organizer.menus.add_profile.imported" = "Добавить %@"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "Новый профиль"; +"add_profile.shared.views.existing.header" = "Существующие профили"; +"add_profile.shared.alerts.overwrite.message" = "Профиль с этим именем уже существует. Заменить?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Введите кодовую фразу"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Провайдеры"; +"add_profile.provider.sections.vpn.footer" = "Здесь Вы найдёте несколько провайдеров с уже созданными профилями."; +"add_profile.provider.items.update_list" = "Обновить список"; +"add_profile.provider.errors.no_default_server" = "Не удалось найти сервер."; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Провайдер"; + +"profile.welcome.message" = "Добро пожаловать в Passepartout!\n\nИспользуйте организатор для добавления нового профиля."; + +"profile.sections.vpn.footer" = "Соединение будет установлено при необходимости."; +"profile.sections.status.header" = "Соединение"; +"profile.sections.configuration.header" = "Конфигурация"; +"profile.sections.provider_infrastructure.footer" = "Последнее обновление %@."; +"profile.sections.vpn_survives_sleep.footer" = "Отключите для уменьшения расхода заряда аккумулятора, может привести к временным замедлениям в связи с повторным подключением после \"пробуждения\"."; +"profile.sections.vpn_resolves_hostname.footer" = "Предпочтительно в большинстве сетей и необходимо в некоторых IPv6 сетях. Отключите если  DNS заблокирован, или для увеличения скорости в случае медленных ответов DNS."; +"profile.sections.feedback.header" = "Отзыв"; +"profile.items.use_profile.caption" = "Использовать это профиль."; +"profile.items.vpn_service.caption" = "Включен"; +"profile.items.vpn.turn_on.caption" = "Включить VPN"; +"profile.items.vpn.turn_off.caption" = "Отключить VPN"; +"profile.items.connection_status.caption" = "Статус"; +"profile.items.data_count.caption" = "Переданная информация"; +"profile.items.provider.refresh.caption" = "Обновить инфраструктуру"; +"profile.items.category.caption" = "Категория"; +"profile.items.only_shows_favorites.caption" = "Показывать только места из избранного"; +"profile.items.vpn_survives_sleep.caption" = "Оставлять включенным во время сна"; +"profile.items.vpn_resolves_hostname.caption" = "Разрешить имя хоста сервера"; +"profile.items.reconnect.caption" = "Переподключиться"; + +"profile.alerts.rename.title" = "Переименовать профиль"; +"profile.alerts.reconnect_vpn.message" = "Хотите заново подключиться к VPN?"; +"profile.alerts.test_connectivity.title" = "Связь"; +"profile.alerts.test_connectivity.messages.success" = "Ваше устройство подключено к интернету!"; +"profile.alerts.test_connectivity.messages.failure" = "Ваше устройство не подключено к интернету, пожалйста проверьте установки Вашего профиля."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Местоположение"; +"provider.location.sections.empty_favorites.footer" = "Свайп в лево на локации, чтобы добавить или убрать из избранного."; +"provider.location.actions.favorite" = "Добавить в избранное"; +"provider.location.actions.unfavorite" = "Убрать из избранного"; + +"provider.preset.title" = "Пресет"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Аккаунт"; +"account.sections.credentials.header" = "Данные для входа"; +"account.sections.registration.footer" = "Создайте аккаунт на %@ веб-сайте."; +"account.items.username.caption" = "Логин"; +"account.items.username.placeholder" = "логин"; +"account.items.password.caption" = "Пароль"; +"account.items.password.placeholder" = "пароль"; +"account.items.open_guide.caption" = "Проверьте Ваши данные"; +"account.items.signup.caption" = "Зарегистрируйтесь с %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Используйте Ваши данные для входа с веб-сайта %@."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Используйте свои учетные данные %@ service, которые могут отличаться от учетных данных веб-сайта."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Используйте Ваши данные для входа с веб-сайта %@. Ваш логин обычно числовой с (без пробелов)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Используйте данные для входа на %@ веб-сайт. Ваш логин обычно Ваш e-mail."; +"account.sections.guidance.footer.infrastructure.pia" = "Используйте Ваши данные для входа с веб-сайта %@. Ваш логин обычно числовой с приставкой \"p\"."; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Найдите Ваши данные для входа %@ \"Account > OpenVPN / IKEv2 Username\" секции веб-сайта."; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Используйте данные для входа на %@ веб-сайт. Ваш логин обычно Ваш e-mail."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Используйте данные для входа на %@ веб-сайт. Ваш логин обычно Ваш e-mail."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Найдите Ваши данные для входа %@ в OpenVPN Config Generator на веб-сайте."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Узел"; +"endpoint.wireguard.items.preshared_key.caption" = "Общий ключ"; +"endpoint.wireguard.items.allowed_ip.caption" = "Допустимый IP"; + +"endpoint.advanced.title" = "Техническая информация"; +"endpoint.advanced.openvpn.sections.communication.header" = "Связь"; +"endpoint.advanced.openvpn.sections.reset.footer" = "Если после изменения параметров связи у Вас разорвалось соединение, нажмите, чтобы вернуться к исходной конфигурации."; +"endpoint.advanced.openvpn.sections.compression.header" = "Компресия"; +"endpoint.advanced.openvpn.sections.network.header" = "Сеть"; +"endpoint.advanced.openvpn.sections.other.header" = "Другое"; +"endpoint.advanced.openvpn.items.route.caption" = "Маршрут"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Шифруем"; +"endpoint.advanced.openvpn.items.digest.caption" = "Аутентификация"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Внедрена"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Фрейминг"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Алгоритм"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "Неподдерживаемое"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Сброс конфигурации"; +"endpoint.advanced.openvpn.items.client.caption" = "Сертификат"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Ключ"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Проверено"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "Не проверено"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Упаковываем"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Аутентификация"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Шифрование"; +"endpoint.advanced.openvpn.items.eku.caption" = "Расширенная проверка"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d секунд"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "Перезаключение"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "после %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Рандомная конечная точка"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Сетевые настройки"; +"network_settings.sections.choices.header" = "Игнорировать"; +"network_settings.gateway.title" = "Шлюз по умолчанию"; +"network_settings.proxy.items.bypass_domains.caption" = "Обходные домены"; +"network_settings.items.add_dns_server.caption" = "Добавить адрес"; +"network_settings.items.add_dns_domain.caption" = "Добавить домен поиска"; +"network_settings.items.proxy_bypass.caption" = "Обход домена"; +"network_settings.items.add_proxy_bypass.caption" = "Добавить обходной домен"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Доверенные сети"; +"on_demand.sections.policy.footer" = "При подключении к доверенным сетям VPN обычно выключается, и остаётся отключенным. Отключите эту опцию чтобы оставлять VPN подключенным."; +"on_demand.items.add_ssid.caption" = "Добавить Wi-Fi"; +"on_demand.items.active.caption" = "Доверенные"; +"on_demand.items.mobile.caption" = "Мобильная сеть"; +"on_demand.items.ethernet.caption" = "Доверенные проводные подключения"; +"on_demand.items.ethernet.description" = "Включите, чтобы добавить в доверенные проводное подключение."; +"on_demand.items.policy.caption" = "Дов. сеть отключает VPN"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Диагностика"; +"diagnostics.sections.debug_log.footer" = "Маскировка включится после повторного подключения. Информация о сети - это названия хост профилей, IP адрес, маршрутизация и SSID. Данные для входа и приватные ключи не собираются."; +"diagnostics.items.server_configuration.caption" = "Конфигурация сервера"; +"diagnostics.items.masks_private_data.caption" = "Маскировать информацию сети"; +"diagnostics.items.report_issue.caption" = "Сообщить о проблеме подкл."; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "Для безопасного сброса журнала отладки и изменения маскировки информации сети Вы должны заново подключиться к VPN."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Журнал отладки"; +"debug_log.buttons.copy" = "Копировать"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Сообщить о проблеме"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Создать команду"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "Мобильная сеть"; +"shortcuts.add.items.connect.caption" = "Подключиться к"; +"shortcuts.add.items.enable_vpn.caption" = "Включи VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Выключи VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Доверять текущему Wi-Fi"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Не доверять текущему Wi-Fi"; +"shortcuts.add.items.trust_cellular.caption" = "Доверять мобильной сети"; +"shortcuts.add.items.untrust_cellular.caption" = "Не доверять мобильной сети"; +"shortcuts.add.alerts.no_profiles.message" = "Нет профиля для подключения."; + +"shortcuts.edit.title" = "Управлять командами"; +"shortcuts.edit.sections.all.header" = "Существующие команды"; +"shortcuts.edit.items.add_shortcut.caption" = "Создать команду"; + +// MARK: PaywallView + +"paywall.title" = "Покупка"; +"paywall.sections.products.footer" = "Каждый продукт является разовой покупкой. Покупка провайдера не включает подписку на VPN."; +"paywall.items.loading.caption" = "Загрузка продуктов"; +"paywall.items.full_version.extra_description" = "Все провайдеры (включая добавленных в будущем)\n%@"; +"paywall.items.restore.title" = "Восстановить покупки"; +"paywall.items.restore.description" = "Если Вы купили это приложение или совершили встроенные покупки в прошлом, вы можете восстановить ваши покупки, и этот баннер больше не появится."; + +// MARK: DonateView + +"donate.title" = "Пожертвовать"; +"donate.sections.one_time.header" = "Один раз"; +"donate.sections.one_time.footer" = "Если Вы хотите поблагодарить мою бесплатную работу, здесь есть несколько сумм, которые Вы можете пожертвовать прямо сейчас.\n\nСумма будет списана только один раз, а Вы можете пожертвовать несколько раз."; +"donate.items.loading.caption" = "Загружаем пожертвования"; +"donate.items.purchasing.caption" = "Исполняется"; +"donate.alerts.purchase.success.title" = "Спасибо"; +"donate.alerts.purchase.success.message" = "Это значит многое для меня, и, я надеюсь, Вы продолжить использовать и рассказывать об этом приложении."; +"donate.alerts.purchase.failure.message" = "Не получается совершить пожертвование. %@"; + +// MARK: AboutView + +"about.title" = "О нас"; +"about.sections.web.header" = "Веб"; +"about.sections.share.header" = "Поделиться"; +"about.items.credits.caption" = "Благодарности"; +"about.items.website.caption" = "Домашняя страница"; +"about.items.disclaimer.caption" = "Предупреждение"; +"about.items.privacy_policy.caption" = "Политика конфиденциальности"; +"about.items.share_twitter.caption" = "Твитнуть о нас!"; +"about.items.share_generic.caption" = "Пригласить друга"; + +// MARK: AboutView -> VersionView + +"version.title" = "Версия"; +"version.labels.intro" = "Passepartout и TunnelKit написаны и установлены Davide De Rosa (keeshux).\n\nИсходные коды для Passepartout и TunnelKit публично доступны на GitHub под GPLv3, вы можете найти ссылки на домашней странице.\n\nPassepartout является неофициальным клиентом, и никаким образом не связан с OpenVPN Inc."; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Благодарность"; +"credits.sections.licenses.header" = "Лицензии"; +"credits.sections.notices.header" = "Упоминания"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Настройки"; +"preferences.sections.general.header" = "Общие"; +"preferences.items.launches_on_login.caption" = "Запускать при входе"; +"preferences.items.launches_on_login.footer" = "Включите, чтобы приложение автоматически запускалось при загрузке или входе."; +"preferences.items.confirm_quit.caption" = "Подтверждать выход"; +"preferences.items.confirm_quit.footer" = "Включите, чтобы выход надо было подтверждать."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Показать"; +"menu.switch_profile.title" = "Активный профиль"; +"menu.active_profile.title.none" = "Нет активных профилей"; +"menu.active_profile.items.customize.title" = "Настроить..."; +"menu.active_profile.messages.missing_credentials" = "Нет настроенных аккаунтов"; +"menu.organizer.title" = "Организатор"; +"menu.preferences.title" = "Настройки"; +"menu.support.title" = "Поддержка"; +"menu.quit.title" = "Выйти из %@"; +"menu.quit.messages.confirm" = "Если включить VPN, он всё равно будет работать в фоновом режиме. Вы точно хотите выйти?"; diff --git a/Passepartout/App/Shared/L10n/sv.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/sv.lproj/Localizable.strings new file mode 100644 index 00000000..4c958cbd --- /dev/null +++ b/Passepartout/App/Shared/L10n/sv.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "Avbryt"; +"global.strings.next" = "Nästa"; +/* MARK: Global */ +"global.strings.ok" = "OK"; +"global.strings.save" = "Spara"; +"global.strings.rename" = "Byt namn"; +"global.strings.add" = "Lägg till"; +"global.strings.default" = "Default"; +"global.strings.name" = "Namn"; +"global.strings.address" = "Adress"; +"global.strings.addresses" = "Adresser"; +"global.strings.port" = "Port"; +"global.strings.protocol" = "Protokoll"; +"global.strings.protocols" = "Protokoll"; +"global.strings.enabled" = "Aktiverad"; +"global.strings.disabled" = "Inaktiverad"; +"global.strings.none" = "Ingen"; +"global.strings.automatic" = "Automatiskt"; +"global.strings.manual" = "Manuellt"; +"global.strings.encryption" = "Kryptering"; +"global.strings.reconnect" = "Reconnect"; +"global.strings.servers" = "Servrar"; +"global.strings.domain" = "Domain"; +"global.strings.domains" = "Domäner"; +"global.strings.proxy" = "Proxy"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "Gränssnitt"; +"global.strings.private_key" = "Privat nyckel"; +"global.strings.public_key" = "Publik nyckel"; +"global.strings.endpoint" = "Slutpunkt"; +"global.strings.keepalive" = "hål-vid-liv"; +"global.strings.advanced" = "Avancerat"; +"global.strings.translations" = "Translations"; + +"global.messages.email_not_configured" = "Inget e-postkonto är konfigurerat."; +"global.messages.share" = "Passepartout är en användarvänlig öppen källkod OpenVPN-klient för iOS och macOS"; + +"global.placeholders.profile_name" = "Min profil"; + +"global.errors.missing_profile" = "Profil saknas"; +"global.errors.missing_account" = "Konto saknas"; +"global.errors.missing_provider_server" = "Server saknas"; +"global.errors.missing_provider_preset" = "Förinställning saknas"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "Anslutning"; +"tunnelkit.vpn.active" = "Aktiv"; +"tunnelkit.vpn.disconnecting" = "Koppla från"; +"tunnelkit.vpn.inactive" = "Inaktiv"; +"tunnelkit.vpn.disabled" = "Inaktiverad"; +"tunnelkit.vpn.unused" = "Av"; + +"tunnelkit.errors.vpn.timeout" = "Timeout"; +"tunnelkit.errors.vpn.dns" = "DNS misslyckades"; +"tunnelkit.errors.vpn.auth" = "Auth failed"; +"tunnelkit.errors.vpn.tls" = "TLS misslyckades"; +"tunnelkit.errors.vpn.encryption" = "Kryptering misslyckades"; +"tunnelkit.errors.vpn.compression" = "Komprimering utan stöd"; +"tunnelkit.errors.vpn.network" = "Nätverk ändrat"; +"tunnelkit.errors.vpn.routing" = "Saknad routing"; +"tunnelkit.errors.vpn.gateway" = "Ingen gateway"; +"tunnelkit.errors.vpn.shutdown" = "Servern stängs av"; + +"tunnelkit.errors.parsing" = "Det gick inte att analysera den angivna konfigurationsfilen (%@)."; +"tunnelkit.errors.openvpn.malformed" = "Konfigurationsfilen innehåller ett felaktigt val (%@)."; +"tunnelkit.errors.openvpn.required_option" = "Konfigurationsfilen saknar ett obligatoriskt val (%@)."; +"tunnelkit.errors.openvpn.unsupported_option" = "Konfigurationsfilen innehåller ett stöd som inte stöds (%@)."; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "Konfigurationsfilen är korrekt men innehåller ett eventuellt stöd som inte stöds (%@). \n\nConnectivity kan bryta beroende på serverinställningar."; +"tunnelkit.errors.openvpn.passphrase_required" = "Var god ange krypteringslösenfrasen."; +"tunnelkit.errors.openvpn.decryption" = "Konfigurationen innehåller en krypterad privat nyckel och den kan inte dekrypteras. Kontrollera din inmatade lösenfras."; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "Kom och se mig göra Passepartout live på Twitch, gå med i chatten för att interagera och bidra!"; +"organizer.sections.siri.footer" = "Få hjälp från Siri för att påskynda dina vanligaste interaktioner med appen."; +"organizer.sections.support.header" = "Support"; +"organizer.items.follow_twitch.caption" = "Se Passepartout on Twitch"; +"organizer.items.profile.value.current" = "Under användning"; +"organizer.items.siri_shortcuts.caption" = "Hantera genvägar"; +"organizer.items.join_community.caption" = "Gå med i communityn"; +"organizer.items.write_review.caption" = "Skriv en recension"; +"organizer.items.donate.caption" = "Gör en donation"; +"organizer.items.github_sponsors.caption" = "Stötta mig på GitHub"; +"organizer.items.translate.caption" = "Erbjuda att översätta"; +"organizer.items.about.caption" = "Om %@"; +"organizer.items.uninstall.caption" = "Ta bort VPN-konfiguration"; +"organizer.items.add_provider.caption" = "Lägg till ny leverantör"; +"organizer.items.add_host.caption" = "Lägg till från Filer"; + +"organizer.alerts.reddit.message" = "Visste du att Passepartout har en subreddit? Prenumerera på uppdateringar eller diskutera problem, funktioner, nya plattformar eller vad du vill. \n\nDet är också ett bra sätt att visa dig bryr dig om detta projekt."; +"organizer.alerts.reddit.buttons.subscribe" = "Prenumerera nu!"; +"organizer.alerts.reddit.buttons.remind" = "Påminn mig senare"; +"organizer.alerts.reddit.buttons.never" = "Fråga inte igen"; + +"organizer.alerts.uninstall_vpn.message" = "Vill du verkligen radera VPN-konfigurationen från enhetens inställningar? Detta kan fixa några trasiga VPN tillstånd och påverkar inte dina leverantörs- och värdprofiler."; +"organizer.alerts.remove_profile.title" = "Ta bort profil"; +"organizer.alerts.remove_profile.message" = "Är det säkert att du vill ta bort profilen %@?"; + +"organizer.menus.add_profile.imported" = "Lägg till %@"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "Ny profil"; +"add_profile.shared.views.existing.header" = "Befintliga profiler"; +"add_profile.shared.alerts.overwrite.message" = "En profil med samma namn finns redan. Vill du ersätta den?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "Ange lösenfras"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "Leverantörer"; +"add_profile.provider.sections.vpn.footer" = "Här hittar du några leverantörer med förinställda konfigurationsprofiler."; +"add_profile.provider.items.update_list" = "Uppdatera listan"; +"add_profile.provider.errors.no_default_server" = "Ingen server hittades."; + +// MARK: ProfileView + +"profile.sections.provider.header" = "Leverantör"; + +"profile.welcome.message" = "Välkommen till Passepartout! \n\nAnvänd arrangören för att lägga till en ny profil."; + +"profile.sections.vpn.footer" = "Anslutningen kommer att upprättas vid behov."; +"profile.sections.status.header" = "Koppling"; +"profile.sections.configuration.header" = "Konfiguration"; +"profile.sections.provider_infrastructure.footer" = "Senast uppdaterad på %@."; +"profile.sections.vpn_survives_sleep.footer" = "Inaktivera för att förbättra batterianvändningen, på bekostnad av tillfälliga avmattningar på grund av återuppkoppling."; +"profile.sections.vpn_resolves_hostname.footer" = "Föredragna i de flesta nätverk och krävs i vissa IPv6-nätverk. Inaktivera var DNS blockeras eller för att påskynda förhandlingar när DNS är långsamt att svara."; +"profile.sections.feedback.header" = "Feedback"; +"profile.items.use_profile.caption" = "Använd den här profilen"; +"profile.items.vpn_service.caption" = "Aktiverad"; +"profile.items.vpn.turn_on.caption" = "Aktivera VPN"; +"profile.items.vpn.turn_off.caption" = "Inaktivera VPN"; +"profile.items.connection_status.caption" = "Status"; +"profile.items.data_count.caption" = "Utbyttad data"; +"profile.items.provider.refresh.caption" = "Uppdatera infrastruktur"; +"profile.items.category.caption" = "Kategori"; +"profile.items.only_shows_favorites.caption" = "Visa endast favoritplatser"; +"profile.items.vpn_survives_sleep.caption" = "Håll dig levande i sömnen"; +"profile.items.vpn_resolves_hostname.caption" = "Lösa server värdnamn"; +"profile.items.reconnect.caption" = "Återanslut"; + +"profile.alerts.rename.title" = "Byt namn på profil"; +"profile.alerts.reconnect_vpn.message" = "Vill du återansluta till VPN?"; +"profile.alerts.test_connectivity.title" = "Anslutningar"; +"profile.alerts.test_connectivity.messages.success" = "Din enhet är ansluten till Internet!"; +"profile.alerts.test_connectivity.messages.failure" = "Din enhet har ingen Internetanslutning, var god granska dina profilparametrar."; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "Plats"; +"provider.location.sections.empty_favorites.footer" = "Dra åt vänster på en plats för att lägga till eller ta bort den från favoriter."; +"provider.location.actions.favorite" = "Favorit"; +"provider.location.actions.unfavorite" = "Inte favorit"; + +"provider.preset.title" = "Förinställt"; + +// MARK: ProfileView -> AccountView + +"account.title" = "Konto"; +"account.sections.credentials.header" = "Referenser"; +"account.sections.registration.footer" = "Hämta ett konto på %@ webbplatsen."; +"account.items.username.caption" = "Användarnamn"; +"account.items.username.placeholder" = "användarnamn"; +"account.items.password.caption" = "Lösenord"; +"account.items.password.placeholder" = "hemlighet"; +"account.items.open_guide.caption" = "Visa dina uppgifter"; +"account.items.signup.caption" = "Registrera med %@"; + +"account.sections.guidance.footer.infrastructure.default.web" = "Använd dina %@ webbplatsuppgifter."; +"account.sections.guidance.footer.infrastructure.default.specific" = "Använd dina %@ service-referenser, som kan skilja sig från webbplatsens referenser."; +"account.sections.guidance.footer.infrastructure.mullvad" = "Använd dina %@ webbplatsuppgifter. Ditt användarnamn är vanligtvis numeriskt (utan utrymmen)."; +"account.sections.guidance.footer.infrastructure.nordvpn" = "Använd dina %@ webbplatsuppgifter. Ditt användarnamn är vanligtvis ditt e-postmeddelande."; +"account.sections.guidance.footer.infrastructure.pia" = "Använd dina %@ webbplatsuppgifter. Ditt användarnamn är vanligtvis numeriskt med ett prefix för \" p \"."; +"account.sections.guidance.footer.infrastructure.protonvpn" = "Hitta dina %@ credentials i avsnittet \" Konto> OpenVPN / IKEv2 Användarnamn \"på webbplatsen."; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "Använd dina %@ webbplatsuppgifter. Ditt användarnamn är vanligtvis ditt e-postmeddelande."; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "Använd dina %@ webbplatsuppgifter. Ditt användarnamn är vanligtvis ditt e-postmeddelande."; +"account.sections.guidance.footer.infrastructure.windscribe" = "Hitta din %@ credentials i OpenVPN Config Generator på webbplatsen."; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "Peer"; +"endpoint.wireguard.items.preshared_key.caption" = "Tidigare delad nyckel"; +"endpoint.wireguard.items.allowed_ip.caption" = "Tillåtet IP"; + +"endpoint.advanced.title" = "Tekniska detaljer"; +"endpoint.advanced.openvpn.sections.communication.header" = "Communication"; +"endpoint.advanced.openvpn.sections.reset.footer" = "Om du slutade med bruten anslutning efter att ha ändrat kommunikationsparametrarna trycker du på för att återgå till den ursprungliga konfigurationen."; +"endpoint.advanced.openvpn.sections.compression.header" = "Kompression"; +"endpoint.advanced.openvpn.sections.network.header" = "Nätverk"; +"endpoint.advanced.openvpn.sections.other.header" = "Other"; +"endpoint.advanced.openvpn.items.route.caption" = "Rutt"; +"endpoint.advanced.openvpn.items.cipher.caption" = "Cipher"; +"endpoint.advanced.openvpn.items.digest.caption" = "Autentisering"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "Inbäddad"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "Inramning"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "Algoritm"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "Utan stöd"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "Återställ konfiguration"; +"endpoint.advanced.openvpn.items.client.caption" = "Certifikat"; +"endpoint.advanced.openvpn.items.client_key.caption" = "Nyckel"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "Verified"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "Ej verifierad"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "Omslagning"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "Autentisering"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "Kryptering"; +"endpoint.advanced.openvpn.items.eku.caption" = "Förlängd verifering"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d seconds"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "Omförhandling"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "efter %@"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "Omställ slutpunkt på slumpmässigt sätt"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "Nätverksinställningar"; +"network_settings.sections.choices.header" = "Åsidosätt"; +"network_settings.gateway.title" = "Normal gateway"; +"network_settings.proxy.items.bypass_domains.caption" = "Kringgå domäner"; +"network_settings.items.add_dns_server.caption" = "Lägg till adress"; +"network_settings.items.add_dns_domain.caption" = "Lägg till domän"; +"network_settings.items.proxy_bypass.caption" = "Bypass-domän"; +"network_settings.items.add_proxy_bypass.caption" = "Add bypass domain"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "Tillförlitliga nätverk"; +"on_demand.sections.policy.footer" = "När du advänder ett betrott nätverk, VPN:et stängs normalt och hålls bortkopplat. Avaktivera detta alternativet för att inte genomdriva sådant beteende."; +"on_demand.items.add_ssid.caption" = "Lägg till Wi-Fi"; +"on_demand.items.active.caption" = "Betrodda"; +"on_demand.items.mobile.caption" = "Mobilt nätverk"; +"on_demand.items.ethernet.caption" = "Lita på kabelanslutna uppkopplingar"; +"on_demand.items.ethernet.description" = "Markera för att lita på alla kabelanslutna uppkopplingar."; +"on_demand.items.policy.caption" = "Förtroende inaktiverar VPN"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "Diagnostics"; +"diagnostics.sections.debug_log.footer" = "Masking status kommer att fungera efter återanslutning. Nätverksdata är värdnamn, IP-adresser, routing, SSID. Referenser och privata nycklar loggas inte oavsett."; +"diagnostics.items.server_configuration.caption" = "Server konfiguration"; +"diagnostics.items.masks_private_data.caption" = "Mask nätverksdata"; +"diagnostics.items.report_issue.caption" = "Rapportera anslutningsproblem"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "För att säkert återställa den aktuella felsökningsloggen och tillämpa den nya maskeringspreferensen måste du återansluta till VPN nu."; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "Debug log"; +"debug_log.buttons.copy" = "Kopiera"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "Rapportera problem"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "Lägg till genväg"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "Cellular"; +"shortcuts.add.items.connect.caption" = "Anslut till"; +"shortcuts.add.items.enable_vpn.caption" = "Aktivera VPN"; +"shortcuts.add.items.disable_vpn.caption" = "Inaktivera VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "Lita på nuvarande Wi-Fi"; +"shortcuts.add.items.untrust_current_wifi.caption" = "Avaktivera nuvarande Wi-Fi"; +"shortcuts.add.items.trust_cellular.caption" = "Lita på mobilnätverk"; +"shortcuts.add.items.untrust_cellular.caption" = "Untrust cellular network"; +"shortcuts.add.alerts.no_profiles.message" = "Det finns ingen profil att ansluta till."; + +"shortcuts.edit.title" = "Hantera genvägar"; +"shortcuts.edit.sections.all.header" = "Befintliga genvägar"; +"shortcuts.edit.items.add_shortcut.caption" = "Lägg till genväg"; + +// MARK: PaywallView + +"paywall.title" = "Köp"; +"paywall.sections.products.footer" = "Varje produkt är ett engångsköp. Leverantörsköp inkluderar inte ett VPN-abonnemang."; +"paywall.items.loading.caption" = "Laddar produkter"; +"paywall.items.full_version.extra_description" = "Alla leverantörer (inklusive framtida)\n%@"; +"paywall.items.restore.title" = "Återställ köp"; +"paywall.items.restore.description" = "Om du köpte den här appen eller funktionen tidigare kan du återställa dina inköp och den här skärmen visas inte igen."; + +// MARK: DonateView + +"donate.title" = "Donera"; +"donate.sections.one_time.header" = "En gång"; +"donate.sections.one_time.footer" = "Om du vill visa tacksamhet för mitt fria arbete, här är några belopp du kan donera direkt. \n\nDu betalas endast en gång per donation, och du kan donera flera gånger. "; +"donate.items.loading.caption" = "Laddar donationer"; +"donate.items.purchasing.caption" = "Performing donation"; +"donate.alerts.purchase.success.title" = "Tack"; +"donate.alerts.purchase.success.message" = "Detta betyder mycket för mig och jag hoppas verkligen att du fortsätter att använda och marknadsföra denna app."; +"donate.alerts.purchase.failure.message" = "Kan inte göra donationen. %@"; + +// MARK: AboutView + +"about.title" = "About"; +"about.sections.web.header" = "Web"; +"about.sections.share.header" = "Dela"; +"about.items.credits.caption" = "Credits"; +"about.items.website.caption" = "Hemsida"; +"about.items.disclaimer.caption" = "Disclaimer"; +"about.items.privacy_policy.caption" = "Sekretesspolicy"; +"about.items.share_twitter.caption" = "Tweet om det!"; +"about.items.share_generic.caption" = "Bjud in en vän"; + +// MARK: AboutView -> VersionView + +"version.title" = "Version"; +"version.labels.intro" = "Passepartout och TunnelKit skrivs och underhålls av Davide De Rosa (keeshux). \n\nKällkod för Passepartout och TunnelKit är offentligt tillgänglig på GitHub under GPLv3, du kan hitta länkar på hemsidan. \n\nPassepartout är en icke-officiell klient och är inte på något sätt ansluten till OpenVPN Inc. "; + +// MARK: AboutView -> CreditsView + +"credits.title" = "Credits"; +"credits.sections.licenses.header" = "Licenses"; +"credits.sections.notices.header" = "Meddelanden"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "Inställningar"; +"preferences.sections.general.header" = "Allmänt"; +"preferences.items.launches_on_login.caption" = "Öppna vid inloggning"; +"preferences.items.launches_on_login.footer" = "Markera för att starta appen automatiskt efter omstart eller vid inloggning."; +"preferences.items.confirm_quit.caption" = "Bekräfta lämna"; +"preferences.items.confirm_quit.footer" = "Markera för att visa en uppmaning att bekräfta att man vill lämna appen."; + +// MARK: Menu (macOS) + +"menu.show.title" = "Visa"; +"menu.switch_profile.title" = "Aktiv profil"; +"menu.active_profile.title.none" = "Ingen aktiv profil"; +"menu.active_profile.items.customize.title" = "Anpassa"; +"menu.active_profile.messages.missing_credentials" = "Inget konto har konfigurerats"; +"menu.organizer.title" = "Organisatör"; +"menu.preferences.title" = "Inställningar"; +"menu.support.title" = "Support"; +"menu.quit.title" = "Lämna %@"; +"menu.quit.messages.confirm" = "Om ett VPN är aktiverat kommer detta fortfarande att köra i bakgrunden. Vill du lämna?"; diff --git a/Passepartout/App/Shared/L10n/zh-Hans.lproj/Localizable.strings b/Passepartout/App/Shared/L10n/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000..4f23d8dd --- /dev/null +++ b/Passepartout/App/Shared/L10n/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,344 @@ +// +// Localizable.strings +// Passepartout +// +// Created by Davide De Rosa on 6/13/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// + +"global.strings.cancel" = "取消"; +"global.strings.next" = "下一步"; +/* MARK: Global */ +"global.strings.ok" = "好的"; +"global.strings.save" = "保存"; +"global.strings.rename" = "重命名"; +"global.strings.add" = "添加"; +"global.strings.default" = "默认"; +"global.strings.name" = "名称"; +"global.strings.address" = "地址"; +"global.strings.addresses" = "地址"; +"global.strings.port" = "端口"; +"global.strings.protocol" = "协议"; +"global.strings.protocols" = "协议"; +"global.strings.enabled" = "已启用"; +"global.strings.disabled" = "未启用"; +"global.strings.none" = "无"; +"global.strings.automatic" = "自动设置"; +"global.strings.manual" = "手动设置"; +"global.strings.encryption" = "加密"; +"global.strings.reconnect" = "重连"; +"global.strings.servers" = "服务器"; +"global.strings.domain" = "域名"; +"global.strings.domains" = "域"; +"global.strings.proxy" = "代理"; +"global.strings.bytes" = "Bytes"; +"global.strings.interface" = "界面"; +"global.strings.private_key" = "密钥"; +"global.strings.public_key" = "公共密钥"; +"global.strings.endpoint" = "终端"; +"global.strings.keepalive" = "保持活跃状态"; +"global.strings.advanced" = "高级"; +"global.strings.translations" = "翻译"; + +"global.messages.email_not_configured" = "未配置e-mail账户。"; +"global.messages.share" = "Passepartout是iOS和macOS平台下的用户友的、开源的OpenVPN客户端"; + +"global.placeholders.profile_name" = "我的配置"; + +"global.errors.missing_profile" = "缺少配置"; +"global.errors.missing_account" = "缺少帐户"; +"global.errors.missing_provider_server" = "缺少服务器"; +"global.errors.missing_provider_preset" = "缺少预设"; + +// MARK: TunnelKit + +"tunnelkit.vpn.connecting" = "连接中"; +"tunnelkit.vpn.active" = "活动的"; +"tunnelkit.vpn.disconnecting" = "断开中"; +"tunnelkit.vpn.inactive" = "不活动"; +"tunnelkit.vpn.disabled" = "未启用"; +"tunnelkit.vpn.unused" = "关"; + +"tunnelkit.errors.vpn.timeout" = "超时"; +"tunnelkit.errors.vpn.dns" = "DNS解析失败"; +"tunnelkit.errors.vpn.auth" = "身份验证失败"; +"tunnelkit.errors.vpn.tls" = "TLS连接失败"; +"tunnelkit.errors.vpn.encryption" = "加密失败"; +"tunnelkit.errors.vpn.compression" = "压缩方式不支持"; +"tunnelkit.errors.vpn.network" = "网络更改"; +"tunnelkit.errors.vpn.routing" = "缺少路由"; +"tunnelkit.errors.vpn.gateway" = "无网关"; +"tunnelkit.errors.vpn.shutdown" = "服务器关闭"; + +"tunnelkit.errors.parsing" = "无法解析提供的配置文件(%@)。"; +"tunnelkit.errors.openvpn.malformed" = "此配置文件包含格式错误的选项(%@)。"; +"tunnelkit.errors.openvpn.required_option" = "此配置文件缺少必须的选项(%@)。"; +"tunnelkit.errors.openvpn.unsupported_option" = "此配置文件包含不支持的选项(%@)。"; +"tunnelkit.errors.openvpn.potentially_unsupported_option" = "配置文件正确但包含不支持的选项(%@)。\n\n根据服务端的设置连接可能会断开。"; +"tunnelkit.errors.openvpn.passphrase_required" = "请输入加密密码"; +"tunnelkit.errors.openvpn.decryption" = "该配置包含加密私钥且不能被解密。请再次检查输入的密钥。"; + +// MARK: OrganizerView + +"organizer.sections.twitch.footer" = "快来看我让Passepartout在Twitch上直播,加入聊天互动并做出贡献!"; +"organizer.sections.siri.footer" = "通过Siri来帮助你加快常用的操作。"; +"organizer.sections.support.header" = "支持"; +"organizer.items.follow_twitch.caption" = "在Twitch上观看路路通"; +"organizer.items.profile.value.current" = "使用中"; +"organizer.items.siri_shortcuts.caption" = "管理捷径"; +"organizer.items.join_community.caption" = "加入社区"; +"organizer.items.write_review.caption" = "写下想法"; +"organizer.items.donate.caption" = "提供捐助"; +"organizer.items.github_sponsors.caption" = "在GitHub上支持我"; +"organizer.items.translate.caption" = "提供翻译"; +"organizer.items.about.caption" = "关于 %@"; +"organizer.items.uninstall.caption" = "移除VPN配置"; +"organizer.items.add_provider.caption" = "添加新的提供商配置"; +"organizer.items.add_host.caption" = "从文件添加"; + +"organizer.alerts.reddit.message" = "你知道Passepartout有一个subreddit吗?可以在上面讨论更新、问题、功能、新的平台等n任何你想要的。\n\n这同样是表达你对此项目关注的地方。"; +"organizer.alerts.reddit.buttons.subscribe" = "立即订阅!"; +"organizer.alerts.reddit.buttons.remind" = "稍后提醒我"; +"organizer.alerts.reddit.buttons.never" = "不要再问"; + +"organizer.alerts.uninstall_vpn.message" = "你确定要从此设备移除VPN配置吗?这可能会固定一些断开的VPN状态,不会影响已经存在的配置。"; +"organizer.alerts.remove_profile.title" = "删除配置"; +"organizer.alerts.remove_profile.message" = "确定要删除配置%@吗?"; + +"organizer.menus.add_profile.imported" = "添加%@"; + +// MARK: AddProfileView + +"add_profile.shared.title" = "新的配置"; +"add_profile.shared.views.existing.header" = "已存在的配置"; +"add_profile.shared.alerts.overwrite.message" = "已经存在同名的配置。要替换吗?"; + +// MARK: AddHostView + +"add_profile.host.sections.encryption.footer" = "输入密码"; + +// MARK: AddProviderView + +"add_profile.provider.sections.providers.header" = "提供商"; +"add_profile.provider.sections.vpn.footer" = "在这里你可以找到预设的新的提供商配置。"; +"add_profile.provider.items.update_list" = "更新列表"; +"add_profile.provider.errors.no_default_server" = "未找到任何服务器"; + +// MARK: ProfileView + +"profile.sections.provider.header" = "提供商"; + +"profile.welcome.message" = "欢迎使用Passepartout!\n\n使用分类页面来添加一个新的配置。"; + +"profile.sections.vpn.footer" = "必要时连接将会建立。"; +"profile.sections.status.header" = "连接"; +"profile.sections.configuration.header" = "配置"; +"profile.sections.provider_infrastructure.footer" = "最后在%@时更新"; +"profile.sections.vpn_survives_sleep.footer" = "禁用以减少电池消耗,由于存在可能的唤醒时重连消耗。"; +"profile.sections.vpn_resolves_hostname.footer" = "推荐在大部分的网络中打开,并要求在IPv6环境下。当DNS被阻断或相应缓慢时禁用。"; +"profile.sections.feedback.header" = "反馈"; +"profile.items.use_profile.caption" = "使用此配置"; +"profile.items.vpn_service.caption" = "已启用"; +"profile.items.vpn.turn_on.caption" = "启用VPN"; +"profile.items.vpn.turn_off.caption" = "禁用VPN"; +"profile.items.connection_status.caption" = "状态"; +"profile.items.data_count.caption" = "已交换数据量"; +"profile.items.provider.refresh.caption" = "刷新基础设置"; +"profile.items.category.caption" = "类别"; +"profile.items.only_shows_favorites.caption" = "仅显示收藏的地点"; +"profile.items.vpn_survives_sleep.caption" = "休眠时保持连接"; +"profile.items.vpn_resolves_hostname.caption" = "解析服务器主机名"; +"profile.items.reconnect.caption" = "重连"; + +"profile.alerts.rename.title" = "重命名配置"; +"profile.alerts.reconnect_vpn.message" = "要重连VPN吗?"; +"profile.alerts.test_connectivity.title" = "连接性"; +"profile.alerts.test_connectivity.messages.success" = "你的设备连接到了网络!"; +"profile.alerts.test_connectivity.messages.failure" = "你的设备没有连接到网络,请检查你的配置参数。"; + +// MARK: ProfileView -> Provider*View + +"provider.location.title" = "位置"; +"provider.location.sections.empty_favorites.footer" = "向左轻扫以将其从最喜爱列表中移除或添加。"; +"provider.location.actions.favorite" = "最喜爱"; +"provider.location.actions.unfavorite" = "不喜爱"; + +"provider.preset.title" = "预设"; + +// MARK: ProfileView -> AccountView + +"account.title" = "账户"; +"account.sections.credentials.header" = "认证方式"; +"account.sections.registration.footer" = "在%@网站上获取一个账户"; +"account.items.username.caption" = "用户名"; +"account.items.username.placeholder" = "username"; +"account.items.password.caption" = "密码"; +"account.items.password.placeholder" = "secret"; +"account.items.open_guide.caption" = "查看你的认证信息"; +"account.items.signup.caption" = "以%@注册"; + +"account.sections.guidance.footer.infrastructure.default.web" = "使用你的%@网站认证信息。"; +"account.sections.guidance.footer.infrastructure.default.specific" = "使用您的%@服务凭据,该凭据可能与网站凭据不同。"; +"account.sections.guidance.footer.infrastructure.mullvad" = "使用你的%@网站认证信息。你的用户名一般是数字 (没有空格)。"; +"account.sections.guidance.footer.infrastructure.nordvpn" = "使用你的%@网站认证信息。你的用户名一般是你的e-mail。"; +"account.sections.guidance.footer.infrastructure.pia" = "使用你的%@网站认证信息。你的用户名一般是数字且带有前缀\"p\"。"; +"account.sections.guidance.footer.infrastructure.protonvpn" = "在网站的\"Account > OpenVPN / IKEv2 Username\"部分找到你的%@的认证信息。"; +"account.sections.guidance.footer.infrastructure.tunnelbear" = "使用你的%@网站认证信息。你的用户名一般是你的e-mail。"; +"account.sections.guidance.footer.infrastructure.vyprvpn" = "使用你的%@网站认证信息。你的用户名一般是你的e-mail。"; +"account.sections.guidance.footer.infrastructure.windscribe" = "在网站上的OpenVPN配置生成器中找到你的%@认证信息。 "; + +// MARK: ProfileView -> EndpointView + +"endpoint.wireguard.items.peer.caption" = "同级"; +"endpoint.wireguard.items.preshared_key.caption" = "预共用密码"; +"endpoint.wireguard.items.allowed_ip.caption" = "允许的IP"; + +"endpoint.advanced.title" = "技术细节"; +"endpoint.advanced.openvpn.sections.communication.header" = "通信"; +"endpoint.advanced.openvpn.sections.reset.footer" = "如果你在更改连接参数后变成断开状态,点按已恢复到原始的配置。"; +"endpoint.advanced.openvpn.sections.compression.header" = "压缩"; +"endpoint.advanced.openvpn.sections.network.header" = "网络"; +"endpoint.advanced.openvpn.sections.other.header" = "其他"; +"endpoint.advanced.openvpn.items.route.caption" = "路由"; +"endpoint.advanced.openvpn.items.cipher.caption" = "加密"; +"endpoint.advanced.openvpn.items.digest.caption" = "身份验证"; +"endpoint.advanced.openvpn.items.digest.value.embedded" = "已包含"; +"endpoint.advanced.openvpn.items.compression_framing.caption" = "分帧"; +"endpoint.advanced.openvpn.items.compression_algorithm.caption" = "算法"; +"endpoint.advanced.openvpn.items.compression_algorithm.value.other" = "未支持"; +"endpoint.advanced.openvpn.items.reset_original.caption" = "重置配置"; +"endpoint.advanced.openvpn.items.client.caption" = "证书"; +"endpoint.advanced.openvpn.items.client_key.caption" = "密钥"; +"endpoint.advanced.openvpn.items.client.value.enabled" = "已验证"; +"endpoint.advanced.openvpn.items.client.value.disabled" = "未验证"; +"endpoint.advanced.openvpn.items.tls_wrapping.caption" = "组包"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.auth" = "身份验证"; +"endpoint.advanced.openvpn.items.tls_wrapping.value.crypt" = "加密"; +"endpoint.advanced.openvpn.items.eku.caption" = "扩展身份验证"; +"endpoint.advanced.openvpn.items.keep_alive.value.seconds" = "%d秒"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.caption" = "重协商"; +"endpoint.advanced.openvpn.items.renegotiation_seconds.value.after" = "在%@之后"; +"endpoint.advanced.openvpn.items.random_endpoint.caption" = "随机的服务端"; + +// MARK: ProfileView -> NetworkSettingsView + +"network_settings.title" = "网络设置"; +"network_settings.sections.choices.header" = "覆盖"; +"network_settings.gateway.title" = "默认网关"; +"network_settings.proxy.items.bypass_domains.caption" = "旁路域"; +"network_settings.items.add_dns_server.caption" = "添加地址"; +"network_settings.items.add_dns_domain.caption" = "添加搜索域名"; +"network_settings.items.proxy_bypass.caption" = "旁路域名"; +"network_settings.items.add_proxy_bypass.caption" = "添加旁路域名"; + +// MARK: ProfileView -> OnDemandView + +"on_demand.title" = "可信网络"; +"on_demand.sections.policy.footer" = "当进入可信网络后,VPN将会断开并保持断开状态。禁用此选项可关闭此功能。"; +"on_demand.items.add_ssid.caption" = "新增Wi-Fi"; +"on_demand.items.active.caption" = "信任"; +"on_demand.items.mobile.caption" = "蜂窝网络"; +"on_demand.items.ethernet.caption" = "信任有线连接"; +"on_demand.items.ethernet.description" = "选中以信任所有有线连接。"; +"on_demand.items.policy.caption" = "信任网络中禁用VPN"; + +// MARK: ProfileView -> DiagnosticsView + +"diagnostics.title" = "分析数据"; +"diagnostics.sections.debug_log.footer" = "在重连后状态隐藏才有效。网络数据包括主机名、IP地址、路由、SSID、认证方式,但私钥不会出现在日志中。"; +"diagnostics.items.server_configuration.caption" = "服务端配置"; +"diagnostics.items.masks_private_data.caption" = "隐藏网络数据"; +"diagnostics.items.report_issue.caption" = "报告连接问题"; + +"diagnostics.alerts.masks_private_data.messages.must_reconnect" = "为了安全地重置当前调试日志并应用新的掩码设置,你现在必须重连VPN。"; + +// MARK: DiagnosticsView -> DebugLogView + +"debug_log.title" = "调试日志"; +"debug_log.buttons.copy" = "复制"; + +// MARK: DiagnosticsView -> ReportIssueView + +"report_issue.alert.title" = "提交问题"; +// MARK: ShortcutsView + +"shortcuts.add.title" = "添加捷径"; +"shortcuts.add.sections.wifi.header" = "Wi-Fi"; +"shortcuts.add.sections.cellular.header" = "蜂窝网络"; +"shortcuts.add.items.connect.caption" = "连接到"; +"shortcuts.add.items.enable_vpn.caption" = "开启VPN"; +"shortcuts.add.items.disable_vpn.caption" = "关闭VPN"; +"shortcuts.add.items.trust_current_wifi.caption" = "信任当前Wi-Fi"; +"shortcuts.add.items.untrust_current_wifi.caption" = "不信任当前Wi-Fi"; +"shortcuts.add.items.trust_cellular.caption" = "信任蜂窝网络"; +"shortcuts.add.items.untrust_cellular.caption" = "不信任蜂窝网络"; +"shortcuts.add.alerts.no_profiles.message" = "没有可以连接的配置。"; + +"shortcuts.edit.title" = "管理捷径"; +"shortcuts.edit.sections.all.header" = "已经存在的捷径"; +"shortcuts.edit.items.add_shortcut.caption" = "添加捷径"; + +// MARK: PaywallView + +"paywall.title" = "购买"; +"paywall.sections.products.footer" = "每件产品都是一次性的购买。 购买的提供商并不包含VPN订阅。"; +"paywall.items.loading.caption" = "加载产品中"; +"paywall.items.full_version.extra_description" = "所有的提供商(包括未来添加的)\n%@"; +"paywall.items.restore.title" = "恢复购买"; +"paywall.items.restore.description" = "如果你购买过此应用或其特征, 你可以恢复购买,此页面将不在显示。"; + +// MARK: DonateView + +"donate.title" = "捐助"; +"donate.sections.one_time.header" = "一次性"; +"donate.sections.one_time.footer" = "如果你想对我免费的工作表示感谢,这里是一些你可以捐助的数额。\n\n每次捐助只需要付款一次,你可以捐助多次。"; +"donate.items.loading.caption" = "加载捐助中"; +"donate.items.purchasing.caption" = "展示捐助页面中"; +"donate.alerts.purchase.success.title" = "感谢"; +"donate.alerts.purchase.success.message" = "这对于我意味着很多,希望你保持使用并使它更好。"; +"donate.alerts.purchase.failure.message" = "无法展现捐助内容。%@"; + +// MARK: AboutView + +"about.title" = "关于"; +"about.sections.web.header" = "网络"; +"about.sections.share.header" = "分享"; +"about.items.credits.caption" = "评分"; +"about.items.website.caption" = "主页"; +"about.items.disclaimer.caption" = "免责声明"; +"about.items.privacy_policy.caption" = "隐私政策"; +"about.items.share_twitter.caption" = "发送关于它的推特!"; +"about.items.share_generic.caption" = "邀请朋友"; + +// MARK: AboutView -> VersionView + +"version.title" = "版本"; +"version.labels.intro" = "Passepartout和TunnelKit由Davide De Rosa (keeshux)编写并维护。\n\nPassepartout和TunnelKit的源代码在GitHub上以GPLv3许可证面向大众开放,你可以在主页上找到链接。\n\nPassepartout不是官方的客户端,同OpenVPN Inc也没有关系。"; + +// MARK: AboutView -> CreditsView + +"credits.title" = "评分"; +"credits.sections.licenses.header" = "许可证"; +"credits.sections.notices.header" = "注意"; + +// MARK: PreferencesView (macOS) + +"preferences.title" = "偏好设置"; +"preferences.sections.general.header" = "一般"; +"preferences.items.launches_on_login.caption" = "登录时启动"; +"preferences.items.launches_on_login.footer" = "选中以在启动或登录时自动启动应用。"; +"preferences.items.confirm_quit.caption" = "确认退出"; +"preferences.items.confirm_quit.footer" = "选中以显示退出确认提醒。"; + +// MARK: Menu (macOS) + +"menu.show.title" = "显示"; +"menu.switch_profile.title" = "有效配置"; +"menu.active_profile.title.none" = "无有效配置"; +"menu.active_profile.items.customize.title" = "自定义......"; +"menu.active_profile.messages.missing_credentials" = "未配置账户"; +"menu.organizer.title" = "分类页面"; +"menu.preferences.title" = "偏好设置"; +"menu.support.title" = "支持"; +"menu.quit.title" = "退出%@"; +"menu.quit.messages.confirm" = "VPN(如果启用)仍将在后台运行。您要退出吗?"; diff --git a/Passepartout/App/Shared/PassepartoutApp.swift b/Passepartout/App/Shared/PassepartoutApp.swift new file mode 100644 index 00000000..bc56907c --- /dev/null +++ b/Passepartout/App/Shared/PassepartoutApp.swift @@ -0,0 +1,61 @@ +// +// PassepartoutApp.swift +// Passepartout +// +// Created by Davide De Rosa on 12/28/21. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +@main +struct PassepartoutApp: App { + @SceneBuilder var body: some Scene { + WindowGroup { + MainView() + .onIntentActivity(IntentDispatcher.connectVPN) + .onIntentActivity(IntentDispatcher.disableVPN) + .onIntentActivity(IntentDispatcher.enableVPN) + .onIntentActivity(IntentDispatcher.moveToLocation) + .onIntentActivity(IntentDispatcher.trustCellularNetwork) + .onIntentActivity(IntentDispatcher.trustCurrentNetwork) + .onIntentActivity(IntentDispatcher.untrustCellularNetwork) + .onIntentActivity(IntentDispatcher.untrustCurrentNetwork) + } + } +} + +extension View { + + @MainActor + fileprivate func onIntentActivity(_ activity: IntentActivity) -> some View { + onContinueUserActivity(activity.name) { userActivity in + + // eligibility: ignore Siri shortcuts if not purchased + guard AppContext.shared.productManager.isEligible(forFeature: .siriShortcuts) else { + pp_log.warning("Ignore activity handler, not eligible for Siri shortcuts") + return + } + pp_log.info("Handling activity: \(activity.name)") + activity.handler(userActivity, AppContext.shared.vpnManager) + } + } +} diff --git a/Passepartout/App/macOS/Launcher/Assets.xcassets/Contents.json b/Passepartout/App/Shared/Providers.xcassets/Contents.json similarity index 100% rename from Passepartout/App/macOS/Launcher/Assets.xcassets/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/Contents.json diff --git a/Passepartout/App/macOS/Launcher/Assets.xcassets/AccentColor.colorset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/Contents.json similarity index 51% rename from Passepartout/App/macOS/Launcher/Assets.xcassets/AccentColor.colorset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/Contents.json index eb878970..6e965652 100644 --- a/Passepartout/App/macOS/Launcher/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Passepartout/App/Shared/Providers.xcassets/providers/Contents.json @@ -1,11 +1,9 @@ { - "colors" : [ - { - "idiom" : "universal" - } - ], "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "provides-namespace" : true } } diff --git a/Passepartout/App/iOS/Providers.xcassets/hideme.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/hideme.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/hideme.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/hideme.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/hideme.imageset/hideme@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/hideme.imageset/hideme@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/hideme.imageset/hideme@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/hideme.imageset/hideme@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/hideme.imageset/hideme@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/hideme.imageset/hideme@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/hideme.imageset/hideme@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/hideme.imageset/hideme@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/mullvad.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/mullvad.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/mullvad.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/mullvad.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/mullvad.imageset/mullvad@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/mullvad.imageset/mullvad@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/mullvad.imageset/mullvad@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/mullvad.imageset/mullvad@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/mullvad.imageset/mullvad@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/mullvad.imageset/mullvad@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/mullvad.imageset/mullvad@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/mullvad.imageset/mullvad@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/nordvpn.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/nordvpn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/nordvpn.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/nordvpn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/nordvpn.imageset/nordvpn-dark@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/nordvpn.imageset/nordvpn-dark@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/nordvpn.imageset/nordvpn-dark@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/nordvpn.imageset/nordvpn-dark@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/nordvpn.imageset/nordvpn-dark@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/nordvpn.imageset/nordvpn-dark@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/nordvpn.imageset/nordvpn-dark@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/nordvpn.imageset/nordvpn-dark@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/nordvpn.imageset/nordvpn@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/nordvpn.imageset/nordvpn@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/nordvpn.imageset/nordvpn@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/nordvpn.imageset/nordvpn@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/nordvpn.imageset/nordvpn@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/nordvpn.imageset/nordvpn@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/nordvpn.imageset/nordvpn@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/nordvpn.imageset/nordvpn@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/oeck.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/oeck.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/oeck.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/oeck.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/oeck.imageset/oeck-dark@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/oeck.imageset/oeck-dark@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/oeck.imageset/oeck-dark@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/oeck.imageset/oeck-dark@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/oeck.imageset/oeck-dark@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/oeck.imageset/oeck-dark@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/oeck.imageset/oeck-dark@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/oeck.imageset/oeck-dark@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/oeck.imageset/oeck@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/oeck.imageset/oeck@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/oeck.imageset/oeck@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/oeck.imageset/oeck@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/oeck.imageset/oeck@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/oeck.imageset/oeck@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/oeck.imageset/oeck@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/oeck.imageset/oeck@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/pia.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/pia.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/pia.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/pia.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/pia.imageset/pia@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/pia.imageset/pia@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/pia.imageset/pia@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/pia.imageset/pia@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/pia.imageset/pia@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/pia.imageset/pia@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/pia.imageset/pia@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/pia.imageset/pia@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/placeholder.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/placeholder.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/placeholder.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/placeholder.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/placeholder.imageset/placeholder@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/placeholder.imageset/placeholder@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/placeholder.imageset/placeholder@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/placeholder.imageset/placeholder@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/placeholder.imageset/placeholder@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/placeholder.imageset/placeholder@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/placeholder.imageset/placeholder@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/placeholder.imageset/placeholder@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/protonvpn.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/protonvpn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/protonvpn.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/protonvpn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/protonvpn.imageset/protonvpn@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/protonvpn.imageset/protonvpn@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/protonvpn.imageset/protonvpn@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/protonvpn.imageset/protonvpn@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/protonvpn.imageset/protonvpn@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/protonvpn.imageset/protonvpn@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/protonvpn.imageset/protonvpn@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/protonvpn.imageset/protonvpn@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/surfshark.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/surfshark.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/surfshark.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/surfshark.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/surfshark.imageset/surfshark@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/surfshark.imageset/surfshark@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/surfshark.imageset/surfshark@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/surfshark.imageset/surfshark@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/surfshark.imageset/surfshark@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/surfshark.imageset/surfshark@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/surfshark.imageset/surfshark@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/surfshark.imageset/surfshark@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/torguard.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/torguard.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/torguard.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/torguard.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/torguard.imageset/torguard@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/torguard.imageset/torguard@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/torguard.imageset/torguard@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/torguard.imageset/torguard@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/torguard.imageset/torguard@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/torguard.imageset/torguard@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/torguard.imageset/torguard@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/torguard.imageset/torguard@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/tunnelbear.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/tunnelbear.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/tunnelbear.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/tunnelbear.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/tunnelbear.imageset/tunnelbear@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/tunnelbear.imageset/tunnelbear@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/tunnelbear.imageset/tunnelbear@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/tunnelbear.imageset/tunnelbear@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/tunnelbear.imageset/tunnelbear@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/tunnelbear.imageset/tunnelbear@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/tunnelbear.imageset/tunnelbear@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/tunnelbear.imageset/tunnelbear@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/vyprvpn.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/vyprvpn.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/vyprvpn.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/vyprvpn.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/vyprvpn.imageset/vyprvpn@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/vyprvpn.imageset/vyprvpn@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/vyprvpn.imageset/vyprvpn@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/vyprvpn.imageset/vyprvpn@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/vyprvpn.imageset/vyprvpn@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/vyprvpn.imageset/vyprvpn@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/vyprvpn.imageset/vyprvpn@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/vyprvpn.imageset/vyprvpn@3x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/windscribe.imageset/Contents.json b/Passepartout/App/Shared/Providers.xcassets/providers/windscribe.imageset/Contents.json similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/windscribe.imageset/Contents.json rename to Passepartout/App/Shared/Providers.xcassets/providers/windscribe.imageset/Contents.json diff --git a/Passepartout/App/iOS/Providers.xcassets/windscribe.imageset/windscribe@2x.png b/Passepartout/App/Shared/Providers.xcassets/providers/windscribe.imageset/windscribe@2x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/windscribe.imageset/windscribe@2x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/windscribe.imageset/windscribe@2x.png diff --git a/Passepartout/App/iOS/Providers.xcassets/windscribe.imageset/windscribe@3x.png b/Passepartout/App/Shared/Providers.xcassets/providers/windscribe.imageset/windscribe@3x.png similarity index 100% rename from Passepartout/App/iOS/Providers.xcassets/windscribe.imageset/windscribe@3x.png rename to Passepartout/App/Shared/Providers.xcassets/providers/windscribe.imageset/windscribe@3x.png diff --git a/Passepartout/App/Shared/Reusable/AddingTextField.swift b/Passepartout/App/Shared/Reusable/AddingTextField.swift new file mode 100644 index 00000000..487d9601 --- /dev/null +++ b/Passepartout/App/Shared/Reusable/AddingTextField.swift @@ -0,0 +1,68 @@ +// +// AddingTextField.swift +// Passepartout +// +// Created by Davide De Rosa on 2/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct AddingTextField: View { + var onAdd: (() -> Void)? + + var onCommit: (() -> Void)? + + let textField: (@escaping () -> Void) -> Field + + let addLabel: () -> ActionLabel + + var commitLabel: (() -> ActionLabel)? + + @State private var isAdding = false + + var body: some View { + HStack { + if isAdding { + textField(doCommit) + Spacer() + if let commitLabel = commitLabel { + Button(action: doCommit, label: commitLabel) + } + } else { + Button(action: doAdd, label: addLabel) + } + } + } + + private func doAdd() { + withAnimation { + onAdd?() + isAdding = true + } + } + + private func doCommit() { + withAnimation { + onCommit?() + isAdding = false + } + } +} diff --git a/Passepartout/App/iOS/Cells/ActivityTableViewCell.swift b/Passepartout/App/Shared/Reusable/Binding+Extensions.swift similarity index 50% rename from Passepartout/App/iOS/Cells/ActivityTableViewCell.swift rename to Passepartout/App/Shared/Reusable/Binding+Extensions.swift index 2a6a12fd..54ca2508 100644 --- a/Passepartout/App/iOS/Cells/ActivityTableViewCell.swift +++ b/Passepartout/App/Shared/Reusable/Binding+Extensions.swift @@ -1,8 +1,8 @@ // -// ActivityTableViewCell.swift +// Binding+Extensions.swift // Passepartout // -// Created by Davide De Rosa on 4/8/19. +// Created by Davide De Rosa on 2/19/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -23,27 +23,36 @@ // along with Passepartout. If not, see . // -import UIKit +import SwiftUI -extension Cells { - static let activity = ActivityTableViewCell.Provider() +func ??(lhs: Binding>, rhs: T) -> Binding { + Binding { + lhs.wrappedValue ?? rhs + } set: { + lhs.wrappedValue = $0 + } } -class ActivityTableViewCell: UITableViewCell { - private lazy var activityIndicator = UIActivityIndicatorView(style: .gray) -} - -extension ActivityTableViewCell { - class Provider: CellProvider { - typealias T = ActivityTableViewCell - - func dequeue(from tableView: UITableView, for indexPath: IndexPath) -> ActivityTableViewCell { - let cell = tableView.dequeue(T.self, identifier: Provider.identifier, for: indexPath) - cell.apply(.current) - cell.activityIndicator.startAnimating() - cell.accessoryView = cell.activityIndicator - cell.selectionStyle = .none - return cell +extension Binding { + func toString() -> Binding where Value == Optional { + .init { + wrappedValue?.absoluteString ?? "" + } set: { + wrappedValue = URL(string: $0) ?? wrappedValue + } + } + + func toString() -> Binding where Value == Optional, T: FixedWidthInteger { + .init { + guard let v = wrappedValue else { + return "" + } + return v.description + } set: { + guard let v = T($0) else { + return + } + wrappedValue = v } } } diff --git a/Passepartout/App/Shared/Reusable/CopySavingButton.swift b/Passepartout/App/Shared/Reusable/CopySavingButton.swift new file mode 100644 index 00000000..59f3d08b --- /dev/null +++ b/Passepartout/App/Shared/Reusable/CopySavingButton.swift @@ -0,0 +1,80 @@ +// +// CopySavingButton.swift +// Passepartout +// +// Created by Davide De Rosa on 4/6/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +protocol CopySavingModel: Equatable { + init() +} + +struct CopySavingButton: View { + @Binding var original: T + + @Binding var copy: T + + var mapping: (T) -> T + + let label: () -> Label + + var saveAnyway = false + + var onSave: (() -> Void)? + + @State private var isLoaded = false + + var body: some View { + Button(action: saveToOriginal, label: label) + .disabled(!canSave) + .onAppear { + loadFromOriginal(once: true) + }.onChange(of: original) { _ in + withAnimation { + loadFromOriginal(once: false) + } + } + } + + private var canSave: Bool { + isLoaded && (saveAnyway || copy != original) + } + + private func loadFromOriginal(once: Bool) { + guard !once || !isLoaded else { + return + } + copy = original + isLoaded = true + } + + private func saveToOriginal() { + defer { + onSave?() + } + guard copy != original else { + return + } + original = copy + } +} diff --git a/Passepartout/App/Shared/Reusable/EditableTextList.swift b/Passepartout/App/Shared/Reusable/EditableTextList.swift new file mode 100644 index 00000000..71df69dd --- /dev/null +++ b/Passepartout/App/Shared/Reusable/EditableTextList.swift @@ -0,0 +1,206 @@ +// +// EditableTextList.swift +// Passepartout +// +// Created by Davide De Rosa on 3/31/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct IdentifiableString: Identifiable, Equatable { + var id = UUID() + + var string: String +} + +struct EditableTextList: View { + typealias FieldCallback = ( + isNewElement: Bool, + text: Binding, + onEditingChanged: (Bool) -> Void, + onCommit: () -> Void + ) + + @Binding var elements: [String] + + var allowsDuplicates = true + + var mapping: ([IdentifiableString]) -> [IdentifiableString] = { $0 } + + var onAdd: ((Binding) -> Void)? + + let textField: (FieldCallback) -> Field + + let addLabel: () -> ActionLabel + + var commitLabel: (() -> ActionLabel)? + + @State private var isLoaded = false + + @State private var identifiableElements: [IdentifiableString] = [] + + @State private var editedTextStrings: [UUID: String] = [:] + + private let addedUUID = UUID() + + private var addedText: Binding { + .init { + editedTextStrings[addedUUID] ?? "" + } set: { + editedTextStrings[addedUUID] = $0 + } + } + + var body: some View { + debugChanges() + return Group { + ForEach(mapping(identifiableElements), content: existingRow) + .onDelete(perform: onDelete) + .onMove(perform: onMove) + + newRow + }.onAppear { + guard !isLoaded else { + return + } + isLoaded = true + identifiableElements = elements.map { + IdentifiableString(string: $0) + } + }.onChange(of: elements, perform: remapElements) + } + + private func existingRow(_ element: IdentifiableString) -> some View { + let editedText = binding(toEditedElement: element) + + return textField((false, editedText, { + if $0 { + editedTextStrings.removeValue(forKey: element.id) +// print(">>> editing: '\(text.wrappedValue.string)' (\(text.wrappedValue.id))") + } + }, { + replaceElement(at: element.id, with: editedText) + })) + } + + private var newRow: some View { + AddingTextField( + onAdd: { + addedText.wrappedValue = "" + onAdd?(addedText) + }, + onCommit: addElement, + textField: { + textField((true, addedText, { _ in }, $0)) + }, + addLabel: addLabel, + commitLabel: commitLabel + ) + } +} + +// MARK: View model + +extension EditableTextList { + private func remapElements(_ newElements: [String]) { + var oldIdentifiableElements = identifiableElements + var newIdentifiableElements: [IdentifiableString] = [] + + newElements.forEach { newString in + let id: UUID + if let found = oldIdentifiableElements.firstIndex(where: { + $0.string == newString + }) { + id = oldIdentifiableElements[found].id + oldIdentifiableElements.remove(at: found) + } else { + id = UUID() + } + newIdentifiableElements.append(.init(id: id, string: newString)) + } + + guard newIdentifiableElements != identifiableElements else { + return + } + withAnimation { + identifiableElements = newIdentifiableElements + } + } + + private func addElement() { + guard allowsDuplicates || !identifiableElements.contains(where: { + $0.string == addedText.wrappedValue + }) else { + return + } +// print(">>> + \(addedElement.wrappedValue)") + identifiableElements.append(.init(string: addedText.wrappedValue)) + commit() + } + + private func binding(toEditedElement element: IdentifiableString) -> Binding { +// print(">>> <-> \(element)") + return .init { + editedTextStrings[element.id] ?? element.string + } set: { + editedTextStrings[element.id] = $0 + } + } + + private func replaceElement(at id: UUID, with editedText: Binding) { +// print(">>> \(identifiableElements[id].string) -> \(editedText.wrappedValue)") + guard let i = identifiableElements.firstIndex(where: { + $0.id == id + }) else { + assertionFailure("Editing removed element?") + return + } + guard allowsDuplicates || !identifiableElements.contains(where: { + $0.string == editedText.wrappedValue + }) else { + editedText.wrappedValue = identifiableElements[i].string + return + } + withAnimation { + identifiableElements[i].string = editedText.wrappedValue + } + commit() + } + + private func onDelete(offsets: IndexSet) { + var mapped = mapping(identifiableElements) + mapped.remove(atOffsets: offsets) + identifiableElements = mapped + commit() + } + + private func onMove(indexSet: IndexSet, to: Int) { + var mapped = mapping(identifiableElements) + mapped.move(fromOffsets: indexSet, toOffset: to) + identifiableElements = mapped + commit() + } + + private func commit() { +// print(">>> identifiableElements = \(identifiableElements.map { "\($0.string) (\($0.id))" })") + elements = identifiableElements.map(\.string) + } +} diff --git a/Passepartout/App/Shared/Reusable/GenericCreditsView.swift b/Passepartout/App/Shared/Reusable/GenericCreditsView.swift new file mode 100644 index 00000000..b6b74bfc --- /dev/null +++ b/Passepartout/App/Shared/Reusable/GenericCreditsView.swift @@ -0,0 +1,175 @@ +// +// GenericCreditsView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/27/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct GenericCreditsView: View { + typealias License = (String, String, URL) + + typealias Notice = (String, String) + + var licensesHeader: String? = "Licenses" + + var noticesHeader: String? = "Notices" + + var translationsHeader: String? = "Translations" + + let licenses: [License] + + let notices: [Notice] + + let translations: [String: String] + + @State private var contentForLicense: [String: String] = [:] + + var body: some View { + List { + if !licenses.isEmpty { + licensesSection + } + if !notices.isEmpty { + noticesSection + } + if !translations.isEmpty { + translationsSection + } + } + } + + private var sortedLicenses: [License] { + return licenses.sorted { + $0.0.lowercased() < $1.0.lowercased() + } + } + + private var sortedNotices: [Notice] { + return notices.sorted { + $0.0.lowercased() < $1.0.lowercased() + } + } + + private var sortedLanguages: [String] { + return translations.keys.sorted { + $0.localizedAsCountryCode < $1.localizedAsCountryCode + } + } + + private var licensesSection: some View { + Section ( + header: licensesHeader.map(Text.init) + ) { + ForEach(sortedLicenses, id: \.0) { license in + NavigationLink { + LicenseView( + url: license.2, + content: $contentForLicense[license.0] + ).navigationTitle(license.0) + } label: { + HStack { + Text(license.0) + Spacer() + Text(license.1) + } + } + } + } + } + + private var noticesSection: some View { + Section ( + header: noticesHeader.map(Text.init) + ) { + ForEach(sortedNotices, id: \.0) { notice in + NavigationLink(notice.0, destination: noticeView(notice)) + } + } + } + + private var translationsSection: some View { + Section ( + header: translationsHeader.map(Text.init) + ) { + ForEach(sortedLanguages, id: \.self) { code in + HStack { + Text(code.localizedAsCountryCode) + Spacer() + translations[code].map { author in + Text(author) + .padding() + } + } + } + } + } + + private func noticeView(_ content: (String, String)) -> some View { + VStack { + Text(content.1) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .padding() + }.navigationTitle(content.0) + .navigationBarTitleDisplayMode(.inline) + } +} + +extension GenericCreditsView { + struct LicenseView: View { + let url: URL + + @Binding var content: String? + + var body: some View { + content.map { unwrapped in + ScrollView { + Text(unwrapped) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .padding() + } + }.replacedWithProgress(when: content == nil) + .onAppear(perform: loadURL) + } + + private func loadURL() { + guard content == nil else { + return + } + Task { + withAnimation { + do { + content = try String(contentsOf: url) + } catch { + content = error.localizedDescription + } + } + } + } + } +} + +private extension String { + var localizedAsCountryCode: String { + return Locale.current.localizedString(forLanguageCode: self)?.capitalized ?? self + } +} diff --git a/Passepartout/App/Shared/Reusable/GenericVersionView.swift b/Passepartout/App/Shared/Reusable/GenericVersionView.swift new file mode 100644 index 00000000..a4a04d09 --- /dev/null +++ b/Passepartout/App/Shared/Reusable/GenericVersionView.swift @@ -0,0 +1,56 @@ +// +// GenericVersionView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct GenericVersionView: View { + let logoName: String + + let appName: String + + let versionString: String + + let extraString: String? + + var body: some View { + ScrollView { + Image(logoName) + Spacer() + Text(appName) + .font(.largeTitle) + Spacer() + Text(versionString) + + extraString.map { string in + VStack { + Text(string) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .multilineTextAlignment(.center) + .padding() + } + } + } + } +} diff --git a/Passepartout/App/Shared/Reusable/InApp.swift b/Passepartout/App/Shared/Reusable/InApp.swift new file mode 100644 index 00000000..687c2fb8 --- /dev/null +++ b/Passepartout/App/Shared/Reusable/InApp.swift @@ -0,0 +1,225 @@ +// +// InApp.swift +// Passepartout +// +// Created by Davide De Rosa on 9/9/19. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +#if canImport(StoreKit) +import Foundation +import StoreKit + +public enum InAppPurchaseResult { + case done + + case cancelled +} + +public enum InAppError: Error { + case unknown +} + +public class InApp: NSObject, + SKProductsRequestDelegate, SKPaymentTransactionObserver + where PID.RawValue == String { + + public typealias ProductObserver = ([PID: SKProduct]) -> Void + + public typealias TransactionObserver = (Result) -> Void + + public typealias RestoreObserver = (Bool, PID?, Error?) -> Void + + public typealias FailureObserver = (Error) -> Void + + private var productsMap: [PID: SKProduct] + + public var products: [SKProduct] { + return [SKProduct](productsMap.values) + } + + private var productObservers: [ProductObserver] + + private var productFailureObserver: FailureObserver? + + private var transactionObservers: [String: TransactionObserver] + + private var restoreObservers: [RestoreObserver] + + public override init() { + productsMap = [:] + productObservers = [] + productFailureObserver = nil + transactionObservers = [:] + restoreObservers = [] + super.init() + + SKPaymentQueue.default().add(self) + } + + deinit { + SKPaymentQueue.default().remove(self) + } + + public func requestProducts(withIdentifiers identifiers: [PID], completionHandler: ProductObserver?, failureHandler: FailureObserver?) { + let req = SKProductsRequest(productIdentifiers: Set(identifiers.map { $0.rawValue })) + req.delegate = self + if let observer = completionHandler { + productObservers.append(observer) + } + productFailureObserver = failureHandler + req.start() + } + + @discardableResult + public func purchase(productWithIdentifier productIdentifier: PID, completionHandler: @escaping TransactionObserver) -> Bool { + guard let product = productsMap[productIdentifier] else { + return false + } + purchase(product: product, completionHandler: completionHandler) + return true + } + + public func purchase(product: SKProduct, completionHandler: @escaping TransactionObserver) { + let queue = SKPaymentQueue.default() + let payment = SKPayment(product: product) + transactionObservers[product.productIdentifier] = completionHandler + queue.add(payment) + } + + public func restorePurchases(completionHandler: @escaping RestoreObserver) { + let queue = SKPaymentQueue.default() + restoreObservers.append(completionHandler) + queue.restoreCompletedTransactions() + } + + public func product(withIdentifier productIdentifier: PID) -> SKProduct? { + return productsMap[productIdentifier] + } + + // MARK: SKProductsRequestDelegate + + public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { + DispatchQueue.main.async { + self.receiveProducts(response.products) + } + } + + public func request(_ request: SKRequest, didFailWithError error: Error) { + if let _ = request as? SKProductsRequest { + DispatchQueue.main.async { + self.productFailureObserver?(error) + } + } + DispatchQueue.main.async { + self.transactionObservers.removeAll() + } + } + + private func receiveProducts(_ products: [SKProduct]) { + productsMap.removeAll() + for p in products { + guard let pid = PID(rawValue: p.productIdentifier) else { + continue + } + productsMap[pid] = p + } + productObservers.forEach { $0(productsMap) } + productObservers.removeAll() + } + + // MARK: SKPaymentTransactionObserver + + public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { + DispatchQueue.main.async { + let currentRestoreObservers = self.restoreObservers + + for tx in transactions { + let productIdentifier = tx.payment.productIdentifier + let observer = self.transactionObservers[productIdentifier] + + switch tx.transactionState { + case .purchased: + queue.finishTransaction(tx) + observer?(.success(.done)) + + case .restored: + queue.finishTransaction(tx) + observer?(.success(.done)) + for r in currentRestoreObservers { + guard let pid = PID(rawValue: productIdentifier) else { + continue + } + r(false, pid, nil) + } + + case .failed: + queue.finishTransaction(tx) + if let skError = tx.error as? SKError, skError.code == .paymentCancelled { + observer?(.success(.cancelled)) + } else { + observer?(.failure(tx.error ?? InAppError.unknown)) + for r in currentRestoreObservers { + guard let pid = PID(rawValue: productIdentifier) else { + continue + } + r(false, pid, tx.error) + } + } + + default: + break + } + } + } + } + + public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { + DispatchQueue.main.async { + for r in self.restoreObservers { + r(true, nil, nil) + } + self.restoreObservers.removeAll() + } + } + + public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) { + DispatchQueue.main.async { + for r in self.restoreObservers { + r(true, nil, error) + } + self.restoreObservers.removeAll() + } + } +} + +extension SKProduct { + private var localizedCurrencyFormatter: NumberFormatter { + let fmt = NumberFormatter() + fmt.locale = priceLocale + fmt.numberStyle = .currency + return fmt + } + + public var localizedPrice: String? { + return localizedCurrencyFormatter.string(from: price) + } +} +#endif diff --git a/Passepartout/App/Shared/Reusable/IntentActivity.swift b/Passepartout/App/Shared/Reusable/IntentActivity.swift new file mode 100644 index 00000000..d573fd66 --- /dev/null +++ b/Passepartout/App/Shared/Reusable/IntentActivity.swift @@ -0,0 +1,40 @@ +// +// IntentActivity.swift +// Passepartout +// +// Created by Davide De Rosa on 3/12/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +public struct IntentActivity { + public let name: String + + public let handler: (NSUserActivity, UserObject) -> Void +} + +extension View { + public func onIntentActivity(_ activity: IntentActivity, object: UserObject) -> some View { + return onContinueUserActivity(activity.name) { + activity.handler($0, object) + } + } +} diff --git a/Passepartout/App/Shared/Reusable/LongContentView.swift b/Passepartout/App/Shared/Reusable/LongContentView.swift new file mode 100644 index 00000000..7afc0a5e --- /dev/null +++ b/Passepartout/App/Shared/Reusable/LongContentView.swift @@ -0,0 +1,77 @@ +// +// LongContentView.swift +// Passepartout +// +// Created by Davide De Rosa on 3/4/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct LongContentView: View { + @Binding var content: String + + var body: some View { + TextEditor(text: $content) + .font(.system(.body, design: .monospaced)) + .padding() + + // FIXME: layout, padding should rather be an inset, and extend beyond safe areas + } +} + +struct LongContentLink: View { + private let title: String + + @Binding private var content: String + + private let preview: String? + + private let previewLabel: ((String) -> Preview)? + + init( + _ title: String, + content: Binding, + preview: String? = nil, + previewLabel: ((String) -> Preview)? = nil + ) { + self.title = title + _content = content + self.preview = preview + self.previewLabel = previewLabel + } + + var body: some View { + NavigationLink { + LongContentView(content: $content) + .navigationTitle(title) + } label: { + HStack { + Text(title) + Spacer() + previewLabel.map { + $0(preview ?? content) + .lineLimit(1) + .truncationMode(.middle) + } + } + } + } +} diff --git a/Passepartout/App/Shared/Reusable/ReloadingSection.swift b/Passepartout/App/Shared/Reusable/ReloadingSection.swift new file mode 100644 index 00000000..50ed4f66 --- /dev/null +++ b/Passepartout/App/Shared/Reusable/ReloadingSection.swift @@ -0,0 +1,81 @@ +// +// ReloadingSection.swift +// Passepartout +// +// Created by Davide De Rosa on 4/4/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct ReloadingSection: View { + @Environment(\.scenePhase) private var scenePhase + + let header: Header + + let footer: Footer + + let elements: [T] + + var equality: ([T], [T]) -> Bool = { $0 == $1 } + + var isReloading = false + + var reload: (() -> Void)? + + @ViewBuilder let content: ([T]) -> Content + + @State private var localElements: [T] = [] + + var body: some View { + Section( + header: header,//progressHeader, + footer: footer + ) { + content(localElements) + }.onAppear { + localElements = elements + if localElements.isEmpty { + reload?() + } + }.onChange(of: elements) { newElements in + guard !equality(localElements, newElements) else { + return + } + withAnimation { + localElements = newElements + } + }.onChange(of: scenePhase) { + if $0 == .active { + reload?() + } + } + } + +// private var progressHeader: some View { +// HStack { +// header +// if isReloading { +// ProgressView() +// .padding(.leading, 5) +// } +// } +// } +} diff --git a/Passepartout/App/Shared/Reusable/RevealingSecureField.swift b/Passepartout/App/Shared/Reusable/RevealingSecureField.swift new file mode 100644 index 00000000..f85ff2b5 --- /dev/null +++ b/Passepartout/App/Shared/Reusable/RevealingSecureField.swift @@ -0,0 +1,72 @@ +// +// RevealingSecureField.swift +// Passepartout +// +// Created by Davide De Rosa on 3/22/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct RevealingSecureField: View { + let title: String + + @Binding private var text: String + + private let conceilImage: () -> ImageContent + + private let revealImage: () -> ImageContent + + @State private var isRevealed = false + + init( + _ title: String, + text: Binding, + conceilImage: @escaping () -> ImageContent, + revealImage: @escaping () -> ImageContent + ) { + self.title = title + _text = text + self.conceilImage = conceilImage + self.revealImage = revealImage + } + + var body: some View { + HStack { + if isRevealed { + TextField(title, text: $text) + Spacer() + Button { + isRevealed.toggle() + } label: { + conceilImage() + }.buttonStyle(.plain) + } else { + SecureField(title, text: $text) + Spacer() + Button { + isRevealed.toggle() + } label: { + revealImage() + }.buttonStyle(.plain) + } + } + } +} diff --git a/Passepartout/App/Shared/Reusable/Reviewer.swift b/Passepartout/App/Shared/Reusable/Reviewer.swift new file mode 100644 index 00000000..55d875dd --- /dev/null +++ b/Passepartout/App/Shared/Reusable/Reviewer.swift @@ -0,0 +1,96 @@ +// +// Reviewer.swift +// Passepartout +// +// Created by Davide De Rosa on 9/9/19. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +#if os(iOS) && canImport(StoreKit) +import UIKit +import StoreKit + +@MainActor +public class Reviewer: ObservableObject { + private struct Keys { + static let eventCount = "Reviewer.EventCount" + + static let lastVersion = "Reviewer.LastVersion" + } + + private let defaults: UserDefaults + + public var eventCountBeforeRating: Int = .max + + public init() { + defaults = .standard + } + + @discardableResult + public func reportEvent() -> Bool { + return reportEvents(1) + } + + @discardableResult + public func reportEvents(_ eventCount: Int, appStoreId: String? = nil) -> Bool { + guard let currentVersionString = Bundle.main.infoDictionary?["CFBundleVersion"] as? String, let currentVersion = Int(currentVersionString) else { + return false + } + let lastVersion = defaults.integer(forKey: Keys.lastVersion) + if lastVersion > 0 { + print("Reviewer: App last reviewed for version \(lastVersion)") + } else { + print("Reviewer: App was never reviewed") + } + guard currentVersion != lastVersion else { + print("Reviewer: App already reviewed for version \(currentVersion)") + return false + } + + var count = defaults.integer(forKey: Keys.eventCount) + count += eventCount + defaults.set(count, forKey: Keys.eventCount) + print("Reviewer: Event reported for version \(currentVersion) (count: \(count), prompt: \(eventCountBeforeRating))") + + guard count >= eventCountBeforeRating else { + return false + } + print("Reviewer: Prompting for review...") + + defaults.removeObject(forKey: Keys.eventCount) + defaults.set(currentVersion, forKey: Keys.lastVersion) + + requestReview() + return true + } + + // may or may not appear + private func requestReview() { + guard let scene = UIApplication.shared.windows.first(where: { $0.windowScene != nil })?.windowScene else { + return + } + SKStoreReviewController.requestReview(in: scene) + } + + public static func urlForReview(withAppId appId: String) -> URL { + return URL(string: "https://apps.apple.com/app/id\(appId)?action=write-review")! + } +} +#endif diff --git a/Passepartout/App/Shared/Reusable/Shortcut.swift b/Passepartout/App/Shared/Reusable/Shortcut.swift new file mode 100644 index 00000000..7caad1ec --- /dev/null +++ b/Passepartout/App/Shared/Reusable/Shortcut.swift @@ -0,0 +1,55 @@ +// +// Shortcut.swift +// Passepartout +// +// Created by Davide De Rosa on 3/13/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Intents + +struct Shortcut: Identifiable, Hashable, Comparable { + let native: INVoiceShortcut + + init(_ native: INVoiceShortcut) { + self.native = native + } + + var id: UUID { + return native.identifier + } + + static func ==(lhs: Self, rhs: Self) -> Bool { + return lhs.phrase == rhs.phrase + } + + static func <(lhs: Self, rhs: Self) -> Bool { + return lhs.phrase < rhs.phrase + } + + func hash(into hasher: inout Hasher) { + hasher.combine(phrase) + } + + private var phrase: String { + return native.invocationPhrase.lowercased() + } +} diff --git a/Passepartout/App/Shared/Reusable/StyledPicker.swift b/Passepartout/App/Shared/Reusable/StyledPicker.swift new file mode 100644 index 00000000..07427774 --- /dev/null +++ b/Passepartout/App/Shared/Reusable/StyledPicker.swift @@ -0,0 +1,74 @@ +// +// StyledPicker.swift +// Passepartout +// +// Created by Davide De Rosa on 2/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct StyledPicker: View { + let title: String + + @Binding var selection: T + + let values: [T] + + let pickerLabel: (T) -> Label + + let selectionLabel: (T) -> Label + + let listStyle: () -> Style + + @State private var isPresented = false + + var body: some View { + NavigationLink(isActive: $isPresented, destination: pickerView) { + HStack { + Text(title) + Spacer() + selectionLabel(selection) + } + } + } + + private func pickerView() -> some View { + List { + Section { + ForEach(values, id: \.self) { value in + Button { + selection = value + isPresented = false + } label: { + HStack { + pickerLabel(value) + Spacer() + if value == selection { + Image(systemName: "checkmark") + } + } + } + } + } + }.navigationTitle(title) + .listStyle(listStyle()) + } +} diff --git a/Passepartout/App/Shared/Reusable/Validators.swift b/Passepartout/App/Shared/Reusable/Validators.swift new file mode 100644 index 00000000..63d172a8 --- /dev/null +++ b/Passepartout/App/Shared/Reusable/Validators.swift @@ -0,0 +1,80 @@ +// +// Validators.swift +// Passepartout +// +// Created by Davide De Rosa on 3/31/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +struct Validators { + enum ValidationError: Error { + case notSet + + case empty + + case ipAddress + + case domainName + + case url + } + + static func notNil(_ string: String?) throws { + guard let _ = string else { + throw ValidationError.notSet + } + } + + static func notEmpty(_ string: String) throws { + let valid = string.trimmingCharacters(in: .whitespacesAndNewlines) + guard !valid.isEmpty else { + throw ValidationError.empty + } + } + + static func ipAddress(_ string: String) throws { + var sin = sockaddr_in() + var sin6 = sockaddr_in6() + + if string.withCString({ cstring in inet_pton(AF_INET6, cstring, &sin6.sin6_addr) }) == 1 { + return + } + if string.withCString({ cstring in inet_pton(AF_INET, cstring, &sin.sin_addr) }) == 1 { + return + } + throw ValidationError.ipAddress + } + + private static let rxDomainName = NSRegularExpression("^((?!-)[A-Za-z0-9-]{1,63}(? 0 else { + throw ValidationError.domainName + } + } + + static func url(_ string: String) throws { + guard let _ = URL(string: string) else { + throw ValidationError.url + } + } +} diff --git a/Passepartout/App/Shared/Reusable/View+Extensions.swift b/Passepartout/App/Shared/Reusable/View+Extensions.swift new file mode 100644 index 00000000..4ada5d8c --- /dev/null +++ b/Passepartout/App/Shared/Reusable/View+Extensions.swift @@ -0,0 +1,132 @@ +// +// View+Extensions.swift +// Passepartout +// +// Created by Davide De Rosa on 2/18/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore +import SwiftyBeaver + +extension View { + func withLeadingText(_ text: String?, color: Color? = nil, truncationMode: Text.TruncationMode = .tail) -> some View { + HStack { + text.map(Text.init) + .foregroundColor(color) + .lineLimit(1) + .truncationMode(truncationMode) + Spacer() + self + } + } + + func withLeadingLabel(_ text: String, color: Color? = nil, image: String) -> some View { + HStack { + Label(text, image: image) + .foregroundColor(color) + Spacer() + self + } + } + + func withTrailingText(_ text: String?, color: Color? = nil, truncationMode: Text.TruncationMode = .tail, copyOnTap: Bool = false) -> some View { + HStack { + self + Spacer() + let trailing = text.map(Text.init) + .foregroundColor(color ?? themeSecondaryColor) + .lineLimit(1) + .truncationMode(truncationMode) + if copyOnTap { + trailing + .onTapGesture { + text.map(Utils.copyToPasteboard) + } + } else { + trailing + } + } + } + + func withTrailingCheckmark(when condition: Bool) -> some View { + HStack { + self + if condition { + Spacer() + themeCheckmarkImage.asSystemImage + .foregroundColor(themeAccentColor) + } + } + } + + func withTrailingProgress(when condition: Bool) -> some View { + HStack { + self + .disabled(condition) + if condition { + Spacer() + ProgressView() + } + } + } + + @ViewBuilder + func replacedWithProgress(when condition: Bool) -> some View { + if condition { + ProgressView() + } else { + self + } + } +} + +extension View { + func debugChanges() { + if #available(iOS 15, *), + SwiftyBeaver.destinations.first?.minLevel == .verbose { + + Self._printChanges() + } + } +} + +extension ScrollViewProxy { + func maybeScrollTo( + _ id: ID?, + afterMilliseconds millis: Int = Constants.Delays.scrolling, + animated: Bool = false, + anchor: UnitPoint = .center + ) { + guard let id = id else { + return + } + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(millis)) { + if animated { + withAnimation { + scrollTo(id, anchor: anchor) + } + } else { + scrollTo(id, anchor: anchor) + } + } + } +} diff --git a/Passepartout/App/iOS/Settings.bundle/Root.plist b/Passepartout/App/Shared/Settings.bundle/Root.plist similarity index 100% rename from Passepartout/App/iOS/Settings.bundle/Root.plist rename to Passepartout/App/Shared/Settings.bundle/Root.plist diff --git a/Passepartout/App/iOS/Settings.bundle/en.lproj/Root.strings b/Passepartout/App/Shared/Settings.bundle/en.lproj/Root.strings similarity index 100% rename from Passepartout/App/iOS/Settings.bundle/en.lproj/Root.strings rename to Passepartout/App/Shared/Settings.bundle/en.lproj/Root.strings diff --git a/Passepartout/App/iOS/de.lproj/InfoPlist.strings b/Passepartout/App/Shared/de.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/de.lproj/InfoPlist.strings rename to Passepartout/App/Shared/de.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/el.lproj/InfoPlist.strings b/Passepartout/App/Shared/el.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/el.lproj/InfoPlist.strings rename to Passepartout/App/Shared/el.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/en.lproj/InfoPlist.strings b/Passepartout/App/Shared/en.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/en.lproj/InfoPlist.strings rename to Passepartout/App/Shared/en.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/es.lproj/InfoPlist.strings b/Passepartout/App/Shared/es.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/es.lproj/InfoPlist.strings rename to Passepartout/App/Shared/es.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/fr.lproj/InfoPlist.strings b/Passepartout/App/Shared/fr.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/fr.lproj/InfoPlist.strings rename to Passepartout/App/Shared/fr.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/it.lproj/InfoPlist.strings b/Passepartout/App/Shared/it.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/it.lproj/InfoPlist.strings rename to Passepartout/App/Shared/it.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/nl.lproj/InfoPlist.strings b/Passepartout/App/Shared/nl.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/nl.lproj/InfoPlist.strings rename to Passepartout/App/Shared/nl.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/pl.lproj/InfoPlist.strings b/Passepartout/App/Shared/pl.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/pl.lproj/InfoPlist.strings rename to Passepartout/App/Shared/pl.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/pt.lproj/InfoPlist.strings b/Passepartout/App/Shared/pt.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/pt.lproj/InfoPlist.strings rename to Passepartout/App/Shared/pt.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/ru.lproj/InfoPlist.strings b/Passepartout/App/Shared/ru.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/ru.lproj/InfoPlist.strings rename to Passepartout/App/Shared/ru.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/sv.lproj/InfoPlist.strings b/Passepartout/App/Shared/sv.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/sv.lproj/InfoPlist.strings rename to Passepartout/App/Shared/sv.lproj/InfoPlist.strings diff --git a/Passepartout/App/Shared/swiftgen.yml b/Passepartout/App/Shared/swiftgen.yml new file mode 100644 index 00000000..be8e89fd --- /dev/null +++ b/Passepartout/App/Shared/swiftgen.yml @@ -0,0 +1,15 @@ +strings: + inputs: + - L10n/en.lproj/Localizable.strings + outputs: + - templateName: structured-swift4 + output: Constants/SwiftGen+Strings.swift + +xcassets: + inputs: + - Assets.xcassets + - Flags.xcassets + - Providers.xcassets + outputs: + - templateName: swift4 + output: Constants/SwiftGen+Assets.swift diff --git a/Passepartout/App/iOS/zh-Hans.lproj/InfoPlist.strings b/Passepartout/App/Shared/zh-Hans.lproj/InfoPlist.strings similarity index 100% rename from Passepartout/App/iOS/zh-Hans.lproj/InfoPlist.strings rename to Passepartout/App/Shared/zh-Hans.lproj/InfoPlist.strings diff --git a/Passepartout/App/iOS/App.entitlements b/Passepartout/App/iOS/App.entitlements deleted file mode 100644 index 2718db63..00000000 --- a/Passepartout/App/iOS/App.entitlements +++ /dev/null @@ -1,22 +0,0 @@ - - - - - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - com.apple.developer.networking.wifi-info - - com.apple.developer.siri - - com.apple.security.application-groups - - group.$(CFG_GROUP_ID) - - keychain-access-groups - - $(AppIdentifierPrefix)group.com.algoritmico.Passepartout - - - diff --git a/Passepartout/App/iOS/AppDelegate.swift b/Passepartout/App/iOS/AppDelegate.swift deleted file mode 100644 index 1f2ab0d3..00000000 --- a/Passepartout/App/iOS/AppDelegate.swift +++ /dev/null @@ -1,174 +0,0 @@ -// -// AppDelegate.swift -// Passepartout -// -// Created by Davide De Rosa on 6/6/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import SwiftyBeaver -import PassepartoutCore -import Convenience - -private let log = SwiftyBeaver.self - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { - - var window: UIWindow? - - private var importer: HostImporter? - - override init() { - AppConstants.Log.configure() - InfrastructureFactory.shared.preload() - super.init() - } - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - Theme.current.applyAppearance() - Reviewer.shared.eventCountBeforeRating = AppConstants.Rating.eventCount - - // Override point for customization after application launch. - let splitViewController = window!.rootViewController as! UISplitViewController -// splitViewController.preferredPrimaryColumnWidthFraction = 0.4 -// splitViewController.minimumPrimaryColumnWidth = 360.0 - splitViewController.maximumPrimaryColumnWidth = .infinity - splitViewController.delegate = self - if UIDevice.current.userInterfaceIdiom == .pad { - splitViewController.preferredDisplayMode = .oneBesideSecondary -// } else { -// splitViewController.preferredDisplayMode = .primaryOverlay - } - - ProductManager.shared.listProducts(completionHandler: nil) - - return true - } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. - TransientStore.shared.serialize(withProfiles: true) // synchronize - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - ProductManager.shared.reviewPurchases() - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - // MARK: UISplitViewControllerDelegate - - func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool { - return !TransientStore.shared.service.hasActiveProfile() - } - - // MARK: URLs - - func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { - guard let root = window?.rootViewController else { - fatalError("No window.rootViewController?") - } - let topmost = root.presentedViewController ?? root - return tryParseURL(url, passphrase: nil, target: topmost) - } - - private func tryParseURL(_ url: URL, passphrase: String?, target: UIViewController) -> Bool { - guard let rootViewController = window?.rootViewController else { - return false - } - importer = HostImporter(withConfigurationURL: url, parentViewController: rootViewController) - importer?.importHost(withPassphrase: passphrase, removeOnError: true, removeOnCancel: true) { - self.handleParsingResult($0, in: rootViewController) - } - return true - } - - private func handleParsingResult(_ parsingResult: OpenVPN.ConfigurationParser.Result, in target: UIViewController) { - - // already presented: update parsed configuration - if let nav = target as? UINavigationController, let wizard = nav.topViewController as? WizardHostViewController { - if let oldURL = wizard.parsingResult?.url { - try? FileManager.default.removeItem(at: oldURL) - } - wizard.parsingResult = parsingResult - wizard.removesConfigurationOnCancel = true - return - } - - // present now - let wizardNav = StoryboardScene.Organizer.wizardHostIdentifier.instantiate() - guard let wizard = wizardNav.topViewController as? WizardHostViewController else { - fatalError("Expected WizardHostViewController from storyboard") - } - wizard.parsingResult = parsingResult - wizard.removesConfigurationOnCancel = true - - wizardNav.applyModalPresentation(.current) - target.present(wizardNav, animated: true, completion: nil) - } -} - -extension UISplitViewController { - var serviceViewController: ServiceViewController? { - for vc in viewControllers { - guard let nav = vc as? UINavigationController else { - continue - } - if let found = nav.viewControllers.first(where: { - $0 as? ServiceViewController != nil - }) as? ServiceViewController { - return found - } - } - return nil - } -} - -extension AppDelegate { - func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { - do { - try ProductManager.shared.verifyEligible(forFeature: .siriShortcuts) - } catch { - return false - } - guard let interaction = userActivity.interaction else { - return false - } - if #available(iOS 12, *) { - IntentDispatcher.handleInteraction(interaction, completionHandler: nil) - } - return true - } -} diff --git a/Passepartout/App/iOS/Assets.xcassets/Contents.json b/Passepartout/App/iOS/Assets.xcassets/Contents.json deleted file mode 100644 index da4a164c..00000000 --- a/Passepartout/App/iOS/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/iOS/Base.lproj/About.storyboard b/Passepartout/App/iOS/Base.lproj/About.storyboard deleted file mode 100644 index b4790b3d..00000000 --- a/Passepartout/App/iOS/Base.lproj/About.storyboard +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/iOS/Base.lproj/LaunchScreen.storyboard b/Passepartout/App/iOS/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index b5600727..00000000 --- a/Passepartout/App/iOS/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/iOS/Base.lproj/Main.storyboard b/Passepartout/App/iOS/Base.lproj/Main.storyboard deleted file mode 100644 index 39142b22..00000000 --- a/Passepartout/App/iOS/Base.lproj/Main.storyboard +++ /dev/null @@ -1,686 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/iOS/Base.lproj/Organizer.storyboard b/Passepartout/App/iOS/Base.lproj/Organizer.storyboard deleted file mode 100644 index 8271a132..00000000 --- a/Passepartout/App/iOS/Base.lproj/Organizer.storyboard +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/iOS/Base.lproj/Purchase.storyboard b/Passepartout/App/iOS/Base.lproj/Purchase.storyboard deleted file mode 100644 index f969ca83..00000000 --- a/Passepartout/App/iOS/Base.lproj/Purchase.storyboard +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/iOS/Base.lproj/Shortcuts.storyboard b/Passepartout/App/iOS/Base.lproj/Shortcuts.storyboard deleted file mode 100644 index 3b89aa4b..00000000 --- a/Passepartout/App/iOS/Base.lproj/Shortcuts.storyboard +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/iOS/CHANGELOG.md b/Passepartout/App/iOS/CHANGELOG.md index 63e9f658..620eb301 100644 --- a/Passepartout/App/iOS/CHANGELOG.md +++ b/Passepartout/App/iOS/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- App completely rewritten in SwiftUI. + +### Fixed + +- Files occasionally not selectable in browser. + ## 1.18.0 (2022-02-15) ### Added diff --git a/Passepartout/App/iOS/Cells/Cells.swift b/Passepartout/App/iOS/Cells/Cells.swift deleted file mode 100644 index 1a4a032f..00000000 --- a/Passepartout/App/iOS/Cells/Cells.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// Cells.swift -// Passepartout -// -// Created by Davide De Rosa on 6/25/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit - -struct Cells { -} - -extension UITableView { - func dequeue(_ type: T.Type, identifier: String, for indexPath: IndexPath) -> T { - guard let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? T else { - fatalError("Not a \(T.description())") - } - return cell - } -} - -protocol CellProvider { - associatedtype T: UITableViewCell - - static var identifier: String { get } - - func register(with tableView: UITableView) - - func dequeue(from tableView: UITableView, for indexPath: IndexPath) -> T -} - -extension CellProvider { - static var identifier: String { - return String(describing: T.self) - } - - func register(with tableView: UITableView) { - tableView.register(T.self, forCellReuseIdentifier: Self.identifier) - } -} diff --git a/Passepartout/App/iOS/Cells/DestructiveTableViewCell.swift b/Passepartout/App/iOS/Cells/DestructiveTableViewCell.swift deleted file mode 100644 index bb191305..00000000 --- a/Passepartout/App/iOS/Cells/DestructiveTableViewCell.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// DestructiveTableViewCell.swift -// Passepartout -// -// Created by Davide De Rosa on 6/22/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit - -extension Cells { - static let destructive = DestructiveTableViewCell.Provider() -} - -class DestructiveTableViewCell: UITableViewCell { - @IBOutlet private(set) lazy var labelCaption: UILabel? = { - let label = UILabel() - label.textAlignment = .center - contentView.addSubview(label) - return label - }() - - var caption: String? { - get { - return labelCaption?.text - } - set { - labelCaption?.text = newValue - } - } - - var captionColor: UIColor? { - get { - return labelCaption?.textColor - } - set { - labelCaption?.textColor = newValue - } - } - - override func layoutSubviews() { - super.layoutSubviews() - - labelCaption?.frame = contentView.bounds - } -} - -extension DestructiveTableViewCell { - class Provider: CellProvider { - typealias T = DestructiveTableViewCell - - func dequeue(from tableView: UITableView, for indexPath: IndexPath) -> DestructiveTableViewCell { - let cell = tableView.dequeue(T.self, identifier: Provider.identifier, for: indexPath) - cell.apply(.current) - return cell - } - } -} diff --git a/Passepartout/App/iOS/Cells/FieldTableViewCell.swift b/Passepartout/App/iOS/Cells/FieldTableViewCell.swift deleted file mode 100644 index d719cb7f..00000000 --- a/Passepartout/App/iOS/Cells/FieldTableViewCell.swift +++ /dev/null @@ -1,137 +0,0 @@ -// -// FieldTableViewCell.swift -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit - -extension Cells { - static let field = FieldTableViewCell.Provider() -} - -protocol FieldTableViewCellDelegate: AnyObject { - func fieldCellDidEdit(_: FieldTableViewCell) - - func fieldCellDidEnter(_: FieldTableViewCell) -} - -class FieldTableViewCell: UITableViewCell { - var caption: String? { - get { - return textLabel?.text - } - set { - textLabel?.text = newValue - } - } - - var captionColor: UIColor? { - get { - return textLabel?.textColor - } - set { - textLabel?.textColor = newValue - } - } - - var captionWidth: CGFloat = 0.0 { - didSet { - layoutSubviews() - } - } - - var allowedCharset: CharacterSet? { - didSet { - illegalCharset = allowedCharset?.inverted - } - } - - private var illegalCharset: CharacterSet? - - private(set) lazy var field = UITextField() - - weak var delegate: FieldTableViewCellDelegate? - - override func awakeFromNib() { - super.awakeFromNib() - - field.autocapitalizationType = .none - field.autocorrectionType = .no - field.textAlignment = .left - field.delegate = self - selectionStyle = .none - contentView.addSubview(field) - } - - override func layoutSubviews() { - super.layoutSubviews() - - var frame: CGRect - let label: UILabel = textLabel! - - frame = label.frame - frame.size.width = captionWidth - label.frame = frame - - let offset: CGFloat = 15.0 - field.frame = CGRect( - x: label.frame.maxX, - y: 0.0, - width: bounds.size.width - label.frame.maxX - offset, - height: bounds.size.height - ) - } -} - -extension FieldTableViewCell: UITextFieldDelegate { - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - guard let illegalCharset = illegalCharset else { - return true - } - guard string.rangeOfCharacter(from: illegalCharset) == nil else { - return false - } - return true - } - - func textFieldDidEndEditing(_ textField: UITextField) { - delegate?.fieldCellDidEdit(self) - } - - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - delegate?.fieldCellDidEnter(self) - return true - } -} - -extension FieldTableViewCell { - class Provider: CellProvider { - typealias T = FieldTableViewCell - - func dequeue(from tableView: UITableView, for indexPath: IndexPath) -> FieldTableViewCell { - let cell = tableView.dequeue(T.self, identifier: Provider.identifier, for: indexPath) - cell.apply(.current) - return cell - } - } -} diff --git a/Passepartout/App/iOS/Cells/SettingTableViewCell.swift b/Passepartout/App/iOS/Cells/SettingTableViewCell.swift deleted file mode 100644 index b81f4c80..00000000 --- a/Passepartout/App/iOS/Cells/SettingTableViewCell.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// SettingTableViewCell.swift -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit - -extension Cells { - static let setting = SettingTableViewCell.Provider() -} - -class SettingTableViewCell: UITableViewCell { - var isTappable: Bool = true { - didSet { - selectionStyle = isTappable ? .default : .none - } - } - - var leftText: String? { - get { - return textLabel?.text - } - set { - textLabel?.text = newValue - } - } - - var leftTextColor: UIColor? { - get { - return textLabel?.textColor - } - set { - textLabel?.textColor = newValue - } - } - - var rightText: String? { - get { - return detailTextLabel?.text - } - set { - detailTextLabel?.text = newValue - } - } - - var rightTextColor: UIColor? { - get { - return detailTextLabel?.textColor - } - set { - detailTextLabel?.textColor = newValue - } - } -} - -extension SettingTableViewCell { - class Provider: CellProvider { - typealias T = SettingTableViewCell - - func dequeue(from tableView: UITableView, for indexPath: IndexPath) -> SettingTableViewCell { - let cell = tableView.dequeue(T.self, identifier: Provider.identifier, for: indexPath) - cell.apply(.current) - cell.imageView?.image = nil - cell.rightText = nil - cell.isTappable = true - cell.accessoryType = .disclosureIndicator - return cell - } - } -} diff --git a/Passepartout/App/iOS/Cells/ToggleTableViewCell.swift b/Passepartout/App/iOS/Cells/ToggleTableViewCell.swift deleted file mode 100644 index a2bc344a..00000000 --- a/Passepartout/App/iOS/Cells/ToggleTableViewCell.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// ToggleTableViewCell.swift -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit - -extension Cells { - static let toggle = ToggleTableViewCell.Provider() -} - -protocol ToggleTableViewCellDelegate: AnyObject { - func toggleCell(_: ToggleTableViewCell, didToggleToValue value: Bool) -} - -class ToggleTableViewCell: UITableViewCell { - var caption: String? { - get { - return textLabel?.text - } - set { - textLabel?.text = newValue - } - } - - var captionColor: UIColor? { - get { - return textLabel?.textColor - } - set { - textLabel?.textColor = newValue - } - } - - var toggle: UISwitch { - return accessoryView as! UISwitch - } - - var isOn: Bool { - get { - return toggle.isOn - } - set { - guard newValue != toggle.isOn else { - return - } - toggle.isOn = newValue - } - } - - func setOn(_ on: Bool, animated: Bool) { - guard on != toggle.isOn else { - return - } - toggle.setOn(on, animated: animated) - } - - weak var delegate: ToggleTableViewCellDelegate? - - override func awakeFromNib() { - super.awakeFromNib() - - let toggle = UISwitch() - toggle.addTarget(self, action: #selector(toggleMoved), for: .valueChanged) - accessoryView = toggle - selectionStyle = .none - } - - @objc private func toggleMoved() { - delegate?.toggleCell(self, didToggleToValue: toggle.isOn) - } -} - -extension ToggleTableViewCell { - class Provider: CellProvider { - typealias T = ToggleTableViewCell - - func dequeue(from tableView: UITableView, for indexPath: IndexPath) -> ToggleTableViewCell { - let cell = tableView.dequeue(T.self, identifier: Provider.identifier, for: indexPath) - cell.apply(.current) - return cell - } - - func dequeue(from tableView: UITableView, for indexPath: IndexPath, tag: Int, delegate: ToggleTableViewCellDelegate) -> ToggleTableViewCell { - let cell = tableView.dequeue(T.self, identifier: Provider.identifier, for: indexPath) - cell.apply(.current) - cell.tag = tag - cell.delegate = delegate - return cell - } - } -} diff --git a/Passepartout/App/iOS/Flags.xcassets/Contents.json b/Passepartout/App/iOS/Flags.xcassets/Contents.json deleted file mode 100644 index da4a164c..00000000 --- a/Passepartout/App/iOS/Flags.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/iOS/Global/HostImporter.swift b/Passepartout/App/iOS/Global/HostImporter.swift deleted file mode 100644 index 54141fc8..00000000 --- a/Passepartout/App/iOS/Global/HostImporter.swift +++ /dev/null @@ -1,169 +0,0 @@ -// -// HostImporter.swift -// Passepartout -// -// Created by Davide De Rosa on 10/22/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import SwiftyBeaver -import PassepartoutCore - -private let log = SwiftyBeaver.self - -class HostImporter { - private let service = TransientStore.shared.service - - private weak var viewController: UIViewController? - - private let configurationURL: URL - - init(withConfigurationURL configurationURL: URL, parentViewController: UIViewController) { - self.configurationURL = configurationURL - log.debug("Parsing configuration URL: \(configurationURL)") - - viewController = parentViewController - } - - func importHost(withPassphrase passphrase: String?, removeOnError: Bool, removeOnCancel: Bool, completionHandler: @escaping (OpenVPN.ConfigurationParser.Result) -> Void) { - let result: OpenVPN.ConfigurationParser.Result - do { - result = try OpenVPN.ConfigurationParser.parsed(fromURL: configurationURL, passphrase: passphrase) - } catch let e as ConfigurationError { - switch e { - case .encryptionPassphrase, .unableToDecrypt(_): - enterPassphraseForHost(at: configurationURL, removeOnError: removeOnError, removeOnCancel: removeOnCancel, completionHandler: completionHandler) - - default: - alertImportError(e, removeOnError: removeOnError) - } - return - } catch let e { - alertImportError(e, removeOnError: removeOnError) - return - } - - if let warning = result.warning { - alertImportWarning(warning, removeOnCancel: removeOnCancel) { - completionHandler(result) - } - return - } - - completionHandler(result) - } - - private func alertImportError(_ error: Error, removeOnError: Bool) { - let message = HostImporter.localizedMessage(forError: error) - let alert = UIAlertController.asAlert(configurationURL.normalizedFilename, message) - alert.addCancelAction(L10n.Global.ok) - viewController?.present(alert, animated: true, completion: nil) - - if removeOnError { - try? FileManager.default.removeItem(at: configurationURL) - } - } - - private func alertImportWarning(_ warning: ConfigurationError, removeOnCancel: Bool, completionHandler: @escaping () -> Void) { - let message = HostImporter.localizedDetailsMessage(forWarning: warning) - let alert = UIAlertController.asAlert(configurationURL.normalizedFilename, L10n.ParsedFile.Alerts.PotentiallyUnsupported.message(message)) - alert.addPreferredAction(L10n.Global.ok) { - completionHandler() - } - alert.addCancelAction(L10n.Global.cancel) { - if removeOnCancel { - try? FileManager.default.removeItem(at: self.configurationURL) - } - } - viewController?.present(alert, animated: true, completion: nil) - } - - private func enterPassphraseForHost(at url: URL, removeOnError: Bool, removeOnCancel: Bool, completionHandler: @escaping (OpenVPN.ConfigurationParser.Result) -> Void) { - let alert = UIAlertController.asAlert(configurationURL.normalizedFilename, L10n.ParsedFile.Alerts.EncryptionPassphrase.message) - alert.addTextField { (field) in - field.isSecureTextEntry = true - } - alert.addPreferredAction(L10n.Global.ok) { - guard let passphrase = alert.textFields?.first?.text else { - return - } - self.importHost( - withPassphrase: passphrase, - removeOnError: removeOnError, - removeOnCancel: removeOnCancel, - completionHandler: completionHandler - ) - } - alert.addCancelAction(L10n.Global.cancel) { - if removeOnCancel { - try? FileManager.default.removeItem(at: url) - } - } - viewController?.present(alert, animated: true, completion: nil) - } - - // MARK: Helpers - - private static func localizedMessage(forError error: Error) -> String { - if let appError = error as? ConfigurationError { - switch appError { - case .malformed(let option): - log.error("Could not parse configuration URL: malformed option, \(option)") - return L10n.ParsedFile.Alerts.Malformed.message(option) - - case .missingConfiguration(let option): - log.error("Could not parse configuration URL: missing configuration, \(option)") - return L10n.ParsedFile.Alerts.Missing.message(option) - - case .unsupportedConfiguration(var option): - if option.contains("external") { - option.append(" (see FAQ)") - } - log.error("Could not parse configuration URL: unsupported configuration, \(option)") - return L10n.ParsedFile.Alerts.Unsupported.message(option) - - default: - break - } - } - log.error("Could not parse configuration URL: \(error)") - return L10n.ParsedFile.Alerts.Parsing.message(error.localizedDescription) - } - - private static func localizedDetailsMessage(forWarning warning: ConfigurationError) -> String { - switch warning { - case .malformed(let option): - return option - - case .missingConfiguration(let option): - return option - - case .unsupportedConfiguration(var option): - if option.contains("external") { - option.append(" (see FAQ)") - } - return option - - default: - return "" // XXX: should never get here - } - } -} diff --git a/Passepartout/App/iOS/Global/IssueReporter.swift b/Passepartout/App/iOS/Global/IssueReporter.swift deleted file mode 100644 index 98ee5add..00000000 --- a/Passepartout/App/iOS/Global/IssueReporter.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// IssueReporter.swift -// Passepartout -// -// Created by Davide De Rosa on 9/26/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import MessageUI -import PassepartoutCore - -class IssueReporter: NSObject { - static let shared = IssueReporter() - - private weak var viewController: UIViewController? - - override private init() { - super.init() - } - - func present(in viewController: UIViewController, withIssue issue: Issue) { - guard MFMailComposeViewController.canSendMail() else { - let app = UIApplication.shared - let V = AppConstants.IssueReporter.Email.self - let body = V.body(V.template, DebugLog(raw: "--").decoratedString()) - guard let url = URL.mailto(to: V.recipient, subject: V.subject, body: body), app.canOpenURL(url) else { - let alert = UIAlertController.asAlert(L10n.IssueReporter.title, L10n.Global.emailNotConfigured) - alert.addCancelAction(L10n.Global.ok) - viewController.present(alert, animated: true, completion: nil) - return - } - app.open(url, options: [:], completionHandler: nil) - return - } - - self.viewController = viewController - - if issue.debugLog { - let alert = UIAlertController.asAlert(L10n.IssueReporter.title, L10n.IssueReporter.message) - alert.addPreferredAction(L10n.IssueReporter.Buttons.accept) { - VPN.shared.requestDebugLog(fallback: TransientStore.shared.debugSnapshot) { - self.composeEmail(withDebugLog: $0, issue: issue) - } - } - alert.addCancelAction(L10n.Global.cancel) - viewController.present(alert, animated: true, completion: nil) - } else { - composeEmail(withDebugLog: nil, issue: issue) - } - } - - private func composeEmail(withDebugLog debugLog: String?, issue: Issue) { - let vc = MFMailComposeViewController() - vc.setToRecipients([AppConstants.IssueReporter.Email.recipient]) - vc.setSubject(AppConstants.IssueReporter.Email.subject) - - let bodyContent = AppConstants.IssueReporter.Email.template - var bodyMetadata = "--\n\n" - bodyMetadata += DebugLog(raw: "").decoratedString() - if let metadata = issue.infrastructureMetadata { - bodyMetadata += "Provider: \(metadata.description)\n" - if let lastUpdated = InfrastructureFactory.shared.modificationDate(forName: metadata.name) { - bodyMetadata += "Last updated: \(lastUpdated)\n" - } - bodyMetadata += "\n" - } - bodyMetadata += "--" - vc.setMessageBody(AppConstants.IssueReporter.Email.body(bodyContent, bodyMetadata), isHTML: false) - - if let raw = debugLog { - let attachment = DebugLog(raw: raw).decoratedData() - vc.addAttachmentData(attachment, mimeType: AppConstants.IssueReporter.MIME.debugLog, fileName: AppConstants.IssueReporter.Filenames.debugLog) - } - if let url = issue.configurationURL { - do { - let parsedFile = try OpenVPN.ConfigurationParser.parsed(fromURL: url, returnsStripped: true) - if let attachment = parsedFile.strippedLines?.joined(separator: "\n").data(using: .utf8) { - vc.addAttachmentData(attachment, mimeType: AppConstants.IssueReporter.MIME.configuration, fileName: AppConstants.IssueReporter.Filenames.configuration) - } - } catch { - } - } - vc.mailComposeDelegate = self - vc.apply(.current) - viewController?.present(vc, animated: true, completion: nil) - } -} - -extension IssueReporter: MFMailComposeViewControllerDelegate { - func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { - viewController?.dismiss(animated: true, completion: nil) - } -} diff --git a/Passepartout/App/iOS/Global/Macros.swift b/Passepartout/App/iOS/Global/Macros.swift deleted file mode 100644 index cee610e4..00000000 --- a/Passepartout/App/iOS/Global/Macros.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// Macros.swift -// Passepartout -// -// Created by Davide De Rosa on 6/16/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import PassepartoutCore - -extension UIView { - static func get() -> T { - let name = String(describing: T.self) - let nib = UINib(nibName: name, bundle: nil) - let objects = nib.instantiate(withOwner: nil) - for o in objects { - if let view = o as? T { - return view - } - } - fatalError() - } -} - -extension UITableView { - func scrollToRowAsync(at indexPath: IndexPath) { - DispatchQueue.main.async { [weak self] in - self?.scrollToRow(at: indexPath, at: .middle, animated: false) - } - } - - func selectRowAsync(at indexPath: IndexPath) { - DispatchQueue.main.async { [weak self] in - self?.selectRow(at: indexPath, animated: false, scrollPosition: .middle) - } - } -} - -extension UIColor { - convenience init(rgb: UInt32, alpha: CGFloat) { - let r = CGFloat((rgb & 0xff0000) >> 16) / 255.0 - let g = CGFloat((rgb & 0xff00) >> 8) / 255.0 - let b = CGFloat(rgb & 0xff) / 255.0 - self.init(red: r, green: g, blue: b, alpha: alpha) - } -} - -extension UIViewController { - func presentPurchaseScreen(forProduct product: LocalProduct, delegate: PurchaseViewControllerDelegate? = nil) { - let nav = StoryboardScene.Purchase.initialScene.instantiate() - let vc = nav.topViewController as? PurchaseViewController -// vc?.feature = product - vc?.delegate = delegate - - // enforce pre iOS 13 behavior - nav.modalPresentationStyle = .fullScreen - - present(nav, animated: true, completion: nil) - } - - func presentBetaFeatureUnavailable(_ title: String) { - let alert = UIAlertController.asAlert(title, "The requested feature is unavailable in beta.") - alert.addCancelAction("OK") - present(alert, animated: true, completion: nil) - } -} - -func visitURL(_ url: URL) { - UIApplication.shared.open(url, options: [:], completionHandler: nil) -} - -func delay(_ block: @escaping () -> Void) { - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) { - block() - } -} diff --git a/Passepartout/App/iOS/Global/SwiftGen+Assets.swift b/Passepartout/App/iOS/Global/SwiftGen+Assets.swift deleted file mode 100644 index 91737add..00000000 --- a/Passepartout/App/iOS/Global/SwiftGen+Assets.swift +++ /dev/null @@ -1,355 +0,0 @@ -// swiftlint:disable all -// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen - -#if os(macOS) - import AppKit -#elseif os(iOS) - import UIKit -#elseif os(tvOS) || os(watchOS) - import UIKit -#endif - -// Deprecated typealiases -@available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0") -internal typealias AssetImageTypeAlias = ImageAsset.Image - -// swiftlint:disable superfluous_disable_command file_length implicit_return - -// MARK: - Asset Catalogs - -// swiftlint:disable identifier_name line_length nesting type_body_length type_name -internal enum Asset { - internal enum Assets { - internal static let logo = ImageAsset(name: "logo") - } - internal enum Flags { - internal static let ad = ImageAsset(name: "ad") - internal static let ae = ImageAsset(name: "ae") - internal static let af = ImageAsset(name: "af") - internal static let ag = ImageAsset(name: "ag") - internal static let ai = ImageAsset(name: "ai") - internal static let al = ImageAsset(name: "al") - internal static let am = ImageAsset(name: "am") - internal static let ao = ImageAsset(name: "ao") - internal static let aq = ImageAsset(name: "aq") - internal static let ar = ImageAsset(name: "ar") - internal static let `as` = ImageAsset(name: "as") - internal static let at = ImageAsset(name: "at") - internal static let au = ImageAsset(name: "au") - internal static let aw = ImageAsset(name: "aw") - internal static let ax = ImageAsset(name: "ax") - internal static let az = ImageAsset(name: "az") - internal static let ba = ImageAsset(name: "ba") - internal static let bb = ImageAsset(name: "bb") - internal static let bd = ImageAsset(name: "bd") - internal static let be = ImageAsset(name: "be") - internal static let bf = ImageAsset(name: "bf") - internal static let bg = ImageAsset(name: "bg") - internal static let bh = ImageAsset(name: "bh") - internal static let bi = ImageAsset(name: "bi") - internal static let bj = ImageAsset(name: "bj") - internal static let bl = ImageAsset(name: "bl") - internal static let bm = ImageAsset(name: "bm") - internal static let bn = ImageAsset(name: "bn") - internal static let bo = ImageAsset(name: "bo") - internal static let bq = ImageAsset(name: "bq") - internal static let br = ImageAsset(name: "br") - internal static let bs = ImageAsset(name: "bs") - internal static let bt = ImageAsset(name: "bt") - internal static let bv = ImageAsset(name: "bv") - internal static let bw = ImageAsset(name: "bw") - internal static let by = ImageAsset(name: "by") - internal static let bz = ImageAsset(name: "bz") - internal static let ca = ImageAsset(name: "ca") - internal static let cc = ImageAsset(name: "cc") - internal static let cd = ImageAsset(name: "cd") - internal static let cf = ImageAsset(name: "cf") - internal static let cg = ImageAsset(name: "cg") - internal static let ch = ImageAsset(name: "ch") - internal static let ci = ImageAsset(name: "ci") - internal static let ck = ImageAsset(name: "ck") - internal static let cl = ImageAsset(name: "cl") - internal static let cm = ImageAsset(name: "cm") - internal static let cn = ImageAsset(name: "cn") - internal static let co = ImageAsset(name: "co") - internal static let cr = ImageAsset(name: "cr") - internal static let cu = ImageAsset(name: "cu") - internal static let cv = ImageAsset(name: "cv") - internal static let cw = ImageAsset(name: "cw") - internal static let cx = ImageAsset(name: "cx") - internal static let cy = ImageAsset(name: "cy") - internal static let cz = ImageAsset(name: "cz") - internal static let de = ImageAsset(name: "de") - internal static let dj = ImageAsset(name: "dj") - internal static let dk = ImageAsset(name: "dk") - internal static let dm = ImageAsset(name: "dm") - internal static let `do` = ImageAsset(name: "do") - internal static let dz = ImageAsset(name: "dz") - internal static let ec = ImageAsset(name: "ec") - internal static let ee = ImageAsset(name: "ee") - internal static let eg = ImageAsset(name: "eg") - internal static let eh = ImageAsset(name: "eh") - internal static let er = ImageAsset(name: "er") - internal static let esCt = ImageAsset(name: "es-ct") - internal static let es = ImageAsset(name: "es") - internal static let et = ImageAsset(name: "et") - internal static let eu = ImageAsset(name: "eu") - internal static let fi = ImageAsset(name: "fi") - internal static let fj = ImageAsset(name: "fj") - internal static let fk = ImageAsset(name: "fk") - internal static let fm = ImageAsset(name: "fm") - internal static let fo = ImageAsset(name: "fo") - internal static let fr = ImageAsset(name: "fr") - internal static let ga = ImageAsset(name: "ga") - internal static let gbEng = ImageAsset(name: "gb-eng") - internal static let gbNir = ImageAsset(name: "gb-nir") - internal static let gbSct = ImageAsset(name: "gb-sct") - internal static let gbWls = ImageAsset(name: "gb-wls") - internal static let gb = ImageAsset(name: "gb") - internal static let gd = ImageAsset(name: "gd") - internal static let ge = ImageAsset(name: "ge") - internal static let gf = ImageAsset(name: "gf") - internal static let gg = ImageAsset(name: "gg") - internal static let gh = ImageAsset(name: "gh") - internal static let gi = ImageAsset(name: "gi") - internal static let gl = ImageAsset(name: "gl") - internal static let gm = ImageAsset(name: "gm") - internal static let gn = ImageAsset(name: "gn") - internal static let gp = ImageAsset(name: "gp") - internal static let gq = ImageAsset(name: "gq") - internal static let gr = ImageAsset(name: "gr") - internal static let gs = ImageAsset(name: "gs") - internal static let gt = ImageAsset(name: "gt") - internal static let gu = ImageAsset(name: "gu") - internal static let gw = ImageAsset(name: "gw") - internal static let gy = ImageAsset(name: "gy") - internal static let hk = ImageAsset(name: "hk") - internal static let hm = ImageAsset(name: "hm") - internal static let hn = ImageAsset(name: "hn") - internal static let hr = ImageAsset(name: "hr") - internal static let ht = ImageAsset(name: "ht") - internal static let hu = ImageAsset(name: "hu") - internal static let id = ImageAsset(name: "id") - internal static let ie = ImageAsset(name: "ie") - internal static let il = ImageAsset(name: "il") - internal static let im = ImageAsset(name: "im") - internal static let `in` = ImageAsset(name: "in") - internal static let io = ImageAsset(name: "io") - internal static let iq = ImageAsset(name: "iq") - internal static let ir = ImageAsset(name: "ir") - internal static let `is` = ImageAsset(name: "is") - internal static let it = ImageAsset(name: "it") - internal static let je = ImageAsset(name: "je") - internal static let jm = ImageAsset(name: "jm") - internal static let jo = ImageAsset(name: "jo") - internal static let jp = ImageAsset(name: "jp") - internal static let ke = ImageAsset(name: "ke") - internal static let kg = ImageAsset(name: "kg") - internal static let kh = ImageAsset(name: "kh") - internal static let ki = ImageAsset(name: "ki") - internal static let km = ImageAsset(name: "km") - internal static let kn = ImageAsset(name: "kn") - internal static let kp = ImageAsset(name: "kp") - internal static let kr = ImageAsset(name: "kr") - internal static let kw = ImageAsset(name: "kw") - internal static let ky = ImageAsset(name: "ky") - internal static let kz = ImageAsset(name: "kz") - internal static let la = ImageAsset(name: "la") - internal static let lb = ImageAsset(name: "lb") - internal static let lc = ImageAsset(name: "lc") - internal static let li = ImageAsset(name: "li") - internal static let lk = ImageAsset(name: "lk") - internal static let lr = ImageAsset(name: "lr") - internal static let ls = ImageAsset(name: "ls") - internal static let lt = ImageAsset(name: "lt") - internal static let lu = ImageAsset(name: "lu") - internal static let lv = ImageAsset(name: "lv") - internal static let ly = ImageAsset(name: "ly") - internal static let ma = ImageAsset(name: "ma") - internal static let mc = ImageAsset(name: "mc") - internal static let md = ImageAsset(name: "md") - internal static let me = ImageAsset(name: "me") - internal static let mf = ImageAsset(name: "mf") - internal static let mg = ImageAsset(name: "mg") - internal static let mh = ImageAsset(name: "mh") - internal static let mk = ImageAsset(name: "mk") - internal static let ml = ImageAsset(name: "ml") - internal static let mm = ImageAsset(name: "mm") - internal static let mn = ImageAsset(name: "mn") - internal static let mo = ImageAsset(name: "mo") - internal static let mp = ImageAsset(name: "mp") - internal static let mq = ImageAsset(name: "mq") - internal static let mr = ImageAsset(name: "mr") - internal static let ms = ImageAsset(name: "ms") - internal static let mt = ImageAsset(name: "mt") - internal static let mu = ImageAsset(name: "mu") - internal static let mv = ImageAsset(name: "mv") - internal static let mw = ImageAsset(name: "mw") - internal static let mx = ImageAsset(name: "mx") - internal static let my = ImageAsset(name: "my") - internal static let mz = ImageAsset(name: "mz") - internal static let na = ImageAsset(name: "na") - internal static let nc = ImageAsset(name: "nc") - internal static let ne = ImageAsset(name: "ne") - internal static let nf = ImageAsset(name: "nf") - internal static let ng = ImageAsset(name: "ng") - internal static let ni = ImageAsset(name: "ni") - internal static let nl = ImageAsset(name: "nl") - internal static let no = ImageAsset(name: "no") - internal static let np = ImageAsset(name: "np") - internal static let nr = ImageAsset(name: "nr") - internal static let nu = ImageAsset(name: "nu") - internal static let nz = ImageAsset(name: "nz") - internal static let om = ImageAsset(name: "om") - internal static let pa = ImageAsset(name: "pa") - internal static let pe = ImageAsset(name: "pe") - internal static let pf = ImageAsset(name: "pf") - internal static let pg = ImageAsset(name: "pg") - internal static let ph = ImageAsset(name: "ph") - internal static let pk = ImageAsset(name: "pk") - internal static let pl = ImageAsset(name: "pl") - internal static let pm = ImageAsset(name: "pm") - internal static let pn = ImageAsset(name: "pn") - internal static let pr = ImageAsset(name: "pr") - internal static let ps = ImageAsset(name: "ps") - internal static let pt = ImageAsset(name: "pt") - internal static let pw = ImageAsset(name: "pw") - internal static let py = ImageAsset(name: "py") - internal static let qa = ImageAsset(name: "qa") - internal static let re = ImageAsset(name: "re") - internal static let ro = ImageAsset(name: "ro") - internal static let rs = ImageAsset(name: "rs") - internal static let ru = ImageAsset(name: "ru") - internal static let rw = ImageAsset(name: "rw") - internal static let sa = ImageAsset(name: "sa") - internal static let sb = ImageAsset(name: "sb") - internal static let sc = ImageAsset(name: "sc") - internal static let sd = ImageAsset(name: "sd") - internal static let se = ImageAsset(name: "se") - internal static let sg = ImageAsset(name: "sg") - internal static let sh = ImageAsset(name: "sh") - internal static let si = ImageAsset(name: "si") - internal static let sj = ImageAsset(name: "sj") - internal static let sk = ImageAsset(name: "sk") - internal static let sl = ImageAsset(name: "sl") - internal static let sm = ImageAsset(name: "sm") - internal static let sn = ImageAsset(name: "sn") - internal static let so = ImageAsset(name: "so") - internal static let sr = ImageAsset(name: "sr") - internal static let ss = ImageAsset(name: "ss") - internal static let st = ImageAsset(name: "st") - internal static let sv = ImageAsset(name: "sv") - internal static let sx = ImageAsset(name: "sx") - internal static let sy = ImageAsset(name: "sy") - internal static let sz = ImageAsset(name: "sz") - internal static let tc = ImageAsset(name: "tc") - internal static let td = ImageAsset(name: "td") - internal static let tf = ImageAsset(name: "tf") - internal static let tg = ImageAsset(name: "tg") - internal static let th = ImageAsset(name: "th") - internal static let tj = ImageAsset(name: "tj") - internal static let tk = ImageAsset(name: "tk") - internal static let tl = ImageAsset(name: "tl") - internal static let tm = ImageAsset(name: "tm") - internal static let tn = ImageAsset(name: "tn") - internal static let to = ImageAsset(name: "to") - internal static let tr = ImageAsset(name: "tr") - internal static let tt = ImageAsset(name: "tt") - internal static let tv = ImageAsset(name: "tv") - internal static let tw = ImageAsset(name: "tw") - internal static let tz = ImageAsset(name: "tz") - internal static let ua = ImageAsset(name: "ua") - internal static let ug = ImageAsset(name: "ug") - internal static let um = ImageAsset(name: "um") - internal static let un = ImageAsset(name: "un") - internal static let us = ImageAsset(name: "us") - internal static let uy = ImageAsset(name: "uy") - internal static let uz = ImageAsset(name: "uz") - internal static let va = ImageAsset(name: "va") - internal static let vc = ImageAsset(name: "vc") - internal static let ve = ImageAsset(name: "ve") - internal static let vg = ImageAsset(name: "vg") - internal static let vi = ImageAsset(name: "vi") - internal static let vn = ImageAsset(name: "vn") - internal static let vu = ImageAsset(name: "vu") - internal static let wf = ImageAsset(name: "wf") - internal static let ws = ImageAsset(name: "ws") - internal static let xk = ImageAsset(name: "xk") - internal static let ye = ImageAsset(name: "ye") - internal static let yt = ImageAsset(name: "yt") - internal static let za = ImageAsset(name: "za") - internal static let zm = ImageAsset(name: "zm") - internal static let zw = ImageAsset(name: "zw") - } - internal enum Providers { - internal static let csv = ImageAsset(name: "csv") - internal static let hideme = ImageAsset(name: "hideme") - internal static let mullvad = ImageAsset(name: "mullvad") - internal static let nordvpn = ImageAsset(name: "nordvpn") - internal static let oeck = ImageAsset(name: "oeck") - internal static let pia = ImageAsset(name: "pia") - internal static let placeholder = ImageAsset(name: "placeholder") - internal static let protonvpn = ImageAsset(name: "protonvpn") - internal static let surfshark = ImageAsset(name: "surfshark") - internal static let torguard = ImageAsset(name: "torguard") - internal static let tunnelbear = ImageAsset(name: "tunnelbear") - internal static let vyprvpn = ImageAsset(name: "vyprvpn") - internal static let windscribe = ImageAsset(name: "windscribe") - } -} -// swiftlint:enable identifier_name line_length nesting type_body_length type_name - -// MARK: - Implementation Details - -internal struct ImageAsset { - internal fileprivate(set) var name: String - - #if os(macOS) - internal typealias Image = NSImage - #elseif os(iOS) || os(tvOS) || os(watchOS) - internal typealias Image = UIImage - #endif - - internal var image: Image { - let bundle = BundleToken.bundle - #if os(iOS) || os(tvOS) - let image = Image(named: name, in: bundle, compatibleWith: nil) - #elseif os(macOS) - let name = NSImage.Name(self.name) - let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) - #elseif os(watchOS) - let image = Image(named: name) - #endif - guard let result = image else { - fatalError("Unable to load image named \(name).") - } - return result - } -} - -internal extension ImageAsset.Image { - @available(macOS, deprecated, - message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") - convenience init!(asset: ImageAsset) { - #if os(iOS) || os(tvOS) - let bundle = BundleToken.bundle - self.init(named: asset.name, in: bundle, compatibleWith: nil) - #elseif os(macOS) - self.init(named: NSImage.Name(asset.name)) - #elseif os(watchOS) - self.init(named: asset.name) - #endif - } -} - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type diff --git a/Passepartout/App/iOS/Global/SwiftGen+Scenes.swift b/Passepartout/App/iOS/Global/SwiftGen+Scenes.swift deleted file mode 100644 index 1c53260d..00000000 --- a/Passepartout/App/iOS/Global/SwiftGen+Scenes.swift +++ /dev/null @@ -1,119 +0,0 @@ -// swiftlint:disable all -// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen - -// swiftlint:disable sorted_imports -import Foundation -import UIKit - -// swiftlint:disable superfluous_disable_command -// swiftlint:disable file_length implicit_return - -// MARK: - Storyboard Scenes - -// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name -internal enum StoryboardScene { - internal enum About: StoryboardType { - internal static let storyboardName = "About" - - internal static let initialScene = InitialSceneType(storyboard: About.self) - } - internal enum Main: StoryboardType { - internal static let storyboardName = "Main" - - internal static let initialScene = InitialSceneType(storyboard: Main.self) - - internal static let accountIdentifier = SceneType(storyboard: Main.self, identifier: "AccountIdentifier") - - internal static let configurationIdentifier = SceneType(storyboard: Main.self, identifier: "ConfigurationIdentifier") - - internal static let providerPoolViewController = SceneType(storyboard: Main.self, identifier: "ProviderPoolViewController") - - internal static let serverNetworkViewController = SceneType(storyboard: Main.self, identifier: "ServerNetworkViewController") - - internal static let serviceIdentifier = SceneType(storyboard: Main.self, identifier: "ServiceIdentifier") - } - internal enum Organizer: StoryboardType { - internal static let storyboardName = "Organizer" - - internal static let initialScene = InitialSceneType(storyboard: Organizer.self) - - internal static let provider = SceneType(storyboard: Organizer.self, identifier: "Provider") - - internal static let wizardHostIdentifier = SceneType(storyboard: Organizer.self, identifier: "WizardHostIdentifier") - } - internal enum Purchase: StoryboardType { - internal static let storyboardName = "Purchase" - - internal static let initialScene = InitialSceneType(storyboard: Purchase.self) - } - internal enum Shortcuts: StoryboardType { - internal static let storyboardName = "Shortcuts" - - internal static let initialScene = InitialSceneType(storyboard: Shortcuts.self) - - internal static let shortcutsViewController = SceneType(storyboard: Shortcuts.self, identifier: "ShortcutsViewController") - } -} -// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name - -// MARK: - Implementation Details - -internal protocol StoryboardType { - static var storyboardName: String { get } -} - -internal extension StoryboardType { - static var storyboard: UIStoryboard { - let name = self.storyboardName - return UIStoryboard(name: name, bundle: BundleToken.bundle) - } -} - -internal struct SceneType { - internal let storyboard: StoryboardType.Type - internal let identifier: String - - internal func instantiate() -> T { - let identifier = self.identifier - guard let controller = storyboard.storyboard.instantiateViewController(withIdentifier: identifier) as? T else { - fatalError("ViewController '\(identifier)' is not of the expected class \(T.self).") - } - return controller - } - - @available(iOS 13.0, tvOS 13.0, *) - internal func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { - return storyboard.storyboard.instantiateViewController(identifier: identifier, creator: block) - } -} - -internal struct InitialSceneType { - internal let storyboard: StoryboardType.Type - - internal func instantiate() -> T { - guard let controller = storyboard.storyboard.instantiateInitialViewController() as? T else { - fatalError("ViewController is not of the expected class \(T.self).") - } - return controller - } - - @available(iOS 13.0, tvOS 13.0, *) - internal func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { - guard let controller = storyboard.storyboard.instantiateInitialViewController(creator: block) else { - fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") - } - return controller - } -} - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type diff --git a/Passepartout/App/iOS/Global/SwiftGen+Segues.swift b/Passepartout/App/iOS/Global/SwiftGen+Segues.swift deleted file mode 100644 index 1b1897ab..00000000 --- a/Passepartout/App/iOS/Global/SwiftGen+Segues.swift +++ /dev/null @@ -1,58 +0,0 @@ -// swiftlint:disable all -// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen - -// swiftlint:disable sorted_imports -import Foundation -import UIKit - -// swiftlint:disable superfluous_disable_command -// swiftlint:disable file_length - -// MARK: - Storyboard Segues - -// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name -internal enum StoryboardSegue { - internal enum Main: String, SegueType { - case accountSegueIdentifier = "AccountSegueIdentifier" - case debugLogSegueIdentifier = "DebugLogSegueIdentifier" - case endpointSegueIdentifier = "EndpointSegueIdentifier" - case hostParametersSegueIdentifier = "HostParametersSegueIdentifier" - case networkSettingsSegueIdentifier = "NetworkSettingsSegueIdentifier" - case providerPoolSegueIdentifier = "ProviderPoolSegueIdentifier" - case providerPresetSegueIdentifier = "ProviderPresetSegueIdentifier" - case serverNetworkSegueIdentifier = "ServerNetworkSegueIdentifier" - } - internal enum Organizer: String, SegueType { - case aboutSegueIdentifier = "AboutSegueIdentifier" - case addProviderSegueIdentifier = "AddProviderSegueIdentifier" - case donateSegueIdentifier = "DonateSegueIdentifier" - case importHostSegueIdentifier = "ImportHostSegueIdentifier" - case selectProfileSegueIdentifier = "SelectProfileSegueIdentifier" - case showImportedHostsSegueIdentifier = "ShowImportedHostsSegueIdentifier" - case siriShortcutsSegueIdentifier = "SiriShortcutsSegueIdentifier" - } - internal enum Shortcuts: String, SegueType { - case connectToSegueIdentifier = "ConnectToSegueIdentifier" - case pickLocationSegueIdentifier = "PickLocationSegueIdentifier" - case shortcutAddSegueIdentifier = "ShortcutAddSegueIdentifier" - } -} -// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name - -// MARK: - Implementation Details - -internal protocol SegueType: RawRepresentable {} - -internal extension UIViewController { - func perform(segue: S, sender: Any? = nil) where S.RawValue == String { - let identifier = segue.rawValue - performSegue(withIdentifier: identifier, sender: sender) - } -} - -internal extension SegueType where RawValue == String { - init?(_ segue: UIStoryboardSegue) { - guard let identifier = segue.identifier else { return nil } - self.init(rawValue: identifier) - } -} diff --git a/Passepartout/App/iOS/Global/Theme+Cells.swift b/Passepartout/App/iOS/Global/Theme+Cells.swift deleted file mode 100644 index d6265ab6..00000000 --- a/Passepartout/App/iOS/Global/Theme+Cells.swift +++ /dev/null @@ -1,168 +0,0 @@ -// -// Theme+Cells.swift -// Passepartout -// -// Created by Davide De Rosa on 6/25/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import PassepartoutCore -import ConvenienceUI - -extension UITableViewCell { - func applyChecked(_ checked: Bool, _ theme: Theme) { - textLabel?.font = .preferredFont(forTextStyle: .body) - accessoryType = checked ? .checkmark : .none - tintColor = theme.palette.accessory - } -} - -extension SingleOptionViewController { - func applyTint(_ theme: Theme) { - configurationBlock = { (cell, _) in - cell.tintColor = theme.palette.accessory - } - } -} - -extension DestructiveTableViewCell { - func apply(_ theme: Theme) { - labelCaption?.font = .preferredFont(forTextStyle: .body) - captionColor = theme.palette.destructive - accessoryType = .none - selectionStyle = .default - } -} - -extension FieldTableViewCell { - func apply(_ theme: Theme) { - textLabel?.font = .preferredFont(forTextStyle: .body) - field.font = .preferredFont(forTextStyle: .body) - captionColor = theme.palette.primaryText - } -} - -extension SettingTableViewCell { - func apply(_ theme: Theme) { - textLabel?.font = .preferredFont(forTextStyle: .body) - detailTextLabel?.font = .preferredFont(forTextStyle: .body) - leftTextColor = theme.palette.primaryText - rightTextColor = theme.palette.secondaryText - } -} - -extension ToggleTableViewCell { - func apply(_ theme: Theme) { - textLabel?.font = .preferredFont(forTextStyle: .body) - captionColor = theme.palette.primaryText - } -} - -extension ActivityTableViewCell { - func apply(_ theme: Theme) { - textLabel?.font = .preferredFont(forTextStyle: .body) - textLabel?.text = nil - detailTextLabel?.text = nil - } -} - -extension SettingTableViewCell { - func applyAction(_ theme: Theme) { - textLabel?.font = .preferredFont(forTextStyle: .body) - detailTextLabel?.font = .preferredFont(forTextStyle: .body) - leftTextColor = theme.palette.action - rightTextColor = nil - accessoryType = .none - } - - func applyVPN(_ theme: Theme, with vpnStatus: VPNStatus?, error: OpenVPNProviderError?) { - textLabel?.font = .preferredFont(forTextStyle: .body) - detailTextLabel?.font = .preferredFont(forTextStyle: .body) - - leftTextColor = theme.palette.primaryText - guard let vpnStatus = vpnStatus else { - rightText = L10n.Vpn.disabled - rightTextColor = theme.palette.secondaryText - return - } - - switch vpnStatus { - case .connecting: - rightText = L10n.Vpn.connecting - rightTextColor = theme.palette.indeterminate - - case .connected: - rightText = L10n.Vpn.active - rightTextColor = theme.palette.on - - case .disconnecting, .disconnected: - var disconnectionReason: String? - if let error = error { - switch error { - case .socketActivity, .timeout: - disconnectionReason = L10n.Vpn.Errors.timeout - - case .dnsFailure: - disconnectionReason = L10n.Vpn.Errors.dns - - case .tlsInitialization, .tlsServerVerification, .tlsHandshake: - disconnectionReason = L10n.Vpn.Errors.tls - - case .authentication: - disconnectionReason = L10n.Vpn.Errors.auth - - case .encryptionInitialization, .encryptionData: - disconnectionReason = L10n.Vpn.Errors.encryption - - case .serverCompression, .lzo: - disconnectionReason = L10n.Vpn.Errors.compression - - case .networkChanged: - disconnectionReason = L10n.Vpn.Errors.network - - case .routing: - disconnectionReason = L10n.Vpn.Errors.routing - - case .gatewayUnattainable: - disconnectionReason = L10n.Vpn.Errors.gateway - - case .serverShutdown: - disconnectionReason = L10n.Vpn.Errors.shutdown - - default: - break - } - } - switch vpnStatus { - case .disconnecting: - rightText = disconnectionReason ?? L10n.Vpn.disconnecting - rightTextColor = theme.palette.indeterminate - - case .disconnected: - rightText = disconnectionReason ?? L10n.Vpn.inactive - rightTextColor = theme.palette.off - - default: - break - } - } - } -} diff --git a/Passepartout/App/iOS/Global/Theme.swift b/Passepartout/App/iOS/Global/Theme.swift deleted file mode 100644 index 1f3d988b..00000000 --- a/Passepartout/App/iOS/Global/Theme.swift +++ /dev/null @@ -1,229 +0,0 @@ -// -// Theme.swift -// Passepartout -// -// Created by Davide De Rosa on 6/14/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import MessageUI -import StoreKit -import PassepartoutCore - -struct Theme { - struct Palette { - var primaryBackground = UIColor(rgb: 0x515d71, alpha: 1.0) - - var accent1 = UIColor(rgb: 0xd69c68, alpha: 1.0) - - var primaryText: UIColor = { - guard #available(iOS 13, *) else { - return .darkText - } - return .label - }() - - var primaryLightText: UIColor = .white - - var secondaryText: UIColor = { - guard #available(iOS 13, *) else { - return .gray - } - return .secondaryLabel - }() - - var on: UIColor { - return accent1 - } - - var indeterminate: UIColor { - return secondaryText - } - - var off: UIColor { - return secondaryText - } - -// var action = UIColor(red: 214.0 / 255.0, green: 156.0 / 255.0, blue: 104.0 / 255.0, alpha: 1.0) - var action: UIColor { - return accent1 - } - - var accessory: UIColor { - return accent1.withAlphaComponent(0.7) - } - - var destructive = UIColor(red: 0.8, green: 0.27, blue: 0.2, alpha: 1.0) - } - - static let current = Theme() - - var palette: Palette - - var modalPresentationStyle: UIModalPresentationStyle - - private init() { - palette = Palette() - modalPresentationStyle = .formSheet - } -} - -extension Theme { - func applyAppearance() { - if #available(iOS 15, *) { - let navBar = UINavigationBarAppearance() - navBar.configureWithOpaqueBackground() - navBar.backgroundColor = palette.primaryBackground - navBar.titleTextAttributes = [.foregroundColor: palette.primaryLightText] - navBar.buttonAppearance.normal.titleTextAttributes = navBar.titleTextAttributes - navBar.doneButtonAppearance.normal.titleTextAttributes = navBar.titleTextAttributes - let appearance = UINavigationBar.appearance() - appearance.standardAppearance = navBar - appearance.scrollEdgeAppearance = appearance.standardAppearance - } - - let bar = UINavigationBar.appearance() - bar.barTintColor = palette.primaryBackground - bar.tintColor = palette.primaryLightText - bar.titleTextAttributes = [.foregroundColor: palette.primaryLightText] - bar.largeTitleTextAttributes = bar.titleTextAttributes - - let pickerBar = UINavigationBar.appearance(whenContainedInInstancesOf: [UIDocumentBrowserViewController.self]) - pickerBar.barTintColor = nil - pickerBar.tintColor = nil - pickerBar.titleTextAttributes = nil - pickerBar.largeTitleTextAttributes = nil - - let toolbar = UIToolbar.appearance() - toolbar.barTintColor = palette.primaryBackground - toolbar.tintColor = palette.primaryLightText - - let toggle = UISwitch.appearance() - toggle.onTintColor = palette.accessory - - let activity = UIActivityIndicatorView.appearance() - activity.color = palette.accessory - } -} - -extension UIViewController { - func applyModalPresentation(_ theme: Theme) { - modalPresentationStyle = theme.modalPresentationStyle - } -} - -//extension UIView { -// func applyPrimaryBackground(_ theme: Theme) { -// backgroundColor = theme.palette.primaryBackground -// } -//} - -extension UILabel { - func apply(_ theme: Theme) { - font = .preferredFont(forTextStyle: .body) - textColor = theme.palette.primaryText - } - - func applyLight(_ theme: Theme) { - font = .preferredFont(forTextStyle: .body) - textColor = theme.palette.primaryLightText - } - - func applyAccent(_ theme: Theme) { - font = .preferredFont(forTextStyle: .body) - textColor = theme.palette.accent1 - } - - func applySecondarySize(_ theme: Theme) { - font = .preferredFont(forTextStyle: .callout) - } -} - -extension UITextField { - func applyAlert(_ theme: Theme) { - clearButtonMode = .always - keyboardType = .default - returnKeyType = .done - autocapitalizationType = .none - autocorrectionType = .no - } - - func applyHostTitle(_ theme: Theme) { - applyAlert(theme) - placeholder = L10n.Global.Host.TitleInput.placeholder - } - - func applyWiFiTitle(_ theme: Theme) { - applyAlert(theme) - placeholder = nil - } -} - -extension UIActivityIndicatorView { - func applyAccent(_ theme: Theme) { - color = theme.palette.accent1 - } -} - -extension UIBarButtonItem { - func apply(_ theme: Theme) { - tintColor = nil - } - - func applyAccent(_ theme: Theme) { - tintColor = theme.palette.accent1 - } -} - -extension UIContextualAction { - func applyNormal(_ theme: Theme) { - backgroundColor = theme.palette.primaryBackground - } -} - -// XXX: status bar is broken -extension MFMailComposeViewController { - func apply(_ theme: Theme) { - modalPresentationStyle = theme.modalPresentationStyle - - let bar = navigationBar - bar.barTintColor = theme.palette.primaryBackground - bar.tintColor = theme.palette.primaryLightText - bar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: theme.palette.primaryLightText] - bar.largeTitleTextAttributes = bar.titleTextAttributes - } -} - -extension Infrastructure.Metadata { - var logo: UIImage? { - let bundle = Bundle(for: AppDelegate.self) - guard let image = ImageAsset.Image(named: name, in: bundle, compatibleWith: nil) else { - return Asset.Providers.placeholder.image - } - return image - } -} - -extension PoolGroup { - var logo: UIImage? { - return ImageAsset(name: country.lowercased()).image - } -} diff --git a/Passepartout/App/iOS/Global/UITextView+Search.swift b/Passepartout/App/iOS/Global/UITextView+Search.swift deleted file mode 100644 index 4200e57e..00000000 --- a/Passepartout/App/iOS/Global/UITextView+Search.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// UITextView+Search.swift -// Passepartout -// -// Created by Davide De Rosa on 8/1/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit - -extension UITextView { - func firstVisibleIndex() -> Int { - let endOffset = contentOffset - let end = closestPosition(to: endOffset) ?? beginningOfDocument - return offset(from: beginningOfDocument, to: end) - } - - func lastVisibleIndex() -> Int { - let startOffset = CGPoint( - x: contentOffset.x + frame.size.width, - y: contentOffset.y + frame.size.height - ) - let start = closestPosition(to: startOffset) ?? endOfDocument - return offset(from: beginningOfDocument, to: start) - } - - func findPrevious(string: String) { - let last = text.index(text.startIndex, offsetBy: firstVisibleIndex()) - let context = text.startIndex..>> found: \(nsRange)") - scrollRangeToVisible(nsRange) -// scrollRangeToTop(nsRange) - } - - func findNext(string: String) { - let first = text.index(text.startIndex, offsetBy: lastVisibleIndex()) - let context = first..>> found: \(nsRange)") - scrollRangeToVisible(nsRange) -// scrollRangeToTop(nsRange) - } - - func scrollRangeToTop(_ nsRange: NSRange) { - let start = position(from: beginningOfDocument, offset: nsRange.location) ?? beginningOfDocument - let end = position(from: start, offset: nsRange.length) ?? endOfDocument - guard let range = textRange(from: start, to: end) else { - return - } - let target = convert(firstRect(for: range), to: textInputView) - setContentOffset(target.origin, animated: true) - } - -// func scrollRangeToTop(_ range: NSRange) { -// let glyphRange = layoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil) -// let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) -// let topTextInset = textContainerInset.top -// let target = CGPoint(x: 0, y: topTextInset + rect.origin.y) -// setContentOffset(target, animated: true) -// log.debug(">>> target: \(target)") -// } - - func scrollToBegin() { - scrollRangeToVisible(NSMakeRange(0, 1)) - } - - func scrollToEnd() { - scrollRangeToVisible(NSMakeRange(text.count - 1, 1)) - } -} diff --git a/Passepartout/App/iOS/Intents/IntentDispatcher.swift b/Passepartout/App/iOS/Intents/IntentDispatcher.swift deleted file mode 100644 index d0305e2e..00000000 --- a/Passepartout/App/iOS/Intents/IntentDispatcher.swift +++ /dev/null @@ -1,320 +0,0 @@ -// -// IntentDispatcher.swift -// Passepartout -// -// Created by Davide De Rosa on 3/8/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import Intents -import SwiftyBeaver -import PassepartoutCore - -private let log = SwiftyBeaver.self - -@available(iOS 12, *) -public class IntentDispatcher { - private class Groups { - static let vpn = "VPN" - - static let trust = "Trust" - } - - public static let didUpdateService = Notification.Name("IntentDispatcherDidUpdateService") - - // MARK: Intents - - public static func intentConnect(profile: ConnectionProfile, title: String?) -> ConnectVPNIntent { - let intent = ConnectVPNIntent() - intent.context = profile.context.rawValue - intent.profileId = profile.id - intent.profileTitle = title ?? profile.id - return intent - } - - public static func intentMoveTo(profile: ProviderConnectionProfile, pool: Pool) -> MoveToLocationIntent { - let intent = MoveToLocationIntent() - intent.providerId = profile.id - intent.poolId = pool.id - intent.poolName = pool.localizedId - return intent - } - - public static func intentEnable() -> EnableVPNIntent { - return EnableVPNIntent() - } - - public static func intentDisable() -> DisableVPNIntent { - return DisableVPNIntent() - } - - public static func intentTrustWiFi() -> TrustCurrentNetworkIntent { - return TrustCurrentNetworkIntent() - } - - public static func intentUntrustWiFi() -> UntrustCurrentNetworkIntent { - return UntrustCurrentNetworkIntent() - } - - public static func intentTrustCellular() -> TrustCellularNetworkIntent { - return TrustCellularNetworkIntent() - } - - public static func intentUntrustCellular() -> UntrustCellularNetworkIntent { - return UntrustCellularNetworkIntent() - } - - // MARK: Donations - - public static func donateConnection(with profile: ConnectionProfile, title: String?) { - let genericIntent: INIntent - if let provider = profile as? ProviderConnectionProfile, let pool = provider.pool { - genericIntent = intentMoveTo(profile: provider, pool: pool) - } else { - genericIntent = intentConnect(profile: profile, title: title) - } - - let interaction = INInteraction(intent: genericIntent, response: nil) - interaction.groupIdentifier = ProfileKey(profile).rawValue - interaction.donateAndLog() - } - - public static func donateEnableVPN() { - let interaction = INInteraction(intent: intentEnable(), response: nil) - interaction.groupIdentifier = Groups.vpn - interaction.donateAndLog() - } - - public static func donateDisableVPN() { - let interaction = INInteraction(intent: intentDisable(), response: nil) - interaction.groupIdentifier = Groups.vpn - interaction.donateAndLog() - } - - public static func donateTrustCurrentNetwork() { - let interaction = INInteraction(intent: intentTrustWiFi(), response: nil) - interaction.groupIdentifier = Groups.trust - interaction.donateAndLog() - } - - public static func donateUntrustCurrentNetwork() { - let interaction = INInteraction(intent: intentUntrustWiFi(), response: nil) - interaction.groupIdentifier = Groups.trust - interaction.donateAndLog() - } - - public static func donateTrustCellularNetwork() { - let interaction = INInteraction(intent: intentTrustCellular(), response: nil) - interaction.groupIdentifier = Groups.trust - interaction.donateAndLog() - } - - public static func donateUntrustCellularNetwork() { - let interaction = INInteraction(intent: intentUntrustCellular(), response: nil) - interaction.groupIdentifier = Groups.trust - interaction.donateAndLog() - } - - // - - public static func handleInteraction(_ interaction: INInteraction, completionHandler: ((Error?) -> Void)?) { - handleIntent(interaction.intent, interaction: interaction, completionHandler: completionHandler) - } - - public static func handleIntent(_ intent: INIntent, interaction: INInteraction?, completionHandler: ((Error?) -> Void)?) { - if let custom = intent as? ConnectVPNIntent { - handleConnectVPN(custom, interaction: interaction, completionHandler: completionHandler) - } else if let custom = intent as? EnableVPNIntent { - handleEnableVPN(custom, interaction: interaction, completionHandler: completionHandler) - } else if let custom = intent as? DisableVPNIntent { - handleDisableVPN(custom, interaction: interaction, completionHandler: completionHandler) - } else if let custom = intent as? MoveToLocationIntent { - handleMoveToLocation(custom, interaction: interaction, completionHandler: completionHandler) - } else if let _ = intent as? TrustCurrentNetworkIntent { - handleCurrentNetwork(trust: true, interaction: interaction, completionHandler: completionHandler) - } else if let _ = intent as? UntrustCurrentNetworkIntent { - handleCurrentNetwork(trust: false, interaction: interaction, completionHandler: completionHandler) - } else if let _ = intent as? TrustCellularNetworkIntent { - handleCellularNetwork(trust: true, interaction: interaction, completionHandler: completionHandler) - } else if let _ = intent as? UntrustCellularNetworkIntent { - handleCellularNetwork(trust: false, interaction: interaction, completionHandler: completionHandler) - } - } - - public static func handleConnectVPN(_ intent: ConnectVPNIntent, interaction: INInteraction?, completionHandler: ((Error?) -> Void)?) { - guard let contextValue = intent.context, let context = Context(rawValue: contextValue), let id = intent.profileId else { - if let interactionIdentifier = interaction?.identifier { - INInteraction.delete(with: [interactionIdentifier], completion: nil) - } - // FIXME: error = missing data, programming error - completionHandler?(nil) - return - } - let profileKey = ProfileKey(context, id) - log.info("Connect to profile: \(profileKey)") - - let service = TransientStore.shared.service - let vpn = VPN.shared - guard !(service.isActiveProfile(profileKey) && (vpn.status == .connected)) else { - log.info("Profile is already active and connected") - completionHandler?(nil) - return - } - - guard let profile = service.profile(withContext: context, id: id) else { - // FIXME: error = no profile - completionHandler?(nil) - return - } - service.activateProfile(profile) - refreshVPN(service: service, doReconnect: true, completionHandler: completionHandler) - } - - public static func handleMoveToLocation(_ intent: MoveToLocationIntent, interaction: INInteraction?, completionHandler: ((Error?) -> Void)?) { - guard let providerId = intent.providerId, let poolId = intent.poolId else { - // FIXME: error = no provider/pool - completionHandler?(nil) - return - } - let service = TransientStore.shared.service - guard let providerProfile = service.profile(withContext: .provider, id: providerId) as? ProviderConnectionProfile else { - // FIXME: error = no provider - completionHandler?(nil) - return - } - log.info("Connect to provider location: \(providerId) @ [\(poolId)]") - - let vpn = VPN.shared - guard !(service.isActiveProfile(providerProfile) && (providerProfile.poolId == poolId) && (vpn.status == .connected)) else { - log.info("Profile is already active and connected to \(poolId)") - completionHandler?(nil) - return - } - - providerProfile.poolId = poolId - service.activateProfile(providerProfile) - refreshVPN(service: service, doReconnect: true, completionHandler: completionHandler) - } - - public static func handleEnableVPN(_ intent: EnableVPNIntent, interaction: INInteraction?, completionHandler: ((Error?) -> Void)?) { - let service = TransientStore.shared.service - log.info("Enabling VPN...") - refreshVPN(service: service, doReconnect: true, completionHandler: completionHandler) - } - - public static func handleDisableVPN(_ intent: DisableVPNIntent, interaction: INInteraction?, completionHandler: ((Error?) -> Void)?) { - log.info("Disabling VPN...") - - let vpn = VPN.shared - vpn.prepare { - vpn.disconnect { (error) in - notifyServiceUpdate() - completionHandler?(error) - } - } - } - - public static func handleCurrentNetwork(trust: Bool, interaction: INInteraction?, completionHandler: ((Error?) -> Void)?) { - guard let currentWifi = Utils.currentWifiNetworkName() else { - // FIXME: error = not connected to wifi - completionHandler?(nil) - return - } - let service = TransientStore.shared.service - service.activeProfile?.trustedNetworks.includedWiFis[currentWifi] = trust - TransientStore.shared.serialize(withProfiles: false) - - log.info("\(trust ? "Trusted" : "Untrusted") Wi-Fi: \(currentWifi)") - refreshVPN(service: service, doReconnect: false, completionHandler: completionHandler) - } - - public static func handleCellularNetwork(trust: Bool, interaction: INInteraction?, completionHandler: ((Error?) -> Void)?) { - #if os(iOS) - guard Utils.hasCellularData() else { - // FIXME: error = has no mobile data - completionHandler?(nil) - return - } - let service = TransientStore.shared.service - service.activeProfile?.trustedNetworks.includesMobile = trust - TransientStore.shared.serialize(withProfiles: false) - - log.info("\(trust ? "Trusted" : "Untrusted") cellular network") - refreshVPN(service: service, doReconnect: false, completionHandler: completionHandler) - #endif - } - - private static func refreshVPN(service: ConnectionService, doReconnect: Bool, completionHandler: ((Error?) -> Void)?) { - let configuration: VPNConfiguration - do { - configuration = try service.vpnConfiguration() - } catch let e { - log.error("Unable to build VPN configuration: \(e)") - notifyServiceUpdate() - completionHandler?(e) - return - } - - let vpn = VPN.shared - if doReconnect { - log.info("Reconnecting VPN: \(configuration)") - vpn.reconnect(configuration: configuration, delay: nil) { (error) in - notifyServiceUpdate() - completionHandler?(error) - } - } else { - log.info("Reinstalling VPN: \(configuration)") - vpn.install(configuration: configuration) { (error) in - notifyServiceUpdate() - completionHandler?(error) - } - } - } - - // - - public static func forgetProfile(withKey profileKey: ProfileKey) { - INInteraction.delete(with: profileKey.rawValue) { (error) in - if let error = error { - log.error("Unable to forget interactions: \(error)") - return - } - log.debug("Removed profile \(profileKey) interactions") - } - } - - // - - private static func notifyServiceUpdate() { - NotificationCenter.default.post(name: IntentDispatcher.didUpdateService, object: nil) - } -} - -private extension INInteraction { - func donateAndLog() { - donate { (error) in - if let error = error { - log.error("Unable to donate interaction: \(error)") - } - log.debug("Donated \(self.intent)") - } - } -} diff --git a/Passepartout/App/iOS/Providers.xcassets/csv.imageset/Contents.json b/Passepartout/App/iOS/Providers.xcassets/csv.imageset/Contents.json deleted file mode 100644 index 68bb32b9..00000000 --- a/Passepartout/App/iOS/Providers.xcassets/csv.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "csv@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "csv@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Passepartout/App/iOS/Providers.xcassets/csv.imageset/csv@2x.png b/Passepartout/App/iOS/Providers.xcassets/csv.imageset/csv@2x.png deleted file mode 100644 index 1c79287bd13300eb97ba9a1e90c6cf8cca02c16e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3435 zcmV-x4V3bUP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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. +// + +import UIKit +import SwiftUI + +struct ActivityView: UIViewControllerRepresentable { + let activityItems: [Any] + + let applicationActivities: [UIActivity]? = nil + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIActivityViewController { + return UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities) + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext) { + } +} diff --git a/Passepartout/App/iOS/Reusable/IntentAddView.swift b/Passepartout/App/iOS/Reusable/IntentAddView.swift new file mode 100644 index 00000000..d76d0ec8 --- /dev/null +++ b/Passepartout/App/iOS/Reusable/IntentAddView.swift @@ -0,0 +1,46 @@ +// +// IntentAddView.swift +// Passepartout +// +// Created by Davide De Rosa on 3/13/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import Intents +#if canImport(IntentsUI) +import IntentsUI +#endif + +@available(iOS 12, macOS 12, *) +struct IntentAddView: UIViewControllerRepresentable { + let shortcut: INShortcut + + let delegate: INUIAddVoiceShortcutViewControllerDelegate? + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> INUIAddVoiceShortcutViewController { + let vc = INUIAddVoiceShortcutViewController(shortcut: shortcut) + vc.delegate = delegate + return vc + } + + func updateUIViewController(_ uiViewController: INUIAddVoiceShortcutViewController, context: UIViewControllerRepresentableContext) { + } +} diff --git a/Passepartout/App/iOS/Reusable/IntentEditView.swift b/Passepartout/App/iOS/Reusable/IntentEditView.swift new file mode 100644 index 00000000..9900d385 --- /dev/null +++ b/Passepartout/App/iOS/Reusable/IntentEditView.swift @@ -0,0 +1,46 @@ +// +// IntentEditView.swift +// Passepartout +// +// Created by Davide De Rosa on 3/13/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import Intents +#if canImport(IntentsUI) +import IntentsUI +#endif + +@available(iOS 12, macOS 12, *) +struct IntentEditView: UIViewControllerRepresentable { + let shortcut: Shortcut + + let delegate: INUIEditVoiceShortcutViewControllerDelegate? + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> INUIEditVoiceShortcutViewController { + let vc = INUIEditVoiceShortcutViewController(voiceShortcut: shortcut.native) + vc.delegate = delegate + return vc + } + + func updateUIViewController(_ uiViewController: INUIEditVoiceShortcutViewController, context: UIViewControllerRepresentableContext) { + } +} diff --git a/Passepartout/App/iOS/Reusable/MailComposerView.swift b/Passepartout/App/iOS/Reusable/MailComposerView.swift new file mode 100644 index 00000000..06f207df --- /dev/null +++ b/Passepartout/App/iOS/Reusable/MailComposerView.swift @@ -0,0 +1,79 @@ +// +// MailComposerView.swift +// Passepartout +// +// Created by Davide De Rosa on 3/23/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +#if canImport(MessageUI) +import SwiftUI +import Combine +import MessageUI + +struct MailComposerView: UIViewControllerRepresentable { + class Coordinator: NSObject, MFMailComposeViewControllerDelegate { + @Binding private var isPresented: Bool + + init(_ view: MailComposerView) { + _isPresented = view._isPresented + } + + public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + isPresented = false + } + } + + typealias Attachment = (data: Data, mimeType: String, fileName: String) + + static func canSendMail() -> Bool { + MFMailComposeViewController.canSendMail() + } + + @Binding var isPresented: Bool + + let toRecipients: [String] + + let subject: String + + let messageBody: String + + var attachments: [Attachment]? + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> MFMailComposeViewController { + let vc = MFMailComposeViewController() + vc.setToRecipients(toRecipients) + vc.setSubject(subject) + vc.setMessageBody(messageBody, isHTML: false) + attachments?.forEach { + vc.addAttachmentData($0.data, mimeType: $0.mimeType, fileName: $0.fileName) + } + vc.mailComposeDelegate = context.coordinator + return vc + } + + func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: UIViewControllerRepresentableContext) { + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } +} +#endif diff --git a/Passepartout/App/iOS/Scenes/About/AboutViewController.swift b/Passepartout/App/iOS/Scenes/About/AboutViewController.swift deleted file mode 100644 index 128debfa..00000000 --- a/Passepartout/App/iOS/Scenes/About/AboutViewController.swift +++ /dev/null @@ -1,236 +0,0 @@ -// -// AboutViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 9/28/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import PassepartoutCore -import Convenience -import ConvenienceUI - -class AboutViewController: UITableViewController, StrongTableHost { - - // MARK: StrongTableHost - - let model: StrongTableModel = { - let model: StrongTableModel = StrongTableModel() - model.add(.info) - model.add(.github) - model.add(.web) - model.add(.share) - model.setHeader("", forSection: .info) - model.setHeader("GitHub", forSection: .github) - model.setHeader(L10n.About.Sections.Web.header, forSection: .web) - model.setHeader(L10n.About.Sections.Share.header, forSection: .share) - model.set([.version, .credits], forSection: .info) - model.set([.readme, .changelog], forSection: .github) - model.set([.website, .faq, .disclaimer, .privacyPolicy], forSection: .web) - model.set([.shareTwitter, .shareGeneric, .visitAlternativeTo], forSection: .share) - return model - }() - - func reloadModel() { - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.About.title - } - - // MARK: Actions - - private func showVersion() { - let vc = VersionViewController() - vc.appIcon = Asset.Assets.logo.image - vc.extraText = L10n.Version.Labels.intro - vc.backgroundColor = Theme.current.palette.primaryBackground - vc.textColor = Theme.current.palette.primaryLightText - navigationController?.pushViewController(vc, animated: true) - } - - private func showCredits() { - let vc = CreditsViewController() - vc.title = L10n.Credits.title - vc.licensesHeader = L10n.Credits.Sections.Licenses.header - vc.noticesHeader = L10n.Credits.Sections.Notices.header - vc.translationsHeader = L10n.Credits.Sections.Translations.header - vc.software = AppConstants.Credits.software - vc.translators = AppConstants.Translations.translators - vc.accentColor = Theme.current.palette.accent1 - navigationController?.pushViewController(vc, animated: true) - } - - private func inviteFriend(sender: UITableViewCell?) { - let message = "\(L10n.Share.message) \(AppConstants.URLs.website)" - let vc = UIActivityViewController(activityItems: [message], applicationActivities: nil) - vc.popoverPresentationController?.sourceView = sender - present(vc, animated: true, completion: nil) - } - - @IBAction private func close() { - dismiss(animated: true, completion: nil) - } -} - -// MARK: - - -extension AboutViewController { - enum SectionType: Int { - case info - - case github - - case web - - case share - } - - enum RowType: Int { - case version - - case credits - - case readme - - case changelog - - case website - - case faq - - case disclaimer - - case privacyPolicy - - case shareTwitter - - case shareGeneric - - case visitAlternativeTo - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return model.footer(forSection: section) - } - - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return model.headerHeight(for: section) - } - - override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - return model.footerHeight(for: section) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - switch model.row(at: indexPath) { - case .version: - cell.leftText = L10n.Version.title - cell.rightText = ApplicationInfo.appVersion - - case .credits: - cell.leftText = L10n.About.Cells.Credits.caption - - case .readme: - cell.leftText = "README" - - case .changelog: - cell.leftText = "CHANGELOG" - - case .website: - cell.leftText = L10n.About.Cells.Website.caption - - case .faq: - cell.leftText = L10n.About.Cells.Faq.caption - - case .disclaimer: - cell.leftText = L10n.About.Cells.Disclaimer.caption - - case .privacyPolicy: - cell.leftText = L10n.About.Cells.PrivacyPolicy.caption - - case .shareTwitter: - cell.leftText = L10n.About.Cells.ShareTwitter.caption - - case .shareGeneric: - cell.leftText = L10n.About.Cells.ShareGeneric.caption - - case .visitAlternativeTo: - cell.leftText = "AlternativeTo" - } - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - switch model.row(at: indexPath) { - case .version: - showVersion() - - case .credits: - showCredits() - - case .readme: - visitURL(AppConstants.URLs.readme) - - case .changelog: - visitURL(AppConstants.URLs.iOS.changelog) - - case .website: - visitURL(AppConstants.URLs.website) - - case .faq: - visitURL(AppConstants.URLs.faq) - - case .disclaimer: - visitURL(AppConstants.URLs.disclaimer) - - case .privacyPolicy: - visitURL(AppConstants.URLs.privacyPolicy) - - case .shareTwitter: - visitURL(AppConstants.URLs.twitterIntent(withMessage: L10n.Share.message)) - - case .shareGeneric: - inviteFriend(sender: tableView.cellForRow(at: indexPath)) - - case .visitAlternativeTo: - visitURL(AppConstants.URLs.alternativeTo) - } - tableView.deselectRow(at: indexPath, animated: true) - } -} diff --git a/Passepartout/App/iOS/Scenes/AccountViewController.swift b/Passepartout/App/iOS/Scenes/AccountViewController.swift deleted file mode 100644 index 80660f64..00000000 --- a/Passepartout/App/iOS/Scenes/AccountViewController.swift +++ /dev/null @@ -1,288 +0,0 @@ -// -// AccountViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/12/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import PassepartoutCore -import ConvenienceUI - -protocol AccountViewControllerDelegate: AnyObject { - func accountController(_: AccountViewController, didEnterCredentials credentials: Credentials) - - func accountControllerDidComplete(_: AccountViewController) -} - -class AccountViewController: UIViewController, StrongTableHost { - @IBOutlet private weak var tableView: UITableView? - - private weak var cellUsername: FieldTableViewCell? - - private weak var cellPassword: FieldTableViewCell? - - var currentCredentials: Credentials? - - var usernamePlaceholder: String? - - var infrastructureName: InfrastructureName? { - didSet { - reloadModel() - tableView?.reloadData() - } - } - - var credentials: Credentials { - let username = cellUsername?.field.text ?? "" - let password = cellPassword?.field.text ?? "" - return Credentials(username, password).trimmed() - } - - weak var delegate: AccountViewControllerDelegate? - - // MARK: StrongTableHost - - var model: StrongTableModel = StrongTableModel() - - func reloadModel() { - model.clear() - - model.add(.credentials) - model.setHeader(L10n.Account.Sections.Credentials.header, forSection: .credentials) - model.set([.username, .password], forSection: .credentials) - - if let _ = infrastructureName { - if let guidanceString = guidanceString { - if let _ = guidanceURL { - model.add(.guidance) - model.setFooter(guidanceString, forSection: .guidance) - model.set([.openGuide], forSection: .guidance) - } else { - model.setFooter(guidanceString, forSection: .credentials) - } -// model.setHeader("", forSection: .registration) - } else if let _ = guidanceURL { - model.add(.guidance) - model.set([.openGuide], forSection: .guidance) -// model.setHeader("", forSection: .registration) - } -// if let _ = referralURL { -// model.add(.registration) -// model.setFooter(L10n.Account.Sections.Registration.footer(name.rawValue), forSection: .registration) -// model.set([.signUp], forSection: .registration) -// } - } - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Account.title - cellUsername?.field.text = currentCredentials?.username - cellPassword?.field.text = currentCredentials?.password - - reloadModel() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - cellUsername?.field.becomeFirstResponder() - } - - // MARK: Actions - - private func commit() { - let newCredentials = credentials -// guard !credentials.isEmpty else { -// return -// } - currentCredentials = newCredentials - delegate?.accountController(self, didEnterCredentials: newCredentials) - } - - private func openGuidanceURL() { - guard let url = guidanceURL else { - return - } - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } - - private func openReferralURL() { - guard let url = referralURL else { - return - } - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } - - @IBAction private func done() { - view.endEditing(true) - delegate?.accountControllerDidComplete(self) - } -} - -// MARK: - - -extension AccountViewController: UITableViewDataSource, UITableViewDelegate, FieldTableViewCellDelegate { - enum SectionType: Int { - case credentials - - case guidance - - case registration - } - - enum RowType: Int { - case username - - case password - - case openGuide - - case signUp - } - - private static let footerButtonTag = 1000 - - func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return model.footer(forSection: section) - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return model.headerHeight(for: section) - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - switch model.row(at: indexPath) { - case .username: - let cell = Cells.field.dequeue(from: tableView, for: indexPath) - cellUsername = cell - cell.caption = L10n.Account.Cells.Username.caption - cell.field.placeholder = usernamePlaceholder ?? L10n.Account.Cells.Username.placeholder - cell.field.clearButtonMode = .always - cell.field.isSecureTextEntry = false - cell.field.text = currentCredentials?.username - cell.field.keyboardType = .emailAddress - cell.field.returnKeyType = .next - cell.field.textContentType = .username - cell.captionWidth = 120.0 - cell.delegate = self - return cell - - case .password: - let cell = Cells.field.dequeue(from: tableView, for: indexPath) - cellPassword = cell - cell.caption = L10n.Account.Cells.Password.caption - cell.field.placeholder = L10n.Account.Cells.Password.placeholder - cell.field.clearButtonMode = .always - cell.field.isSecureTextEntry = true - cell.field.text = currentCredentials?.password - cell.field.returnKeyType = .done - cell.field.textContentType = .password - cell.captionWidth = 120.0 - cell.delegate = self - return cell - - case .openGuide: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Account.Cells.OpenGuide.caption - cell.applyAction(.current) - return cell - - case .signUp: - guard let name = infrastructureName else { - fatalError("Sign-up shown when not a provider profile") - } - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Account.Cells.Signup.caption(name) - cell.applyAction(.current) - return cell - } - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - switch model.row(at: indexPath) { - case .openGuide: - openGuidanceURL() - - case .signUp: - openReferralURL() - - default: - break - } - tableView.deselectRow(at: indexPath, animated: true) - } - - func fieldCellDidEdit(_: FieldTableViewCell) { - commit() - } - - func fieldCellDidEnter(_ cell: FieldTableViewCell) { - switch cell { - case cellUsername: - cellPassword?.field.becomeFirstResponder() - - case cellPassword: - cellPassword?.field.resignFirstResponder() - done() - - default: - break - } - } -} - -extension AccountViewController { - private var guidanceString: String? { - return metadata?.guidanceString - } - - private var guidanceURL: URL? { - return metadata?.guidanceURL - } - - private var referralURL: URL? { - return metadata?.referralURL - } - - private var metadata: Infrastructure.Metadata? { - guard let name = infrastructureName else { - return nil - } - return InfrastructureFactory.shared.metadata(forName: name) - } -} diff --git a/Passepartout/App/iOS/Scenes/ConfigurationViewController.swift b/Passepartout/App/iOS/Scenes/ConfigurationViewController.swift deleted file mode 100644 index 84887998..00000000 --- a/Passepartout/App/iOS/Scenes/ConfigurationViewController.swift +++ /dev/null @@ -1,449 +0,0 @@ -// -// ConfigurationViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 9/2/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import SwiftyBeaver -import PassepartoutCore -import ConvenienceUI - -private let log = SwiftyBeaver.self - -class ConfigurationViewController: UIViewController, StrongTableHost { - @IBOutlet private weak var tableView: UITableView! - - private lazy var itemRefresh = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh)) - - var initialConfiguration: OpenVPN.Configuration! - - private lazy var configuration: OpenVPN.ConfigurationBuilder = initialConfiguration.builder() - - var originalConfigurationURL: URL? - - private var isEditable: Bool { - return originalConfigurationURL != nil - } - - var isServerPushed = false - - weak var delegate: ConfigurationModificationDelegate? - - // MARK: StrongTableHost - - let model: StrongTableModel = StrongTableModel() - - func reloadModel() { - model.clear() - - // sections - if isEditable { - model.add(.reset) - model.setHeader("", forSection: .reset) - } - - // headers - model.setHeader(L10n.Configuration.Sections.Communication.header, forSection: .communication) - model.setHeader(L10n.Configuration.Sections.Tls.header, forSection: .tls) - model.setHeader(L10n.Configuration.Sections.Compression.header, forSection: .compression) - model.setHeader(L10n.Configuration.Sections.Other.header, forSection: .other) - - // footers - if isEditable { - model.setFooter(L10n.Configuration.Sections.Reset.footer, forSection: .reset) - } - - // rows - if isServerPushed { - var rows: [RowType] - - rows = [] - if let _ = configuration.cipher { - rows.append(.cipher) - } - if let _ = configuration.digest { - rows.append(.digest) - } - if !rows.isEmpty { - model.add(.communication) - model.set(rows, forSection: .communication) - } - - rows = [] - if let _ = configuration.compressionFraming { - rows.append(.compressionFraming) - } - if let _ = configuration.compressionAlgorithm { - rows.append(.compressionAlgorithm) - } - if !rows.isEmpty { - model.add(.compression) - model.set(rows, forSection: .compression) - } - - rows = [] - if let _ = configuration.keepAliveInterval { - rows.append(.keepAlive) - } - if let _ = configuration.renegotiatesAfter { - rows.append(.renegSeconds) - } - if let _ = configuration.randomizeEndpoint { - rows.append(.randomEndpoint) - } - if !rows.isEmpty { - model.add(.other) - model.set(rows, forSection: .other) - } - } else { - model.add(.communication) - model.add(.compression) - model.add(.tls) - model.add(.other) - model.set([.cipher, .digest, .xorMask], forSection: .communication) - model.set([.compressionFraming, .compressionAlgorithm], forSection: .compression) - model.set([.keepAlive, .renegSeconds, .randomEndpoint], forSection: .other) - } - if isEditable { - model.set([.resetOriginal], forSection: .reset) - } - model.set([.client, .tlsWrapping, .eku], forSection: .tls) - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - guard let _ = initialConfiguration else { - fatalError("Initial configuration not set") - } - reloadModel() - - guard isEditable else { - tableView.allowsSelection = false - return - } - itemRefresh.isEnabled = false - navigationItem.rightBarButtonItem = itemRefresh - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - if let ip = tableView.indexPathForSelectedRow { - tableView.deselectRow(at: ip, animated: true) - } - } - - // MARK: Actions - - private func resetOriginalConfiguration(passphrase: String? = nil) { - guard let originalURL = originalConfigurationURL else { - log.warning("Resetting with no original configuration set? Bad table model?") - return - } - let parsingResult: OpenVPN.ConfigurationParser.Result - do { - parsingResult = try OpenVPN.ConfigurationParser.parsed(fromURL: originalURL, passphrase: passphrase) - } catch let e as ConfigurationError { - switch e { - case .encryptionPassphrase: - log.warning("Configuration is encrypted, ask for passphrase") - askForResetConfigurationWithPassphrase(originalURL) - - default: - log.error("Could not parse original configuration: \(e)") - } - return - } catch let e { - log.error("Could not parse original configuration: \(e)") - return - } - configuration = parsingResult.configuration.builder() - itemRefresh.isEnabled = !configuration.canCommunicate(with: initialConfiguration) - initialConfiguration = parsingResult.configuration - tableView.reloadData() - - delegate?.configuration(didUpdate: initialConfiguration) - } - - private func askForResetConfigurationWithPassphrase(_ originalURL: URL) { - let alert = UIAlertController.asAlert(nil, L10n.ParsedFile.Alerts.EncryptionPassphrase.message) - alert.addTextField { (field) in - field.isSecureTextEntry = true - } - alert.addPreferredAction(L10n.Global.ok) { - guard let passphrase = alert.textFields?.first?.text else { - return - } - self.resetOriginalConfiguration(passphrase: passphrase) - } - alert.addCancelAction(L10n.Global.cancel) { - } - present(alert, animated: true, completion: nil) - } - - @IBAction private func refresh() { - guard isEditable else { - return - } - initialConfiguration = configuration.build() - itemRefresh.isEnabled = false - - delegate?.configurationShouldReinstall() - } -} - -// MARK: - - -extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegate { - enum SectionType: Int { - case communication - - case reset - - case tls - - case compression - - case other - } - - enum RowType: Int { - case cipher - - case digest - - case resetOriginal - - case client - - case tlsWrapping - - case eku - - case compressionFraming - - case compressionAlgorithm - - case xorMask - - case keepAlive - - case renegSeconds - - case randomEndpoint - } - - func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return model.footer(forSection: section) - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return model.headerHeight(for: section) - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let row = model.row(at: indexPath) - let V = L10n.Configuration.Cells.self - - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - if !isEditable { - cell.accessoryType = .none - } - cell.isTappable = isEditable - switch row { - case .resetOriginal: - cell.leftText = V.ResetOriginal.caption - cell.applyAction(.current) - - case .cipher: - cell.leftText = V.Cipher.caption - cell.rightText = configuration.fallbackCipher.uiDescription - - case .digest: - cell.leftText = V.Digest.caption - cell.rightText = configuration.fallbackDigest.uiDescription - - case .compressionFraming: - cell.leftText = V.CompressionFraming.caption - cell.rightText = configuration.fallbackCompressionFraming.uiDescription - - case .compressionAlgorithm: - cell.leftText = V.CompressionAlgorithm.caption - cell.rightText = configuration.fallbackCompressionAlgorithm.uiDescription - cell.isTappable = (configuration.compressionFraming != .disabled) - - case .client: - cell.leftText = V.Client.caption - cell.rightText = configuration.uiDescriptionForClientCertificate - cell.accessoryType = .none - cell.isTappable = false - - case .tlsWrapping: - cell.leftText = V.TlsWrapping.caption - cell.rightText = configuration.uiDescriptionForTLSWrap - cell.accessoryType = .none - cell.isTappable = false - - case .eku: - cell.leftText = V.Eku.caption - cell.rightText = configuration.uiDescriptionForEKU - cell.accessoryType = .none - cell.isTappable = false - - case .keepAlive: - cell.leftText = V.KeepAlive.caption - cell.rightText = configuration.uiDescriptionForKeepAlive - cell.accessoryType = .none - cell.isTappable = false - - case .renegSeconds: - cell.leftText = V.RenegotiationSeconds.caption - cell.rightText = configuration.uiDescriptionForRenegotiatesAfter - cell.accessoryType = .none - cell.isTappable = false - - case .randomEndpoint: - cell.leftText = V.RandomEndpoint.caption - cell.rightText = configuration.uiDescriptionForRandomizeEndpoint - cell.accessoryType = .none - cell.isTappable = false - - case .xorMask: - cell.leftText = "XOR" - cell.rightText = configuration.uiDescriptionForXOR - cell.accessoryType = .none - cell.isTappable = false - } - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard isEditable else { - fatalError("Table should not allow selection when isEditable is false") - } - - let settingCell = tableView.cellForRow(at: indexPath) as? SettingTableViewCell - - switch model.row(at: indexPath) { - case .cipher: - var options: [OpenVPN.Cipher] = configuration.dataCiphers ?? [] - if !options.isEmpty { - if let cipher = configuration.cipher, !options.contains(cipher) { - options.append(cipher) - } - } else { - options.append(contentsOf: OpenVPN.Cipher.available) - } - - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = settingCell?.leftText - vc.options = options - vc.selectedOption = configuration.cipher - vc.descriptionBlock = { $0.uiDescription } - vc.selectionBlock = { [weak self] in - self?.configuration.cipher = $0 - self?.popAndCheckRefresh() - } - navigationController?.pushViewController(vc, animated: true) - - case .digest: - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = settingCell?.leftText - vc.options = OpenVPN.Digest.available - vc.selectedOption = configuration.digest - vc.descriptionBlock = { $0.uiDescription } - vc.selectionBlock = { [weak self] in - self?.configuration.digest = $0 - self?.popAndCheckRefresh() - } - navigationController?.pushViewController(vc, animated: true) - - case .compressionFraming: - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = settingCell?.leftText - vc.options = OpenVPN.CompressionFraming.available - vc.selectedOption = configuration.compressionFraming ?? .disabled - vc.descriptionBlock = { $0.uiDescription } - vc.selectionBlock = { [weak self] in - self?.configuration.compressionFraming = $0 - if $0 == .disabled { - self?.configuration.compressionAlgorithm = .disabled - } - self?.popAndCheckRefresh() - } - navigationController?.pushViewController(vc, animated: true) - - case .compressionAlgorithm: - guard configuration.compressionFraming != .disabled else { - return - } - - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = settingCell?.leftText - vc.options = OpenVPN.CompressionAlgorithm.available - vc.selectedOption = configuration.compressionAlgorithm ?? .disabled - vc.descriptionBlock = { $0.uiDescription } - vc.selectionBlock = { [weak self] in - self?.configuration.compressionAlgorithm = $0 - self?.popAndCheckRefresh() - } - navigationController?.pushViewController(vc, animated: true) - - case .resetOriginal: - tableView.deselectRow(at: indexPath, animated: true) - resetOriginalConfiguration() - - default: - break - } - } - - // MARK: Helpers - - private func popAndCheckRefresh() { - itemRefresh.isEnabled = !configuration.canCommunicate(with: initialConfiguration) - tableView.reloadData() - navigationController?.popViewController(animated: true) - - delegate?.configuration(didUpdate: configuration.build()) - } -} diff --git a/Passepartout/App/iOS/Scenes/DebugLogViewController.swift b/Passepartout/App/iOS/Scenes/DebugLogViewController.swift deleted file mode 100644 index a455c625..00000000 --- a/Passepartout/App/iOS/Scenes/DebugLogViewController.swift +++ /dev/null @@ -1,147 +0,0 @@ -// -// DebugLogViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/12/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import SwiftyBeaver -import PassepartoutCore - -private let log = SwiftyBeaver.self - -class DebugLogViewController: UIViewController { - @IBOutlet private weak var textLog: UITextView? - - private let vpn = VPN.shared - - deinit { - NotificationCenter.default.removeObserver(self) - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Service.Cells.DebugLog.caption - textLog?.contentInsetAdjustmentBehavior = .never - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(toggleBars)) - textLog?.addGestureRecognizer(tapGestureRecognizer) - - NotificationCenter.default.addObserver(self, selector: #selector(vpnDidPrepare), name: VPN.didPrepare, object: nil) - if vpn.isPrepared { - startRefreshingLog() - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - navigationController?.isToolbarHidden = false - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - navigationController?.isToolbarHidden = true - } - - // MARK: Actions - - @objc private func toggleBars() { - let nav = navigationController - let isHidden = nav?.isToolbarHidden ?? true -// nav?.setNavigationBarHidden(!isHidden, animated: true) - nav?.setToolbarHidden(!isHidden, animated: true) - } - - @IBAction private func share(_ sender: Any?) { - guard let raw = textLog?.text, !raw.isEmpty else { - let alert = UIAlertController.asAlert(title, L10n.DebugLog.Alerts.EmptyLog.message) - alert.addCancelAction(L10n.Global.ok) - present(alert, animated: true, completion: nil) - return - } - let data = DebugLog(raw: raw).decoratedData() - - let path = NSTemporaryDirectory().appending(AppConstants.IssueReporter.Filenames.debugLog) - let url = URL(fileURLWithPath: path) - do { - try data.write(to: url) - } catch let e { - log.error("Failed saving temporary debug log file: \(e)") - return - } - let vc = UIActivityViewController(activityItems: [url], applicationActivities: nil) - vc.popoverPresentationController?.barButtonItem = sender as? UIBarButtonItem - vc.completionWithItemsHandler = { (type, completed, items, error) in - try? FileManager.default.removeItem(at: url) - } - present(vc, animated: true, completion: nil) - } - - @IBAction private func previousSession() { - textLog?.findPrevious(string: GroupConstants.VPN.sessionMarker) - } - - @IBAction private func nextSession() { - textLog?.findNext(string: GroupConstants.VPN.sessionMarker) - } - - private func startRefreshingLog() { - vpn.requestDebugLog(fallback: TransientStore.shared.debugSnapshot) { - self.textLog?.text = $0 - - DispatchQueue.main.async { - self.textLog?.scrollToEnd() - self.refreshLogInBackground() - } - } - } - - private func refreshLogInBackground() { - let updateBlock = { - DispatchQueue.main.asyncAfter(deadline: .now() + AppConstants.Log.viewerRefreshInterval) { [weak self] in - self?.refreshLogInBackground() - } - } - - // only update if screen is visible - guard let _ = viewIfLoaded?.window else { - updateBlock() - return - } - - vpn.requestDebugLog(fallback: TransientStore.shared.debugSnapshot) { - self.textLog?.text = $0 - updateBlock() - } - } - - // MARK: Notifications - - @objc private func vpnDidPrepare() { - startRefreshingLog() - } -} diff --git a/Passepartout/App/iOS/Scenes/EndpointViewController.swift b/Passepartout/App/iOS/Scenes/EndpointViewController.swift deleted file mode 100644 index bfebcd9e..00000000 --- a/Passepartout/App/iOS/Scenes/EndpointViewController.swift +++ /dev/null @@ -1,302 +0,0 @@ -// -// EndpointViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/25/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import PassepartoutCore -import ConvenienceUI - -protocol EndpointViewControllerDelegate: AnyObject { - func endpointController(_: EndpointViewController, didUpdateWithNewAddress newAddress: String?, newProtocol: EndpointProtocol?) -} - -class EndpointViewController: UIViewController, StrongTableHost { - @IBOutlet private weak var tableView: UITableView! - - private lazy var itemRefresh = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh)) - - private var endpointAddresses: [String] = [] - - private var endpointProtocols: [EndpointProtocol] = [] - - private var initialAddress: String? - - private var initialProtocol: EndpointProtocol? - - private var currentAddress: String? - - private var currentProtocol: EndpointProtocol? - - private var currentAddressIndexPath: IndexPath? - - private var currentProtocolIndexPath: IndexPath? - - var dataSource: EndpointDataSource! - - weak var delegate: EndpointViewControllerDelegate? - - weak var modificationDelegate: ConfigurationModificationDelegate? - - // MARK: StrongTableHost - - lazy var model: StrongTableModel = { - let model: StrongTableModel = StrongTableModel() - - model.add(.locationAddresses) - model.add(.locationProtocols) - - model.setHeader(L10n.Endpoint.Sections.LocationAddresses.header, forSection: .locationAddresses) - model.setHeader(L10n.Endpoint.Sections.LocationProtocols.header, forSection: .locationProtocols) - - if dataSource.canCustomizeEndpoint { - var addressRows: [RowType] = Array(repeating: .availableAddress, count: dataSource.addresses.count) - addressRows.insert(.anyAddress, at: 0) - model.set(addressRows, forSection: .locationAddresses) - - var protocolRows: [RowType] = Array(repeating: .availableProtocol, count: dataSource.protocols.count) - protocolRows.insert(.anyProtocol, at: 0) - model.set(protocolRows, forSection: .locationProtocols) - } else { - model.set(.availableAddress, count: dataSource.addresses.count, forSection: .locationAddresses) - model.set(.availableProtocol, count: dataSource.protocols.count, forSection: .locationProtocols) - } - - return model - }() - - func reloadModel() { - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Endpoint.title - guard let _ = dataSource else { - fatalError("Data source not set") - } - endpointAddresses = dataSource.addresses - endpointProtocols = dataSource.protocols - - guard dataSource.canCustomizeEndpoint else { - tableView.allowsSelection = false - return - } - itemRefresh.isEnabled = false - navigationItem.rightBarButtonItem = itemRefresh - - initialAddress = dataSource.customAddress - initialProtocol = dataSource.customProtocol - currentAddress = initialAddress - currentProtocol = initialProtocol - - tableView.reloadData() - if let ip = selectedIndexPath { - tableView.scrollToRowAsync(at: ip) - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - guard let selected = tableView.indexPathForSelectedRow else { - return - } - tableView.deselectRow(at: selected, animated: true) - } - - // MARK: Actions - - @IBAction private func refresh() { - guard dataSource.canCustomizeEndpoint else { - return - } - initialAddress = dataSource.customAddress - initialProtocol = dataSource.customProtocol - itemRefresh.isEnabled = false - - modificationDelegate?.configurationShouldReinstall() - } - - // MARK: Helpers - - private func setNeedsRefresh() { - itemRefresh.isEnabled = (currentAddress != initialAddress) || (currentProtocol != initialProtocol) - } - - private func commitChanges() { - guard dataSource.canCustomizeEndpoint else { - return - } - - delegate?.endpointController(self, didUpdateWithNewAddress: currentAddress, newProtocol: currentProtocol) - } -} - -// MARK: - - -extension EndpointViewController: UITableViewDataSource, UITableViewDelegate { - enum SectionType { - case locationAddresses - - case locationProtocols - } - - enum RowType: Int { - case anyAddress - - case availableAddress - - case anyProtocol - - case availableProtocol - } - - private var selectedIndexPath: IndexPath? { - guard let i = endpointAddresses.firstIndex(where: { $0 == currentAddress }) else { - return nil - } - return IndexPath(row: i, section: 0) - } - - func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return model.footer(forSection: section) - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let row = model.row(at: indexPath) - switch row { - case .anyAddress: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Endpoint.Cells.AnyAddress.caption - cell.accessoryType = .none - cell.isTappable = true - if let _ = currentAddress { - cell.applyChecked(false, .current) - } else { - cell.applyChecked(true, .current) - currentAddressIndexPath = indexPath - } - return cell - - case .availableAddress: - let address = endpointAddresses[mappedIndex(indexPath.row)] - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = address - cell.accessoryType = .none - cell.isTappable = true - if address == currentAddress { - cell.applyChecked(true, .current) - currentAddressIndexPath = indexPath - } else { - cell.applyChecked(false, .current) - } - return cell - - case .anyProtocol: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Endpoint.Cells.AnyProtocol.caption - cell.accessoryType = .none - cell.isTappable = true - if let _ = currentProtocol { - cell.applyChecked(false, .current) - } else { - cell.applyChecked(true, .current) - currentProtocolIndexPath = indexPath - } - return cell - - case .availableProtocol: - let proto = endpointProtocols[mappedIndex(indexPath.row)] - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = proto.description - cell.accessoryType = .none - cell.isTappable = true - if proto == currentProtocol { - cell.applyChecked(true, .current) - currentProtocolIndexPath = indexPath - } else { - cell.applyChecked(false, .current) - } - return cell - } - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let row = model.row(at: indexPath) - var updatedIndexPaths: [IndexPath] = [indexPath] - - switch row { - case .anyAddress: - currentAddress = nil - if let old = currentAddressIndexPath { - updatedIndexPaths.append(old) - } - - case .availableAddress: - currentAddress = endpointAddresses[mappedIndex(indexPath.row)] - if let old = currentAddressIndexPath { - updatedIndexPaths.append(old) - } - - case .anyProtocol: - currentProtocol = nil - if let old = currentProtocolIndexPath { - updatedIndexPaths.append(old) - } - - case .availableProtocol: - currentProtocol = endpointProtocols[mappedIndex(indexPath.row)] - if let old = currentProtocolIndexPath { - updatedIndexPaths.append(old) - } - } - - setNeedsRefresh() - commitChanges() - tableView.reloadRows(at: updatedIndexPaths, with: .none) - } - - // MARK: Helpers - - private func mappedIndex(_ i: Int) -> Int { - if dataSource.canCustomizeEndpoint { - return i - 1 - } - return i - } -} diff --git a/Passepartout/App/iOS/Scenes/NetworkSettingsViewController.swift b/Passepartout/App/iOS/Scenes/NetworkSettingsViewController.swift deleted file mode 100644 index 766f719d..00000000 --- a/Passepartout/App/iOS/Scenes/NetworkSettingsViewController.swift +++ /dev/null @@ -1,798 +0,0 @@ -// -// NetworkSettingsViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 4/29/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import SwiftyBeaver -import PassepartoutCore -import ConvenienceUI - -private let log = SwiftyBeaver.self - -private enum FieldTag: Int { - case dnsCustom = 50 - - case dnsAddress = 100 - - case dnsDomain = 200 - - case proxyAddress = 301 - - case proxyPort = 302 - - case proxyAutoConfigurationURL = 303 - - case proxyBypass = 400 -} - -private struct Offsets { - static let dnsAddress = 0 - - static let dnsDomain = 0 - - static let proxyBypass = 3 -} - -class NetworkSettingsViewController: UITableViewController { - var profile: ConnectionProfile? - - private lazy var networkChoices = ProfileNetworkChoices.with(profile: profile) - - private lazy var clientNetworkSettings = profile?.clientNetworkSettings - - private let networkSettings = ProfileNetworkSettings() - - // MARK: StrongTableHost - - let model: StrongTableModel = StrongTableModel() - - func reloadModel() { - model.clear() - - // sections (candidate) - var sections: [SectionType] = [] - sections.append(.choices) - if networkChoices.gateway != .server { - sections.append(.manualGateway) - } - if networkChoices.dns != .server { - sections.append(.manualDNSProtocol) - sections.append(.manualDNSServers) - sections.append(.manualDNSDomains) - } - if networkChoices.proxy != .server { - sections.append(.manualProxy) - } - if networkChoices.mtu == .manual { - sections.append(.manualMTU) - } - - // headers - model.setHeader("", forSection: .choices) - model.setHeader(L10n.NetworkSettings.Gateway.title, forSection: .manualGateway) - model.setHeader(L10n.NetworkSettings.Proxy.title, forSection: .manualProxy) - model.setHeader(L10n.NetworkSettings.Mtu.title, forSection: .manualMTU) - - // footers -// model.setFooter(L10n.Configuration.Sections.Reset.footer, for: .reset) - - // rows - model.set([.gateway, .dns, .proxy, .mtu], forSection: .choices) - model.set([.gatewayIPv4, .gatewayIPv6], forSection: .manualGateway) - model.set([.mtuBytes], forSection: .manualMTU) - - var dnsProtocolRows: [RowType] = [.dnsProtocol] - switch networkSettings.dnsProtocol { - case .https, .tls: - dnsProtocolRows.append(.dnsCustom) - - default: - break - } - model.set(dnsProtocolRows, forSection: .manualDNSProtocol) - - var dnsServers: [RowType] = Array(repeating: .dnsAddress, count: networkSettings.dnsServers?.count ?? 0) - if networkChoices.dns == .manual { - dnsServers.append(.dnsAddAddress) - } - model.set(dnsServers, forSection: .manualDNSServers) - - var dnsDomains: [RowType] = Array(repeating: .dnsDomain, count: networkSettings.dnsSearchDomains?.count ?? 0) - if networkChoices.dns == .manual { - dnsDomains.append(.dnsAddDomain) - } - model.set(dnsDomains, forSection: .manualDNSDomains) - - var proxyRows: [RowType] = Array(repeating: .proxyBypass, count: networkSettings.proxyBypassDomains?.count ?? 0) - proxyRows.insert(.proxyAddress, at: 0) - proxyRows.insert(.proxyPort, at: 1) - proxyRows.insert(.proxyAutoConfigurationURL, at: 2) - if networkChoices.proxy == .manual { - proxyRows.append(.proxyAddBypass) - } - model.set(proxyRows, forSection: .manualProxy) - - // refine sections before add (DNS is tricky) - model.setHeader(L10n.NetworkSettings.Dns.title, forSection: .manualDNSProtocol) - if !dnsServers.isEmpty { - } else if !dnsDomains.isEmpty { - sections.removeAll { $0 == .manualDNSServers } - } else { - sections.removeAll { $0 == .manualDNSServers } - sections.removeAll { $0 == .manualDNSDomains } - } - for s in sections { - model.add(s) - } - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - updateGateway(networkChoices.gateway) - updateDNS(networkChoices.dns) - updateProxy(networkChoices.proxy) - updateMTU(networkChoices.mtu ?? ProfileNetworkChoices.defaultChoice) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - reloadModel() - tableView.reloadData() - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - commitChanges() - } - - // MARK: Actions - - private func updateGateway(_ choice: NetworkChoice) { - networkChoices.gateway = choice - switch networkChoices.gateway { - case .client: - if let settings = clientNetworkSettings { - networkSettings.copyGateway(from: settings) - } - - case .server: - break - - case .manual: - if let settings = profile?.manualNetworkSettings { - networkSettings.copyGateway(from: settings) - } - } - } - - private func updateDNS(_ choice: NetworkChoice) { - networkChoices.dns = choice - switch networkChoices.dns { - case .client: - if let settings = clientNetworkSettings { - networkSettings.copyDNS(from: settings) - } - - case .server: - break - - case .manual: - if let settings = profile?.manualNetworkSettings { - networkSettings.copyDNS(from: settings) - } - } - } - - private func updateProxy(_ choice: NetworkChoice) { - networkChoices.proxy = choice - switch networkChoices.proxy { - case .client: - if let settings = clientNetworkSettings { - networkSettings.copyProxy(from: settings) - } - - case .server: - break - - case .manual: - if let settings = profile?.manualNetworkSettings { - networkSettings.copyProxy(from: settings) - } - } - } - - private func updateMTU(_ choice: NetworkChoice) { - networkChoices.mtu = choice - switch networkChoices.mtu ?? ProfileNetworkChoices.defaultChoice { - case .client: - if let settings = clientNetworkSettings { - networkSettings.copyMTU(from: settings) - } - - case .server: - break - - case .manual: - if let settings = profile?.manualNetworkSettings { - networkSettings.copyMTU(from: settings) - } - } - } - - private func commitTextField(_ field: UITextField) { - - // DNS: servers, domains - // Proxy: address, port, PAC, bypass domains - - let text = field.text?.stripped ?? "" - - if field.tag == FieldTag.dnsCustom.rawValue { - switch networkSettings.dnsProtocol { - case .https: - networkSettings.dnsHTTPSURL = URL(string: text) - - case .tls: - networkSettings.dnsTLSServerName = text - - default: - break - } - } else if field.tag >= FieldTag.dnsAddress.rawValue && field.tag < FieldTag.dnsDomain.rawValue { - let i = field.tag - FieldTag.dnsAddress.rawValue - if let _ = networkSettings.dnsServers { - networkSettings.dnsServers?[i] = text - } else { - networkSettings.dnsServers = [text] - } - } else if field.tag >= FieldTag.dnsDomain.rawValue && field.tag < FieldTag.proxyAddress.rawValue { - let i = field.tag - FieldTag.dnsDomain.rawValue - if let _ = networkSettings.dnsSearchDomains { - networkSettings.dnsSearchDomains?[i] = text - } else { - networkSettings.dnsSearchDomains = [text] - } - } else if field.tag == FieldTag.proxyAddress.rawValue { - networkSettings.proxyAddress = text - } else if field.tag == FieldTag.proxyPort.rawValue { - networkSettings.proxyPort = UInt16(text) ?? 0 - } else if field.tag == FieldTag.proxyAutoConfigurationURL.rawValue { - networkSettings.proxyAutoConfigurationURL = URL(string: text) - } else if field.tag >= FieldTag.proxyBypass.rawValue { - let i = field.tag - FieldTag.proxyBypass.rawValue - if let _ = networkSettings.proxyBypassDomains { - networkSettings.proxyBypassDomains?[i] = text - } else { - networkSettings.proxyBypassDomains = [text] - } - } - - log.debug("Network settings: \(networkSettings)") - } - - private func commitChanges() { - let settings = profile?.manualNetworkSettings ?? ProfileNetworkSettings() - profile?.networkChoices = networkChoices - if networkChoices.gateway == .manual { - settings.copyGateway(from: networkSettings) - } - if networkChoices.dns == .manual { - settings.copyDNS(from: networkSettings) - } - if networkChoices.proxy == .manual { - settings.copyProxy(from: networkSettings) - } - if networkChoices.mtu == .manual { - settings.copyMTU(from: networkSettings) - } - profile?.manualNetworkSettings = settings - } -} - -// MARK: - - -extension NetworkSettingsViewController { - enum SectionType: Int { - case choices - - case manualGateway - - case manualDNSProtocol - - case manualDNSServers - - case manualDNSDomains - - case manualProxy - - case manualMTU - } - - enum RowType: Int { - case gateway - - case dns - - case proxy - - case mtu - - case gatewayIPv4 - - case gatewayIPv6 - - case dnsProtocol - - case dnsCustom - - case dnsAddress - - case dnsAddAddress - - case dnsDomain - - case dnsAddDomain - - case proxyAddress - - case proxyPort - - case proxyAutoConfigurationURL - - case proxyBypass - - case proxyAddBypass - - case mtuBytes - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return model.footer(forSection: section) - } - - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return model.headerHeight(for: section) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let row = model.row(at: indexPath) - - switch row { - case .gateway: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.NetworkSettings.Gateway.title - cell.rightText = networkChoices.gateway.description - return cell - - case .dns: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.NetworkSettings.Dns.title - cell.rightText = networkChoices.dns.description - return cell - - case .proxy: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.NetworkSettings.Proxy.title - cell.rightText = networkChoices.proxy.description - return cell - - case .mtu: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.NetworkSettings.Mtu.title - cell.rightText = (networkChoices.mtu ?? ProfileNetworkChoices.defaultChoice).description - return cell - - case .gatewayIPv4: - let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self) - cell.caption = "IPv4" - cell.toggle.isEnabled = (networkChoices.gateway == .manual) - cell.isOn = networkSettings.gatewayPolicies?.contains(.IPv4) ?? false - return cell - - case .gatewayIPv6: - let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self) - cell.caption = "IPv6" - cell.toggle.isEnabled = (networkChoices.gateway == .manual) - cell.isOn = networkSettings.gatewayPolicies?.contains(.IPv6) ?? false - return cell - - case .dnsProtocol: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Global.Captions.protocol - cell.rightText = (networkSettings.dnsProtocol ?? .fallback)?.description - if networkChoices.dns == .manual { - cell.accessoryType = .disclosureIndicator - cell.isTappable = true - } else { - cell.accessoryType = .none - cell.isTappable = false - } - return cell - - case .dnsCustom: - let cell = Cells.field.dequeue(from: tableView, for: indexPath) - cell.caption = nil - cell.field.tag = FieldTag.dnsCustom.rawValue - cell.field.isEnabled = (networkChoices.dns == .manual) - switch networkSettings.dnsProtocol { - case .https: - cell.field.placeholder = AppConstants.Placeholders.dohURL - cell.field.text = networkSettings.dnsHTTPSURL?.absoluteString - - case .tls: - cell.field.placeholder = AppConstants.Placeholders.dotServerName - cell.field.text = networkSettings.dnsTLSServerName - - default: - break - } - cell.field.clearButtonMode = .always - cell.field.keyboardType = .asciiCapable - cell.captionWidth = 0.0 - cell.delegate = self - return cell - - case .dnsAddress: - let i = indexPath.row - Offsets.dnsAddress - - let cell = Cells.field.dequeue(from: tableView, for: indexPath) - cell.caption = L10n.Global.Captions.address - cell.field.tag = FieldTag.dnsAddress.rawValue + i - cell.field.placeholder = L10n.Global.Values.none - cell.field.text = networkSettings.dnsServers?[i] - cell.field.clearButtonMode = .always - cell.field.keyboardType = .numbersAndPunctuation - cell.captionWidth = 160.0 - cell.delegate = self - cell.field.isEnabled = (networkChoices.dns == .manual) - return cell - - case .dnsAddAddress: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.NetworkSettings.Cells.AddDnsServer.caption - return cell - - case .dnsDomain: - let i = indexPath.row - Offsets.dnsDomain - - let cell = Cells.field.dequeue(from: tableView, for: indexPath) - cell.caption = L10n.NetworkSettings.Dns.Cells.Domain.caption - cell.field.tag = FieldTag.dnsDomain.rawValue + i - cell.field.placeholder = L10n.Global.Values.none - cell.field.text = networkSettings.dnsSearchDomains?[i] - cell.field.clearButtonMode = .always - cell.field.keyboardType = .asciiCapable - cell.captionWidth = 160.0 - cell.delegate = self - cell.field.isEnabled = (networkChoices.dns == .manual) - return cell - - case .dnsAddDomain: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.NetworkSettings.Cells.AddDnsDomain.caption - return cell - - case .proxyAddress: - let cell = Cells.field.dequeue(from: tableView, for: indexPath) - cell.caption = L10n.Global.Captions.address - cell.field.tag = FieldTag.proxyAddress.rawValue - cell.field.placeholder = L10n.Global.Values.none - cell.field.text = networkSettings.proxyAddress - cell.field.clearButtonMode = .always - cell.field.keyboardType = .numbersAndPunctuation - cell.captionWidth = 160.0 - cell.delegate = self - cell.field.isEnabled = (networkChoices.proxy == .manual) - return cell - - case .proxyPort: - let cell = Cells.field.dequeue(from: tableView, for: indexPath) - cell.caption = L10n.Global.Captions.port - cell.field.tag = FieldTag.proxyPort.rawValue - cell.field.placeholder = L10n.Global.Values.none - cell.field.text = networkSettings.proxyPort?.description - cell.field.clearButtonMode = .always - cell.field.keyboardType = .numberPad - cell.captionWidth = 160.0 - cell.delegate = self - cell.field.isEnabled = (networkChoices.proxy == .manual) - return cell - - case .proxyAutoConfigurationURL: - let cell = Cells.field.dequeue(from: tableView, for: indexPath) - cell.caption = "PAC" - cell.field.tag = FieldTag.proxyAutoConfigurationURL.rawValue - cell.field.placeholder = L10n.Global.Values.none - cell.field.text = networkSettings.proxyAutoConfigurationURL?.absoluteString - cell.field.clearButtonMode = .always - cell.field.keyboardType = .asciiCapable - cell.captionWidth = 160.0 - cell.delegate = self - cell.field.isEnabled = (networkChoices.proxy == .manual) - return cell - - case .proxyBypass: - let i = indexPath.row - Offsets.proxyBypass - - let cell = Cells.field.dequeue(from: tableView, for: indexPath) - cell.caption = L10n.NetworkSettings.Cells.ProxyBypass.caption - cell.field.tag = FieldTag.proxyBypass.rawValue + i - cell.field.placeholder = L10n.Global.Values.none - cell.field.text = networkSettings.proxyBypassDomains?[i] - cell.field.clearButtonMode = .always - cell.field.keyboardType = .asciiCapable - cell.captionWidth = 160.0 - cell.delegate = self - cell.field.isEnabled = (networkChoices.proxy == .manual) - return cell - - case .proxyAddBypass: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.NetworkSettings.Cells.AddProxyBypass.caption - return cell - - case .mtuBytes: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.NetworkSettings.Mtu.Cells.Bytes.caption - cell.rightText = networkSettings.mtuBytes?.description ?? L10n.Global.Values.default - return cell - } - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let cell = tableView.cellForRow(at: indexPath) - - switch model.row(at: indexPath) { - case .gateway: - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = (cell as? SettingTableViewCell)?.leftText - vc.options = NetworkChoice.choices(for: profile) - vc.descriptionBlock = { $0.description } - - vc.selectedOption = networkChoices.gateway - vc.selectionBlock = { [weak self] in - self?.updateGateway($0) - self?.navigationController?.popViewController(animated: true) - } - navigationController?.pushViewController(vc, animated: true) - - case .dns: - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = (cell as? SettingTableViewCell)?.leftText - vc.options = NetworkChoice.choices(for: profile) - vc.descriptionBlock = { $0.description } - - vc.selectedOption = networkChoices.dns - vc.selectionBlock = { [weak self] in - self?.updateDNS($0) - self?.navigationController?.popViewController(animated: true) - } - navigationController?.pushViewController(vc, animated: true) - - case .proxy: - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = (cell as? SettingTableViewCell)?.leftText - vc.options = NetworkChoice.choices(for: profile) - vc.descriptionBlock = { $0.description } - - vc.selectedOption = networkChoices.proxy - vc.selectionBlock = { [weak self] in - self?.updateProxy($0) - self?.navigationController?.popViewController(animated: true) - } - navigationController?.pushViewController(vc, animated: true) - - case .mtu: - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = (cell as? SettingTableViewCell)?.leftText - vc.options = NetworkChoice.choices(for: profile) - vc.descriptionBlock = { $0.description } - - vc.selectedOption = networkChoices.mtu - vc.selectionBlock = { [weak self] in - self?.updateMTU($0) - self?.navigationController?.popViewController(animated: true) - } - navigationController?.pushViewController(vc, animated: true) - - case .dnsProtocol: - guard networkChoices.dns == .manual else { - break - } - - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = (cell as? SettingTableViewCell)?.leftText - if #available(iOS 14, macOS 11, *) { - vc.options = [.plain, .https, .tls] - } else { - vc.options = [.plain] - } - vc.descriptionBlock = { $0.description } - - vc.selectedOption = networkSettings.dnsProtocol ?? .fallback - vc.selectionBlock = { [weak self] in - self?.networkSettings.dnsProtocol = $0 - self?.navigationController?.popViewController(animated: true) - } - navigationController?.pushViewController(vc, animated: true) - - case .dnsAddAddress: - tableView.deselectRow(at: indexPath, animated: true) - - var dnsServers = networkSettings.dnsServers ?? [] - dnsServers.append("") - networkSettings.dnsServers = dnsServers - reloadModel() - tableView.insertRows(at: [indexPath], with: .automatic) - - case .dnsAddDomain: - tableView.deselectRow(at: indexPath, animated: true) - - var dnsSearchDomains = networkSettings.dnsSearchDomains ?? [] - dnsSearchDomains.append("") - networkSettings.dnsSearchDomains = dnsSearchDomains - reloadModel() - tableView.insertRows(at: [indexPath], with: .automatic) - - case .proxyAddBypass: - tableView.deselectRow(at: indexPath, animated: true) - - var bypassDomains = networkSettings.proxyBypassDomains ?? [] - bypassDomains.append("") - networkSettings.proxyBypassDomains = bypassDomains - reloadModel() - tableView.insertRows(at: [indexPath], with: .automatic) - - case .mtuBytes: - guard networkChoices.mtu == .manual else { - break - } - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = (cell as? SettingTableViewCell)?.leftText - vc.options = ProfileNetworkSettings.mtuOptions - vc.descriptionBlock = { - guard $0 != 0 else { - return L10n.Global.Values.default - } - return $0.description - } - - vc.selectedOption = networkSettings.mtuBytes ?? 0 - vc.selectionBlock = { [weak self] in - self?.networkSettings.mtuBytes = ($0 != 0) ? $0 : nil - self?.navigationController?.popViewController(animated: true) - } - navigationController?.pushViewController(vc, animated: true) - - default: - break - } - } - - private func handle(row: RowType, cell: ToggleTableViewCell) { - switch row { - case .gatewayIPv4: - guard networkChoices.gateway == .manual else { - return - } - var policies = networkSettings.gatewayPolicies ?? [] - if cell.toggle.isOn { - policies.append(.IPv4) - } else { - policies.removeAll { $0 == .IPv4 } - } - policies.sort { $0.rawValue < $1.rawValue } - networkSettings.gatewayPolicies = policies - - case .gatewayIPv6: - guard networkChoices.gateway == .manual else { - return - } - var policies = networkSettings.gatewayPolicies ?? [] - if cell.toggle.isOn { - policies.append(.IPv6) - } else { - policies.removeAll { $0 == .IPv6 } - } - policies.sort { $0.rawValue < $1.rawValue } - networkSettings.gatewayPolicies = policies - - default: - break - } - } - - override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - switch model.row(at: indexPath) { - case .dnsAddress, .dnsDomain, .proxyBypass: - return true - - default: - return false - } - } - - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - switch model.row(at: indexPath) { - case .dnsAddress: - networkSettings.dnsServers?.remove(at: indexPath.row - Offsets.dnsAddress) - - case .dnsDomain: - networkSettings.dnsSearchDomains?.remove(at: indexPath.row - Offsets.dnsDomain) - - case .proxyBypass: - networkSettings.proxyBypassDomains?.remove(at: indexPath.row - Offsets.proxyBypass) - - default: - break - } - - reloadModel() - tableView.deleteRows(at: [indexPath], with: .automatic) - } -} - -extension NetworkSettingsViewController: ToggleTableViewCellDelegate { - func toggleCell(_ cell: ToggleTableViewCell, didToggleToValue value: Bool) { - guard let item = RowType(rawValue: cell.tag) else { - return - } - handle(row: item, cell: cell) - } -} - -extension NetworkSettingsViewController: FieldTableViewCellDelegate { - func fieldCellDidEdit(_ cell: FieldTableViewCell) { - commitTextField(cell.field) - } - - func fieldCellDidEnter(_ cell: FieldTableViewCell) { - cell.field.resignFirstResponder() - } -} diff --git a/Passepartout/App/iOS/Scenes/Organizer/DonationViewController.swift b/Passepartout/App/iOS/Scenes/Organizer/DonationViewController.swift deleted file mode 100644 index 22f139ab..00000000 --- a/Passepartout/App/iOS/Scenes/Organizer/DonationViewController.swift +++ /dev/null @@ -1,213 +0,0 @@ -// -// DonationViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 4/6/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import StoreKit -import SwiftyBeaver -import PassepartoutCore -import Convenience -import ConvenienceUI - -private let log = SwiftyBeaver.self - -class DonationViewController: UITableViewController, StrongTableHost { - private var donationList: [LocalProduct] = [] - - private var productsByIdentifier: [String: SKProduct] = [:] - - private var isLoading = true - - private var isPurchasing = false - - private func setProducts(_ products: [SKProduct]) { - for p in products { - productsByIdentifier[p.productIdentifier] = p - } - reloadModel() - tableView.reloadData() - } - - // MARK: StrongTableModel - - var model: StrongTableModel = StrongTableModel() - - func reloadModel() { - donationList = [] - model.clear() - - model.add(.oneTime) - model.setHeader(L10n.Donation.Sections.OneTime.header, forSection: .oneTime) - model.setFooter(L10n.Donation.Sections.OneTime.footer, forSection: .oneTime) - - guard !isLoading else { - model.set([.loading], forSection: .oneTime) - return - } - - donationList.append(contentsOf: LocalProduct.allDonations.filter { productsByIdentifier[$0.rawValue] != nil }) - model.set(.donation, count: donationList.count, forSection: .oneTime) - - if isPurchasing { - model.add(.activity) - model.set([.purchasing], forSection: .activity) - } - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Donation.title - reloadModel() - - ProductManager.shared.listProducts { - self.isLoading = false - guard let products = $0 else { - log.error("Unable to list products: \($1?.localizedDescription ?? "")") - return - } - self.setProducts(products) - } - } - - @IBAction private func close() { - dismiss(animated: true, completion: nil) - } - - // MARK: UITableViewController - - override func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return model.footer(forSection: section) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - switch model.row(at: indexPath) { - case .loading: - let cell = Cells.activity.dequeue(from: tableView, for: indexPath) - cell.textLabel?.text = L10n.Donation.Cells.Loading.caption - return cell - - case .purchasing: - let cell = Cells.activity.dequeue(from: tableView, for: indexPath) - cell.textLabel?.text = L10n.Donation.Cells.Purchasing.caption - return cell - - case .donation: - let productId = productIdentifier(at: indexPath) - guard let product = productsByIdentifier[productId] else { - fatalError("Row with no associated product") - } - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = product.localizedTitle - cell.rightText = product.localizedPrice - cell.isTappable = !isPurchasing - return cell - } - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - switch model.row(at: indexPath) { - case .donation: - guard !isPurchasing else { - return - } - tableView.deselectRow(at: indexPath, animated: true) - let productId = productIdentifier(at: indexPath) - guard let product = productsByIdentifier[productId] else { - fatalError("Row with no associated product") - } - - isPurchasing = true - reloadModel() - tableView.reloadData() - - ProductManager.shared.purchase(product) { - self.handlePurchase(result: $0, error: $1) - } - - default: - break - } - } - - private func handlePurchase(result: InAppPurchaseResult, error: Error?) { - let alert: UIAlertController - switch result { - case .cancelled: - isPurchasing = false - reloadModel() - tableView.reloadData() - return - - case .success: - alert = UIAlertController.asAlert(L10n.Donation.Alerts.Purchase.Success.title, L10n.Donation.Alerts.Purchase.Success.message) - - case .failure: - alert = UIAlertController.asAlert(title, L10n.Donation.Alerts.Purchase.Failure.message(error?.localizedDescription ?? "")) - } - alert.addCancelAction(L10n.Global.ok) { - self.isPurchasing = false - self.reloadModel() - self.tableView.reloadData() - } - present(alert, animated: true) - } -} - -extension DonationViewController { - enum SectionType { - case oneTime - - case activity - } - - enum RowType { - case loading - - case purchasing - - case donation - } - - private func productIdentifier(at indexPath: IndexPath) -> String { - guard model.row(at: indexPath) == .donation else { - fatalError("Not a donation row") - } - return donationList[indexPath.row].rawValue - } -} diff --git a/Passepartout/App/iOS/Scenes/Organizer/ImportedHostsViewController.swift b/Passepartout/App/iOS/Scenes/Organizer/ImportedHostsViewController.swift deleted file mode 100644 index 0e376556..00000000 --- a/Passepartout/App/iOS/Scenes/Organizer/ImportedHostsViewController.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// ImportedHostsViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 10/27/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import SwiftyBeaver -import PassepartoutCore - -private let log = SwiftyBeaver.self - -protocol ImportedHostsViewControllerDelegate: AnyObject { - func importedHostsController(_: ImportedHostsViewController, didImport url: URL) -} - -class ImportedHostsViewController: UITableViewController { - private lazy var pendingConfigurationURLs = TransientStore.shared.service.pendingConfigurationURLs().sortedCaseInsensitive() - - weak var delegate: ImportedHostsViewControllerDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.ImportedHosts.title - } - - private func selectHost(withUrl url: URL) { - delegate?.importedHostsController(self, didImport: url) - } - - @IBAction private func close() { - dismiss(animated: true, completion: nil) - } - - private func deselectSelectedRow() { - if let selectedIP = tableView.indexPathForSelectedRow { - tableView.deselectRow(at: selectedIP, animated: true) - } - } -} - -extension ImportedHostsViewController { - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return pendingConfigurationURLs.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let url = pendingConfigurationURLs[indexPath.row] - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = url.normalizedFilename - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let url = pendingConfigurationURLs[indexPath.row] - selectHost(withUrl: url) - } - - override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - return true - } - - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - let url = pendingConfigurationURLs[indexPath.row] - try? FileManager.default.removeItem(at: url) - pendingConfigurationURLs.remove(at: indexPath.row) - tableView.deleteRows(at: [indexPath], with: .top) - } -} diff --git a/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift b/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift deleted file mode 100644 index 82765791..00000000 --- a/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift +++ /dev/null @@ -1,798 +0,0 @@ -// -// OrganizerViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 9/2/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import StoreKit -import MessageUI -import SystemConfiguration.CaptiveNetwork -import PassepartoutCore -import Convenience -import ConvenienceUI - -// XXX: convoluted due to the separation of provider/host profiles - -class OrganizerViewController: UITableViewController, StrongTableHost { - private let service = TransientStore.shared.service - - private var providers: [String] = [] - - private var hosts: [String] = [] - - private var didShowSubreddit = false - - private var importer: HostImporter? - - private var hostParsingResult: OpenVPN.ConfigurationParser.Result? - - // MARK: StrongTableHost - - let model: StrongTableModel = StrongTableModel() - - func reloadModel() { - model.clear() - model.add(.vpn) - model.add(.providers) - model.add(.hosts) - if #available(iOS 12, *) { - model.add(.siri) - } - model.add(.support) - model.add(.about) - model.add(.destruction) - model.setHeader(L10n.Service.Sections.Vpn.header, forSection: .vpn) - model.setHeader(L10n.Organizer.Sections.Providers.header, forSection: .providers) - model.setHeader(L10n.Organizer.Sections.Hosts.header, forSection: .hosts) - model.setFooter(L10n.Organizer.Sections.Providers.footer, forSection: .providers) - model.setFooter(L10n.Organizer.Sections.Hosts.footer, forSection: .hosts) - if #available(iOS 12, *) { - model.setHeader(L10n.Organizer.Sections.Siri.header, forSection: .siri) - model.setFooter(L10n.Organizer.Sections.Siri.footer, forSection: .siri) - model.set([.siriShortcuts], forSection: .siri) - } - model.setHeader(L10n.Organizer.Sections.Support.header, forSection: .support) - model.set([.connectionStatus], forSection: .vpn) - if ProductManager.shared.isEligibleForFeedback() { - model.set([.donate, .joinCommunity, .writeReview], forSection: .support) - } else { - model.set([.donate, .joinCommunity], forSection: .support) - } - - model.set([.openAbout], forSection: .about) - model.set([.uninstall], forSection: .destruction) - if ProductManager.shared.isBeta { - model.add(.test) - model.setHeader("Beta", forSection: .test) - model.set([.testInterfaces, .testDisplayLog, .testTermination], forSection: .test) - } - - // - - providers = service.sortedProviderNames() - hosts = service.sortedHostIds() - - var providerRows = [RowType](repeating: .profile, count: providers.count) - var hostRows = [RowType](repeating: .profile, count: hosts.count) - providerRows.append(.addProvider) - hostRows.append(.addHost) - hostRows.append(.importHost) - - model.set(providerRows, forSection: .providers) - model.set(hostRows, forSection: .hosts) - } - - // MARK: UIViewController - - deinit { - NotificationCenter.default.removeObserver(self) - } - - override func viewDidLoad() { - super.viewDidLoad() - - title = GroupConstants.App.title - navigationItem.rightBarButtonItem = editButtonItem - Cells.destructive.register(with: tableView) - reloadModel() - tableView.reloadData() - - // XXX: if split vc is collapsed when a profile is in use, this vc - // is not loaded on app launch. consequentially, service.delegate remains - // nil until the Organizer is entered - // - // see UISplitViewControllerDelegate in AppDelegate (collapse is now commented out) - service.delegate = self - - let nc = NotificationCenter.default - nc.addObserver(self, selector: #selector(vpnDidUpdate), name: VPN.didChangeStatus, object: nil) - nc.addObserver(self, selector: #selector(productManagerDidReloadReceipt), name: ProductManager.didReloadReceipt, object: nil) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !didShowSubreddit && !TransientStore.didHandleSubreddit { - didShowSubreddit = true - - let alert = UIAlertController.asAlert(L10n.Reddit.title, L10n.Reddit.message) - alert.addPreferredAction(L10n.Reddit.Buttons.subscribe) { - TransientStore.didHandleSubreddit = true - self.subscribeSubreddit() - } - alert.addAction(L10n.Reddit.Buttons.never) { - TransientStore.didHandleSubreddit = true - } - alert.addCancelAction(L10n.Reddit.Buttons.remind) - present(alert, animated: true, completion: nil) - } - } - - override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { - if let cell = sender as? UITableViewCell, let indexPath = tableView.indexPath(for: cell) { - return model.row(at: indexPath) == .profile - } - - // fall back to active profile if no selection - return service.hasActiveProfile() - } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - let destination = (segue.destination as? UINavigationController)?.topViewController - - if let vc = destination as? ServiceViewController { - var selectedProfile: ConnectionProfile? - - // XXX: sender can be a cell or a profile - selectedProfile = sender as? ConnectionProfile - if selectedProfile == nil, let cell = sender as? UITableViewCell, let indexPath = tableView.indexPath(for: cell) { - selectedProfile = profile(at: indexPath) - } - guard selectedProfile != nil else { - assertionFailure("No selected profile") - return - } - - vc.setProfile(selectedProfile) - } else if let vc = destination as? ImportedHostsViewController { - vc.delegate = self - } else if let vc = destination as? WizardHostViewController { - vc.parsingResult = hostParsingResult - } - } - - // MARK: Actions - - private func enterProfile(_ profile: ConnectionProfile) { - perform(segue: StoryboardSegue.Organizer.selectProfileSegueIdentifier, sender: profile) - } - - private func enterActiveProfile() { - guard let activeProfile = service.activeProfile else { - return - } - enterProfile(activeProfile) - } - - private func addNewProvider() { - guard service.hasAvailableProviders() else { - let alert = UIAlertController.asAlert( - L10n.Organizer.Sections.Providers.header, - L10n.Organizer.Alerts.ExhaustedProviders.message - ) - alert.addCancelAction(L10n.Global.ok) - present(alert, animated: true, completion: nil) - return - } - perform(segue: StoryboardSegue.Organizer.addProviderSegueIdentifier) - } - - private func addNewHost() { - let picker = UIDocumentPickerViewController(documentTypes: AppConstants.URLs.filetypes, in: .import) - picker.allowsMultipleSelection = false - picker.delegate = self - present(picker, animated: true, completion: nil) - } - - private func tryParseHostURL(_ url: URL) { - importer = HostImporter(withConfigurationURL: url, parentViewController: self) - importer?.importHost(withPassphrase: nil, removeOnError: false, removeOnCancel: false) { - self.hostParsingResult = $0 - self.perform(segue: StoryboardSegue.Organizer.importHostSegueIdentifier) - } - } - - private func importNewHost() { - perform(segue: StoryboardSegue.Organizer.showImportedHostsSegueIdentifier) - } - - private func addShortcuts() { - do { - try ProductManager.shared.verifyEligible(forFeature: .siriShortcuts) - } catch ProductError.beta { - presentBetaFeatureUnavailable("Siri") - return - } catch { - presentPurchaseScreen(forProduct: .siriShortcuts) - return - } - perform(segue: StoryboardSegue.Organizer.siriShortcutsSegueIdentifier) - } - - private func donateToDeveloper() { - guard SKPaymentQueue.canMakePayments() else { - let alert = UIAlertController.asAlert( - L10n.Organizer.Cells.Donate.caption, - L10n.Organizer.Alerts.CannotDonate.message - ) - alert.addCancelAction(L10n.Global.ok) - present(alert, animated: true, completion: nil) - return - } - perform(segue: StoryboardSegue.Organizer.donateSegueIdentifier, sender: nil) - } - - private func offerTranslation() { - let V = AppConstants.Translations.Email.self - let recipient = V.recipient - let subject = V.subject - let body = V.body(V.template) - - guard MFMailComposeViewController.canSendMail() else { - let app = UIApplication.shared - guard let url = URL.mailto(to: recipient, subject: subject, body: body), app.canOpenURL(url) else { - let alert = UIAlertController.asAlert(L10n.Translations.title, L10n.Global.emailNotConfigured) - alert.addCancelAction(L10n.Global.ok) - present(alert, animated: true, completion: nil) - return - } - app.open(url, options: [:], completionHandler: nil) - return - } - - let vc = MFMailComposeViewController() - vc.setToRecipients([recipient]) - vc.setSubject(subject) - vc.setMessageBody(body, isHTML: false) - vc.mailComposeDelegate = self - vc.apply(.current) - present(vc, animated: true, completion: nil) - } - - private func about() { - perform(segue: StoryboardSegue.Organizer.aboutSegueIdentifier, sender: nil) - } - - private func removeProfile(at indexPath: IndexPath) { - let sectionObject = model.section(forIndex: indexPath.section) - let rowProfile = profileKey(at: indexPath) - switch sectionObject { - case .providers: - providers.remove(at: indexPath.row) - - case .hosts: - hosts.remove(at: indexPath.row) - - default: - return - } - -// var fallbackSection: SectionType? - - let total = providers.count + hosts.count - - // removed all profiles - if total == 0 { - VPN.shared.disconnect(completionHandler: nil) - } - // removed active profile - else if service.isActiveProfile(rowProfile) { -// let anyProvider = providerProfiles.first -// let anyHost = hostProfiles.first -// guard let anyProfile: ConnectionProfile = firstProvider ?? firstHost else { -// fatalError("There must be one profile somewhere") -// } -// fallbackSection = (anyProvider != nil) ? .providers : .hosts -// store.service.activateProfile(only) - VPN.shared.disconnect(completionHandler: nil) - } - - tableView.beginUpdates() - model.deleteRow(at: indexPath.row, ofSection: sectionObject) - tableView.deleteRows(at: [indexPath], with: .automatic) -// if let fallbackSection = fallbackSection { -// let section = model.index(ofSection: fallbackSection) -// tableView.reloadRows(at: [IndexPath(row: 0, section: section)], with: .none) -// } - tableView.endUpdates() - - service.removeProfile(rowProfile) - if #available(iOS 12, *) { - IntentDispatcher.forgetProfile(withKey: rowProfile) - } - } - - private func confirmVpnProfileDeletion() { - let alert = UIAlertController.asAlert( - L10n.Organizer.Cells.Uninstall.caption, - L10n.Organizer.Alerts.DeleteVpnProfile.message - ) - alert.addPreferredAction(L10n.Global.ok) { - VPN.shared.uninstall(completionHandler: nil) - } - alert.addCancelAction(L10n.Global.cancel) - present(alert, animated: true, completion: nil) - } - - private func becomeSponsor() { - UIApplication.shared.open(AppConstants.URLs.githubSponsors, options: [:], completionHandler: nil) - } - - private func subscribeSubreddit() { - UIApplication.shared.open(AppConstants.URLs.subreddit, options: [:], completionHandler: nil) - } - - private func writeReview() { - let url = Reviewer.urlForReview(withAppId: AppConstants.App.appStoreId) - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } - - // - - private func testInterfaces() { - let alert = UIAlertController.asAlert("Test interfaces", nil) - alert.addCancelAction(L10n.Global.ok) - defer { - present(alert, animated: true, completion: nil) - } - guard let interfaceNames = CNCopySupportedInterfaces() as? [CFString] else { - alert.message = "Nil result from CNCopySupportedInterfaces()" - return - } - - var message = interfaceNames.description - message += "\n\n" - for name in interfaceNames { - message += name as String - message += "\n" - guard let iface = CNCopyCurrentNetworkInfo(name) else { - continue - } - message += (iface as NSDictionary).description - message += "\n" - } - alert.message = message - } - - private func testDisplayLog() { - guard let log = try? String(contentsOf: AppConstants.Log.fileURL) else { - return - } - let alert = UIAlertController.asAlert("Debug log", log) - alert.addCancelAction(L10n.Global.ok) - present(alert, animated: true, completion: nil) - } - - private func testTermination() { - exit(0) - } - - // MARK: Notifications - - @objc private func vpnDidUpdate() { - tableView.reloadData() - } - - @objc private func productManagerDidReloadReceipt() { - reloadModel() - tableView.reloadData() - } -} - -// MARK: - - -extension OrganizerViewController { - enum SectionType: Int { - case vpn - - case providers - - case hosts - - case siri - - case support - - case about - - case destruction - - case test - } - - enum RowType: Int { - case connectionStatus - - case profile - - case addProvider - - case addHost - - case importHost - - case siriShortcuts - - case donate - - case githubSponsors - - case translate - - case joinCommunity - - case writeReview - - case openAbout - - case uninstall - - case testInterfaces - - case testDisplayLog - - case testTermination - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return model.footer(forSection: section) - } - - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return model.headerHeight(for: section) - } - - override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - return model.footerHeight(for: section) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - switch model.row(at: indexPath) { - case .connectionStatus: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyVPN(.current, with: VPN.shared.isEnabled ? VPN.shared.status : nil, error: nil) - cell.leftText = L10n.Service.Cells.ConnectionStatus.caption - return cell - - case .profile: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - let rowProfile = profileKey(at: indexPath) - if rowProfile.context == .provider { - let metadata = InfrastructureFactory.shared.metadata(forName: rowProfile.id) - cell.imageView?.image = metadata?.logo - } else { - cell.imageView?.image = nil - } - cell.leftText = service.screenTitle(rowProfile) - cell.rightText = service.isActiveProfile(rowProfile) ? L10n.Organizer.Cells.Profile.Value.current : nil - return cell - - case .addProvider: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.Organizer.Cells.AddProvider.caption - return cell - - case .addHost: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.Organizer.Cells.AddHost.caption - return cell - - case .importHost: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.Organizer.Cells.ImportHost.caption - return cell - - case .siriShortcuts: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.Organizer.Cells.SiriShortcuts.caption - return cell - - case .donate: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Organizer.Cells.Donate.caption - return cell - - case .githubSponsors: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Organizer.Cells.GithubSponsors.caption - return cell - - case .translate: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Organizer.Cells.Translate.caption - return cell - - case .joinCommunity: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Organizer.Cells.JoinCommunity.caption - return cell - - case .writeReview: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Organizer.Cells.WriteReview.caption - return cell - - case .openAbout: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Organizer.Cells.About.caption(GroupConstants.App.name) - cell.rightText = ApplicationInfo.appVersion - return cell - - case .uninstall: - let cell = Cells.destructive.dequeue(from: tableView, for: indexPath) - cell.caption = L10n.Organizer.Cells.Uninstall.caption - return cell - - case .testInterfaces: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = "Show interfaces" - return cell - - case .testDisplayLog: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = "Display current log" - return cell - - case .testTermination: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = "Terminate app" - return cell - } - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - switch model.row(at: indexPath) { - case .connectionStatus: - enterActiveProfile() - - case .profile: - enterProfile(profile(at: indexPath)) - - case .addProvider: - addNewProvider() - - case .addHost: - addNewHost() - - case .importHost: - importNewHost() - - case .siriShortcuts: - addShortcuts() - - case .donate: - donateToDeveloper() - - case .githubSponsors: - becomeSponsor() - - case .translate: - offerTranslation() - - case .joinCommunity: - subscribeSubreddit() - - case .writeReview: - writeReview() - - case .openAbout: - about() - - case .uninstall: - confirmVpnProfileDeletion() - - case .testInterfaces: - testInterfaces() - - case .testDisplayLog: - testDisplayLog() - - case .testTermination: - testTermination() - } - tableView.deselectRow(at: indexPath, animated: true) - } - - override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - guard tableView.isEditing else { - return false - } - return model.row(at: indexPath) == .profile - } - - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - removeProfile(at: indexPath) - } - - // MARK: Helpers - - private func sectionProfiles(at indexPath: IndexPath) -> [String] { - let ids: [String] - let sectionObject = model.section(forIndex: indexPath.section) - switch sectionObject { - case .providers: - ids = providers - - case .hosts: - ids = hosts - - default: - fatalError("Unexpected section: \(sectionObject)") - } - guard indexPath.row < ids.count else { - fatalError("No profile found at \(indexPath), is it an add cell?") - } - return ids - } - - private func profileKey(at indexPath: IndexPath) -> ProfileKey { - let section = model.section(forIndex: indexPath.section) - switch section { - case .providers: - return ProfileKey(.provider, providers[indexPath.row]) - - case .hosts: - return ProfileKey(.host, hosts[indexPath.row]) - - default: - fatalError("Profile found in unexpected section: \(section)") - } - } - - private func profile(at indexPath: IndexPath) -> ConnectionProfile { - let id = sectionProfiles(at: indexPath)[indexPath.row] - let section = model.section(forIndex: indexPath.section) - let context: Context - switch section { - case .providers: - context = .provider - - case .hosts: - context = .host - - default: - fatalError("Profile found in unexpected section: \(section)") - } - guard let found = service.profile(withContext: context, id: id) else { - fatalError("Profile (\(context), \(id)) could not be found, why was it returned?") - } - return found - } -} - -// MARK: - - -extension OrganizerViewController: ConnectionServiceDelegate { - func connectionService(didAdd profile: ConnectionProfile) { - TransientStore.shared.serialize(withProfiles: false) // add - - reloadModel() - tableView.reloadData() - - if #available(iOS 12, *) { - IntentDispatcher.donateEnableVPN() - } - - // XXX: hack around bad replace when detail presented in compact view - if let detailNav = navigationController?.viewControllers.last as? UINavigationController { - var existingServiceVC: ServiceViewController? - for vc in detailNav.viewControllers { - if let found = vc as? ServiceViewController { - existingServiceVC = found - break - } - } - let serviceVC = existingServiceVC ?? (StoryboardScene.Main.serviceIdentifier.instantiate().topViewController as! ServiceViewController) - serviceVC.setProfile(profile) - detailNav.setViewControllers([serviceVC], animated: true) - return - } - perform(segue: StoryboardSegue.Organizer.selectProfileSegueIdentifier, sender: profile) - } - - func connectionService(didRename profile: ConnectionProfile, to newTitle: String) { - TransientStore.shared.serialize(withProfiles: false) // rename - - reloadModel() - tableView.reloadData() - } - - func connectionService(didRemoveProfileWithKey key: ProfileKey) { - TransientStore.shared.serialize(withProfiles: false) // delete - - splitViewController?.serviceViewController?.hideProfileIfDeleted() - } - - // XXX: deactivate + activate leads to a redundant serialization - - func connectionService(willDeactivate profile: ConnectionProfile) { - TransientStore.shared.serialize(withProfiles: false) // deactivate - - tableView.reloadData() - } - - func connectionService(didActivate profile: ConnectionProfile) { - TransientStore.shared.serialize(withProfiles: false) // activate - - tableView.reloadData() - - if #available(iOS 12, *) { - IntentDispatcher.donateEnableVPN() - } - } - - func connectionService(didUpdate profile: ConnectionProfile) { - } -} - -extension OrganizerViewController: UIDocumentPickerDelegate { - func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { - } - - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let url = urls.first else { - return - } - tryParseHostURL(url) - } -} - -extension OrganizerViewController: ImportedHostsViewControllerDelegate { - func importedHostsController(_: ImportedHostsViewController, didImport url: URL) { - dismiss(animated: true) { - self.tryParseHostURL(url) - } - } -} - -extension OrganizerViewController: MFMailComposeViewControllerDelegate { - func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { - dismiss(animated: true, completion: nil) - } -} diff --git a/Passepartout/App/iOS/Scenes/Organizer/WizardHostViewController.swift b/Passepartout/App/iOS/Scenes/Organizer/WizardHostViewController.swift deleted file mode 100644 index e2b4e567..00000000 --- a/Passepartout/App/iOS/Scenes/Organizer/WizardHostViewController.swift +++ /dev/null @@ -1,269 +0,0 @@ -// -// WizardHostViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 9/4/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import SwiftyBeaver -import PassepartoutCore -import ConvenienceUI - -private let log = SwiftyBeaver.self - -class WizardHostViewController: UITableViewController, StrongTableHost { - @IBOutlet private weak var itemNext: UIBarButtonItem! - - private let existingHostIds = TransientStore.shared.service.sortedHostIds() - - var parsingResult: OpenVPN.ConfigurationParser.Result? { - didSet { - useSuggestedTitle() - } - } - - var removesConfigurationOnCancel = false - - private var createdProfile: HostConnectionProfile? - - private var createdTitle: String? - - private var replacedProfile: ConnectionProfile? - - // MARK: StrongTableHost - - lazy var model: StrongTableModel = { - let model: StrongTableModel = StrongTableModel() - model.add(.meta) -// model.setFooter(L10n.Global.Host.TitleInput.message, forSection: .meta) - if !existingHostIds.isEmpty { - model.add(.existing) - model.setHeader(L10n.Wizards.Host.Sections.Existing.header, forSection: .existing) - } - model.set([.titleInput], forSection: .meta) - model.set(.existingHost, count: existingHostIds.count, forSection: .existing) - return model - }() - - func reloadModel() { - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Organizer.Sections.Hosts.header - itemNext.title = L10n.Global.next - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - useSuggestedTitle() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - cellTitle?.field.becomeFirstResponder() - } - - // MARK: Actions - - private func useSuggestedTitle() { - cellTitle?.field.text = parsingResult?.url?.normalizedFilename - } - - @IBAction private func next() { - guard let enteredTitle = cellTitle?.field.text?.stripped, !enteredTitle.isEmpty else { - return - } - guard let result = parsingResult else { - return - } - guard let hostname = result.configuration.hostname else { - return - } - - let profile = HostConnectionProfile(hostname: hostname) - let builder = OpenVPNProvider.ConfigurationBuilder(sessionConfiguration: result.configuration) - profile.parameters = builder.build() - - let service = TransientStore.shared.service - replacedProfile = nil - if let existingProfile = service.hostProfile(withTitle: enteredTitle) { - replacedProfile = existingProfile - let alert = UIAlertController.asAlert(title, L10n.Wizards.Host.Alerts.Existing.message) - alert.addPreferredAction(L10n.Global.ok) { - self.next(withProfile: profile, title: enteredTitle) - } - alert.addCancelAction(L10n.Global.cancel) - present(alert, animated: true, completion: nil) - return - } - next(withProfile: profile, title: enteredTitle) - } - - private func next(withProfile profile: HostConnectionProfile, title: String) { - createdProfile = profile - createdTitle = title - - let accountVC = StoryboardScene.Main.accountIdentifier.instantiate() - if let replacedProfile = replacedProfile { - accountVC.currentCredentials = TransientStore.shared.service.credentials(for: replacedProfile) - } - accountVC.delegate = self - navigationController?.pushViewController(accountVC, animated: true) - } - - private func finish(withCredentials credentials: Credentials) { - guard let profile = createdProfile, let title = createdTitle else { - fatalError("No profile created?") - } - let service = TransientStore.shared.service - if let url = parsingResult?.url { - do { - let savedURL = try service.save(configurationURL: url, for: profile) - log.debug("Associated .ovpn configuration file to profile '\(profile.id)': \(savedURL)") - - // can now delete imported file - try? FileManager.default.removeItem(at: url) - } catch let e { - log.error("Could not associate .ovpn configuration file to profile: \(e)") - } - } - dismiss(animated: true) { - if let replacedProfile = self.replacedProfile { - service.removeProfile(ProfileKey(replacedProfile)) - } - service.addOrReplaceProfile(profile, credentials: credentials, title: title) - } - } - - @IBAction private func close() { - if removesConfigurationOnCancel, let url = parsingResult?.url { - try? FileManager.default.removeItem(at: url) - } - dismiss(animated: true, completion: nil) - } -} - -// MARK: - - -extension WizardHostViewController { - enum SectionType: Int { - case meta - - case existing - } - - enum RowType: Int { - case titleInput - - case existingHost - } - - private var cellTitle: FieldTableViewCell? { - guard let ip = model.indexPath(forRow: .titleInput, ofSection: .meta) else { - return nil - } - return tableView.cellForRow(at: ip) as? FieldTableViewCell - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return model.footer(forSection: section) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - switch model.row(at: indexPath) { - case .titleInput: - let cell = Cells.field.dequeue(from: tableView, for: indexPath) - cell.caption = L10n.Wizards.Host.Cells.TitleInput.caption - cell.captionWidth = 100.0 -// cell.allowedCharset = .filename - cell.field.applyHostTitle(.current) - cell.delegate = self - return cell - - case .existingHost: - let existingTitle = hostTitle(at: indexPath.row) - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = existingTitle - cell.accessoryType = .none - cell.isTappable = true - return cell - } - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - switch model.row(at: indexPath) { - case .existingHost: - guard let titleIndexPath = model.indexPath(forRow: .titleInput, ofSection: .meta) else { - fatalError("Could not found title cell?") - } - let existingTitle = hostTitle(at: indexPath.row) - let cellTitle = tableView.cellForRow(at: titleIndexPath) as? FieldTableViewCell - cellTitle?.field.text = existingTitle - tableView.deselectRow(at: indexPath, animated: true) - - default: - break - } - } - - private func hostTitle(at row: Int) -> String { - return TransientStore.shared.service.screenTitle(forHostId: existingHostIds[row]) - } -} - -// MARK: - - -extension WizardHostViewController: FieldTableViewCellDelegate { - func fieldCellDidEdit(_: FieldTableViewCell) { - } - - func fieldCellDidEnter(_: FieldTableViewCell) { - next() - } -} - -extension WizardHostViewController: AccountViewControllerDelegate { - func accountController(_: AccountViewController, didEnterCredentials credentials: Credentials) { - } - - func accountControllerDidComplete(_ vc: AccountViewController) { - finish(withCredentials: vc.credentials) - } -} diff --git a/Passepartout/App/iOS/Scenes/Organizer/WizardProviderViewController.swift b/Passepartout/App/iOS/Scenes/Organizer/WizardProviderViewController.swift deleted file mode 100644 index 0f05d9dc..00000000 --- a/Passepartout/App/iOS/Scenes/Organizer/WizardProviderViewController.swift +++ /dev/null @@ -1,246 +0,0 @@ -// -// WizardProviderViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 9/4/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import SwiftyBeaver -import PassepartoutCore -import ConvenienceUI - -private let log = SwiftyBeaver.self - -class WizardProviderViewController: UITableViewController, StrongTableHost { - private var available: [Infrastructure.Metadata] = [] - - private var createdProfile: ProviderConnectionProfile? - - private var selectedMetadata: Infrastructure.Metadata? - - // MARK: StrongTableHost - - let model = StrongTableModel() - - func reloadModel() { - available = TransientStore.shared.service.availableProviders() - - model.clear() - model.add(.availableProviders) - model.add(.listActions) - model.set(.provider, count: available.count, forSection: .availableProviders) - model.set([.updateList], forSection: .listActions) - } - - // MARK: UIViewController - - deinit { - NotificationCenter.default.removeObserver(self) - } - - override func viewDidLoad() { - super.viewDidLoad() - - let nc = NotificationCenter.default - nc.addObserver(self, selector: #selector(didReloadReceipt), name: ProductManager.didReloadReceipt, object: nil) - - title = L10n.Organizer.Sections.Providers.header - reloadModel() - } - - private func tryNext(withMetadata metadata: Infrastructure.Metadata, purchaseIfNecessary: Bool) { - selectedMetadata = metadata - - do { - try ProductManager.shared.verifyEligible(forProvider: metadata) - } catch ProductError.beta { - presentBetaFeatureUnavailable("Providers") - return - } catch { - guard purchaseIfNecessary else { - return - } - presentPurchaseScreen(forProduct: metadata.product, delegate: self) - return - } - - // make sure that infrastructure exists locally - guard let _ = InfrastructureFactory.shared.infrastructure(forName: metadata.name) else { - let hud = HUD(view: view) - _ = InfrastructureFactory.shared.update(metadata.name, notBeforeInterval: nil) { [weak self] in - hud.hide() - guard let _ = $0 else { - self?.alertMissingInfrastructure(forMetadata: metadata, error: $1) - return - } - self?.next(withMetadata: metadata) - } - return - } - - next(withMetadata: metadata) - } - - private func next(withMetadata metadata: Infrastructure.Metadata) { - let profile = ProviderConnectionProfile(name: metadata.name) - createdProfile = profile - - let accountVC = StoryboardScene.Main.accountIdentifier.instantiate() - guard let infrastructure = InfrastructureFactory.shared.infrastructure(forName: metadata.name) else { - fatalError("Moving to credentials with nil infrastructure, not downloaded properly?") - } - accountVC.usernamePlaceholder = infrastructure.defaults.username - accountVC.infrastructureName = infrastructure.name - accountVC.delegate = self - navigationController?.pushViewController(accountVC, animated: true) - } - - private func alertMissingInfrastructure(forMetadata metadata: Infrastructure.Metadata, error: Error?) { - var message = L10n.Wizards.Provider.Alerts.Unavailable.message - if let error = error { - log.error("Unable to download missing \(metadata.description) infrastructure (network error): \(error.localizedDescription)") - message.append(" \(error.localizedDescription)") - } else { - log.error("Unable to download missing \(metadata.description) infrastructure (API error)") - } - - let alert = UIAlertController.asAlert(metadata.description, message) - alert.addCancelAction(L10n.Global.ok) - present(alert, animated: true, completion: nil) - - if let ip = tableView.indexPathForSelectedRow { - tableView.deselectRow(at: ip, animated: true) - } - } - - private func finish(withCredentials credentials: Credentials) { - guard let profile = createdProfile else { - fatalError("No profile created?") - } - let service = TransientStore.shared.service - dismiss(animated: true) { - service.addOrReplaceProfile(profile, credentials: credentials) - } - } - - private func updateProvidersList() { - let hud = HUD(view: view) - InfrastructureFactory.shared.updateIndex { [weak self] in - hud.hide() - if let error = $0 { - log.error("Unable to update providers list: \(error)") - return - } - - self?.reloadModel() - self?.tableView.reloadData() - } - } - - @IBAction private func close() { - dismiss(animated: true, completion: nil) - } -} - -// MARK: - - -extension WizardProviderViewController { - enum SectionType { - case availableProviders - - case listActions - } - - enum RowType { - case provider - - case updateList - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let row = model.row(at: indexPath) - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - switch row { - case .provider: - let metadata = available[indexPath.row] - cell.apply(.current) - cell.imageView?.image = metadata.logo - cell.leftText = metadata.description - - case .updateList: - cell.applyAction(.current) - cell.imageView?.image = nil - cell.leftText = L10n.Wizards.Provider.Cells.UpdateList.caption - } - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let row = model.row(at: indexPath) - switch row { - case .provider: - let metadata = available[indexPath.row] - tryNext(withMetadata: metadata, purchaseIfNecessary: true) - - case .updateList: - tableView.deselectRow(at: indexPath, animated: true) - updateProvidersList() - } - } -} - -// MARK: - - -extension WizardProviderViewController: AccountViewControllerDelegate { - func accountController(_: AccountViewController, didEnterCredentials credentials: Credentials) { - } - - func accountControllerDidComplete(_ vc: AccountViewController) { - finish(withCredentials: vc.credentials) - } -} - -// MARK: - - -extension WizardProviderViewController: PurchaseViewControllerDelegate { - func purchaseController(_ purchaseController: PurchaseViewController, didPurchase product: LocalProduct?) { - guard let metadata = selectedMetadata else { - return - } - tryNext(withMetadata: metadata, purchaseIfNecessary: false) - } - - @objc private func didReloadReceipt() { - guard let metadata = selectedMetadata else { - return - } - tryNext(withMetadata: metadata, purchaseIfNecessary: false) - } -} diff --git a/Passepartout/App/iOS/Scenes/ProviderPoolViewController.swift b/Passepartout/App/iOS/Scenes/ProviderPoolViewController.swift deleted file mode 100644 index 4899bcee..00000000 --- a/Passepartout/App/iOS/Scenes/ProviderPoolViewController.swift +++ /dev/null @@ -1,287 +0,0 @@ -// -// ProviderPoolViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/12/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import PassepartoutCore -import ConvenienceUI - -protocol ProviderPoolViewControllerDelegate: AnyObject { - func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool) - - func providerPoolController(_: ProviderPoolViewController, didUpdateFavoriteGroups favoriteGroupIds: [String]) -} - -class ProviderPoolViewController: UIViewController { - @IBOutlet private weak var tableView: UITableView! - - private var allCategories: [PoolCategory] = [] - - private var favoriteCategories: [PoolCategory] = [] - - private var currentPool: Pool? - - private var isShowingFavorites = false - - var favoriteGroupIds: [String] = [] - - var isReadonly = false - - weak var delegate: ProviderPoolViewControllerDelegate? - - func setInfrastructure(_ infrastructure: Infrastructure, currentPoolId: String?) { - let sortedCategories = infrastructure.categories.sorted { $0.name.lowercased() < $1.name.lowercased() } - allCategories = [] - for c in sortedCategories { - allCategories.append(PoolCategory( - name: c.name, - groups: c.groups.sorted(), - presets: c.presets - )) - } - - // XXX: uglyyy - for cat in allCategories { - for group in cat.groups { - for p in group.pools { - if p.id == currentPoolId { - currentPool = p - return - } - } - } - } - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Service.Cells.Provider.Pool.caption - tableView.reloadData() - if let ip = selectedIndexPath { - tableView.selectRowAsync(at: ip) - } - - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: self, action: #selector(toggleFavorites)) - } - - // MARK: Actions - - @objc private func toggleFavorites() { - isShowingFavorites = !isShowingFavorites - if isShowingFavorites { - reloadFavorites() - navigationItem.rightBarButtonItem?.applyAccent(.current) - } else { - navigationItem.rightBarButtonItem?.apply(.current) - } - tableView.reloadData() - - if let ip = selectedIndexPath { - tableView.selectRowAsync(at: ip) - } - } - - private func favoriteGroup(withId groupId: String) { - favoriteGroupIds.append(groupId) - delegate?.providerPoolController(self, didUpdateFavoriteGroups: favoriteGroupIds) - } - - private func unfavoriteGroup(in category: PoolCategory, withId groupId: String, deletingRowAt indexPath: IndexPath?) { - favoriteGroupIds.removeAll(where: { $0 == groupId }) - if let indexPath = indexPath { - reloadFavorites() - if category.groups.count == 1 { - let indexSet = IndexSet(integer: indexPath.section) - if isShowingEmptyFavorites { - tableView.reloadSections(indexSet, with: .automatic) - } else { - tableView.deleteSections(indexSet, with: .automatic) - } - } else { - tableView.deleteRows(at: [indexPath], with: .automatic) - } - } - delegate?.providerPoolController(self, didUpdateFavoriteGroups: favoriteGroupIds) - } - - private func reloadFavorites() { - favoriteCategories = [] - for c in allCategories { - let favoriteGroups = c.groups.filter { - return favoriteGroupIds.contains($0.uniqueId(in: c)) - } - guard !favoriteGroups.isEmpty else { - continue - } - favoriteCategories.append(PoolCategory( - name: c.name, - groups: favoriteGroups, - presets: c.presets - )) - } - } -} - -// MARK: - - -extension ProviderPoolViewController: UITableViewDataSource, UITableViewDelegate { - private var selectedIndexPath: IndexPath? { - for (i, cat) in categories.enumerated() { - for (j, group) in cat.groups.enumerated() { - guard let _ = group.pools.firstIndex(where: { $0.id == currentPool?.id }) else { - continue - } - return IndexPath(row: j, section: i) - } - } - return nil - } - - func numberOfSections(in tableView: UITableView) -> Int { - if isShowingEmptyFavorites { - return 1 - } - return categories.count - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - if isShowingEmptyFavorites { - return nil - } - if categories.count == 1 && categories.first?.name == "" { - return nil - } - let model = categories[section] - return model.name - } - - func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - if isShowingEmptyFavorites { - return L10n.Provider.Pool.Sections.EmptyFavorites.footer - } - return nil - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if isShowingEmptyFavorites { - return 0 - } - let model = categories[section] - return model.groups.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let group = poolGroup(at: indexPath) - guard let pool = group.pools.first else { - fatalError("Empty pools in group \(group)") - } - - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.imageView?.image = group.logo - cell.leftText = pool.localizedCountry - if group.pools.count > 1 { - cell.accessoryType = .detailDisclosureButton // no checkmark! - } else { - cell.rightText = pool.secondaryId - } - cell.rightText = cell.rightText?.uppercased() - cell.isTappable = true - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let group = poolGroup(at: indexPath) - guard let pool = group.pools.randomElement() else { - fatalError("Empty pools in group \(group)") - } - currentPool = pool - delegate?.providerPoolController(self, didSelectPool: pool) - } - - func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) { - let group = poolGroup(at: indexPath) - guard group.pools.count > 1 else { - return - } - let vc = SingleOptionViewController() - vc.applyTint(.current) - vc.title = group.localizedCountry - vc.options = group.pools.sortedPools() - vc.selectedOption = currentPool - vc.descriptionBlock = { !$0.secondaryId.isEmpty ? $0.secondaryId : L10n.Global.Values.default } - vc.selectionBlock = { - self.currentPool = $0 - self.delegate?.providerPoolController(self, didSelectPool: $0) - } - navigationController?.pushViewController(vc, animated: true) - } - - func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - return !isReadonly - } - - func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let category = categories[indexPath.section] - let group = poolGroup(at: indexPath) - let groupId = group.uniqueId(in: category) - - let action: UIContextualAction - if favoriteGroupIds.contains(groupId) { - action = UIContextualAction(style: .destructive, title: L10n.Provider.Pool.Actions.unfavorite) { - self.unfavoriteGroup(in: category, withId: groupId, deletingRowAt: self.isShowingFavorites ? indexPath : nil) - $2(true) - } - } else if !isShowingFavorites { - action = UIContextualAction(style: .normal, title: L10n.Provider.Pool.Actions.favorite) { - self.favoriteGroup(withId: groupId) - $2(true) - } - action.applyNormal(.current) - } else { - return nil - } - - let cfg = UISwipeActionsConfiguration(actions: [action]) - cfg.performsFirstActionWithFullSwipe = false - return cfg - } - - // MARK: Helpers - - private func poolGroup(at indexPath: IndexPath) -> PoolGroup { - let model = categories[indexPath.section] - return model.groups[indexPath.row] - } - - private var categories: [PoolCategory] { - return isShowingFavorites ? favoriteCategories : allCategories - } - - private var isShowingEmptyFavorites: Bool { - return isShowingFavorites && favoriteGroupIds.isEmpty - } -} diff --git a/Passepartout/App/iOS/Scenes/ProviderPresetViewController.swift b/Passepartout/App/iOS/Scenes/ProviderPresetViewController.swift deleted file mode 100644 index ac0e5823..00000000 --- a/Passepartout/App/iOS/Scenes/ProviderPresetViewController.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// ProviderPresetViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 9/2/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import PassepartoutCore - -protocol ProviderPresetViewControllerDelegate: AnyObject { - func providerPresetController(_: ProviderPresetViewController, didSelectPreset preset: InfrastructurePreset) -} - -class ProviderPresetViewController: UIViewController { - @IBOutlet private weak var tableView: UITableView! - - var presets: [InfrastructurePreset] = [] - - var currentPresetId: String? - - weak var delegate: ProviderPresetViewControllerDelegate? - - // MARK: Table - - private let rows: [RowType] = [.presetDescription, .techDetails] - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Service.Cells.Provider.Preset.caption - tableView.reloadData() - if let ip = selectedIndexPath { - tableView.scrollToRowAsync(at: ip) - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - if let ip = tableView.indexPathForSelectedRow { - tableView.deselectRow(at: ip, animated: true) - } - } -} - -extension ProviderPresetViewController: UITableViewDataSource, UITableViewDelegate { - enum RowType: Int { - case presetDescription - - case techDetails - } - - private var selectedIndexPath: IndexPath? { - guard let i = presets.firstIndex(where: { $0.id == currentPresetId }) else { - return nil - } - return IndexPath(row: 0, section: i) - } - - func numberOfSections(in tableView: UITableView) -> Int { - return presets.count - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - let preset = presets[section] - return preset.name - } - -// func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { -// return L10n.Provider.Preset.Sections.Main.footer -// } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return rows.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let preset = presets[indexPath.section] - - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.isTappable = true - switch rows[indexPath.row] { - case .presetDescription: - cell.leftText = preset.comment - cell.applyChecked(preset.id == currentPresetId, .current) - - case .techDetails: - cell.applyAction(.current) - cell.leftText = L10n.Provider.Preset.Cells.TechDetails.caption - cell.accessoryType = .none - } - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let preset = presets[indexPath.section] - - switch rows[indexPath.row] { - case .presetDescription: - currentPresetId = preset.id - delegate?.providerPresetController(self, didSelectPreset: preset) - - case .techDetails: - let vc = StoryboardScene.Main.configurationIdentifier.instantiate() - vc.title = preset.name - vc.initialConfiguration = preset.configuration.sessionConfiguration - navigationController?.pushViewController(vc, animated: true) - } - } -} diff --git a/Passepartout/App/iOS/Scenes/Purchase/PurchaseTableViewCell.swift b/Passepartout/App/iOS/Scenes/Purchase/PurchaseTableViewCell.swift deleted file mode 100644 index f089d6a4..00000000 --- a/Passepartout/App/iOS/Scenes/Purchase/PurchaseTableViewCell.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// PurchaseTableViewCell.swift -// Passepartout -// -// Created by Davide De Rosa on 10/30/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import StoreKit - -class PurchaseTableViewCell: UITableViewCell { - @IBOutlet private weak var labelTitle: UILabel? - - @IBOutlet private weak var labelPrice: UILabel? - - @IBOutlet private weak var labelDescription: UILabel? - - override func awakeFromNib() { - super.awakeFromNib() - - labelTitle?.applyAccent(.current) - labelPrice?.applyAccent(.current) - labelDescription?.apply(.current) - labelDescription?.applySecondarySize(.current) - } - - func fill(product: SKProduct, customDescription: String? = nil) { - fill( - title: product.localizedTitle, - description: customDescription ?? "\(product.localizedDescription)." - ) - labelPrice?.text = product.localizedPrice - } - - func fill(title: String, description: String) { - labelTitle?.text = title - labelDescription?.text = description - labelPrice?.text = nil - } -} diff --git a/Passepartout/App/iOS/Scenes/Purchase/PurchaseViewController.swift b/Passepartout/App/iOS/Scenes/Purchase/PurchaseViewController.swift deleted file mode 100644 index 14f8e1a1..00000000 --- a/Passepartout/App/iOS/Scenes/Purchase/PurchaseViewController.swift +++ /dev/null @@ -1,261 +0,0 @@ -// -// PurchaseViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 10/27/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import StoreKit -import PassepartoutCore -import SwiftyBeaver -import ConvenienceUI - -private let log = SwiftyBeaver.self - -protocol PurchaseViewControllerDelegate: AnyObject { - func purchaseController(_ purchaseController: PurchaseViewController, didPurchase product: LocalProduct?) -} - -class PurchaseViewController: UITableViewController, StrongTableHost { - private var isLoading = true - - var feature: LocalProduct? - - weak var delegate: PurchaseViewControllerDelegate? - - private var skFeature: SKProduct? - - private var skPlatformVersion: SKProduct? - - private var skFullVersion: SKProduct? - - private var platformVersionExtra: String? - - private var fullVersionExtra: String? - - // MARK: StrongTableHost - - var model: StrongTableModel = StrongTableModel() - - func reloadModel() { - model.clear() - model.add(.products) - model.setFooter(L10n.Purchase.Sections.Products.footer, forSection: .products) - - var rows: [RowType] = [] - let pm = ProductManager.shared - if let skPlatformVersion = pm.product(withIdentifier: .fullVersion_iOS) { - self.skPlatformVersion = skPlatformVersion - rows.append(.platformVersion) - - let bullets: [String] = ProductManager.shared.featureProducts(excluding: [.fullVersion, .fullVersion_iOS, .fullVersion_macOS]).map { - return $0.localizedTitle - }.sortedCaseInsensitive() - platformVersionExtra = bullets.joined(separator: "\n") - } - if !pm.hasPurchased(.fullVersion_macOS), let skFullVersion = pm.product(withIdentifier: .fullVersion) { - self.skFullVersion = skFullVersion - rows.append(.fullVersion) - - let bullets: [String] = ProductManager.shared.featureProducts(including: [.fullVersion_iOS, .fullVersion_macOS]).map { - return $0.localizedTitle - }.sortedCaseInsensitive() - fullVersionExtra = bullets.joined(separator: "\n") - } - if let feature = feature, let skFeature = pm.product(withIdentifier: feature) { - self.skFeature = skFeature - rows.append(.feature) - } - rows.append(.restore) - model.set(rows, forSection: .products) - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Purchase.title - navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(close)) - - isLoading = true - tableView.reloadData() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - let hud = HUD(view: view) - ProductManager.shared.listProducts { [weak self] (_, _) in - self?.reloadModel() - self?.isLoading = false - self?.tableView.reloadData() - hud.hide() - } - } - - // MARK: Actions - - private func purchaseFeature() { - guard let sk = skFeature else { - return - } - purchase(sk) - } - - private func purchasePlatformVersion() { - guard let sk = skPlatformVersion else { - return - } - purchase(sk) - } - - private func purchaseFullVersion() { - guard let sk = skFullVersion else { - return - } - purchase(sk) - } - - private func restorePurchases() { - let hud = HUD(view: view) - ProductManager.shared.restorePurchases { [weak self] in - hud.hide() - guard $0 == nil else { - return - } - self?.dismiss(animated: true, completion: nil) - } - } - - private func purchase(_ skProduct: SKProduct) { - let hud = HUD(view: view) - ProductManager.shared.purchase(skProduct) { [weak self] in - hud.hide() - guard $0 == .success else { - if let error = $1 { - self?.reportPurchaseError(withProduct: skProduct, error: error) - } - return - } - - self?.dismiss(animated: true) { - guard let weakSelf = self else { - return - } - let product = LocalProduct(rawValue: skProduct.productIdentifier) - weakSelf.delegate?.purchaseController(weakSelf, didPurchase: product) - } - } - } - - private func reportPurchaseError(withProduct product: SKProduct, error: Error) { - log.error("Unable to purchase \(product): \(error)") - - let alert = UIAlertController.asAlert(product.localizedTitle, error.localizedDescription) - alert.addCancelAction(L10n.Global.ok) - present(alert, animated: true, completion: nil) - } - - @objc private func close() { - dismiss(animated: true, completion: nil) - } -} - -extension PurchaseViewController { - enum SectionType { - case products - } - - enum RowType { - case feature - - case platformVersion - - case fullVersion - - case restore - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return model.footer(forSection: section) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard !isLoading else { - return 0 - } - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "PurchaseTableViewCell", for: indexPath) as! PurchaseTableViewCell - switch model.row(at: indexPath) { - case .feature: - guard let product = skFeature else { - fatalError("Loaded feature cell, yet no corresponding product?") - } - cell.fill(product: product) - - case .platformVersion: - guard let product = skPlatformVersion else { - fatalError("Loaded platform version cell, yet no corresponding product?") - } - cell.fill(product: product, customDescription: platformVersionExtra) - - case .fullVersion: - guard let product = skFullVersion else { - fatalError("Loaded full version cell, yet no corresponding product?") - } - cell.fill(product: product, customDescription: fullVersionExtra) - - case .restore: - cell.fill( - title: L10n.Purchase.Cells.Restore.title, - description: L10n.Purchase.Cells.Restore.description - ) - } - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - switch model.row(at: indexPath) { - case .feature: - purchaseFeature() - - case .platformVersion: - purchasePlatformVersion() - - case .fullVersion: - purchaseFullVersion() - - case .restore: - restorePurchases() - } - } -} diff --git a/Passepartout/App/iOS/Scenes/ServerNetworkViewController.swift b/Passepartout/App/iOS/Scenes/ServerNetworkViewController.swift deleted file mode 100644 index 981fbc82..00000000 --- a/Passepartout/App/iOS/Scenes/ServerNetworkViewController.swift +++ /dev/null @@ -1,291 +0,0 @@ -// -// ServerNetworkViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 10/23/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import SwiftyBeaver -import PassepartoutCore -import ConvenienceUI - -private let log = SwiftyBeaver.self - -class ServerNetworkViewController: UITableViewController, StrongTableHost { - var configuration: OpenVPN.Configuration! - - private let indexOfFirstRoute4 = 2 - - private let indexOfFirstRoute6 = 2 - - private var indexOfFirstDNSAddress = 0 - - private var indexOfFirstProxyBypassDomain = 0 - - // MARK: StrongTableHost - - lazy var model: StrongTableModel = { - let model: StrongTableModel = StrongTableModel() - var rows: [RowType] - - if let ipv4 = configuration.ipv4 { - model.add(.ipv4) - rows = [.address, .defaultGateway] - for i in 0.. Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return model.footer(forSection: section) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let section = model.section(forIndex: indexPath.section) - let row = model.row(at: indexPath) - - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.accessoryType = .none - cell.isTappable = false - - // family-specific rows - switch section { - case .ipv4: - switch row { - case .address: - cell.leftText = L10n.Global.Captions.address - if let ipv4 = configuration.ipv4 { - cell.rightText = "\(ipv4.address)/\(ipv4.addressMask)" - } else { - cell.rightText = L10n.Global.Values.none - } - - case .defaultGateway: - cell.leftText = L10n.NetworkSettings.Gateway.title - cell.rightText = configuration.ipv4?.defaultGateway ?? L10n.Global.Values.none - - case .route: - guard let route = configuration.ipv4?.routes[indexPath.row - indexOfFirstRoute4] else { - fatalError("Got an IPv4 route cell with empty routes") - } - cell.leftText = L10n.ServerNetwork.Cells.Route.caption - cell.rightText = "\(route.destination)/\(route.mask) -> \(route.gateway)" - - default: - break - } - - case .ipv6: - switch row { - case .address: - cell.leftText = L10n.Global.Captions.address - if let ipv6 = configuration.ipv6 { - cell.rightText = "\(ipv6.address)/\(ipv6.addressPrefixLength)" - } else { - cell.rightText = L10n.Global.Values.none - } - - case .defaultGateway: - cell.leftText = L10n.NetworkSettings.Gateway.title - cell.rightText = configuration.ipv6?.defaultGateway ?? L10n.Global.Values.none - - case .route: - guard let route = configuration.ipv6?.routes[indexPath.row - indexOfFirstRoute6] else { - fatalError("Got an IPv6 route cell with empty routes") - } - cell.leftText = L10n.ServerNetwork.Cells.Route.caption - cell.rightText = "\(route.destination)/\(route.prefixLength) -> \(route.gateway)" - - default: - break - } - - default: - break - } - - // shared rows - switch row { - case .dnsDomain: - guard let domain = configuration.searchDomains?[indexPath.row] else { - fatalError("Got DNS search domain with empty search domains") - } - cell.leftText = L10n.NetworkSettings.Dns.Cells.Domain.caption - cell.rightText = domain - - case .dnsAddress: - guard let server = configuration.dnsServers?[indexPath.row - indexOfFirstDNSAddress] else { - fatalError("Got DNS server with empty servers") - } - cell.leftText = L10n.Global.Captions.address - cell.rightText = server - - case .proxyAddress: - guard let proxy = configuration.httpsProxy ?? configuration.httpProxy else { - fatalError("Got proxy section without a proxy") - } - cell.leftText = L10n.Global.Captions.address - cell.rightText = "\(proxy.address):\(proxy.port)" - - case .proxyAutoConfigurationURL: - cell.leftText = "PAC" - guard let url = configuration.proxyAutoConfigurationURL else { - fatalError("Got PAC cell without a PAC") - } - cell.rightText = url.absoluteString - - case .proxyBypassDomains: - guard let domain = configuration.proxyBypassDomains?[indexPath.row - indexOfFirstProxyBypassDomain] else { - fatalError("Got proxy bypass domain with empty domains") - } - cell.leftText = L10n.NetworkSettings.Cells.ProxyBypass.caption - cell.rightText = domain - - default: - break - } - - return cell - } - - override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool { - return true - } - - override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool { - return action == #selector(UIResponderStandardEditActions.copy(_:)) - } - - override func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) { - let cell = tableView.cellForRow(at: indexPath) - UIPasteboard.general.string = cell?.detailTextLabel?.text - } -} diff --git a/Passepartout/App/iOS/Scenes/ServiceViewController.swift b/Passepartout/App/iOS/Scenes/ServiceViewController.swift deleted file mode 100644 index 2475311a..00000000 --- a/Passepartout/App/iOS/Scenes/ServiceViewController.swift +++ /dev/null @@ -1,1536 +0,0 @@ -// -// ServiceViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/6/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import NetworkExtension -import CoreLocation -import SwiftyBeaver -import PassepartoutCore -import Convenience -import ConvenienceUI - -private let log = SwiftyBeaver.self - -class ServiceViewController: UIViewController, StrongTableHost { - @IBOutlet private weak var tableView: UITableView! - - @IBOutlet private weak var viewWelcome: UIView! - - @IBOutlet private weak var labelWelcome: UILabel! - - @IBOutlet private weak var itemEdit: UIBarButtonItem! - - private let locationManager = CLLocationManager() - - private var isPendingTrustedWiFi = false - - private let downloader = FileDownloader( - temporaryURL: GroupConstants.App.cachesURL.appendingPathComponent("downloaded.tmp"), - timeout: AppConstants.Services.timeout - ) - - private var profile: ConnectionProfile? - - private let service = TransientStore.shared.service - - private lazy var vpn = GracefulVPN(service: service) - - private weak var pendingRenameAction: UIAlertAction? - - private var lastInfrastructureUpdate: Date? - - private var shouldDeleteLogOnDisconnection = false - - private var currentDataCount: (Int, Int)? - - // MARK: Table - - var model: StrongTableModel = StrongTableModel() - - private let trustedNetworks = TrustedNetworksUI() - - // MARK: UIViewController - - deinit { - NotificationCenter.default.removeObserver(self) - } - - func setProfile(_ profile: ConnectionProfile?, reloadingViews: Bool = true) { - self.profile = profile - - if let profile = profile { - title = service.screenTitle(ProfileKey(profile)) - } else { - title = nil - } - navigationItem.rightBarButtonItem = (profile?.context == .host) ? itemEdit : nil - if reloadingViews { - reloadModel() - updateViewsIfNeeded() - } - } - - override func viewDidLoad() { - super.viewDidLoad() - - // fall back to active profile - if profile == nil { - setProfile(service.activeProfile) - } - if let providerProfile = profile as? ProviderConnectionProfile { - lastInfrastructureUpdate = InfrastructureFactory.shared.modificationDate(forName: providerProfile.name) - } - - navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem - navigationItem.leftItemsSupplementBackButton = true - - labelWelcome.text = L10n.Service.Welcome.message - labelWelcome.apply(.current) - - let nc = NotificationCenter.default - nc.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) - nc.addObserver(self, selector: #selector(vpnDidUpdate), name: VPN.didChangeStatus, object: nil) - nc.addObserver(self, selector: #selector(vpnDidUpdate), name: VPN.didReinstall, object: nil) - if #available(iOS 13, *) { - nc.addObserver(self, selector: #selector(intentDidUpdateService), name: IntentDispatcher.didUpdateService, object: nil) - } - nc.addObserver(self, selector: #selector(serviceDidUpdateDataCount(_:)), name: ConnectionService.didUpdateDataCount, object: nil) - nc.addObserver(self, selector: #selector(productManagerDidReloadReceipt), name: ProductManager.didReloadReceipt, object: nil) - nc.addObserver(self, selector: #selector(productManagerDidReviewPurchases), name: ProductManager.didReviewPurchases, object: nil) - - // run this no matter what - // XXX: convenient here vs AppDelegate for updating table - vpn.prepare() { - self.reloadModel() - self.updateViewsIfNeeded() - } - - updateViewsIfNeeded() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - hideProfileIfDeleted() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - clearSelection() - hideProfileIfDeleted() - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - guard let sid = segue.identifier, let segueType = StoryboardSegue.Main(rawValue: sid) else { - return - } - - let destination = segue.destination - - switch segueType { - case .accountSegueIdentifier: - let vc = destination as? AccountViewController - vc?.currentCredentials = service.credentials(for: uncheckedProfile) - vc?.usernamePlaceholder = (profile as? ProviderConnectionProfile)?.infrastructure.defaults.username - vc?.infrastructureName = (profile as? ProviderConnectionProfile)?.infrastructure.name - vc?.delegate = self - - case .providerPoolSegueIdentifier: - let vc = destination as? ProviderPoolViewController - vc?.setInfrastructure(uncheckedProviderProfile.infrastructure, currentPoolId: uncheckedProviderProfile.poolId) - vc?.favoriteGroupIds = uncheckedProviderProfile.favoriteGroupIds ?? [] - vc?.delegate = self - - case .endpointSegueIdentifier: - let vc = destination as? EndpointViewController - vc?.dataSource = profile - vc?.delegate = self - vc?.modificationDelegate = self - - case .providerPresetSegueIdentifier: - let infra = uncheckedProviderProfile.infrastructure - let presets: [InfrastructurePreset] = uncheckedProviderProfile.pool?.supportedPresetIds(in: uncheckedProviderProfile.infrastructure).map { - return infra.preset(for: $0)! - } ?? [] - - let vc = destination as? ProviderPresetViewController - vc?.presets = presets - vc?.currentPresetId = uncheckedProviderProfile.presetId - vc?.delegate = self - - case .hostParametersSegueIdentifier: - let vc = destination as? ConfigurationViewController - vc?.title = L10n.Service.Cells.Host.Parameters.caption - vc?.initialConfiguration = uncheckedHostProfile.parameters.sessionConfiguration - vc?.originalConfigurationURL = service.configurationURL(for: uncheckedHostProfile) - vc?.delegate = self - - case .networkSettingsSegueIdentifier: - let vc = destination as? NetworkSettingsViewController - vc?.title = L10n.NetworkSettings.title - vc?.profile = profile - - case .serverNetworkSegueIdentifier: - break - - case .debugLogSegueIdentifier: - break - } - } - - // MARK: Actions - - func hideProfileIfDeleted() { - guard let profile = profile else { - return - } - if !service.containsProfile(profile) { - setProfile(nil) - } - } - - // XXX: outlets can be nil here! - private func updateViewsIfNeeded() { - tableView?.reloadData() - viewWelcome?.isHidden = (profile != nil) - } - - private func activateProfile() { - service.activateProfile(uncheckedProfile) - vpn.disconnect { (error) in - self.reloadModel() - self.updateViewsIfNeeded() - } - } - - @IBAction private func renameProfile() { - let alert = UIAlertController.asAlert(L10n.Service.Alerts.Rename.title, nil) - alert.addTextField { (field) in - field.text = self.service.screenTitle(ProfileKey(self.uncheckedProfile)) - field.applyHostTitle(.current) - field.delegate = self - } - pendingRenameAction = alert.addPreferredAction(L10n.Global.ok) { - guard let newTitle = alert.textFields?.first?.text else { - return - } - self.confirmRenameCurrentProfile(to: newTitle) - } - alert.addCancelAction(L10n.Global.cancel) - pendingRenameAction?.isEnabled = false - present(alert, animated: true, completion: nil) - } - - private func confirmRenameCurrentProfile(to newTitle: String) { - guard let profile = profile else { - return - } - if let existingProfile = service.hostProfile(withTitle: newTitle) { - let alert = UIAlertController.asAlert(L10n.Service.Alerts.Rename.title, L10n.Wizards.Host.Alerts.Existing.message) - alert.addPreferredAction(L10n.Global.ok) { - self.doReplaceProfile(profile, to: newTitle, existingProfile: existingProfile) - } - alert.addCancelAction(L10n.Global.cancel) - present(alert, animated: true, completion: nil) - return - } - doRenameProfile(profile, to: newTitle) - } - - private func doReplaceProfile(_ profile: ConnectionProfile, to newTitle: String, existingProfile: ConnectionProfile) { - let wasActive = service.isActiveProfile(existingProfile) - service.removeProfile(ProfileKey(existingProfile)) - service.renameProfile(profile, to: newTitle) - if wasActive { - service.activateProfile(profile) - } - setProfile(profile, reloadingViews: true) - } - - private func doRenameProfile(_ profile: ConnectionProfile, to newTitle: String) { - service.renameProfile(profile, to: newTitle) - setProfile(profile, reloadingViews: false) - } - - private func toggleVpnService(cell: ToggleTableViewCell) { - if cell.isOn { - if #available(iOS 12, *) { - let title = service.screenTitle(ProfileKey(uncheckedProfile)) - IntentDispatcher.donateConnection(with: uncheckedProfile, title: title) - } - guard !service.needsCredentials(for: uncheckedProfile) else { - let alert = UIAlertController.asAlert( - L10n.Service.Sections.Vpn.header, - L10n.Service.Alerts.CredentialsNeeded.message - ) - alert.addCancelAction(L10n.Global.ok) { - cell.setOn(false, animated: true) - } - present(alert, animated: true, completion: nil) - return - } - vpn.reconnect { (error) in - guard error == nil else { - - // XXX: delay to avoid weird toggle state - delay { - cell.setOn(false, animated: true) - if error as? ApplicationError == .externalResources { - self.requireDownload() - } - } - return - } - self.reloadModel() - self.updateViewsIfNeeded() - } - } else { - if #available(iOS 12, *) { - IntentDispatcher.donateDisableVPN() - } - vpn.disconnect { (error) in - self.reloadModel() - self.updateViewsIfNeeded() - } - } - } - - private func confirmVpnReconnection() { - guard vpn.status == .disconnected else { - let alert = UIAlertController.asAlert( - L10n.Service.Cells.ConnectionStatus.caption, - L10n.Service.Alerts.ReconnectVpn.message - ) - alert.addPreferredAction(L10n.Global.ok) { - self.vpn.reconnect(completionHandler: nil) - } - alert.addCancelAction(L10n.Global.cancel) - present(alert, animated: true, completion: nil) - return - } - vpn.reconnect(completionHandler: nil) - } - - private func refreshProviderInfrastructure() { - let name = uncheckedProviderProfile.name - - let hud = HUD(view: view.window!) - let isUpdating = InfrastructureFactory.shared.update(name, notBeforeInterval: AppConstants.Services.minimumUpdateInterval) { (response, error) in - hud.hide() - guard let response = response else { - return - } - self.lastInfrastructureUpdate = response.1 - self.tableView.reloadData() - } - if !isUpdating { - hud.hide() - } - } - - private func toggleDisconnectsOnSleep(_ isOn: Bool) { - service.preferences.disconnectsOnSleep = !isOn - if vpn.isEnabled { - vpn.reinstall(completionHandler: nil) - } - } - - private func toggleResolvesHostname(_ isOn: Bool) { - service.preferences.resolvesHostname = isOn - if vpn.isEnabled { - guard vpn.status == .disconnected else { - confirmVpnReconnection() - return - } - vpn.reinstall(completionHandler: nil) - } - } - - private func trustMobileNetwork(cell: ToggleTableViewCell) { - do { - try ProductManager.shared.verifyEligible(forFeature: .trustedNetworks) - } catch ProductError.beta { - delay { - cell.setOn(false, animated: true) - } - presentBetaFeatureUnavailable("Trusted networks") - return - } catch { - delay { - cell.setOn(false, animated: true) - } - presentPurchaseScreen(forProduct: .trustedNetworks) - return - } - - if #available(iOS 12, *) { - IntentDispatcher.donateTrustCellularNetwork() - IntentDispatcher.donateUntrustCellularNetwork() - } - - trustedNetworks.setMobile(cell.isOn) - } - - private func trustCurrentWiFi() { - do { - try ProductManager.shared.verifyEligible(forFeature: .trustedNetworks) - } catch ProductError.beta { - presentBetaFeatureUnavailable("Trusted networks") - return - } catch { - presentPurchaseScreen(forProduct: .trustedNetworks) - return - } - - if #available(iOS 13, *) { - switch CLLocationManager.authorizationStatus() { - case .authorizedAlways, .authorizedWhenInUse: - break - - case .denied: - isPendingTrustedWiFi = false - let alert = UIAlertController.asAlert( - L10n.Service.Cells.TrustedAddWifi.caption, - L10n.Service.Alerts.Location.Message.denied - ) - alert.addCancelAction(L10n.Global.ok) - alert.addPreferredAction(L10n.Service.Alerts.Location.Button.settings) { - UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil) - } - present(alert, animated: true, completion: nil) - return - - default: - isPendingTrustedWiFi = true - locationManager.delegate = self - locationManager.requestWhenInUseAuthorization() - return - } - } - - if #available(iOS 12, *) { - IntentDispatcher.donateTrustCurrentNetwork() - IntentDispatcher.donateUntrustCurrentNetwork() - } - - let alert = UIAlertController.asAlert(L10n.Service.Sections.Trusted.header, nil) - alert.addTextField { (field) in - field.text = Utils.currentWifiNetworkName() ?? "" - field.applyWiFiTitle(.current) - field.delegate = self - } - alert.addCancelAction(L10n.Global.cancel) - alert.addPreferredAction(L10n.Service.Cells.TrustedAddWifi.caption) { - let ssid = alert.textFields?.first?.text?.stripped - guard let ssid = ssid, !ssid.isEmpty else { - return - } - self.trustedNetworks.addWifi(ssid) - } - present(alert, animated: true, completion: nil) - } - - private func toggleTrustWiFi(cell: ToggleTableViewCell, at row: Int) { - do { - try ProductManager.shared.verifyEligible(forFeature: .trustedNetworks) - } catch ProductError.beta { - delay { - cell.setOn(false, animated: true) - } - presentBetaFeatureUnavailable("Trusted networks") - return - } catch { - delay { - cell.setOn(false, animated: true) - } - presentPurchaseScreen(forProduct: .trustedNetworks) - return - } - - if cell.isOn { - trustedNetworks.enableWifi(at: row) - } else { - trustedNetworks.disableWifi(at: row) - } - } - - private func toggleTrustedConnectionPolicy(_ isOn: Bool, sender: ToggleTableViewCell) { - let completionHandler: () -> Void = { - self.uncheckedProfile.trustedNetworks.policy = isOn ? .disconnect : .ignore - if self.vpn.isEnabled { - self.vpn.reinstall(completionHandler: nil) - } - } - guard isOn else { - completionHandler() - return - } - guard vpn.isEnabled else { - completionHandler() - return - } - let alert = UIAlertController.asAlert( - L10n.Service.Sections.Trusted.header, - L10n.Service.Alerts.Trusted.WillDisconnectPolicy.message - ) - alert.addPreferredAction(L10n.Global.ok) { - completionHandler() - } - alert.addCancelAction(L10n.Global.cancel) { - sender.setOn(false, animated: true) - } - present(alert, animated: true, completion: nil) - } - - private func confirmPotentialTrustedDisconnection(at rowIndex: Int?, completionHandler: @escaping () -> Void) { - let alert = UIAlertController.asAlert( - L10n.Service.Sections.Trusted.header, - L10n.Service.Alerts.Trusted.WillDisconnectTrusted.message - ) - alert.addPreferredAction(L10n.Global.ok) { - completionHandler() - } - alert.addCancelAction(L10n.Global.cancel) { - guard let rowIndex = rowIndex else { - return - } - let indexPath = IndexPath(row: rowIndex, section: self.trustedSectionIndex) - let cell = self.tableView.cellForRow(at: indexPath) as? ToggleTableViewCell - cell?.setOn(false, animated: true) - } - present(alert, animated: true, completion: nil) - } - - private func testInternetConnectivity() { - let hud = HUD(view: view.window!) - Utils.checkConnectivityURL(AppConstants.Services.connectivityURL, timeout: AppConstants.Services.connectivityTimeout) { - hud.hide() - - let V = L10n.Service.Alerts.TestConnectivity.Messages.self - let alert = UIAlertController.asAlert( - L10n.Service.Alerts.TestConnectivity.title, - $0 ? V.success : V.failure - ) - alert.addCancelAction(L10n.Global.ok) - self.present(alert, animated: true, completion: nil) - } - } - -// private func displayDataCount() { -// guard vpn.isEnabled else { -// let alert = UIAlertController.asAlert( -// L10n.Service.Cells.DataCount.caption, -// L10n.Service.Alerts.DataCount.Messages.notAvailable -// ) -// alert.addCancelAction(L10n.Global.ok) -// present(alert, animated: true, completion: nil) -// return -// } -// -// vpn.requestBytesCount { -// let message: String -// if let count = $0 { -// message = L10n.Service.Alerts.DataCount.Messages.current(Int(count.0), Int(count.1)) -// } else { -// message = L10n.Service.Alerts.DataCount.Messages.notAvailable -// } -// let alert = UIAlertController.asAlert( -// L10n.Service.Cells.DataCount.caption, -// message -// ) -// alert.addCancelAction(L10n.Global.ok) -// self.present(alert, animated: true, completion: nil) -// } -// } - - private func discloseServerConfiguration() { - let caption = L10n.Service.Cells.ServerConfiguration.caption - tryRequestServerConfiguration(withCaption: caption) { [weak self] in - let vc = StoryboardScene.Main.configurationIdentifier.instantiate() - vc.title = caption - vc.initialConfiguration = $0 - vc.isServerPushed = true - self?.navigationController?.pushViewController(vc, animated: true) - } - } - - private func discloseServerNetwork() { - let caption = L10n.Service.Cells.ServerNetwork.caption - tryRequestServerConfiguration(withCaption: caption) { [weak self] in - let vc = StoryboardScene.Main.serverNetworkViewController.instantiate() - vc.title = caption - vc.configuration = $0 - self?.navigationController?.pushViewController(vc, animated: true) - } - } - - private func tryRequestServerConfiguration(withCaption caption: String, completionHandler: @escaping (OpenVPN.Configuration) -> Void) { - vpn.requestServerConfiguration { [weak self] in - guard let cfg = $0 as? OpenVPN.Configuration else { - let alert = UIAlertController.asAlert( - caption, - L10n.Service.Alerts.Configuration.disconnected - ) - alert.addCancelAction(L10n.Global.ok) - self?.present(alert, animated: true, completion: nil) - return - } - completionHandler(cfg) - } - } - - private func togglePrivateDataMasking(cell: ToggleTableViewCell) { - let handler = { - TransientStore.masksPrivateData = cell.isOn - self.service.baseConfiguration = TransientStore.baseVPNConfiguration.build() - } - - guard vpn.status == .disconnected else { - let alert = UIAlertController.asAlert( - L10n.Service.Cells.MasksPrivateData.caption, - L10n.Service.Alerts.MasksPrivateData.Messages.mustReconnect - ) - alert.addDestructiveAction(L10n.Service.Alerts.Buttons.reconnect) { - handler() - self.shouldDeleteLogOnDisconnection = true - self.vpn.reconnect(completionHandler: nil) - } - alert.addCancelAction(L10n.Global.cancel) { - cell.setOn(!cell.isOn, animated: true) - } - present(alert, animated: true, completion: nil) - return - } - - handler() - service.eraseVpnLog() - shouldDeleteLogOnDisconnection = false - } - - private func reportConnectivityIssue() { - let issue = Issue(debugLog: true, profile: uncheckedProfile) - IssueReporter.shared.present(in: self, withIssue: issue) - } - - private func requireDownload() { - guard let providerProfile = profile as? ProviderConnectionProfile else { - return - } - guard let downloadURL = AppConstants.URLs.externalResources[providerProfile.name] else { - return - } - - let alert = UIAlertController.asAlert( - L10n.Service.Alerts.Download.title, - L10n.Service.Alerts.Download.message(providerProfile.name) - ) - alert.addCancelAction(L10n.Global.cancel) - alert.addPreferredAction(L10n.Global.ok) { - self.confirmDownload(URL(string: downloadURL)!) - } - present(alert, animated: true, completion: nil) - } - - private func confirmDownload(_ url: URL) { - _ = downloader.download(url: url, in: view) { (url, error) in - self.handleDownloadedProviderResources(url: url, error: error) - } - } - - private func handleDownloadedProviderResources(url: URL?, error: Error?) { - guard let url = url else { - let alert = UIAlertController.asAlert( - L10n.Service.Alerts.Download.title, - L10n.Service.Alerts.Download.failed(error?.localizedDescription ?? "") - ) - alert.addCancelAction(L10n.Global.ok) - present(alert, animated: true, completion: nil) - return - } - - let hud = HUD(view: view.window!, label: L10n.Service.Alerts.Download.Hud.extracting) - hud.show() - uncheckedProviderProfile.name.importExternalResources(from: url) { - hud.hide() - } - } - - // MARK: Notifications - - @objc private func vpnDidUpdate() { - reloadVpnStatus() - - guard let status = vpn.status else { - return - } - log.debug("VPN.status: \(status)") - switch status { - case .connected: - Reviewer.shared.reportEvent() - - case .disconnected: - if shouldDeleteLogOnDisconnection { - service.eraseVpnLog() - shouldDeleteLogOnDisconnection = false - } - - default: - break - } - } - - @objc private func intentDidUpdateService() { - setProfile(service.activeProfile) - } - - @objc private func applicationDidBecomeActive() { - reloadModel() - updateViewsIfNeeded() - } - - @objc private func serviceDidUpdateDataCount(_ notification: Notification) { - guard let dataCount = notification.userInfo?[ConnectionService.NotificationKeys.dataCount] as? (Int, Int) else { - return - } - refreshDataCount(dataCount) - } - - @objc private func productManagerDidReloadReceipt() { - reloadModel() - tableView.reloadData() - } - - @objc private func productManagerDidReviewPurchases() { - hideProfileIfDeleted() - } -} - -// MARK: - - -extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, ToggleTableViewCellDelegate { - enum SectionType { - case vpn - - case authentication - - case hostProfile - - case configuration - - case providerInfrastructure - - case vpnResolvesHostname - - case vpnSurvivesSleep - - case trusted - - case trustedPolicy - - case diagnostics - - case feedback - } - - enum RowType: Int { - case useProfile - - case vpnService - - case connectionStatus - - case reconnect - - case account - - case endpoint - - case providerPool - - case providerPreset - - case providerRefresh - - case hostParameters - - case networkSettings - - case vpnResolvesHostname - - case vpnSurvivesSleep - - case trustedMobile - - case trustedWiFi - - case trustedAddCurrentWiFi - - case trustedPolicy - - case testConnectivity - - case dataCount - - case serverConfiguration - - case serverNetwork - - case debugLog - - case masksPrivateData - - case faq - - case reportIssue - } - - private var trustedSectionIndex: Int { - return model.index(ofSection: .trusted) - } - - private var statusIndexPath: IndexPath? { - return model.indexPath(forRow: .connectionStatus, ofSection: .vpn) - } - - private var dataCountIndexPath: IndexPath? { - return model.indexPath(forRow: .dataCount, ofSection: .diagnostics) - } - - private var endpointIndexPath: IndexPath { - guard let ip = model.indexPath(forRow: .endpoint, ofSection: .configuration) else { - fatalError("Could not locate endpointIndexPath") - } - return ip - } - - private var providerPresetIndexPath: IndexPath { - guard let ip = model.indexPath(forRow: .providerPreset, ofSection: .configuration) else { - fatalError("Could not locate presetIndexPath") - } - return ip - } - - private func mappedTrustedNetworksRow(_ from: TrustedNetworksUI.RowType) -> RowType { - switch from { - case .trustsMobile: - return .trustedMobile - - case .trustedWiFi: - return .trustedWiFi - - case .addCurrentWiFi: - return .trustedAddCurrentWiFi - } - } - - func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - let rows = model.rows(forSection: section) - if rows.contains(.providerRefresh), let date = lastInfrastructureUpdate { - return L10n.Service.Sections.ProviderInfrastructure.footer(date.timestamp) - } - return model.footer(forSection: section) - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return model.headerHeight(for: section) - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let row = model.row(at: indexPath) - switch row { - case .useProfile: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.Service.Cells.UseProfile.caption - return cell - - case .vpnService: - guard service.isActiveProfile(uncheckedProfile) else { - fatalError("Do not show vpnService in non-active profile") - } - - let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self) - cell.caption = L10n.Service.Cells.VpnService.caption - cell.isOn = vpn.isEnabled - return cell - - case .connectionStatus: - guard service.isActiveProfile(uncheckedProfile) else { - fatalError("Do not show connectionStatus in non-active profile") - } - - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyVPN(.current, with: vpn.isEnabled ? vpn.status : nil, error: service.vpnLastError) - cell.leftText = L10n.Service.Cells.ConnectionStatus.caption - cell.accessoryType = .none - cell.isTappable = false - return cell - - case .reconnect: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.Service.Cells.Reconnect.caption - cell.accessoryType = .none - cell.isTappable = !service.needsCredentials(for: uncheckedProfile) && vpn.isEnabled - return cell - - // shared cells - - case .account: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Account.title - cell.rightText = profile?.username - return cell - - case .endpoint: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Endpoint.title - - let V = L10n.Global.Values.self - if let provider = profile as? ProviderConnectionProfile { - cell.rightText = provider.usesCustomEndpoint ? V.manual : V.automatic - } else { - cell.rightText = profile?.mainAddress - } - return cell - - case .networkSettings: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.NetworkSettings.title - return cell - - // provider cells - - case .providerPool: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Service.Cells.Provider.Pool.caption - cell.rightText = uncheckedProviderProfile.pool?.localizedId - return cell - - case .providerPreset: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Service.Cells.Provider.Preset.caption - cell.rightText = uncheckedProviderProfile.preset?.name // XXX: localize? - return cell - - case .providerRefresh: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.Service.Cells.Provider.Refresh.caption - return cell - - // host cells - - case .hostParameters: - let parameters = uncheckedHostProfile.parameters - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Service.Cells.Host.Parameters.caption - if !parameters.sessionConfiguration.fallbackCipher.embedsDigest { - cell.rightText = "\(parameters.sessionConfiguration.fallbackCipher.genericName) / \(parameters.sessionConfiguration.fallbackDigest.genericName)" - } else { - cell.rightText = parameters.sessionConfiguration.fallbackCipher.genericName - } - return cell - - // VPN preferences - - case .vpnResolvesHostname: - let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self) - cell.caption = L10n.Service.Cells.VpnResolvesHostname.caption - cell.isOn = service.preferences.resolvesHostname - return cell - - case .vpnSurvivesSleep: - let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self) - cell.caption = L10n.Service.Cells.VpnSurvivesSleep.caption - cell.isOn = !service.preferences.disconnectsOnSleep - return cell - - case .trustedMobile: - let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self) - cell.caption = L10n.Service.Cells.TrustedMobile.caption - cell.isOn = uncheckedProfile.trustedNetworks.includesMobile - return cell - - case .trustedWiFi: - let wifi = trustedNetworks.wifi(at: indexPath.row) - let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self) - cell.caption = wifi.0 - cell.isOn = wifi.1 - return cell - - case .trustedAddCurrentWiFi: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.applyAction(.current) - cell.leftText = L10n.Service.Cells.TrustedAddWifi.caption - return cell - - case .trustedPolicy: - let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self) - cell.caption = L10n.Service.Cells.TrustedPolicy.caption - cell.isOn = (uncheckedProfile.trustedNetworks.policy == .disconnect) - return cell - - // diagnostics - - case .testConnectivity: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Service.Cells.TestConnectivity.caption - return cell - - case .dataCount: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Service.Cells.DataCount.caption - if let count = currentDataCount, vpn.status == .connected { - let down = count.0.dataUnitDescription - let up = count.1.dataUnitDescription - cell.rightText = "↓\(down) / ↑\(up)" - } else { - cell.rightText = L10n.Service.Cells.DataCount.none - } - cell.accessoryType = .none - cell.isTappable = false - return cell - - case .serverConfiguration: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Service.Cells.ServerConfiguration.caption - return cell - - case .serverNetwork: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Service.Cells.ServerNetwork.caption - return cell - - case .debugLog: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Service.Cells.DebugLog.caption - return cell - - case .masksPrivateData: - let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self) - cell.caption = L10n.Service.Cells.MasksPrivateData.caption - cell.isOn = TransientStore.masksPrivateData - return cell - - // feedback - - case .faq: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.About.Cells.Faq.caption - return cell - - case .reportIssue: - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - cell.leftText = L10n.Service.Cells.ReportIssue.caption - return cell - } - } - -// func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// cell.isSelected = (indexPath == lastSelectedIndexPath) -// } - - // MARK: Actions - - func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { - guard let cell = tableView.cellForRow(at: indexPath) else { - return nil - } - if let settingCell = cell as? SettingTableViewCell { - guard settingCell.isTappable else { - return nil - } - } - guard handle(row: model.row(at: indexPath), cell: cell) else { - return nil - } - return indexPath - } - - func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - return model.row(at: indexPath) == .trustedWiFi - } - - func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - precondition(indexPath.section == model.index(ofSection: .trusted)) - trustedNetworks.removeWifi(at: indexPath.row) - } - - func toggleCell(_ cell: ToggleTableViewCell, didToggleToValue value: Bool) { - guard let item = RowType(rawValue: cell.tag) else { - return - } - handle(row: item, cell: cell) - } - - // true if enters subscreen - private func handle(row: RowType, cell: UITableViewCell) -> Bool { - switch row { - case .useProfile: - activateProfile() - - case .reconnect: - confirmVpnReconnection() - - case .account: - perform(segue: StoryboardSegue.Main.accountSegueIdentifier, sender: cell) - return true - - case .endpoint: - perform(segue: StoryboardSegue.Main.endpointSegueIdentifier, sender: cell) - return true - - case .providerPool: - perform(segue: StoryboardSegue.Main.providerPoolSegueIdentifier, sender: cell) - return true - - case .providerPreset: - perform(segue: StoryboardSegue.Main.providerPresetSegueIdentifier, sender: cell) - return true - - case .providerRefresh: - refreshProviderInfrastructure() - return false - - case .hostParameters: - perform(segue: StoryboardSegue.Main.hostParametersSegueIdentifier, sender: cell) - return true - - case .networkSettings: - perform(segue: StoryboardSegue.Main.networkSettingsSegueIdentifier, sender: cell) - return true - - case .trustedAddCurrentWiFi: - trustCurrentWiFi() - - case .testConnectivity: - testInternetConnectivity() - -// case .dataCount: -// displayDataCount() - - case .serverConfiguration: - discloseServerConfiguration() - - case .serverNetwork: - discloseServerNetwork() - - case .debugLog: - perform(segue: StoryboardSegue.Main.debugLogSegueIdentifier, sender: cell) - return true - - case .faq: - visitURL(AppConstants.URLs.faq) - - case .reportIssue: - reportConnectivityIssue() - - default: - break - } - return false - } - - private func handle(row: RowType, cell: ToggleTableViewCell) { - switch row { - case .vpnService: - toggleVpnService(cell: cell) - - case .vpnResolvesHostname: - toggleResolvesHostname(cell.isOn) - - case .vpnSurvivesSleep: - toggleDisconnectsOnSleep(cell.isOn) - - case .trustedMobile: - trustMobileNetwork(cell: cell) - - case .trustedWiFi: - guard let indexPath = tableView.indexPath(for: cell) else { - return - } - toggleTrustWiFi(cell: cell, at: indexPath.row) - - case .trustedPolicy: - toggleTrustedConnectionPolicy(cell.isOn, sender: cell) - - case .masksPrivateData: - togglePrivateDataMasking(cell: cell) - - default: - break - } - } - - // MARK: Updates - - func reloadModel() { - model.clear() - - guard let profile = profile else { - return - } -// assert(profile != nil, "Profile not set") - - let isActiveProfile = service.isActiveProfile(profile) - let isProvider = (profile as? ProviderConnectionProfile) != nil - - // sections - model.add(.vpn) - if isProvider { - model.add(.authentication) - } - model.add(.configuration) - if isProvider { - model.add(.providerInfrastructure) - } - if isActiveProfile { - if isProvider { - model.add(.vpnResolvesHostname) - } - model.add(.vpnSurvivesSleep) - model.add(.trusted) - model.add(.trustedPolicy) - model.add(.diagnostics) - model.add(.feedback) - } - - // headers - model.setHeader(L10n.Service.Sections.Vpn.header, forSection: .vpn) - if isProvider { - model.setHeader(L10n.Service.Sections.Configuration.header, forSection: .authentication) - } else { - model.setHeader(L10n.Service.Sections.Configuration.header, forSection: .configuration) - } - if isActiveProfile { - if isProvider { - model.setHeader("", forSection: .vpnResolvesHostname) - model.setHeader("", forSection: .vpnSurvivesSleep) - } - model.setHeader(L10n.Service.Sections.Trusted.header, forSection: .trusted) - model.setHeader(L10n.Service.Sections.Diagnostics.header, forSection: .diagnostics) - model.setHeader(L10n.Organizer.Sections.Feedback.header, forSection: .feedback) - } - - // footers - if isActiveProfile { - model.setFooter(L10n.Service.Sections.Vpn.footer, forSection: .vpn) - if isProvider { - model.setFooter(L10n.Service.Sections.VpnResolvesHostname.footer, forSection: .vpnResolvesHostname) - } - model.setFooter(L10n.Service.Sections.VpnSurvivesSleep.footer, forSection: .vpnSurvivesSleep) - model.setFooter(L10n.Service.Sections.Trusted.footer, forSection: .trustedPolicy) - model.setFooter(L10n.Service.Sections.Diagnostics.footer, forSection: .diagnostics) - } - - // rows - if isActiveProfile { - var rows: [RowType] = [.vpnService, .connectionStatus] - if vpn.isEnabled { - rows.append(.reconnect) - } - model.set(rows, forSection: .vpn) - } else { - model.set([.useProfile], forSection: .vpn) - } - if isProvider { - model.set([.account], forSection: .authentication) - model.set([.providerPool, .endpoint, .providerPreset, .networkSettings], forSection: .configuration) - model.set([.providerRefresh], forSection: .providerInfrastructure) - } else { - model.set([.account, .endpoint, .hostParameters, .networkSettings], forSection: .configuration) - } - if isActiveProfile { - if isProvider { - model.set([.vpnResolvesHostname], forSection: .vpnResolvesHostname) - } - model.set([.vpnSurvivesSleep], forSection: .vpnSurvivesSleep) - model.set([.trustedPolicy], forSection: .trustedPolicy) - model.set([.dataCount, .serverConfiguration, .serverNetwork, .debugLog, .masksPrivateData], forSection: .diagnostics) - - var feedbackRows: [RowType] = [.faq] - if ProductManager.shared.isEligibleForFeedback() { - feedbackRows.append(.reportIssue) - } - model.set(feedbackRows, forSection: .feedback) - } - - trustedNetworks.delegate = self - trustedNetworks.load(from: uncheckedProfile.trustedNetworks) - model.set(trustedNetworks.rows.map { mappedTrustedNetworksRow($0) }, forSection: .trusted) - } - - private func reloadVpnStatus() { - guard let profile = profile else { - return - } - guard service.isActiveProfile(profile) else { - return - } - var ips: [IndexPath] = [] - guard let statusIndexPath = statusIndexPath else { - return - } - ips.append(statusIndexPath) - if let dataCountIndexPath = dataCountIndexPath { - currentDataCount = service.vpnDataCount - ips.append(dataCountIndexPath) - } - tableView.reloadRows(at: ips, with: .none) - } - - private func refreshDataCount(_ dataCount: (Int, Int)?) { - currentDataCount = dataCount - guard let dataCountIndexPath = dataCountIndexPath else { - return - } - tableView.reloadRows(at: [dataCountIndexPath], with: .none) - } - - func reloadSelectedRow(andRowsAt indexPaths: [IndexPath]? = nil) { - guard let selectedIP = tableView.indexPathForSelectedRow else { - return - } - var outdatedIPs = [selectedIP] - if let otherIPs = indexPaths { - outdatedIPs.append(contentsOf: otherIPs) - } - tableView.reloadRows(at: outdatedIPs, with: .none) - tableView.selectRow(at: selectedIP, animated: false, scrollPosition: .none) - } - - func clearSelection() { - guard let selected = tableView.indexPathForSelectedRow else { - return - } - tableView.deselectRow(at: selected, animated: true) - } -} - -// MARK: - - -extension ServiceViewController: UITextFieldDelegate { - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { -// guard string.rangeOfCharacter(from: CharacterSet.filename.inverted) == nil else { -// return false -// } - if let text = textField.text { - let replacement = (text as NSString).replacingCharacters(in: range, with: string) - pendingRenameAction?.isEnabled = (replacement != uncheckedProfile.id) - } - return true - } - - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - return true - } -} - -// MARK: - - -extension ServiceViewController: TrustedNetworksUIDelegate { - func trustedNetworksCouldDisconnect(_: TrustedNetworksUI) -> Bool { - return (uncheckedProfile.trustedNetworks.policy == .disconnect) && (vpn.status != .disconnected) - } - - func trustedNetworksShouldConfirmDisconnection(_: TrustedNetworksUI, triggeredAt rowIndex: Int, completionHandler: @escaping () -> Void) { - confirmPotentialTrustedDisconnection(at: rowIndex, completionHandler: completionHandler) - } - - func trustedNetworks(_: TrustedNetworksUI, shouldInsertWifiAt rowIndex: Int) { - model.set(trustedNetworks.rows.map { mappedTrustedNetworksRow($0) }, forSection: .trusted) - tableView.insertRows(at: [IndexPath(row: rowIndex, section: trustedSectionIndex)], with: .bottom) - } - - func trustedNetworks(_: TrustedNetworksUI, shouldReloadWifiAt rowIndex: Int, isTrusted: Bool) { - let genericCell = tableView.cellForRow(at: IndexPath(row: rowIndex, section: trustedSectionIndex)) - guard let cell = genericCell as? ToggleTableViewCell else { - fatalError("Not a trusted Wi-Fi cell (\(type(of: genericCell)) != ToggleTableViewCell)") - } - guard isTrusted != cell.isOn else { - return - } - cell.setOn(isTrusted, animated: true) - } - - func trustedNetworks(_: TrustedNetworksUI, shouldDeleteWifiAt rowIndex: Int) { - model.set(trustedNetworks.rows.map { mappedTrustedNetworksRow($0) }, forSection: .trusted) - tableView.deleteRows(at: [IndexPath(row: rowIndex, section: trustedSectionIndex)], with: .top) - } - - func trustedNetworksShouldReinstall(_: TrustedNetworksUI) { - uncheckedProfile.trustedNetworks.includesMobile = trustedNetworks.trustsMobileNetwork - uncheckedProfile.trustedNetworks.includedWiFis = trustedNetworks.trustedWifis - if vpn.isEnabled { - vpn.reinstall(completionHandler: nil) - } - } -} - -// MARK: - - -extension ServiceViewController: ConfigurationModificationDelegate { - func configuration(didUpdate newConfiguration: OpenVPN.Configuration) { - if let hostProfile = profile as? HostConnectionProfile { - var builder = hostProfile.parameters.builder() - builder.sessionConfiguration = newConfiguration - hostProfile.parameters = builder.build() - } - reloadSelectedRow() - } - - func configurationShouldReinstall() { - vpn.reinstallIfEnabled() - } -} - -extension ServiceViewController: AccountViewControllerDelegate { - func accountController(_ vc: AccountViewController, didEnterCredentials credentials: Credentials) { - } - - func accountControllerDidComplete(_ accountVC: AccountViewController) { - navigationController?.popViewController(animated: true) - - let credentials = accountVC.credentials - guard credentials != service.credentials(for: uncheckedProfile) else { - return - } - try? service.setCredentials(credentials, for: uncheckedProfile) - reloadSelectedRow() - vpn.reinstallIfEnabled() - } -} - -extension ServiceViewController: EndpointViewControllerDelegate { - func endpointController(_: EndpointViewController, didUpdateWithNewAddress newAddress: String?, newProtocol: EndpointProtocol?) { - profile?.customAddress = newAddress - profile?.customProtocol = newProtocol - - reloadSelectedRow() - } -} - -extension ServiceViewController: ProviderPoolViewControllerDelegate { - func providerPoolController(_ vc: ProviderPoolViewController, didSelectPool pool: Pool) { - navigationController?.popToViewController(self, animated: true) - - guard pool.id != uncheckedProviderProfile.poolId else { - return - } - uncheckedProviderProfile.poolId = pool.id - - var extraReloadedRows = [endpointIndexPath] - - // fall back to a supported preset and reload preset row too - let supportedPresets = pool.supportedPresetIds(in: uncheckedProviderProfile.infrastructure) - if let presetId = uncheckedProviderProfile.preset?.id, !supportedPresets.contains(presetId), - let fallback = supportedPresets.first { - - if fallback != uncheckedProviderProfile.presetId { - extraReloadedRows.append(providerPresetIndexPath) - } - uncheckedProviderProfile.presetId = fallback - } - - reloadSelectedRow(andRowsAt: extraReloadedRows) - vpn.reinstallIfEnabled() - - if #available(iOS 12, *) { - let title = service.screenTitle(forProviderName: uncheckedProviderProfile.name) - IntentDispatcher.donateConnection(with: uncheckedProviderProfile, title: title) - } - } - - func providerPoolController(_: ProviderPoolViewController, didUpdateFavoriteGroups favoriteGroupIds: [String]) { - uncheckedProviderProfile.favoriteGroupIds = favoriteGroupIds - } -} - -extension ServiceViewController: ProviderPresetViewControllerDelegate { - func providerPresetController(_: ProviderPresetViewController, didSelectPreset preset: InfrastructurePreset) { - navigationController?.popViewController(animated: true) - - guard preset.id != uncheckedProviderProfile.presetId else { - return - } - uncheckedProviderProfile.presetId = preset.id - reloadSelectedRow(andRowsAt: [endpointIndexPath]) - vpn.reinstallIfEnabled() - } -} - -extension ServiceViewController: CLLocationManagerDelegate { - func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { - guard isPendingTrustedWiFi else { - return - } - isPendingTrustedWiFi = false - trustCurrentWiFi() - } -} - -// MARK: - - -private extension ServiceViewController { - private var uncheckedProfile: ConnectionProfile { - guard let profile = profile else { - fatalError("Expected non-nil profile here") - } - return profile - } - - private var uncheckedProviderProfile: ProviderConnectionProfile { - guard let profile = profile as? ProviderConnectionProfile else { - fatalError("Expected ProviderConnectionProfile (found: \(type(of: self.profile)))") - } - return profile - } - - private var uncheckedHostProfile: HostConnectionProfile { - guard let profile = profile as? HostConnectionProfile else { - fatalError("Expected HostConnectionProfile (found: \(type(of: self.profile)))") - } - return profile - } -} diff --git a/Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsAddViewController.swift b/Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsAddViewController.swift deleted file mode 100644 index 4189660a..00000000 --- a/Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsAddViewController.swift +++ /dev/null @@ -1,208 +0,0 @@ -// -// ShortcutsAddViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 3/18/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import Intents -import PassepartoutCore -import ConvenienceUI - -@available(iOS 12, *) -class ShortcutsAddViewController: UITableViewController, StrongTableHost { - weak var delegate: ShortcutsIntentDelegate? - - // MARK: StrongTableModel - - let model: StrongTableModel = { - let model: StrongTableModel = StrongTableModel() - model.add(.vpn) - model.add(.wifi) - model.add(.cellular) - model.set([.connect, .enableVPN, .disableVPN], forSection: .vpn) - model.set([.trustCurrentWiFi, .untrustCurrentWiFi], forSection: .wifi) - model.set([.trustCellular, .untrustCellular], forSection: .cellular) - model.setHeader(L10n.Shortcuts.Add.Sections.Vpn.header, forSection: .vpn) - model.setHeader(L10n.Shortcuts.Add.Sections.Wifi.header, forSection: .wifi) - model.setHeader(L10n.Shortcuts.Add.Sections.Cellular.header, forSection: .cellular) - return model - }() - - func reloadModel() { - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Shortcuts.Add.title - } - - // MARK: UITableViewController - - enum SectionType { - case vpn - - case wifi - - case cellular - } - - enum RowType { - case connect // host or provider+location - - case enableVPN - - case disableVPN - - case trustCurrentWiFi - - case untrustCurrentWiFi - - case trustCellular - - case untrustCellular - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - switch model.row(at: indexPath) { - case .connect: - cell.leftText = L10n.Shortcuts.Add.Cells.Connect.caption - - case .enableVPN: - cell.leftText = L10n.Shortcuts.Add.Cells.EnableVpn.caption - - case .disableVPN: - cell.leftText = L10n.Shortcuts.Add.Cells.DisableVpn.caption - - case .trustCurrentWiFi: - cell.leftText = L10n.Shortcuts.Add.Cells.TrustCurrentWifi.caption - - case .untrustCurrentWiFi: - cell.leftText = L10n.Shortcuts.Add.Cells.UntrustCurrentWifi.caption - - case .trustCellular: - cell.leftText = L10n.Shortcuts.Add.Cells.TrustCellular.caption - - case .untrustCellular: - cell.leftText = L10n.Shortcuts.Add.Cells.UntrustCellular.caption - } - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - switch model.row(at: indexPath) { - case .connect: - addConnect() - - case .enableVPN: - addEnable() - - case .disableVPN: - addDisable() - - case .trustCurrentWiFi: - addTrustWiFi() - - case .untrustCurrentWiFi: - addUntrustWiFi() - - case .trustCellular: - addTrustCellular() - - case .untrustCellular: - addUntrustCellular() - } - } - - // MARK: Actions - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if let vc = segue.destination as? ShortcutsConnectToViewController { - vc.delegate = delegate - } - } - - private func addConnect() { - guard TransientStore.shared.service.hasProfiles() else { - let alert = UIAlertController.asAlert( - L10n.Shortcuts.Add.Cells.Connect.caption, - L10n.Shortcuts.Add.Alerts.NoProfiles.message - ) - alert.addAction(L10n.Global.ok) { - if let ip = self.tableView.indexPathForSelectedRow { - self.tableView.deselectRow(at: ip, animated: true) - } - } - present(alert, animated: true, completion: nil) - return - } - perform(segue: StoryboardSegue.Shortcuts.connectToSegueIdentifier) - } - - private func addEnable() { - addShortcut(with: IntentDispatcher.intentEnable()) - } - - private func addDisable() { - addShortcut(with: IntentDispatcher.intentDisable()) - } - - private func addTrustWiFi() { - addShortcut(with: IntentDispatcher.intentTrustWiFi()) - } - - private func addUntrustWiFi() { - addShortcut(with: IntentDispatcher.intentUntrustWiFi()) - } - - private func addTrustCellular() { - addShortcut(with: IntentDispatcher.intentTrustCellular()) - } - - private func addUntrustCellular() { - addShortcut(with: IntentDispatcher.intentUntrustCellular()) - } - - private func addShortcut(with intent: INIntent) { - delegate?.shortcutsDidSelectIntent(intent: intent) - } - - @IBAction private func close() { - dismiss(animated: true, completion: nil) - } -} diff --git a/Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsConnectToViewController.swift b/Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsConnectToViewController.swift deleted file mode 100644 index dc012aae..00000000 --- a/Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsConnectToViewController.swift +++ /dev/null @@ -1,186 +0,0 @@ -// -// ShortcutsConnectToViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 3/18/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import Intents -import IntentsUI -import PassepartoutCore -import ConvenienceUI - -@available(iOS 12, *) -class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewControllerDelegate, StrongTableHost { - private let service = TransientStore.shared.service - - private var providers: [InfrastructureName] = [] - - private var hosts: [String] = [] - - private var selectedProfile: ConnectionProfile? - - weak var delegate: ShortcutsIntentDelegate? - - // MARK: StrongTableHost - - let model: StrongTableModel = { - let model: StrongTableModel = StrongTableModel() - model.setHeader(L10n.Organizer.Sections.Providers.header, forSection: .providers) - model.setHeader(L10n.Organizer.Sections.Hosts.header, forSection: .hosts) - return model - }() - - func reloadModel() { - providers = service.sortedProviderNames() - hosts = service.sortedHostIds() - - if !providers.isEmpty { - model.add(.providers) - model.set(.providerShortcut, count: providers.count, forSection: .providers) - } - if !hosts.isEmpty { - model.add(.hosts) - model.set(.hostShortcut, count: hosts.count, forSection: .hosts) - } - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Shortcuts.Add.Cells.Connect.caption - reloadModel() - } - - override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { - guard identifier == StoryboardSegue.Shortcuts.pickLocationSegueIdentifier.rawValue else { - return false - } - guard let _ = selectedProfile as? ProviderConnectionProfile else { - return false - } - return true - } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - guard let vc = segue.destination as? ProviderPoolViewController else { - return - } - guard let provider = selectedProfile as? ProviderConnectionProfile else { - return - } - vc.setInfrastructure(provider.infrastructure, currentPoolId: nil) - vc.delegate = self - } - - // MARK: UITableViewController - - enum SectionType { - case providers - - case hosts - } - - enum RowType { - case providerShortcut - - case hostShortcut - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - switch model.row(at: indexPath) { - case .providerShortcut: - let name = providers[indexPath.row] - if let metadata = InfrastructureFactory.shared.metadata(forName: name) { - cell.leftText = metadata.description - } else { - cell.leftText = name - } - - case .hostShortcut: - let id = hosts[indexPath.row] - cell.leftText = service.screenTitle(forHostId: id) - } - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - switch model.row(at: indexPath) { - case .providerShortcut: - selectedProfile = service.profile(withContext: .provider, id: providers[indexPath.row]) - pickProviderLocation() - - case .hostShortcut: - selectedProfile = service.profile(withContext: .host, id: hosts[indexPath.row]) - addConnect() - } - } - - // MARK: Actions - - private func addConnect() { - guard let host = selectedProfile as? HostConnectionProfile else { - fatalError("Not a HostConnectionProfile") - } - let title = service.screenTitle(forHostId: host.id) - addShortcut(with: IntentDispatcher.intentConnect(profile: host, title: title)) - } - - private func addMoveToLocation(pool: Pool) { - guard let provider = selectedProfile as? ProviderConnectionProfile else { - fatalError("Not a ProviderConnectionProfile") - } - addShortcut(with: IntentDispatcher.intentMoveTo(profile: provider, pool: pool)) - } - - private func addShortcut(with intent: INIntent) { - delegate?.shortcutsDidSelectIntent(intent: intent) - } - - private func pickProviderLocation() { - perform(segue: StoryboardSegue.Shortcuts.pickLocationSegueIdentifier) - } - - // MARK: ProviderPoolViewControllerDelegate - - func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool) { - addMoveToLocation(pool: pool) - } - - func providerPoolController(_: ProviderPoolViewController, didUpdateFavoriteGroups favoriteGroupIds: [String]) { - } -} diff --git a/Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsViewController.swift b/Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsViewController.swift deleted file mode 100644 index 4b58449b..00000000 --- a/Passepartout/App/iOS/Scenes/Shortcuts/ShortcutsViewController.swift +++ /dev/null @@ -1,279 +0,0 @@ -// -// ShortcutsViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 3/27/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import UIKit -import Intents -import IntentsUI -import PassepartoutCore -import ConvenienceUI - -@available(iOS 12, *) -protocol ShortcutsIntentDelegate: AnyObject { - func shortcutsDidSelectIntent(intent: INIntent) -} - -@available(iOS 12, *) -private struct ShortcutWrapper: Comparable { - let phrase: String - - let intentDescription: String? - - let original: INVoiceShortcut - - static func from(_ vs: INVoiceShortcut) -> ShortcutWrapper { - return ShortcutWrapper( - phrase: vs.invocationPhrase, - intentDescription: vs.shortcut.intent?.intentDescription, - original: vs - ) - } - - // MARK: Equatable - - static func ==(lhs: ShortcutWrapper, rhs: ShortcutWrapper) -> Bool { - return lhs.phrase == rhs.phrase - } - - // MARK: Comparable - - static func <(lhs: ShortcutWrapper, rhs: ShortcutWrapper) -> Bool { - return lhs.phrase < rhs.phrase - } -} - -@available(iOS 12, *) -class ShortcutsViewController: UITableViewController, INUIAddVoiceShortcutViewControllerDelegate, INUIEditVoiceShortcutViewControllerDelegate, ShortcutsIntentDelegate, StrongTableHost { - private var wrappers: [ShortcutWrapper]? - - private var pendingShortcut: INShortcut? - - private var editedIndexPath: IndexPath? - - // MARK: StrongTableModel - - let model: StrongTableModel = { - let model: StrongTableModel = StrongTableModel() - model.add(.all) - model.setHeader(L10n.Shortcuts.Edit.Sections.All.header, forSection: .all) - model.set([], forSection: .all) - return model - }() - - func reloadModel() { - var rows = [RowType](repeating: .shortcut, count: wrappers?.count ?? 0) - rows.append(.addShortcut) - model.set(rows, forSection: .all) - } - - // MARK: UIViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Organizer.Cells.SiriShortcuts.caption - - INVoiceShortcutCenter.shared.getAllVoiceShortcuts { [weak self] (shortcuts, error) in - DispatchQueue.main.async { - guard let shortcuts = shortcuts else { - self?.handleShortcutsFetchError(error) - return - } - self?.handleShortcuts(shortcuts) - } - } - } - - // MARK: Actions - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if let nav = segue.destination as? UINavigationController, let vc = nav.topViewController as? ShortcutsAddViewController { - vc.delegate = self - } - } - - private func addShortcut() { - perform(segue: StoryboardSegue.Shortcuts.shortcutAddSegueIdentifier) - } - - private func handleShortcutsFetchError(_ error: Error?) { - - // TODO: really show it? -// let alert = UIAlertController.asAlert( -// title, -// L10n.Shortcuts.Edit.message(error?.localizedDescription ?? "") -// ) -// alert.addCancelAction(L10n.Global.ok) { -// self.close() -// } -// present(alert, animated: true, completion: nil) - } - - private func handleShortcuts(_ shortcuts: [INVoiceShortcut]) { - wrappers = shortcuts.map { ShortcutWrapper.from($0) } - wrappers?.sort() - reloadModel() - tableView.reloadData() - } - - private func finishAddingPendingShortcut() { - guard let shortcut = pendingShortcut else { - return - } - if let ip = tableView.indexPathForSelectedRow { - tableView.deselectRow(at: ip, animated: true) - } - pendingShortcut = nil - let vc = INUIAddVoiceShortcutViewController(shortcut: shortcut) - vc.applyModalPresentation(.current) - vc.delegate = self - present(vc, animated: true, completion: nil) - } - - @IBAction private func close() { - dismiss(animated: true, completion: nil) - } - - // MARK: UITableViewController - - enum SectionType { - case all - } - - enum RowType { - case shortcut - - case addShortcut - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return model.numberOfSections - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return model.header(forSection: section) - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.numberOfRows(forSection: section) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = Cells.setting.dequeue(from: tableView, for: indexPath) - switch model.row(at: indexPath) { - case .shortcut: - guard let wrapper = wrappers?[indexPath.row] else { - break - } - cell.apply(.current) - cell.leftText = wrapper.phrase - cell.rightText = wrapper.intentDescription - - case .addShortcut: - cell.applyAction(.current) - cell.leftText = L10n.Shortcuts.Edit.Cells.AddShortcut.caption - cell.accessoryType = .none - cell.isTappable = true - } - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - switch model.row(at: indexPath) { - case .shortcut: - guard let wrapper = wrappers?[indexPath.row] else { - break - } - let vc = INUIEditVoiceShortcutViewController(voiceShortcut: wrapper.original) - vc.applyModalPresentation(.current) - vc.delegate = self - editedIndexPath = indexPath - present(vc, animated: true, completion: nil) - - case .addShortcut: - addShortcut() - } - } - - // MARK: ShortcutsIntentDelegate - - func shortcutsDidSelectIntent(intent: INIntent) { - pendingShortcut = INShortcut(intent: intent) - dismiss(animated: true) { - self.finishAddingPendingShortcut() - } - } - - // MARK: INUIAddVoiceShortcutViewControllerDelegate - - func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) { - guard let voiceShortcut = voiceShortcut else { - dismiss(animated: true, completion: nil) - return - } - - wrappers?.append(ShortcutWrapper.from(voiceShortcut)) - wrappers?.sort() - reloadModel() - tableView.reloadData() - - dismiss(animated: true, completion: nil) - } - - func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) { - dismiss(animated: true, completion: nil) - } - - // MARK: INUIEditVoiceShortcutViewControllerDelegate - - func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) { - guard let indexPath = editedIndexPath, let voiceShortcut = voiceShortcut else { - return - } - editedIndexPath = nil - wrappers?[indexPath.row] = ShortcutWrapper.from(voiceShortcut) - wrappers?.sort() - tableView.reloadData() - - dismiss(animated: true) - } - - func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) { - guard let indexPath = editedIndexPath else { - return - } - editedIndexPath = nil - wrappers?.remove(at: indexPath.row) - reloadModel() - - dismiss(animated: true) { - self.tableView.deleteRows(at: [indexPath], with: .automatic) - } - } - - func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) { - editedIndexPath = nil - dismiss(animated: true, completion: nil) - } -} diff --git a/Passepartout/App/iOS/ViewModels/AddHostViewModel.swift b/Passepartout/App/iOS/ViewModels/AddHostViewModel.swift new file mode 100644 index 00000000..f5436e27 --- /dev/null +++ b/Passepartout/App/iOS/ViewModels/AddHostViewModel.swift @@ -0,0 +1,105 @@ +// +// AddHostViewModel.swift +// Passepartout +// +// Created by Davide De Rosa on 2/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore +import TunnelKitOpenVPN +import TunnelKitWireGuard + +extension AddHostView { + struct ViewModel { + var profileName = "" + + private(set) var requiresPassphrase = false + + var encryptionPassphrase = "" + + var processedProfile: Profile = .placeholder + + private(set) var errorMessage: String? + + var isAskingOverwrite = false + + @MainActor + mutating func processURL( + _ url: URL, + with profileManager: ProfileManager, + replacingExisting: Bool, + deletingURLOnSuccess: Bool + ) { + profileName = profileName.stripped + guard !profileName.isEmpty else { + return + } + + if !replacingExisting { + guard !profileManager.isExistingProfile(withName: profileName) else { + isAskingOverwrite = true + return + } + } + + errorMessage = nil + do { + let profile = try profileManager.profile( + withHeader: .init(name: profileName), + fromURL: url, + passphrase: encryptionPassphrase + ) + processedProfile = profile + + if deletingURLOnSuccess { + try? FileManager.default.removeItem(at: url) + } + } catch { + switch error { + case OpenVPN.ConfigurationError.encryptionPassphrase, + OpenVPN.ConfigurationError.unableToDecrypt: + + requiresPassphrase = true + + default: + requiresPassphrase = false + } + setMessage(forParsingError: error) + } + } + + @MainActor + mutating func addProcessedProfile(to profileManager: ProfileManager) -> Bool { + guard !processedProfile.isPlaceholder else { + assertionFailure("Saving profile without processing first?") + return false + } + errorMessage = nil + profileManager.saveProfile(processedProfile, isActive: nil) + return true + } + + private mutating func setMessage(forParsingError error: Error) { + errorMessage = error.localizedVPNParsingDescription + } + } +} diff --git a/Passepartout/App/iOS/ViewModels/AddProviderViewModel.swift b/Passepartout/App/iOS/ViewModels/AddProviderViewModel.swift new file mode 100644 index 00000000..4f773df7 --- /dev/null +++ b/Passepartout/App/iOS/ViewModels/AddProviderViewModel.swift @@ -0,0 +1,170 @@ +// +// AddProviderViewModel.swift +// Passepartout +// +// Created by Davide De Rosa on 3/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore + +extension AddProviderView { + class ViewModel: ObservableObject { + enum PendingOperation { + case index + + case provider(ProviderName) + } + + // local copy for animations + @Published var providers: [ProviderMetadata] = [] + + @Published private(set) var newProviders: [ProviderMetadata] = [] + + @Published var selectedVPNProtocol: VPNProtocolType = .openVPN + + @Published var selectedProvider: ProviderMetadata? + + @Published var pendingProfile: Profile = .placeholder + + @Published private(set) var pendingOperation: PendingOperation? + + @Published var isPaywallPresented = false + + @Published private(set) var errorMessage: String? + + @MainActor + func selectProvider(_ metadata: ProviderMetadata, _ providerManager: ProviderManager) { + errorMessage = nil + guard let server = providerManager.anyDefaultServer( + metadata.name, + vpnProtocol: selectedVPNProtocol + ) else { + Task { + await selectProviderAfterFetchingInfrastructure(metadata, providerManager) + } + return + } + doSelectProvider(metadata, server) + } + + @MainActor + private func selectProviderAfterFetchingInfrastructure(_ metadata: ProviderMetadata, _ providerManager: ProviderManager) async { + errorMessage = nil + pendingOperation = .provider(metadata.name) + Task { + do { + try await providerManager.fetchProviderPublisher( + withName: metadata.name, + vpnProtocol: pendingProfile.currentVPNProtocol, + priority: .remoteThenBundle + ).async() + + if let server = providerManager.anyDefaultServer( + metadata.name, + vpnProtocol: selectedVPNProtocol + ) { + doSelectProvider(metadata, server) + } else { + errorMessage = L10n.AddProfile.Provider.Errors.noDefaultServer + } + } catch { + errorMessage = error.localizedDescription + } + pendingOperation = nil + } + } + + private func doSelectProvider(_ metadata: ProviderMetadata, _ server: ProviderServer) { + pendingProfile = Profile(metadata, server: server) + selectedProvider = metadata + } + + @MainActor + func updateIndex(_ providerManager: ProviderManager) { + errorMessage = nil + pendingOperation = .index + Task { + do { + try await providerManager.fetchProvidersIndexPublisher( + priority: .remoteThenBundle + ).async() + + newProviders = providerManager.allProviders() + } catch { + errorMessage = error.localizedDescription + } + pendingOperation = nil + } + } + + func presentPaywall() { + isPaywallPresented = true + } + } +} + +extension AddProviderView.NameView { + struct ViewModel { + var profileName = "" + + var isAskingOverwrite = false + + private(set) var errorMessage: String? + + @MainActor + mutating func addProfile( + _ profile: Profile, + to profileManager: ProfileManager, + replacingExisting: Bool + ) -> Profile? { + profileName = profileName.stripped + guard !profileName.isEmpty else { + return nil + } + + if !replacingExisting { + guard !profileManager.isExistingProfile(withName: profileName) else { + isAskingOverwrite = true + return nil + } + } + + errorMessage = nil + + let finalProfile = profile.renamed(to: profileName) + profileManager.saveProfile(finalProfile, isActive: nil) + return finalProfile + } + + private mutating func setMessage(forError error: Error) { + errorMessage = error.localizedDescription + } + } +} + +extension Profile { + func renamed(to newName: String) -> Profile { + var profile = self + profile.header.name = newName + return profile + } +} diff --git a/Passepartout/App/iOS/Views/AboutView.swift b/Passepartout/App/iOS/Views/AboutView.swift new file mode 100644 index 00000000..f3d8f179 --- /dev/null +++ b/Passepartout/App/iOS/Views/AboutView.swift @@ -0,0 +1,143 @@ +// +// AboutView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct AboutView: View { + enum ModalType: Identifiable { + case share([Any]) + + // XXX: alert ids + var id: Int { + return 1 + } + } + + @State private var modalType: ModalType? + + private let versionString = Constants.Global.appVersionString + + private let readmeURL = Constants.URLs.readme + + private let changelogURL = Constants.URLs.iOS.changelog + + private let homeURL = Constants.URLs.website + + private let faqURL = Constants.URLs.faq + + private let disclaimerURL = Constants.URLs.disclaimer + + private let privacyURL = Constants.URLs.privacyPolicy + + private let alternativeToURL = Constants.URLs.alternativeTo + + private let shareMessage = L10n.Global.Messages.share + + var body: some View { + List { + infoSubview + githubSubview + webSubview + shareSubview + }.themeSecondaryView() + .navigationTitle(L10n.About.title) + .sheet(item: $modalType) { + switch $0 { + case .share(let items): + ActivityView(activityItems: items) + } + } + } + + private var infoSubview: some View { + Section { + NavigationLink { + VersionView() + } label: { + Text(L10n.Version.title) + .withTrailingText(versionString) + } + NavigationLink(L10n.Credits.title) { + CreditsView() + } + } + } + + private var githubSubview: some View { + Section( + header: Text(Unlocalized.About.github) + ) { + Button(Unlocalized.About.readme) { + URL.openURL(readmeURL) + } + Button(Unlocalized.About.changelog) { + URL.openURL(changelogURL) + } + } + } + + private var webSubview: some View { + Section( + header: Text(L10n.About.Sections.Web.header) + ) { + Button(L10n.About.Items.Website.caption) { + URL.openURL(readmeURL) + } + Button(Unlocalized.About.faq) { + URL.openURL(faqURL) + } + Button(L10n.About.Items.Disclaimer.caption) { + URL.openURL(disclaimerURL) + } + Button(L10n.About.Items.PrivacyPolicy.caption) { + URL.openURL(privacyURL) + } + } + } + + private var shareSubview: some View { + Section( + header: Text(L10n.About.Sections.Share.header) + ) { + Button(L10n.About.Items.ShareTwitter.caption, action: shareOnTwitter) + Button(L10n.About.Items.ShareGeneric.caption, action: shareWithFriend) + Button(Unlocalized.About.alternativeTo, action: shareAlternativeTo) + } + } + + private func shareOnTwitter() { + let url = Unlocalized.Social.twitterIntent(withMessage: shareMessage) + URL.openURL(url) + } + + private func shareWithFriend() { + let shareMessage = "\(shareMessage) \(Constants.URLs.website)" + modalType = .share([shareMessage]) + } + + private func shareAlternativeTo() { + URL.openURL(alternativeToURL) + } +} diff --git a/Passepartout/App/iOS/Views/AccountView.swift b/Passepartout/App/iOS/Views/AccountView.swift new file mode 100644 index 00000000..edcdbdf7 --- /dev/null +++ b/Passepartout/App/iOS/Views/AccountView.swift @@ -0,0 +1,132 @@ +// +// AccountView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/11/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension Profile.Account: CopySavingModel { +} + +struct AccountView: View { + @ObservedObject private var providerManager: ProviderManager + + private let providerName: ProviderName? + + private let vpnProtocol: VPNProtocolType + + @Binding private var account: Profile.Account + + private let saveAnyway: Bool + + private let onSave: (() -> Void)? + + @State private var liveAccount = Profile.Account() + + @State private var isPasswordRevealed = false + + init( + providerName: ProviderName?, + vpnProtocol: VPNProtocolType, + account: Binding, + saveAnyway: Bool = false, + onSave: (() -> Void)? = nil + ) { + providerManager = .shared + self.providerName = providerName + self.vpnProtocol = vpnProtocol + _account = account + self.saveAnyway = saveAnyway + self.onSave = onSave + } + + var body: some View { + List { + Section( + footer: metadata?.localizedGuidanceString.map { + Text($0) + } + ) { + TextField(usernamePlaceholder ?? L10n.Account.Items.Username.placeholder, text: $liveAccount.username) + .textContentType(.username) + .disableAutocorrection(true) + .autocapitalization(.none) + .keyboardType(.emailAddress) + .withLeadingText(L10n.Account.Items.Username.caption) + + RevealingSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password) { + themeConceilImage.asSystemImage + .foregroundColor(themeAccentColor) + } revealImage: { + themeRevealImage.asSystemImage + .foregroundColor(themeAccentColor) + }.textContentType(.password) + .disableAutocorrection(true) + .autocapitalization(.none) + .withLeadingText(L10n.Account.Items.Password.caption) + } + if vpnProtocol == .openVPN { + metadata?.openVPNGuidanceURL.map { guidanceURL in + Section { + Button(L10n.Account.Items.OpenGuide.caption) { + openGuidanceURL(guidanceURL) + } + } + } + } + }.navigationTitle(L10n.Account.title) + .toolbar { + CopySavingButton( + original: $account, + copy: $liveAccount, + mapping: \.stripped, + label: themeSaveButtonLabel, + saveAnyway: saveAnyway, + onSave: onSave + ) + } + } + + private func openGuidanceURL(_ url: URL) { + URL.openURL(url) + } +} + +// MARK: Provider + +extension AccountView { + private var usernamePlaceholder: String? { + guard let name = providerName else { + return nil + } + return providerManager.defaultUsername(name, vpnProtocol: vpnProtocol) + } + + private var metadata: ProviderMetadata? { + guard let name = providerName else { + return nil + } + return providerManager.provider(withName: name) + } +} diff --git a/Passepartout/App/iOS/Views/AddProfile/AddHostView.swift b/Passepartout/App/iOS/Views/AddProfile/AddHostView.swift new file mode 100644 index 00000000..259a7630 --- /dev/null +++ b/Passepartout/App/iOS/Views/AddProfile/AddHostView.swift @@ -0,0 +1,194 @@ +// +// AddHostView.swift +// Passepartout +// +// Created by Davide De Rosa on 3/18/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore +import TunnelKitOpenVPN +import TunnelKitWireGuard + +struct AddHostView: View { + @ObservedObject private var profileManager: ProfileManager + + private let url: URL + + private let deletingURLOnSuccess: Bool + + private let bindings: AddProfileView.Bindings + + @State private var viewModel = ViewModel() + + @State private var isEnteringCredentials = false + + init( + url: URL, + deletingURLOnSuccess: Bool, + bindings: AddProfileView.Bindings + ) { + profileManager = .shared + self.url = url + self.deletingURLOnSuccess = deletingURLOnSuccess + self.bindings = bindings + } + + var body: some View { + ZStack { + List { + if viewModel.processedProfile.isPlaceholder { + processingView + } else { + completeView + } + } + + // hidden + NavigationLink("", isActive: $isEnteringCredentials) { + AddProfileView.AccountWrapperView( + profile: $viewModel.processedProfile, + bindings: bindings + ) + } + }.themeSecondaryView() + .navigationTitle(L10n.AddProfile.Shared.title) + .toolbar(content: toolbar) + .alert(isPresented: $viewModel.isAskingOverwrite, content: alertOverwriteExistingProfile) + .onAppear(perform: requestResourcePermissions) + .onDisappear(perform: dropResourcePermissions) + } + + private func toolbar() -> some View { + Button(nextString) { + if !viewModel.processedProfile.isPlaceholder { + saveProfile() + } else { + processProfile(replacingExisting: false) + } + } + } + + @ViewBuilder + private var processingView: some View { + AddProfileView.ProfileNameSection( + profileName: $viewModel.profileName, + initialName: url.normalizedFilename, + errorMessage: viewModel.errorMessage + ) { + processProfile(replacingExisting: false) + } + if viewModel.requiresPassphrase { + encryptionSection + } + let headers = profileManager.headers.sorted() + if !headers.isEmpty { + AddProfileView.ExistingProfilesSection( + headers: headers, + profileName: $viewModel.profileName + ) + } + } + + private var encryptionSection: some View { + Section( + header: Text(L10n.Global.Strings.encryption) + ) { + SecureField(L10n.AddProfile.Host.Sections.Encryption.footer, text: $viewModel.encryptionPassphrase) { + processProfile(replacingExisting: false) + } + } + } + + private var completeView: some View { + Section( + footer: themeErrorMessage(viewModel.errorMessage) + ) { + Text(L10n.Global.Strings.name) + .withTrailingText(viewModel.processedProfile.header.name) + Text(Unlocalized.Network.url) + .withTrailingText(url.lastPathComponent) + viewModel.processedProfile.vpnProtocols.first.map { + Text(L10n.Global.Strings.protocol) + .withTrailingText($0.description) + } + } + } + + private var nextString: String { + if !viewModel.processedProfile.isPlaceholder { + return viewModel.processedProfile.requiresCredentials ? L10n.Global.Strings.next : L10n.Global.Strings.save + } else { + return L10n.Global.Strings.next + } + } + + private func requestResourcePermissions() { + _ = url.startAccessingSecurityScopedResource() + } + + private func dropResourcePermissions() { + url.stopAccessingSecurityScopedResource() + } + + private func alertOverwriteExistingProfile() -> Alert { + return Alert( + title: Text(L10n.AddProfile.Shared.title), + message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message), + primaryButton: .destructive(Text(L10n.Global.Strings.ok)) { + + // XXX: delay withAnimation() to not overlap with alert dismiss animation + Task { + processProfile(replacingExisting: true) + } + }, + secondaryButton: .cancel(Text(L10n.Global.Strings.cancel)) + ) + } + + private func processProfile(replacingExisting: Bool) { + withAnimation { + viewModel.processURL( + url, + with: profileManager, + replacingExisting: replacingExisting, + deletingURLOnSuccess: deletingURLOnSuccess + ) + } + } + + private func saveProfile() { + let result = withAnimation { + viewModel.addProcessedProfile(to: profileManager) + } + guard result else { + return + } + + let profile = viewModel.processedProfile + if profile.requiresCredentials { + isEnteringCredentials = true + } else { + bindings.isPresented = false + profileManager.didCreateProfile.send(profile) + } + } +} diff --git a/Passepartout/App/iOS/Views/AddProfile/AddProfileView.swift b/Passepartout/App/iOS/Views/AddProfile/AddProfileView.swift new file mode 100644 index 00000000..a23fa1c0 --- /dev/null +++ b/Passepartout/App/iOS/Views/AddProfile/AddProfileView.swift @@ -0,0 +1,116 @@ +// +// AddProfileView.swift +// Passepartout +// +// Created by Davide De Rosa on 3/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +enum AddProfileView { + struct Bindings { + @Binding var isPresented: Bool + } + + struct ProfileNameSection: View { + @Binding var profileName: String + + let initialName: String + + let errorMessage: String? + + let onCommit: () -> Void + + var body: some View { + Section( + header: Text(L10n.Global.Strings.name), + footer: themeErrorMessage(errorMessage) + ) { + TextField(L10n.Global.Placeholders.profileName, text: $profileName, onCommit: onCommit) + .onAppear { + // XXX: this is reset on the way back, but: + // host: there is no back button after processing profile + // host/provider: back button is hidden after going to credentials + profileName = initialName + } + } + } + } + + struct ExistingProfilesSection: View { + let headers: [Profile.Header] + + @Binding var profileName: String + + var body: some View { + Section( + header: Text(L10n.AddProfile.Shared.Views.Existing.header) + ) { + ForEach(headers, content: existingProfileButton) + } + } + + private func existingProfileButton(_ header: Profile.Header) -> some View { + Button(header.name) { + profileName = header.name + }.themeLongText() + } + } + + struct AccountWrapperView: View { + @ObservedObject private var profileManager: ProfileManager + + @Binding private var profile: Profile + + private let bindings: AddProfileView.Bindings + + @State private var account = Profile.Account() + + init( + profile: Binding, + bindings: AddProfileView.Bindings + ) { + profileManager = .shared + _profile = profile + self.bindings = bindings + } + + var body: some View { + AccountView( + providerName: profile.header.providerName, + vpnProtocol: profile.currentVPNProtocol, + account: $account, + saveAnyway: true, + onSave: { + bindings.isPresented = false + } + ).navigationBarBackButtonHidden(true) + .onDisappear(perform: saveAccount) + } + + private func saveAccount() { + profile.account = account + profileManager.saveProfile(profile, isActive: nil) + profileManager.didCreateProfile.send(profile) + } + } +} diff --git a/Passepartout/App/iOS/Views/AddProfile/AddProviderView+Name.swift b/Passepartout/App/iOS/Views/AddProfile/AddProviderView+Name.swift new file mode 100644 index 00000000..042825d0 --- /dev/null +++ b/Passepartout/App/iOS/Views/AddProfile/AddProviderView+Name.swift @@ -0,0 +1,129 @@ +// +// AddProviderView+Name.swift +// Passepartout +// +// Created by Davide De Rosa on 3/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension AddProviderView { + struct NameView: View { + @ObservedObject private var profileManager: ProfileManager + + @Binding private var profile: Profile + + private let providerMetadata: ProviderMetadata + + private let bindings: AddProfileView.Bindings + + @State private var viewModel = ViewModel() + + @State private var isEnteringCredentials = false + + init( + profile: Binding, + providerMetadata: ProviderMetadata, + bindings: AddProfileView.Bindings + ) { + profileManager = .shared + _profile = profile + self.providerMetadata = providerMetadata + self.bindings = bindings + } + + var body: some View { + ZStack { + List { + AddProfileView.ProfileNameSection( + profileName: $viewModel.profileName, + initialName: providerMetadata.fullName, + errorMessage: viewModel.errorMessage + ) { + saveProfile(replacingExisting: false) + } + let headers = profileManager.headers.sorted() + if !headers.isEmpty { + AddProfileView.ExistingProfilesSection( + headers: headers, + profileName: $viewModel.profileName + ) + } + } + + // hidden + NavigationLink("", isActive: $isEnteringCredentials) { + AddProfileView.AccountWrapperView( + profile: $profile, + bindings: bindings + ) + } + }.navigationTitle(providerMetadata.fullName) + .toolbar(content: toolbar) + .alert(isPresented: $viewModel.isAskingOverwrite, content: alertOverwriteExistingProfile) + } + + private func toolbar() -> some View { + Button { + saveProfile(replacingExisting: false) + } label: { + themeSaveButtonLabel() + } + } + + private func alertOverwriteExistingProfile() -> Alert { + return Alert( + title: Text(L10n.AddProfile.Shared.title), + message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message), + primaryButton: .destructive(Text(L10n.Global.Strings.ok)) { + + // XXX: delay withAnimation() to not overlap with alert dismiss animation + Task { + saveProfile(replacingExisting: true) + } + }, + secondaryButton: .cancel(Text(L10n.Global.Strings.cancel)) + ) + } + + private func saveProfile(replacingExisting: Bool) { + let addedProfile = withAnimation { + viewModel.addProfile( + profile, + to: profileManager, + replacingExisting: replacingExisting + ) + } + guard let addedProfile = addedProfile else { + return + } + profile = addedProfile + + if profile.requiresCredentials { + isEnteringCredentials = true + } else { + bindings.isPresented = false + profileManager.didCreateProfile.send(profile) + } + } + } +} diff --git a/Passepartout/App/iOS/Views/AddProfile/AddProviderView.swift b/Passepartout/App/iOS/Views/AddProfile/AddProviderView.swift new file mode 100644 index 00000000..8778babb --- /dev/null +++ b/Passepartout/App/iOS/Views/AddProfile/AddProviderView.swift @@ -0,0 +1,181 @@ +// +// AddProviderView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/10/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +struct AddProviderView: View { + @ObservedObject private var providerManager: ProviderManager + + @ObservedObject private var productManager: ProductManager + + private let bindings: AddProfileView.Bindings + + @StateObject private var viewModel = ViewModel() + + init(bindings: AddProfileView.Bindings) { + providerManager = .shared + productManager = .shared + self.bindings = bindings + } + + private var availableVPNProtocols: [VPNProtocolType] { + var protos: Set = [] + viewModel.providers.forEach { + $0.supportedVPNProtocols.forEach { + protos.insert($0) + } + } + return protos.sorted() + } + + private func isFetchingProvider(_ name: ProviderName) -> Bool { + if case .provider(name) = viewModel.pendingOperation { + return true + } + return false + } + + private var isUpdatingIndex: Bool { + if case .index = viewModel.pendingOperation { + return true + } + return false + } + + var body: some View { + ZStack { + ScrollViewReader { scrollProxy in + List { + mainSection + if !viewModel.providers.isEmpty { + providersSection + } + }.onChange(of: viewModel.errorMessage) { + onErrorMessage($0, scrollProxy) + }.disabled(viewModel.pendingOperation != nil) + } + + // hidden + ForEach(viewModel.providers, id: \.navigationId, content: providerNavigationLink) + }.themeSecondaryView() + .navigationTitle(L10n.AddProfile.Shared.title) + .sheet(isPresented: $viewModel.isPaywallPresented) { + NavigationView { + PaywallView() + }.themeGlobal() + }.onAppear { + refreshProviders() + }.onChange(of: viewModel.newProviders) { newValue in + withAnimation { + refreshProviders(newValue) + } + } + } + + private var mainSection: some View { + Section( + footer: Text(L10n.AddProfile.Provider.Sections.Vpn.footer) + ) { + let protos = availableVPNProtocols + if !protos.isEmpty { + themeTextPicker( + L10n.Global.Strings.protocol, + selection: $viewModel.selectedVPNProtocol, + values: protos, + description: \.description + ) + } + updateListButton + } + } + + private var providersSection: some View { + Section( + footer: themeErrorMessage(viewModel.errorMessage) + ) { + ForEach(viewModel.providers, content: providerRow) + } + } + + private func providerRow(_ metadata: ProviderMetadata) -> some View { + Button { + presentOrPurchaseProvider(metadata) + } label: { + Label(metadata.description, image: themeAssetsProviderImage(metadata.name)) + }.withTrailingProgress(when: isFetchingProvider(metadata.name)) + } + + private func providerNavigationLink(_ metadata: ProviderMetadata) -> some View { + NavigationLink("", tag: metadata, selection: $viewModel.selectedProvider) { + NameView( + profile: $viewModel.pendingProfile, + providerMetadata: metadata, + bindings: bindings + ) + } + } + + private var updateListButton: some View { + Button(L10n.AddProfile.Provider.Items.updateList) { + viewModel.updateIndex(providerManager) + }.withTrailingProgress(when: isUpdatingIndex) + } + + private func refreshProviders(_ newProviders: [ProviderMetadata]? = nil) { + viewModel.providers = (newProviders ?? providerManager.allProviders()) + .filter { + $0.supportedVPNProtocols.contains(viewModel.selectedVPNProtocol) + }.sorted() + } + + // eligibility: select or purchase provider + private func presentOrPurchaseProvider(_ metadata: ProviderMetadata) { + if productManager.isEligible(forProvider: metadata.name) { + viewModel.selectProvider(metadata, providerManager) + } else { + viewModel.presentPaywall() + } + } + + private func onErrorMessage(_ message: String?, _ scrollProxy: ScrollViewProxy) { + guard let _ = message else { + return + } + scrollToErrorMessage(scrollProxy) + } +} + +extension AddProviderView { + private func scrollToErrorMessage(_ proxy: ScrollViewProxy) { + proxy.maybeScrollTo(viewModel.providers.last?.id, animated: true) + } +} + +private extension ProviderMetadata { + var navigationId: String { + return "navigation.\(name)" + } +} diff --git a/Passepartout/App/iOS/Views/CreditsView.swift b/Passepartout/App/iOS/Views/CreditsView.swift new file mode 100644 index 00000000..88a23425 --- /dev/null +++ b/Passepartout/App/iOS/Views/CreditsView.swift @@ -0,0 +1,39 @@ +// +// CreditsView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct CreditsView: View { + var body: some View { + GenericCreditsView( + licensesHeader: nil,//L10n.Credits.Sections.Licenses.header, + noticesHeader: nil,//L10n.Credits.Sections.Notices.header, + translationsHeader: L10n.Global.Strings.translations, + licenses: Unlocalized.Credits.licenses, + notices: Unlocalized.Credits.notices, + translations: Unlocalized.Translations.translators + ).navigationTitle(L10n.Credits.title) + } +} diff --git a/Passepartout/App/iOS/Views/DebugLogView.swift b/Passepartout/App/iOS/Views/DebugLogView.swift new file mode 100644 index 00000000..947f5e77 --- /dev/null +++ b/Passepartout/App/iOS/Views/DebugLogView.swift @@ -0,0 +1,131 @@ +// +// DebugLogView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import Combine +import PassepartoutCore + +struct DebugLogView: View { + private let url: URL + + private let timer: AnyPublisher + + @State private var logLines: [String] = [] + + @State private var isSharing = false + + private let maxBytes = UInt64(Constants.Log.tunnelLogMaxBytes) + + private let appName = Constants.Global.appName + + private let appVersion = Constants.Global.appVersionString + + private let shareFilename = Unlocalized.Issues.Filenames.debugLog + + init(url: URL, updateInterval: TimeInterval) { + self.url = url + timer = Timer.TimerPublisher(interval: updateInterval, runLoop: .main, mode: .common) + .autoconnect() + .eraseToAnyPublisher() + } + + var body: some View { + ScrollViewReader { scrollProxy in + ScrollView(showsIndicators: true) { + contentView + }.toolbar(content: toolbar) + .onAppear { + refreshLog(scrollingToLatestWith: scrollProxy) + }.onReceive(timer, perform: refreshLog) + }.sheet(isPresented: $isSharing, content: sharingActivityView) + .navigationTitle(L10n.DebugLog.title) + .themeDebugLogFont() + .edgesIgnoringSafeArea([.leading, .trailing]) + } + + private func toolbar() -> some View { + Button(action: shareDebugLog) { + themeShareImage.asSystemImage + }.replacedWithProgress(when: isSharing) + .disabled(logLines.isEmpty) + } + + private var contentView: some View { + LazyVStack { + ForEach(logLines.indices, id: \.self) { + Text(logLines[$0]) + .frame(maxWidth: .infinity, alignment: .leading) + } + }//.padding() + // FIXME: layout, a slight padding would be nice, but it glitches on first touch + } + + private func refreshLog(scrollingToLatestWith scrollProxy: ScrollViewProxy?) { + logLines = url.trailingLines(bytes: maxBytes) + if let scrollProxy = scrollProxy { + scrollToLatestUpdate(scrollProxy) + } + } + + private func refreshLog(_: Date) { + refreshLog(scrollingToLatestWith: nil) + } + + private func shareDebugLog() { + guard !logLines.isEmpty else { + assertionFailure("Log is empty, why could it share?") + return + } + isSharing = true + } +} + +extension DebugLogView { + private func sharingActivityView() -> some View { + ActivityView(activityItems: sharingItems) + } + + private var sharingItems: [Any] { + let raw = logLines.joined(separator: "\n") + let data = DebugLog(content: raw).decoratedData(appName, appVersion) + + let path = NSTemporaryDirectory().appending(shareFilename) + let url = URL(fileURLWithPath: path) + do { + try data.write(to: url) + return [url] + } catch { + // highly unlikely to happen + assertionFailure("Unable to save temporary debug log file: \(error)") + return [] + } + } +} + +extension DebugLogView { + private func scrollToLatestUpdate(_ proxy: ScrollViewProxy) { + proxy.maybeScrollTo(logLines.count - 1, anchor: .bottomLeading) + } +} diff --git a/Passepartout/App/iOS/Views/DiagnosticsView+OpenVPN.swift b/Passepartout/App/iOS/Views/DiagnosticsView+OpenVPN.swift new file mode 100644 index 00000000..a21a55b8 --- /dev/null +++ b/Passepartout/App/iOS/Views/DiagnosticsView+OpenVPN.swift @@ -0,0 +1,193 @@ +// +// DiagnosticsView+OpenVPN.swift +// Passepartout +// +// Created by Davide De Rosa on 3/11/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore +import TunnelKitOpenVPN + +extension DiagnosticsView { + struct OpenVPNView: View { + enum AlertType: Int, Identifiable { + case emailNotConfigured + + var id: Int { + return rawValue + } + } + + @ObservedObject private var appManager: AppManager + + @ObservedObject private var providerManager: ProviderManager + + @ObservedObject private var vpnManager: VPNManager + + @ObservedObject private var currentVPNState: VPNManager.ObservableState + + @ObservedObject private var productManager: ProductManager + + private let providerName: ProviderName? + + private var isEligibleForFeedback: Bool { + productManager.isEligibleForFeedback() + } + + @State private var isReportingIssue = false + + @State private var alertType: AlertType? + + private let vpnProtocol: VPNProtocolType = .openVPN + + private let logUpdateInterval = Constants.Log.tunnelLogRefreshInterval + + init(providerName: ProviderName?) { + appManager = .shared + providerManager = .shared + vpnManager = .shared + currentVPNState = .shared + productManager = .shared + self.providerName = providerName + } + + var body: some View { + List { + serverConfigurationSection + debugLogSection + + // eligibility: to report a connectivity issue + if isEligibleForFeedback { + issueReporterSection + } + }.sheet(isPresented: $isReportingIssue, content: reportIssueView) + .alert(item: $alertType, content: presentedAlert) + } + + private func presentedAlert(_ alertType: AlertType) -> Alert { + switch alertType { + case .emailNotConfigured: + return Alert( + title: Text(L10n.ReportIssue.Alert.title), + message: Text(L10n.Global.Messages.emailNotConfigured), + dismissButton: .cancel(Text(L10n.Global.Strings.ok)) + ) + } + } + + private var serverConfigurationSection: some View { + Section { + let cfg = currentServerConfiguration + NavigationLink(L10n.Diagnostics.Items.ServerConfiguration.caption) { + cfg.map { + EndpointAdvancedView.OpenVPNView( + builder: .constant($0), + isReadonly: true, + isServerPushed: true + ).navigationTitle(L10n.Diagnostics.Items.ServerConfiguration.caption) + } + }.disabled(cfg == nil) + } + } + + private var debugLogSection: some View { + Section( + footer: Text(L10n.Diagnostics.Sections.DebugLog.footer) + ) { + let url = debugLogURL + NavigationLink(L10n.DebugLog.title) { + url.map { + DebugLogView( + url: $0, + updateInterval: logUpdateInterval + ) + } + }.disabled(url == nil) + Toggle(L10n.Diagnostics.Items.MasksPrivateData.caption, isOn: $appManager.masksPrivateData) + } + } + + private var issueReporterSection: some View { + Section { + Button(L10n.Diagnostics.Items.ReportIssue.caption, action: presentReportIssue) + } + } + + private func reportIssueView() -> some View { + let logURL = vpnManager.debugLogURL(forProtocol: vpnProtocol) + var metadata: ProviderMetadata? + var lastUpdate: Date? + if let name = providerName { + metadata = providerManager.provider(withName: name) + lastUpdate = providerManager.lastUpdate(name, vpnProtocol: vpnProtocol) + } + + return ReportIssueView( + isPresented: $isReportingIssue, + vpnProtocol: vpnProtocol, + logURL: logURL, + providerMetadata: metadata, + lastUpdate: lastUpdate + ) + } + } +} + +extension DiagnosticsView.OpenVPNView { + private var currentServerConfiguration: OpenVPN.ConfigurationBuilder? { + guard currentVPNState.vpnStatus == .connected else { + return nil + } + guard let cfg = vpnManager.serverConfiguration(forProtocol: vpnProtocol) as? OpenVPN.Configuration else { + return nil + } + // "withFallbacks: false" for view to hide nil options + return cfg.builder(withFallbacks: false) + } + + private var debugLogURL: URL? { + return vpnManager.debugLogURL(forProtocol: vpnProtocol) + } +} + +extension DiagnosticsView.OpenVPNView { + private func presentReportIssue() { + guard MailComposerView.canSendMail() else { + openReportIssueMailTo() + return + } + isReportingIssue = true + } + + private func openReportIssueMailTo() { + let V = Unlocalized.Issues.self + let body = V.body(V.template, DebugLog(content: "--").decoratedString()) + + guard let url = URL.mailto(to: V.recipient, subject: V.subject, body: body) else { + return + } + guard URL.openURL(url) else { + alertType = .emailNotConfigured + return + } + } +} diff --git a/Passepartout/App/iOS/Views/DiagnosticsView+WireGuard.swift b/Passepartout/App/iOS/Views/DiagnosticsView+WireGuard.swift new file mode 100644 index 00000000..8a970371 --- /dev/null +++ b/Passepartout/App/iOS/Views/DiagnosticsView+WireGuard.swift @@ -0,0 +1,63 @@ +// +// DiagnosticsView+WireGuard.swift +// Passepartout +// +// Created by Davide De Rosa on 3/11/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore +import TunnelKitWireGuard + +extension DiagnosticsView { + struct WireGuardView: View { + @ObservedObject private var vpnManager: VPNManager + + private let providerName: ProviderName? + + private let logUpdateInterval = Constants.Log.tunnelLogRefreshInterval + + init(providerName: ProviderName?) { + vpnManager = .shared + self.providerName = providerName + } + + var body: some View { + List { + Section { + let url = debugLogURL + NavigationLink(L10n.DebugLog.title) { + url.map { + DebugLogView( + url: $0, + updateInterval: logUpdateInterval + ) + } + }.disabled(url == nil) + } + } + } + + private var debugLogURL: URL? { + return vpnManager.debugLogURL(forProtocol: .wireGuard) + } + } +} diff --git a/Passepartout/App/iOS/Views/DiagnosticsView.swift b/Passepartout/App/iOS/Views/DiagnosticsView.swift new file mode 100644 index 00000000..62815f44 --- /dev/null +++ b/Passepartout/App/iOS/Views/DiagnosticsView.swift @@ -0,0 +1,49 @@ +// +// DiagnosticsView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/20/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +struct DiagnosticsView: View { + let vpnProtocol: VPNProtocolType + + let providerName: ProviderName? + + var body: some View { + Group { + switch vpnProtocol { + case .openVPN: + DiagnosticsView.OpenVPNView( + providerName: providerName + ) + + case .wireGuard: + DiagnosticsView.WireGuardView( + providerName: providerName + ) + } + }.navigationTitle(L10n.Diagnostics.title) + } +} diff --git a/Passepartout/App/iOS/Views/DonateView.swift b/Passepartout/App/iOS/Views/DonateView.swift new file mode 100644 index 00000000..d2336efa --- /dev/null +++ b/Passepartout/App/iOS/Views/DonateView.swift @@ -0,0 +1,153 @@ +// +// DonateView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/8/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import StoreKit + +struct DonateView: View { + enum AlertType: Identifiable { + case thankYou + + case purchaseFailed(Error) + + // XXX: alert ids + var id: Int { + switch self { + case .thankYou: return 1 + + case .purchaseFailed: return 2 + } + } + } + + @Environment(\.scenePhase) private var scenePhase + + @ObservedObject private var productManager: ProductManager + + @State private var alertType: AlertType? + + @State private var pendingDonationIdentifier: String? + + init() { + productManager = .shared + } + + var body: some View { + List { + productsSection + .disabled(pendingDonationIdentifier != nil) + }.themeSecondaryView() + .navigationTitle(L10n.Donate.title) + .alert(item: $alertType, content: presentedAlert) + } + + private func presentedAlert(_ alertType: AlertType) -> Alert { + switch alertType { + case .thankYou: + return Alert( + title: Text(L10n.Donate.Alerts.Purchase.Success.title), + message: Text(L10n.Donate.Alerts.Purchase.Success.message), + dismissButton: .cancel(Text(L10n.Global.Strings.ok)) + ) + + case .purchaseFailed(let error): + return Alert( + title: Text(L10n.Donate.title), + message: Text(L10n.Donate.Alerts.Purchase.Failure.message(error.localizedDescription)), + dismissButton: .cancel(Text(L10n.Global.Strings.ok)) + ) + } + } + + private var productsSection: some View { + ReloadingSection( + header: Text(L10n.Donate.Sections.OneTime.header), + footer: Text(L10n.Donate.Sections.OneTime.footer) + .xxxThemeTruncation(), + elements: productManager.donations, + equality: { + Set($0.map(\.productIdentifier)) == Set($1.map(\.productIdentifier)) + }, + isReloading: productManager.isRefreshingProducts, + reload: { + productManager.refreshProducts() + } + ) { + ForEach($0, id: \.productIdentifier, content: productRow) + } + } + + @ViewBuilder + private func productRow(_ product: SKProduct) -> some View { + HStack { + Button(product.localizedTitle) { + purchaseProduct(product) + } + Spacer() + if let pending = pendingDonationIdentifier, pending == product.productIdentifier { + ProgressView() + } else { + product.localizedPrice.map { + Text($0) + .foregroundColor(themeSecondaryColor) + } + } + } + } +} + +extension DonateView { + private func purchaseProduct(_ product: SKProduct) { + pendingDonationIdentifier = product.productIdentifier + productManager.purchase(product, completionHandler: handlePurchaseResult) + } + + private func handlePurchaseResult(_ result: Result) { + switch result { + case .success(let value): + if case .done = value { + alertType = .thankYou + } else { + // cancelled + } + + case .failure(let error): + alertType = .purchaseFailed(error) + } + pendingDonationIdentifier = nil + } +} + +private extension ProductManager { + var donations: [SKProduct] { + return products.filter { product in + LocalProduct.allDonations.contains { + $0.matchesStoreKitProduct(product) + } + }.sorted { + $0.price.decimalValue < $1.price.decimalValue + } + } +} diff --git a/Passepartout/App/iOS/Views/EndpointAdvancedView+OpenVPN.swift b/Passepartout/App/iOS/Views/EndpointAdvancedView+OpenVPN.swift new file mode 100644 index 00000000..be5af1e5 --- /dev/null +++ b/Passepartout/App/iOS/Views/EndpointAdvancedView+OpenVPN.swift @@ -0,0 +1,310 @@ +// +// EndpointAdvancedView+OpenVPN.swift +// Passepartout +// +// Created by Davide De Rosa on 3/8/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import TunnelKitOpenVPN +import PassepartoutCore + +extension EndpointAdvancedView { + struct OpenVPNView: View { + @Binding var builder: OpenVPN.ConfigurationBuilder + + let isReadonly: Bool + + let isServerPushed: Bool + + var body: some View { + List { + let cfg = builder.build() + if isServerPushed { + ipv4Section + ipv6Section + } + dnsSection(configuration: cfg) + proxySection(configuration: cfg) + if !isReadonly { + communicationEditableSection + compressionEditableSection + } else { + communicationSection(configuration: cfg) + compressionSection(configuration: cfg) + } + if !isServerPushed { + tlsSection + } + otherSection(configuration: cfg) + } + } + } +} + +extension EndpointAdvancedView.OpenVPNView { + private var ipv4Section: some View { + builder.ipv4.map { cfg in + Section( + header: Text(Unlocalized.Network.ipv4) + ) { + Text(L10n.Global.Strings.address) + .withTrailingText(builder.ipv4.localizedAddress, copyOnTap: true) + Text(L10n.NetworkSettings.Gateway.title) + .withTrailingText(builder.ipv4.localizedDefaultGateway, copyOnTap: true) + + ForEach(cfg.routes, id: \.self) { route in + Text(L10n.Endpoint.Advanced.Openvpn.Items.Route.caption) + .withTrailingText(route.localizedDescription, copyOnTap: true) + } + } + } + } + + private var ipv6Section: some View { + builder.ipv6.map { cfg in + Section( + header: Text(Unlocalized.Network.ipv6) + ) { + Text(L10n.Global.Strings.address) + .withTrailingText(builder.ipv6.localizedAddress, copyOnTap: true) + + Text(L10n.NetworkSettings.Gateway.title) + .withTrailingText(builder.ipv6.localizedDefaultGateway, copyOnTap: true) + + ForEach(cfg.routes, id: \.self) { route in + Text(L10n.Endpoint.Advanced.Openvpn.Items.Route.caption) + .withTrailingText(route.localizedDescription, copyOnTap: true) + } + } + } + } + + private func communicationSection(configuration: OpenVPN.Configuration) -> some View { + configuration.communicationSettings.map { settings in + Section( + header: Text(L10n.Endpoint.Advanced.Openvpn.Sections.Communication.header) + ) { + settings.cipher.map { + Text(L10n.Endpoint.Advanced.Openvpn.Items.Cipher.caption) + .withTrailingText($0.localizedDescription) + } + settings.digest.map { + Text(L10n.Endpoint.Advanced.Openvpn.Items.Digest.caption) + .withTrailingText($0.localizedDescription) + } + settings.xor.map { + Text(Unlocalized.VPN.xor) + .withTrailingText($0.localizedDescriptionAsXOR) + } + } + } + } + + private var communicationEditableSection: some View { + Section( + header: Text(L10n.Endpoint.Advanced.Openvpn.Sections.Communication.header) + ) { + themeTextPicker( + L10n.Endpoint.Advanced.Openvpn.Items.Cipher.caption, + selection: $builder.cipher ?? OpenVPN.Configuration.Fallback.cipher, + values: OpenVPN.Cipher.available, + description: \.localizedDescription + ) + themeTextPicker( + L10n.Endpoint.Advanced.Openvpn.Items.Digest.caption, + selection: $builder.digest ?? OpenVPN.Configuration.Fallback.digest, + values: OpenVPN.Digest.available, + description: \.localizedDescription + ) + builder.xorMask.map { + Text(Unlocalized.VPN.xor) + .withTrailingText($0.localizedDescriptionAsXOR) + } + } + } + + private func compressionSection(configuration: OpenVPN.Configuration) -> some View { + configuration.compressionSettings.map { settings in + Section( + header: Text(L10n.Endpoint.Advanced.Openvpn.Sections.Compression.header) + ) { + settings.framing.map { + Text(L10n.Endpoint.Advanced.Openvpn.Items.CompressionFraming.caption) + .withTrailingText($0.localizedDescription) + } + settings.algorithm.map { + Text(L10n.Endpoint.Advanced.Openvpn.Items.CompressionAlgorithm.caption) + .withTrailingText($0.localizedDescription) + } + } + } + } + + private var compressionEditableSection: some View { + Section( + header: Text(L10n.Endpoint.Advanced.Openvpn.Sections.Compression.header) + ) { + themeTextPicker( + L10n.Endpoint.Advanced.Openvpn.Items.CompressionFraming.caption, + selection: $builder.compressionFraming ?? OpenVPN.Configuration.Fallback.compressionFraming, + values: OpenVPN.CompressionFraming.available, + description: \.localizedDescription + ) + themeTextPicker( + L10n.Endpoint.Advanced.Openvpn.Items.CompressionAlgorithm.caption, + selection: $builder.compressionAlgorithm ?? OpenVPN.Configuration.Fallback.compressionAlgorithm, + values: OpenVPN.CompressionAlgorithm.available, + description: \.localizedDescription + ).disabled(builder.compressionFraming == .disabled) + } + } + + private func dnsSection(configuration: OpenVPN.Configuration) -> some View { + configuration.dnsSettings.map { settings in + Section( + header: Text(Unlocalized.Network.dns) + ) { + ForEach(settings.servers, id: \.self) { + Text(L10n.Global.Strings.address) + .withTrailingText($0) + } + ForEach(settings.domains, id: \.self) { + Text(L10n.Global.Strings.domain) + .withTrailingText($0) + } + } + } + } + + private func proxySection(configuration: OpenVPN.Configuration) -> some View { + configuration.proxySettings.map { settings in + Section( + header: Text(L10n.Global.Strings.proxy) + ) { + settings.proxy.map { + Text(L10n.Global.Strings.address) + .withTrailingText($0.rawValue) + } + settings.pac.map { + Text(Unlocalized.Network.proxyAutoConfiguration) + .withTrailingText($0.absoluteString) + } + ForEach(settings.bypass, id: \.self) { + Text(L10n.NetworkSettings.Items.ProxyBypass.caption) + .withTrailingText($0) + } + } + } + } + + private var tlsSection: some View { + Section( + header: Text(Unlocalized.Network.tls) + ) { + builder.ca.map { ca in + themeLongContentLink( + Unlocalized.VPN.certificateAuthority, + content: .constant(ca.pem) + ) + } + builder.clientCertificate.map { cert in + themeLongContentLink( + L10n.Endpoint.Advanced.Openvpn.Items.Client.caption, + content: .constant(cert.pem) + ) + } + builder.clientKey.map { key in + themeLongContentLink( + L10n.Endpoint.Advanced.Openvpn.Items.ClientKey.caption, + content: .constant(key.pem) + ) + } + builder.tlsWrap.map { wrap in + themeLongContentLink( + L10n.Endpoint.Advanced.Openvpn.Items.TlsWrapping.caption, + content: .constant(wrap.key.hexString), + withPreview: builder.tlsWrap.localizedDescription + ) + } + Text(L10n.Endpoint.Advanced.Openvpn.Items.Eku.caption) + .withTrailingText(builder.checksEKU.localizedDescriptionAsEKU) + } + } + + private func otherSection(configuration: OpenVPN.Configuration) -> some View { + configuration.otherSettings.map { settings in + Section( + header: Text(L10n.Endpoint.Advanced.Openvpn.Sections.Other.header) + ) { + settings.keepAlive.map { + Text(L10n.Global.Strings.keepalive) + .withTrailingText($0.localizedDescriptionAsKeepAlive) + } + settings.reneg.map { + Text(L10n.Endpoint.Advanced.Openvpn.Items.RenegotiationSeconds.caption) + .withTrailingText($0.localizedDescriptionAsRenegotiatesAfter) + } + settings.randomize.map { + Text(L10n.Endpoint.Advanced.Openvpn.Items.RandomEndpoint.caption) + .withTrailingText($0.localizedDescriptionAsRandomizeEndpoint) + } + } + } + } +} + +extension OpenVPN.Configuration { + var communicationSettings: (cipher: OpenVPN.Cipher?, digest: OpenVPN.Digest?, xor: UInt8?)? { + guard cipher != nil || digest != nil || xorMask != nil else { + return nil + } + return (cipher, digest, xorMask) + } + + var compressionSettings: (framing: OpenVPN.CompressionFraming?, algorithm: OpenVPN.CompressionAlgorithm?)? { + guard compressionFraming != nil || compressionAlgorithm != nil else { + return nil + } + return (compressionFraming, compressionAlgorithm) + } + + var dnsSettings: (servers: [String], domains: [String])? { + guard !(dnsServers?.isEmpty ?? true) || !(searchDomains?.isEmpty ?? true) else { + return nil + } + return (dnsServers ?? [], searchDomains ?? []) + } + + var proxySettings: (proxy: Proxy?, pac: URL?, bypass: [String])? { + guard httpsProxy != nil || httpProxy != nil || proxyAutoConfigurationURL != nil || !(proxyBypassDomains?.isEmpty ?? true) else { + return nil + } + return (httpsProxy ?? httpProxy, proxyAutoConfigurationURL, proxyBypassDomains ?? []) + } + + var otherSettings: (keepAlive: TimeInterval?, reneg: TimeInterval?, randomize: Bool?)? { + guard keepAliveInterval != nil || renegotiatesAfter != nil || randomizeEndpoint != nil else { + return nil + } + return (keepAliveInterval, renegotiatesAfter, randomizeEndpoint) + } +} diff --git a/Passepartout/App/iOS/Views/EndpointAdvancedView+WireGuard.swift b/Passepartout/App/iOS/Views/EndpointAdvancedView+WireGuard.swift new file mode 100644 index 00000000..cc3c8921 --- /dev/null +++ b/Passepartout/App/iOS/Views/EndpointAdvancedView+WireGuard.swift @@ -0,0 +1,99 @@ +// +// EndpointAdvancedView+WireGuard.swift +// Passepartout +// +// Created by Davide De Rosa on 3/8/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import TunnelKitWireGuard + +extension EndpointAdvancedView { + struct WireGuardView: View { + @Binding var builder: WireGuard.ConfigurationBuilder + + let isReadonly: Bool + + var body: some View { + List { + let cfg = builder.build() + keySection + addressesSection + dnsSection(configuration: cfg) + mtuSection + } + } + } +} + +extension EndpointAdvancedView.WireGuardView { + private var keySection: some View { + Section( + header: Text(L10n.Global.Strings.interface) + ) { + themeLongContentLink(L10n.Global.Strings.privateKey, content: .constant(builder.privateKey)) + themeLongContentLink(L10n.Global.Strings.publicKey, content: .constant(builder.publicKey)) + } + } + + private var addressesSection: some View { + Section( + header: Text(L10n.Global.Strings.addresses) + ) { + ForEach(builder.addresses, id: \.self, content: Text.init) + } + } + + private func dnsSection(configuration: WireGuard.Configuration) -> some View { + configuration.dnsSettings.map { settings in + Section( + header: Text(Unlocalized.Network.dns) + ) { + ForEach(settings.servers, id: \.self) { + Text(L10n.Global.Strings.address) + .withTrailingText($0) + } + ForEach(settings.domains, id: \.self) { + Text(L10n.Global.Strings.domain) + .withTrailingText($0) + } + } + } + } + + private var mtuSection: some View { + builder.mtu.map { mtu in + Section { + Text(Unlocalized.Network.mtu) + .withTrailingText(Int(mtu).localizedDescriptionAsMTU) + } + } + } +} + +extension WireGuard.Configuration { + var dnsSettings: (servers: [String], domains: [String])? { + guard !dnsServers.isEmpty || !dnsSearchDomains.isEmpty else { + return nil + } + return (dnsServers, dnsSearchDomains) + } +} diff --git a/Passepartout/App/macOS/Global/NSTextView+Search.swift b/Passepartout/App/iOS/Views/EndpointAdvancedView.swift similarity index 80% rename from Passepartout/App/macOS/Global/NSTextView+Search.swift rename to Passepartout/App/iOS/Views/EndpointAdvancedView.swift index d626a9e3..0f29b7a9 100644 --- a/Passepartout/App/macOS/Global/NSTextView+Search.swift +++ b/Passepartout/App/iOS/Views/EndpointAdvancedView.swift @@ -1,8 +1,8 @@ // -// NSTextView+Search.swift +// EndpointAdvancedView.swift // Passepartout // -// Created by Davide De Rosa on 8/1/18. +// Created by Davide De Rosa on 3/8/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -23,10 +23,7 @@ // along with Passepartout. If not, see . // -import Cocoa +import Foundation -extension NSTextView { - func scrollToEnd() { - scrollRangeToVisible(NSMakeRange(string.count - 1, 1)) - } +enum EndpointAdvancedView { } diff --git a/Passepartout/App/iOS/Views/EndpointView+OpenVPN.swift b/Passepartout/App/iOS/Views/EndpointView+OpenVPN.swift new file mode 100644 index 00000000..ff412c19 --- /dev/null +++ b/Passepartout/App/iOS/Views/EndpointView+OpenVPN.swift @@ -0,0 +1,274 @@ +// +// EndpointView+OpenVPN.swift +// Passepartout +// +// Created by Davide De Rosa on 2/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore +import TunnelKitOpenVPN + +extension EndpointView { + struct OpenVPNView: View { + @Environment(\.presentationMode) private var presentationMode + + @ObservedObject private var providerManager: ProviderManager + + @ObservedObject private var currentProfile: ObservableProfile + + @Binding private var builder: OpenVPN.ConfigurationBuilder + + @Binding private var customEndpoint: Endpoint? + + private var isConfigurationReadonly: Bool { + currentProfile.value.isProvider + } + + @State private var isFirstAppearance = true + + @State private var isAutomatic = false + + @State private var selectedSocketType: SocketType = .udp + + @State private var selectedPort: UInt16 = 0 + + // XXX: do not escape mutating 'self', use constant providerManager + init(currentProfile: ObservableProfile) { + let providerManager: ProviderManager = .shared + + self.providerManager = providerManager + self.currentProfile = currentProfile + + _builder = .init { + if currentProfile.value.isProvider { + guard let server = currentProfile.value.providerServer(providerManager) else { + assertionFailure("Server not found") + return .init() + } + guard let preset = currentProfile.value.providerPreset(server) else { + assertionFailure("Preset not found") + return .init() + } + guard let cfg = preset.openVPNConfiguration else { + assertionFailure("Preset \(preset.id) (\(preset.name)) has no OpenVPN configuration") + return .init() + } + var builder = cfg.builder(withFallbacks: true) + try? builder.setRemotes(from: preset, with: server, excludingHostname: false) + return builder + } else if let cfg = currentProfile.value.hostOpenVPNSettings?.configuration { + let builder = cfg.builder(withFallbacks: true) +// pp_log.debug("Loading OpenVPN configuration: \(builder)") + return builder + } + // fall back gracefully + return .init() + } set: { + if currentProfile.value.isProvider { + // readonly + } else { + pp_log.debug("Saving OpenVPN configuration: \($0)") + currentProfile.value.hostOpenVPNSettings?.configuration = $0.build() + } + } + _customEndpoint = .init { + if currentProfile.value.isProvider { + return currentProfile.value.providerCustomEndpoint() + } else { + return currentProfile.value.hostOpenVPNSettings?.customEndpoint + } + } set: { + if currentProfile.value.isProvider { + currentProfile.value.setProviderCustomEndpoint($0) + } else { + currentProfile.value.hostOpenVPNSettings?.customEndpoint = $0 + } + } + } + + var body: some View { + ScrollViewReader { scrollProxy in + List { + mainSection + if !isAutomatic { + filtersSection + addressesSection + } + advancedSection + }.onAppear { + scrollToCustomEndpoint(scrollProxy) + preselectFilters(once: true) + }.onChange(of: isAutomatic, perform: onToggleAutomatic) + .onChange(of: selectedSocketType, perform: preselectPort) + .onChange(of: customEndpoint) { _ in + withAnimation { + preselectFilters(once: false) + } + } + }.navigationTitle(L10n.Global.Strings.endpoint) + } + } +} + +extension EndpointView.OpenVPNView { + private var mainSection: some View { + Section { + Toggle(L10n.Global.Strings.automatic, isOn: $isAutomatic.animation()) + } + } + + private var filtersSection: some View { + Section { + themeTextPicker( + L10n.Global.Strings.protocol, + selection: $selectedSocketType, + values: allSocketTypes, + description: \.rawValue + ) + themeTextPicker( + L10n.Global.Strings.port, + selection: $selectedPort, + values: allPorts(forSocketType: selectedSocketType), + description: \.description + ) + } + } + + private var addressesSection: some View { + Section( + header: Text(L10n.Global.Strings.addresses) + ) { + filteredRemotes.map { + ForEach($0, content: button(forEndpoint:)) + } + } + } + + private var advancedSection: some View { + Section { + let caption = L10n.Endpoint.Advanced.title + NavigationLink(caption) { + EndpointAdvancedView.OpenVPNView( + builder: $builder, + isReadonly: isConfigurationReadonly, + isServerPushed: false + ).navigationTitle(caption) + } + } + } + + private func button(forEndpoint endpoint: Endpoint?) -> some View { + Button { + customEndpoint = endpoint + presentationMode.wrappedValue.dismiss() + } label: { + text(forEndpoint: endpoint) + }.withTrailingCheckmark(when: customEndpoint == endpoint) + } + + private func text(forEndpoint endpoint: Endpoint?) -> some View { + Text(endpoint?.address ?? L10n.Global.Strings.automatic) + .themeLongText() + } +} + +extension EndpointView.OpenVPNView { + private func onToggleAutomatic(_ value: Bool) { + if value { + guard customEndpoint != nil else { + return + } + customEndpoint = nil + } + } + + private func preselectFilters(once: Bool) { + guard !once || isFirstAppearance else { + return + } + isFirstAppearance = false + + if let customEndpoint = customEndpoint { + isAutomatic = false + selectedSocketType = customEndpoint.proto.socketType + selectedPort = customEndpoint.proto.port + } else { + isAutomatic = true + guard let socketType = allSocketTypes.first else { + assertionFailure("No socket types, empty remotes?") + return + } + selectedSocketType = socketType + preselectPort(forSocketType: socketType) + } + } + + private func preselectPort(forSocketType socketType: SocketType) { + let supported = allPorts(forSocketType: socketType) + guard !supported.contains(selectedPort) else { + return + } + guard let port = supported.first else { + assertionFailure("No ports, empty remotes?") + return + } + selectedPort = port + } + + private var allSocketTypes: [SocketType] { + guard let remotes = builder.remotes else { + return [] + } + var allTypes: [SocketType] = [] + [SocketType.udp, SocketType.tcp].forEach { socketType in + guard remotes.contains(where: { + $0.proto.socketType == socketType + }) else { + return + } + allTypes.append(socketType) + } + return allTypes + } + + private func allPorts(forSocketType socketType: SocketType) -> [UInt16] { + guard let remotes = builder.remotes else { + return [] + } + let allPorts = Set(remotes.filter { + $0.proto.socketType == socketType + }.map(\.proto.port)) + return Array(allPorts).sorted() + } + + private var filteredRemotes: [Endpoint]? { + return builder.remotes?.filter { + $0.proto.socketType == selectedSocketType && $0.proto.port == selectedPort + } + } +} + +extension EndpointView.OpenVPNView { + private func scrollToCustomEndpoint(_ proxy: ScrollViewProxy) { + proxy.maybeScrollTo(customEndpoint?.id) + } +} diff --git a/Passepartout/App/iOS/Views/EndpointView+WireGuard.swift b/Passepartout/App/iOS/Views/EndpointView+WireGuard.swift new file mode 100644 index 00000000..0b9b7ed7 --- /dev/null +++ b/Passepartout/App/iOS/Views/EndpointView+WireGuard.swift @@ -0,0 +1,146 @@ +// +// EndpointView+WireGuard.swift +// Passepartout +// +// Created by Davide De Rosa on 2/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore +import TunnelKitWireGuard + +extension EndpointView { + struct WireGuardView: View { + @ObservedObject private var providerManager: ProviderManager + + @ObservedObject private var currentProfile: ObservableProfile + + let isReadonly: Bool + + @Binding private var builder: WireGuard.ConfigurationBuilder + +// var customPeer: Binding? = nil + + // XXX: do not escape mutating 'self', use constant providerManager + init(currentProfile: ObservableProfile, isReadonly: Bool) { + let providerManager: ProviderManager = .shared + + self.providerManager = providerManager + self.currentProfile = currentProfile + self.isReadonly = isReadonly + + _builder = .init { + if currentProfile.value.isProvider { + guard let server = currentProfile.value.providerServer(providerManager) else { + assertionFailure("Server not found") + return .init() + } + guard let preset = currentProfile.value.providerPreset(server) else { + assertionFailure("Preset not found") + return .init() + } + guard let cfg = preset.wireGuardConfiguration else { + assertionFailure("Preset \(preset.id) (\(preset.name)) has no WireGuard configuration") + return .init() + } + return cfg.builder() + } else if let cfg = currentProfile.value.hostWireGuardSettings?.configuration { + let builder = cfg.builder() +// pp_log.debug("Loading WireGuard configuration: \(builder)") + return builder + } + // fall back gracefully + return .init() + } set: { + if currentProfile.value.isProvider { + // readonly + } else { + pp_log.debug("Saving WireGuard configuration: \($0)") + currentProfile.value.hostWireGuardSettings?.configuration = $0.build() + } + } + } + + var body: some View { + List { + peersSections + advancedSection + }.navigationTitle(L10n.Global.Strings.endpoint) + } + } +} + +extension EndpointView.WireGuardView { + private var peersSections: some View { + + // TODO: WireGuard, make peers editable +// if !isReadonly { + ForEach(0.. some View { + Section( + header: Text(L10n.Endpoint.Wireguard.Items.Peer.caption) + ) { + themeLongContentLink( + L10n.Global.Strings.publicKey, + content: .constant(builder.publicKey(ofPeer: peerIndex)) + ) + builder.preSharedKey(ofPeer: peerIndex).map { key in + themeLongContentLink( + L10n.Endpoint.Wireguard.Items.PresharedKey.caption, + content: .constant(key) + ) + } + builder.endpoint(ofPeer: peerIndex).map { + Text(L10n.Global.Strings.endpoint) + .withTrailingText($0) + } + ForEach(builder.allowedIPs(ofPeer: peerIndex), id: \.self) { + Text(L10n.Endpoint.Wireguard.Items.AllowedIp.caption) + .withTrailingText($0) + } + builder.keepAlive(ofPeer: peerIndex).map { + Text(L10n.Global.Strings.keepalive) + .withTrailingText(TimeInterval($0).localizedDescriptionAsKeepAlive) + } + } + } + + private var advancedSection: some View { + Section { + let caption = L10n.Endpoint.Advanced.title + NavigationLink(caption) { + EndpointAdvancedView.WireGuardView( + builder: $builder, + isReadonly: isReadonly + ).navigationTitle(caption) + } + } + } +} diff --git a/Passepartout/App/iOS/Global/Theme+Titles.swift b/Passepartout/App/iOS/Views/EndpointView.swift similarity index 56% rename from Passepartout/App/iOS/Global/Theme+Titles.swift rename to Passepartout/App/iOS/Views/EndpointView.swift index ff85ba34..8ef55893 100644 --- a/Passepartout/App/iOS/Global/Theme+Titles.swift +++ b/Passepartout/App/iOS/Views/EndpointView.swift @@ -1,8 +1,8 @@ // -// Theme+Titles.swift +// EndpointView.swift // Passepartout // -// Created by Davide De Rosa on 7/16/18. +// Created by Davide De Rosa on 3/27/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -23,27 +23,28 @@ // along with Passepartout. If not, see . // -import UIKit -import ConvenienceUI +import SwiftUI +import PassepartoutCore -extension StrongTableModel { - func headerHeight(for section: Int) -> CGFloat { - guard let title = header(forSection: section) else { - return 1.0 - } - guard !title.isEmpty else { - return 0.0 - } - return UITableView.automaticDimension +struct EndpointView: View { + @ObservedObject private var currentProfile: ObservableProfile + + init(currentProfile: ObservableProfile) { + self.currentProfile = currentProfile } + + var body: some View { + switch currentProfile.value.currentVPNProtocol { + case .openVPN: + EndpointView.OpenVPNView( + currentProfile: currentProfile + ) - func footerHeight(for section: Int) -> CGFloat { - guard let title = footer(forSection: section) else { - return 1.0 + case .wireGuard: + EndpointView.WireGuardView( + currentProfile: currentProfile, + isReadonly: true + ) } - guard !title.isEmpty else { - return 0.0 - } - return UITableView.automaticDimension } } diff --git a/Passepartout/App/iOS/Views/MainView.swift b/Passepartout/App/iOS/Views/MainView.swift new file mode 100644 index 00000000..6de9604b --- /dev/null +++ b/Passepartout/App/iOS/Views/MainView.swift @@ -0,0 +1,41 @@ +// +// MainView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/8/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct MainView: View { + init() { + themeConfigureNavigationBarAppearance() + } + + var body: some View { + NavigationView { + OrganizerView() + .themePrimaryView() + + ProfileView(header: nil) + }.themeGlobal() + } +} diff --git a/Passepartout/App/iOS/Views/NetworkSettingsView.swift b/Passepartout/App/iOS/Views/NetworkSettingsView.swift new file mode 100644 index 00000000..61bd8e0a --- /dev/null +++ b/Passepartout/App/iOS/Views/NetworkSettingsView.swift @@ -0,0 +1,281 @@ +// +// NetworkSettingsView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension Profile.NetworkSettings: CopySavingModel { +} + +struct NetworkSettingsView: View { + @ObservedObject private var currentProfile: ObservableProfile + + private var vpnProtocol: VPNProtocolType { + currentProfile.value.currentVPNProtocol + } + + @State private var settings = Profile.NetworkSettings() + + init(currentProfile: ObservableProfile) { + self.currentProfile = currentProfile + } + + var body: some View { + debugChanges() + return List { + if vpnProtocol.supportsGateway { + gatewayView + } + if vpnProtocol.supportsDNS { + dnsView + } + if vpnProtocol.supportsProxy { + proxyView + } + if vpnProtocol.supportsMTU { + mtuView + } + }.navigationTitle(L10n.NetworkSettings.title) + .toolbar { + CopySavingButton( + original: $currentProfile.value.networkSettings, + copy: $settings, + mapping: \.stripped, + label: themeSaveButtonLabel + ) + } + } + +// EditButton() +// .disabled(!isAnythingManual) + + private var isAnythingManual: Bool { +// if settings.gateway.choice == .manual { +// return true +// } + if settings.dns.choice == .manual { + return true + } + if settings.proxy.choice == .manual { + return true + } +// if settings.mtu.choice == .manual { +// return true +// } + return false + } + + private func mapNotEmpty(elements: [IdentifiableString]) -> [IdentifiableString] { + elements + .filter { !$0.string.isEmpty } + } +} + +// MARK: Gateway + +extension NetworkSettingsView { + private var gatewayView: some View { + Section( + header: Text(L10n.NetworkSettings.Gateway.title) + ) { + Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticGateway.animation()) + + if !settings.isAutomaticGateway { + Toggle(Unlocalized.Network.ipv4, isOn: $settings.gateway.isDefaultIPv4) + Toggle(Unlocalized.Network.ipv6, isOn: $settings.gateway.isDefaultIPv6) + } + } + } +} + +// MARK: DNS + +extension NetworkSettingsView { + + @ViewBuilder + private var dnsView: some View { + Section( + header: Text(Unlocalized.Network.dns) + ) { + Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticDNS.animation()) + + if !settings.isAutomaticDNS { + Toggle(L10n.Global.Strings.enabled, isOn: $settings.dns.isDNSEnabled.animation()) + + if settings.dns.isDNSEnabled { + themeTextPicker( + L10n.Global.Strings.protocol, + selection: $settings.dns.dnsProtocol, + values: Network.DNSSettings.availableProtocols(forVPNProtocol: vpnProtocol), + description: \.localizedDescription + ) + switch settings.dns.dnsProtocol { + case .plain: + EmptyView() + + case .https: + dnsManualHTTPSRow + + case .tls: + dnsManualTLSRow + } + } + } + } + if !settings.isAutomaticDNS && settings.dns.isDNSEnabled { + dnsManualServers + dnsManualDomains + } + } + + private var dnsManualHTTPSRow: some View { + TextField(Unlocalized.Placeholders.dohURL, text: $settings.dns.dnsHTTPSURL.toString()) + .themeURL(settings.dns.dnsHTTPSURL?.absoluteString) + } + + private var dnsManualTLSRow: some View { + TextField(Unlocalized.Placeholders.dotServerName, text: $settings.dns.dnsTLSServerName ?? "") + .themeDomainName(settings.dns.dnsTLSServerName) + } + + private var dnsManualServers: some View { + Section { + EditableTextList( + elements: $settings.dns.dnsServers, + allowsDuplicates: false, + mapping: mapNotEmpty + ) { + TextField( + Unlocalized.Placeholders.dnsAddress, + text: $0.text, + onEditingChanged: $0.onEditingChanged, + onCommit: $0.onCommit + ).themeIPAddress($0.text.wrappedValue) + } addLabel: { + Text(L10n.NetworkSettings.Items.AddDnsServer.caption) +// } commitLabel: { +// Text(L10n.Global.Strings.add) + } + } + } + + private var dnsManualDomains: some View { + Section { + EditableTextList( + elements: $settings.dns.dnsSearchDomains, + allowsDuplicates: false, + mapping: mapNotEmpty + ) { + TextField( + Unlocalized.Placeholders.dnsDomain, + text: $0.text, + onEditingChanged: $0.onEditingChanged, + onCommit: $0.onCommit + ).themeDomainName($0.text.wrappedValue) + } addLabel: { + Text(L10n.NetworkSettings.Items.AddDnsDomain.caption) +// } commitLabel: { +// Text(L10n.Global.Strings.add) + } + } + } +} + +// MARK: Proxy + +extension NetworkSettingsView { + + @ViewBuilder + private var proxyView: some View { + Section( + header: Text(L10n.Global.Strings.proxy) + ) { + Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticProxy.animation()) + + if !settings.isAutomaticProxy { + Toggle(L10n.Global.Strings.enabled, isOn: $settings.proxy.isProxyEnabled.animation()) + + if settings.proxy.isProxyEnabled { + TextField(Unlocalized.Placeholders.address, text: $settings.proxy.proxyAddress ?? "") + .themeIPAddress(settings.proxy.proxyAddress) + .withLeadingText(L10n.Global.Strings.address) + + TextField(Unlocalized.Placeholders.port, text: $settings.proxy.proxyPort.toString()) + .themeSocketPort() + .withLeadingText(L10n.Global.Strings.port) + + TextField(Unlocalized.Placeholders.pacURL, text: $settings.proxy.proxyAutoConfigurationURL.toString()) + .themeURL(settings.proxy.proxyAutoConfigurationURL?.absoluteString) + .withLeadingText(Unlocalized.Network.proxyAutoConfiguration) + } + } + } + if !settings.isAutomaticProxy && settings.proxy.isProxyEnabled { + proxyManualBypassDomains + } + } + + private var proxyManualBypassDomains: some View { + Section { + EditableTextList( + elements: $settings.proxy.proxyBypassDomains, + allowsDuplicates: false, + mapping: mapNotEmpty + ) { + TextField( + Unlocalized.Placeholders.proxyBypassDomain, + text: $0.text, + onEditingChanged: $0.onEditingChanged, + onCommit: $0.onCommit + ).themeDomainName($0.text.wrappedValue) + } addLabel: { + Text(L10n.NetworkSettings.Items.AddProxyBypass.caption) +// } commitLabel: { +// Text(L10n.Global.Strings.add) + } + } + } +} + +// MARK: MTU + +extension NetworkSettingsView { + private var mtuView: some View { + Section( + header: Text(Unlocalized.Network.mtu) + ) { + Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticMTU.animation()) + + if !settings.isAutomaticMTU { + themeTextPicker( + L10n.Global.Strings.bytes, + selection: $settings.mtu.mtuBytes, + values: Network.MTUSettings.availableBytes, + description: \.localizedDescriptionAsMTU + ) + } + } + } +} diff --git a/Passepartout/App/iOS/Views/OnDemandView+SSID.swift b/Passepartout/App/iOS/Views/OnDemandView+SSID.swift new file mode 100644 index 00000000..d2e1205f --- /dev/null +++ b/Passepartout/App/iOS/Views/OnDemandView+SSID.swift @@ -0,0 +1,134 @@ +// +// OnDemandView+SSID.swift +// Passepartout +// +// Created by Davide De Rosa on 2/23/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension OnDemandView { + struct SSIDList: View { + @Binding var withSSIDs: [String: Bool] + + @StateObject private var reader = SSIDReader() + + var body: some View { + EditableTextList(elements: allSSIDs, allowsDuplicates: false, mapping: mapElements) { text in + reader.requestCurrentSSID { + if !withSSIDs.keys.contains($0) { + text.wrappedValue = $0 + } + } + } textField: { + ssidRow(callback: $0) + } addLabel: { + Text(L10n.OnDemand.Items.AddSsid.caption) + } commitLabel: { + Text(L10n.Global.Strings.add) + } + } + + private func mapElements(elements: [IdentifiableString]) -> [IdentifiableString] { + elements + .filter { !$0.string.isEmpty } + .sorted { $0.string.lowercased() < $1.string.lowercased() } + } + + private func ssidRow(callback: EditableTextList.FieldCallback) -> some View { + Group { + if callback.isNewElement { + ssidField(callback: callback) + } else { + Toggle(isOn: isSSIDOn(callback.text.wrappedValue)) { + ssidField(callback: callback) + } + } + } + } + + private func ssidField(callback: EditableTextList.FieldCallback) -> some View { + TextField( + Unlocalized.Network.ssid, + text: callback.text, + onEditingChanged: callback.onEditingChanged, + onCommit: callback.onCommit + ).themeSSID(callback.text.wrappedValue) + } + } +} + +extension OnDemandView.SSIDList { + private var allSSIDs: Binding<[String]> { + .init { + Array(withSSIDs.keys) + } set: { newValue in + withSSIDs.forEach { + guard newValue.contains($0.key) else { + withSSIDs.removeValue(forKey: $0.key) + return + } + } + newValue.forEach { + guard withSSIDs[$0] == nil else { + return + } + withSSIDs[$0] = false + } +// print(">>> withSSIDs (allSSIDs): \(withSSIDs)") + } + } + + private var onSSIDs: Binding> { + .init { + Set(withSSIDs.filter { + $0.value + }.map(\.key)) + } set: { newValue in + withSSIDs.forEach { + guard newValue.contains($0.key) else { + if let _ = withSSIDs[$0.key] { + withSSIDs[$0.key] = false + } else { + withSSIDs.removeValue(forKey: $0.key) + } + return + } + } + newValue.forEach { + guard !(withSSIDs[$0] ?? false) else { + return + } + withSSIDs[$0] = true + } +// print(">>> withSSIDs (onSSIDs): \(withSSIDs)") + } + } + + private func isSSIDOn(_ ssid: String) -> Binding { + .init { + withSSIDs[ssid] ?? false + } set: { + withSSIDs[ssid] = $0 + } + } +} diff --git a/Passepartout/App/iOS/Views/OnDemandView.swift b/Passepartout/App/iOS/Views/OnDemandView.swift new file mode 100644 index 00000000..f35b0e2d --- /dev/null +++ b/Passepartout/App/iOS/Views/OnDemandView.swift @@ -0,0 +1,123 @@ +// +// OnDemandView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/23/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension Profile.OnDemand: CopySavingModel { +} + +struct OnDemandView: View { + @ObservedObject private var productManager: ProductManager + + @ObservedObject private var currentProfile: ObservableProfile + + private var isEligibleForSiri: Bool { + productManager.isEligible(forFeature: .siriShortcuts) + } + + @State private var onDemand = Profile.OnDemand() + + init(currentProfile: ObservableProfile) { + productManager = .shared + self.currentProfile = currentProfile + } + + var body: some View { + debugChanges() + return List { + // TODO: on-demand, restore when "trusted networks" -> "on-demand" +// enabledView +// if onDemand.isEnabled { + mainView +// } + }.navigationTitle(L10n.OnDemand.title) + .toolbar { + CopySavingButton( + original: $currentProfile.value.onDemand, + copy: $onDemand, + mapping: \.stripped, + label: themeSaveButtonLabel + ) + } + + // Siri + .onChange(of: onDemand.withMobileNetwork, perform: donateMobileIntent) + .onChange(of: onDemand.withSSIDs, perform: donateNetworkIntents) + } +} + +extension OnDemandView { + private var enabledView: some View { + Section { + Toggle(L10n.Global.Strings.enabled, isOn: $onDemand.isEnabled.animation()) + } + } + + @ViewBuilder + private var mainView: some View { + if Utils.hasCellularData() { + Section( + // TODO: on-demand, restore when "trusted networks" -> "on-demand" +// header: Text(L10n.Profile.Sections.Trusted.header) + ) { + Toggle(L10n.OnDemand.Items.Mobile.caption, isOn: $onDemand.withMobileNetwork) + } + Section { + SSIDList(withSSIDs: $onDemand.withSSIDs) + } + } else { + Section( + // TODO: on-demand, restore when "trusted networks" -> "on-demand" +// header: Text(L10n.Profile.Sections.Trusted.header) + ) { + SSIDList(withSSIDs: $onDemand.withSSIDs) + } + } + Section( + footer: Text(L10n.OnDemand.Sections.Policy.footer) + ) { + Toggle(L10n.OnDemand.Items.Policy.caption, isOn: $onDemand.disconnectsIfNotMatching) + } + } + + // eligibility: donate intents if eligible for Siri + private func donateMobileIntent(_ isEnabled: Bool) { + guard isEligibleForSiri else { + return + } + IntentDispatcher.donateTrustCellularNetwork() + IntentDispatcher.donateUntrustCellularNetwork() + } + + // eligibility: donate intents if eligible for Siri + private func donateNetworkIntents(_: [String: Bool]) { + guard isEligibleForSiri else { + return + } + IntentDispatcher.donateTrustCurrentNetwork() + IntentDispatcher.donateUntrustCurrentNetwork() + } +} diff --git a/Passepartout/App/iOS/Views/OrganizerView+AddProfileMenu.swift b/Passepartout/App/iOS/Views/OrganizerView+AddProfileMenu.swift new file mode 100644 index 00000000..10b0c3a1 --- /dev/null +++ b/Passepartout/App/iOS/Views/OrganizerView+AddProfileMenu.swift @@ -0,0 +1,118 @@ +// +// OrganizerView+AddProfileMenu.swift +// Passepartout +// +// Created by Davide De Rosa on 4/2/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension OrganizerView { + struct AddProfileMenu: View { + struct Bindings { + @Binding var modalType: ModalType? + + @Binding var alertType: AlertType? + + @Binding var isHostFileImporterPresented: Bool + } + + private let withImportedURLs: Bool + + private let bindings: Bindings + + init( + withImportedURLs: Bool, + bindings: Bindings + ) { + self.withImportedURLs = withImportedURLs + self.bindings = bindings + } + + var body: some View { + Group { + Button { + bindings.modalType = .addProvider + } label: { + Label(L10n.Organizer.Items.AddProvider.caption, systemImage: themeProviderImage) + } + Button { + presentHostFileImporter() + } label: { + Label(L10n.Organizer.Items.AddHost.caption, systemImage: themeHostImage) + } + if withImportedURLs { + Divider() + importedURLs.map { urls in + ForEach(urls, id: \.absoluteString, content: importedURLRow) + } + } + } + } + + private func importedURLRow(_ url: URL) -> some View { + Button(L10n.Organizer.Menus.AddProfile.imported(url.lastPathComponent)) { + presentAddHost(withURL: url, deletingURLOnSuccess: true) + } + } + + private var importedURLs: [URL]? { + do { + let url = FileManager.default.userURL(for: .documentDirectory, appending: nil) + let list = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil) + return list.filter { + VPNProtocolType.knownFileExtensions.contains($0.pathExtension) + } + } catch { + return nil + } + } + } +} + +extension OrganizerView.AddProfileMenu { + private func presentAddProvider() { + bindings.modalType = .addProvider + } + + private func presentAddHost(withURL url: URL, deletingURLOnSuccess: Bool) { + bindings.modalType = .addHost(url, deletingURLOnSuccess) + } + + private func presentHostFileImporter() { + + // XXX: iOS bug, hack around crappy bug when dismissing by swiping down + // + // https://stackoverflow.com/questions/66965471/swiftui-fileimporter-modifier-not-updating-binding-when-dismissed-by-tapping + bindings.isHostFileImporterPresented = false + Task { + await Task.maybeWait(forMilliseconds: Constants.Delays.xxxPresentFileImporter) + bindings.isHostFileImporterPresented = true + } +// isHostFileImporterPresented = true + +// // use this to test hardcoded bundle file +// let url = Bundle.main.url(forResource: "pia", withExtension: "ovpn")! +// importedProfileName = "pia.ovpn" +// modalType = .addHost(url, false) + } +} diff --git a/Passepartout/App/iOS/Views/OrganizerView+Profiles.swift b/Passepartout/App/iOS/Views/OrganizerView+Profiles.swift new file mode 100644 index 00000000..5a88edbd --- /dev/null +++ b/Passepartout/App/iOS/Views/OrganizerView+Profiles.swift @@ -0,0 +1,157 @@ +// +// OrganizerView+Profiles.swift +// Passepartout +// +// Created by Davide De Rosa on 4/2/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension OrganizerView { + struct ProfilesSection: View { + @ObservedObject private var appManager: AppManager + + @ObservedObject private var profileManager: ProfileManager + + @ObservedObject private var providerManager: ProviderManager + + // just to observe changes in profiles eligibility + @ObservedObject private var productManager: ProductManager + + private let addProfileMenuBindings: AddProfileMenu.Bindings + + @State private var isFirstLaunch = true + + @State private var selectedProfileId: UUID? + + init(addProfileMenuBindings: AddProfileMenu.Bindings) { + appManager = .shared + profileManager = .shared + providerManager = .shared + productManager = .shared + self.addProfileMenuBindings = addProfileMenuBindings + } + + var body: some View { + debugChanges() + return ReloadingSection( + header: Text(Unlocalized.VPN.vpn), + footer: EmptyView(), + elements: profileManager.headers, + equality: { + Set($0) == Set($1) + } + ) { + if !$0.isEmpty { + ForEach($0.sorted(), content: navigationLink(forHeader:)) + .onAppear(perform: selectActiveProfile) + } else { + AddProfileMenu( + withImportedURLs: false, + bindings: addProfileMenuBindings + ) + } + }.onAppear(perform: performMigrationsIfNeeded) + + // detect deletion + .onChange(of: profileManager.headers, perform: dismissSelectionIfDeleted) + + // from AddProfileView + .onReceive(profileManager.didCreateProfile) { + selectedProfileId = $0.id + } + } + + private func navigationLink(forHeader header: Profile.Header) -> some View { + NavigationLink(tag: header.id, selection: $selectedProfileId) { + ProfileView(header: header) + } label: { + if profileManager.isActiveProfile(header.id) { + ActiveProfileHeaderRow(header: header) + } else { + ProfileHeaderRow(header: header) + } + } + } + } +} + +extension OrganizerView.ProfilesSection { + struct ActiveProfileHeaderRow: View { + @ObservedObject private var currentVPNState: VPNManager.ObservableState + + private let header: Profile.Header + + init(header: Profile.Header) { + currentVPNState = .shared + self.header = header + } + + var body: some View { + debugChanges() + return ProfileHeaderRow(header: header) + .withTrailingText(statusDescription) + } + + private var statusDescription: String { + return currentVPNState.localizedStatusDescription( + withErrors: false, + withDataCount: false + ) + } + } +} + +extension OrganizerView.ProfilesSection { + private func selectActiveProfile() { + guard isFirstLaunch else { + return + } + isFirstLaunch = false + + // do not push profile if: + // + // - an alert is active, as it would break navigation + // - on iPad, as it's already shown + // + if addProfileMenuBindings.alertType == nil, + themeIdiom != .pad, + let activeProfileId = profileManager.activeHeader?.id { + + selectedProfileId = activeProfileId + } + } + + private func performMigrationsIfNeeded() { + Task { + await appManager.doMigrations(profileManager) + } + } + + private func dismissSelectionIfDeleted(headers: [Profile.Header]) { + if let selectedProfileId = selectedProfileId, + !profileManager.isExistingProfile(withId: selectedProfileId) { + + self.selectedProfileId = nil + } + } +} diff --git a/Passepartout/App/iOS/Views/OrganizerView+Scene.swift b/Passepartout/App/iOS/Views/OrganizerView+Scene.swift new file mode 100644 index 00000000..bea8e1ab --- /dev/null +++ b/Passepartout/App/iOS/Views/OrganizerView+Scene.swift @@ -0,0 +1,91 @@ +// +// OrganizerView+Scene.swift +// Passepartout +// +// Created by Davide De Rosa on 4/2/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension OrganizerView { + struct SceneView: View { + @Environment(\.scenePhase) private var scenePhase + + @ObservedObject private var appManager: AppManager + + @ObservedObject private var profileManager: ProfileManager + + @ObservedObject private var vpnManager: VPNManager + + @ObservedObject private var productManager: ProductManager + + @Binding private var alertType: AlertType? + + @Binding private var didHandleSubreddit: Bool + + init(alertType: Binding, didHandleSubreddit: Binding) { + appManager = .shared + profileManager = .shared + vpnManager = .shared + productManager = .shared + _alertType = alertType + _didHandleSubreddit = didHandleSubreddit + } + + var body: some View { + + // dummy text, EmptyView() does not trigger on*() handlers + Text("Scene") + .hidden() + .onAppear(perform: onAppear) + .onChange(of: scenePhase, perform: onScenePhase) + } + + private func onAppear() { + if !didHandleSubreddit { + alertType = .subscribeReddit + } + } + + private func onScenePhase(_ phase: ScenePhase) { + switch phase { + case .active: + if productManager.hasRefunded() { + Task { + await vpnManager.uninstall() + } + } + + case .background: + persist() + + default: + break + } + } + + private func persist() { + appManager.activeProfileId = profileManager.activeProfileId + profileManager.persist() + } + } +} diff --git a/Passepartout/App/iOS/Views/OrganizerView+Shortcuts.swift b/Passepartout/App/iOS/Views/OrganizerView+Shortcuts.swift new file mode 100644 index 00000000..f94ffc5b --- /dev/null +++ b/Passepartout/App/iOS/Views/OrganizerView+Shortcuts.swift @@ -0,0 +1,70 @@ +// +// OrganizerView+Scene.swift +// Passepartout +// +// Created by Davide De Rosa on 4/2/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +extension OrganizerView { + struct ShortcutsSection: View { + @ObservedObject private var productManager: ProductManager + + private var isEligibleForSiri: Bool { + productManager.isEligible(forFeature: .siriShortcuts) + } + + @Binding private var modalType: ModalType? + + init(modalType: Binding) { + productManager = .shared + _modalType = modalType + } + + var body: some View { + Section( + header: Text(Unlocalized.Other.siri), + footer: Text(L10n.Organizer.Sections.Siri.footer) + ) { + // eligibility: enter Siri shortcuts or present paywall + if isEligibleForSiri { + NavigationLink { + ShortcutsView() + } label: { + shortcutsRow + } + } else { + Button { + modalType = .presentPaywallShortcuts + } label: { + shortcutsRow + } + } + } + } + + private var shortcutsRow: some View { +// Text(L10n.Organizer.Items.SiriShortcuts.caption) + Label(L10n.Organizer.Items.SiriShortcuts.caption, systemImage: themeShortcutsImage) + } + } +} diff --git a/Passepartout/App/iOS/Views/OrganizerView+VPN.swift b/Passepartout/App/iOS/Views/OrganizerView+VPN.swift new file mode 100644 index 00000000..f44cbded --- /dev/null +++ b/Passepartout/App/iOS/Views/OrganizerView+VPN.swift @@ -0,0 +1,63 @@ +// +// OrganizerView+VPN.swift +// Passepartout +// +// Created by Davide De Rosa on 4/2/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension OrganizerView { + struct RemoveVPNSection: View { + @ObservedObject private var vpnManager: VPNManager + + @State private var isAskingUninstallVPN = false + + init() { + vpnManager = .shared + } + + var body: some View { + Section { + Button { + isAskingUninstallVPN = true + } label: { + Label(L10n.Organizer.Items.Uninstall.caption, systemImage: themeDeleteImage) + }.foregroundColor(themeErrorColor) + .actionSheet(isPresented: $isAskingUninstallVPN) { + ActionSheet( + title: Text(L10n.Organizer.Alerts.UninstallVpn.message), + message: nil, + buttons: [ + .destructive(Text(L10n.Organizer.Items.Uninstall.caption), action: { + Task { + await vpnManager.uninstall() + } + }), + .cancel(Text(L10n.Global.Strings.cancel)) + ] + ) + } + } + } + } +} diff --git a/Passepartout/App/iOS/Views/OrganizerView.swift b/Passepartout/App/iOS/Views/OrganizerView.swift new file mode 100644 index 00000000..388c7cb3 --- /dev/null +++ b/Passepartout/App/iOS/Views/OrganizerView.swift @@ -0,0 +1,275 @@ +// +// OrganizerView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/6/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import StoreKit +import PassepartoutCore + +struct OrganizerView: View { + enum ModalType: Identifiable { + case addProvider + + case addHost(URL, Bool) + + case presentPaywallShortcuts + + // XXX: alert ids + var id: Int { + switch self { + case .addProvider: return 1 + + case .addHost: return 2 + + case .presentPaywallShortcuts: return 3 + } + } + } + + enum AlertType: Identifiable { + case subscribeReddit + + case error(String, Error) + + // XXX: alert ids + var id: Int { + switch self { + case .subscribeReddit: return 1 + + case .error: return 2 + } + } + } + + @ObservedObject private var appManager: AppManager + + @State private var modalType: ModalType? + + @State private var alertType: AlertType? + + @State private var isHostFileImporterPresented = false + + @AppStorage(AppManager.DefaultKey.didHandleSubreddit.rawValue) var didHandleSubreddit = false + + init() { + appManager = .shared + } + + private let hostFileTypes = Constants.URLs.filetypes + + private let redditURL = Constants.URLs.subreddit + + private let appName = Unlocalized.appName + + private let versionString = Constants.Global.appVersionString + + var body: some View { + debugChanges() + return ZStack { + SceneView( + alertType: $alertType, + didHandleSubreddit: $didHandleSubreddit + ) + List { + ProfilesSection( + addProfileMenuBindings: .init( + modalType: $modalType, + alertType: $alertType, + isHostFileImporterPresented: $isHostFileImporterPresented + ) + ) + ShortcutsSection( + modalType: $modalType + ) + supportSection + aboutSection + RemoveVPNSection() +// betaSection + } + }.navigationTitle(Unlocalized.appName) + .toolbar(content: toolbar) + .sheet(item: $modalType, content: presentedModal) + .alert(item: $alertType, content: presentedAlert) + .fileImporter( + isPresented: $isHostFileImporterPresented, + allowedContentTypes: hostFileTypes, + allowsMultipleSelection: false, + onCompletion: onHostFileImporterResult + ).onOpenURL(perform: onOpenURL) + } + + private func toolbar() -> some View { + Menu { + AddProfileMenu( + withImportedURLs: true, + bindings: .init( + modalType: $modalType, + alertType: $alertType, + isHostFileImporterPresented: $isHostFileImporterPresented + ) + ) + } label: { + themeAddProfileImage.asSystemImage + } + } +} + +// MARK: Global handlers + +extension OrganizerView { + + @ViewBuilder + private func presentedModal(_ modalType: ModalType) -> some View { + switch modalType { + case .addProvider: + NavigationView { + AddProviderView( + bindings: .init( + isPresented: isModalPresented + ) + ) + }.themeGlobal() + + case .addHost(let url, let deletingURLOnSuccess): + NavigationView { + AddHostView( + url: url, + deletingURLOnSuccess: deletingURLOnSuccess, + bindings: .init( + isPresented: isModalPresented + ) + ) + }.themeGlobal() + + case .presentPaywallShortcuts: + NavigationView { + PaywallView(feature: .siriShortcuts) + }.themeGlobal() + } + } + + private var isModalPresented: Binding { + .init { + modalType != nil + } set: { + if !$0 { + modalType = nil + } + } + } + + private func presentedAlert(_ alertType: AlertType) -> Alert { + switch alertType { + case .subscribeReddit: + return Alert( + title: Text(Unlocalized.Social.reddit), + message: Text(L10n.Organizer.Alerts.Reddit.message), + primaryButton: .default(Text(L10n.Organizer.Alerts.Reddit.Buttons.subscribe)) { + didHandleSubreddit = true + URL.openURL(redditURL) + }, + secondaryButton: .cancel(Text(L10n.Organizer.Alerts.Reddit.Buttons.never)) { + didHandleSubreddit = true + } + ) + + case .error(let title, let error): + return Alert( + title: Text(title), + message: Text(error.localizedDescription), + dismissButton: .cancel(Text(L10n.Global.Strings.ok)) + ) + } + } + + private func onHostFileImporterResult(_ result: Result<[URL], Error>) { + switch result { + case .success(let urls): + guard let url = urls.first else { + assertionFailure("Empty URLs from file importer?") + return + } + modalType = .addHost(url, false) + + case .failure(let error): + alertType = .error( + L10n.Organizer.Items.AddHost.caption, + error + ) + } + } + + private func onOpenURL(_ url: URL) { + modalType = .addHost(url, false) + } +} + +// MARK: Minor sections + +extension OrganizerView { + private var supportSection: some View { + Section( + header: Text(L10n.Organizer.Sections.Support.header) + ) { + NavigationLink { + DonateView() + } label: { + Label(L10n.Organizer.Items.Donate.caption, systemImage: themeDonateImage) + }.disabled(!SKPaymentQueue.canMakePayments()) + + Button { + URL.openURL(redditURL) + } label: { + Label(L10n.Organizer.Items.JoinCommunity.caption, systemImage: themeRedditImage) + } + Button(action: submitReview) { + Label(L10n.Organizer.Items.WriteReview.caption, systemImage: themeWriteReviewImage) + } + } + } + + private var aboutSection: some View { + Section { + NavigationLink { + AboutView() + } label: { + Text(L10n.Organizer.Items.About.caption(appName)) +// .withTrailingText(versionString) + } + } + } +} + +// MARK: Actions + +extension OrganizerView { + private func presentSubscribeReddit() { + alertType = .subscribeReddit + } + + private func submitReview() { + let reviewURL = Reviewer.urlForReview(withAppId: Constants.App.appStoreId) + URL.openURL(reviewURL) + } +} diff --git a/Passepartout/App/iOS/Views/Paywall/PaywallView+Beta.swift b/Passepartout/App/iOS/Views/Paywall/PaywallView+Beta.swift new file mode 100644 index 00000000..58519607 --- /dev/null +++ b/Passepartout/App/iOS/Views/Paywall/PaywallView+Beta.swift @@ -0,0 +1,36 @@ +// +// PaywallView+Beta.swift +// Passepartout +// +// Created by Davide De Rosa on 3/22/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +extension PaywallView { + struct BetaView: View { + var body: some View { + Text("The requested feature in unavailable in beta.") + .navigationTitle("Beta") + .padding() + } + } +} diff --git a/Passepartout/App/iOS/Views/Paywall/PaywallView+Purchase.swift b/Passepartout/App/iOS/Views/Paywall/PaywallView+Purchase.swift new file mode 100644 index 00000000..ef75ca94 --- /dev/null +++ b/Passepartout/App/iOS/Views/Paywall/PaywallView+Purchase.swift @@ -0,0 +1,311 @@ +// +// PaywallView+Purchase.swift +// Passepartout +// +// Created by Davide De Rosa on 3/12/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import StoreKit +import PassepartoutCore + +extension PaywallView { + struct PurchaseView: View { + private enum AlertType: Identifiable { + case purchaseFailed(SKProduct, Error) + + case restoreFailed(Error) + + var id: Int { + switch self { + case .purchaseFailed: return 1 + + case .restoreFailed: return 2 + } + } + } + + fileprivate enum PurchaseState { + case purchasing(SKProduct) + + case restoring + } + + private typealias RowModel = (product: SKProduct, extra: String?) + + @Environment(\.scenePhase) private var scenePhase + + @Environment(\.presentationMode) private var presentationMode + + @ObservedObject private var productManager: ProductManager + + private let feature: LocalProduct? + + @State private var alertType: AlertType? + + @State private var purchaseState: PurchaseState? + + init(feature: LocalProduct? = nil) { + productManager = .shared + self.feature = feature + } + + var body: some View { + List { + productsSection + .disabled(purchaseState != nil) + }.navigationTitle(Unlocalized.appName) + .alert(item: $alertType, content: presentedAlert) + } + + private func presentedAlert(_ alertType: AlertType) -> Alert { + switch alertType { + case .purchaseFailed(let product, let error): + return Alert( + title: Text(product.localizedTitle), + message: Text(error.localizedDescription), + dismissButton: .default(Text(L10n.Global.Strings.ok)) { + purchaseState = nil + } + ) + + case .restoreFailed(let error): + return Alert( + title: Text(L10n.Paywall.Items.Restore.title), + message: Text(error.localizedDescription), + dismissButton: .default(Text(L10n.Global.Strings.ok)) { + purchaseState = nil + } + ) + } + } + + private var productsSection: some View { + ReloadingSection( + header: Text(L10n.Paywall.title), + footer: Text(L10n.Paywall.Sections.Products.footer), + elements: productManager.products, + equality: { + Set($0.map(\.productIdentifier)) == Set($1.map(\.productIdentifier)) + }, + isReloading: productManager.isRefreshingProducts, + reload: { + productManager.refreshProducts() + }, + content: { _ in + ForEach(productRowModels, id: \.product.productIdentifier, content: productRow) + restoreRow + } + ) + } + + private func productRow(_ model: RowModel) -> some View { + PurchaseRow( + product: model.product, + title: model.product.localizedTitle, + extra: model.extra, + action: { + purchaseProduct(model.product) + }, + purchaseState: purchaseState + ) + } + + private var restoreRow: some View { + PurchaseRow( + title: L10n.Paywall.Items.Restore.title, + extra: L10n.Paywall.Items.Restore.description, + action: restorePurchases, + purchaseState: purchaseState + ) + } + } +} + +extension PaywallView.PurchaseView { + private func purchaseProduct(_ product: SKProduct) { + purchaseState = .purchasing(product) + + productManager.purchase(product) { + switch $0 { + case .success(let result): + switch result { + case .done: + presentationMode.wrappedValue.dismiss() + + case .cancelled: + break + } + purchaseState = nil + + case .failure(let error): + pp_log.error("Unable to purchase: \(error)") + alertType = .purchaseFailed(product, error) + } + } + } + + private func restorePurchases() { + purchaseState = .restoring + + productManager.restorePurchases { + if let error = $0 { + pp_log.error("Unable to restore purchases: \(error)") + alertType = .restoreFailed(error) + return + } + presentationMode.wrappedValue.dismiss() + purchaseState = nil + } + } +} + +extension PaywallView.PurchaseView { + private var skFeature: SKProduct? { + guard let feature = feature else { + return nil + } + return productManager.product(withIdentifier: feature) + } + + private var skPlatformVersion: SKProduct? { + #if os(iOS) + return productManager.product(withIdentifier: .fullVersion_iOS) + #else + return productManager.product(withIdentifier: .fullVersion_macOS) + #endif + } + + // hide full version if already bought the other platform version + private var skFullVersion: SKProduct? { + #if os(iOS) + guard !productManager.hasPurchased(.fullVersion_macOS) else { + return nil + } + #else + guard !productManager.hasPurchased(.fullVersion_iOS) else { + return nil + } + #endif + return productManager.product(withIdentifier: .fullVersion) + } + + private var platformVersionExtra: [String] { + return productManager.featureProducts(excluding: [ + .fullVersion, + .fullVersion_iOS, + .fullVersion_macOS + ]).map { + $0.localizedTitle + }.sorted { + $0.lowercased() < $1.lowercased() + } + } + + private var fullVersionExtra: [String] { + return productManager.featureProducts(including: [ + .fullVersion_iOS, + .fullVersion_macOS + ]).map { + $0.localizedTitle + }.sorted { + $0.lowercased() < $1.lowercased() + } + } + + private var productRowModels: [RowModel] { + var models: [RowModel] = [] + skPlatformVersion.map { + let extra = platformVersionExtra.joined(separator: "\n") + models.append(($0, extra)) + } + skFullVersion.map { + let extra = fullVersionExtra.joined(separator: "\n") + models.append(($0, extra)) + } + skFeature.map { + models.append(($0, nil)) + } + return models + } +} + +private struct PurchaseRow: View { + var product: SKProduct? + + let title: String + + let extra: String? + + let action: () -> Void + + let purchaseState: PaywallView.PurchaseView.PurchaseState? + + var body: some View { + VStack { + actionButton + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.bottom, 5.0) +// .border(.black, width: 1.0) + + extra.map { + Text($0) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .multilineTextAlignment(.leading) +// .xxxThemeTruncation() + } + }.padding([.top, .bottom]) + } + + @ViewBuilder + private var actionButton: some View { + if let product = product { + purchaseButton(product) + } else { + restoreButton + } + } + + private func purchaseButton(_ product: SKProduct) -> some View { + HStack { + Button(title, action: action) + Spacer() + if case .purchasing(let pending) = purchaseState, pending.productIdentifier == product.productIdentifier { + ProgressView() + } else { + product.localizedPrice.map { + Text($0) + .foregroundColor(themeSecondaryColor) + } + } + } + } + + private var restoreButton: some View { + HStack { + Button(title, action: action) + Spacer() + if case .restoring = purchaseState { + ProgressView() + } + } + } +} diff --git a/Passepartout/App/iOS/Views/Paywall/PaywallView.swift b/Passepartout/App/iOS/Views/Paywall/PaywallView.swift new file mode 100644 index 00000000..3128d928 --- /dev/null +++ b/Passepartout/App/iOS/Views/Paywall/PaywallView.swift @@ -0,0 +1,47 @@ +// +// PaywallView.swift +// Passepartout +// +// Created by Davide De Rosa on 3/12/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct PaywallView: View { + @ObservedObject private var productManager: ProductManager + + private let feature: LocalProduct? + + init(feature: LocalProduct? = nil) { + productManager = .shared + self.feature = feature + } + + var body: some View { + Group { + if productManager.cfg.appType == .beta { + BetaView() + } else { + PurchaseView(feature: feature) + } + }.themeSecondaryView() + } +} diff --git a/Passepartout/App/iOS/Views/Profile/ProfileView+Rename.swift b/Passepartout/App/iOS/Views/Profile/ProfileView+Rename.swift new file mode 100644 index 00000000..aa730781 --- /dev/null +++ b/Passepartout/App/iOS/Views/Profile/ProfileView+Rename.swift @@ -0,0 +1,104 @@ +// +// ProfileView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/6/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension ProfileView { + struct RenameView: View { + @Environment(\.presentationMode) private var presentationMode + + @ObservedObject private var profileManager: ProfileManager + + @ObservedObject private var currentProfile: ObservableProfile + + @State private var newName = "" + + @State private var isOverwritingExistingProfile = false + + init(currentProfile: ObservableProfile) { + profileManager = .shared + self.currentProfile = currentProfile + } + + var body: some View { + List { + Section( + header: Text(L10n.Profile.Alerts.Rename.title) + ) { + TextField(L10n.Global.Placeholders.profileName, text: $newName, onCommit: commitRenaming) + .disableAutocorrection(true) + .onAppear(perform: loadCurrentName) + } + }.themeSecondaryView() + .navigationTitle(currentProfile.value.header.name) + .toolbar { + Button(action: commitRenaming) { + themeDoneButtonLabel() + } + }.alert(isPresented: $isOverwritingExistingProfile, content: alertOverwriteExistingProfile) + } + + private func alertOverwriteExistingProfile() -> Alert { + return Alert( + title: Text(L10n.Profile.Alerts.Rename.title), + message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message), + primaryButton: .destructive(Text(L10n.Global.Strings.ok)) { + commitRenaming(force: true) + }, + secondaryButton: .cancel(Text(L10n.Global.Strings.cancel)) + ) + } + + private func loadCurrentName() { + newName = currentProfile.value.header.name + } + + private func commitRenaming() { + commitRenaming(force: false) + } + + private func commitRenaming(force: Bool) { + let name = newName.stripped + + guard !name.isEmpty else { + return + } + guard name != currentProfile.value.header.name else { + presentationMode.wrappedValue.dismiss() + return + } + guard force || !profileManager.isExistingProfile(withName: name) else { + isOverwritingExistingProfile = true + return + } + + let renamed = currentProfile.value.renamed(to: name) + profileManager.saveProfile(renamed, isActive: nil) + + presentationMode.wrappedValue.dismiss() + } + } +} diff --git a/Passepartout/App/iOS/Views/ProfileHeaderRow.swift b/Passepartout/App/iOS/Views/ProfileHeaderRow.swift new file mode 100644 index 00000000..d8fc1999 --- /dev/null +++ b/Passepartout/App/iOS/Views/ProfileHeaderRow.swift @@ -0,0 +1,52 @@ +// +// ProfileHeaderRow.swift +// Passepartout +// +// Created by Davide De Rosa on 3/13/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +struct ProfileHeaderRow: View { + let header: Profile.Header + + var body: some View { + if let name = header.providerName { + providerView(name) + } else { + hostView + } + } + + private func providerView(_ name: ProviderName) -> some View { +// Label(header.name, systemImage: themeProviderImage) +// Label(header.name, image: themeAssetsProviderImage(name)) + Text(header.name) + .themeLongText() + } + + private var hostView: some View { +// Label(header.name, systemImage: themeHostImage) + Text(header.name) + .themeLongText() + } +} diff --git a/Passepartout/App/iOS/Views/ProfileView+Configuration.swift b/Passepartout/App/iOS/Views/ProfileView+Configuration.swift new file mode 100644 index 00000000..23b37200 --- /dev/null +++ b/Passepartout/App/iOS/Views/ProfileView+Configuration.swift @@ -0,0 +1,105 @@ +// +// ProfileView+Configuration.swift +// Passepartout +// +// Created by Davide De Rosa on 3/27/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension ProfileView { + struct ConfigurationSection: View { + @ObservedObject private var productManager: ProductManager + + @ObservedObject private var currentProfile: ObservableProfile + + @Binding private var modalType: ModalType? + + private var isEligibleForTrustedNetworks: Bool { + productManager.isEligible(forFeature: .trustedNetworks) + } + + init(currentProfile: ObservableProfile, modalType: Binding) { + productManager = .shared + self.currentProfile = currentProfile + _modalType = modalType + } + + var body: some View { + Section( + header: Text(L10n.Profile.Sections.Configuration.header) + ) { + if currentProfile.value.vpnProtocols.count > 1 { + themeTextPicker( + L10n.Global.Strings.protocol, + selection: $currentProfile.value.currentVPNProtocol, + values: currentProfile.value.vpnProtocols, + description: \.description + ) + } else { + Label(L10n.Global.Strings.protocol, systemImage: themeVPNProtocolImage) + .withTrailingText(currentProfile.value.currentVPNProtocol.description) + } + NavigationLink { + EndpointView(currentProfile: currentProfile) + } label: { + Label(L10n.Global.Strings.endpoint, systemImage: themeEndpointImage) + } + if currentProfile.value.requiresCredentials { + NavigationLink { + AccountView( + providerName: currentProfile.value.header.providerName, + vpnProtocol: currentProfile.value.currentVPNProtocol, + account: $currentProfile.value.account + ) + } label: { + Label(L10n.Account.title, systemImage: themeAccountImage) + } + } + NavigationLink { + NetworkSettingsView(currentProfile: currentProfile) + } label: { + Label(L10n.NetworkSettings.title, systemImage: themeNetworkSettingsImage) + } + + // eligibility: enter trusted networks or present paywall + if isEligibleForTrustedNetworks { + NavigationLink { + OnDemandView(currentProfile: currentProfile) + } label: { + onDemandRow + } + } else { + Button { + modalType = .paywallTrustedNetworks + } label: { + onDemandRow + } + } + } + } + + private var onDemandRow: some View { + Label(L10n.OnDemand.title, systemImage: themeOnDemandImage) + } + } +} diff --git a/Passepartout/App/iOS/Views/ProfileView+Diagnostics.swift b/Passepartout/App/iOS/Views/ProfileView+Diagnostics.swift new file mode 100644 index 00000000..4f9fb5cb --- /dev/null +++ b/Passepartout/App/iOS/Views/ProfileView+Diagnostics.swift @@ -0,0 +1,76 @@ +// +// ProfileView+Diagnostics.swift +// Passepartout +// +// Created by Davide De Rosa on 3/27/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension ProfileView { + struct DiagnosticsSection: View { + @ObservedObject private var profileManager: ProfileManager + + @ObservedObject private var currentProfile: ObservableProfile + + private var isActiveProfile: Bool { + profileManager.isCurrentProfileActive() + } + + private var vpnProtocol: VPNProtocolType { + currentProfile.value.currentVPNProtocol + } + + private var providerName: ProviderName? { + currentProfile.value.header.providerName + } + + private let faqURL = Constants.URLs.faq + + init(currentProfile: ObservableProfile) { + profileManager = .shared + self.currentProfile = currentProfile + } + + var body: some View { + Section( + header: Text(L10n.Profile.Sections.Feedback.header) + ) { + if isActiveProfile { + NavigationLink { + DiagnosticsView( + vpnProtocol: vpnProtocol, + providerName: providerName + ) + } label: { + Label(L10n.Diagnostics.title, systemImage: themeDiagnosticsImage) + } + } + Button { + URL.openURL(faqURL) + } label: { + Label(Unlocalized.About.faq, systemImage: themeFAQImage) + } + } + } + } +} diff --git a/Passepartout/App/iOS/Views/ProfileView+Extra.swift b/Passepartout/App/iOS/Views/ProfileView+Extra.swift new file mode 100644 index 00000000..476c0e71 --- /dev/null +++ b/Passepartout/App/iOS/Views/ProfileView+Extra.swift @@ -0,0 +1,58 @@ +// +// ProfileView+Extra.swift +// Passepartout +// +// Created by Davide De Rosa on 3/27/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension ProfileView { + struct ExtraSection: View { + @ObservedObject private var currentProfile: ObservableProfile + + init(currentProfile: ObservableProfile) { + self.currentProfile = currentProfile + } + + var body: some View { + if currentProfile.value.isProvider { + Section( + footer: Text(L10n.Profile.Sections.VpnResolvesHostname.footer) + ) { + Toggle( + L10n.Profile.Items.VpnResolvesHostname.caption, + isOn: $currentProfile.value.networkSettings.resolvesHostname + ) + } + } + Section( + footer: Text(L10n.Profile.Sections.VpnSurvivesSleep.footer) + ) { + Toggle( + L10n.Profile.Items.VpnSurvivesSleep.caption, + isOn: $currentProfile.value.networkSettings.keepsAliveOnSleep + ) + } + } + } +} diff --git a/Passepartout/App/iOS/Views/ProfileView+Provider.swift b/Passepartout/App/iOS/Views/ProfileView+Provider.swift new file mode 100644 index 00000000..c6d42638 --- /dev/null +++ b/Passepartout/App/iOS/Views/ProfileView+Provider.swift @@ -0,0 +1,127 @@ +// +// ProfileView+Provider.swift +// Passepartout +// +// Created by Davide De Rosa on 3/18/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension ProfileView { + struct ProviderSection: View { + @ObservedObject private var providerManager: ProviderManager + + @ObservedObject private var currentProfile: ObservableProfile + + @State private var isProviderLocationPresented = false + + @State private var isRefreshingInfrastructure = false + + init(currentProfile: ObservableProfile) { + providerManager = .shared + self.currentProfile = currentProfile + } + + var body: some View { + debugChanges() + return Group { + if !isEmpty { + mainView + } else { + EmptyView() + } + } + } + + private var isEmpty: Bool { + currentProfile.value.isPlaceholder || !currentProfile.value.isProvider + } + + private var mainView: some View { + Section( + header: Text(currentProvider.fullName), + footer: lastInfrastructureUpdate.map { + Text(L10n.Profile.Sections.ProviderInfrastructure.footer($0)) + } + ) { + NavigationLink(isActive: $isProviderLocationPresented) { + ProviderLocationView( + currentProfile: currentProfile, + isEditable: true, + isPresented: $isProviderLocationPresented + ) + } label: { + HStack { + Label(L10n.Provider.Location.title, systemImage: themeProviderLocationImage) + Spacer() + currentProviderCountryImage + } + } + NavigationLink { + ProviderPresetView(currentProfile: currentProfile) + } label: { + Label(L10n.Provider.Preset.title, systemImage: themeProviderPresetImage) + .withTrailingText(currentProviderPreset) + } + Button(action: refreshInfrastructure) { + Text(L10n.Profile.Items.Provider.Refresh.caption) + }.withTrailingProgress(when: isRefreshingInfrastructure) + } + } + + private var currentProvider: ProviderMetadata { + guard let name = currentProfile.value.header.providerName else { + fatalError("Provider name accessed but profile is not a provider (isPlaceholder? \(currentProfile.value.isPlaceholder))") + } + guard let metadata = providerManager.provider(withName: name) else { + fatalError("Provider metadata not found") + } + return metadata + } + +// private var currentProviderLocation: String? { +// return providerManager.localizedLocation(forProfile: profile) +// } + private var currentProviderCountryImage: Image? { + guard let code = currentProfile.value.providerServer(providerManager)?.countryCode else { + return nil + } + return themeAssetsCountryImage(code).asAssetImage + } + + private var currentProviderPreset: String? { + return providerManager.localizedPreset(forProfile: currentProfile.value) + } + + private var lastInfrastructureUpdate: String? { + return providerManager.localizedInfrastructureUpdate(forProfile: currentProfile.value) + } + + private func refreshInfrastructure() { + isRefreshingInfrastructure = true + Task { + try await providerManager.fetchRemoteProviderPublisher(forProfile: currentProfile.value).async() + isRefreshingInfrastructure = false + } + } + } +} diff --git a/Passepartout/App/iOS/Views/ProfileView+VPN.swift b/Passepartout/App/iOS/Views/ProfileView+VPN.swift new file mode 100644 index 00000000..24627a71 --- /dev/null +++ b/Passepartout/App/iOS/Views/ProfileView+VPN.swift @@ -0,0 +1,127 @@ +// +// ProfileView+VPN.swift +// Passepartout +// +// Created by Davide De Rosa on 3/18/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +extension ProfileView { + struct VPNSection: View { + @ObservedObject private var profileManager: ProfileManager + + @ObservedObject private var currentProfile: ObservableProfile + + @ObservedObject private var providerManager: ProviderManager + + @ObservedObject private var vpnManager: VPNManager + + @ObservedObject private var currentVPNState: VPNManager.ObservableState + + @ObservedObject private var productManager: ProductManager + + private var isActiveProfile: Bool { + profileManager.isCurrentProfileActive() + } + + private var isEligibleForSiri: Bool { + productManager.isEligible(forFeature: .siriShortcuts) + } + + init(currentProfile: ObservableProfile) { + profileManager = .shared + providerManager = .shared + vpnManager = .shared + currentVPNState = .shared + productManager = .shared + self.currentProfile = currentProfile + } + + var body: some View { + if isActiveProfile { + activeView + } else { + inactiveSubview + } + } + + private var activeView: some View { + Section( + header: Text(Unlocalized.VPN.vpn), + footer: Text(L10n.Profile.Sections.Vpn.footer) + .xxxThemeTruncation() + ) { + HStack { + Button(vpnToggleString, action: toggleVPNAndDonateIntents) + .disabled(vpnManager.isRateLimiting) + Spacer() + Toggle("", isOn: .constant(currentVPNState.isEnabled)) + .disabled(true) + } + Text(L10n.Profile.Items.ConnectionStatus.caption) + .withTrailingText(currentVPNState.localizedStatusDescription( + withErrors: true, + withDataCount: true + )) + } + } + + private var vpnToggleString: String { + let V = L10n.Profile.Items.Vpn.self + return currentVPNState.isEnabled ? V.TurnOff.caption : V.TurnOn.caption + } + + private var inactiveSubview: some View { + Section( + header: Text(Unlocalized.VPN.vpn) + ) { + Button(L10n.Profile.Items.UseProfile.caption) { + withAnimation { + profileManager.activateCurrentProfile() + } + Task { + await vpnManager.disable() + } + } + } + } + + private func toggleVPNAndDonateIntents() { + guard vpnManager.toggle() else { + return + } + + // eligibility: donate intents if eligible for Siri + if isEligibleForSiri { + pp_log.debug("Donating connection intents...") + + IntentDispatcher.donateEnableVPN() + IntentDispatcher.donateDisableVPN() + IntentDispatcher.donateConnection( + with: currentProfile.value, + providerManager: providerManager + ) + } + } + } +} diff --git a/Passepartout/App/iOS/Views/ProfileView+Welcome.swift b/Passepartout/App/iOS/Views/ProfileView+Welcome.swift new file mode 100644 index 00000000..74edc774 --- /dev/null +++ b/Passepartout/App/iOS/Views/ProfileView+Welcome.swift @@ -0,0 +1,35 @@ +// +// ProfileView+Welcome.swift +// Passepartout +// +// Created by Davide De Rosa on 2/9/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +extension ProfileView { + struct WelcomeView: View { + var body: some View { + Text(L10n.Profile.Welcome.message) + .multilineTextAlignment(.center) + } + } +} diff --git a/Passepartout/App/iOS/Views/ProfileView.swift b/Passepartout/App/iOS/Views/ProfileView.swift new file mode 100644 index 00000000..4800e919 --- /dev/null +++ b/Passepartout/App/iOS/Views/ProfileView.swift @@ -0,0 +1,217 @@ +// +// ProfileView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/6/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import Combine +import PassepartoutCore + +struct ProfileView: View { + enum ModalType: Int, Identifiable { + case rename + + case paywallTrustedNetworks + + var id: Int { + return rawValue + } + } + + @Environment(\.presentationMode) private var presentationMode + + @ObservedObject private var profileManager: ProfileManager + + private let header: Profile.Header + + private var isDeleted: Bool { + !profileManager.isExistingProfile(withId: header.id) + } + + @State private var modalType: ModalType? + + @State private var isLoaded = false + + @State private var isAskingRemoveProfile = false + + init(header: Profile.Header?) { + let profileManager: ProfileManager = .shared + + self.profileManager = profileManager + self.header = header ?? profileManager.activeHeader ?? Profile.placeholder.header + } + + var body: some View { + debugChanges() + return Group { + if !isDeleted { + List { + if isLoaded { + mainView + } else { + loadingSection + } + } + } else { + welcomeView + } + }.themeSecondaryView() + .navigationTitle(title) + .toolbar(content: toolbar) + .sheet(item: $modalType, content: presentedModal) + .onAppear(perform: loadProfileIfNeeded) + } + + private var title: String { + !isDeleted ? header.name : "" + } + + @ViewBuilder + private var mainView: some View { + VPNSection(currentProfile: profileManager.currentProfile) + ProviderSection(currentProfile: profileManager.currentProfile) + ConfigurationSection( + currentProfile: profileManager.currentProfile, + modalType: $modalType + ) + ExtraSection(currentProfile: profileManager.currentProfile) + DiagnosticsSection(currentProfile: profileManager.currentProfile) + removeProfileSection + } + + private var welcomeView: some View { + WelcomeView() + } + + private func toolbar() -> some ToolbarContent { + ToolbarItemGroup(placement: .navigationBarTrailing) { + if !isDeleted { + Button { + modalType = .rename + } label: { + Image(systemName: themeRenameProfileImage) + } + } + } + } + + @ViewBuilder + private func presentedModal(_ modalType: ModalType) -> some View { + switch modalType { + case .rename: + NavigationView { + RenameView(currentProfile: profileManager.currentProfile) + }.themeGlobal() + + case .paywallTrustedNetworks: + NavigationView { + PaywallView( + feature: .trustedNetworks + ) + }.themeGlobal() + } + } + + private var loadingSection: some View { + Section( + header: Text(Unlocalized.VPN.vpn) + ) { + ProgressView() + } + } + + private var removeProfileSection: some View { + Section { + Button { + isAskingRemoveProfile = true + } label: { + Label(L10n.Organizer.Alerts.RemoveProfile.title, systemImage: themeDeleteImage) + }.foregroundColor(themeErrorColor) + .actionSheet(isPresented: $isAskingRemoveProfile) { + ActionSheet( + title: Text(L10n.Organizer.Alerts.RemoveProfile.message(header.name)), + message: nil, + buttons: [ + .destructive(Text(L10n.Organizer.Alerts.RemoveProfile.title), action: confirmRemoveProfile), + .cancel(Text(L10n.Global.Strings.cancel)) + ] + ) + } + } + } + + private func loadProfileIfNeeded() { + guard !isLoaded else { + return + } + guard !header.isPlaceholder else { + pp_log.debug("ProfileView is a placeholder for WelcomeView, no active profile") + return + } + do { + let result = try profileManager.loadCurrentProfile(withId: header.id) + if result.isReady { + isLoaded = true + return + } + Task { + do { + try await profileManager.makeProfileReady(result.profile) + withAnimation { + isLoaded = true + } + } catch { + pp_log.error("Profile \(header.id) could not be made ready: \(error)") + presentationMode.wrappedValue.dismiss() + } + } + } catch { + pp_log.error("Profile \(header.id) could not be loaded: \(error)") + presentationMode.wrappedValue.dismiss() + } + } + + private func confirmRemoveProfile() { + withAnimation { + removeProfile() + } + } + + private func removeProfile() { + guard profileManager.isExistingProfile(withId: header.id) else { + assertionFailure("Deleting non-existent profile \(header.name)") + return + } + IntentDispatcher.forgetProfile(withHeader: header) + profileManager.removeProfiles(withIds: [header.id]) + + // XXX: iOS 14, NavigationLink removal via header removal in OrganizerView+Profiles doesn't pop + if #unavailable(iOS 15) { + presentationMode.wrappedValue.dismiss() + } + } + + private func presentPaywallTrustedNetworks() { + modalType = .paywallTrustedNetworks + } +} diff --git a/Passepartout/App/iOS/Views/ProviderLocationView.swift b/Passepartout/App/iOS/Views/ProviderLocationView.swift new file mode 100644 index 00000000..a5040e03 --- /dev/null +++ b/Passepartout/App/iOS/Views/ProviderLocationView.swift @@ -0,0 +1,321 @@ +// +// ProviderLocationView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +struct ProviderLocationView: View { + @ObservedObject private var appManager: AppManager + + @ObservedObject private var providerManager: ProviderManager + + @ObservedObject private var currentProfile: ObservableProfile + + private let isEditable: Bool + + private var providerName: ProviderName { + guard let name = currentProfile.value.header.providerName else { + assertionFailure("Not a provider") + return "" + } + return name + } + + private var vpnProtocol: VPNProtocolType { + currentProfile.value.currentVPNProtocol + } + + @Binding private var selectedServer: ProviderServer? + + @Binding private var favoriteLocationIds: Set? + + @AppStorage(AppManager.DefaultKey.isShowingFavorites.rawValue) private var isShowingFavorites = false + + private var isShowingEmptyFavorites: Bool { + guard isShowingFavorites else { + return false + } + return favoriteLocationIds?.isEmpty ?? true + } + + // XXX: do not escape mutating 'self', use constant providerManager + init(currentProfile: ObservableProfile, isEditable: Bool, isPresented: Binding) { + let providerManager: ProviderManager = .shared + + appManager = .shared + self.providerManager = providerManager + self.currentProfile = currentProfile + self.isEditable = isEditable + + _selectedServer = .init { + guard let serverId = currentProfile.value.providerServerId() else { + return nil + } + return providerManager.server(withId: serverId) + } set: { + // user never selects a nil server + guard let server = $0 else { + return + } + currentProfile.value.setProviderServer(server) + isPresented.wrappedValue = false + } + _favoriteLocationIds = .init { + currentProfile.value.providerFavoriteLocationIds() + } set: { + currentProfile.value.setProviderFavoriteLocationIds($0) + } + } + + var body: some View { + debugChanges() + return Group { + if !isEmpty { + mainView + } else { + EmptyView() + } + }.navigationTitle(L10n.Provider.Location.title) + .toolbar(content: toolbar) + } + + private var isEmpty: Bool { + currentProfile.value.isPlaceholder || !currentProfile.value.isProvider + } + + private var mainView: some View { + ScrollViewReader { scrollProxy in + List { + if !isShowingEmptyFavorites { + categoriesView + } else { + emptyFavoritesSection + } + }.onAppear { + scrollToSelectedLocation(scrollProxy) + } + } + } + + @ViewBuilder + private func toolbar() -> some View { + if #available(iOS 15, macOS 12, *) { + Button { + withAnimation { + isShowingFavorites.toggle() + } + } label: { + themeFavoritesImage(isShowingFavorites).asSystemImage + } + } else { + self + } + } + + private var categoriesView: some View { + ForEach(categories, content: categorySection) + } + + private func categorySection(_ category: ProviderCategory) -> some View { + Section( + header: !category.name.isEmpty ? Text(category.name) : nil + ) { + ForEach(filteredLocations(for: category)) { location in + if isEditable, #available(iOS 15, macOS 12, *) { + locationRow(location) + .swipeActions(edge: .trailing, allowsFullSwipe: false) { + favoriteActions(location) + } + } else { + locationRow(location) + } + } + } + } + + @ViewBuilder + private func locationRow(_ location: ProviderLocation) -> some View { + if let onlyServer = location.onlyServer { + singleServerRow(location, onlyServer) + } else { + multipleServersRow(location) + } + } + + private func multipleServersRow(_ location: ProviderLocation) -> some View { + NavigationLink(destination: { + ServerListView( + location: location, + selectedServer: $selectedServer + ).navigationTitle(location.localizedCountry) + }, label: { + LocationRow( + location: location, + selectedLocationId: selectedServer?.locationId + ) + }) + } + + private func singleServerRow(_ location: ProviderLocation, _ server: ProviderServer) -> some View { + Button { + selectedServer = server + } label: { + LocationRow( + location: location, + selectedLocationId: selectedServer?.locationId + ) + } + } + + private var emptyFavoritesSection: some View { + Section( + footer: Text(L10n.Provider.Location.Sections.EmptyFavorites.footer) + ) { + } + } + + @available(iOS 15, macOS 12, *) + private func favoriteActions(_ location: ProviderLocation) -> some View { + Button { + withAnimation { + toggleFavoriteLocation(location) + } + } label: { + themeFavoriteActionImage(!isFavoriteLocation(location)).asSystemImage + }.tint(themePrimaryBackgroundColor) + } +} + +extension ProviderLocationView { + private func server(withId serverId: String) -> ProviderServer? { + providerManager.server(withId: serverId) + } + + private var categories: [ProviderCategory] { + providerManager.categories(providerName, vpnProtocol: vpnProtocol) + .filter { + !filteredLocations(for: $0).isEmpty + }.sorted() + } + + private func filteredLocations(for category: ProviderCategory) -> [ProviderLocation] { + let locations: [ProviderLocation] + if isShowingFavorites { + locations = category.locations.filter { + favoriteLocationIds?.contains($0.id) ?? false + } + } else { + locations = category.locations + } + return locations.sorted() + } + + private func isFavoriteLocation(_ location: ProviderLocation) -> Bool { + return favoriteLocationIds?.contains(location.id) ?? false + } + + private func toggleFavoriteLocation(_ location: ProviderLocation) { + if !isFavoriteLocation(location) { + if favoriteLocationIds == nil { + favoriteLocationIds = [location.id] + } else { + favoriteLocationIds?.insert(location.id) + } + } else { + favoriteLocationIds?.remove(location.id) + } + // may trigger view updates? +// pp_log.debug("New favorite locations: \(favoriteLocationIds ?? [])") + } +} + +extension ProviderLocationView { + struct LocationRow: View { + let location: ProviderLocation + + let selectedLocationId: String? + + var body: some View { + HStack { + themeAssetsCountryImage(location.countryCode).asAssetImage + VStack { + if let singleServer = location.onlyServer, let _ = singleServer.details { + Text(location.localizedCountry) + .frame(maxWidth: .infinity, alignment: .leading) + Text(singleServer.localizedDetails.uppercased()) + .frame(maxWidth: .infinity, alignment: .leading) + } else { + Text(location.localizedCountry) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + } + }.withTrailingCheckmark(when: location.id == selectedLocationId) + }.frame(height: 60) + } + } + + struct ServerListView: View { + @ObservedObject private var providerManager: ProviderManager + + private let location: ProviderLocation + + @Binding private var selectedServer: ProviderServer? + + init(location: ProviderLocation, selectedServer: Binding) { + providerManager = .shared + self.location = location + _selectedServer = selectedServer + } + + var body: some View { + ScrollViewReader { scrollProxy in + List { + ForEach(servers) { server in + Button(server.localizedDetailsWithDefault) { + selectedServer = server + }.withTrailingCheckmark(when: server.id == selectedServer?.id) + } + }.onAppear { + scrollToSelectedServer(scrollProxy) + } + } + } + + private var servers: [ProviderServer] { + return providerManager.servers(forLocation: location).sorted() + } + } +} + +extension ProviderLocationView { + private func scrollToSelectedLocation(_ proxy: ScrollViewProxy) { + proxy.maybeScrollTo(selectedServer?.locationId) + } +} + +extension ProviderLocationView.ServerListView { + private func scrollToSelectedServer(_ proxy: ScrollViewProxy) { + proxy.maybeScrollTo(selectedServer?.id) + } +} diff --git a/Passepartout/App/iOS/Views/ProviderPresetView.swift b/Passepartout/App/iOS/Views/ProviderPresetView.swift new file mode 100644 index 00000000..6ee8c0ec --- /dev/null +++ b/Passepartout/App/iOS/Views/ProviderPresetView.swift @@ -0,0 +1,104 @@ +// +// ProviderPresetView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import PassepartoutCore + +struct ProviderPresetView: View { + @Environment(\.presentationMode) private var presentationMode + + @ObservedObject private var providerManager: ProviderManager + + @ObservedObject private var currentProfile: ObservableProfile + + private var server: ProviderServer? + + @Binding private var selectedPreset: ProviderServer.Preset? + + // XXX: do not escape mutating 'self', use constant providerManager + init(currentProfile: ObservableProfile) { + let providerManager: ProviderManager = .shared + + self.providerManager = providerManager + self.currentProfile = currentProfile + + server = currentProfile.value.providerServer(providerManager) + _selectedPreset = .init { + guard let serverId = currentProfile.value.providerServerId() else { + return nil + } + guard let server = providerManager.server(withId: serverId) else { + return nil + } + return currentProfile.value.providerPreset(server) + } set: { + // user never selects a nil preset + guard let preset = $0 else { + return + } + currentProfile.value.setProviderPreset(preset) + } + } + + var body: some View { + List { + ForEach(availablePresets, id: \.id, content: presetSection) + }.navigationTitle(L10n.Provider.Preset.title) + } + + private func presetSection(_ preset: ProviderServer.Preset) -> some View { + Section( + header: Text(preset.name) + ) { + Button { + selectedPreset = preset + presentationMode.wrappedValue.dismiss() + } label: { + presetSelectionRow(preset) + } + NavigationLink(L10n.Endpoint.Advanced.title) { + + // TODO: WireGuard, preset assumes OpenVPN (read current protocol instead) + preset.openVPNConfiguration.map { + EndpointAdvancedView.OpenVPNView( + builder: .constant($0.builder()), + isReadonly: true, + isServerPushed: false + ).navigationTitle(preset.name) + } + } + } + } + + private func presetSelectionRow(_ preset: ProviderServer.Preset) -> some View { + Text(preset.comment) + .withTrailingCheckmark(when: preset.id == selectedPreset?.id) + } + + // some providers (e.g. NordVPN) have specific presets based on selected server + private var availablePresets: [ProviderServer.Preset] { + return server?.presets?.sorted() ?? [] + } +} diff --git a/Passepartout/App/iOS/Views/ReportIssueView.swift b/Passepartout/App/iOS/Views/ReportIssueView.swift new file mode 100644 index 00000000..a9a00adb --- /dev/null +++ b/Passepartout/App/iOS/Views/ReportIssueView.swift @@ -0,0 +1,90 @@ +// +// ReportIssueView.swift +// Passepartout +// +// Created by Davide De Rosa on 3/24/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import MessageUI +import PassepartoutCore + +struct ReportIssueView: View { + @Binding private var isPresented: Bool + + private let toRecipients: [String] + + private let subject: String + + private let messageBody: String + + private let attachments: [MailComposerView.Attachment] + + init( + isPresented: Binding, + vpnProtocol: VPNProtocolType, + logURL: URL?, + providerMetadata: ProviderMetadata? = nil, + lastUpdate: Date? = nil + ) { + _isPresented = isPresented + + toRecipients = [Unlocalized.Issues.recipient] + subject = Unlocalized.Issues.subject + + let bodyContent = Unlocalized.Issues.template + var bodyMetadata = "--\n\n" + bodyMetadata += DebugLog(content: "").decoratedString() + + if let metadata = providerMetadata { + bodyMetadata += "Provider: \(metadata.fullName)\n" + if let lastUpdate = lastUpdate { + bodyMetadata += "Last updated: \(lastUpdate)\n" + } + bodyMetadata += "\n" + } + bodyMetadata += "--" + messageBody = Unlocalized.Issues.body(bodyContent, bodyMetadata) + + var attachments: [MailComposerView.Attachment] = [] + if let logURL = logURL { + let logContent = logURL.trailingContent(bytes: Unlocalized.Issues.maxLogBytes) + let attachment = DebugLog(content: logContent).decoratedData() + + attachments.append(( + attachment, + Unlocalized.Issues.MIME.debugLog, + Unlocalized.Issues.Filenames.debugLog + )) + } + self.attachments = attachments + } + + var body: some View { + MailComposerView( + isPresented: $isPresented, + toRecipients: toRecipients, + subject: subject, + messageBody: messageBody, + attachments: attachments + ) + } +} diff --git a/Passepartout/App/iOS/Views/ShortcutsView+Add.swift b/Passepartout/App/iOS/Views/ShortcutsView+Add.swift new file mode 100644 index 00000000..61ec165b --- /dev/null +++ b/Passepartout/App/iOS/Views/ShortcutsView+Add.swift @@ -0,0 +1,104 @@ +// +// ShortcutsView+Add.swift +// Passepartout +// +// Created by Davide De Rosa on 3/13/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import Intents +import PassepartoutCore + +extension ShortcutsView { + struct AddView: View { + @ObservedObject private var profileManager: ProfileManager + + @StateObject private var pendingProfile = ObservableProfile() + + @Binding private var pendingShortcut: INShortcut? + + init(pendingShortcut: Binding) { + profileManager = .shared + _pendingShortcut = pendingShortcut + } + + var body: some View { + List { + Section( + header: Text(Unlocalized.VPN.vpn) + ) { + NavigationLink(L10n.Shortcuts.Add.Items.Connect.caption) { + ConnectToView( + pendingProfile: pendingProfile, + pendingShortcut: $pendingShortcut + ) + }.disabled(profileManager.headers.isEmpty) + + Button(L10n.Shortcuts.Add.Items.EnableVpn.caption, action: addEnableVPN) + Button(L10n.Shortcuts.Add.Items.DisableVpn.caption, action: addDisableVPN) + } + Section( + header: Text(L10n.Shortcuts.Add.Sections.Wifi.header) + ) { + Button(L10n.Shortcuts.Add.Items.TrustCurrentWifi.caption, action: addTrustWiFi) + Button(L10n.Shortcuts.Add.Items.UntrustCurrentWifi.caption, action: addUntrustWiFi) + } + Section( + header: Text(L10n.Shortcuts.Add.Sections.Cellular.header) + ) { + Button(L10n.Shortcuts.Add.Items.TrustCellular.caption, action: addTrustCellular) + Button(L10n.Shortcuts.Add.Items.UntrustCellular.caption, action: addUntrustCellular) + } + }.navigationTitle(L10n.Shortcuts.Add.title) + } + + private func addEnableVPN() { + addShortcut(with: IntentDispatcher.intentEnable()) + } + + private func addDisableVPN() { + addShortcut(with: IntentDispatcher.intentDisable()) + } + + private func addTrustWiFi() { + addShortcut(with: IntentDispatcher.intentTrustWiFi()) + } + + private func addUntrustWiFi() { + addShortcut(with: IntentDispatcher.intentUntrustWiFi()) + } + + private func addTrustCellular() { + addShortcut(with: IntentDispatcher.intentTrustCellular()) + } + + private func addUntrustCellular() { + addShortcut(with: IntentDispatcher.intentUntrustCellular()) + } + + private func addShortcut(with intent: INIntent) { + guard let shortcut = INShortcut(intent: intent) else { + fatalError("Unable to create INShortcut, intent '\(intent.description)' not exposed by app?") + } + pendingShortcut = shortcut + } + } +} diff --git a/Passepartout/App/iOS/Views/ShortcutsView+ConnectTo.swift b/Passepartout/App/iOS/Views/ShortcutsView+ConnectTo.swift new file mode 100644 index 00000000..230b579d --- /dev/null +++ b/Passepartout/App/iOS/Views/ShortcutsView+ConnectTo.swift @@ -0,0 +1,135 @@ +// +// ShortcutsView+ConnectTo.swift +// Passepartout +// +// Created by Davide De Rosa on 3/13/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import Intents +import PassepartoutCore + +extension ShortcutsView { + struct ConnectToView: View { + @ObservedObject private var profileManager: ProfileManager + + @ObservedObject private var providerManager: ProviderManager + + @ObservedObject private var pendingProfile: ObservableProfile + + @Binding private var pendingShortcut: INShortcut? + + @State private var profileIdMakingReady: UUID? + + @State private var presentedHeader: Profile.Header? + + private var isLocationPresented: Binding { + .init { + presentedHeader != nil + } set: { + if !$0 { + presentedHeader = nil + addMoveToPendingProfile() + } + } + } + + init(pendingProfile: ObservableProfile, pendingShortcut: Binding) { + profileManager = .shared + providerManager = .shared + self.pendingProfile = pendingProfile + _pendingShortcut = pendingShortcut + } + + var body: some View { + debugChanges() + return ZStack { + let headers = profileManager.headers + List { + Section { + ForEach(headers.sorted(), content: profileRow) + .disabled(profileIdMakingReady != nil) + } + } + ForEach(Array(headers.filter { + $0.providerName != nil + }), content: providerLocationLink) + }.navigationTitle(L10n.Shortcuts.Add.Items.Connect.caption) + } + + private func profileRow(_ header: Profile.Header) -> some View { + Button { + if let _ = header.providerName { + Task { + profileIdMakingReady = header.id + await loadAndSelectProfile(withHeader: header) + profileIdMakingReady = nil + } + } else { + addConnect(header) + } + } label: { + ProfileHeaderRow(header: header) + }.withTrailingProgress(when: profileIdMakingReady == header.id) + } + + private func providerLocationLink(_ header: Profile.Header) -> some View { + NavigationLink("", tag: header, selection: $presentedHeader) { + ProviderLocationView( + currentProfile: pendingProfile, + isEditable: false, + isPresented: isLocationPresented + ) + } + } + + private func loadAndSelectProfile(withHeader header: Profile.Header) async { + do { + let result = try profileManager.loadProfile(withId: header.id) + if !result.isReady { + try await profileManager.makeProfileReady(result.profile) + } + pendingProfile.value = result.profile + presentedHeader = header + } catch { + pp_log.error("Unable to select profile: \(error)") + } + } + + private func addConnect(_ header: Profile.Header) { + pendingShortcut = INShortcut(intent: IntentDispatcher.intentConnect( + header: header + )) + } + + private func addMoveToPendingProfile() { + let header = pendingProfile.value.header + guard let server = pendingProfile.value.providerServer(providerManager) else { + return + } + pendingShortcut = INShortcut(intent: IntentDispatcher.intentMoveTo( + header: header, + providerFullName: server.providerMetadata.fullName, + server: server + )) + } + } +} diff --git a/Passepartout/App/iOS/Views/ShortcutsView.swift b/Passepartout/App/iOS/Views/ShortcutsView.swift new file mode 100644 index 00000000..87de4c77 --- /dev/null +++ b/Passepartout/App/iOS/Views/ShortcutsView.swift @@ -0,0 +1,140 @@ +// +// ShortcutsView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/8/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI +import Intents +import PassepartoutCore + +struct ShortcutsView: View { + enum ModalType: Identifiable { + case edit(shortcut: Shortcut) + + case add(shortcut: INShortcut) + + // XXX: alert ids + var id: Int { + switch self { + case .edit: return 1 + + case .add: return 2 + } + } + } + + @ObservedObject private var intentsManager: IntentsManager + + @State private var modalType: ModalType? + + @State private var isNavigationPresented = false + + @State private var pendingShortcut: INShortcut? + + init() { + intentsManager = .shared + } + + var body: some View { + List { + if !intentsManager.shortcuts.isEmpty { + shortcutsSection + } + addSection + }.themeSecondaryView() + .navigationTitle(L10n.Organizer.Items.SiriShortcuts.caption) + .sheet(item: $modalType, content: presentedModal) + .onAppear { + intentsManager.reloadShortcuts() + }.onReceive(intentsManager.shouldDismissIntentView) { _ in + modalType = nil + } + } + + private var shortcutsSection: some View { + Section( + header: Text(L10n.Shortcuts.Edit.Sections.All.header) + ) { + ForEach(intentsManager.shortcuts.values.sorted(), content: rowView) + } + } + + private var addSection: some View { + Section { + NavigationLink(isActive: $isNavigationPresented) { + AddView( + pendingShortcut: delegatingPendingShortcut + ) + } label: { + Text(L10n.Shortcuts.Edit.Items.AddShortcut.caption) + } + } + } + + @ViewBuilder + private func presentedModal(_ modalType: ModalType) -> some View { + switch modalType { + case .edit(let shortcut): + IntentEditView( + shortcut: shortcut, + delegate: intentsManager + ) + + case .add(let shortcut): + IntentAddView( + shortcut: shortcut, + delegate: intentsManager + ) + } + } + + private func rowView(forShortcut vs: Shortcut) -> some View { + Button { + presentEditShortcut(vs) + } label: { + Label(vs.native.invocationPhrase, systemImage: themeShortcutsImage) + } + } + + private var delegatingPendingShortcut: Binding { + .init { + pendingShortcut + } set: { + guard let pendingShortcut = $0 else { + return + } + presentAddShortcut(pendingShortcut) + } + } + + @available(iOS 12, macOS 12, *) + private func presentEditShortcut(_ shortcut: Shortcut) { + modalType = .edit(shortcut: shortcut) + } + + @available(iOS 12, macOS 12, *) + private func presentAddShortcut(_ shortcut: INShortcut) { + isNavigationPresented = false + modalType = .add(shortcut: shortcut) + } +} diff --git a/Passepartout/App/iOS/Views/VersionView.swift b/Passepartout/App/iOS/Views/VersionView.swift new file mode 100644 index 00000000..eed95975 --- /dev/null +++ b/Passepartout/App/iOS/Views/VersionView.swift @@ -0,0 +1,41 @@ +// +// VersionView.swift +// Passepartout +// +// Created by Davide De Rosa on 2/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct VersionView: View { + private let versionString = Constants.Global.appVersionString + + var body: some View { + GenericVersionView( + logoName: themeAssetsLogoImage, + appName: Unlocalized.appName, + versionString: versionString, + extraString: L10n.Version.Labels.intro + ).navigationTitle(L10n.Version.title) + .background(themePrimaryBackground) + .foregroundColor(themeLightTextColor) + } +} diff --git a/Passepartout/App/iOS/swiftgen.yml b/Passepartout/App/iOS/swiftgen.yml deleted file mode 100644 index 759455ea..00000000 --- a/Passepartout/App/iOS/swiftgen.yml +++ /dev/null @@ -1,21 +0,0 @@ -ib: - inputs: - - Base.lproj/About.storyboard - - Base.lproj/Main.storyboard - - Base.lproj/Organizer.storyboard - - Base.lproj/Purchase.storyboard - - Base.lproj/Shortcuts.storyboard - outputs: - - templateName: scenes-swift4 - output: Global/SwiftGen+Scenes.swift - - templateName: segues-swift4 - output: Global/SwiftGen+Segues.swift - -xcassets: - inputs: - - Assets.xcassets - - Flags.xcassets - - Providers.xcassets - outputs: - - templateName: swift4 - output: Global/SwiftGen+Assets.swift diff --git a/Passepartout/App/macOS/AppDelegate.swift b/Passepartout/App/macOS/AppDelegate.swift deleted file mode 100644 index d104d53f..00000000 --- a/Passepartout/App/macOS/AppDelegate.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// AppDelegate.swift -// Passepartout -// -// Created by Davide De Rosa on 6/6/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore -import Convenience -import ServiceManagement - -// comment on release -//import AppCenter -//import AppCenterAnalytics -//import AppCenterCrashes - -extension Notification.Name { - static let killLauncher = Notification.Name("killLauncher") -} - -@NSApplicationMain -class AppDelegate: NSObject, NSApplicationDelegate { - private let appCenterSecret = GroupConstants.App.config?["appcenter_secret"] as? String - - private var importer: HostImporter? - - override init() { - AppConstants.Log.configure() - InfrastructureFactory.shared.preload() - super.init() - } - - func applicationDidFinishLaunching(_ aNotification: Notification) { - Reviewer.shared.eventCountBeforeRating = AppConstants.Rating.eventCount - ProductManager.shared.listProducts(completionHandler: nil) - - NSApp.mainMenu = loadMainMenu() - StatusMenu.shared.install() - ProductManager.shared.reviewPurchases() - -// if let appCenterSecret = appCenterSecret, !appCenterSecret.isEmpty { -// AppCenter.start(withAppSecret: appCenterSecret, services: [Analytics.self, Crashes.self]) -// } - - // launcher configuration - - let launcherAppId = AppConstants.App.appLauncherId - let runningApps = NSWorkspace.shared.runningApplications - let isRunning = !runningApps.filter { $0.bundleIdentifier == launcherAppId }.isEmpty - - if isRunning { - DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!) - } - - if !TransientStore.didHandleSubreddit { - let alert = Macros.warning(L10n.Reddit.title, L10n.Reddit.message) - alert.present(in: nil, withOK: L10n.Reddit.Buttons.subscribe, cancel: L10n.Reddit.Buttons.never, dummy: L10n.Reddit.Buttons.remind, handler: { - TransientStore.didHandleSubreddit = true - self.subscribeSubreddit() - }, cancelHandler: { - TransientStore.didHandleSubreddit = true - }) - } - } - - func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { -// TransientStore.shared.service.preferences.confirmsQuit = true - guard TransientStore.shared.service.preferences.confirmsQuit ?? true else { - return .terminateNow - } - let alert = Macros.warning( - L10n.Menu.Quit.title(GroupConstants.App.name), - L10n.Menu.Quit.Messages.confirm - ) - switch alert.presentModallyEx(withOK: L10n.Global.ok, other1: L10n.Global.cancel, other2: L10n.Reddit.Buttons.never) { - case .alertSecondButtonReturn: - return .terminateCancel - - case .alertThirdButtonReturn: - TransientStore.shared.service.preferences.confirmsQuit = false - break - - default: - break - } - return .terminateNow - } - - func applicationWillTerminate(_ aNotification: Notification) { - TransientStore.shared.serialize(withProfiles: true) // exit - } - - func application(_ sender: NSApplication, openFile filename: String) -> Bool { - let url = URL(fileURLWithPath: filename) - importer = HostImporter(withConfigurationURL: url) - importer?.importHost(withPassphrase: nil) - return true - } - - // MARK: Helpers - - private func loadMainMenu() -> NSMenu? { - let nibName = "MainMenu" - guard let nib = NSNib(nibNamed: nibName, bundle: nil) else { - fatalError(nibName) - } - var objects: NSArray? - guard nib.instantiate(withOwner: nil, topLevelObjects: &objects) else { - fatalError(nibName) - } - guard let nonOptionalObjects = objects else { - fatalError(nibName) - } - for o in nonOptionalObjects { - if let menu = o as? NSMenu { - return menu - } - } - return nil - } - - private func subscribeSubreddit() { - NSWorkspace.shared.open(AppConstants.URLs.subreddit) - } -} diff --git a/Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png b/Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png deleted file mode 100644 index 83a6038a5cde53fd0e9bb43ef8ad5387a7143a27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63023 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

}NoM3Z3OJ|0l-L9hLz2W}W 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 diff --git a/Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png b/Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png deleted file mode 100644 index 6eb6a23c792024ee71b8d3ab26766b3a4737f610..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9730 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 diff --git a/Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png b/Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png deleted file mode 100644 index e762da9685ea515d327eac4de1b17bfdd741689c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmbVKdrZ?;82uHfwQULoDI%gn5oKjSOD(X1tSygHXiL|Y@+i>9uca-8($cz#Zma^u zv?eMBga9Hq!TN{~(7Ax}FcXIqon#8i7Db>k+rU?x|_5~@=Ks?g^kV#rXhxe>?nu|1$!&U;-+dE&zaC zfnfkrMH=9lmuCdFK?Kfu5+g7V9`&J&$(4d1pmPd>)`fF&N>{BV0wJgbF_tMv%PvXF zDvn|B<5RNOVjYkG|KPn0u9PLxfD^!`gPQDOc9xF8k%m*5Xbdj+<0v9IKAcLApwfXi zE|`+CjJ&dZg#lKUDwL%K3WL?88`+~6+bh4mSMJ=Ss4?q=1;$>Ra;il>(XPJJET7sh zKW|rxq{cowJbg$#(_b-lP(5|9aAu%lx6rVs|FhNzkXkSr%g5a-Ii-7Ojn!Wb2aj(Bi5M?O}`!PvB9o( zc>a`O`TzjR^ESA;R5h|odc9UUvRi63sJJ=$97&-xUsaH=%9Rv?f8$7upe25O7PAdB zU%6Sp6N3Cd1_JGg_sVVSzcL!!^{1EQ4Wb=^8N}e7c%N%NKY@iG^~PSrx_7uM-ok|5 z^L~+iZzBUrPC0K*+;r*#(0+ldS3k>G$pJSb6zQy_H9r*N{J;ifW^hmQj3UZnlAr97`a@giT*3lk1oGDy=_ulgYU`lM}aZ1ta1l zD|KNh>%VZk^4!I%_a7L(j!pw(kr3Ots*1R@ouQoHDwuTS#diq2Ub%ec7oYA&{vIoagYXZr2|>h)dA~_e+J@ zu^YQQ1LMtekEarM>M-V^G+x1l~gZKzcv6wDInMBnKFNvTp>SpF{oDTYRl`5>59UNr#$#7^cVonXo;{{pI#K-vHR diff --git a/Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png b/Passepartout/App/macOS/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png deleted file mode 100644 index d2ad2457720211ea27c9cccdfc8e25b36df06d0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25585 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 FO#s0vjxhiL diff --git a/Passepartout/App/macOS/Assets.xcassets/StatusActive.imageset/StatusActive@3x.png b/Passepartout/App/macOS/Assets.xcassets/StatusActive.imageset/StatusActive@3x.png deleted file mode 100644 index 18d8e235d1cbc25bcee11db9c6a6f8743ee5e5a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1038 zcmeAS@N?(olHy`uVBq!ia0vp^vOsLa!3-p)Z&$bnq*&4&eH|GXHuiJ>Nn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4FdzCMSxF;>+5%)fNFr` zC?Y-t{{R2~{f6U2V8Er71o;IsFfiHAxpe>9a$h!%s+S)p2r^dwnQHgqYs~C>yLRmS zu;<_Yi?@x~IoF;NQ9Slw!|D^f{l6csWA5!+b)2i`u$!~%!RtNz$3Dlfk96h zgc&QA+Lr+Z*-JcqUD=;wuSk+s1PTcxC;EY;Vu3ggh!ucXY?5e!5YRjWPZ!4! zi_>qXMEW-e2(+?qNm_X|>u&DG(1oSW-H-N#3VGfhrS*?^5eqi zf*+HL)$h!n$-~f*`*vgBh9&2A@T_nNztkI4?$5PgyZ+mvEAtx~9phf`@gC-=bw6&j zLPs{_7^iU%pC!NWi^;*8R;}6;=Ah?iJa6XG$p-Cq`j@^sT$v~?Ch5$1vqAMssxXTq z!)I&NwyE-$uZI0Po_R1^YK`Lh{E9swF`IQ(mj$`!%`vh&?Y7^8d;g32vV~LG%n$E8IV+8AJJ;1^j!yltEM@0ZAB&X! z=UR8M*xdJa;hQvO%gXK-H_A;WyY2|zk^J=ohrepxD*MOsadC^aznbrA_!bw|v%&7w z{D_~dQU_e>Elsx+0mD$W#5JNMC9x#cD!C{XNHG{07#Zps8tNLDh8S8}83B=rwt<0_ kfx+#Eb_Y>3l4r>mdKI;Vst0CL7U?EnA( diff --git a/Passepartout/App/macOS/Assets.xcassets/StatusPending.imageset/Contents.json b/Passepartout/App/macOS/Assets.xcassets/StatusPending.imageset/Contents.json deleted file mode 100644 index 756b263d..00000000 --- a/Passepartout/App/macOS/Assets.xcassets/StatusPending.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "StatusPending@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "StatusPending@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/Passepartout/App/macOS/Assets.xcassets/StatusPending.imageset/StatusPending@2x.png b/Passepartout/App/macOS/Assets.xcassets/StatusPending.imageset/StatusPending@2x.png deleted file mode 100644 index f6529f875d8955a04f3b782f047932203a47229e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1230 zcmeAS@N?(olHy`uVBq!ia0vp^B0#Lf!3-obN;vz06id3JuOkD)#(wTUiL5}rLb6AY zF9SoB8UsT^3j@P1pisjL28L1t28LG&3=CE?7#PG0=IjczVPIgK8sHP+`ug1`pc)`K ziUvst{Qv)-JtCN`ao`6!h%w3A-KDMPo^>pc!&%@FSZHu#d}(H+EX+3Pn~uWlGmJ%_Th6#MM!wKs>I!X~bjhgoX+cn#*gkAnG4U|z0f0es!G>n`=k$-t}knSEA;n8 zfK|QI@=LAn=H0W?eIx%u^2q({3=0Dx3D#px2OWji9DXc(P~puXvHvOtu72keuBodi z?sIiLAYl2OceC@tfC{+?11WLeh)o=k-}+zvI{8%T?Q`##mD&+g-}33kyq-|Ik#}NU zwEDE3uf1n=b?@A}$#Epk{mjv~@7~I<`1|nD#f@D4e&5d>`SRK9tI@nY0q0G3c^ zi@jfS^x3y}_x$w_9ootMH~hev{<=g(qxp*hYs4J8-9D-^>FllA&9X=7K1>B9w^9HA diff --git a/Passepartout/App/macOS/Assets.xcassets/StatusPending.imageset/StatusPending@3x.png b/Passepartout/App/macOS/Assets.xcassets/StatusPending.imageset/StatusPending@3x.png deleted file mode 100644 index 8c17d727e0f258558227823703ebb6d5867a7808..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1658 zcmeAS@N?(olHy`uVBq!ia0vp^vOsLa!3-p)Z&$bnq*&4&eH|GXHuiJ>Nn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4Fdy{aDY#U>+5%)fNFr` zC>jj`UAY6KTZ4f(m^V>NGuZn(} z()4dO$KFdb+~)1RAk^|LZpp13xn~7pwqL$^O^1bT+i8A6v!;#6}!A6NH;v!1to=k6i_-aydLx>;`FW1GcD~rp{?EJhwDsve#(j+2Jk`KS(+sV^2>@yN8zs!Zj0D(WG=UKMsiKA2g4;usbMJ1OyT_0{ zt6<&T#PG$>Zgj?k_b=IIU9R|^jqSOu-X@zHw;T4XpJ?>Pz3gzKTL1OyJX%w^-mveT z=iJUas|uE|xe3TgYs?xg&4VIqR4u z%jcwQn%@23#cO6=UJ2hd5A_c1{e9BMOUO3ea${TFq>Yv9zVd&6pMKZCz{72Uu<7rB z1OtOL&Q$^nULEGKj`As*)AZrR#wKeejzhu{XS97 z5w4jtFZ+-`$9(lkzBfLza(Pc$xmZ{0+|~@edl~u;=( z{_pwAa^j7SAKcdO%yoarq*xi1_5#zQYKdz^NlIc#s#S7PDv)9@GB7gKH8j*UFby%Z uv@)`^GB(sUFt9Q(a4i=6fubQdKP5A*61N5uy_GIN4Gf;HelF{r5}E+OB*NhU diff --git a/Passepartout/App/macOS/Base.lproj/Main.storyboard b/Passepartout/App/macOS/Base.lproj/Main.storyboard deleted file mode 100644 index f1eddc16..00000000 --- a/Passepartout/App/macOS/Base.lproj/Main.storyboard +++ /dev/null @@ -1,261 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - diff --git a/Passepartout/App/macOS/Base.lproj/Preferences.storyboard b/Passepartout/App/macOS/Base.lproj/Preferences.storyboard deleted file mode 100644 index a9cb1a26..00000000 --- a/Passepartout/App/macOS/Base.lproj/Preferences.storyboard +++ /dev/null @@ -1,485 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/macOS/Base.lproj/Purchase.storyboard b/Passepartout/App/macOS/Base.lproj/Purchase.storyboard deleted file mode 100644 index debb685e..00000000 --- a/Passepartout/App/macOS/Base.lproj/Purchase.storyboard +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/macOS/Base.lproj/Service.storyboard b/Passepartout/App/macOS/Base.lproj/Service.storyboard deleted file mode 100644 index 8fbab8db..00000000 --- a/Passepartout/App/macOS/Base.lproj/Service.storyboard +++ /dev/null @@ -1,1573 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/macOS/CHANGELOG.md b/Passepartout/App/macOS/CHANGELOG.md index 9fb6cb63..80626a8f 100644 --- a/Passepartout/App/macOS/CHANGELOG.md +++ b/Passepartout/App/macOS/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- App completely rewritten in SwiftUI. + ## 1.18.0 (2022-02-15) ### Added diff --git a/Passepartout/App/macOS/Flags.xcassets/Contents.json b/Passepartout/App/macOS/Flags.xcassets/Contents.json deleted file mode 100644 index da4a164c..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ad.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ad.imageset/Contents.json deleted file mode 100644 index a471458c..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ad.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ad@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ad@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ad.imageset/ad@2x.png b/Passepartout/App/macOS/Flags.xcassets/ad.imageset/ad@2x.png deleted file mode 100644 index 8e579814b92360607bd7c5d7522e03507c3e8f84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 560 zcmV-00?+-4P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0p&?VK~zYI?bSVNQ&AYe@&7sZ-I%u~7){!QrcgJFAV|9@D59f_AP#;E zKZ#qvfSW^bu;3sT9fVYZ5pf7K(xkCzn&jr@=DxTU$&?!jWXOMh&clzxfg>J2cx6|< z?O4oEaQJMNvCEEb@69wPgm@z(ZC+q2$aE{nbV~zK=Q-p!Ff}x=r9gK3^ko zwS^Q8)|JuXA>&DsFV&X>{+JuN2Axh3MU$kQAwPcIhfrbn&OU}QTr9-UdL-jbPNG~Q`|v$sr-bT`P$o9&#HQUTQ>{N= zJS0S-N{L7ZoK~I?rZV`NNuXPJnuQtD2}5J?kaFn*iVR@uD}&>Gw4KlNa|ya97C}L0 zJnWNCzl#is{6_3y0--3x!XA?W2;mV+1_a3on&}{gpg%}*jo*>>M!u1Y;W~~RGE&y? zbroM45I68W1ACmsbp?OU3sKibzY$&YF-@JL!6W3RK)4cW0?(WvTM{d2Vd{|`(IGdt z4~QMN@cb0rL6%;3169|ETMg2wGc>J7aig|)h_3mRH>&`go#&{J?xGr!t>Py#*>+^W y+>n1;OSRbBefvKvE?b&UZv~ld1(|MXAb$a1N}E7ZB{ymS0000c4srQyJ6Q!nFOR*m|A}S(? zh~ka(#!GJmFMI8ke}k6_f_R})u_y=zYiUI#txeiS2u)3zZj;^YM|Qr?cok4vP}mdL zi+!#J=Dg?k{y6VBb6!TAIQ2nH7kqc-!*)0EJN)tXq4{p&&7YPInB7d^Jl-yM6Td?S z&vg@T=t!a$=o{%Z`bK(>P6!#wnF*46iJpLO~V^q6Y>U81z~ByZ|c!UW;UZF|gw1OleC%6VrRf&X)z8qs0DlKfj$saX Q|gW!U_%O?Xx)_J-( zhIn))+bA6VeB<9qaY;6%1DTw9Y@0Jelcr2!laLHE4!j@o>A(u-3kRW?v3ui+PyB7_ z;+;Wj^c6loEql7`^p&zBx7W{~xADUV&H{nejlI+7ySql5n{}SO$L)`Fg#7vT@v;7CHUDK*mWPfW{W|9%1M}g>|9N=+cOG0WA@R@n0^52i jlR)EF0wo4e%)n43z#|e-Ywp9?ys3$*yr`7eXaI3_TH~2-0V=Ebb90YFS1?x=1p|Eq0!O{B7991?T$|f z7EjuKTV(In7^X>;rwkYrQzFuLW$l{=R|Qm>vUBUNZN1Z%{rtbUkpIEFM9FSs1wdo| lvF%yw?54n^QI-9dLCZxpda?ETeLznzc)I$ztaD0e0svk_X}16X diff --git a/Passepartout/App/macOS/Flags.xcassets/af.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/af.imageset/Contents.json deleted file mode 100644 index 69e26fc4..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/af.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "af@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "af@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/af.imageset/af@2x.png b/Passepartout/App/macOS/Flags.xcassets/af.imageset/af@2x.png deleted file mode 100644 index 4c978a46bd283273fe92403abdcdaa7f46b2ab67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 587 zcmV-R0<`^!P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0su)wK~zYI?Ug}m6HydK&&-?5WRjGaSQ0d`(O8u%#JCVwf)qDW3l+48 z;Kr>#z=9w`D~LZp+zBoe47d;!t8`I>NTCJ=DM1BmDQMHC#!NcxB$f(F6dY#DF7%fdmi%3r-aB>tPCfgsr5k7yG;fYCFo=5rR3#@lb80XFq$R38$7pYw7 zi3HQfG4{vsg@zC!1a^W+2>S5w}uhb>=Sd>sL8AJIn6(6!%K0bfQYRaqNdm>g}N}h zG}fezgoS%!Vq#J?zM>0PO;{Mx#7#F{6O%?nT#%U35P@b|N=;!hFc4^mGVeRwxo(U* zSND)i%>1jf`v3pSIXRCU0nFnwxv?_MgsP&2!-Otez)B`5BobJ=yQsk+rDPH- zl|qk32@VY*G)-o-#2FES#_@5~P>B5UGWq-WQFI+)7$~|hkvm3pojmrK$#q9@__cqbmPRfdx z0}?oM27jcVhLN*aJNp>D&mj~AqbG)wvv3MWoOt~;P(AogbyG?2(J(TC+4=(J>C-YQ z$N^E?A~;2hhT-!FkHM*HQwSlj9`A7P$`ulqE|GtjK-g7!u1z641`We;?87`-YqN|B zazGSQ#W6jkr>6#zo9CsXpLcp78WoE2dSjeIEur@cVA-;zC`-^bwq=XC+L+? zK@Nzuu|s(DA}y2eBQ%x#ofQ};0mVV;evpL1e(gxvBf zoO9nn&h~3rWS5pOx;juagUG~Xypc94J9~Hn0RR-uhtVA)GI1G2_u}=L+{u%4-?KEB-!wEsK_Z3PTNs4z9I5-$R&*Sg!!Zk|I+{Fg;)ovHPu0CO?8l3 wQyrw%qyoRYF{nUj-Q{O!zCwwlyU)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x158OoK~zYI#g=VoQ&$+re>X2Rv$oQb4y)P5W<-i273tP);-U`3e%Khp zposHjbZ(VtH>Y1FjD6`8Utlj4+D*}Qt}t70;D#UkP~Dg>)fR_VT2hh5FLP^R)7+T! zCVBSZO4lS!@4eBn2M+h#=lst++h>qUP4A}whk%Mi!oeB0J2ni`XktYw%8}4z& zkoE&e>L&7Pjj`W0kYSKBZg*?~Hl7g_a^ zzD?PlOF#mXKqhv?0elUVPqBnULD=*O5dZi9YivZEPzHY?}I7Oesr>@M1zvjCVi zQ&?WDv9d;Q$#jbFm@!OkwVnNYoOEAva^bp@P(**c&8awVXwe+K^1Ig?@&p29laooC zopoz3a{cL-jepTXRE7TFr?a)VQV%oo_q^V)&Fk@~*%KV;6fNEBDNBqzF^FO<%F%9r z4g`SL<5BaLkGX1)BfoAzL7oUm^QHHgKRS?ieA+s;I(U|?{a-#d#KwVE-W)nLt;VpR z?(5mZvip|`hFBQB$){I7G}Pt1H93N`#`jm(^Ya&dBub{LGSkeBtJHrq^|`PN0#N+d zFq^yH#ogIy2_*FP`>4U&rnIKNM;>-{5p8KP4WS-8hT7h4T5svY=)vyYjQ92$>a<(8 z2=Cl!#o8K(Bod7H{iH`n^J`R<5x*aa#r``GfaK5+;f991goyAv526+Gg002ovPDHLkV1kuEyNUn+ diff --git a/Passepartout/App/macOS/Flags.xcassets/ag.imageset/ag@3x.png b/Passepartout/App/macOS/Flags.xcassets/ag.imageset/ag@3x.png deleted file mode 100644 index a810f5fbf3d4b234a7abe8143ddcc76e1675d4c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1253 zcmVkP);yKLvTZWgssTU+AxOc`UMo( zGM3nAl1AlRT5j4HHFf2#{-{i1xr+4%BK3z$TOuGNbAlow1gOKs@#BzR_k(YLJOy`h z+}-osbG7x}p6&U5pU>y}{=Dz`e!idQzF$pRTAG>v0W^U4uY*tn+JA#2AvW+FstbAI zVb}JeC@6fPh* z_uq>Um!zxW(DZ%Nj|lrk>Hh6k5qIejZ{#7&wTO4}m2v0&9eX#3DIpyoQWc8X|E3gR z+fRsf>4<^)X#wu81Mec5E2aA@)rcUye9i|!v^fdUbUW3Nn?NQoAoKMa=fLF?QXT*} z9H7(r=9{?%em*0QF9fIpoL6-hAppMsoAF#FmoAcW!#oHr?Qo?N664^=et7|%tcBuI zh>d||F%TOqEf$}YgVi>>-yz_cJNjk_0oV(C=_QDTk?`$DQcVDuoK(K@0N5R{>mYo5 z1cZlQF&8Lx)9+S@9e5u2-3z%+2fMez?$`YjalIE_D26lVJ=-k@jstVtJaa1q;098@ zc&l4MdP&H8TfGPD4xQIw?JLlA)3aR&l89xqJfBqvz)2teVjGCZ0L8IQo- zb7jRo19k^oXq9rNrU`y+0LyJxn*+de9>(w}1Ypmq5BNIv<*D}kkOAfy+>~Y&F<}ql zh9zpMy|L#wpVk+7^zkahste}ntQsyxq^J|6Of0dqQB|2rsLk__=QTxx921yMstaagRxK?NNs5B=6%Saha-uSoP@BbPA6aEA$T7iZr?X`d$VISY z9MkC>*&MR#@nTDRK=Wm9eGv)fCfRYdmbyvQRJ&wh{j+b zvaSw&Vxl7AJ7I5c=T=GzlNM#uR`I8v7$2uEI~y_Vf0Y*#6PR;y6pH{qw}b3$ZS)ru z_%j3mgFAL$J@4hy*9-jK*BU)`jFF>9mHSszaNA_^Hw@)D;xdTQNaVS5=;PyM`PsX= zxRspDWPiWEA*i#{C`LvwXJ;cuM}7CP*)Zqksv-hZ+%mPbv@o!3o6q(`#l=|b>eQlF zXKFbkysQl4rcEAg#;U65+pxj&pL3Z|U5LhLB(k9a{fZTCpF4Vb=uJvu(yZ>WjOx4b zQ~5GBHuKp(d7-ih2+;Y6t+|=O?b~Pc5AEE^#M!ffpj3ZqIU}OHoRGXc0LH7U`E&hx zRrA6b0TrSN4JE3v5slF(kzjEcU#$a$*8yi(VqjJSx5rFw<;(`&g P00000NkvXXu0mjfQ3hR) diff --git a/Passepartout/App/macOS/Flags.xcassets/ai.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ai.imageset/Contents.json deleted file mode 100644 index c16ebd2b..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ai.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ai@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ai@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ai.imageset/ai@2x.png b/Passepartout/App/macOS/Flags.xcassets/ai.imageset/ai@2x.png deleted file mode 100644 index 0730496fed6ac51919668f03824af6aa030f2c38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1160 zcmV;31b6$1P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1U*SaK~zYI&6a;mlyw}(U-xkGqc{XnK*EdAOBv&j^U_&38lZ-@SfGShQzos)Dpp_Z;W=d>Y8wJEu2P~<7nibetoS^e0;bgBAWE{LUM8{ktB&>ZQU9}s;QXnD;@ccb z)o|g$Hs;RtAD7)`8zSv}HQ&yu$LDkofjP%GAyd zOe7>KitCqlbKr|g_U-%WnZ~!?4Cg?EouLzZ@y^JgHfSLUYfn;N-wEL6EQlbA63xv9 zR;8D-BWMF%f)_1|USUhfeX6QHLZeYWPVORbK6?rK4SBe?HlXPh@gA9Xob#zn}mB)wy%Hg@lX*mYZ38c8VKB%J&ta1zq$8ntnaYxGgfIVD{|k?J)tiUcqq36Tlp{ zpfm5x?~9%cQYxq2)@YQiGi*kAVM*gl_l8`3pR5@JLo0)nTwsl}Uoc)}WVm54h`77E z5ErLHukYsS)w`%vp6GPF6cvepOe>!#fMF#kFDE#7IdonGV*@ej1U9Yr0}p@F($Xm^ z>H#L9p-IximU8nO=x+y;9_;-93=WQth6x9;Sgg>L3m$$jF9{6QQ1vlLA_jwX@<5En zUJzViW+ZgiLf{fG-Gt6k^!onE1F5R&gn$URunt-Zz+r{(RII*X)YXm7#tHvf^YYq= zPnv=?p%GCULT>&GyN`y>>YuUOWs?V@(@AVE_?lB;X%A0)nMoH@x0C~;FES}>K=Ech zk)~n*+!!S6lmuLIiL6R{F)RHfL@aeKWuAz=KcR> aL;D-wH=-C*U>VW?0000~RWdfk3M#~^NkePX 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/am.imageset/am@3x.png b/Passepartout/App/macOS/Flags.xcassets/am.imageset/am@3x.png deleted file mode 100644 index c33d1b5fd82af0908c8679bf14f75e2578450c13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDva!(h> zkcif|Hv)MZ9C%nR>RNKXaFS;;XkaQ}uo4LLF?so9lmEeU@s~YbUSd&N`bI7=@ughA zQAHEJg@qiw&Kb)%C6{m8UhUF4;nH@oPUj36r^figP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0l!H^K~zYI?bbg?)L|UQ@z)(n!sVd_R+gtr;1DI$5Y$k#Ib=gfLw^LL znyiLGY^bIPa&d49GHS^%Y6uMJ&(I%iD2$>%LXnt5*pjp;`ZTBscXW5&rDxy=KEv<% zKF`CO@8|dZ@e{Zm2nBX`)DqD$NV1kek~M#jV(h@yLW5jE7tWy?D+&eDh6YUH5zZ_& z$ZG5|-HCBDq7Um(x0oPfIEo`Prvh5A2bc2)Dav~1IBwz^Mw8YC{{hHhO1?3YZd0sO zq^4HUyIvE?wf#TDn%l)selYrJAhn*2)>8uArW^fNH`EKSo(LN%Q)}nX3LTe(cxn1O zGWb?j@5t*n)FilbgJVrZXHG>TG#y|~1wY4f>BXF~0vQ@+^G-ev(sr8boz!lm`5@m% z>3x_V#H`Xrv#(xQTQT>%eVg#0N9gYpdb)&DC-REqPc#{)rYz3;7d(H-mm#L6NL2Fq zD~T%Vez9c}18?)|M#g9!KjUdX?VTKIBpRXn9(V7vdoRNyv>fMRE33-$gUL8Gv#zcZ zR>o4-%gcldt-{rg+@qNnNG2B-FYx$gtBIUGXVdzRe~cDLO9dqI8&5_bO099QyZ`_I M07*qoM6N<$f_6>qPXGV_ diff --git a/Passepartout/App/macOS/Flags.xcassets/ao.imageset/ao@3x.png b/Passepartout/App/macOS/Flags.xcassets/ao.imageset/ao@3x.png deleted file mode 100644 index b3370cb525d23d107574e5a0acd6e7ba98c4d184..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 804 zcmV+<1Ka$GP)#5o z6q%AWBAljV83kPEl`S*)IRWsPQXw<~nbO80ZAGd_@*&kCy++D5DSwD&j>rNeNANfa zX*ZG==>t;Ez=)W$N45ibKyvW=VW0$X0CR!)rc~*ln0|?Dq`Kfz@*y2Z8rp082vRGO z2WgVkm)2UOw@A%M_C8~&NNq@Wk!HqMB=)=32w)+Q3%G!ny~g~&L!hxwe;lw?5#5WG zGa0%&LdVTH3295={bhKyKblJ6qiEW-b0~ds=(y9zjs5fJPIPr5Xpx^}v!S1D=N0fm5}Gx`f+B$4)FZW{+8b!ohJVMTB%{t2bm}NdO0uX(!D8fVMjh|a?W^d_ zNtBWPXG}>k3V4mBm7i--5kO zbpG9!Kmc66JERyw?g#T2ws+O7@7pTis8Us2n3+I2A;oyPoKdx4OTCM@86;3 zzH$7e11j&pxhv4n6h{k;$GXyRgL`^lLKJ)V06e_=nvS(Z1t`$ognS%23;*&e7rDa7D|7%%|goxGn iZ$zAcd$1>A2EPE@Bgy!6pT>Ux0000X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0wqaAK~zYI?Uu`HQ$ZAmzxzmfFOMOQq^RlfB*@jh(7Y z(C-ok*)}!tZ+T*0GTgm#l*m+ycrwpWsDo2|J|XkLpobS9R+|R_m|H54&F=-wqR5hP8ihbYd&O^mt$c13`FADO zD9E+@^CWb`7D%R0qEfNy;!bt*)JM3`*+h;-y(r`_{KT*3O5A$9z@3*#d~O9TW8!tIg!_FQ+1JkKiSAAFEer0wT4p6{ z3i&tOebf083o8bNVuj^&k!Vaq&(`;php`lsAJ>F^ZFk?Fawrl(zY8s6;&#f+EfshZ zUne@9YRIQ)5VLG?{lPp?eK*QmREUUL`a`n|0sqQZv6cF3Kz;xLWT$x7O5m>m0000< KMNUMnLSTX)*(E^$ diff --git a/Passepartout/App/macOS/Flags.xcassets/aq.imageset/aq@3x.png b/Passepartout/App/macOS/Flags.xcassets/aq.imageset/aq@3x.png deleted file mode 100644 index 0b15fda0e613c1ca3e353c3b591ef630740a8907..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 908 zcmV;719SX|P)(Wnp*tn#p8uAlB` zoSuFI(}IqWUI;m$Kt{GMuV(Y&04`PhR^D2nIY^wO3ZGQsh!tHoKhDJ7%(MR(ZCmWF%`N$yA!%Yp2ps zQ}VM+bN$})^Mj?*iFiG#bS&h!{l0v!b86d6_N=R=(x>5b{}L}QQdrsOmri8i^fKvK z$Z?GvxzdB4Fxj*yz?!BC4qxn{D{8Q4dKq)3dH*`~>{w7=iXy<0TqijhNv6`2dWJ(3 zMX+x}t((6Cm+tKXJX6V^=?5=A)W{0X;>fPm_+%*+}0&f(wtLJnfE2i)Nys zri7NA4QyOkB^57q)~cUeOk=HAO3;pSuZ!jxa*JNdNNuJ2kK>0^bVuS+aZ*NBH&sfB zWkXZY_s9P!8>EaJz50PmkNagp|B4X9yJN0M8R-b=oWCC-I+&CRx25gbC)@A7QBDBT z7pphnA;<3*9U+~wcfz)Hz8Yr6^1N<{H(q=mwP6`dOa9NUPFuR|psetlk3+ORi~xcu zemANj&=VG&;UQAy@H7wtEGvx=_6zvgyBND#I!T&xPV&Bw=y8kV*Fv1W(M!@yqbLIF zd$$Gy9v0M>5{sMc-%w}U&hwravAF!`I(D)8(UaDQx$(Bo;BB8lwcl+Ud%3-z<87h5 z^#92#%Bp!4qkc1z7OfAWQd{mw3i9|c60cjODd@vUT4<_3RRqz&B$_H{d;Xb|*LxYz zQ+ew+{`imyMS!XZYAW1Rm1#Wb`U)VuurpEc5rN??)iTp`MGU$kMnN?Go0rB3Bjb$; iBjXJhaBHlSaDs0h76XKA1%k8y0000X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0X#`WK~zYI?bJO_!ax9r@#pH5HZpiEIuIBLD$zkl5@#75{V0AbR!0X{ z7fl>Vh$uu!)KX$v-rAlk>3A{V(B}jE^SjIC#7%e5Md)j`nGjuTtAMo%NWKD+uYlx} zSLdqb?aHkpf;ef(y65dTICvmiOv%y&>3XQ+ l_iw)V0+O$QxGMkbOrGLf{gF(UGK*IsuGS`bPpmDuV^5d{kn28Of{hM8EDOlsk!+hfxH zKom>}7cPbRGq!6j@|A-`@)8jlUYrt)E+JN|=3N5M!^~~5nzr#u$5dPT2X5^m{-{gf zUg5hxS&grl-S$Amc>S+9htDC32&Rv*igm5IT18YGF~7Ya4r8Y6kEr=4jvlxGv^yo1 z+dJ4!gVEJ{>Mve_5Zbb}w%>TQ#Ot54=zk#$7wp$v1~(ytn~=tyi|_XFXB~*{2>qGP zXx%3gBJC2=&SMG#nS=7ZKYJx5L{UuWdxYK;Vdiv3>pc-^JBXZv?L4GVt@Hgy8S(e) zbV^8>r%-!Jq4oqrS~{aO|Lj6gX`V3r{El4Z5yG9&8O{+HIEQ=W%1sPuP}ps0zf-h# zEvtB+t!fKQiRCm{jxRB#jkR+Crh!#D!mjM=j!o`n_s3oCex0-EpCj`{Oshhrd4g4} zB@vhGh@#?%I1E6=m{vh+yZsmO=B$_8to0v}-v<7=YtX{Ryhk#Tw2_IVjZ7qMWFl!} hV?-o^vvn7tgWuiFvhmfLlG*?O002ovPDHLkV1iv77xMrB diff --git a/Passepartout/App/macOS/Flags.xcassets/as.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/as.imageset/Contents.json deleted file mode 100644 index ce2c26cc..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/as.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "as@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "as@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/as.imageset/as@2x.png b/Passepartout/App/macOS/Flags.xcassets/as.imageset/as@2x.png deleted file mode 100644 index 1691f39b1eb6bf291798887d0d01bd91a0e8e1c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1097 zcmV-P1h)H$P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1O7=wK~zYIz1MF{8+9DV@%Qzvccm?~l>eo`hU^bx8LWttO$a2;80Wvl zkv&-A6O*~k`E2@d)0k+CK3I&2niyyHzz2JPCZv-%!{(BSb0X?sW+2X{tc|r@fsSjh zT`9k*)mh-4c=R zcd;OeIJY+A>3Ig%!w=%#`4Dv%MqCAj+-tE^>|8dR7bMr>>8S~3LX%uM^BLRw4q{y^ zB-i3d=ntZkzi_$#Bw8#?=;{wBf`KVo=|22A>FABsA^B|e^u!p~FPap2usH#etY|j=1Vwxt? z6GK#2SEH&b;c%EO+uAtNKaT%}O z34eha>c{J?!Rz(na5%8aGEW?N4ZYNd(|UVHW-?IXurFI9Z%A-rnE20cQ(2;;IK51M zdt&)Rij}A)AeH;*e(^Z<&8^EGkxbLf3(~N$li=u8>_z|~eu`iyg3~TCH#p^jFpd=j(VQ3n1RRtDBUK7S$SgrBPm1hO=r1)7QQt8jeupaA8YCNSr-Nd{(Ebx|ZhdS8$cp-I)()zHmVE z0ZZ&!VYN@i)i1%7VWw$v`;2U*SNWQT7 zTeFy^$@Q@>QDiF{+xM=x?wTR_!u~6HLka~eI3!nC@WROd3%M8A;(yKGS|#0Q>Is3W P00000NkvXXu0mjfw^160 diff --git a/Passepartout/App/macOS/Flags.xcassets/as.imageset/as@3x.png b/Passepartout/App/macOS/Flags.xcassets/as.imageset/as@3x.png deleted file mode 100644 index 5cd681454ddc2a2047f9c84e68006f792c199b20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1730 zcmV;z20i(SP)74QRu5E4>;fDlDODpf&9BuGI`QmJa{#8i%D1rpo1P26~Ed*eFs zdN1~__cD7KeoQd+Hi--IZh%)>&5X|3@4U}B=R4=@jsVY};eS8^;E`4=ol?-*Duo>u zAuD~nTmtsp9c!DbT8#4) z`vDwX58&L>iKv$W?k>ok7|&NxLAKI(z6uIb2;-`%GXCyquDo-MOOxY#|JXTTi$Mxu zd}it*6C*?D<3W69kMqIY0_E8VN)BwEuJw9%1X8FT|I@F2Mybuj{KzR*#)p_&Nl|Km z6$SJvWF<)Dprh-N{|uy1b3C-X#MGG+EKiNG^!^yvFI_+qwX$FeO7x(rAP5juKn1KO z13M2s^XWhenVbL%GnX0u^$!V+oy4fDq5}Syw)u&Ib~WiBkOG|mb{WEGP|aU_iRK+0 zHwP)S@qE2=ta5Yg@{jBqUhc0E{R$N{r4?b%q<$QOR_3k*i%Mvy_% z<)Rt?r!tugr(Zq7_0bckK$S`B`D7OKf|d(r304)5^xCuCW}$oF>pb*9G^uxuIIDuJ3oBNUoHD(Pakp+VYa~bf(z-832 z>$6YOuyZ%FAI_i(Is`#L6h%~3rKP0>K~+_?pw9CY4h4Dr=@xWKjPl;Ye6W&(cV7db z%)t6;mCk8PY%HxMK4@3t!9gB>_61Z`B@&6SxVT7bYise(TCS2(p84H4iDZK4S_1p* z48!37RdYiG*Ai6TnEJA)L0NA&yq*OA8tk@N$>nnBbUI{N#^do2jYf+P5{*U)t*l}& z7-(y2!)mdxcA%f>qjTuuQ%r`zb|WoCLCYV#sI68tW|&`1vv~YCX0sWu*NZI6h@x0r z5TDP-!omWnREoa7J^-q!tFf0=Ba_8t(CX<$Z4ZfO|Fr}s9QUyMb6+BEDy6OW2>?tc z6F#4hJ$v>PFGq5@924W?%=`Uxb#-C2TJih+2!cR-HH?%=G5Y2)LSv_pmwhC2P-)bX zUlu`;tu%e(2#+29+Iq~Tr6tnoG;X)Mcq4LYV%DUD=I41@5Q1}068Uu61{?58KJJek^1`jo7QW4 z=pg1=ueO7W+SIbiP$et0c)hobUz8DvgoDT>ofzUO_wIWc-^3fJxeO&0bvLb_QZiU1 zh^DoV)7wS;zJ8iJx^LM=K|xei<>ViJM5FjeyzT@z;QX6GO47reo9N_;XJ5JL_jIxj zbEBDsnIuMwgXV`0(f;_rZQCkJviq-$!t{4>N{h^dx8oA0xfUzI)i?mQRu(Zn)rxb0+AHy#ka`GmF)lKc@pt8ELPj5@rBdQ`t=bh#-Z`=e;XpY zvP|=i9%A9En5^XlFaMS}+nF5t310Ua21#T!q2j1LfVpfJf~}1@PY<$uYcIl+hR4)m z9pl5H0I}gmI5!cbVVfCmt-$0;AFk3$rZ2pKnME%9)1Wir+}4Y={635XDKiBKT%W^T zQ-fI2!+kx2p9F#D*GHrnKddIs{G1QZ{gn1530ZIA?S-dkY-}PCTOt!aPbhGed)$>c zoGwgKic})PawtYDp%MtL(z5s4>^<<@?LqQYD6scNf6t2lG;;JT;Ygfa_Zm2RF+o+i z1)F<6$wZuMp$ywS594t6P}kJHY0TS#6dLU6?E6FmQ{+O!G)SiypEZ$IQrL}goHjk9 za}6|g4RZh9egr|-I*>wwjgK59o=j8Q*umI|?@?+8#okBv{;y!QS~vA_MKhGaprT_o{07*qoM6N<$f?tS9*Z=?k diff --git a/Passepartout/App/macOS/Flags.xcassets/at.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/at.imageset/Contents.json deleted file mode 100644 index 05bdb0a5..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/at.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "at@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "at@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/at.imageset/at@2x.png b/Passepartout/App/macOS/Flags.xcassets/at.imageset/at@2x.png deleted file mode 100644 index 4a37c7e45d1088a3b8e3b56edc72429a67ecb9b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx@;qG} zLp(a)UR%g}KtZJCp_9OZY-^sC>5&&y-ZZT^w9S9wtgdHb6&=6Te@rq?UnjA9OIc3W zPl?@ES^g-qfS_^txzesVvp8PYmMSh?*!e^1w2w1~8^<=DBeqN51iOePI^W>!7jyv% cxpAb#z1NYx|0-Mm9?)h6Pgg&ebxsLQ0OevtCIA2c diff --git a/Passepartout/App/macOS/Flags.xcassets/at.imageset/at@3x.png b/Passepartout/App/macOS/Flags.xcassets/at.imageset/at@3x.png deleted file mode 100644 index d5fe09eedbaff2a18fff95097928c9185d65d2ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvQcoAh zkcif|Hx6<(IPkb!?A1xK-E=+m_xeMVE3WV*2{-aQ+|TvwzSA4I$GNOGuFgvD@7R#a z-{I6D<#a`0QJa8is?#sS^lxnf&)(gM^5O_|hD&@~6Z+`dBgf@HH7zR?U3e9bP0l+XkK8PiGC diff --git a/Passepartout/App/macOS/Flags.xcassets/au.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/au.imageset/Contents.json deleted file mode 100644 index 069342c5..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/au.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "au@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "au@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/au.imageset/au@2x.png b/Passepartout/App/macOS/Flags.xcassets/au.imageset/au@2x.png deleted file mode 100644 index f573888404d91100965d4b22b6fb113d7466bee5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1250 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 M07*qoM6N<$f;f`W!2kdN diff --git a/Passepartout/App/macOS/Flags.xcassets/aw.imageset/aw@3x.png b/Passepartout/App/macOS/Flags.xcassets/aw.imageset/aw@3x.png deleted file mode 100644 index 7d6e1a6d887fc3a215f4f2a028e7a352ea7afa89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 643 zcmV-}0(||6P)@_w(5onjqmS_f2_d)?qhwkMqN4MD-58Up|8tKvyx)Qzr?4k_qaUcmk81!vu=(N1-tDnLoK7>8OBn4`7rH`D z|5&Ax%K;Ghx@4$Oy(rZVb}5pv&OH6;IDzCRyfYEDZz%uMCp^CXjYMCNWbY|v9f!$m zRa&TA9WgR7uK`FQxYer=ipS`F6TvYZK`j(&pDiUae1ZJc8+-|kF`0rw$(%j(H)NaR zE;wJzaN_$4M^nqVUOuOs+`TWP+fC`>6_UYWl7T@MkM#3CFHm<~skQtQvJ)u;A&<(n zr8#^L#{t;9d!MOGcX;=+lV4@)Ot6}pUDY(^Hv}J!4T1c#bailqnavJMMS#w!H7mV{ z@F>FRBLNH0rRKSUUY>3tn~#cI=k2?S}y|j5fbki z0FTE=mxj-&vc7 z&@^ljqCAK`GTWgkDA5&L{4qs5TfAwDTgyIsWHNToY=@>$^PByD!Zr`Yc8JxmO^Bi) dqGn4pgWp1dqPG(lbW8vM002ovPDHLkV1nWzBpUz# diff --git a/Passepartout/App/macOS/Flags.xcassets/ax.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ax.imageset/Contents.json deleted file mode 100644 index b5ba2c77..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ax.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ax@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ax@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ax.imageset/ax@2x.png b/Passepartout/App/macOS/Flags.xcassets/ax.imageset/ax@2x.png deleted file mode 100644 index f02843435cc6fddefc36facf6b9783e439f797db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O^81Fmidi zIEHw1CZ}-3?doc5RGh-do{;|Y?1#lRjUe!_Y?ImH{@-(WKtSrWzTt+h|4AH?v(In% z)+VX<_6VcfF;kx@GaYOVUI~=I@PQ5KST#_{jJX~??<^%29kz(r_OOEIt-hZFnw}VHj zeM8N;zhAF8zGDguY+n0qDVxQIfY)yya85iFYdC$T4vk`5f6{nBk(2 W%j>7p=cfU~iNVv=&t;ucLK6Uc2a@mr diff --git a/Passepartout/App/macOS/Flags.xcassets/ax.imageset/ax@3x.png b/Passepartout/App/macOS/Flags.xcassets/ax.imageset/ax@3x.png deleted file mode 100644 index af32efc2b3bd91a060144d42ae4bda5efc9ae476..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 480 zcmV<60U!Q}P)EC`pwOWrNI?V#ZBwXe&Z&_)NUmwZQSRAZ2zS3Hc@vJjgtfvY z1PDGcjV8O@*qdqBM!~Y!3y-yHcgk_T*X;%X2rw+3?B4rbI|v*CS-`&t0hmu#y!!b) zjoRa&R}YPT8I#G12>`Aj0Lb<#?0>K*Hi-f)q8eI6HMEFoXc5)WBC5gnmx@HbPg;P% zTE4F(5;#~cb`eV+p3j_vNQAOD0mx_0LRtGS{nJ{FvK8owxb@C^(W=GJC|f~uNkb3A za{}RXR;2VzYiSYHp!#WC1bNx3#}lxh5?&pj{lxg@YcH}%!OgTC=&QxzZbuzUv|y&~ zptph#+iV=eo@%Mc=fR z7Eui?q8eI6HMEFoXc5)mAY91|Lj}O@s;S>$=-7b8rX6n>`Y~cUZ`G7H3 diff --git a/Passepartout/App/macOS/Flags.xcassets/az.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/az.imageset/Contents.json deleted file mode 100644 index 467ecc6d..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/az.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "az@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "az@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/az.imageset/az@2x.png b/Passepartout/App/macOS/Flags.xcassets/az.imageset/az@2x.png deleted file mode 100644 index 95ef09b34c5ee558542f2bc5838c2fb51c90cfef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 490 zcmVX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0iQ`kK~zYI?bJU=8&Mp<@$Wk>HRg|vrA-L9i4ah$4yA=uoa!J5PL2)^ zf{S3cY^_7VN!pV7>$rLNyV4+J%=FbZ7&dXxbJkl z<9B?9-#hX0<6?QE{h5zDEWZ2u+`3D^&0L5x7vju?I1?Yn$BzT&p@{LI5Cp=H@OZrx zjT`J>yJqhB*k}JiqcZBvT1+mS33xE2q<8`_Sjvw0RRn|d-IET gAC@L;mP*Kp*7;|c|lYxZ^Z_YNZ5!(!bT(#HX@O*(J>~#t zEraV4pBTqJI3yZwGq&VnUAJa%$42qrE>fDhfm#3c&wJIKCf2`);QNop10$CTVj|AS zaon*{g4H!j*RErVr7d$zxdK6eojnAA&U+BE!1uQuUrfZkcmaUg!^ce}00`=&?%YPD zyFk}5@68dcuCadpa`QTI9F*%~4IKbv zi(PC-{5S8YKA0!AtC!T=EUKq_%N&(VpwcOHu7J)JpdN@wfb^f;vPe!(62Ec@Q!G(^ z^aSmF0U*}58-L-|-xQQ)XIhkf12|_-V-5B<>R|B$wT0I^+M=k*np3BL?U4$5(yiTNZ5!(!bZo4*kE25quO5RH;k>jfDkF+Pyhe`07*qoM6N<$g0!k4 ACjbBd diff --git a/Passepartout/App/macOS/Flags.xcassets/ba.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ba.imageset/Contents.json deleted file mode 100644 index 20c2a32b..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ba.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ba@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ba@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ba.imageset/ba@2x.png b/Passepartout/App/macOS/Flags.xcassets/ba.imageset/ba@2x.png deleted file mode 100644 index 3cff7d5ad1e2d8650f6b68881b02b899fec686a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 951 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 IM6N<$g6}RN0ssI2 diff --git a/Passepartout/App/macOS/Flags.xcassets/bb.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/bb.imageset/Contents.json deleted file mode 100644 index 674dc77e..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/bb.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "bb@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "bb@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/bb.imageset/bb@2x.png b/Passepartout/App/macOS/Flags.xcassets/bb.imageset/bb@2x.png deleted file mode 100644 index e4a4afc2e4f3d19558886e6808b411b42b9cee0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 460 zcmV;-0WX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0f9+GK~zYI?bXdIL{S*W@$b3!GS^*=ye5ex3Y$SwRy4&HVPnC{#!IqN z7S>7)OaH*sG&@#gp(uvJV!W-cw=5=QV%)jLxYt4~W;NY0v(Wr@&*?d*Pn~mm1WijJ z1jA$y|JVt0eb5RDqr1H%_Yi>D(NZ?|;{eDKNaA~aV=vC^h}+4L&LI6> zjmw)PA1NClK#~M+35)w^iXM;V2%%PO_f-lDkpAO`G=IdW#IdvN&?hkfm$r* zAM%sBYK0P4_LCxO$l^p<)(H4dL+`IrfTX7YNzWItFKYz@yOU1<0000iIGKo$l+G=WwKWl%vJMMH4*B>Wg-hm$#JK*KHmSOYROIx4a+)XCxNb+9Iu|YlLB2c z!&sKB9Ez>CT_g|0ZSp|eCJ)4I@<4W~eBNn?H8~FQ@XighE>{}*eD4aeT zaMVXiMN%1yD;?83>RTq8+nAZYj;!$JbCRC72}XY93h_+V;z7?6S37@idESnsfE4S3 zShSe-?io(CPcSx>BfVnL^<)865vU5>eLPP#XYqYJOSCP{jr+4SHyQYSLdFv0?UA+% zr5vg8Gdh{0ETj@B5s&b8|9eeTn1f?Y^Kc`I^>U3ITL0R_lKvV^8UW>5! zca_(lk{dS!Z2so`g9(!9Jh!e#5ORtENd9ZJ#)9k#t6M+>Y+3NDk4g4dXf!vK$h4LV z;`a$IpDkfvD76vX1Yue5emG5QEQlic$1lB)oNF|gm@%1MTm{xEvPLsHm1E)0D(4#w z*|zdQPSolE{FulVV(Ud&k40;XWZTMbi2KSl{60Z@_Y8?to~bz#0UW$EMxaO#38|C? zRoS-kD`uw?F+Tp{kkb}`w@2J355#TqK-?w|#BKU-Ks1DSXD30%)WSpY lUA7KGGo}_t2wR7t{{VWOqM9RCmB;`9002ovPDHLkV1gd`JT3qL diff --git a/Passepartout/App/macOS/Flags.xcassets/bd.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/bd.imageset/Contents.json deleted file mode 100644 index d756d84f..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/bd.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "bd@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "bd@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/bd.imageset/bd@2x.png b/Passepartout/App/macOS/Flags.xcassets/bd.imageset/bd@2x.png deleted file mode 100644 index be8ec43820718d168abb8a3773d5eb8572f2b363..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 531 zcmV+u0_^>XP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0mw;2K~zYI?bbg?n?V@I@$V&X-ZyHc#3%%tAfdFRP6`eVy0(K*6brg` zkQNtdJGZohlSOd~Zf*_=>ZXHTU4?=URTQI$S_4Vin7n^G6++;B^PVxagTM8C?zzu< z9Cs&}e7Ov~;w~dt4%{iABKMH$C^E&JWrvXqlYyK`On0z=({O;TNRYJ-g}fpf3(;)& zcvR-?iB8zA`k1#Ey)c+Q5KJdE79NFg8UblFVfA~F{+#7m7cm{a{wUFxw|J8ZV@WQK z=N<_-u()?!6H%G*bAvC3WjRGH$k@5biz`#Q>C~}KZ^1vlCgj7h{GR~`;9W+)9mtcS zO{!SEGyUiJm4zlT+!fOO`{oFM2bM#kT&emHW*B_UgcFMiq34L(OxhnJ$U7t%xmPE+IG86SC3~Vk0VbF9I;1P}ymf zTTRbuTfpmQO>935`fj)sQ}~?Jq!2YB00jkxdYYL{sFXaavmwFzry3KfCg}?gd_Dto z0iPagq~j{Xmj;75i&)vg7Ld^d-`W(`+yB9PleK~z|U?U>6;6>${DKQnWkxpP&nam}Q>X1hR=lmaQ}fw<^_+ZI8a zR!WrrLhBZ0ix$zQbz74Nq+BGnNizt7ihN~8J~QXe+U|v`FYHEC_ zRtaP|Dj{Kz^UEIHvqd@=T`Ilp!<{n}25M9EH|TsflW78lo~~HQYO;JDe6hK*;D$cq zDDdQ%&ixZQmZrpUTT-#o)4t;I`u%z&2mn2RJ2MVLV-}4Cxy=?Cg^I}}LA zl8ct$)!ee?2~V(4AWfFMF6H%x!Hp#p$WJ}_ZGTP| zBMB8@QXA`LO{K+rggQNRNW+!8tp2C*YL)rjsK{c4!e~|#aOEFv{zbbu=I==+l3LR$YfgORa1Cj!U_c{m}`Uga{ VyNLh|IJy7;002ovPDHLkV1k8aRVx4h diff --git a/Passepartout/App/macOS/Flags.xcassets/be.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/be.imageset/Contents.json deleted file mode 100644 index 1f7f4e05..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/be.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "be@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "be@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/be.imageset/be@2x.png b/Passepartout/App/macOS/Flags.xcassets/be.imageset/be@2x.png deleted file mode 100644 index a427e6cc29d4fd9666b3ce62d63b36dcc28d8398..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx;yhg( zLp(a)o?FP<;2^?skyV7tRe!m{LbhLv*i1O2EEr=a8b5chb#A(`+}%g{<%BI-3h)1J zUXs_f<-OUxr&7;6v|r{lKYVWEB&XOv!Tp5t6Twd$l`UZIg?u-y?^8G>c1eYQ0@}pj M>FVdQ&MBb@0HjMiUjP6A diff --git a/Passepartout/App/macOS/Flags.xcassets/be.imageset/be@3x.png b/Passepartout/App/macOS/Flags.xcassets/be.imageset/be@3x.png deleted file mode 100644 index 9e02609760ab73e0559d5b0f4b73d17d54e22958..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvK2I0N zkcif|=Qr{m3J`I=C@oUqo92+jZFMZ6<;+1bNp(r(tMN>Vf7KE>%=K#6Hq@{uxEkzV zpZoKQ^UWV;zn8r+&rD{CJLMpH^g!^<2i}d_3McYOcy`Qg*+Cp3cwX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0mey0K~zYI?Uqk#6G0q?pNSEZ%|Bzold)6<_mW)quavppFf0cH9h0CC?phOF!|hix8kP5}aJ zKB(m-vSdJ%84wN-CqTI?lOze^o^6^-mdCU-&KqE#XkQn{z;V6t?6?WuDCkJL&msH5>@6UkPLWVuTr)F{Ux82*JY(je0BR0it--gX zx!1SD2N7A~$CrzowG!?=4v!|`UWZaOE1rSnB;?ChQsL?bKYME5LIoZtp{m1!bDcf0 z@34~UCDl-0T{G!Fc<=JbeiHit3Ao@=6G)+Aj5^3E!!vY{+kwVBm4(O~2*=^m@5XvC z(0*>5Dr^^F;l1(ylr#tp8{~oyiW>bYM}1{-&Jek TAMKDy00000NkvXXu0mjfd%N!p diff --git a/Passepartout/App/macOS/Flags.xcassets/bf.imageset/bf@3x.png b/Passepartout/App/macOS/Flags.xcassets/bf.imageset/bf@3x.png deleted file mode 100644 index 48383357d4bcd5690e3006fb93b06ad5d770f9d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 700 zcmV;t0z>_YP)XR|w|H$K#ac*GAE-#_R9~$QB1+?^5UI4q z)yLxEEa4 z<9zr3{W$Nd#oz&8@SjVIK?X1zU5HzG8eM=awLqFpEs$oDauDBEFnU$HtN_UBVXz{q zT~+|Zihwn&+C?#lxea`qz_$_1ew9v&L6%M_)zeEIhz8c-a?^^4m?|j)Y1<9nb>+4< z1Z@MV+!TUXN0x~xoL*``bg&LphOG!_UL}q=`mDAJI?sbS_>X=j=xZzEdKUzy!Om6B z&pv~Naj?@4YIFP^@j%B(=)5QjmMJ?A->*RGGEj7uTld4t=!G@c<(&_?Irw%QaowOsFuDa!^ntg|)%M$l+wb7U9ONC2LP;UjPifGCmcDXuL9R=&&~+%iZD0UQGx8m{vdXaP1A; zdoS7tf^fDUI+py?3BsQ(S7pCs2F^TzOEZuzik$sH85KwZ3HQM31sIFM#5`3`SQ{}?qYq&w z0bxnIm+Lj+H^pL}m>1WLTJ|gW!U_%O?Xxnmk<` zLp(Z@|NQ^|KZ}j6O`xrjb@fcP8;k`8uLMe9_(0OeZSQa1o*&=OzeKs^X63w%b85Z@ zOloXASfn6Z^!)$qpz7C)YZh7W|iQKi@bmTr}$M{K5oj ox1Pnk$~p;G6kmYx1q&92m@~46gTl8Y16{-5>FVdQ&MBb@0KLdiTL1t6 diff --git a/Passepartout/App/macOS/Flags.xcassets/bg.imageset/bg@3x.png b/Passepartout/App/macOS/Flags.xcassets/bg.imageset/bg@3x.png deleted file mode 100644 index 5d511c1124145c036a98befffceb573e1d44f2fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 201 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvI!_nJ zkcif|*Bp5p9C%zV>PE75Jm}&-vY<_Yh4n#$FGuO1X=}5TUIE#@G55Q;#_(B`<6F?f@|Jr$9r=sW^n{McSt#15mX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0uD(;K~zYI&6iJW6G0fpf4kX|lq3WbqO4IAqgLt%P_T#;LHq*hK|S~f zQNen%>P1oX;6XtIPaZ02C5R?zYhq|rN^KCY8{2|XwAA9CrY%i&+hjK$N~9DvIp}nK zuJbbUoA;gPof%1gJ@D1Dr)?d}Wc`{#@5@k%#WB)ZstXItL;j^=vfYRr3gSI-kZ|HL zk#8UIpFIJxZTMeHu^ll@=5rq?#p0BnB{0V)gh2qRVrSDnd#TP97u)>Aq)-t1T)SMjckN)+og1k6oO7@E n+P_9mrqKH`REs5H5CFdc5+laNWzjjq00000NkvXXu0mjfi}4M^ diff --git a/Passepartout/App/macOS/Flags.xcassets/bh.imageset/bh@3x.png b/Passepartout/App/macOS/Flags.xcassets/bh.imageset/bh@3x.png deleted file mode 100644 index 8ee86495e1c05ce764c6fe089fda0ba69dcea0b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 677 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/bl.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/bl.imageset/Contents.json deleted file mode 100644 index 59cca760..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/bl.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "bl@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "bl@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/bl.imageset/bl@2x.png b/Passepartout/App/macOS/Flags.xcassets/bl.imageset/bl@2x.png deleted file mode 100644 index 62d74b4234bbd0e63fcc2d2c282acfe986a5e93d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx5M eG`?WL!Z1mKXa7E*nWjL?7(8A5T-G@yGywpH!bC~{ diff --git a/Passepartout/App/macOS/Flags.xcassets/bl.imageset/bl@3x.png b/Passepartout/App/macOS/Flags.xcassets/bl.imageset/bl@3x.png deleted file mode 100644 index 5b94a2263d0ae4c51f921f00255def0ba318d445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv6`n4R zArY-_Z|ZU}C5p5@v_9-|n8Rp+K~I9v#-6LZ60!a34y2WMvk9^pM)N5B2yM3Sws^|> z{N?upv-7X5<6keJBVKwdKg>M!@!q?p^B?aFG3&bgRFBu^Rh^_@a_Zyn700&k))JoR zeO-ZXVIfDabB2u5V*#o53l=TM6iu)SY2Lc2p~-xB)pRANsX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1BgjPK~zYI?Ua8^lyw}(U(dsxaCC5Rz#({airA5ep)*4yr!F>}61pPW z++4b;%O(trwZ+zQwVJI~{nJ*Ope|D{1>8!g1#?Ou$>=;mt{Vgl26w0&cffNyxV!#P zS^PL#JI?io@4uey^ZI;0uV>#M!fz#?xR<3R$%nEw@WIy&ocrk(Uat>;Uyp1=T~x@z z(u1U)wKL*$0kBFR&(RE#)X@Q=3i9aOzK6gf7{vGLhlWmfO|szFw|tT8#8kDP-8(V} z2@z-EE0@Jl{koRQqOG$mc40oTE7F8wk@c zB}|{P6c~+EN<&NZI)0Wl5*8NOkuZ23Rmk-OE*)O#Rs3Ek= z3g6ukxqW#g_6z4}R3`9_MMJCE`T)>Rq0TWkN58qsv2!;l-kZf+uO(CZy@9=XVR$O0 zFFP5hMod}kcdWn&r~fe zDgs@01~QjK;^nGoePVtNE02E;fVIA!o}wc3)zzq$ECJwp)>c9-|C}WlWQo`6%m}1w z=2CC$oBB+cSsMZ>Z~Oi0hq-&^3{I1Y;mS%>B_)2j*{{|-W!y1J^QrG}=-dd!nfx`{ z&9xI1etB82L?#4@j)VbRaceQU_Yfk=St|WO_G1@mYGwJo>%?e7(F{ICqu-&B&OZ){~#%8?m8$aWmGMn#U*Nz(qvaVFtW9w5wj>K=~C`WIls6 zR!Pe`6_V(k)f1GhahN+OJ6*u8vkqF{T+8|mJ87)=5|^=`E4gvJ-y_ZH5n5x?3<$hB z-!gsa8ZN%9!su(mBX|f!LAEE2Z56#l4?1Vif+1*!Rco?QXtFV^iNgA947wWwD8}0S z@<9`Mm}uKL(YEn_<$`u={%f9y%*+#!nfc#{{0)AcL8E^bfFu9_002ovPDHLkV1kHD B-dq3x diff --git a/Passepartout/App/macOS/Flags.xcassets/bm.imageset/bm@3x.png b/Passepartout/App/macOS/Flags.xcassets/bm.imageset/bm@3x.png deleted file mode 100644 index 32ced602f6d69b3496db79f82b3f19c3ce6a42dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1525 zcmV*sFa~0qoz($s7f~X>${d6DmrKoc>E7 zYM{Prz$kal>Yv;<$0>?T%G09^k7*Z7V%c#)ooe?P7Qel(jo~epY>VtI>Py}eL#IsQ ztpzX0Gv_5*-e0wsFE%^yc(v<+*8Y946;;u6xD=b!AXwukn~zDM=4b`C2Sj-!0nn`F$2!(@~@ zG*D!Tw8^m?p4*={_T&)M+Qgvm*W*b|Wntz%22Whk-3$Q8UR5J!>k-z=63-{kWRb0z zhU8R&lM+}ye>Mx!ALgU%LL^DNAyE`W(k92Us9!U}H){z_N+2SC163_L3qRRUR_-BM z-EFsMcS%^ep-8{Ez(IX&M4vr@)oLOn`B4Oef>+h3d}D>=e_WAS9?(lhf-#yy@ymKw>*O^)kb%hBx^ zAS$xN%xS|p_QQN~-kV79mOM@-K7+HNmCq)=#IW4Ue6-Zbcl&DQ&9dYAp#5A;6!6^L3qj~wu~N+y z6t}nCIidKoJYCwFk2Yj5yY+Ck7(Li%nSQtrlf7jG~dc&PjUMx*iinCrp?PAywTgxyZB#Karo4?K~Ixu~># z5P!Dc_CSuLrQzJT5!2A24BxvK$zRZJtgF5vH2oO{AMaH&nCNc!> zmyWYtPNz>q2z9M0VP&i6?MeX#cjyriXQVn~36|nw3gq5~6c&IEf1WI(&%#VvhJ|op&z=r_{JyoUVLgdn zvx$?w(}bIo7@9ntn&vFxQ|1w56sR~`NaT`FF!#H!Lm$6hh%E+*qMRA@x+{YEGi5~i z>X~R?h9HLlQ1bJ9_EtCZo9@J-SeTSkM^sT|yIy{MBNmUw?~Ve>z2#)As>f+?^Pzr) zl(?5DIFXC{oh=MM>f)FA_fj2hCA`%2uU>xtL#*#%W^HwdEe6u^RQeyU{Z}8qgIv?mF?aK8oQ+?VN*a`UjH;1gz9>c<7Z|y{0)y6ZnE?ZG zO&U6t144+XYQ*a8dTD;|S~p8VAgZF_Kj-=V9=TPz0}^Pu0}^Pu0}^Pu0}^PGfdYRQ baVzvUX1^@s6RQmj^00006VoOIv0RI60 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 JPDHLkV1jik&S?Mu diff --git a/Passepartout/App/macOS/Flags.xcassets/bo.imageset/bo@3x.png b/Passepartout/App/macOS/Flags.xcassets/bo.imageset/bo@3x.png deleted file mode 100644 index 22bb99dce35173d10cd16795254f82eb0cbf9221..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 679 zcmV;Y0$BZtP)kff)X=J+#f2bhqp09Uu&%pt z=fYNSC{_^B_$oleyy}OSd;<2=i@cxaWM# zIp>}c^LLKev;l^m2^g(TLIql@6HtXO;x&8`ui=Y$4PV4-w2g=nK4Y~y2PFu11-Ds; z?;Hf^4mWnv>_LEJxOw|&(@HB>?%>Z-8q1DTvEqDO7-p?xHY*&;H(%Eklq&|$UR+=~ zvr7HuUH%q!`1XCC#ewrY7@OsA!a@kOg`l*wq|~W8L)40Svd@!z8a7#($&%=)Q%?19 z@5)6wORq6wopk7nXquxcw7M_ix*B_h)8v2eBi{zyXFU*m~PD+fDj#E-QXVi++CBg+lE>5k$ z1;tN5HzlePf#L?(5u0UE%~ef{8Ta`ZRLW zfVybBs}Q9kX1|ThOR94rhBvcJti9rAF~~;OZ6;P|gW!U_%O?Xxx;$MR zLp(Z@?}*L$@%H%1$8&g?Id16c>^J6J!aU*N3g-(j%=mcW(%bP`9}PQrmngrh`}M!8 z)z|&`^eywwIP7YE_;Km+{cOHhdaPJvL6}FXeM8KyuW#<|?!V7w^53rR<40ZZ-*23^ qOGrq3VY{Tr)6=sZNjXRh1H;zo3TH#>k~RTd#o+1c=d#Wzp$P!RMpfzn diff --git a/Passepartout/App/macOS/Flags.xcassets/bq.imageset/bq@3x.png b/Passepartout/App/macOS/Flags.xcassets/bq.imageset/bq@3x.png deleted file mode 100644 index 333a132024ce04a789f2a3adfb6fd13dc4025d8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvc25__ zkcif|*9>_Z0(e>;P77fQ^Vrb9z*eKc#eOE|%zIA<7G`I|i%X_Ivt#|PF1zaarn;bp zy?dj-b#OR;YtZ0vJS<=WCQ_Yp<}821t9a$EZM92_(ek5Eaj@jBx9*+sr{8@`$>#`s qwnd_;O)&#ZoNAfT5pz>n>Cb@-{-X5A%UVDuF?hQAxvXo! diff --git a/Passepartout/App/macOS/Flags.xcassets/br.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/br.imageset/Contents.json deleted file mode 100644 index 3eb3ddb8..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/br.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "br@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "br@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/br.imageset/br@2x.png b/Passepartout/App/macOS/Flags.xcassets/br.imageset/br@2x.png deleted file mode 100644 index 02e55517d532592d89a38962e20ce2d36d61e366..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1312 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 IM6N<$f)BK}UH||9 diff --git a/Passepartout/App/macOS/Flags.xcassets/bs.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/bs.imageset/Contents.json deleted file mode 100644 index c28f5fec..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/bs.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "bs@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "bs@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/bs.imageset/bs@2x.png b/Passepartout/App/macOS/Flags.xcassets/bs.imageset/bs@2x.png deleted file mode 100644 index 5ce924776105c3b09f35d37a75d04849df6791b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 647 zcmV;20(kw2P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0z64XK~zYI#g$t~R6!hvzd2{lYMU-*v62y%6<%H{gCM9dhz!dtvNGDR z3vCynB0@Cmf)5r5L4^-K20`=?-F%Bcw3jG)lZZevwOA0Po4aQ(XL?9&!*VZYcm3W5 z<~Q@r{D=RD(uSkMUrWjgUA21@bHwU-S?}eK(NX7@yG_FP=%Fy0FC&&0XI!Mn;mFaZ z2G;9WmS%|FiHyr=3dHm%942!A9_HR1M7p13Rs=QkX(*qM(3KW~=gy!;hB72F z3szqbW?e1eu1*M=X%_j%jr@ffAl%i7S+@)8WpDaKq~IAY}FRpx*RRIA0b9rUFaX=@p~eHBY*tHC%Cfft(ES|BH^Q|J2!@grv28KQ&k4A38y-APR>X?x ztTx!Z0p7lV!~2rU%UEqB7WsK_y%kP)K-f5vOg1P4c!f@d|U? ha|CVex;YV;;uom~q7b;g_hSG6002ovPDHLkV1kfpB+CE* diff --git a/Passepartout/App/macOS/Flags.xcassets/bs.imageset/bs@3x.png b/Passepartout/App/macOS/Flags.xcassets/bs.imageset/bs@3x.png deleted file mode 100644 index 090f7264612bb3ba08ea0028e20050004a4e50f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 801 zcmV++1K#|JP)%z(1e@GDZ`m%LAE^KtQ^wnt|sy)BV;{^L^7@ zRigU-gRkWsoqxPo%&>|PUU7*Dd>`FAgseNBZ=c8rueiiakBl@I0Lx{~=@~jme zArr+Wm>8e^Bo;$m>p->Lgo1+nnT2HjM6zFOFGu$cA#3XLpcNh=6A2ScOe}bTtZzUM z3?nvf%ArlCqvyN z>l@I6j}Xa?jB2kfrA#cB6vvO zH$f(|%oKrl<}B)dFQT;6j#*d*yp;oA=vBg{t{M~io3;O#r8tbrGAAPC%ylSTOJgvtDJ zzZFUEpJ}(lDL47fDl(UkpP}Z=OuKy_v!9>SM@taQv|H72c>lp}cFro2r7#A!TA}WU z-CUK+iJUwJubx5W9{btKe&l>MCE>|KXuare45t;55^h|D=Oa)QcQ+0=tVpUFUO#vD zxSfmjiX@6)v=_SDbGy11QhO$tnzjf#JNK zxG0v>TS}ptucL0?fr2&mtfHtyy-}tQp$A70J9pXiiA?BQ%_VgnX#ZIN7u%y-!6_x105#+uDu6-h-V3Crz&U~}X z`?HH=64f(+cj|PWl%gTX_Lfq37cQgk+=I1i^J^6W1t3%DnRmi&2OEVPq4thG{#JUi fNv@=KLKOZ1mXoMyLkj`?00000NkvXXu0mjf`>%Wf diff --git a/Passepartout/App/macOS/Flags.xcassets/bt.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/bt.imageset/Contents.json deleted file mode 100644 index 59623c89..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/bt.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "bt@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "bt@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/bt.imageset/bt@2x.png b/Passepartout/App/macOS/Flags.xcassets/bt.imageset/bt@2x.png deleted file mode 100644 index b24dd866da0d5120220e95cbfecb2f81a2b38d5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1139 zcmV-(1dRKMP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1Sm;FK~zYI#nww~R8<(j@&A3zojVU^hPIZDCbm+LDj0bPMdcw6BO9Y8 zvJqVv;|3SRjSJKbE4GpV3r)}k1~G_=NfRUi5el}BjX_!jd8f!sA5%JWA9LFKE+YnHLju>G9Q+6=y zzY5VdgzuxsJf7Bs@B3uWcH$rY40Zl$IIt66IOxSfRh>ygR8>+|pFxkcA{7lOCFT4v zjQ-2S1`MLJ79iqj!qYkY`KwWe3y9O*SVR4{*O@E?z>2D<<%0;}p=lZ^WhU~RhrVYE z%8n0-*jCgHmG2}TC05b$3+qC_CE6zm-6^{Dn?;(8yx-ALH52&;JxBMq$#FIer#~=p-SEPH4_SOnC!o%s^OzigT5O)=A`8FVXrToCfewu`ZuP zxE__(xkS6ZBaBZW*)$zv-?lMJ-y1}$qZC_DvVJ8}O6=i0!kC7gSV(!n6)K~{;JPS^ zg7x!$D3>rV^$|bQeb>_W1QEs}obX|kfgGySMY`zWRUSiFDLk)&UY7-3r{X%qbKjHf zKQrOl6NZ%iCWN*OS46NXotS&K6CSs*%0(>GLe6TZ_~c5i&09md^Goy_2tSrWiI`}` zF@E{~hzZ{sOB+TICl-t0bvfAcH#4$)9YMMgJO_EI3z2L<2?Ta3%jlA|xKAv{-SiE1 zcII8{Pq-sN-3DaPN;r50F?}iVM`shZ%;s7uLuThDbhkvq)_3ryHd1PTnSmMYJn-C0 zD7`9}!sFd2~5}&&Sr{W`9X2a?65A8S*6;Ei0_}d7wsM05MzLWTljpWyF!woge zecP~t%2?^wQAncO-^H7^ifAH*diK|G-vvt=$&#@%GRBI=J{VIKP8Uq3LRXwmwsI%0=2h^C%K{rojD-}Y4HIInCVBOOXc9CI*_ zIe7+f$E^`EMq)6B`N3NlV;}(3KYCEw7NK}H$rA@_#JJ;(7(FA`huKOvZbB@#pK#Y^ z(mjVzYR0$;8Dp3oFI3-yJs;JOa1(MVNV3PR8fO3R_zS%DxLj`w#bp2h002ovPDHLk FV1hD#7uNs) diff --git a/Passepartout/App/macOS/Flags.xcassets/bt.imageset/bt@3x.png b/Passepartout/App/macOS/Flags.xcassets/bt.imageset/bt@3x.png deleted file mode 100644 index 4d6f6b430c548bba76a82ccd85e0bb713cefa286..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2040 zcmVAA7#p!d zVpE&`Af>HMZ8gTUnzS*|{xE8bwn?q28WJ0oR$F^XjJEX>ib`!NU}*q@NC6pUhBLQw z-S@ToV_GOWgTpXjcz>PC+TZs+S>LzU+Ixv~*UVP|$+@gI!VVp{3J@Q&m5C_={^Y~f z5dGu!GGUb!Z1SKt0XRoNc7*VU0K)XxPv;PbfG9myN*~n6*W^YQNN0Rw?S)Y5ghv{q zLcrOc5$O<#>C}9ejHVOkZ$%_!vh|-qI=Vps2A9BwUz|`%AJkbcErD+4Xzjd-M(r?l zPtcaL3EF=`&^Uml-X*DT#}NG(vN>{B>RtIyS&Q6c0Ce&Yos23I0 z@#@{g`US+XB8sBp!<1snmMwIhcNtu?5$mB{1Q;;X2dRQjeIDj49yNtXcg<9sp#@PX z!ChH)Cx33k2Rt zbDh!Bk}{=ay#)$_z$0+3!*Fbb5F|;0V|aL>MAHfk4-b>EO!)cj=i2Y&v%Miw5s_h!^ubF{!p_%7hx(CgzfEP?8n}4_O6nAP_Kulj@=HrA zKpGtXE;LAL(0GlkRiT;8K$=sKQj%?Hr#ba}j7wMI=SrlZN2BKuiBk9rmy_rgX2*2= z-fp5y9%tXSG1E?RX=zaWBG`+dz8Rt(a92Rzk4Bi3pheFyICK=NZ7M+!;AjFbQE=rA zRFP1@B*5@ejU3cE%hu!+^iPi1o|^H1bo(baWwKdJ5+^KSn0; zm=;cdMzj_v-GV2VU>7e&h>^BaN@2U_(+mRa{9-B{D@m`|j%e?qp#-ku5JeG5lHfk~ z5Te+LT(pe%o-fhf_bveWJ+F`3#_5A3LquLL4YL&^QD|C9b7(gzuA$OANn9jNEz*`w z`1ESxB*7MfG)+-T5l0cS?;tG)cH%uegy~f#WaG3!l#(=zRR}GI?^TE@uanXHvAh8o zT8Q6uDgIC&R0oK%t;k{tP1F8+ts>m9nL_m_0J&YSP1whTLDCfJ$!wC+ozy)GbPpri zOH1?wZ2kxQ(T6a_)^zL(m&4p8F!xdhD@Uoi?dZ7zh7{PF?wzptNdXZ8Y`q5Z01bH_ zhH2tA8swjSle&E)@q!+T|9lye7&M41fIAIt;IMb~wrBa-0#IjGg&z{t3Gmt1|~c+0_Gd=*J`7){e? zT=Y4rSAUJ>g`Xv`T^j4}!@u?>MEM|N@2W=E#CN~KL`}V)0dEd9KZj*m#Bq${57Rzx zG1cAMP;p4+k=w9(_X7}H8Im+5ZJS2(^6RNAy_V3-;AAqi5|0aEz5^bGb=zn|ur19NDyHQBI#xQAh0NpK8E|caF8OJzX_WWScw;k0PY+ht9J0^c zhaMh({8UJ~Z+(>P#h=00^Cn5p0rJ1P7nvp#f;v@=T3T@;sj&d7Z7F8{GSt>PaUXx4 ze7#9xSQwH2p=-QvJw$Qo3Jlw!R+x&_f0$s#e4InO&o~5(0TM6+Va-FYz5vhOh4u1* zWBOCMdp`x6HJ+PMof>U4}qkqz|tyYdmcaGZTCmX+5HCYvQ-QZ z4(n&@vOPMs?LJqf^Jch*?O`V!f^+pK5#T>s WU@h*#rL_3~0000|gW!U_%O^81FdBNg zIEHw1Cf7)afB$iNWv~&C1%om>d-JcKpLr&S$0we6ZGGqf5Gb5EfAB+^JJaFrV{UEy z+F=had6_&gGOs`5vQaaHX)&K8I}E#6Kl%Uv|CW;D4>$$I79KfryJ_M?3-2(tw6r-v zObZwC+rJ%w3vtl>3Z#-}lXX9i+}gSw8Ou6xq^FUVc7#w~8Vy#E?2S6|_#Coc*%QUbl7 z`5BqbE`x{$iLK3Yi;LwJYAySQUz(qQHtFmUJxCPG;y;Y&WdYe ztc=dmDkl<50zajgNhbfT`>0f4rc%7Vm?3Y40;7UBD(g3!Xy$SpmiQQ%r6q#^^5cUi z$RMI2gNTL%MIvngj{q|gW!U_%O?Xx=6bp~ zhIn+oy`;!@$U(&QVzkkc2_gQo9GX@)vz`%BSec}~hUv84E@m4Em34hf7OmCyY0}{P zX#PR_pYJ^TN2)f*xGWY$n+CJ}G442{VAOo#PNnZg&r-ql9uKFTueOt~3O%9p*)lPH zUt?hL!vMa?KRX;#CO4`~f4w%Eckgu;*q=($mPQpJKqE?3Z#TH5?)@-QV$!SM`}q;JfwJPQ0}j nHbV6QHA@OVVz~ECP_&MFo`mHGb$t(@V;DSL{an^LB{Ts593D!> diff --git a/Passepartout/App/macOS/Flags.xcassets/by.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/by.imageset/Contents.json deleted file mode 100644 index f7b13fc6..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/by.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "by@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "by@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/by.imageset/by@2x.png b/Passepartout/App/macOS/Flags.xcassets/by.imageset/by@2x.png deleted file mode 100644 index 3b09bc47daeaf4279a6f7112689e1c4a8153bbfc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 717 zcmV;;0y6!HP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0)k0IK~zYI#nwMh8gT%|@#l`?4miN;i56O}(xPdNNYffa+C*ZA>7on5 z;Ns%kkZ96D-+&V_(cOup+BkGDoi(;4O(0RgKZA-Q3f6k%4)jj`guaVkpI7k7GkvLx zM53e>3Qkuz4BBMEX*>>BG>V_2qae61OsiI-%#wu5wKbUCZakitfSSvZL;wJ2HJjyZ z0|Vea9%#iPY(5|Kiwp7)M=+?BHa1XK6xjOvkzZbhFg#2g!sx}K!Q%Cz-DsepD6so{ z&46K8LP%;_v5a;;#U8`1uD-wZYYz*~M z3DWd5d5DrubFWudal5qz{pt$7l@)0DJaGs^^L+bsX$dyJAFL?iBpwID^TZ*v$?0r1 zDis(zI`D9F16B}V9~vSL(aUAVCTNZm$1bO_D-z z8q!Q_xw*V4EAvE*sNlQCB zLrd3T6a-ZF_rdi@L=gZ$*S>u>yP{E06a_V##q(GU)ntl1#Qe_xaCdTox-7%m*N18@ z2hQ^!9-(Y}`~LVCPp76J27|cX+ywu|M;!7G*X-}nUU)A700000NkvXXu0mjf-eW%5 diff --git a/Passepartout/App/macOS/Flags.xcassets/by.imageset/by@3x.png b/Passepartout/App/macOS/Flags.xcassets/by.imageset/by@3x.png deleted file mode 100644 index ac56a118df382db0780f20a598180a96e22d74f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1456 zcmV;h1yA~kP)t*Z3*`bUMwv;i3x(zo80wMaqd{ETH z@Ze^=AP|D0Bw~DkNTLvp3K0#GXoi49vXDSEDWa1ZqrvJ1(-1bcZj_eYy3w_3*Y$dQ z!1&tpcG&0Xf8M?i|NqJV{EjgB^Ut@{Y?g@6reVD~+@R>b`^ZG2NEVCUjhYB^BO~hNmtMlLdNtOD2CNMY z^lsfsNnIVzmKKH%9%TIc@Ad91NSyBOrgGahvhg^^%1UCry;QWdp{CO)rKRNJalIQg zk)z$+AW7V_ZXN&Z*nw^3N(@dXjdxA#ci&Ne>=>r1DsCJ(Lg_;f zA(_oow6-!kJWTk)1-%^1sk&X`XsT9VF3Pw9R5Q~c$_j<9^)DZjoZ+tIUroB%wqjzW_!kEXS z4jnv*WHMn{whUWyGsA}uBN`0YA9;jt9>2=(!Mp#hC7RuCiptBG?(SyE&YgFz@OC@# z>(^PleLF?tNxd625yrhNIsWi?Ce<{`0O+0LLoA-7`K1(}r<3PBva<7;yx7|(g{!4(#-AQk1@8Kvq5lGrKpXdzN=In`0000< KMNUMnLSTaax7p+X diff --git a/Passepartout/App/macOS/Flags.xcassets/bz.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/bz.imageset/Contents.json deleted file mode 100644 index 35c628e4..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/bz.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "bz@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "bz@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/bz.imageset/bz@2x.png b/Passepartout/App/macOS/Flags.xcassets/bz.imageset/bz@2x.png deleted file mode 100644 index db05dfc960d061044d795844cbc7f217adc8a81b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1505 zcmV<71s?i|P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1(r!fK~zYI#g}<((`6XPKfm5x+pXQzcI!s34YxW#m>W1$Ku|yk^GL)i z5)%_*f_U)<;(<|v3TiZjAl}9xf}jut$6-_yHW_ml+uCjJ9_}jby3uyM{KEue#L}S& z&!5lxJm2T@9(mvA6>7A3dk}=Xxw^JT;!>umA|m0cQi!85m1IkHNl~@RIO$}WR7}1p zmF#pi0DZ$_xO&}8gd&sHDjx4Il1w&fX1;-SD@&PPAfv!&qrz2zBh!IFW1zm@iPI&p zc~=#`whl~6H(?N^T*B71w<0q%FegjOjuTtC$uJvtz{`QQIu@Dc@j>l&Hm%r9k)wpl zGhx=eeU!jNI3ayeB0*ZUg0Eg(%#^$#(tTp~*X`xn{1WVrX$VFUsw^2!Sq~50vYLk0 zYPKDDg9mP%%J*;IM~X`R|A2@^0-rs*oc!D*4xZYDy>J%GO3K;a`VJ>MD(LBMVra0J zA3MJ0%Z9C}vom>e>3a4Z`v5m}?A}<8M11-AUN+*<@|k$#m3(|`D~~Q&N17y^eV-nn zOqj!t`q$6|lgNx|nfrqaU3)j*A3ly!l7v?$B5ef3slV~avg!W~BuOFV*$2y*tt!G{ zD<&@^pOLmUvcoa%n!bh?%v(t9kWn#wi1!P;6r>e$lPQ~&5UiTDievt2x_g{F|Il2N z^7zM5oRwAjWWP+4e5t`@bL>crwd|?=oK^OFIl1F4prA#TX=U1j4hZ|$clI#l*RNzr=>-n_ z-1@Jz;%<$_sO7yA+o%n+l5RGm)oK|V>87);f(f68iTWdCM z*dWQt$t0&{qe{{7bLwY5{X1m zDwV_}QffR>qH;9>#qIneY($!5LMD?D3WWd&N5d@8&E|p9hv_)yjSCWYYYtbnqqlY9 z9rsYxTZKdjV^HL9s`(3=y6SPcZzkl`AP5T7hEXihpRf)$QDVCdEn0Rr>_+2@kP)?V zsIvXaL5@^+u>Gmk42sWkxaBAst(c+43glKBgV8p+g$hg==eZD;GH^l9xJ*u$rP!uKrGwYsEap(_L3-L-xPRelp4ncBKN$b;!XFpPm%YwvWaH#S(=3kdffz3<-*=(uY#5J};%+{$`9C{1}1A*+%uNT*_i*%a} zncq(NhQ0VN(+XsTe4`1q0iLk{^VaU9<*bG&*UX@-XaVUy3GaTlfdQ9`*AH$a%{PK@ z-cKN1&W07Q;A+${|M8u8$AgKKKqxMLtfyLKG$j-b%&_ZOx3ZL_v#oTFw&2h^XdY}v zm!v}@&fuF{&CM0Q3#{aP_(gg`<;| z1VHk1-!Bajk&y6;{LNafDC-|4j>Z}!`Tmh{gsc0V0LK3Sd=wb$h$tE`00000NkvXX Hu0mjfpeE;| diff --git a/Passepartout/App/macOS/Flags.xcassets/bz.imageset/bz@3x.png b/Passepartout/App/macOS/Flags.xcassets/bz.imageset/bz@3x.png deleted file mode 100644 index 81824caa89ad2bb4a7c9265f12910d61e28bd111..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2754 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/ca.imageset/ca@3x.png b/Passepartout/App/macOS/Flags.xcassets/ca.imageset/ca@3x.png deleted file mode 100644 index 938af2e0f473981309d006b3908c50306b7126a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 705 zcmV;y0zUnTP)JQH5H5b}=kHA`lkoS65IjA*3+I(wv0fVDO41OPZZ zgiJ=uMeRbmy3}51apA=`FaU#tdOo!Fh=|C}jz~v`eIFmo!h%SpqP<7-g@}lx(<0s7 z&IGiziEM6~y8AjIA|hvJBB78yKY@TqDrGIA1|jZ!*~4L&pSNcli9kH=T4-?ltd+~b zz4| diff --git a/Passepartout/App/macOS/Flags.xcassets/cc.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/cc.imageset/Contents.json deleted file mode 100644 index 4f10c903..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/cc.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "cc@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "cc@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/cc.imageset/cc@2x.png b/Passepartout/App/macOS/Flags.xcassets/cc.imageset/cc@2x.png deleted file mode 100644 index 70e652a1c252b5a82693563f76b3b0021c514542..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 650 zcmV;50(Jd~P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0zXMaK~zYI?UqeQR8bVie{ZH4N6Q>j8np}}7_G>Zz{ntNBBMn?&`#P3 zx+>aKxN=k5A_8mK2hCzIh{A~s>h(cIwJ8`2CT&pDXKEV7jLA4H4vxL|oDW`@v+xg> zd*9)lcYpVubMJc!odkg6EUyB#Wd=xU1;nVF0a;}ZB_6g3@3f^ z{CHF!n04JV-e*l^*z_ACq zo-nq@`2jm-O4*&}+!`^ekvm zbIi_S>D3g4Yw2daKPV~bsDbLD1$(IrzNc8Rs-=do3=)e&QxNVALQgaJ8{yPeDaPM( zR^GTsYO<9UgVq<>`6`&}%%~;*=+&-+t_CQ`u;e8N@wnuaWdgCBnrziOBF%Vj{CAK{ k0Q1V3AEOlzqjCo14=kaW=lMg_LjV8(07*qoM6N<$g6Zrk`~Uy| diff --git a/Passepartout/App/macOS/Flags.xcassets/cc.imageset/cc@3x.png b/Passepartout/App/macOS/Flags.xcassets/cc.imageset/cc@3x.png deleted file mode 100644 index 738e29a84595e4995aab5e63679ce2d8aad724d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1014 zcmVve0@*pe93&7}>tLew@T5g|S#4aFo6 zVvC?j3WfHqdB|h&!9p8IX-nIeLTF0ckkTX%Aqk0?=3)#rLcms=;3Wys6=maX&ARHk z_TebgsXM!~IMyNT4~Cg<=KDD3KXcCc&L}i;o#y~MP`TDln1OAr9WaZ85UY_8Vl|!{ zA$Gk7JJ&&VKCDjzUl6W(;qm}1{0~kk#BXPrkW2@hs(=F<)qDOB)D=OGA8LMw!NAHK zjYdfp?8<{%!_YZqe4Qq=Yzmy)Eh2nD_`?Sa3s89pnmXaFTxi|{8M@zbWhlVm&G5|* zcz?5ujiM1!TL4ur!{i*C=z=4yaPS)38-*8~(BXqu-Eg$nRR2|H6u3AbVSH&o#zs+S zRKxK?6}*!VH;19w3*X&`sX4GO%B@%jU%n{@ww^OvhTuwq;JkV^isp-K%3MtBLCAB# z$zAY@8(w#U(*(311 zPHYiw2ef$MiWi(ZA*vD5&>As2~MJJb}&^^bkOHW{9b3qg0DN=@8g4@GWaK%^c7&5Xk_(@gL1{Kre=PTVC*hNOaQdOX!NPPksXKi7 z^G1U3)!$G&1gH993jvszi~c;~*48ovWfx$5svJ*epwA!M*uV2%iBZ+gKZY|=UJ6LD zNV36C2E;~f8V3W=?lUF=B$TEX%VueI^ttl}nkhw*&cX<$cE5oT{Q5|)>pc=`k#N|I z3HhZTe(pDB+w?a6*j>q|bcdA8$~^7!jcHO8%tq6qm_>Fnd{zo|9@*W8rs2;qxa)&2 zx?pt1Y>TTTWQk;(v^^kSX(k+f{z_P-hFq8lL)Uox>y~!@c(B~BZfyblPyz22#<$K= zLhLpu&xQT@@J6os`(SA8>bVs^p)h>^0Pat~?O_=k#Zp259!$y+FO~|em5>l(H4;Ls kMnZ_yumd;N+6goG2Tqy^qQ-XPasU7T07*qoM6N<$f&(MkV*mgE diff --git a/Passepartout/App/macOS/Flags.xcassets/cd.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/cd.imageset/Contents.json deleted file mode 100644 index 3f149671..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/cd.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "cd@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "cd@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/cd.imageset/cd@2x.png b/Passepartout/App/macOS/Flags.xcassets/cd.imageset/cd@2x.png deleted file mode 100644 index 228e8eda8d080147983684e1b47e34d1b5a2b05d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 910 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/cg.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/cg.imageset/Contents.json deleted file mode 100644 index 3ad6e3e3..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/cg.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "cg@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "cg@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/cg.imageset/cg@2x.png b/Passepartout/App/macOS/Flags.xcassets/cg.imageset/cg@2x.png deleted file mode 100644 index 0e219d6d8b4714c872f6472c0d7c2011b80160ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xxj(NH` zhIn)yy=u$FbP0l+XkKl!IbB diff --git a/Passepartout/App/macOS/Flags.xcassets/cg.imageset/cg@3x.png b/Passepartout/App/macOS/Flags.xcassets/cg.imageset/cg@3x.png deleted file mode 100644 index 71e7524d5bc3b0ccf861252f66277b55772cd243..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 360 zcmV-u0hj)XP)1by&(7l(i4JTAiW{@0n#IaZ;)OQtU-E4@CE4|0p?4wcoh)|=3d012<6A} zt$#!iLqvift6JA-Ez{rZ4Ip5Ma69_+vzaTWUhF08G%BAqwP3NN)6|Oq0000|gW!U_%O?XxUVFMY zhIn))?^&h0eO6sVKamjyssvP6n z3nXV7o+$Bm6RZxJb^1f*Z?#Qlq)h^iUkQ{LWXK%y@HwevT`0!9dy`g|gs?e3bN32~ zt{d~Oub22&`?pEy+vj(=ZO4CRvcw$RFl$B3VJ+q32bL{x36KRUV`iT9_}||@o;{+? z>RD{P&K+H|?)%Fb8f@IWxI=KZTb+HSi*0*?cTUZ`ItkU+-RquRoHR8n;fmsmhBs*k zwr^f;AoLOF%9rma*sqT>sJZ7Ir_3QO0dW}vLrSQle(tgTD}df+@O1TaS?83{1OSl| BeeM7N diff --git a/Passepartout/App/macOS/Flags.xcassets/ch.imageset/ch@3x.png b/Passepartout/App/macOS/Flags.xcassets/ch.imageset/ch@3x.png deleted file mode 100644 index 01066f88cc33dd6367414a4ceadea7ba0d71dd94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349 zcmV-j0iyniP)K~z|U?U*qOf<>mTpEA3WY28kM)YfCiWV@6E59n1b1_8<+|b!3K$7 z!&Gso745(Zx4l`Lo}fJ+C$FhQWbU}ISod%BS9McSQlHTx5p0kMHnb6uTEAAbgSq1h zbH;5q4HChI31IZ^$v@)*3^6=r2oInt00000NkvXXu0mjf?oEyL diff --git a/Passepartout/App/macOS/Flags.xcassets/ci.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ci.imageset/Contents.json deleted file mode 100644 index 4ec1ef56..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ci.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ci@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ci@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ci.imageset/ci@2x.png b/Passepartout/App/macOS/Flags.xcassets/ci.imageset/ci@2x.png deleted file mode 100644 index 54025ba93f09fa2f984aea9ca4488d11f7aab50f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?XxqCH(4 zLp(a)o-^caFpyw<7#{LqDd$Q?vnz>>voe~wN;XXY>-+zT!5#Da1(|bpsmONSc)!jx zf8v(CYo{-7iQ>JgD&lvbBl(EoBZYyeur;OXk; Jvd$@?2><|$J@o(p diff --git a/Passepartout/App/macOS/Flags.xcassets/ci.imageset/ci@3x.png b/Passepartout/App/macOS/Flags.xcassets/ci.imageset/ci@3x.png deleted file mode 100644 index e3f1990808684496c48c95a73c9c3f19c53dc007..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv$(}Bb zArY-_&u`>yN?>q#sQy#9klWecKk1>9iC&WXW0OrBidViRIkhOR+xckUKfTK3e~(JL z{+{t-UT@Jw{l7LfZL2>8Hta5P*eUX0SF diff --git a/Passepartout/App/macOS/Flags.xcassets/ck.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ck.imageset/Contents.json deleted file mode 100644 index 4e637127..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ck.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ck@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ck@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ck.imageset/ck@2x.png b/Passepartout/App/macOS/Flags.xcassets/ck.imageset/ck@2x.png deleted file mode 100644 index 1543a11d5d5167d0a9e9ba6d95570d520b9ed92d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1326 zcmV+}1=0G6P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1mj6WK~zYIwU&EKlUEqVe_y}0Kr5DuRxPj~pg16amw|#a;&e;Q2%6xA zmx#zVhI8sL(`}mB>7vFM6T`BMFr&fiM2&7{Qxu)zg-n8?a+8Y!3et*=lvb!M-5(4T zDd;-({CB?dyzl#b@8z5$Tun-S60&68P;^v_+IU=C zqz_c7|TbZp;l=lzBIS+L+|Iy$V*3ke8NyLx*o+4##{z8f*0mNqL7 zKT6^2)JQ5TH=z9$_y^QJ>r^=Hn z#Z}QMdV2Oxv0AYijRwz1cHCi7#Tw@-Tdev*@?Fu}(M`>Iv=jqqjr(+GAQxIS`WGTVNKn)rAs{U$P_z?Y4gS9vkvwY7R;W4H0i#*29FImD=h*?f8bAbHwIMvorW z%dtk|flB2@P>`CPJ3k~QCJ>d%jgXL0j)fdK^67*vsc-TyF@fgWRjhVTpzPnyBa8~(DnYakt$r>mDH#L__eg1mKGBLCQ}E;j$P++ z(S6Jlrt-k1;9PMtiSwgbwro0CSr^I6D?_h0vS-f~8X6u`TG~i)aXoQy!GwltC@Hz$ z9i~jG-F34X6p>KkFazs;xCRUb^bYs)^DD^DujpBuOco3V6OSIXQB(8WrnA#dLxTaG z&d8!glc}p~K@bG&_O8;4F!2KrLL5f<7jSWrvV3_QQmM#}9cM9{Ee?4$TPGJT+@Z1Y z3CYP*sIJyyHe1S^Ka*cVM~4-SMunG`0*htf5pHYiVElMLl9Jx)xhMtrI2VM(!4UJt z8(!#iM%vq-y^`zUp+Kc_qrUzzcDp@y&@vJp?#rc1Ylw{WdoBLhv1$qnzak>S7Z}7I zX>M-CU@%iw)`-DiekETf6RE0d=Je?@fWhk#fb#MtGBRep7B?+z8aHn~=vfb{kRd}{ z@b&e?-`^XVOmxVRN+pDc`w|u9-|JxO`vm(B$j`5!xVWA*Yu@ADy%u)wzS3)g+}vbr z-aMD4rY9sOX7wuJpk@TXU@&8`SP?}DDJd~*+LVA&=|Wc4BI4qLsjbzc(;55p(V%9e z8yhzKhFmTsB_#%lL_ndCBa?|(t(|0Mo_A<7sOQMbkjX@zJ+pNM5(Gq1=(;hf`+vjz4SvPfOWVhVUjP6A07*qoM6N<$f*IC=A^-pY diff --git a/Passepartout/App/macOS/Flags.xcassets/ck.imageset/ck@3x.png b/Passepartout/App/macOS/Flags.xcassets/ck.imageset/ck@3x.png deleted file mode 100644 index 17f9f171b5175cbcd89ea17907fc7ae2ddaf510e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2129 zcmV-X2(I^uP)Hp}1XHg>S|br+6fP6%LUBpKg+&QUmDL4gmtmPgK=y|Gw>NJM64iE8cI#A207twj%$COMjZG-vQg-Cv`m z0K7K0dCzTm!J$KGRr;QnNW_9ah5K1gu1vQSUOXewPJO*OFs|l$aY=QvzUO}lVp+-w znc%`nnO#f_9!n=!%$zyKE;KZr#Ka6ei3u5ME}-!0zW}hF?Jt(e207~!c6A*>Oz0Sl z_e7wrug5Mr8dG0iiQw-WJbT*&fbeL*7Uc65aC^!WiWV+puwg3+3Gb4axD*>3iyjLO z7+}nSJ#+YWQXQg@Ak1dWz$q&W*@THiMWs+$`sg{>0Aa+I@0b`!$q#sM5IU?#`g#V4Sya z=hCIMoI95X62$QVYSC)-%A!<$Kyk6&P3{*V$aQtq zKVm1eRac`ad^}Ag*49WAiXII(zkECycQrkV=?>z?YuBDRxaIzRTJGQP*?=}4&pcjN z2%n2;KOnXU4D3-r^WD2Nq@@91Go;`k4~MU@V`a|y!mGPV%Ie{({$qoS_G&R!>FigK&NN*Rr*B%O}%f##brx} zhL>J?fqnae7(3R6ma;O6)~=;CDTzTFHt~HkFE!Wy=s6 z8`BmRLCD@8NlPo}^`!ODzD6Vx5$o1XB_}7GsZ*S&I&_F!XJ=Y#YAD@x6tBIN98Ju8 zN+2Vn7zc-N_Ut)_%zFY(+1XeG2Ew~<5bySYzs1gF;6URZB_)?jF*TL;+=!3QNV2mx zvt!3Bba}bl8b6-WEn6`9bU&fmDfqp0l#-I_PDN^K8wm}K$K5@WtJx1wtXqduRu-DP zTa1oaz~yiy3m3lp)S2`Id;1sL%ODpQTh^?ZLRi>jl*++~L_&;>4RLg|?AplT!%Ij> z`6Eg@IrrDE=a#1@vRAxGUhpw4OH(;{GN;>bB_pGR@#8;a_3ERvC~UDmb&4S|G1Py! zh0Rrooc?k)CMJeY#rgS-CM9Jx!NFdXm#YX4_98gg3zbU4k|nQ@mbRYh(?@msA^?Oz zL2lF}B#@(2(wLb^Et=-)I0o!B#j+$ap~yvZ8w0ElO^l@t$5*5G61zM6Nt`&3m0y8I>3w> zqw)723xIK9E}LSrDXMPc;K4y5)zu9h;-yjv*4E~jno2P< zGh+UH53XJ<=CjYvcQgXO?@@`;8(3PJ@zqyLaCEd}$BysWz58sZwb>KQ&1J;I%wz7{ zajaRBz$c%a?$9p+*NEqsvtCS1rR?3CLTzm$(b50Zd*1v%YIO_A$u}@El5qTZHY$~d zmX@~m{yzO8a=8?D_hE#EO(rbtYx46eXlv8^d!SRKQVD8x3;Xs35gHm#YwP3qKCQLt z>eNh|HVQvKX95CTes((PLn0Az?AS6wLVSpd3TofByLX4}MRIZ;@XkAN+`L)Z>88>Z zDwUekr*CrWRvGE(g)}!muG{*2vD?`V=<;yw4kD3|wl*yag`BdoI-rlImQX0*z<~wq z-1$4q&1KK56A<9S7hk+ZV4!PzTc1v?#f!agcOQz0i6M_3J$nUgVj^YHB2R2>El5hb zPEk=+-%qWL8~ym^n@k=)tb6M4>n>JS=6HLL;M%nk?%XK{`r(Ec6O)Qgr(@#8kYBK8DW8IZiuU$CH&RvAz=;#f*|zO9EG&AyPJDcn6ct4>d9q`N?Yqy7xVa4_ zJNq7Y?o^;uD!Qy_e~LgLz{bV`u~WkI%`N5g&o83Y>NtBg zjQ#uP0T2@6!|~(G@$eW%US1iAi5aA(-sbYY<)5ovsm#MylyIUcPfz)2kr(gIFB8daWz>s=p00000NkvXX Hu0mjfClL+w diff --git a/Passepartout/App/macOS/Flags.xcassets/cl.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/cl.imageset/Contents.json deleted file mode 100644 index f329c863..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/cl.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "cl@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "cl@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/cl.imageset/cl@2x.png b/Passepartout/App/macOS/Flags.xcassets/cl.imageset/cl@2x.png deleted file mode 100644 index 636adc7d17c8266cfd775bd37c7e3c0806c3367b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O^81Fe-Su zIEHw1CZ}wO+1J(huwH|CVncK9&vW0I6Sa=4QWsj2wrEphO7p?@0@loiRlAtCJ1-NF zHvUkw@zPmEof9&dfqrcn<*aORdSPs_sf=wipDyF+kE=_KsQQ5ID_!B5|+&mv6Cqf<;SXV`Jjwhs$}czpGFA@%z2~;py-9Oa2PG zvtXz41}m44hPe-qudnAhr8rwsEVuqZLcYg*m(xvpAAX;&XWQQNm*@Y#zv(C5+n-iA zaoWs)@8^f7;#~j#K97IlmBGa3+juPKU+rgMxwiN0omUTh@BeuH@~zAMD_?QQ-Ba%g zFMSeb_$OUq*QLdr2{LQH7#v*Td;tY7DCA%eJrroNjGa{*7EU>sJHM#yYs zQBcIBZ^wr74kNZ>?4CS8Kj%a!l0vIoDrCNrVPJIR-7NBE31W$K_F(4oR!gKZIpfN1Ffu002ovPDHLkV1fbb&By=% diff --git a/Passepartout/App/macOS/Flags.xcassets/cm.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/cm.imageset/Contents.json deleted file mode 100644 index 40a18873..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/cm.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "cm@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "cm@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/cm.imageset/cm@2x.png b/Passepartout/App/macOS/Flags.xcassets/cm.imageset/cm@2x.png deleted file mode 100644 index f4e504520cf6d108bbda51baab970629710a8569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 304 zcmV-00nh%4P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0Od(UK~zYI?bNYK!$2H>@h>L1kR>F+MTqzS&O#>(BBh&)lY{Tj)i?1O z>gEfyIyu-$+85~PVglAAT+aP)2zM4G+&A6uzvIUpoX6em9Jr=O_mAtRvu%0aXSSH_ zmibjZ?lTKK9P}LAdk&)KAbJj>Hynr`EBsjfJtUhpq?6Vf5}0mGMlJEFB)e=tdW44^ zpKm_pOK5bS@r_ikA>vpfjygmkiB2Td$_!xzQ7u%@0#$o-UmhO|fwFiZl3zK=IEKaAqEdyN76_b&RPMg(k`1XOfU1KBXiP;=%Pfyri<@FK3^MO?#+xP}*T4KLyvUgWQYbs?;a z?HcNPeUW%8qq&{ht|2NrFsKUc&J;-%MW$%1=YXQUm!c|ystUua8bzu~ z?JZrTNEM@-h^31OVY2`QAS|9t&~E`x=d-0tfTgE3xr=z{#H7#R_gX$sJRHn3zAgRi z01G$rg=nGD)<_PBB5Vm_aa61gd%T&G4+9>LV)}XOBBLL(AX@!eD--`kaSbox8eYUTyohUf5!dh{uJLa~BA{mykcB}4YHE@p e;~)Vs%J={{h-X$3$=Uk=0000bQOY diff --git a/Passepartout/App/macOS/Flags.xcassets/cn.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/cn.imageset/Contents.json deleted file mode 100644 index ca204426..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/cn.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "cn@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "cn@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/cn.imageset/cn@2x.png b/Passepartout/App/macOS/Flags.xcassets/cn.imageset/cn@2x.png deleted file mode 100644 index 420508ba86ee65b7f917ccabb27e8ec2408ba8c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 472 zcmV;}0Vn>6P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0gXvSK~zYI?UX-H+dvq`e|J|0I}U^jMH#zR2q7kfm{>ZoFz^)^I|;pPD8@kw9I*J=?)9DT=l6O2h~v+r z8weJ8SBZ@e1MQdK)Kf4JpG&JKecz_4CbwQ&3GnCIAgprq2O0Ysg6gda0T=5PT014g zTY#vKDgJDXJFa``CV`|W&<%<6DWSF;B8KuiH>kQZBB8nvF+3k3OMrstylp}`QO_%FmUJnF7bfmcYQ6_2H@#v8? z?GuS`4Emz;DE{03?~s+$3PdhQd0KPx$K!NqgvNItZ&K%-!<*97*Fw~&?HT!})~@s4 zhG5SjJjjn4eI>cyE|J{XvF6b&*;(jWEAVGx^ra*^RK#Z`D-iHRj^s&5sC;STPH5|a zHc-Ty7#iPvgloXgXgm)DI-zC)%k5x5YhF_YNLB?%Rs~2_1xQu}NLCNLt#GO>-U?*^ O00001(e+FyMy>Mcl7Xxf}(uW=fcwt_HugDEH<^2f=bKh z+yf6$j0>%9_Ni|60SJExj-Na9KG;JARVe7bFHl|&q8bK}f*-NJwm8(a_N9>eW z)ZMW;e&Hb7Vr*f1S>k_5Z@r8(0w7Gd_^KUCq0V-Jqi0TP;$|VS%gnmOsz)YdFY5i$ zl>o*M#GmhL>BexP5x42127+%d%N*RR^7CyGYiV+N2^$KRLuwB_$~OXpsa^9(6890S zCOLexfPWZy{r;wmw`)_p8qoigtWE{s-1c$q_;|Y(-rmHmN(+f=Br$EKNFnEz4_0K# z`#79z|AOT1N)nrNUqE3cWN={8OG*6SXvs%s-KX)SgVXY-4g=k2F``02aW%wlNUZbo zIW#7(kr^-+6h=v5o>9NgTWO;uQsjw1o(O^?9o0_9oPLA!lN-qzxsj}q8_62Ek*tv$ r$r`zltdSeZ8o80IVFBCo7NLV*g$u4t=xq&v00000NkvXXu0mjfiC{C- diff --git a/Passepartout/App/macOS/Flags.xcassets/co.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/co.imageset/Contents.json deleted file mode 100644 index e6ead9c7..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/co.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "co@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "co@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/co.imageset/co@2x.png b/Passepartout/App/macOS/Flags.xcassets/co.imageset/co@2x.png deleted file mode 100644 index 37e680f8c1892b53d661c2e9c84945948aca6a16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?XxT0LDH zLp(Z@|NNhnXnMH5O`xrjrH5zrOtveG9tl?zU!dcL)K5XuGq=m{V80@Gi0OVx#I<*q zrdw9HRXWH%N~j7iyWnzmh4Y1j3ko^9&n*AG=GlIEo)yX|c6-xPV^o)YKKtOR@VUm4 n6Rd?d-#4pexyd&1fiT0yZ&HqHoUZ5q9mL@2>gTe~DWM4fX6ICC diff --git a/Passepartout/App/macOS/Flags.xcassets/co.imageset/co@3x.png b/Passepartout/App/macOS/Flags.xcassets/co.imageset/co@3x.png deleted file mode 100644 index 068e4ef2bfd0b401d925b044d2741e6a764e2f3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvN>3NZ zkcif|*AH^GI0&#_eCPUM+Z#s%na7`#d}g<~B?ugvapZuA?_Q2N$HN9U-~DiBy!kF; zvHO<~lC}%PoilhA8nzr0kb*I0^*sI1?vcdNt7y`8VG8HT+IwC$9hFDdyLimJYy?rb f?DK9rkbWkKX}p@JkMwQ@I)cH|)z4*}Q$iB}Vp2?& diff --git a/Passepartout/App/macOS/Flags.xcassets/cr.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/cr.imageset/Contents.json deleted file mode 100644 index b86ea704..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/cr.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "cr@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "cr@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/cr.imageset/cr@2x.png b/Passepartout/App/macOS/Flags.xcassets/cr.imageset/cr@2x.png deleted file mode 100644 index ec31ed899f8c7f66f0ba867fcb1d426452b63aa3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?XxHhH=@ zhIn))rzG6iG^?>uu_ZC#VAe9mG6oqQsrC&V92+(n81b!9{`WsUA@Rok|7RcnXXg3# zzW#H@1(&lcoG&mwzWLybzY_ns4}43M?`h4fOG>Q#*zxrE&CSd&`PkC<45v8?n=a;a zWC!8qV~Rb;t;6Mg&;P@B2&lYmKKs3Y5+;Gh88U~8{{Fh& xU;ppVbV(2QJNy2=ReqfDSdzELiba-zq5bpvQ!Q&mE(2Z9;OXk;vd$@?2>>YcYKs5> diff --git a/Passepartout/App/macOS/Flags.xcassets/cr.imageset/cr@3x.png b/Passepartout/App/macOS/Flags.xcassets/cr.imageset/cr@3x.png deleted file mode 100644 index f2fefdbd80551156ef1ce6b0a2d706ad329ee71b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDveoq(2 zkcif|*AH?Y4&Z4{w5;gnxh!iGm6rMb?OtvcBNshOYi|jimVKOGnkzpr{isn?Xsq(> zf3d-b|HUH#Gb6>qdF4Q^K&YyqQgIYtqO$O$>-+rkGX&Wmgm-s3->_)u1rt6V&vyCc zXFQZwl5XEn$T9oIw*5y|iynwpa(T8z=8&73O5+cpF&`NC+r-!R>s@OCI*q~8)z4*} HQ$iB}^e#~2 diff --git a/Passepartout/App/macOS/Flags.xcassets/cu.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/cu.imageset/Contents.json deleted file mode 100644 index cd022b9a..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/cu.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "cu@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "cu@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/cu.imageset/cu@2x.png b/Passepartout/App/macOS/Flags.xcassets/cu.imageset/cu@2x.png deleted file mode 100644 index f259ed3fab204a9fda6dda9caa977d907baf1d31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 766 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/cw.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/cw.imageset/Contents.json deleted file mode 100644 index 4f8897e9..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/cw.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "cw@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "cw@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/cw.imageset/cw@2x.png b/Passepartout/App/macOS/Flags.xcassets/cw.imageset/cw@2x.png deleted file mode 100644 index 975bd666b3a8ff43d9b304373331fa5bcf70c164..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410 zcmV;L0cHM)P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0Z&OpK~zYI?bbU>!%!TC@i#Y9Y7>eT3sUXWEm~0&EET^%5gc3`#KF8kmPVb3CA}iDEP26pTNdS3X8L22!{e>ATMtp=-?Pgu^!|KWjvNuG7tbL zC>0D$ucK1cnlqu3|9{B#febnSgtN<1bGE#Y1jjOtL)cae6C+V1hWao!6^Gx)A)9-^ zUiu2}TFnti%j=97Gl&JFii_PhcW~|54~1*3<0#+8UR?^ zJV8b-LJaU=Xc`UEBm>FFMF2om)g1$T<=!WrRIY)rsqvc-&pCGp|B`OdLFyeXN#g>Lod5s;07*qoM6N<$ Ef*S#=Bme*a diff --git a/Passepartout/App/macOS/Flags.xcassets/cw.imageset/cw@3x.png b/Passepartout/App/macOS/Flags.xcassets/cw.imageset/cw@3x.png deleted file mode 100644 index ec84960f396d8a86675753e7f15d0b8499983fff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 567 zcmV-70?7S|P))=fgJamYH zAVE4v;6YIE=0y~|=%GVVCm|5K6jIT_Lqw3VL}EXXpu0vQl+Ny~(2Y9o3^^7Kaqs5EY`Yu??f;9i z(lBk!l~kXJ(0=7C(S?q0tDz^N03Je+zjeFSU^ z0eJoP8tyZAWSwL6){m1ss}8Q1B2a{ z=^uOHmQThAy+=a%L3(a95-HN%^2wOA9obG$3vj!mjsiWO@{@(=p*)6quJZBA3LA#W zA{!d;*=q?W_ydKhb`jF(z0v>x002ovPDHLk FV1mR`2^|0c diff --git a/Passepartout/App/macOS/Flags.xcassets/cx.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/cx.imageset/Contents.json deleted file mode 100644 index 6074dd4c..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/cx.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "cx@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "cx@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/cx.imageset/cx@2x.png b/Passepartout/App/macOS/Flags.xcassets/cx.imageset/cx@2x.png deleted file mode 100644 index cd59e2b74b60c20e171e63c779486427a91787a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 882 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 P00000NkvXXu0mjfr~K|; diff --git a/Passepartout/App/macOS/Flags.xcassets/de.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/de.imageset/Contents.json deleted file mode 100644 index a6648ba1..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/de.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "de@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "de@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/de.imageset/de@2x.png b/Passepartout/App/macOS/Flags.xcassets/de.imageset/de@2x.png deleted file mode 100644 index 8a52fd2ae061ebdccfe6d3c7f332295153a3a285..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx@;zM~ zLp(a)UfIam;K1YduzlHvOSfh_ZYYcOX1!h@@O0g^D2v}Z6GSQ}{A-qH+Q5AE$2K-~ zRu^%BScj4#2e}7ZTx=$!wh2b)D2nQtUuFSH%$4pa5q?^o``_bsu*zDMp8p&LyJxB? g>}q(ywCFdZLzCXwm;X*@0Ig>5boFyt=akR{0H_2+ng9R* diff --git a/Passepartout/App/macOS/Flags.xcassets/de.imageset/de@3x.png b/Passepartout/App/macOS/Flags.xcassets/de.imageset/de@3x.png deleted file mode 100644 index f8cfea2766ffd97a01c7630310052cf6fa135969..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvVow*x zkcif|*Bp5d2M90+=8L2~4fo+}T*WFNwsW~k*sY7#ZCK{b);>9rHDpqHl}meWm4mo* z2G2snmSY05Qk}kqf0`|KaiQLRv2I0^whIzY$sE0kCZ+p7&jpIk>t8N-R6q)%JZX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0k26!K~zYI#gx4-17RG1pF5{b#Wf+m8XYVm62W4S5Yokf0fVK3gu$%B zAmyLv&}jS(Vii%W603y4SEC}8&})fG`x>Nrt?Q*q@1E`6U*5}ePcA=c@BA)#dFfRz z;@C!_;p|y%Z*(dvQv&~pz^T|LBLhglXfFik1l=ARMe?+|AuwrA9l7*ClI*GTLnM#B zj>CdFWksM+XVZc@rXqQ)s8f*$l>9p#R*Iz4hR1Dg2uum)hXwU@R)_?Hkj)y7Bnfm)*f2^Zq9Zzr2;`%=E`QYFHG!0@g zSXlT2|C#|3MryBI5*lTltCl@Hg$EA%uMO>#L1g)GKjynpq6#p3M}Q7u}XKtbf#cq^!?sla!I z*ea=9l@&_(^?LfZ3MEv$&;)|*#5Vos1OF}o6&9&g1wE!kYF$B3*&<{907*qoM6N<$f%i45IgWME|^h_n0!3^G{4Lc-pTg*YII z3@!{VV%T&c;@|)=*oNp}9H40_A=c(N8>3OqT-!8v4)pLfmizH?y&s?P?#=T&-+bOT z@164W!)N_1V(Fem=CQdJ5y?aCQcnjIZKG2VRC`r+<)c7uMI|G zM=!qR(`jfnVR%@okzuxYs6OJc`UDeGcHS#VIlkTAhRI1N6d;#_ogJ8+_1&bX7s=M= zskW+a9i?$DJ2nP;dyq;&u?PnTzKR>BK|xz)Omyx(x5b|gW!U_%O?XxPI|gH zhIn))+bAyBKC7`&v89nC;nx=yW?^x4W*|8HI#pWoUFvng#k~?6rgcWC1#-E`Ht`|j zh>Ep6KWE;E=nOh@qNl)oc8dn{{$|!tnUDX!=hwCQxBJ`vvp?|Q@Wfz$Hs32eQtbwD zU%l=eJ#y@oS5MMc(a91LH*ZQx0D;{*>ti2kJ~^$jc=Z4M{ptuidF$U3GkEL&8yYy> z+#k0m?MKA}mQa~P2M&BFyx?+nh4Y1jNSKjfi_e-&LGxP;KupUbjUp^}r`b=Qv9FSLJh*)=KnJ$mq9QD4`iMf?kz`CK<78!-DQwNA^ZKmHul?7HHh(v<|ktPLEd5iL?WPQ2MJM{!N$SQwuNp=yJ@zGh= zl8{UjTgcWOa9B!ElOb#d%lf~D8+rcP-CM|TqD1l+^OY)Nxg`#7g@K-D@O1TaS?83{ F1OSS9f;s>I diff --git a/Passepartout/App/macOS/Flags.xcassets/dm.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/dm.imageset/Contents.json deleted file mode 100644 index c96c4669..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/dm.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "dm@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "dm@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/dm.imageset/dm@2x.png b/Passepartout/App/macOS/Flags.xcassets/dm.imageset/dm@2x.png deleted file mode 100644 index 159144d1d2d0544650199c0f71248f2c158b1c08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 881 zcmV-%1CIQOP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x113pCK~zYI?UqewR96^=pL6Hj$;@OXlW4JV#@1vEw$gY_p@@wsuB1w7 zsRpUkpM^meMXQL5N*CQI7_o&0X{9Xzp)GWw7VM&Gpiv^BF>PiM6*2yxktSvmCi8P= zGBbCMiv&xv@ZPIbvhZD=^PZPy@qOR}213ho&j)1fT^+h4e@=l>6y&^$l2j-&OHW@X#&yN&#gfa>k~@&q^9k$1nYm$D z^64LyoN7(u8`dyJyu8~VK+W8bR+kum&Eoi(5FK06+=*C@a!X}G;&Kne&GU%bx-A&p zA0jaZfucr5S)c(!b?bRJVX!jYfmAuBgrY(T%!L-QuhZbM2Z<+1I_{dnHOZO!9A`IY z&@J(}-G-xSt}O_1e!=$uR474zL!O`C&oI&Lym-Ytg9oW0B6h}oVIP}bkrV|iN&Q<| z=U?vRWaAfnb4d~|-T`2!xW^+`!ADfwF~;Uqg_f@u3{+s)cwq`}VoR^I@rY&VHIi;sT|JwCr1Y+=I( z{S@?3uAU47aPjp#_l*MYTxsO&hwE)Y#?8b(a>u4NfAgX>w&r4UoZj6nlm!$O=mLr! z#bnI|%ql^_0wq8wSUm0_cU!V#-DVE$JZPI8AN|Ykc#i0AG|?Da%hKL%M*J4Fc@MfJ zSaILaM|y@>pAUc$pGDo6PW@z%nj>4C#v|P9Bd?}4j$ZH+-<##VBOzYO|F<_YLh;Li z6vk;Ey_gF(cO_ zejs9zX~`4rg7hVwsS1VAr|1wVjDi)SOw2UVzNjoB??Rw-XbHvI(Ne00000NkvXX Hu0mjfD&?ab diff --git a/Passepartout/App/macOS/Flags.xcassets/dm.imageset/dm@3x.png b/Passepartout/App/macOS/Flags.xcassets/dm.imageset/dm@3x.png deleted file mode 100644 index 3fc36d7d676834e1d73c5d8dd02e1aa65ba265d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1384 zcmV-u1(*7XP)HpK*Ip>}8|L2{1?mbTgD%g!pSW-mqxdj9PVll*o7eKdA083Xci7YH% zF(7@1rP!IO671<>pa_J5*V zNLNF*kgkSqAzcm9_J|h5jys7Sa#B?+k(m~T6y$vTC5@*p;h8xP_t^2ar?pi`@T{a^ ziwEO^0{~a1$-X6F3@?m(Pmxu*m1yNwoI@X@q*QU(Z(nZPguHmtO>RizsAZDrSClkM z`sxn6S2S=b2F4PpYP|-RWEIc-u94f%#ZQCcw&b^Ek1ROt=Jlf<8cGblUfRm795ZQ~ zyw%6#Z~Pc4vA$VguyaKmr96VGIS;NhqZl9@@`59DwJE{6dmmj1#8b* zG>&|UWk)wqjT2uh6$x|I@_wA*ikricUAw9W*aRuf+H8eToX zZLeZiL9BzlNhn%{NM5h=J3i;~Bms5UN{JaNw`4Zb!H4!);J;pHnd^#?xduxoAMeu( z5+Z;)SI5&iyIE3r!^J|JOwACiIPS)3%XqQPcTkH&I2?HAElh_BV&-?_(>mFEgmqSb zR84gt%&9q1g8L;Ck7eSz=k4fPqNE%O4i1b`JuAk5-7dzT@Nj5EE?>>)pAHS={<^(s z1pufYCwco956&Eg*CCQ36p8DI0325P>(9BRAp#1&4z;-^uMGW%Xx-mwaS(xu^zK@B zF#4QiQVz6@bDBP^A; q0)^u(@lbPJa%xga)dTh%>%hM)(#b`4eL`^n0000X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0c}Y{K~zYI?UlVt!$25^e@WA%X^S;htAha@6bgQ{6}mVGPHuvWnjcX1l|oL!R;AeR$xG_XNaK zhX5wQ$m;Ci)z)${`ui+@kUGn4Zp2w#fN&zQ4}IbFh#~<0A00#=0FB*7gCcLpf8&NY zaYLNAAx@emM4F%Ke^(Xhlyz`oR0jZLvL ioVX!Q+z=;C6Y>cF)o5!@c}_b30000+XeMFn)(IkzTJZG+7Aj>;=4fkyS@H?Evoe^a2 zK1DJ2Zk;b(Up*Qgck8~(mp89o3aWNmAXZe59!S^);MBOA2uJfmiqoo{Q>q956(&SC zOo(on5Zy2#x?w_e!-VL@#Ds`;XHZlCYS48%l7-TE^ae z$@HksjleeTW{0WFMQkhNhZ51ruf0}1YA+s7j(+|)5;&Hk-`&MS6511`_r8EkXSdcq zRJK(+MR93P`RDiG{fDn2*>l-(xAl*;PiL<^T2k$tm_NG-(G3%#8zw|IOo(on5Z#!V r5DO?MUmdy?U}M~=KNwX3N44@VupXPvHxv~600000NkvXXu0mjfrKS52 diff --git a/Passepartout/App/macOS/Flags.xcassets/dz.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/dz.imageset/Contents.json deleted file mode 100644 index 431e938c..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/dz.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "dz@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "dz@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/dz.imageset/dz@2x.png b/Passepartout/App/macOS/Flags.xcassets/dz.imageset/dz@2x.png deleted file mode 100644 index 7700ce96530fc68e9b1868c0c5da0d6984397a44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 627 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 P00000NkvXXu0mjftLIEv diff --git a/Passepartout/App/macOS/Flags.xcassets/ec.imageset/ec@3x.png b/Passepartout/App/macOS/Flags.xcassets/ec.imageset/ec@3x.png deleted file mode 100644 index 7bfe0528d43e50aecca8824187a5528f1cdb1b23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2074 zcmV+#2<7*QP)1ksbMn<1@ZCUa!60 z`<|WQkF`u&RH2Mrw?h5?`QH1z`F-B|{oefMji7nC#Q%W=&5QOyJFy%RKWGP*OCN!} zm*%ISw-u%b{<}}j!t`}WCzkuEU|5bxrn;g35_ozb9$Hu(1MK=1sH)7|d_GP0&mtM& zm1Cf(kY5HBjnJ?Y%GZOi#3j|8?5YCY7oe{d!d?)CclIYzAOIO_A*r=3l#ZUlWYvhx z=h^`@Kvlt#4|Pp&XA8J0R~Tfn8(M!2sW507NCG62IUq>_Nd!Rv6^X2D$CwTQkXCX# zl2)Ls79=y6EFjwZ2eI z9a%XLlX05okKc>eXD6AF7)>Ua_shkGFxUIzNTni;vYCV`aev<}l-43} ze2yOF5Oe<5sf+HRF0zZ6REjqLsGAHBwThIlei!8yKY8LH=Vy=d#kIE( zBn?$i=})Ej=WvE{mx(+}26Wc2`roGtq*FHj_@^g%>#~e#W)lbU!5cV0b>SggmVHzf z?!$f8%a}L5M1X@t#qZ*F9mcVWoQ|aPw@1x1_0`(Iml-=A$Teu#7FrTcd ze_eHhC`cp{IrECJ#-|MJgc=Mu3yW}AAge)I1!umMmJJyud;v5K43a>o^s`K?`!kQf z>ZdJ}d+8)nGTF=ugA9#2sdh(*h6)xY$+AQ760^woqLak6X+p_3h1NV01TYJEm@N|V zY#gKDB4h>0CbKg$!^MSoiiJd~OQH)W+pXk6)758ehBjIC%nGUl2y#B z3`k}(0P4b-q0RtN6Z2t#)WmLFj`gI2Rd~nSDB0YHtvL4~Vh@E-JRkwuLM<#-nXnWa zKvwxref)ldAP@*!DR$*5+5q_gq^^N+JV~bP4y@+-+|YCqS-XpP?+&8IDNMx!tPVei z)|>10lMBzXk|5rYRTl&-;rN{m*r}Chs9m@BIp+HET&cqEI(~kj=?vib79P9{E07qV?D= zoWk%z&c2n+{DLExN1XWI|$UB!SP^fbi)8UOT^scJ`nOB2Pc|GwN&nbPqZi_m=7gk+8Nr zq0dx6Q4~J&i6NS|4kB8NxQflV>Zh@5evI)r1L-Oxzmasn%%tijFA+j4&Zn@&$lZ7M zbNEYV`SyV}0Df>{4_yQLcSLyFbdRobQv(8SiQQlB=gtS_NW_hFcUIARE=|KW0dGvk z=1(%AmZFLxHB6Imi4@u-#;+>u{b~^R`Y57Nqx*aj?Wfn_@~S!yUl0Z<&;=4Ic3}%g zF{ILqZ5=3Vu}IWkh@h2-lr{a8p9dcW;CltnF#WIn2tJjOy?OK|RW>0l+A1cQn8Qx~ z?CqB)O9FYfddadH5eouhfyRTjr|9%-ppAWKz+PTsYef$)#vUgd6iA{mh|0#d!7N2f zzB1K-p<*X>3lIP|f@O4iHew`3DRJDyF%sjdXA=X#Ttb@>s7GX|*tsmf1D1Sc8YnBF zc5WWS+oC&*#5XvBhoZ%{-Mr2!d}J1{8o-J`Z7@QhtOVDzZ%KYETWGyoS1F;I8BFAI zD2>b*rEFzs4!@V)H;|RqNB>7YY%&ZuN|paT?8C2r1Lj~9)mpx79{>OV07*qoM6N<$ Ef-h*wEC2ui diff --git a/Passepartout/App/macOS/Flags.xcassets/ee.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ee.imageset/Contents.json deleted file mode 100644 index e96a6b27..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ee.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ee@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ee@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ee.imageset/ee@2x.png b/Passepartout/App/macOS/Flags.xcassets/ee.imageset/ee@2x.png deleted file mode 100644 index d5c8765b4c4e43e5ff408755b79591505c55b7c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx8a!Pb zLp(a)UR=oAU?AXd(P_uBHWr&n8`z#*OLX$6Di(A?ed;_?Wu@sjqJ@efgJvddvX*+mCVjr2DG(JEexKnelF{r5}E*bTS`#? diff --git a/Passepartout/App/macOS/Flags.xcassets/ee.imageset/ee@3x.png b/Passepartout/App/macOS/Flags.xcassets/ee.imageset/ee@3x.png deleted file mode 100644 index 5aff2cb2369e1edc1b18bb2cb9e5ebdb047ec47e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv22U5q zkcif|*EaGtByh9@22{l!P@Qx3WC5S`X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0Y*tgK~zYI?Ub=j0zni-?+nAbfP!Jg2!(};jiJ;pQ42pnyT;N6+~18aIxwzEi^>JLg2L^WD-`UxV&Rf zWj{@?`Gml0V?Or~>LCKJjc)UitApYW4nLnU9eqGb1w$c-2zuN>>Ewpx5%qfg-f^7Ml?Tl%xseRAMt`SZ2*S$G^dd7NX@{%2 v27^HZ0BQhe&4>RV`ETNoC~-)XI3&s+VAGL;xyoX100000NkvXXu0mjf>*KCw diff --git a/Passepartout/App/macOS/Flags.xcassets/eg.imageset/eg@3x.png b/Passepartout/App/macOS/Flags.xcassets/eg.imageset/eg@3x.png deleted file mode 100644 index 084665d1aa43ee54f1b13bafefcdeba48b429402..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 570 zcmV-A0>%A_P)bFfJMEKoH{k=}oO5{G>O?)_}|`SWeFnQXyr8kRkKQXSVkqB81;)?}vSvd|`BS z$o0iL%;EB-jjrV}; z&oh4UK9NkP1hPhE7S#Vb7jb)^%q|c?h%^6#h)D@ljMWK^?tQ9Jk1+1j*u95cCCEyQ z)&52Nua-%XD#q#=D((V~Kvhsd7poJbs$LatOPh{2- z)^3nxD;y9=L3QgETb%=hs`-rdKM|p7sHjVAd(J8yqfLScBnS{eol4a6E&(8h!{G<- zRo8$3a=mN)D>mkTq!cL{rAW~zMT$l#QZzP3L;X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0y;@VK~zYI#g<)2Q*ju^fBR4~XCKzcuticgrNfz4;;?yD2r?mVSh@XO|QR1VU>A0m*EZd_E}_OYitJAeqXf?Wd*b zXwsXSC=f`h>g%P!!GAmMYDzxq{JiX4zbwV$xj}w5xM%VLNsVgauN4%m7N(ndSF&eK|ibT?v_M%rZ*`%$F()cfFay;qh*e89XfzaAM4>lXLw}amghuJ^P=mi;Hosh9OaVn5h3T31=E-AVY zO_)A77^@#)cedcOqj0Mk=B%qh&P(sIwvLoQ+gW&7TQGn>$_Ub11=l<aSw flVwyH$N<1^yhw9Ly!da^00000NkvXXu0mjfWyL6e diff --git a/Passepartout/App/macOS/Flags.xcassets/eh.imageset/eh@3x.png b/Passepartout/App/macOS/Flags.xcassets/eh.imageset/eh@3x.png deleted file mode 100644 index 16c52ae6a5cfde9a6cfdabc39a6d9b92d8a3dfe8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 790 zcmV+x1L^#UP)Bq2>O!mveVKSZQaNTdl-fhkcz zwUUAasi<&ckwLp~8EvA12&+Yup=2~c5Spb$WM!6FOp0ZV`nDKH@t2u9cRbwxHxjdb{c&3* z@2K9px2A!Jzp}HXwzlynt~xy2S6M&uUi)V$D`gsqfRvCR-Mcq-;=GK(9;fuB&v)(Z zYiWPJl#?MXMk?4O9X}{}eg-TO5w_#d@2q>T)=B9@^V{pa1s4nTrq; z7x1|7h$JUNT^*E`j=i^H?ckyxu00*T9vctde~`Ziy1QV0Y;ZGe-%`7GOCHa}d*FGq zFH8qV{nt*tu~~HUkS%@;ZLM#9P4rH+l8vrOk;!|XLTCyO+(m`|AKG8 U&QFK2djJ3c07*qoM6N<$f?FMIqyPW_ diff --git a/Passepartout/App/macOS/Flags.xcassets/er.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/er.imageset/Contents.json deleted file mode 100644 index 555b3cbd..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/er.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "er@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "er@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/er.imageset/er@2x.png b/Passepartout/App/macOS/Flags.xcassets/er.imageset/er@2x.png deleted file mode 100644 index 7297a1c50227ca2255e212175d1b6f1ea34f683e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1176 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/es-ct.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/es-ct.imageset/Contents.json deleted file mode 100644 index 8db5b0ae..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/es-ct.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "es-ct@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "es-ct@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/es-ct.imageset/es-ct@2x.png b/Passepartout/App/macOS/Flags.xcassets/es-ct.imageset/es-ct@2x.png deleted file mode 100644 index 09abf9843976948de9830c04daf56a2e1de5cac3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 330 zcmV-Q0k!^#P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0RKruK~zYI?UpfW0znjp|Jix7>_P%Y*T9B=-GUZ2V&MUD1AA|vckl)t zA+-ldwP{2JA%!RkqCrqH;yODvV&Mhe6!}l^@jm{aCoVtX5I`He8G6hB_t0q~pM?>u z5T3%5SbUThI#utSAVDKrx}zp+nYEwftQV1yT~VQ<=Kz|VAOPm>sM!v}#H@LFRGcT2 zO=VH1iWMYiWF>dhl>7P{_K&qSfJlj6&6yVh0C1+uM*6UpnM3&h6aN@eRrBi=EbB#7 zobM>==^21JCkTLT%JWSX<*Mj3}s?11H2S cM`zSr0gekyr{_*nVE_OC07*qoM6N<$f-CikyZ`_I diff --git a/Passepartout/App/macOS/Flags.xcassets/es-ct.imageset/es-ct@3x.png b/Passepartout/App/macOS/Flags.xcassets/es-ct.imageset/es-ct@3x.png deleted file mode 100644 index e18fffbe3e1ac005f4b92db86d9f926c9980ee6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvrJgR1 zArY-_XBcudC-Aua_psVwAtFAvYU#X%?VqK}zH%(uy;+vab=7nZ6`N0fA8dadVxO42 z@;H-2-&VW5&Jh8X>>HSrm2R(g4e@?f-7j!3gj;#h+0W1C9&EWYF(bj{i}l?-#xo{Z z3-R`Jgz+wl+^PJ~V`9dMPcH<_7yXX~N;Rw$b91p{P8KW{SllRhl0DYM;Oe1{C(U|N lGqMt0w#<3@R`yU6x71@TfzK>|b%73M@O1TaS?83{1OPxJU8n#6 diff --git a/Passepartout/App/macOS/Flags.xcassets/es.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/es.imageset/Contents.json deleted file mode 100644 index b70dcd3f..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/es.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "es@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "es@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/es.imageset/es@2x.png b/Passepartout/App/macOS/Flags.xcassets/es.imageset/es@2x.png deleted file mode 100644 index 2fca4f3576045adcba9a95182442e7e20e82a6b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 579 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 VMQ2$iXaxWO002ovPDHLkV1g(%^Vt9Z diff --git a/Passepartout/App/macOS/Flags.xcassets/eu.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/eu.imageset/Contents.json deleted file mode 100644 index 70ec8ab9..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/eu.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "eu@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "eu@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/eu.imageset/eu@2x.png b/Passepartout/App/macOS/Flags.xcassets/eu.imageset/eu@2x.png deleted file mode 100644 index e747620e9f43a48db46b43bdeaad18437ad00a02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 661 zcmV;G0&4wX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0!m3lK~zYI?UqkS6j2<JM_GeAWLAY0|zO|TFyciP|Jx;oFM+mUkTb$O0#4B8A_ z9Vc9~fs{J;E=_X(@++j&3D;~8yEaZ^(Dn@C`9z|}mx*;vF!K5$02dMuD{GFUGtfCf za=C#8JLoB|rrN%TbcY|VkE4eR6pI2`(kV*ui^ne{LUZx^CAoZ{5~Cos_H7BLn2it7 z9i1bW4^VLYzd-;LrJ&&W$>jrdN9UN05781%scfP4Myzx_K3%e&uU1M5u%55Rr%SAK zy^>$~YW7%aR}%>@?|28**lj^tLn}-vZA3&5+9n z_>?wDrW(k8SAQb_ng;K`p5*Q4Q^?XkYo6b>w>G7Z7w^t{0w~K`HL+GrC9m?Awe#!; z&mdl6lt7xz3}b!6^ma@sC+_W-VythNW;3JYS887&T$&7c20d*vSc7qz>g)=n5H0{sbvD*uoSwEB7PAIc##Elj zo~Q59n!$W3#DZOm5H2t8KIQrCCkWxPVAnFA3bC|icunhl`c8g4&6P(F0FY8c*CcwC vR;j$aw&x&Qb>ngWZc_mN(XZkG$X5LXH6YL{K^@LT00000NkvXXu0mjfyJ9S( diff --git a/Passepartout/App/macOS/Flags.xcassets/eu.imageset/eu@3x.png b/Passepartout/App/macOS/Flags.xcassets/eu.imageset/eu@3x.png deleted file mode 100644 index 1e13b0dd0660cd7ad329c3c218443c75118f5c2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 956 zcmV;t14I0YP))C1cOFj5P!w^ zW@HSQ;=&J27EE}re*lmGGrDCiP8n)uA}@U52>8f~}scK5cv zfZKG0ExqR+-9q|#bMH-_=leY0oO624sf$P3`nV5Jaq-k*Hz5P}VmBa*l98oG$w;w; zS48WWO?pX3jm6e#WdXneKUOV=6mc2$MAbH z9O`al)>It#AjQb6sc>Ov11p#P!{yN$;we7>rs?L=Xw971c*;+IEKEYz_$sg4tk`04r1JI)U*_MfhKgzIUa^H*A_z^bfTnZy^$uR#{0lYJlN|VH zFS;o|r4j;9!kZ_|ha&8WmcaL|PLnp4YmgVQGpWyhf~oPE8bQh?)RFBU@LGqq?)g zMdw3xXW6=XTqsxJftTK~lhE+kkDPA!lxH_xW$dpievb!*3g68WHKVld{)7WN`k6|W z^Xs3hY~|Y?k#kqJ5UQGF+uAV(Z>+PMJ}-ke*75k-F>0$OId^4??R?umH(Hs&^Q|pZ zcvIwZF53m?BbRgW#@PlEx(}_)AT{f;l`m~)Uu;SjD}d%FFUsn}v6hgLfTq)Pu#NUT zXXtF|!mEg1lDvvRXHysLd(O~vu#JGG3+V_SQ7(^!Naz~pqY=kPlnYlklGMEn42S;z zBg&fPQ;grLWKHE1<5QKu?dxkMTb!xFSWCmw6hU~6SOqN;04H|!6rEofYiWp@07*AQ zzb8X?a|bU!@-tO|X_`7;%}=-GfoTA4CCh=^Yocraht${o#HwJN#*SuYGWPb2dm+Wh zOvb~&a4m+R@XgTryov3hVOpN)Bb$TPZ=dFeky><9|gW!U_%O?Xx_ItWG zhIn))|M~y_e-;~Cn*f{5f}&S(ZGElo8&2iE?N9o!DeUO-xe^;@bp{EqV0Dvi;)7wf zUS|p0&(rVO7yjJHWaoOn{$JvckFWW|Eam*n*uun4{YhS_{=4Dn@BaHb@9qC9+@7F+ zzdrB@2OH3qeET|M{#h#|OF670O!yKXK1ukieByLt_{Hz@_e<9O`PCiTC$+P!yZNiW xvf=+3XF&h} diff --git a/Passepartout/App/macOS/Flags.xcassets/fi.imageset/fi@3x.png b/Passepartout/App/macOS/Flags.xcassets/fi.imageset/fi@3x.png deleted file mode 100644 index ca9ab2a0405cd6314df90c7d95439ea52513db4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 318 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 TzXOia00000NkvXXu0mjff;P&h diff --git a/Passepartout/App/macOS/Flags.xcassets/fm.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/fm.imageset/Contents.json deleted file mode 100644 index 4f6defce..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/fm.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "fm@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "fm@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/fm.imageset/fm@2x.png b/Passepartout/App/macOS/Flags.xcassets/fm.imageset/fm@2x.png deleted file mode 100644 index 2a4a7aad89506339ad3c1ef5ad9fc9a7f2e4bd36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 556 zcmV+{0@MA8P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0pUqRK~zYI&6Z0`8&MdBpP9_W;3Q2GFDOP0r7cPu6c;KY6a>MgmyOVM z3w7VW(iXZcU6mFWVxgPrq8P!2ctPEGs|MpG29q>n#)*p(LBULNPHTKuXL05|&wS^5 zKaTSH{mK^wWsJ(|r&?ivD@lXsE(MQU{(qz$p0#-C4Ev-(G|>4w;LT8opieI! zB$>AO_+yXF_#dJhNg}OYG*{`-P*sJlM-TY5cFO*-$%pU1i_RIxtC?9z5Zy=uP)a*Sv(NmXR$Fw4YH zGjE5(9GsrZSc!uK42_pB8p-BtOv`4xzk%8!bHYi&SCdFvyqn&o`-vZqt}?rt5QdNf uDQI!)484p&_uoLwoQ)~|FuT&diSH#sf{#jb<@f0T0000fKr6E^$wyO$m<TT}PRBXW5MX?E zL4>3jBUYhEPhWy31GA;y`roDKdOU_<*&-yx_Q>8kKhNH!Fw6pnnu5r(gl!jvXbCeC z398(CHp%m$6aX(jWT*{mjLwKDEn!Bc(-zZdt8_guk`W~ZYz)M|kN?+5B&dprV+CQq zf=8BI-Q(Iw$LToAG7(K#ds09EP589M47az$c=0~Xn@<_$jny?A)kRf6C06C7_Ul3LoA_LIjj32h z+9;4SmjbQ{d#SDR5(zl#Nk>M;W)`^LKS}3<5ddBdXP8JXaH(|*lDks=DeWyWlJoh} zNucY|D0ll3e4280{CCwOnj-UdG|TM=pRw&C0o{XFl`&k@^3R^z{d9sHYp15Cxq?png8qYB_zO+sB>mGXg5+bBZ2;cqo52f&XJ)&jA8yT50g!RR8{>VTq oidinM;Gdc73KZY3cM#>^3vKo8Jpu1DSpWb407*qoM6N<$f`tN?%m4rY diff --git a/Passepartout/App/macOS/Flags.xcassets/fo.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/fo.imageset/Contents.json deleted file mode 100644 index fd4e7136..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/fo.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "fo@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "fo@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/fo.imageset/fo@2x.png b/Passepartout/App/macOS/Flags.xcassets/fo.imageset/fo@2x.png deleted file mode 100644 index bd58b6ba9530ea198cc57c84e741044561e76b21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O^81FnW8s zIEHw1Cja^W|GyR+TU!7dlY?S`X7l^zyF54T{frENK;(p-yIj+LEaf8@f8B zmIW~PSh2{$FppIGhB>>wb$!2lJ)TAM-;b~C&ToF+&gZH2vtM{aFh}RiAD!CM`daJn z6i?S-{x-Mr)Q0zGg1clNC8#VtYH{2Cd+(pO!H14yJox{=x$*sfe>Na!yWVq$$6g{* zql|@_nfb6Y=T?0knf?2XPXGJuu_^81|9DA>f5As(dEVF6CnxcSg}Ky&2T leH+bQ36vPXaE1&ML+Ohc^}lAPbb$fQ;OXk;vd$@?2>=kfsuutN diff --git a/Passepartout/App/macOS/Flags.xcassets/fo.imageset/fo@3x.png b/Passepartout/App/macOS/Flags.xcassets/fo.imageset/fo@3x.png deleted file mode 100644 index 28b382af9f4ef6d1fc92af72944e487110d48bd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2rPgj9s2C zjv*1PZ)fiJb_$eeyI=f%r{r~k!V3%Kr|4b_()H`j6J%j^b*^`Cu6N0f*4=s~tk}iz zMHsKA^JK^7wr}1O6?Sr&@BMC~YN2t*e{%DjbnEAjtIvPud4BJqfuf5Li{ATB2k$;^ zf3f82y8JycGwXkc=5;mSSe9CFcCT$z<&u=(2Mj_}+;+cHn5)Uy>whL<=d(J`&&qQ( zTie=80yb-A{IzGP%Wi$XQ&w8|q%@yhKd`z0$I=g9>|C|l{^_zPIp@b4Hvofz^}_b!7vhVfvL3GNo*)K{Ck9VfKbLh*2~7ZI Cbkv{# diff --git a/Passepartout/App/macOS/Flags.xcassets/fr.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/fr.imageset/Contents.json deleted file mode 100644 index e8a2d126..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/fr.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "fr@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "fr@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/fr.imageset/fr@2x.png b/Passepartout/App/macOS/Flags.xcassets/fr.imageset/fr@2x.png deleted file mode 100644 index 62d74b4234bbd0e63fcc2d2c282acfe986a5e93d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx5M eG`?WL!Z1mKXa7E*nWjL?7(8A5T-G@yGywpH!bC~{ diff --git a/Passepartout/App/macOS/Flags.xcassets/fr.imageset/fr@3x.png b/Passepartout/App/macOS/Flags.xcassets/fr.imageset/fr@3x.png deleted file mode 100644 index 5b94a2263d0ae4c51f921f00255def0ba318d445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv6`n4R zArY-_Z|ZU}C5p5@v_9-|n8Rp+K~I9v#-6LZ60!a34y2WMvk9^pM)N5B2yM3Sws^|> z{N?upv-7X5<6keJBVKwdKg>M!@!q?p^B?aFG3&bgRFBu^Rh^_@a_Zyn700&k))JoR zeO-ZXVIfDabB2u5V*#o53l=TM6iu)SY2Lc2p~-xB)pRANs|gW!U_%O?Xxnmt_{ zLp(Z@&)hii;l~|zHpwwHK8IF2#5&=7=0}gAz3{&tC8f?3?TS vg{{NndJuxMuO;VJxRtNpwef*kg9%z4&q?pt#K=p+VDS3j3^P6|gW!U_%O?Xxrg*wI zhIn))|M~y_e-;~Cn*f{9fl9?tk$n$$-+m%-_`rb!8)kLJ%)P?4n9q?N8SAW1*PrzB z^E>8Hp6Yv+oi^?qTqg|fed??F^Yn(M|D{}TIlG{cqq|}MZ(+IU;!TU5 xxjs)3+2>NJSJ@{ZQgO!O7LNlnvaujG!>MxR(w#r|76IMI;OXk;vd$@?2>=M5RU7~S diff --git a/Passepartout/App/macOS/Flags.xcassets/gb-eng.imageset/gb-eng@3x.png b/Passepartout/App/macOS/Flags.xcassets/gb-eng.imageset/gb-eng@3x.png deleted file mode 100644 index f8537799f222b13b205848e3189d17d734601011..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv>7Fi* zArY-_Z*JslFc4{Z*d1~6B~Mptt3{Kv#VY9)+#1s!@vZw1qww~!@}2Ny6%W;YJ(^~& zEhy*>mXFS#{`1QX{TVYWdF)oH?Xt7ke!T2N%(8zDia8>O+~9=T%n2K&Z#`}LFZ<-2 z4yPL$ExmH`AQH@k5-P%zm~YsyiPwt%X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0$xc(K~zYI?UubuTTvK?pTyjJ*0i;W7cG7ucGIB_1+{-c?I6W28HA3~ z7QsO@I%Mb#f`U+5bk9=BR_xkE8cJ0v3bkUZq(N$~ny4`vV{%W2QYcQ|b9M6vXFKo1 zb6(zaI3SsMh%A8kf6YTgmS>kO{Fky5N{Us=C5UXbd-?sqZCS)2Te$i3euwMy2wStA za?wu8U!ZjJ)r8Jc)aY_jc*mzZhQ@1T#X*Q)oy%8CqDrp!<3$p zrjJ_-o@XPEjD~jXq5zwp$d6Bck>yE?2oU=(-q5o89J|B8N>3k(WH$h8W;RELh8mxQ zLN9GL+g>pi>+l4FT?ZZy`PEhKHaByePJd8Bq0hkWnVFf_nx-W$EV2M`py#3mu4t5& z;b8z$b921!>!WIapPr2k6sHq_m1vaW$cULU#!V0Fv8It;Tm-<#W@$<$snK=5&&~p{ zv%Jjl*qB+Y`CC(w&y$**1fZBoaU-9n;Bc@Pivf@vAIFi)UA;)z=c9gL;IEXb;uV76 z$q8y_CjbqDgOvULs~7Q&j#Ay$20(pxHyMuyy|R*f{rvz0TU+sk!=^6mMS>#w2ai!B%5x^z<&@L<2BIk20az1|mE%M=CTM!ik P00000NkvXXu0mjf07E!B diff --git a/Passepartout/App/macOS/Flags.xcassets/gb-nir.imageset/gb-nir@3x.png b/Passepartout/App/macOS/Flags.xcassets/gb-nir.imageset/gb-nir@3x.png deleted file mode 100644 index 31a7955e515e2c85915a344344d7b6ea75b3593c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1095 zcmV-N1i1T&P)CMHM|5bPD{jW#B2LNAGJS->=eR1nfwqy<|Hl%K!?yTG2^J}4vWuKW`(+|6@{dU4XHN32JtrFd5<=5=pq_iYOBRyn1 z6sQN4=+DF79Rs$uk=XL0F_CikwY_dYVQEodXrPhJ_?{FyjF^8_#FA!nO*f*ti@u8G zyFT^3pyAHd3E1KYLtuMV$3LqwM^1*N^=C}n;Ia&+6!k+_uiY*>YEFx%>i*z;(FdX*x@0h=B}8j7$?mEX z2q9QnSRnh!^Zzwdo*>6xcD!2cQeBIp)T)xcP_4Vu8BZGr@R#rgQ zF_O>bz=2Db@PtCen_7w|GVhFBzs~QW(7L9a*;(?Yr+H~+2BGT=+`5(SdwlmU;>Hcr z@TM8DB@#ToaDiAP0zf1j#y>VjLf453!7n{MEIct1TUui5{CVt}W)|MusHG@tM`V6t z0#72rF2f+(Fvx%KfSu*#e|_0Zw0TB!r<0c(8*yi50TdVG(redP7$3)?DENztxOes}c1?Sjx_GhK>2&(dOo&p{4_)^_47atVYptQSHu2+s zvDQ+6$(EM%bGW5N>XnU@GXAMNp%9Pm++kG+?46x5QF4t~fXU`&5CXT5djzB!0kF5X z%SHgt!H8X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x12IWNK~zYI%~xMYQ&AlL&hF;crm5F~MkppYoy1s8phS5+sDQ(9rTl4HlNETbQDll%-R5b86e%=|N}P?$xN~v?R z-e^YAQ!QQ%>GzaGc7hVmuBs5Aknn1RTQUFfk%_&W3zPyiZ>CVuIu47K$JH%J7=r5D zaFm}81tI+U?^TP5X=Sp~j)eF`hO#n8h6G5D@Wq+90CedVu()jTh(xfz6zwI6$WILv zB%zt)ko$0`lbMSwIM>ua6GJI*Jkk%usr#^>6oqL)82N6**Iz4ck;qO^qN6McQ9*KE zjZ)yjyJ_Ut595cy^ofDGD9=g}(=)n=zHttju`Il$jKdQPE=)I{%nDW4u zB!c~==qW#n+c{wn32332Y_XVF}4TX>KdB*XW)rc87dYXrAbg7Qt$}a zOQAQKoy%Gt5dg4Kz#XYXWL=7eE79+xVj0wZz* zjELZ6NdmIs1NNSXSVT~j6Nb*RL#S#wz3JKau5^+C25)x)>(hl!%)!?aXYy(go z>a!EqY=0sdDi)fGWI+;?0<~|aak6FrC+-g5PTK^m{2K+*BYg4kR*Flh1nq0fik%K9 zcL$)rx6Ze(r&$5)E~^|)WEWYn2@kVsJ!`}w60^&!aDYv!fvF{A#*|NxyA37|C+z@HN%e- SmyU`60000jrzN^U}9GGM-A#vF4RD8Lj#7^^pEew5{<^9y0S@8ql zb31v_=B0M4Ly?;*MrS%lh|MPvTLcqKX=CYWm z5@IwENtSc|&uJ0@u&J{$JY~E&Rqnn9nh7RZz1K&~7y~FuE@35|V-z zt7U`U@|_NgkpM!GDBg|%dc*ny2t}gQ+XHE<=9;>}rglmGomP00000NkvXXu0mjfIk0_g diff --git a/Passepartout/App/macOS/Flags.xcassets/gb-wls.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/gb-wls.imageset/Contents.json deleted file mode 100644 index 432f48ff..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/gb-wls.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "gb-wls@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "gb-wls@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/gb-wls.imageset/gb-wls@2x.png b/Passepartout/App/macOS/Flags.xcassets/gb-wls.imageset/gb-wls@2x.png deleted file mode 100644 index bcd336f045496a7a6d027ccf2d380e8272917b00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1502 zcmV<41tI#0P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1(QicK~zYI?Uq|?RL2>|e=~E=o?S2N3pU0n#>QDLxfn0BiIOyzhRZ_< zN~A^>X%&S^%|&WdwMkJPLSIU0(}%VX=}i?hga9fMRsy7<35k^0R7na212(q63$|0+ z*j``u=DnRg=S&|!Zd_w4eWI26f1R1{`_1?NG~WoVbqmnU%~gxM$pF*(-v&v>#^zm5 zjmAhPQ~yy+&d89IEN%^w{Cz|I$F zKhQ!+xSpnU4}Niw*p+^&!^;?M{Ts1A{E6XcFJ5|zv?*yAN|N+!BFA>mf*9}aq2v2M zCV2W1HPw}L9Xvwv)On_kwGsZw3xq!hak=$lmOp&|jPtXsvHJ%(vEfNNPM*SiwwZLz zLiC1vsFi*KLLvkV|9T&pcw+iYbk8B$H*O_(`VtxKGBh&G z;4o8rkIbsbERfm_53u+<%~ZPrUw0p+Ih)M8f2BVlh`Ko{g^4?!ATv6K=enHx;ZC|< z*ol$Jlh6)DG!@byr5!%=@?5R2;_|6>0Qy_rVd%x5lTD@Px5jlG(y?*I#>ZIu{8obH zWw4|gqwW^^o_?7)5E2TvaV1qN!wkIr0qH$&Q6o(TyaE9s2?+xM)JZ?ZHLGYmwj0B5 z(Ra3!^o1TWRUrb|?EE0gFgUmEr;yIj+7)H!&7AY1~3xmUiT$yKJRU9@7mXa5KuF2JCx6F4;1`mG(j+Jp00J0T93nb4LS{HdGL^*G z{tVeff}mlrxbe0b5VI-*5L$Z&iyeU>uQ85Bdnq{|Wn61a1pjagsQrg2ym|mpEHbHG zG#WpWm{+7yny{#X+g^GGtE?2yRD9``ZNxwLm=e=MmWF280$}XwJvva~3$;!6$s2Fi zR#gR-BwpW_Yu{Me;T>tKUIw>&Xe=SAkOsMtN#bZM1O^Zg21V`Bp#1c@c?zQ=SlfPy zjV5%xO69;7f@$x@>DFAzhG|&IV{(*@VmbK_8R4NCA&tFHfzsaDcG# zbE(`Vn7h{TVHKeDPWZw009R@>i4usHKt^dI7abaVJW`fsaOn-FO8308=Jm%+0+|B8 zZ8q8Ys~k;JCB!X{6D#4=SKxG{gx|kXpl-+`t{ozSif_DU^T`^6Pw!FFaenl(6nc^3 z%<8$*%?pyMRQ%>!7EMuyWl6>MXBEK&w63%81fT>jzGU&}M@7E=UYpOqb2=CIH9Q!fW5QC@hdveOAPB;CuV>+~S%9U6XLXOWBx9a?y>e zazl`jdXqD2Y%YA&M_WWvC^Koi;82trfaqNT4(8!v#78!y5FRw0wJD!)7+v6Fyx!z| zW07-rEBeAFMFXnFT`ES4bArrxM{c72|9PIJ{~C~g0{it+@K`Sk;s5{u07*qoM6N<$ Eg6|H`r~m)} diff --git a/Passepartout/App/macOS/Flags.xcassets/gb-wls.imageset/gb-wls@3x.png b/Passepartout/App/macOS/Flags.xcassets/gb-wls.imageset/gb-wls@3x.png deleted file mode 100644 index 04ac076023a4ca2632e7091987fbda5e6fc621a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2773 zcmV;`3M%!9P)^f4kr1T%>b!Ig%_{mLkh?WWWyCu0jnoBr$|I zHMe9;E~T`cTs%PABtSyaKyPV6(Gx z;C$gdA@4SvcOmKN)H(GZ=|B+orXq@%dlT`SdR^moDMj`|kO_2~kQhHaJL8Ss4KBFElXp$QGRD zqx6pqQ>87QsVnEomO)DTMiAOV0+rvpn-w3q;*5h0wYIVEE8m9K_p@BuAS5F%y^3cU z4E}m2L%)2P@{U0&@4SV&d%tp;eS5T4DK9^*?TqJ0{pPPzT3(J?vjV5RoBYy6 z9N)7a_mO8AUR}eorArC_dI9eQqR`Y zw)q~?Jp-h=`zb0aqJ8yJs%nG;#H6B$Wj7{;7@&nTp@s)FkO z{5N#nN{ZgUjF4?14T)^;rTyuxxSoqBh_lz9WJ(9*36s!hlHup;XMK!y_OS2z&5S?w zOEO9^p?y+Xq0!VB5iBGpv`>4GW}iPrlRw4Ynj*~7#B4-Hk9Bk8b9az#?_%0$|0SE-=(pj6n{VO34YzUv%~~T0fc>RS)PCxk*}g5e-3yH^ z_-HhmPFg84bwd)`gTJb&^reC?_itMr|nIZm8OqM6OE!VT+5O(dCquAZ1M z$Y{m14#*J(l@I>|W^N7uDhP0bfQXO`DHk7&i2z_)2c&g?Ap{ALoqco7g+%k>OdKEL z;B}jcxAh_bR|}t5==Ue&oR? zhz}=e3sU4ulOg2+(0%891T!alYIu|>eDZ}!2F0lIkO&lD%zV>Y&F03j=0hl@7%8zmAC(qIK>BpD4(1ijZ8PTB2FX$8{O7(CaPx_5;6=@KvC0CMtb@vs>mKfeTUkp z=ov*yn_lIRFHHKCOI#QjfBkJ@o+d5~@^!$p4j9(nEO7yhDUV6vQzk?BS}}IhdX_A& zq&ObK7Lvr$N-k*l3CDl)8e>DlBnQW!dla*3@&8UprlXg%)I_|1k-Z0Ig-kWP!H{w> z5f})Dl}oJ=#YCcAk8z+|WhfLD5=aOO56y)3sghv;jw_dg{xl&Wx$vQ%F|MB>M;Iif z(?zWI}96(V?M-DxL#{*cc<{?Or-8qGbggm^J;ns3|} z2TOAGwhZ?_m)&u8`AK5JphAX;3xiITA&Mj*n|!}@Tq29JI##p557 zB!hr{-T z$S#pmY10~{7*-x@%@`3O+2c>LNZQy4dX+<&w3*ZaHj)$p%VdPGkfgMtM`ef!gTzcU zUMqC?@wEoKFOMupz;9u2Q^nu^jx?P3$-`=JvB{f@13vw>NlqXzgkVzp#DvM9atX~4 zI-z|$G=thDjUpi|N~O(^^2ik?Nga?cO)8BrFP1xOxeOL}cvKU{2S&9|nt<@M;*G+e=B};g7cvJ%%1kJV>SBxueLaKeKAXh775+(0s>JXVdH6m(?BO)6Sf zSo2X__{D1bRS{m=ox;+B=hs=dNryYPYRY7Y9Fa}%d}*;eoZ{!J1W$h4W>dY(mv_g2 z>@oz%^CaaR9_^PH^jF*TSKDOE%xq~M8Fab(5r;^p$Ii=bMq(gcg|g3nnO#ss01yx! z^Z9k1V9#Y?KDsNQx;vo44D(M{IDDeV=Jp#L9{qA2yVqJcc?O;3f}CE5#E6=?fB0yv zxn+;Z2lhE627Fqp4FdaQGWfX$i~4;oXjimVoBZaQ2-=uGS?!vjv`}h!=QiLb6C5V&A>b`ilOU0z*2OBi?8?z*~d@&AEP`h)A&G%1m z`SuI|nOsTzdYiGBz!H+A4tQ%hJopz8espt~=>n7Mw@ee7I$1@pZOj iC9>2u8i!I6Z|#iJ%=GFzhTEY9%6dY`7NPp%jH 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/ge.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ge.imageset/Contents.json deleted file mode 100644 index ca3dbb2f..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ge.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ge@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ge@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ge.imageset/ge@2x.png b/Passepartout/App/macOS/Flags.xcassets/ge.imageset/ge@2x.png deleted file mode 100644 index 80960fc537d19890b3025d188cad3811ac8e4ea3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 330 zcmV-Q0k!^#P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0RKruK~zYI?UpeL!Y~wuzle)li&xMS2wn9+-F54#vw8_VLcD-O4&5d=lBP?QA;3Td>nQM8ai z{0H<;(6kDIS}53PWvvqKP_z&vf{hSS!N%CcLQp~Xin-u%x0#!DCnSXZAPmF2H}BVE zGkF`ZZTknhnVybvdHLOOt*h&qt*xwXlR?1Jy(xEhaDMK*R$m_mT6CNIrhCJ3Q8j@C zm88;XI6H$#1ZHMR>rsKtO?Y^KzCIWp{;#-NMY35~Sb&2AkKVape_zKSLFB@ z-rk_5M$8)<;p7A!A7O0FtE8zJXNY&&O35Q{52=%|QGs^$mL52Vrv z*p_8$-}T}G@_FaAa2UF~wVMpspg)i@P&G%(52VuY1F1BESo(Vp<#_!1cdbgp52Vsi z3nY~ie|O{Y61(#U@wl*=N-2q}1=7|AudmSF?!hycxU?y6KFrU<>8Z#Jh*r?(sAxVM z9(we&2(g%G0p)UHzP=92%h2BsOG`!7fa`H1X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0gy>VK~zYI#nw+t!f+hN@%L{oY-yM%m61e2hY~z=lkk*w^U$Gd)Pv~W zBj^;JdIa_)f{36)1ks_i$f7?+BBl9%TL(uM)7r%C17oM(cazDKjc|Udv0w5W8WqY9zKTGCLyt4M?Bm{tnueSV<%I25+^^Bts z0Jlq@bE2vah!NYX>IMPqt*R#k?5pZeh>@;UNqYOT3j+LJRrIMz^tov-1Ujoq-qc85 z*ZPLYRu;A1BxUok8ee;Tc`seb0UaSGN*dK6!Cu@;e$@2)ncoord0b;`tIqHzL0?4| zV-<~XQRN`!v4pS;0T>Facw$WufBncOWC+AFj0(G2%<0E*VF zk_OhzF4XG+tqiU}``c9u0?7*vnY+Z%jB4h$LO|N`jtIB@gMcvRqLph z8`V|oxg$B-zs=?MhJ@cTUGdxzC+TGM%GaBP^~)(U z@4h!(FqtaUWR&TOlpxSqwd^O8>{?Y;nT|*c@*m^=rC*uV>t9w9(MDOfY%--9Q-vCa z<=fz{NSToqc1^JLSjWt3G)z&+|BQ*VKk7Qb`|&8{;pTH&JE<{0oxs%O&5hI`&^)L>^zp>ox!h>R??QtP0?0WQC=mqw9}5C| zYxRrYV}UXqapFZaC4)f!swtzEn|@VOwj)k@Q7sff;Aqv9Rm;s$s)eQ_PTHv!${=u9 rwa}>L#;uyLit*Qj-jrXSmXf7^H!)YU_=S5%00000NkvXXu0mjfO6D_t diff --git a/Passepartout/App/macOS/Flags.xcassets/gg.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/gg.imageset/Contents.json deleted file mode 100644 index fe3ea1a1..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/gg.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "gg@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "gg@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/gg.imageset/gg@2x.png b/Passepartout/App/macOS/Flags.xcassets/gg.imageset/gg@2x.png deleted file mode 100644 index 146322fcccb125b0a0bc697fcb48d9d348bf0ddf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 540 zcmV+%0^|LOP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0ntfBK~zYI?Uuhw8(|p7zxT_Fo?_DyA%mgV4yolJe}L&!D0J%JV8_xy z=3;9RO5S(T@L;az6COZd;6PWRlt;F$mQa zNCSk-a|;0AzA->`B{7JcxD!BgS8u&{^q9&^2DwP3AYr8-VX2K5v!=JVFVqwqH@Lae z`IoSl-ikroyKAcc_n$`d>o zeaNF{!-GR5ej|N=a>o+~ab6qTHJ&%$Z(^wO8vtO<4a57a#n$UnHs4Db&0AV&ydx2O z1+-Foj2s4H3}iV7>c1il<=ibuMt@|^`v54nBYJM$U?8F@3CelDMYXI+M-xS22P4D| zM(C(qq*~S_=iR2L9TCymwK-@hNLVRISUrJg4_ND6W-*oB)ze=**%F*rgKe=Z2cfzG ew_+q79M(VXr1qQ%WuvM90000Se;;cfYvvm>d;vgbuDcTOb%Owt0Yq6SKN%SoD+j95v-uvfrAul0HsWVulwX#|@ zvCE$xUfPUHlv2v3!-ssu$Zn?9uo)K2s<%FgvH)B3N4^tR96{55SSLc{EdyWgfG_uk zo{M@4-pQ1Ukf1J{Dj(=>CeJpu<7&T1Vorc0t;P%mNxtQvLo0+;d}Q|8eAIJbnBpL6hNSEgz~s;Xrz~ zZDgwzA{rdt@s!h|=sT1TbT@OLCH?ZN6%yPyDeH|>me_OZ-g9S2R!suc@~8nKY5 z5etbLu@D_336P8&v@_Caappt_;Xr!34O$oop#|x*(sZ(sq9p;pmV#gVTG`QA@qaFQ tf+lm4>`yQN=QbVk>VnZ_C;+RMp|6%}iX7$^P=x>h002ovPDHLkV1nYX_YnX9 diff --git a/Passepartout/App/macOS/Flags.xcassets/gh.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/gh.imageset/Contents.json deleted file mode 100644 index d15231e6..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/gh.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "gh@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "gh@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/gh.imageset/gh@2x.png b/Passepartout/App/macOS/Flags.xcassets/gh.imageset/gh@2x.png deleted file mode 100644 index a6f7bc005d8fdd20a50106898d353b1c95d12b2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 416 zcmV;R0bl-!P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0aZyvK~zYI?UcVu0$~`(Kc}z1GO=!nrpS&Gg95jln{H!0HbV)A;InO6uOMu6%$)p5#XR~Bd;#=+hQe}O`b#H)&iF_a>wAvOk ziBb2hwuO{l)5f?XrHaX_CeSZbs#hx6jG$EUzJ1E^X}Y$M7_PZ}*lZZ2mLdd$qls9S z6f*iY5bcL?~6F`MV$B|PTm0!2w=1SIVFey0000< KMNUMnLSTYgt-hxK diff --git a/Passepartout/App/macOS/Flags.xcassets/gh.imageset/gh@3x.png b/Passepartout/App/macOS/Flags.xcassets/gh.imageset/gh@3x.png deleted file mode 100644 index a8462728cbbf8cf95c540856b2830eb1aa876c9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 538 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2rPgjORUF z977^n-%j`SmJSqYn{VCc*(0(@H1@^0vdc;wbq=h)=2|YXQ5u(A>mA<8zwiqAz%H`n z%32S57DtUM4xQ5W9%5QQ1UbT-GBsjDS(4M9?Gg~VxNFyOp2T}+&m4HV=lRa>^L9R8 z<#YbAkf=iRTg}~fJZDS@eaGUX$?$PDlfZr`wKB=!&8;%`D+jKMpVv~&YA)I)wROqC ztX(ZZ7j6gYW;RzG-Z8^wueiWPsf$w6>O>-a#4av+t;e%zW2{pAhaX>$Cl|YayOo@i z{ghje`~JG~HCnZ0KkW>wo%R@){9G%t$Hyfj)5v(bTX${h+n($1{{{Ydeb;iGV%o>T zM~sI*ZqUr$9KHH~WXGB1RcuvG|7Ab#(RO)Oyf=_TV8z5`?P90jC-Z#0>r-MO_w0AO z#M$0w-oZ<5=yDhRoXwv5ZF#fmmuf-I-Ls2t*q1O?7)m6TJV+|tn|VMT5l?}aQE6-Gj|Ge zdWY0(I{jwm>Vw6m(`u`yzp#{A?bY~D?ULW$zFEByYN2OpRnP-3XNuocYffP+_6K35 Vj$6Z)vj8KT!PC{xWt~$(6969O@azBp diff --git a/Passepartout/App/macOS/Flags.xcassets/gi.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/gi.imageset/Contents.json deleted file mode 100644 index 75faa11d..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/gi.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "gi@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "gi@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/gi.imageset/gi@2x.png b/Passepartout/App/macOS/Flags.xcassets/gi.imageset/gi@2x.png deleted file mode 100644 index 949bd7424c4a1bcd2c2c46034403ee05f32839a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 861 zcmV-j1ETziP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0}@F@K~zYI&6Z0@R8bhme`oHU_d~NXtp-Kh1CrQEs`b?~1^&UWD2%S}Wh5u9JX0-#SSq@=u@8SB>kT@b@C zXx_FRzXf{f6s%y77Y7gHi^T!ZLP5NHcG1()iu1!K6afoQ97UGxO<@~Ek|Ywb7|F&f z02DWzrLd@owpcp?sG2sF;LI|*H*5mHTE7-alI)hZ4Kob34=jUSRSPGaa{iTr{9#7)B#&QG32?pXuyC=Py!PFpfFl zFs9ds+22obZH=8N?18vLA;RtoK^}rcZ(r69nK8%(bRJ@&C<>1;S!~5wg2FRQlz5z0OfF>6tQmoH zI3;$IWFJWY9$^y0q7RFQLq~WYN%oMW!)f2lG6qQv`O~n81p*_6kkX0P9U-uM8O0TI zM+ebgb;L$Xa>c$=wsS{1b~d*ns~WP`$CJbZ5>k@t>ZKT(isADzbz$|$*mob$dV2p@ z-)Mi`FWL#z0~xE~{`&*@>5m~Jsa5d=*^sx+5MS^CJZ zCN>NFZU}x8jDME(Y5+OB(1ztN5a>#75Rl`FjHR*$F@d%%1H@-oqvrlq5CFHv;0-ti n%DP7+Aap~Zby|47p{*@`h=Gfd;xNX=+d;Si^==2?7OK4X(ZP#x z0^u;3L;`{1@gmnbJrQ#L?QX90oFOu0VbVf|mWZ&`*>b7n|?|u(!^(vgfA!e^z!@$}XsZFJ)>`%^!FmLxxMt6UP7M+WJ z=o_r9uW`Nm9Xd}Op}LU0a!^r5G)+UVslm5l69Vv^J5MegqH+7{3_ZG%*xrNuf&gGX zD!yl*rZE;{_`pE|1XfqT9AivE|y^h_$u=(1pcu98TfT zg#|#YwS`=J+Z3PZwk`Pm{z^ir{ES2z8>uUK9>Io&;`%;RbQILW`X-_c4b+!>&Nntz z8isr2*90VB7)H^y4j(@wh$DC&U(s$D2BP5O7F-c>H5j0S3~yjjT02hR@F6*NlH&)Q zqFqYKb|&Z`!_{itjS$=sk_c9lMsm4uuy3ODiRb^~a|KBnNg@<(+qQ}C z=prE|pSA!-GvnlPIg*!urMDyt7jXFPVjL+YqnYud;k_s>LO0Kup+eU-ail-&?N=zNaa{fDS3@g77lf~3mlLs2!VS|r&f3rz!c z$Ok7ia2)cs%^Vb>rK7w8##5_#dEe({2`RgeELyOD;45>d*m6gYv-rX#R9#0=73hN_ zwK+yYf@gZepo0_=p;(M9UP#l5seiDtHQ{o@K^b2Es;Bo_U)sm8g`iV!_mT$X5@An- zNZ{I1$4bNV!UX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0tQJ$K~zYI?bbg`6Hx%i@$a>bgMi?_!9O7`u1+Qz6BiB9!9^nuPC61X z@z27lQG=R5BC?{L;j|w|xa=LVe#v}2)rK+itztt# z&9ZR$Hu~f=d%HV8k~~RD?h#>_%(Y(mC*tpat|#BaGSa zwSfrJLg^j$R3VLYUTUpD`K^KNBu=cV)P?!4v;@@fA`tc2ove*Mvd z&1)achZ$c8JEc~ID5msgRWhC-*~plub!(5Wk0U5d3%O^oHV^>$;Sl=!&sCQt?(|_q z=W7QMrp4U3t1M5)To);QBc!^XdP0(OSM!fANKd#^N^dc*! z;{Qk?mnYtSi&WPDO|2)WJ9L21j;#pC!Hg}Cdp&{n>=jOa$uqzeq!=R>Cwcd=?~7H+ ffAg!j0a=|ti?Yi8$HIfEd{4{{ci#Nbx>iJ4lVzBJ~eve3Yxf4YE8sNoJx3 z3r7%{`c0Y{woaX0 zn1{bJOPw76L`yp&)3c<80N}m%6iP5ms+suq8w|l+e~0$=+uH}vtGC zU5k)&G%W)bj=O<>4 bL1lq|#>*nr%L!B&00000NkvXXu0mjfR5)wl diff --git a/Passepartout/App/macOS/Flags.xcassets/gm.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/gm.imageset/Contents.json deleted file mode 100644 index eb99bd62..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/gm.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "gm@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "gm@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/gm.imageset/gm@2x.png b/Passepartout/App/macOS/Flags.xcassets/gm.imageset/gm@2x.png deleted file mode 100644 index 0b89cf171752d1605bc943436943b9cface64f25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx7I?Zi zhIn+oy}FV2kONQa!}5v#c}lGp(wM?dXq-95bivS-&Ep_bW(Hr-y{9cN_cpBmpuh7! z*XcF}e#Rpe?_{M;Dq6L_;CMVk@X+-T#Yvo+h7FT7SrVeAm9Gkoo5r>1!@|nCd5keS ziVbVd>a2Dy;k#(cyfAn7vc!bT)|=j2UNrgotbED)$6hs$YfdEo%1#nDo*HlX-*l;1 hURtE=qT-Hs{4?XSYdF5ma|F7T!PC{xWt~$(69AdNUSa?M diff --git a/Passepartout/App/macOS/Flags.xcassets/gm.imageset/gm@3x.png b/Passepartout/App/macOS/Flags.xcassets/gm.imageset/gm@3x.png deleted file mode 100644 index 9ab11e755ce6e921c5638a777cd9e8d65708a896..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvZci7- zkcif|Hx6<(7znsrjP%rAsq#0%|577sMX-Q>RO8zO(q>I)Dy`k@?qX1W{p`6TQMca%<#{BmqI{qVfU4Gfe}76&&~bgd zS=jbpDg7M@62FX=E)p=~N?a^pcDY4_A^lqihy9Ng5{$AprkDYp#o+1c=d#Wzp$PzK CR7}DE diff --git a/Passepartout/App/macOS/Flags.xcassets/gn.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/gn.imageset/Contents.json deleted file mode 100644 index 09710fc5..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/gn.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "gn@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "gn@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/gn.imageset/gn@2x.png b/Passepartout/App/macOS/Flags.xcassets/gn.imageset/gn@2x.png deleted file mode 100644 index 88f1362d20f9336de141f8212eba8e0440ba5a43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx!aZFa zLp(a)p4%wskRZbH@UQ5x9;uLpimn%$8UmZkPUu%n6nrCo|4vSI0;`% zrp;dX<@SwTmp(AJsW?wkJUJo7!$?JP5|1aC`_f!d|GN=Kxu5m1uRtppJYD@<);T3K F0RYNDI?4b5 diff --git a/Passepartout/App/macOS/Flags.xcassets/gn.imageset/gn@3x.png b/Passepartout/App/macOS/Flags.xcassets/gn.imageset/gn@3x.png deleted file mode 100644 index 797ab9c5b0426e4d35140767f98bd71c713b4f0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvR!@i)Ar8;*GK0b z98}%8!%p(O|2IR%?N1tJN=8iTemlEfX7WZ)=Nze|%L238ZkQZdrf9~yF|)%LtB_hk kuUr}126gf0!T&z8i)8a(U(_Ld59lBUPgg&ebxsLQ022F5bpQYW diff --git a/Passepartout/App/macOS/Flags.xcassets/gp.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/gp.imageset/Contents.json deleted file mode 100644 index e31b6f54..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/gp.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "gp@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "gp@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/gp.imageset/gp@2x.png b/Passepartout/App/macOS/Flags.xcassets/gp.imageset/gp@2x.png deleted file mode 100644 index 62d74b4234bbd0e63fcc2d2c282acfe986a5e93d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx5M eG`?WL!Z1mKXa7E*nWjL?7(8A5T-G@yGywpH!bC~{ diff --git a/Passepartout/App/macOS/Flags.xcassets/gp.imageset/gp@3x.png b/Passepartout/App/macOS/Flags.xcassets/gp.imageset/gp@3x.png deleted file mode 100644 index 5b94a2263d0ae4c51f921f00255def0ba318d445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv6`n4R zArY-_Z|ZU}C5p5@v_9-|n8Rp+K~I9v#-6LZ60!a34y2WMvk9^pM)N5B2yM3Sws^|> z{N?upv-7X5<6keJBVKwdKg>M!@!q?p^B?aFG3&bgRFBu^Rh^_@a_Zyn700&k))JoR zeO-ZXVIfDabB2u5V*#o53l=TM6iu)SY2Lc2p~-xB)pRANsX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0wPI7K~zYI#nwM)6JZ?2@$Y-NCWa*a6QZ?2tb&V+bP);`ZAH-v?k>7I z*u|j;x`^N(iqb)JR=RYN4ni%Cf`YBmNlPP=N=j>*q~UTo-{x|cbFh`P7gBm+?pY4r z-`(-K_kD&F!%s&mM(}YE;ow*L+Xtlby>vXVT)gHRVk-(jIyXW|_W2KS2(Ym7kgDD3 zF9e`sb+h>M9yr2Z2*7%I0KfX8>|F4xFD{5*eNmIa!&V9c^@|m&n_BfXch9dPcBzcO z2L612*Tm9!U5zOnwMN5_KO%g}oMCkEK;x&48Or4{4&hCZMKl_L$i+QFHr76vuA`~X z?dcWXFBuJI6?KTV&dW?2QKm{EWOxWGbZ*axtu%#LHHRw`Y4V2Unh7D8FMMNceuBw` z8J;grc}4)JyWOy?RT+P`#{J=>3(^-q#@qfoNNEvI_Wl#3>5c##>S*KUX?IVA5IBy5 zEM`#@kU=xg54_ z)82amMN!Z+jaV$^9a7OsCUk{&4lX1%xbbTi`KIxMiOp**v=6&h<6zYUKzcw}nM^^| zmohs0yE^IzJu=VOixJk%l84uNO3Kv{)vn-um*AlZCG>MYtemvvXWe zy=pmP8>C0(8J~NCz@MLJ1^nxU3*uKVwm}B7pI80rMSXq)oJP>v9U*}>00000NkvXX Hu0mjfMgAbk diff --git a/Passepartout/App/macOS/Flags.xcassets/gq.imageset/gq@3x.png b/Passepartout/App/macOS/Flags.xcassets/gq.imageset/gq@3x.png deleted file mode 100644 index 9469ed72acc7dcabc505f0587959fa9229b5da55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 941 zcmV;e15*5nP)S7e~ljFVbr#*wP;!r;8&xL6p+Cw0ee?86Kz4$VlrOH}^&E{J^b! zxg!X;voBqPxUnx?gMj<`(lZFSt1p_}fR*A&=BB(n5W2DlI-_G7#56tpo_>#|Qx9?9 zKHK3Ps^wLLlr+j05keBU_b5u41jAE~gnKcN+11S(ymtO77hXSvu&GR~lBf9ZrwHLC z5PKf8SwKi1^-6~N+V^aFe1xXnA{3q34gU^;(@O(i!Y{sXkQ4Lw_Z`!28U z%Rlnn$}b~}+A-A9y2ZkgaNoY@x=yjN$x1U%Xf;Q>bc08pevVqDN+1xx=iB`m8ER<_ zf@zuvA&7W`%tsG!Z9}JOw(tmf+ZZW_S`4-f{cFjHk8h48d(K8KmqXWel-i!?YB58* z-RgX=>pF!(VKm!53>C!d5uAT@w(ogTtyU?Q%NWLXkJi%{Dg2iPwr8SLDp4-){{4ZW zmewZ=iM~4)z~tm4sZ@%1JWiw0Ao0X2DCHrO$E_1n#&k+%<@%3_kz!4AEv~uYE?QYmkrr`I<-9FYUF9Bkl;| zre0XNEr{ECVWktqjlHna3F5|HSdrk~URaTC?u8ZY?9$>=v{`XyF)aKKi{ehJsG2ZC P00000NkvXXu0mjf(}cpk diff --git a/Passepartout/App/macOS/Flags.xcassets/gr.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/gr.imageset/Contents.json deleted file mode 100644 index 346cdd61..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/gr.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "gr@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "gr@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/gr.imageset/gr@2x.png b/Passepartout/App/macOS/Flags.xcassets/gr.imageset/gr@2x.png deleted file mode 100644 index 0c5e7314b5b6865b2109cd6433b9ec5e930e8099..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 535 zcmV+y0_gpTP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0nAB6K~zYI?Uuhw8(|p7zjt}hc<~ZrmNXbdT9sCm;tvWPT@-QZ&{c;H zg+hz|2}1V_lBE?2f{RNQ2MaF2ArMi7LIR>85)&JmR!T}kn$N~T$mds-%dWdd@NZ1y;dV+;8MP^N{u3H8t!oIB6 zBh`6Gm?o)gYS`a%kS5UzQ5O?opw(G#!$y!gK3!9A#7<9?t3-;lt)}PHX7*=14&4L1g7603dl* zLG=512l(dIU}|#6cg;gWt?CJu{dI%(t_cTZ;EzbYQb#PA?|?roj*LGGDm*-ZATo|A z4NIJstxq!m^Pdlj0Gd1``rIy!=?1a?fm?J2GO>6NODmT~`81Ob=j8Up}T zYnjX?n(c7N?=JN+SBr;;0n!8_+5zyE$A%AxbLI%!ieLP!-RcQDB|L6cWFNnsJRzdj zc8U-Q4w@rh-%nrq`=q#?5^7a1Ja$cc*z?uB%5_ao+bQAqNGiMfeH$R67j}wh4Z}Hq Z);r{vpGaW&Ze#!e002ovPDHLkV1l2P?9BiG diff --git a/Passepartout/App/macOS/Flags.xcassets/gr.imageset/gr@3x.png b/Passepartout/App/macOS/Flags.xcassets/gr.imageset/gr@3x.png deleted file mode 100644 index b879dd8dfa1af56af15212505549ce8709d13cf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 526 zcmV+p0`dKcP)V zXfFp8mS_FeiNP-Jr`|bvs%*y}g=TsJP)5KznkQ=rfWQ$z7fm$au|sgkU9aQGtc3u| z8GG48R0@8X{`Q5fqiq`D|)e*KKR4$9ztpJtK&<%F1ryj7d6$AOQ&pA%vyr?Q;PpmyTFkD!|wy`U7K$4E59U zd+p%nJL+{RQ-V~s)Ks6&m!;_S%MH7TN+2TILlJ5to8bV=jC<`JfTTjOY$-e&Z^pbqKW}fc)jd|G?NI`h!D&L#;!og#Z!00SL5X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x2FyuBK~zYIt(JRGRM#EHKX>=u-GybBjj{_WZ}ACgkX8axwW75#4R)-L zOvjcaO$XEtlh{VB={WT3%x9 zr@pX|hLtOMc2*)46&qQxBJ)X3psE9grcRBe?47y1e0npqw&2*DkA0-b+-F+&#+2SC z#{)FaZ@F>dFN?OI8ckHDrlLK2nnQszGYHV*ejUrEug(`Q7yN__2?VilT-Q zf)^>R zFT&-sV3cIaTc2jxvNX=0ze{+yjyreUkCrSEEbKt;oE?#V8$BuI}G<5ZYl+B#$5zm)~ z4GHo!6%a+FO`CadUxPUMI!HSAo#uZYAPy0X&kVC=~tL z#=U?IeadxuTmC_6f{b5hqwG>T#l<6lD+ByQU%}e0Z|?lfkQq_^e(c%V0E~I_P0aJ> z5111Qg*bP&mL+u^96J`tgxuG0-F^otAfT5fqth8kF#d@sTM4nLHqLyOO?!v_A;`x4 z6*qM{DdthI=x8&qy_QQxMgmgJSDN{t6dK3b3kYLb{yQ7-qWEoF$8x^%~ZZv37)&2v~*4HQI)z&_k zTvz8}-n@zI+O?dxNG*-8zKZMML0tK3ShT5}`E$pk?%xGG{C#J=_zAteer7M9gDGINbUJU=c2B5LOMmBDIK54^-XVJ8`QL}h4ftD8T z{b~=dY%9a*{P$3$Ls?lZ)2DCcjW-rDE;X4+Q>Ic;ewFm3WRjw$((2QZ66wbm%*2Wb z(BSv`5k(P23G~I$e0$FLX!n7ARApqKosrFsw3U2!)hV3Ljv*l)NtP8pI(CdRXG-yT zyo`>E$6y;tbB9VWm`RuxVk4&0s{lM~m|ceOd=#t{ml1PKQ4Wlj(`zJCNmGR5b;#0aS_y{&l|z1it*?L8JQRHc&RZKPm!E&r*Rh+a}Cn%2Y` z_Z4D!wDk^ z0mhn|vAYun*hYo7a?fM?>Tt=h73<>)sM4Siz+eyn{1;a$yok4>9*h70002ovPDHLk FV1k}sgkS&w diff --git a/Passepartout/App/macOS/Flags.xcassets/gs.imageset/gs@3x.png b/Passepartout/App/macOS/Flags.xcassets/gs.imageset/gs@3x.png deleted file mode 100644 index 1884af4f3ec973270eadb130fc7ab62427301a62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3131 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/gt.imageset/gt@3x.png b/Passepartout/App/macOS/Flags.xcassets/gt.imageset/gt@3x.png deleted file mode 100644 index d3d3a4f9f41eab2e79113d276ed702de31ffed59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 676 zcmV;V0$crwP)0p@N`O zAOwAZ4$-aeAan>4Iz%0$f?hDt%23+SvcfdQE!SOl*KO2YcXwuXrkl4;^B3kY&utjy z_wX>oFfh`k;V_LTV1fFnBU{pErr8qP5~f3R@XM0hRsXeN?p9967ke zy@5d%muJXmX__N3(i=%C)g7T;p&?qaK)SF>U+*9w6K*(#5>iovCKpeh;a7T&uFf7( z*;P!-6sZ;)@;kFgsj^9|@etK&70DmRwrYTlx$~XBr7VGfilJA~iW?%;VnckgLO@nY zoA%^yqz9pv!wB3?K8#HBvP$_qWEZQ>J+UN`)C13lEm#h z4|zN`OlRyE6}?QXp@WX21K5_2%NK5lRswqv0w5uAt^d~UaZ9w7c&uwLRJ(ZJT5iJ& zaT{KU+wel%h8N;Cyb!mcfHdJ`z^K`grF_}Z&rqXgCqZ)bGxQ&+Dbj%4BA>qi0000< KMNUMnLSTY<0WfX= diff --git a/Passepartout/App/macOS/Flags.xcassets/gu.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/gu.imageset/Contents.json deleted file mode 100644 index 0f5f389a..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/gu.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "gu@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "gu@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/gu.imageset/gu@2x.png b/Passepartout/App/macOS/Flags.xcassets/gu.imageset/gu@2x.png deleted file mode 100644 index a63dfc15dda97ab7cf3a9103688f53180807ff0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 949 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 Tv3BJh00000NkvXXu0mjfhL0=F diff --git a/Passepartout/App/macOS/Flags.xcassets/gw.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/gw.imageset/Contents.json deleted file mode 100644 index 6fa4eaaa..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/gw.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "gw@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "gw@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/gw.imageset/gw@2x.png b/Passepartout/App/macOS/Flags.xcassets/gw.imageset/gw@2x.png deleted file mode 100644 index 0c03edae42bed9bc07dee371ceb3cbedd4c7ce5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 370 zcmV-&0ge8NP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0VhdBK~zYI?bN+W!$1JQ@qcVSj1k+qWE3hQ2v&Ro(aohGh;QKL3yAb3 zd;@0#ZVqmOvmin@iy|U^P>fYf>?P+?gq$6!G>Jq0+uen`AD0mAyfSx)rfo7rPCaVP z?spf4dG?oiV<~Ls~6)*HNh3JDOPr@$uaW5apQRs6J zcKDQ%hgcwib-#^{M#C(#ZBJ(d*|5sgN+vsRcgX(Sa~#ohTpH#S0M~&b_pWNEqefIs zahrHGOS;XnZgbXq2VkaTd?NsEgr#{e)s<00fTzUA8X>t90b5pu-3gb+SkRGC3g`YT zaU(4e(sW$bj7b3IOBNU6gVp{xGsvPoo>;6a<23ue$+JjpxNFkJ4O!{foI zz3~U1D9FS2t)@4|jiP&s;DJ0Xy2Po-SK$5#eDXV7^P005=_vwV=VASEHHsbvvGm%w<==qCb%t zbb!}auWL}s$L0C^{a>&lreQ-&!-klK4KWQHVj3Q(ouW&w#M11R)WIl(^70#~2X55q S_@&tZ0000{IP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1T0BJK~zYI#g>0elyw}(U(X%S!QnkJ46JmP89$_xtRa7xrv0$A*|be1 zD+BC^QrEAo&D>I#HK&`b<&rraSWD3ykT;kz&LokvoHj+}mnh0n!tulFj(hIB=k9s> z!yzgn2P(b)d_JGg^L>4v@9WvV!irT{E#~P@j6LSsM`Pz1{-0=tI<2W9=F27DkRJUR z`DsU)Z?<3*O~a0&Ut`pLWp)LGW%)ldI_x)AroF?WlpMy7N=8^Eq<4lCt^&j$ST9)q)QW1Rx0Hba}Bw`LS*{V2&5A7X%|jz6O#7 zM=+7{hCLLY-A1ds`M!YY-yR#ecC@StJ&$z2G^0qAtRrjJH4@`>Bu)^304TN4UBHOI zuRQ(e6c(j?%j7Y$@CSX^osFoPI#iOHJ|I1rXwWlF6j@eu4JAxt`V?JnG1v}X8-gpB zvBqv*O0Y0*@;V-o#^LDxo2%aTL4));n%mPFRLO4jkUSgmHi{S{`e_ssKuQNwCUsrS zSkL8A-mnL+;t7G{4vyR@va0cEyNqXIkYBcnJRH;K1NKG;hse1gS@7snIC#F0vibr} zUpad3AkZsusnO5AZ6Ya?1{?t~5u|rPT1u0%jRW<&D5=@OzrLgik$rab7p3WMfBsDf@?odM5UT+HB ze!l3E*`IG<_KZP|f@v|tqxo-Yg=+*{y;FTf_JkJTTA@#^y-LfqsH z#uuq;KZW&N9=3+P$ch_)L4oXc`^mW`vm@8Qyjh_*5)V=am@;uGIw~#{Q*?F}jUCl} zT0;WyYp|wM=2VPARY5pOad-P`3=sJ`NEXzHnVf7njP;L?sk~T1Kvnu38xW*5sIt7x z!;2LT4G?3lfD2e+GUnz=~(}{i0<~& zxM}x(!&Iu84q!c;!3lc>8d^vI1A^>!``P4`*}hq1!5saqMR5X1?|~_ux~>+if97Fp zSbRggp`;IpUxT%sGAE)HDtCr6DWS&)Js4+#WWlAMLB$0tMQ7&Tx9jCCAZz?O9pgqdCd$wIU8 z6t?q4l-9lyvUo!V+3ohTp<8D2##l0!W}}SA+!rHi8myW-^Yyz{A70<4wh{o zD(Pi5R{wyl;kkPfZzvH~u3ypgSnBBUM;ygmzFI%5i}yw={134i&u;#?Wpn@l002ov JPDHLkV1gCMBl-XU diff --git a/Passepartout/App/macOS/Flags.xcassets/gy.imageset/gy@3x.png b/Passepartout/App/macOS/Flags.xcassets/gy.imageset/gy@3x.png deleted file mode 100644 index a8d4b86e23485d93563dffdf93ddb649bb63a9da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1597 zcmV-D2EzG?P)`Gn0O$kdu?8BdxZZh+~B=h7JD*c|G&b3Wh3J3Mp1q*DiRd}b|k7U+fVAoEImoYOVlU2J9Pd@EQ7fc*)G zeGaVG;qjg0JNH*)xYl#7=1h+!-&D;&04g<|1*JZI@z*o=agkMTOV}g;0iwnkyA(YY z4UuDi?Z=sy%z>ias6nNN5Dc{f0SE#gSNK^|9bjyNK-wCKtNSz=B=!aSqu`hgwh`0@ zs>mta$<7l=6qcX3qyP;H3DTflUWHf7{PZ6PyVltlF*L-$h*8Ea#XJEPJNcgDI9CKCTbG|z@-G4U*e!txP7}t1x}4Nb(AasL1xBM?&=ECdatKfuhWADYjvQ0O&wcb%2j5{EQhT zkiJIZnz%40OT)nN6vWH|u@5EQzsN2~B`tR?=WEZJ&A(9{0%=f|Pi3)3rcYnUNRqhY zmW%i%iVRjwRC&)(i;1Zvmrt$`KlyKazN4lGyPe(ok?b z4KYuHcopSx2|0y3aJn{fy290=P^Liu&IdJ~J?|q=4f4u7EAK6_B6MJL{>#D!(Nh|C z2`o*H@$LB4_HP)S0s)XUorNBmA7qtjlSMXtC=nfHJ|0p$IOc+5CJ0yHRjT>n)DALS z>v4O2yXdq|gFy36;P#sZoNFbn>)*+s&Ac=ofTD_%WVuuLHg6-o{1L7P6pr8l-8F z968)VMv{%e13U4pO2ff1!)S4YD*(v%9A~e48++X;)YMgmoYpB2S<_ikCbQRPoLCz_ zkg!La2oz%VjjKU2J9F1kSoV9nAxwk(scOtC^>IoKvUI-58q(0VIA()zC7P~rtT@M1 zl{W?QlV4>~sf-q-leSXg;rlyK)myYEV4=9GkbS3ClX-HB>AtH&APs)_xWdnnK^8KS zBDgu!Wh<1V8;v4&p=mUXo8D!eaM&QSrt@-{OqNgOfpG%cK9lGj+YUsC)i;d$ew)TM zqp(2EUhqRfxPFl0!7;0`8LSOdk>lDBwsFmEF*ndXkNVks-E0y1_VgVrGIE$Jwe=Waqxe?vtA-sV(lVyjv+G zNI=(lz06O#SK;n40^h7^@f_4*^_5_t$KQ)vf&5c%_#rnCO}b$lMI z1B^Svp_l#_FZj(s4%OB3VyO?WsFS=xV$L)X5FzRwWA*Kc)YR4RqdTozjcZ1YAYWZ* z!TVlqA#=5jI}@&jnCTEZA5H9U828}JOJZCzvMLsxWgb5}y$W}Y7TB@jUi!tq0MQR| zwz`PytkPSGBmd5M?TA3ei|U9ZoYJ&(jWlW@9{ v{};wJBg>kM74Aa;XLWw*uX0Z((;58>TR-VpkU%##00000NkvXXu0mjfZp;R4 diff --git a/Passepartout/App/macOS/Flags.xcassets/hk.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/hk.imageset/Contents.json deleted file mode 100644 index 60b14d75..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/hk.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "hk@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "hk@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/hk.imageset/hk@2x.png b/Passepartout/App/macOS/Flags.xcassets/hk.imageset/hk@2x.png deleted file mode 100644 index 975e73b0c6c381a5a6b29b4c92b468ef7c7d5280..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 689 zcmV;i0#5yjP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0%l1>K~zYI?bg3b8*v!N@%LSFu`!7?2@LLybPEsdv z5ISWsgW#%vfkQUIs1VU1P$Ywc1xW-Qq|y#Ws!*30n>4nD#7d&cMBfT(z#fS+>!dZ6|cy>(NWB9H`(oNjLpqh+S)2cXoBcWreB_|Jw05cQq;7!qZEr= zq|;bD9&Ek6R5dnghN#{e_xwCMvl-E7B)PaqDVs$pmnj?`axgi`vv8QZot>92D+)&o z3w-eTP>MxW{Z+4s!QsH(*N4&R#634hc54e1g?9r3IEIF(YHFg8NKi;7`L?@DQ&UJ*EAc>pxBdMTkB_<2(15wMm802NzOJpQh6rkB zjiA%f8VXTAJWL^xK+fm+6b$lZb(LzDi~BwwC+q8+CzBN7aWb)(dIUsOpAkitc|S5j za(S8Bt}f)WGt!X=w%%THJ3EL51J3brK8M33Lm}0O9~iCESF+oA9*JP_c(_QV5cGQX z#>RNMv_vVF!%$z(rqf9wkx-AQWcogwn4re%Mb70w6w%k#qBEJ$na!6lQN-f)YHh3S z%W9~rqi%4Ja;bzMNf;dt_WXV_u^7cnhH94!0QvOv-$DdQA`^{L)6v0sB0)S5;3OIa z;7xZoPl7=tn~luore=udGxEEbT3V3vc?$8kHiVX2qb6tj`xV#TjQ^3pnOn%!{5O#A XGGEP#cP4i#00000NkvXXu0mjfusuQ6 diff --git a/Passepartout/App/macOS/Flags.xcassets/hk.imageset/hk@3x.png b/Passepartout/App/macOS/Flags.xcassets/hk.imageset/hk@3x.png deleted file mode 100644 index 228615a8c8c5639a5cd0470f571e15a4a0b4336a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1135 zcmV-#1d#iQP)|)8By!yysilypi@XTB zG$JnwioUxO1d;Hnguu|7E)s%5hCm{BiD>)4U1*a{cW`SnM;&)&%u#nn=Qy5o_F~$h z*qhy%Gbu68<;&s!`#i<61YN1-i9vKr1pL>y3=adLtw9U$8VZ z;Oy^b_rwHw_4VnD!jP`QB`qyf_4FX<^#DjdU%H}zG_8o);lR<|osa-1J$*VIQ3=u_ zQg-<=I#DDR3Ndx#1_0Lm`_mDXAT1)crX~PZ=jSOoaUwpbs_BSIkkTHpG&C^(?Aa${ zf=-9Apa99|!FejT03^hq66mKnct zg+JQbQd+g7MRKaE`S#VTe~o*)x)4N>{C)c{@Bikh1l@9030B>ZkSu~3N8GiL~U zJY<)bW8S_Ub7du}B(V|+_4OE-Vmmy8*BrJ`5j@TBFs9&Sc`ZmKNlN z1vWaJjJLP*bA3HOmX;>=lxVkOX=p%I6vob!Bewl$H_; zg^(sEF_o7y|NJ?l$Bv=OGAIhUb#+)=E+oI7+57h?yKn((Vh+?^GCGqfaX?gFyN3VH9pp$vix#xI zG!`B@MD4wMAP9-Eg^>qH;38b zK(yQOc6G553=;8p;twkd!iQB8K3ct0Nm)d++u70A7k{M$fh}jxA{G`R==IF>_3@^) z6#&nvQ^=7B=Bg^hO`8B%eDNaH_yj47toZ#*T)j%@@naN8LJo&{KRC!Q2M#dO+??3l z^G~1h)2>~-fA9c+zlMi3`F{L^Cdcpp3N#p~d-8-|j~pR7HKkqM&n*I=&&fd!hc&Ia zCVx@1mVnIfp!G*aWWA9QS#NwXA_m~?S~sBue*?gHfuDo?+57+i002ovPDHLkV1kUF B87lw) diff --git a/Passepartout/App/macOS/Flags.xcassets/hm.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/hm.imageset/Contents.json deleted file mode 100644 index cca51f0b..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/hm.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "hm@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "hm@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/hm.imageset/hm@2x.png b/Passepartout/App/macOS/Flags.xcassets/hm.imageset/hm@2x.png deleted file mode 100644 index d2f46c4ff6535a931f334d139b76fe609b359c5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1293 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 JPDHLkV1m_`)cpVe diff --git a/Passepartout/App/macOS/Flags.xcassets/hn.imageset/hn@3x.png b/Passepartout/App/macOS/Flags.xcassets/hn.imageset/hn@3x.png deleted file mode 100644 index 382c0cf1ba9a6f0b263e14ecadafff3d276954f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 545 zcmV++0^a?JP)l#YQ(F2e;=~h*wC|%(&hzVSmju`#$qLyz|bm z28#zd8UTT0G-wlk&=j-*zlef_jVMUih=PQTC`j07m>^=TFCMf9UofPUQm4=la#Bn_)*KBr=Zb@a2DO^^yH-DsXStXS{X1jR9@<^T0=}g!=K)nvq|wM@K^ZTVLWw zsZ5gr6a6n=Dk_?_&>r@BSj{`E>^VpYU#Bg;j9Txtm3|QK4n!Rj*1B8(36*TxH_cjT z-&#xGFJArTy5g}j*UQd)FV~gV2e=;1=TPp5Q}&qMIA`zVmZ7A{dZvfL&X~3Vfa29} zW;f0_yr?pjYGXaqt^A%Scf^VdGQ;f%!(b?BzBvTIU}ubUSBwwM1~bF$dXJ*iAZNSs z@12zUC0D1sCi|ra&77^L`L4It|3r*`vxD~F3nF(~4%&k+G|c}<6eMg!LBd89By2=M j!bZac5g6oyHsJ^NK*mgfJ}k~x00000NkvXXu0mjfZw~yS diff --git a/Passepartout/App/macOS/Flags.xcassets/hr.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/hr.imageset/Contents.json deleted file mode 100644 index b170577c..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/hr.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "hr@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "hr@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/hr.imageset/hr@2x.png b/Passepartout/App/macOS/Flags.xcassets/hr.imageset/hr@2x.png deleted file mode 100644 index ad56e1049c6dca3c2cddf85a2f571f07f3bd371f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 760 zcmVX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0oCP(B6)8bahlJ;dsv$VoovK)f|Y%u7uX(YfiIi;>$jm;8G;LD18VR4Vj^ z!@2wpU%uyie&>SUYzb?iZD(0ATf#By5bS;-!NbP@SS&8bQ(O+f>d&#QKqoUfke$o( ztBsWOchNkUKxyR#UYs)0GguN(iAT z3c>JRrVZJ&pE*Tw$j$YJOYHHuqY~jaTh^5E=GWfi`b{-jYB z$`Jqx3Lqr~91d_gp|%zx5om3NU=Zr+AR|MXR$fSDC74W5Rt7yi;Pr}eqY)}9psNdp zhyQ^PrxRvpAvYJ&(x9^wbULW20#ya8RX9{sB*(ku9O>>BeWOv+^!jbck(?Z{`iTkf z`M_ckZ>ZG@p^%7WWl7V@J7Tkm+H4l9_j)zW=M(MnauMt3kauKcWciEVzm^`Y@HC$e zlidz^dB1W*QG^o)1K902mUMXE%|va++}x^PQPmc!qG%UoTzpnK&*{b*ag>clu{F!f zFgi+m<4dmeACjR1u*N1Po~>3Cq~7m03(3s)e5T-={00001bfE(UiCtrS~Z#6n6Wq^8x@UY1gbhL9#U zHLVeiE3r~z4aP{H6yt+wVvO+tO%`cdOZ!qutwk{?NtZ^+5@|?nOQCXER?5n)Tvmkc zvU_}(;&`dCItL+y@9E38=gj~A&H2vRlTm^XZ4I*kpa>9i-GmINbKQU}EE<__m=z-; zap`Y-c;i-vHf_7*Z!#fev)@>gPHe9qrl#=jDdiPk7O$oS$g%}^+*!iJG@SN+!0>b; zZ(X^ULn|)etbPY#W4W<%6|tQ^%)Jp2AJ2C81%g7O*xy9t-73<4_K^}9C(%?Jb+0{v5)7I$VmewU%G~&S8p-|fY06^O5(-B- zZrehU{RP5~OuC{Ow1yu*Xe)U3sZU5>_8p<{2ybZVsDo!29c-rR%?h(^m^YF<)Jv~z z9qv00F&<+jn3BexNiT=TJ|aF4!5;aERZ2IilEm1gjgr@!aplMHdCU9kNPQCD@G6KUwk_FYeEp^AXi3@ZN8UTh%hm_6$7nn^d5x^bZ^^jcM)H^s+vpG|3X}Gw1njH1 zlCYMR9ozX>TTV)x!rQ}Ma$eeNwgnWCmuK3@)SdBoHs`SUtgRYSje)fg)Ya-wCifTH||>MCfO zoED@!#o>UO8km~;Skm>%8@ZWEks+J&j0SzDk?Jc&Q4ux zZ51LMmePWp*P&_9+WPP3wYTfBlmKPduG3P!Yb8V{sK^~bJ?GI+C4h?;p|)1%1qG0q zsq=~oqS-m@t9LM>#K|a+rLnP3%7Xy*`NyezYz<0Yo-XwELVdl?3k%_#7s6b8?v+~I_|L*I!HmTgN zjf1Ar5xR>bO;PIl9kWqx_Kf`BSTJ|3MI-YKi$>-f7LCj|Sb&DPZbAlr1*24M6!gEM QPyhe`07*qoM6N<$f-Tt`MF0Q* diff --git a/Passepartout/App/macOS/Flags.xcassets/ht.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ht.imageset/Contents.json deleted file mode 100644 index 02362ebd..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ht.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ht@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ht@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ht.imageset/ht@2x.png b/Passepartout/App/macOS/Flags.xcassets/ht.imageset/ht@2x.png deleted file mode 100644 index cf8db38bef5a18146af64f4d60880835dbd24b66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 436 zcmV;l0ZaagP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0clA@K~zYI?bSO-!%!4};lE8R5)}nes4r+s!KH)6E-Hv3xHt$RicSs= z9o^jAbaNJ02T>$ywS z-$MsRmVn>65PL4fo(r*8HxN~3XnSJ_tx=^SD!jhyRID;xodFb&f?2d!UQIgQ zDRm-qQvnvIgM=a<+$#H|8xILbK3SX&GB*{FSt)lS0LDv}mgjHQ#^>=W8b>ZPQEVD9jhp774x(+ei{lLu9Eb+pHBSfnxz>MJsw@^YIajQoSGN-RYB(QgsLTY>kp$B z&*)3HBlAk6AgQYq(clQCX;SJKMNzzHr2^3=jibal@&AG>Z)Sju%;=xh%?Hhe*mEKF eT!_88f&2g_)OGWM((|JL0000y4rm(~I3<^GpY3wt zJnzGMhGUtI-KsJG*p|h(uirz+z}oKtWMPIF4Ku`Om?1{P3^5vJh|w6B5Sz@y(sxhc z^V)a5RZXE)ZlSc>jKu@kg=6Pm?T^aExp&gsQbT4>h6(t$hb`7i+<5(fa=nPqbo|vY z*U!!o^aKDX{Z?eEck62Dc%5#Jh7O?#f$nf|EI7lk-Mu?HByVIJ9j%q;%d;0;x_pJx z@gX!Z!es0W?;kzncX$ zsSur+WMc6q`{E7!Rx7>M6m%>c4t+zluhQ&#i5H5?DsX^Gd7V~m6VcPL z`zG*@NBGrPXQQ5HHku%HwIE9^cOzTM2+2qSuhT=eP$1-v5RB|a*aS_rL3Xo%%Q;Hi z?c;m4K}w=q>PGs>z`V805Tjv+7!5PTXqX{J!wfMR0~2Bc-u8P48T|gW!U_%O?XxT0C7G zLp(Z@_e_`j@#FT+#YQ~L96Nf}${X`8VV-bsh4Td%W_-Nd+4}px|6dPhI>a74wr_gt z^=oh6Hdrw+-Q%0JLb8;@N&cDFA^YFqhVsBAVcjGY3|-TlXQXZVeoYIb6Mw<&;$VQD^hR( diff --git a/Passepartout/App/macOS/Flags.xcassets/hu.imageset/hu@3x.png b/Passepartout/App/macOS/Flags.xcassets/hu.imageset/hu@3x.png deleted file mode 100644 index 06f50b6bfa2ae384899e22158965a23ce9d856d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvE>9Q7 zkcif|*B0_NFz~npdaVl32ss;{2xHo~}=4Enn{8 zu>bRl#qJdvi`gp@Ii%VafC*3GMWN;{`2~t;X&04kmRRaS#leyjQ)PqyMD4mC{e?|D t)2~}GL!zk-M5y_8Z1er^*8kxHbEU8V@XgNrC_X diff --git a/Passepartout/App/macOS/Flags.xcassets/id.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/id.imageset/Contents.json deleted file mode 100644 index 72af7398..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/id.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "id@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "id@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/id.imageset/id@2x.png b/Passepartout/App/macOS/Flags.xcassets/id.imageset/id@2x.png deleted file mode 100644 index 9a25f4e978b4482dc291f2f22bf8c30225601264..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?XxB0XIk zLp(a)UR@~IU?9SB@wcQ%7vtnZ8cgnqnmq1W33gmZOa(TtZz`3ZK27cDx{XniG9Q$W z2tMK{Y_V{JuCg|OF`jvmzkGII3;uR zDw?!ifHOkMe+zi>EHrF6CLraU0cX5Zc@NZLw=;}mhltFdoj|)7JYD@<);T3K0RY4I BI*|gW!U_%O?Xx5mdKI;Vst09ZjZa{vGU diff --git a/Passepartout/App/macOS/Flags.xcassets/ie.imageset/ie@3x.png b/Passepartout/App/macOS/Flags.xcassets/ie.imageset/ie@3x.png deleted file mode 100644 index deaf1c57d0b6dbb27279a4ba4bf3d971035dcc35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvMV>B> zArY-_Zz}RNJBYMAT+-*IwY)F7NZd4UDBL?4{1kpEUEt^0(f+Tb2hf?J$!5vHpNjpfLNZs1D~V y7B0O+5l=SmQcY73dCt;M-6rSU?7gIJrnR-A@Wzr~kwC{XFnGH9xvXX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0v}04K~zYI?U%7jTTv9ozZ>$BkmS))w2zozw9*7RbO{C*^F&2l`xl6t zf}7&vC=P<74iB;hbx zISCO8DNPX35lQIeCj$Tguh)g0od`T0_Rroz6`7t^(Q0uV9=-!( zK(Y7%h7m+1Mx*<`Hbg{O zDUf=-OOeP0rPDuTAc#a;^HhU_ZWu-s^YgD{Yybdm!)OdOn>{I$%gYC;d{uR6ES36l zT`061Jp+(cJ??k_T1C^DLg+?_45&!@I07*qoM6N<$ Eg4tUSF#rGn diff --git a/Passepartout/App/macOS/Flags.xcassets/il.imageset/il@3x.png b/Passepartout/App/macOS/Flags.xcassets/il.imageset/il@3x.png deleted file mode 100644 index 0f5ce63e267ee6369e70fc0e6268769c163986b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 836 zcmV-K1H1f*P)E?9do;v%wRFYym*^)p1V-D=5krg@7$TI`D{*|zV$rM z`Tf4XZ|58p$rZcuX6#82}0gF`Jz@KE8w5>{L?b2`a?N$sNIcC)Xoc`-8L3c2TY2Vt=U(9obqVxkJN z49w4aP+FRd+}wna+M1dV=;|_rV*!AGQ%jPFl9GKGjVWks)FT)KT3a13nX*w_to>bQ zv)y8F@Emh?G+(EUlVo^AzN#8^@ce)JM?-b&dglWXgqka&S*T~ z__&Mr_cA|!-sRBHxd?fZ0(+#s{+PYJKbe)a!_7_Ki#1zYei{rm_V=GMFYhb;eo2(P zxFB}>9pmFIY;SkGTG!e6oxnEd=iQ>DNs_|m$Wy2B%_*Lg6c=(8hNve}S9i>|HV5^3 zD{Z#Gi!~b?J|-qw*wNu&Vc|DEJjkNt#cZvWm1|~X*ywf#X)>L&vhwg*&EuoYl9E04 z^_?;hkXTT#%jxM05%MGj%#mR5>G-X#)}ggE4L+ZQ+FCn?hqF;umI94N1pruB@L*!% z0!vGupwq=;anXaRsY@Il79le;;SH6y2|LKr(ltz`^N@R9uY{wcdl(FAXti-50jKi; zg@sAT$_h2z(vphq?(}dhJRxjt|Dd_q&Vhk5lH}y1i{<49l*P5gGZYKraNJR+vvPg? zhN-F6zs?p>C>Dey2L{fl(OB5i^Fv7)k^-9jHGDo9Wn~9gU;l#ibhT140RF+NuE!%m zua8$!0sx>M8*{x~^&b%X(im;TfX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0}4q*K~zYI?bcsNQ&Aki@$cQ1UbpF-eeeaVP%mP5@O}ByjVn2r;HMtbGuG=w;lpBDczhFLBGfI zyXW`&aL+yW9HA8&1+efJLx88MzX_Nf{~>c@UdV*e_{(v>ATV84#^j|#Oqxu;d`3}+ z2hsd45d@~KR@@KjiG9-d9hRMqv805Rg@wp!2O8A`!$XNsQ}ff@ud6|SG706=0o;Q& zh)Y4VI}u;J2p=KOgq~2baM9YCC*^L;c(z{G!dV5ge|AG@$&XA@8UEYt!BVb^H825~hGpv)QfeI8-L?EpZIdxd?&s+pWn zRK%LxTzn2kNQjUi8bIF#(ejTmzaS7_Ue3qfI$CaRV&H=lOWPY_s;cm&rm`Y87oX3^ z7n^PAAaf%KXilG|=wdaSw^rftCbRF@6?}?9dTA+ZTUrRDG~)Go@ppDEA7paZF1A-! zW4GHe7z{*tGkJESmROw*v)N2uem>@PR;FC{QK>>Nzu?FE6Cy87IdP>%8CjCSsKQ7gChK42tagmGkrUEl8})B)*6Co@kHPVZ)xU+c6gZg z-QCDTLm01)qY5s*Rzw#gcXTw<@ixS3S5b#}rYs9&Ix`dXBOiK~Cn5wyxHJip1dz|BoX~GNt^!f|&00000NkvXXu0mjfu&Rdf diff --git a/Passepartout/App/macOS/Flags.xcassets/im.imageset/im@3x.png b/Passepartout/App/macOS/Flags.xcassets/im.imageset/im@3x.png deleted file mode 100644 index 57bfb8e4e488072191684ed7ecfefd5f5effd439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1481 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 Hu0mjfr`OBB diff --git a/Passepartout/App/macOS/Flags.xcassets/is.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/is.imageset/Contents.json deleted file mode 100644 index 3ccfe7ce..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/is.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "is@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "is@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/is.imageset/is@2x.png b/Passepartout/App/macOS/Flags.xcassets/is.imageset/is@2x.png deleted file mode 100644 index 2d24e40392ec564f07338f7a3e6105fde3abfdf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 403 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O^81FnV~p zIEHw1CZ}wev9YW1VZ8=37^t9!G!pIa}r-Fm$hB_*4(m1h&jwK z@UTc$Lty)YW*BySEFflG%UNe;mUf71rr}*3-BV{DBxyNsRIdB`@AUagGqP4aP0(?j z(x#tL>mDnXbe;e6UEDK8x5M}O3-$U|Aueu zKY#gHuXOPAw{QQio0|NKzkcDz|F?fCleP-7{623}dnCyI_ZQP`t!G#!|7-K(=h^u7 zxV+&P-Tf|w9e;lP{VsKSVYj}Wk-;DJ6+h13ua`*jkFQU?QT&VZC|_bi!VilKt}QU1 dgJ_`d8J_cAaZ?Zv*#Qh?22WQ%mvv4FO#m9cy3zmu diff --git a/Passepartout/App/macOS/Flags.xcassets/is.imageset/is@3x.png b/Passepartout/App/macOS/Flags.xcassets/is.imageset/is@3x.png deleted file mode 100644 index a7a5481cab95f98c000e3267426a8cfca4cfef2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmV;u0Yd(XP)esK!| z2iq&f;~k^K9`Bgayi|YpgM#LNpPV_i;|(`YZyqSw6c~UFO5>b5gIZO?*K2@o`##)P zbR9-jQ+l5+sM!?cqz;N3A;KF%gg1l;ZwL|I5F)&h8WFa%kRvq~^4W>?j4XkgO@Tc( z_hJd2&u0-=_Fu4(ZAS|5U3Y99_&%a$Qjnf1l_2p`V<0udE<|`k8d_GQ8R?rQ-s<&m zJzFkgvC)Wpv1wtMT(q}dR@Je!B+H1JNrCCRSER=Mt!11at^Hg7um-3dUQ_QiHSDEJ zd-LC|!NmH=4?xYPkeXo^BD^6)cteQrh7jQmA;KG}5n%vKN@L&kimgts#B7Nv#%lO2 n?-Vq@)#*95;|&2M_A~ScTJT*7GTsjn00000NkvXXu0mjfHFv?O diff --git a/Passepartout/App/macOS/Flags.xcassets/it.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/it.imageset/Contents.json deleted file mode 100644 index c59fcb30..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/it.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "it@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "it@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/it.imageset/it@2x.png b/Passepartout/App/macOS/Flags.xcassets/it.imageset/it@2x.png deleted file mode 100644 index 4b09031406a2be5f61c7171fb5e055120b46f196..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx5=@FEu!Hs#H{OC;Z(L|e?#1kxZM0<8~@QZeoAMC z_jLung@qiw&KWXJj|HUKFIco3Q#8RUr1|QjjyCg3^Am!Kw$9dz6R*aeSsMRLzPVPs Va?yjIvOp&@c)I$ztaD0e0sxy8Vjchh diff --git a/Passepartout/App/macOS/Flags.xcassets/je.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/je.imageset/Contents.json deleted file mode 100644 index 2ce97cf8..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/je.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "je@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "je@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/je.imageset/je@2x.png b/Passepartout/App/macOS/Flags.xcassets/je.imageset/je@2x.png deleted file mode 100644 index 90ff19c08eff393634b5af3fe4bc1067d2bea6b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1257 zcmV=P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1fEGmK~zYIy_aijlVudgfA8C_-Ae7+j&a#QNW{pZxPT5Ld?2EcL@tU( zV2LIuiV%WAfUz+Zgt4&=3>OC>T+|@p@E0oeL#Q(?hJm);W=Q(}PIhEA5c14$&k-`9_Dy<+w=#7sKGz6N*i|m;EdWvV) z0fZV(+EEA%rMr%B$c^HzCg?hb8tO*z{euw6$3Y0ZhpTCsI18z9*fT-|6;j5G#$H}X z+6$ACis5stK*%;Ur5RbLN77}XsV$gCR3+uF{B;#)K`DWA7XhLmy)sRkf_-x_`s`r| z1Nm?Kitnc_a85xn`;o_5uufl$arDfDeww=r*QT9#_8x+;7O~5jdn;7^sO;!XPuAdg zem0(}uMr{j{x`blr?dVF(i8RghBeYz|2Il)cLD+p?>FDlk~ag-?t>8s*_KYmmXDE5 z)2C|Le;miQb{X;@Q;(0g8|Noma7>zw|F_@c#?`xg=&sB7j-?U&GnLdc7Bp9TeCW$p zXnkcq?H{fq+}_z^lqAe^XOeyXdmedb4l;n0Y9#%mcgZ^c9p<^wrwujy%dIyS(U!jy zwYfDGWato-ks8L=0+=Q!NaMOO4tYHG5nLXeMddhNoJX+sQp9e^&0+Y-0}S4^5#3;0 z50?-k2pIYX2%N5^V|5v!s|^vGWJ)7_>3q_cEkH6F&~Dx4#_XvWbYX6r6fAA2j68l4 z-KgxIG#ver&QCW-U*Jv}tg9Abc|RX?atw4_5C9QU>DqS$S6LNWmp5Whd2AH+@*>iv zPNHS)62e_S;|XbGroY6{lD#OG>gXsaC3xX-G?*-7esemul2zz3tZ|Z;0Hjypo44uw zY%AWQKY$PsyJ^}KY$Yq{E-FWaLRhLwaPRmE&z?h|MZX5)_$RSf7Gs?7)cuY~4y0Fs zvlno#E<>ri7QN^o6X`1#A{mXi%6FoQ zsNv?T3+{n{o*#JI+skp4??Nb{e#K4tMM4ctIEyynKXs;$w}d%=7W$D{xVP;`1cH$z zL(UlN6>CVH{OrR45&i(~?O)>Fx*rkjbI`c4WKj~>!n441!$WQafd#03d8G~p~P!~e_K zK077Mb7o*$zXDyRJ>l&l#c<;ev~FL-o@msF0g0wzAALsyLv9Wk6>Bi&24_KOOmj=r8~UTb=~_ApN)x6_QtGcO&cgDPn0B8;Z|JY? zo31;(OKQx@6T%;eEEw~0$*3&G@buV+0&us~=2o1=mH4Vp#n>aGqHMQfUHJi)h4~;4 zJPpa}o>8MQd!pzl+$6#-kNCf(1%kr$`5w`jJyA$X-8Cg8XUu^A*E|fV&8_?gLFlY8 TP|eH(00000NkvXXu0mjfe~e&V diff --git a/Passepartout/App/macOS/Flags.xcassets/je.imageset/je@3x.png b/Passepartout/App/macOS/Flags.xcassets/je.imageset/je@3x.png deleted file mode 100644 index 6de40bf1b7a7c5ab6d939459401300f5ca71edea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1796 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 N002ovPDHLkV1k)2rDgyC diff --git a/Passepartout/App/macOS/Flags.xcassets/jm.imageset/jm@3x.png b/Passepartout/App/macOS/Flags.xcassets/jm.imageset/jm@3x.png deleted file mode 100644 index e7f649b9bbca2ca90ae05430cee6915eda577ede..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 730 zcmV<00ww*4P)f{x>c7C|LL4l{0TTwx z!hmseQ6dWrguorQVM&f96Jl5Iz{ju7LBj^TAA*Tf>XURM-4AaEf_{nktn!Lx8GiJX9KlNywah}SMA5~fRu{GaS#&xwt!yoh`SzDTyogXiGQ zbC|dlCX;}L=-n_s4Xcmk?~rT}Qd@9k3l_dR#~D4a{K&qa?E8U?e{+fQ*Do6VuB}Qn zm-}hz*Itp?SVeoQt6C*DRHzo2&q2}pK-X@S+z6tYmP}xEN+mb^s1}{y3bG=yMYxgc zToGVDdt_U_R*M@Bi$=2s_X{w-+xq$7g|cOX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0eMM8K~zYI#nZn_0%07-@weg+JSi9HTpAo|@(++`kO&%G8fhg+aEh+80Ch|{305T`(fA$Eg2qeP;;(`+7CE$|tM z%to1v_Lj?Ob48h3sWBJ~9*|V3DO#4uR zbDnFaYbbt2^V}cOO|{uKyScv$*QaoE0uzHTTsOk)=F!*<_!Ywty8wnEb_2FToC4@V uoCd5x+yZ<*v- diff --git a/Passepartout/App/macOS/Flags.xcassets/jo.imageset/jo@3x.png b/Passepartout/App/macOS/Flags.xcassets/jo.imageset/jo@3x.png deleted file mode 100644 index 33e4251e4c6b23083da5705c65603d0b46b416e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 455 zcmV;&0XY7NP)2KhD)D9r!Iw-3U&~K&F4@rDAtm+xydzmuk>Yh-%O#h)U2lh$>JThzd{^h-lC*5RssLAfiBdKtzD@gp>&~2|xok zeR$sLsnwc4hT(MS`zuF}Sw~}XpU9sT1Z2NYE*5lEF^~d4#X=l{vO!Kx#Dda68V$1D zCWk|^*(AeI94P%nMk6wg$?=%<=>WAFP^|)S3`7y&c}`E$%%z@46p`I7 z+3Sg#ME0!}1nXu)_kUILyjd9lTP~0?0k*v$5&*WHAd&#KOc03xTPlcTfNd*81HiTs xq6uL81DXPOEO`I`002ovPDHLkV1gMH!7%^; diff --git a/Passepartout/App/macOS/Flags.xcassets/jp.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/jp.imageset/Contents.json deleted file mode 100644 index 7ed4886c..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/jp.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "jp@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "jp@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/jp.imageset/jp@2x.png b/Passepartout/App/macOS/Flags.xcassets/jp.imageset/jp@2x.png deleted file mode 100644 index a5c917345228f1cc48881ac1a1a5ebd0c0674580..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 478 zcmV<40U`d0P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0h38YK~zYI?be}B0$~`(@h|5M(7;gV=3QY|BnW}Rs1!QG=twjS{wD@! zV52z_ve5|y8UYG~ON??9CX9lx<1jd!;wJCGJ2CL9@B2LOC%o^T*RxzM%fJo$OhTl`t-zGr}J&iABk|5&vTl~;ZfbS_PBX9 zwq?;zrvn}3 z&rFlS{k>es%rqI7O2SQ789zTWGfjDr$;%5kj%x^x!=zG?2bs27?h&inc3U1~F&b?R zSqz8rAV=rtTSL@~3we;^SZr&EU$9mP63Apg*LoK~z|U?O07q8&MQJZ(b6TOr!y|s1quTk-AG(;s>}G$U^ZC=xjxQ zg-HH}>)_f&h(g8HB!almonS)gA`>eS3TYZi9A6iOZmN>p_d z_=5icOb8)H-GmBgqi#SI0waDyV8m|-4MWsxHqk1T(5=v50UgCDVqSk(W*fU)R>qxw?{SBAUs-bR4N< zq;4&DXGhuu06g8?z}?%E(jX_&xx7SeX$ep`s+o?1`06T&OHSnb{=QNZ0Pv8{lewfs zyvIk}PEA2PKa&f;+6?FT+-M*)IXN(wlt`;oQf>kOgy-Ss=_!dzGSa=fBNgjQyILjj zNJe^%2B}zI+OA9D(LQ#@NVH*UlZ=F?r%A>7(w>tqe7FjwAhd2q;X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0#HdrK~zYI?Uv6=6k!<0KQoL2Yf3UBVU~IbOQ%>0wjbcZ&4XwE044G0 zWxa@}5OE>srhkEp;!O~*b=lLVZ5sxpIbB z6cIT&5CG5f7#|-8pdN@h4l&1}zO+6uF+o0`S1k*wr^d1@aa~tz+m>mNOe7+iNJOSV znwy)&bzQM6OZn7jv$L~_TrPJ@1^(dRfR=O`XL*@RGc$+?&&S5t7#QG9clU42(T}oi zJ7Ek3gP#h%uVfSvR%0;$^gy7T?_67BaCB68P%(_W@nUk)c^8i>`L=-1z*WHC+bicc zvRPjD_tU9{K~qo9Wkb;ez+)g^0?DthSL7Q&83dpz5SL;{0K815oZl&dRHLduK2Yok zK+E7D03TB+-UDB13DN}AmEzrng^GM*EkW8i>_~lA7f|d-8#R74*EyV#hG4K5EG}~6 zI3O3MrZx+{e@?}8O@!6qVaR5wv#fI7X>H}^+*~#H$c8arc-ENP&nfx#clSBl7vjpo z3ST`BN*}mPUx@h2hXh&!M^vbkeadbymYyt~k8i16Xd`DvxF1+yx&zv`w*h$60e1r{ z481zdd}>~)Uif0TO?gH_T8PBk1ZZ&YHncx|02E_1NbV&GX(5u5RH~~ClJZj&i~<0j z33s|SE7sGb(S2RHvhx4PcZlQeIX1{i=|9NN`Uc$h`)R_htG56E002ovPDHLkV1fyR BFYEvS diff --git a/Passepartout/App/macOS/Flags.xcassets/ke.imageset/ke@3x.png b/Passepartout/App/macOS/Flags.xcassets/ke.imageset/ke@3x.png deleted file mode 100644 index ae26b27ed29c82e59b9fabde4c61700e5e674bb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1003 zcmVzyRc^i_n1Ws0+}-iIL+56(cs=$I~2U?k*k; zlJ4$q>g((O^Ss8!M!LGXG)e&SyVjwunwlC(Bog9wyZ8O}xy>dnn{8jbyu4iI=jWxW zs_M}C)}=xsfRD4FM4I`Pf)oWbWKK9Jg*UH8n+1 zQIVE3rr+;ZBLkQmA7|<1%RG5W$+l2wDI1H6oSB@Y#N(l-r$<%SoVj^ZmGEsWh9jK@ zAh*8070;$p02mt^EZn}$%%exDy2hAwqiDeo^pQV}+MQ0CA3p|Q^2!ymsT2bIY~^Ue z)FfL5CV)Ah8ptM-tjFUZvel7U;8$P?a4Bl58i|r`ZV&8mb*B-ZKJB_xA3} zIekEm22?vzVU3kLV$RxHoYu11nNxu-RE;!~Uoy=*Vx_IElw7>HFQ-XU80Do^27C*g zvA)}#P6{e3w?1c+NveTXbe|yrrf1I@J}iEW#W0>d1K^ZvyWCS{Wn_~{0Dd@s9#4~Q zPt3Y|_f!c};V>&#uVSRr*a`}^;^yjh_Jc3%G+Z2 z-W{Pk{v|WN&G3R3YTA0cRt|1_bAyKq56PHtrw!6<@*x`KLo`a-$a1F*GA2A&c)<0u z*D-Q>k3~CY@oNxWE4jw=`R638lh%?G;-zrm^#*_a@tU7%5=1>#vJKY$T%+Z53)7it z4S5WDb^ec3uU9i$IJ+X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0`f^jK~zYI?Uu`nTtyUzzq)nn-rM)~V=|fasF88NFv$$Y#TZZ!QQYX> zjT>Ew2!gKM2mBAjof2>(fYCbSAG!;=DSYo}QlRzR#+1(HjW})gh8u zh=idDU__rQ_+^ItY;@|bbmO(ZRGf_SmO z3oV16Bg!2~(eW8vh}i!v!ldGV01?nQlM|mTs4wSCzci$~QfK4CDG*R!&Y69EK>zz1 z!n3Hhrn^#iPVelWI;u&Qa_UcIOuaZ__03t5#e!4kwmEffo5pg+>RYq4p3bN*=Y+>A zlGC{}h?672gW8D#bxd>Qg%OwDZZrBbWc~f4%v=nSiJ|jZgY>Fm?VUCYA9TpBDx$fH z;qRdnh?^q_|30YGE>u)^jQTw85@}9=e(C6lE)cEd*X@vALk^O0so@ z-!S+sO|n>!Z~2%5Np@L5DbPbfc}rq)w-I)pn)HgI>PvRNix61ol3og2)4T7H?1rM)@)4dz#)hR&w&{G)p!-#V5Q3@aQ`)cY)Bm={ z@M1uEMUij1uPOIP3*D7E(=QM45=+_fkxk9qdp-Q7;oz5$t&f|SOfdQ@VDCcBIrU^9 zj2m|FW5j4pQ9Dr*FBA;_2#MxP(zSr}nj*Wd#=VibulI>T0Lq@E>Pw0ppMzgRgoNCk+P9)R4bLj&>;X1+2l&XxQ%00000NkvXX Hu0mjfau}NF diff --git a/Passepartout/App/macOS/Flags.xcassets/kg.imageset/kg@3x.png b/Passepartout/App/macOS/Flags.xcassets/kg.imageset/kg@3x.png deleted file mode 100644 index e1a0b5a945d3c03c88f53f3177c47df5ddbdac91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1529 zcmVU!D5jbKG)xp84Mw$WKI+ztJR`Qvp!yjI=Ie z+>}r|ZqYig;x?>(L7DPv{5e?h~YGAQr!xcowi z<~s(OlQD9jPH<79dZa*ZOVnvNoo7TTWY{|c)<3z7Yzq3m-s1E<8)-}#h)`NtSG?0l zb5g34g_WzL52fe>DLdYr!F}5#x~fup-D2*gB3U5)7muH+&TFLb#gOuS9*HZXN`ka0 z6W3+F7EF`WW!$$+-rqk={f*r9;1ec(eU;)aAEl5n^_80lE^0(qRQwqYeJG){$76A_ z&`~@cgLv;6WWIzs7Bl!rgVtFc`}Ux1w3yL+*TCEulQtzpBw>ywEFQ_@?DYV!cLaE6 z3{)qjbu`c7WS;0+=l`CLYoxF}z#NOoZHf`G^a*SJIUV{`&tY<N_a)>_!r!9F5OcG!DW@9M;nyyOHkHjsCGu< zA34HV4UtMHc1H2bKKf7shfDY$YRHDbea9rODwv}QSxaXAw>FnwD0N!sKo=QNiy6+{ z9?O5RSbQZJQ zH2rqSB>|J$sy^?|=*!+_Pmj7%q@=bQJqJO8 z+?EJqB%!$5XYrK+o1Uu@e5A2-%%X5xNPZ$h2$((O;OuSTozaQrRhm=AXWRd5bveeb zn1SGej(<)^Hbr}qeX)oG^Rkc169pS~p&6kJzhDYapvlYapvlDoDp~ fbP(N2A`|}t1|wc^-c?rr00000NkvXXu0mjfdR5#- diff --git a/Passepartout/App/macOS/Flags.xcassets/kh.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/kh.imageset/Contents.json deleted file mode 100644 index 423ac3ac..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/kh.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "kh@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "kh@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/kh.imageset/kh@2x.png b/Passepartout/App/macOS/Flags.xcassets/kh.imageset/kh@2x.png deleted file mode 100644 index 5ab5cc81f6d5c22e810a524cedb47be4fccbd9aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 689 zcmV;i0#5yjP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0%l1>K~zYI?bf|d6LB2J@%Q!a`h=8vm7+o=g`wCeJ_JD|(G?#_d;k*@ z65^nVE;>6J<$o{?E-pAbJ32X{A(fc`+fYu;|N^t0B{QkcA-0PRS z;Uu~L^(=6k{fxw{YhMAsqzS1_6H*)H`NF$+Zz;2{h_BvITqqg3hNKabcH~Xk=$-Fhl5p|S0#Vr62|E8jv(?Kckk0qTMulv_-(Kc`~FSTLmHMU zW4Cz8KDpwvKs3KGmkMkf_gTN-5JJAr3fr6Z;M!T2MwiOv2^}4ImgChnivBy zB9miy@_7=81dij-(Gy{9`U$B|^8^i(vao3zIs+d**VSJ)#QW+k;kz>&424MB4whx1 zX&TXJ6aZb<$!4=0X%7+ZiJ}MFa6NcgZ+-m`XXqq)I!!j0LzpJ2p5w>z3d_L&2th?R za9Jfvij0*^A_@h9=T5-;kN*x*KGuOWafQ~^GNsl4{^DBoAXh-rLf}*ohE<)}4h2QU zvsy-;xJ)^b!kzryI79%}oLJMFcQD{o819JN+L!VQATUH{sQC%CoL3b3nX~?!Ak|+<)^M(1g^c38~Fb XdB)izy{}BU00000NkvXXu0mjfG>kns diff --git a/Passepartout/App/macOS/Flags.xcassets/kh.imageset/kh@3x.png b/Passepartout/App/macOS/Flags.xcassets/kh.imageset/kh@3x.png deleted file mode 100644 index f391da82abeb3494823f3040aed5bf6739b598c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 990 zcmV<410np0P)L6P7SAXHivS|!xQ@xOGvjTP5_cQ}AdUAKqW4z?oy zq&>|0&Z~JIy))X`6?*dOPaFay@W!x33<7P~0tUs{$fz+kGHQ&Cj2bdk`gPbE27>sL zWi@OOgW$lo)~#fe+wp~iUgltpw1l8|@pbZ-&eA%_h4|;LH4qdao``L}5Mt`&2?Ve; zA0n114QjbA2GWu3^)(9XYhCfm={QrN5P?vLy;Je7eEx$qwx3eh3w?K_i6V3U3<3y$ zcOCg}Uld@?&5_RKsMqVnj?8v+t%)N2)@d{$i9A-DfqMTg$ldJ5>qUY?gdBi|v%KEDO^#0Z1egR4Nr**JWj81pw1Du`CPQDUr)& z$z`)trH=Q;oqnYBci4^Ze`MmlbDWIFab1_KJ9m*@vn|+lT~=3D0dO1#!!Qs+Ff%hl z+2dg`7DLlC{!2U}py*1a`^BEA`Ra-Tu@S{UVK1VDU|Edv>j$z z08+jC*8AKCqcu4S{%S?tZn3dZKRnlN9&A`4(?(e;3R{ z^I;{wSa^5X8U}*UlUEN#M-I(jl(CUfV{By97#kTiB=GaFMGS(!0dp@jG|%3nFaQ7m M07*qoM6N<$f|*{=w*UYD diff --git a/Passepartout/App/macOS/Flags.xcassets/ki.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ki.imageset/Contents.json deleted file mode 100644 index 103a7f8e..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ki.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ki@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ki@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ki.imageset/ki@2x.png b/Passepartout/App/macOS/Flags.xcassets/ki.imageset/ki@2x.png deleted file mode 100644 index 224aac6fd3eee3a6d3400ec977a24e8271adb166..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1613 zcmV-T2D15yP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1_DV$K~zYI#g|!ZTxS`EpZ_egd1kzfCvnDG#!E6;>a=$3CQZ_sG{r3t z+SHUls#HQ0gb;{au&6hH5J(L|N+N+oRfK@HiAxenL**9dG(&>`@iq~b&eQ&ysQq4{XEz@DLr6-#ZvyKZWe)*r6Ozs zeKdg)(?1;$u~tPbvZ7=wsQD%c>x9nFqUKt#R;6KfD`+|-{Ba`3r|zqV`vNii2{eC# zM3#cS%Ym>7GGER?H3{Mq3Fgkuquq*OOeOCN0$^o3Vv&$^Fc(`fQaZ8e1f)qQU2V?MC#)xLc8}l-B)Vl0uyqcikzi@3W};= zF-cfd34}mOlhB2Mxxj>+et$jQGl(I9o+e{1vLI{{k|d$XV)41E2!sGROGVC*p+LR6 zy!TiNfZ{F2_S{#HJ$Yy!T_^hPpE2U`WkOSt59^L+k&B8jlD(*jdPqfFbF;=+V`C!X zA4sWAr{v!Ln3Cb!Ab-*@84oexKTmk37a=5^tG3`S+=C>k=&2-x7aC9bIaP(X&qcdSDi*h)8pqh=ju0?T7OY+*K zk2!L-mB4J6$4YZKa&R~0-qlN^GvJMDo&4xTBXf~B`zlL0{MBmmU5-UfFsAYA3paSZ zp`F>t0(;9#IP^>fp_n!zT;Du)YC5z~>vr16cRA=Bo5jXH#A_F1wZH{s8er3cFeTi}FyO+y3(IE$rXsLy;u{vteF6^C5p|?Brbg07Agy zzI=|o^d!$$twWV1=Avx%okMR2|9bD=fa)^emp}{9MvjxvizFUz0^N zp1?m9BsbGey3I^9p5Sjc`k4;Ls44SOygGv`-9sbd$EU8fywlQG*)co`fDmwS=Ozw4 zvlXAmMR-BubW0zvp81f$so>(gYpd4r^Dpnh=W$^eaG_&>A0EHL&7r$Ksx0yCryk+! zpDDxZPG@L3M15QT;+_G>l3;In2^;fV9|;Z|uJdHr_kUye7QF6s{@&r|#PwT?$N8Vi zcR4t?a}z#~i{}0@es{T@cp`<%ZY8Pf%tzy7JFL9&>^8ja3{G6{=45j(|6QB&v-#fB zTXET~ocYIX>Y933NF+~+V`p32zqRM#GLy2Ds=l!q{&=;Mj^Qa*Ic7&{U9%orCx%=a*?DAt-P;&Ik@1uEXGg|JVE*$#h~G+s&3~00000 LNkvXXu0mjfpO6<> diff --git a/Passepartout/App/macOS/Flags.xcassets/ki.imageset/ki@3x.png b/Passepartout/App/macOS/Flags.xcassets/ki.imageset/ki@3x.png deleted file mode 100644 index 5eb820faac75324c84eaba59474ba576e772215e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2555 zcmVqx{0Ek|S5jqhfRujbzS=gs_gpn8$kbsO_3)>?hXREj-C*dh^q3bq^A~3=x4zG+P z38F0qM$ja@y&dbDP$J9%32t@~w6JQU1Z$%hGm=Q2o`R%+D_J3O!60}jh*=%MtkV&C zA}uF4Bi#=7$w>Vj*ij4XlmUiyGt2-tWBgtNyWPaBjerFhc89gt&9QY;h}ivAq@aa1 zGX?!v2vmV{q?^$0AofWe5lwt%5=z8Y1#*#woTDJ}93+>zXf-UnT7;r zL>uG5Hf_|Q8X`$TGHfhcAiD$zklZq|A_%p_&_;VO>kX_tmFT)aLiR~QG9t=dSTP&l z9Vy6O8CjCB0SOx$uq==jh?y2zvV@kY;vDF}Y%-8av_!a&03taGYMO$QB%?=6WCw(G z6V)N1DiThIjCSR3Ns`d7MBh>bYN;DxKq6l2`d(U1F&y)pcm_GqV>UwAXsSSwyL+dp zf`|^XB!O4pN>K?!EW!~RPDT6VM&B|b17$9v0h4ebh7b_5tR6{Rg;SM?7#21bZS_%N zEhZ!j>^{~;M@A$G=N+?g-8~Pbuo$!cETI*DC$e?pjg|8Xa7|i(Hg+PCFB!AxAfokS zudxnqgf@Bto<)!2C?D6Ion;Z-y@TlX%?Qh~ z?5pnz!C=S?w6@2Rl01qx$s@-J88O>`@sb{knRaGYxioJ@zy<&4`3wuj87^Ub<1HAP8F_r-MpglSp!(Q|$lFW=0X{WaKEM*6JMfxP?p zEQ+&JyGH`J*cRrEHQ(^|`n|MAILmlsSS&Fkz@=iH?>Uix%9OE(`PW_Da}ZtI`HGjqrBaK$j3s)AVb&MIs9_G7}d zx-TV7mHE-E+j+QR7)9AWR9WVDgP#?vK3Dq>U@cQaq ze7Nag+?i{U>T&Y3d&V$-^kDMSytIXNj@JA6WM>u2wjV=}nLXOdNOEz{s3HUlmzaQ* zm+IxAiecQ=FO!<4%Y3-$5KXPY9+j^`2)J`Z0e6=b;qz$hIMKjgw;hQabvYDreO^Mk z!TFZXvkEg)cxLWcin4rE)->|w7rSW<-FW=^+!{~M9?kgTT+aFftlC?{+5@%m3KAvH zeqjFmp0Uz*Cyzi=JIkRxqVx9py}bFwo^FIvJx;ziWdu*nDrZP;S{K>L^OyO>vdw(9 z`$XKxRb0*R3lC1h>+HO1HMIu$)rzlKy6GUsb=mT0Do@NPJC5P&o$x3!JXH*eCoim2Q%#;oyf7^D%df{JR zi<9RscT+CypUG>FOlS7U!uYzdZP-#($BUnCXXBAG-6JF5v1z5evS2DXK2I0fs=d`b zw`?=V>iymHW{fD{7Bx&(@2lbU)s<9MH+IvJCBePr#XK->NN2MHHKV?+Vak>S5=JMOkvTa z5%J2n5D4+!hW#wvbcoZ)ko>q@ z_}jwvl2w(wwQdh#y|d;tRp$Bm6+An4EN-pKwFW(A^3mo){QC2qoVnQbOL}p3D(^ft zt4o<)>&&YwEBVcuU0rHFGuh2c-x|7y^>Y8%Arxk%phz9f{`sDh)cM3>U z8xGv?wosJiV{Um7`Dsan^%zwRe%2j0Ma#EreN~paPOV!R3a|m}TmHw;{{R*<@>g<$ R%W41s002ovPDHLkV1jmx=~Dmz diff --git a/Passepartout/App/macOS/Flags.xcassets/km.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/km.imageset/Contents.json deleted file mode 100644 index 4b603c3c..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/km.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "km@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "km@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/km.imageset/km@2x.png b/Passepartout/App/macOS/Flags.xcassets/km.imageset/km@2x.png deleted file mode 100644 index 74e6ad276da14e8a41578935c8fd4324220038cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 892 zcmV-?1B3jDP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x12IWNK~zYI#g%JlQ)d{*f9H}WJ^uj?Kb*;i^RIEoosm3{5w#8|Y_GC&w6>72Dvsrud=LOt7bthkX7hz&9NpIBfsClZ zYi8RY%cd+HIjk-TL37-OB#0<-D&>yZRJRldkCc4UV$Yfzm>i5=kDMY|OwTSY%6ys6 z7L{T#D4e)|hU;xLwDh}EORIk<+Bt74UoGB&L6j2=GNUOb^LOX0reggurkia1bniC~ zm+YY+Jr@8~Qz>^;u;s$%IC~ls3?b#N}MaAMTXP4 z_yu*n@#kwIih{iHq8)&Lx*h=J^T4baz0G{Ztx!P6(z|yP*0JsA^ z{OmYAE<}ziOxgFHym!Wn9MLDj5LFc9$G?B2KkR2?{##_Hy+A}$=?@Q%Zqi`gpFX}l z)Jfi>q2~gK191&{*nRaNfk=?UC40%Yy~M%dPsz8QEzFtzmOuBP925;76 zO^?y2AWC~7mU=san&?;de}YVP3h^MifZ{5c@)Tk~TGLFX4j^bMSZOIjUJwOD7Izn9;T#rr=>u`x zWr1BbcVT~@-g(cN|9PHy&U?=LKS%iH?993?#)f8r&_+nvy?kKa^bS5*c%J0Qo{84u zHwu8?+otmPPq~y_NMc~375Rx`Xdw10`LH^jR}N>>+8s8Ln*2gBeByXZ4EYE0`2L^7 zkV?nH2!O3m#k(gm*l;+Tn<0|Uh(?Au))GVhkAvw4 zH8MnHb1eA>7EIc7gp}RO$GxWK&xkO8w($Ooi4kGFfe_^nUX4U*3}nTnqg86Cx%oGp zb{hawGzPZkY-V-J(x3?S#;CW}oY*u=ceevMV?A~PnHjF?m$15S@3W~qZMHW#1##igoH5mnSuj=?#Rqvs zEKAA@xOi9G64^Gl0HrWE^>f2fnr&?Ww05@>q0v)sx$3pxEz!#BWjp^%rz-%GM@BZ1 zXqZlJe3pFRm#dZXczya4>{;w}L2#wyS!Ur3x$UpEJW!F#MMY*FR8_9_`x*hnd zyrnSy2l zhV8XF?KbKx4XnCv30jqgdkix;*m#UJX)D>aU>gs_rg8Y%Z`hrEBNod%FR}GY6Y2HR z7>|y?;IV0Jyxl@g+c_2|&c_%x3xyzXrSm#krH1mRQ~Y77^(Yc1I$68d!t?vAgo~bg zv0yN2KcZLbcsb)~o}Bpzb=C%Up86KCPeO9I_fnbXB??N+L|cM)8;yid9il-Uj#{a9 z>pQ|kCu>TrEUL00OoH!5D(|N-_'JAE6OL9%d#af+Ss$CH%Y2(8uBSQ%NY9cOW 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/kp.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/kp.imageset/Contents.json deleted file mode 100644 index dc604660..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/kp.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "kp@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "kp@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/kp.imageset/kp@2x.png b/Passepartout/App/macOS/Flags.xcassets/kp.imageset/kp@2x.png deleted file mode 100644 index 8150ce682ebd593df5af61541413d0244df347a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 693 zcmV;m0!safP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0%}P_K~zYI?bg3b8*v!N@%LR$6E(4^ZPco5(b|?)Y!#`YgHf=Ht0*`r zA~;FWC4!*T!NtXYz+Ys;V*^uvf9@fVW@fn8(~~}r z2ZQX7jiJP1DQICKPi!{yt*yC(6qrmr7#+P*J6>8MF3W^Go^(x+Bpyyrs|Jy-7w&X+ zB1jVc*;#^4Cjild0|34Z4pLZIi6BYTOiXaMs|!)9B^iyX4=E@w2T`QjYDHUH%h%yy z5~rsC#Qgr${MhZ3wzZ`eC@Uit2xJZUXlZXEH5J&jZ_Q`A!sywUs?HeuiKp-VAUasV35Gv901Y7Ll6XBZ*EdQKTqTO zI^mriP!yDS9N*NGDhS9hBLKe1Npy8}7%UbV*VYg;8UPGtGcQ~&F3--`9~t4o>s1Ys zaUW3<2|oAracH#?-r5310U#NPaO!rmYq20NE~fxb~rqj7Z;YCtp1PD==C1SUD}`v@gtuOjD{ku^l9^;YI`sH4Jn&6H&j*sH@}Lv bkn8ygtm@B)tLU&d00000NkvXXu0mjfsdq1F diff --git a/Passepartout/App/macOS/Flags.xcassets/kp.imageset/kp@3x.png b/Passepartout/App/macOS/Flags.xcassets/kp.imageset/kp@3x.png deleted file mode 100644 index 3d45d78787f902d816eb91ebd34eaa3c9d20f8cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 937 zcmV;a16KTrP)7J6Hye0znhFnw25tE@lkEGhMu${!u?>ET;2m_PhGwEN;kLnZa_&mgYmX6jm7i}prCA)*f- zqRMhXZ|(W>IFBAR#!x4kL4+i+{_54DJyn(&3y1kRI?BY07e!|_T)T!an~ky5fo729 z`}Se;c#8JMpFZX1$Or(@2M>zQ*t}jGd-fQhs1MCmtNOqJ0H*r-=s$IeiKA15lekN0LvO1Y3%N%_QVO~SPVH9 zLrElZbE+)o>dTs%@I@lj`u*8+xZNezpGNy;SEv8*%?<~i&Q3PpyNBc~+)xThqQ0$- zEzh1|YiKBm=uBt^Nq_xXP+M{QILOwQfL{AUNk00}D(XK8slaoj_HEg3|zRt;FT+Rt6s?wLf@YK`^}rY z+rFJ>IE<1?74(%K$)2euc zKz!@$oc}>UT`;eM#9n`J-UnHH8()bm2B|a_Oc2St!5J&Jgc1A#-F+JIbZ56000000 LNkvXXu0mjfM!>`O diff --git a/Passepartout/App/macOS/Flags.xcassets/kr.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/kr.imageset/Contents.json deleted file mode 100644 index e51c6f44..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/kr.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "kr@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "kr@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/kr.imageset/kr@2x.png b/Passepartout/App/macOS/Flags.xcassets/kr.imageset/kr@2x.png deleted file mode 100644 index 4f4b1c5d67a7586df71ec2470e49a7da769d857e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1325 zcmV+|1=9M7P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1ma0VK~zYI#g=VMQ&$+r|GjMiMOg>3BEzA>87T`a1q>l(gja`}I51=r zP=dxUGa)n4EG!{}WKq#CSR+|74VjHLp&_A0QPE}}BqKs(>oT{X2D+3$H$t(th0?eG zJ}g#md&|I??4O(u_jx(@cb`j3{0E2^ru>(l< zGD4-Lr3Ao~loZHZ0*ywaR7h_W$jOr@34nqiu&Szx<>lq{cs#ES z;c~gy)YL?+R?F7bRu&f*(`vOY4`MVL34j3D(9po-;EYHaN)n z8#lP{=#gYhdwV+*5)ydl&K;^$Djq+6oW;e()MzxEpP!EgX=`gE0M^#lQV;}ocXu-@ zD~lBs74-Rh;+0;%&R_ELISh!#*n97B=E4Pr{QhukC=}x5%a`eLxo9vLs8A@_+1bg$ z!a|OXjYWgJNZPq`C)?WEq^QYIh?6ZXQ2>$Sw~7h|oX&9C($W$SA3n^SoE)Azca9p3 zhF-5%^l3!LS0~u zEzCo;FpsU*u3^K017W|-W`kO-MqFH6WU5giHKANC7eN4k{o=)CA%6Uz1Os_B@G1UT z=|_KzqWR*tB9Kj+HpR@3d9y~!anBsn_j~$>Fy((1h5&${GYip!Z3sSpzM}b83^L)0 z?K5ktLCB))zrLG?=T_UB1$m<0E@`*FwGp-rTO@q}L4ifc-z(ij61dFue6+^yhz$AF`{Kg%B9Rp`+Czn_jQS!-o%71~M`-0+Y#v zg9i^{U|=9@U!VFmzTB4$fuUvA^-_m21IR8f3O}38W*j+k1ZJ}tCX*>DUUZ>7efkvj z_4QCHl_)4EKu=E(hK7cao0}U>xcyx}x^7QkA#SZCfFkHe(>6JpzWE|-v)k<$8XCfb z2M;hZG6J{Tjnk)3qq4FRGMNl`^$*Qru^>A;8^?|v!_?Fis;jF}R#t|+d-q~yW+pu8 zCr3Yp;X)%0s28xs`xoMaKFC8ssJ#yCOPs^^UssAC91aKS>gte}mxq>?7I-`!?BBm1 zd-m+X*x1<16};N?PMkQwb?esg{{8!$n3&+%vuC+u#|~<>TDskC$u_^7{C+>n%F39V zo6C-l4qm-_l`fZyMMXvI>gp0TJrPJS7-UUN4Kp$_c;(6!3WC7%=g+gDp+Ve*S0SBF zN1aYbxm?aOXU_1{sZ*StosA6ZjcR&}i;E~NErrkLLv3v>rl+UT-QB%{?HOA)Z{Ec4 z@G$!O`;nHGhFiC8L8(-}8e)|Ed7hr0W@ct4`}+D`s|r#YA0OwgUAyRTIHcUs6?q|# j$AiSg#Q#)@Rmk`^?w9i>i|VBP00000NkvXXu0mjfGq-*t diff --git a/Passepartout/App/macOS/Flags.xcassets/kr.imageset/kr@3x.png b/Passepartout/App/macOS/Flags.xcassets/kr.imageset/kr@3x.png deleted file mode 100644 index 4e83a5d6bb927f605506c4760e7cf89f03b376fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2065 zcmV+s2=4cZP)>-?qP9DrFd(}c#f$?Hp&|>&rP$lE zA0{@EUhOd1WS-=FXwP&0eSUpU-k$egf#fax0wj>+|2xEZNaT(wHZ~Rm0|P&I7Q0fb z)i9gQu8nk|kdP1ppdbjYtmCf2!a@RIPEL+XuTzl3#6$uh07gbeGB-DuuU@?xx1f$R zI5^0iJ9kp6)iNL;fB<;x*fFPT9D;1!y44Ed@9%GYy?F8BaS#5XEEWrMa&icOixw@S zTCJu;B4JWe5<5FP9j>zra_ZD6D}-1qW^r*b0WdHykd2LvOiWDV_3PKi9ipYBg({Vb zO-)UViHRWqZrr$$wY9a>>-8KQ9OTodPu&A~^yrZlLJ$O&mzPr{5>YCZ^5)H(T(V>d z0Wdi^dE7y=va+n(xVgER!NI`Jer;MuchnVFeM0Gu&n24BB^?J}6Lr-#F>tsHD<;JcSEX)zd_`V9ty^^01#a3LES z8mymx?%X-9TD6J*n2?ab;o)J&Afch5R*3xkd@7Yn0$^cbA&ZKN2!KkZl5K5m4#Vi5 zKj#l$f6b=p)A=u8%xF+3_$)1rebv-j@Y4!@O3Z2;_z z|0E|SM%`U!#sHKp!G}CXf({p$>HeesAG`#s;;i4OeSM$YAQ7v&6vw; zPloPpwtn`RTL2%92NNc+zoy1+&CbqF`uqD+5Cm3KR4^kWgMuLNR4}>+9{-uBxiC?u5F!I#xT`k`9k3^XJcp zOeTY`uP?T2*@E4>ciZ*7$jrphg9omU5DsJ+9mTV?Yhku?1X{Ol9rE+@;pgWEi9~{B z%a%E;l=jXm{oW@H3lZJ7re|yM0K{{H!a^F5Tl6i z?uOXc*P|>RgIM%>JX9)S85wcQ-w_y&zb@Pfz0})gPHxoW_xJvSFG>$#;+i#{#BuK! zZ%_UAHEu(UddYDvaf@AuALl>8*~G1QSySg}9M3_XKJIYa+c4vE^m|Wm>KT#w;Eqo4 zG|o=|=^BDrBzDVNKjU+^d~IeQPvdwFBJ=aX_md)B`Z|=;(c>5FUQwyc(>R`kO#W36 z{u#9l7Qyj$FpI?ayK1f5HG_TJyTbP5F-Y8sBzzwffp6l|VG`N?#uy|rRLu0i_x`k+uKAQszR_TQ!_;?ad?82BUr_hzMd90CARC%%KvM1Pz2Efx#f+uIQy z?)+*w-CEC|KgYaz^N^R9XLmkv&0@q0y&n2xS5DZ%mM$L;#|AK8Imqvt&@iN%CJuAhSmQ-W;f&YU@e;^Jb6#bWsR z`Jt?=%wZk7AUAH@KuSsq_Uze%ii!$IBobt2XG5)4+js+%0r+$NT1+$zeKdsBVnkl* zRLoy%yDgtQd4iIX5_oxep|Y|P`}Xa_wr$&R;=~EpAS6*xP=LvkCu7#ES*WY4LseB3 zjvhS%Ji3J&dvW9s^YF!%FKy=Dym^D!vuES> z?c2C?=@PD9y^5Ne8mLq%L_|a&B_#zqoo?)+44b9$Eu{H zga8;F9ZjuPONBzg(9lr!_V$wev7@qbrgujh^(J6K;|&+zbY0^t7r`?+h^E?&NT+3K+;hs_7AR!g~DPC*cO{``4n zWMmKkmoHyVv)MfE5Fe_dqJkokh-qnQEG;c100spGIc*9$?Z#JCRau*3ckbL_c6K&( zI^DPjx2IdTZt>c+YxMH+qEss7-Me?4`d$7%^1eB?ef#!t3+b*}T3R@5+BBAxmAUk~ v1R=?b7cbJ{=zjS#%bmL;NecXh{2BT`D)y^m2SSu~00000NkvXXu0mjf-v{_0 diff --git a/Passepartout/App/macOS/Flags.xcassets/kw.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/kw.imageset/Contents.json deleted file mode 100644 index 7dd61ad6..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/kw.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "kw@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "kw@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/kw.imageset/kw@2x.png b/Passepartout/App/macOS/Flags.xcassets/kw.imageset/kw@2x.png deleted file mode 100644 index ce74c634a7322827934d5c27efb275d7750c7f35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 409 zcmV;K0cQS*P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0ZvIoK~zYI?UTVu0#Ot|&zqUX86CBVC@Ce0pf4yY`h)`efb0iq)h?)Q z&keMp5nOIZy;X{P{gIwpjGG9-#SXyiCbsG-#XnQEilmJClCMGDeGv8EZ@8Otnt zDFWDXPt%4F!efUoh9aE|MLPL6BAR2Doqh()TD{slJD30OG8kq>tm#(i>a9NW*IgCC zZe)UeD?!NnDmhK&R$2{l8c(sJN4yqj@vDdeWW(beMW+dizSqeck+d4(B%Wkdw+1TE zQ^d~-Jw^Pk&?O>(?EotL5Si8Vm&52hOCy8(5i)!NpF>uwW^!b$00000NkvXXu0mjf D0|T;R diff --git a/Passepartout/App/macOS/Flags.xcassets/kw.imageset/kw@3x.png b/Passepartout/App/macOS/Flags.xcassets/kw.imageset/kw@3x.png deleted file mode 100644 index bc18e3ddcfb229a7565f5d39cc2582568bbfc0c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 441 zcmV;q0Y?6bP)-z7EuN}55CBDS`=D2QM;-D;iuD+*46=prIG zi`^^e-{qo{gY7C#j#>waJ=)Tb>(Sgv9%Ma!k4MNwa9(}pw9>`FY6Q@u7D8(q*0XW% zQRY_!AmhMU6;4XvhySXpPZNJK4!yDg~X7Pm-|@giVNoyj5*hS!;1 z1dOgTn@EI#b<{+_xH{@05qlxD;;%Em2p}@9NHEdF*r{k&D>%BP9cIOo$ z-uJ}v%X^=s$RLS`CK8cB5)n-#B7^)Jk+@0eIK+3{uj8lJx6j?ihmP}9nEUp7ln!VY z%B;CdnkK2CTqF=Kx4|mS+%ob1Rw;@!(gE#4iIVH--lNP+BBoW4nOfw~3%C!>svt8_ z#N-MxV?_?UJP%=+4L7~0J<5y}$q9#RzsN;?#hNeXkcdqcWd1~KtRRyD*!S{0T3A6Q jbrTd{uKZ%x#tJe&F@DBeD&uLO00000NkvXXu0mjft}V#V diff --git a/Passepartout/App/macOS/Flags.xcassets/ky.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ky.imageset/Contents.json deleted file mode 100644 index 4a4160cf..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ky.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ky@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ky@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ky.imageset/ky@2x.png b/Passepartout/App/macOS/Flags.xcassets/ky.imageset/ky@2x.png deleted file mode 100644 index 2fb44b3e37acbd1c32b7ac6966f6c255777d4ceb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1533 zcmVX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1+qy*K~zYIy_S7YR8<(pfA{Xf!U`_JiVH}95Q;7z24dkCn4hVcX*!is zX&MG)P6eo$WM(#wA6ZT%=1ioPqLo&reOMTXVJ4{%qNxjlAR@cVBD>t(z5QV@I-(V! z&wuBh=Q-y$?|bH*E9`%GrWE(w45cF`o{Wso$j;73mStMCWXX6Q*_MvAtc;eOc_d8U z!nKNt31s6#YSgP*RdbM#g9q3bEZ#;?OXP zGJ{CkvkJe=Ox%{PWMj#xeiC;@So*Ff( zurOp9O8Z7&Eh|HR_AJiEMigFN$i99U4E31J*0#ee78^-Pt2)AwE950juy-xaSKP|w zG@Fqf4ip*6d@79 zkHvvJHTNLR&C-n^Ybtw~0t5SM0Yp(?*syRWPK?I#*$!-{PNAAL7t4VIIE+TRE_fC1 ze&I-!mDH|TL*0Opd|GV9V!8TGY&JVk0Dy~&f|ZN4C@-$V)_8@k>M};&HysZTxAsB4 z`s%n91frsPvT@@KVtT5mcyt=F%AFkFQ7AVaV)EbS^72~J=k6vudj=tCX;@QJnG+n! zTIn9vZz^g#RHJd{k;NXoyyF=Azp2G)gTyEqA5{Z&b&c&-Q&g)}hODd!94^Wvu9)cWV{c{DSyN^5>^R6>WPpHD)$#D!?cyuq2G) z;>AWjKVqUZ!;gs^M1SH4iN>Q0uQ^Uw`2ogTYMAX~ruUdZ?D?hk+G|P$n4Js4lv|eI zKkD@3Rw`AW;2{Y#`O9DsF$+$_W&zy^S57^+j7L|OUK`21`E>dpvN|2ut3|BcW!xR$ zW(VH@8Qmo%&9M@)d}HRefcVaPk=`Ls5p0}6Xz&`kHw>mv{8ltlB>G7o(dZv~XF%3f zsM#~^750x0XPM7PYCqV?vkiSX^Tt%>H$`%WE_ViGST9d56fY!w?JD~437NTNL|=J{ zlocx&srv;tspW=@0%E6v(6=)HluD88lmHa=7R10nhR1|ZAw@A=U&-(FKFk@_pCV%% z$;Y-+7Z5M*(VBV}A1dn+Q?_GCDN zd;G!4cyMv|CFSzRgoa-IE$y_{cIah;8|xADpUcHBY5G>Ke5}LojcHUx~>RV8^cW7!398*>jbh4bUoC jmMJK#qM)$qU-|z6v$iW0B;zww00000NkvXXu0mjf4s_>u diff --git a/Passepartout/App/macOS/Flags.xcassets/ky.imageset/ky@3x.png b/Passepartout/App/macOS/Flags.xcassets/ky.imageset/ky@3x.png deleted file mode 100644 index 5480fd6dcae734f8be9d8da93e85ba953015084d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2681 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/la.imageset/la@3x.png b/Passepartout/App/macOS/Flags.xcassets/la.imageset/la@3x.png deleted file mode 100644 index b8ad1121ee9ec0fbfdaa29d508fd0e58c73b5159..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 553 zcmV+^0@nSBP)i&)H)y(3l$cB=^%^Q77saeA3Ura-fu`qBKys(aZ{9I=C7kK`v!8$i}}`Yue##%a1D|Ap~H r(Hn+{-Y`V;#@L9k=pa4n5EOg?^B;arTS=1*00000NkvXXu0mjfvqb+` diff --git a/Passepartout/App/macOS/Flags.xcassets/lb.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/lb.imageset/Contents.json deleted file mode 100644 index 195bbb5c..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/lb.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "lb@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "lb@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/lb.imageset/lb@2x.png b/Passepartout/App/macOS/Flags.xcassets/lb.imageset/lb@2x.png deleted file mode 100644 index 22413f2d2cb8f88f435fa1984b4e5a7a28bea185..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 703 zcmV;w0zmzVP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0(414K~zYI?Up}CTTvWF&+}PAF;|0H>l%;ZHfQ%E3ijKSc|74U9%p}6QqQQB79rsQl9jUFR} zaEKf;y*@4U{d%GA*Nb8Iu-N~#pDQZ&=eu#V!Qh8M;@5F5Q&01)U^Q-f0vB(1h15bblng=8qC8=3Ad|gadf0SshTW6^7hHxPQ2*&{r_)pG7C@@I3R-WNgX? z^(Sv75eST7Fg(;ckyMk?t8e3Fk80cF&6Q0FfVtg;;$%80ef;@Z|HAtGxU#4sm6b*` l>LLGC%t&4&GkKBB{05zQ^OI}|&13)o002ovPDHLkV1j$HNofE8 diff --git a/Passepartout/App/macOS/Flags.xcassets/lb.imageset/lb@3x.png b/Passepartout/App/macOS/Flags.xcassets/lb.imageset/lb@3x.png deleted file mode 100644 index 09e5996adebbb19128c82b8d57b53492b2dfbbe4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1117 zcmV-j1fu(iP))Zn#xVX}~j34UA<#0AA5kY+Q2y8xD$;xI}zlA%1REG9iTD8luDP;86V`!W3aV`tA{GrP?1hfFGCE==>hQAHiU3S0t`DI3g5R^2Q@c7l^bz>2s5me+>aJBF%GMRL?ewmSTZReO= zoFqgZLc-<*Qeslbh|fS7qQvI3(a_t#z}$eOo|1zsJC|{JU6`yUs=uk`WX4HW+$%WT z4l?62$x6&ZuhUciM?Ef&OEO|B0Vmh!MQ=yN;Unn{R~@%xqs|F z1=<3d{%RuSMhXqR4S0oD5`Rf&#A3Hl_+udvp%HYCcLNZv2*=@etcg3Db(Wr~9xOHs z8np&PnSta_l6~@)1Vjjdq2Iuj&MUnB_cZ{Kp^@b7$R$aWMDo^TF8*?HO-{HXoc*8e zCod_F{G@#LZr|%m2`GHZ21YC+ocZ|-#xWzhZ8~m$dz-GYE)4w!+K1Z#$V$vwm*?`j zn6^$ML&kz*!Phc;-yEpTPcv!IH*oNagXq)r>`C0S8gE&#FugR* z1N{T89lnOyW~QXEgzL|)`&5Tdd!+VdEtfklW44*mYP2M7PQ*B71VEprXWl-K$!fys zaWY~VS(hIh8B5jSDt2tyL9{BG^tkl*4b=crqjj*A+LyIFpLkCD_wD2-<$oBEjpP+xvTL%} zY;u58U!CIKv3ncl%q`EcDQpuerRtqLCI8kkSxp>oI?kwNl;YH4b|>s!9c%7uUI+J9 zv5~Rw3;}=;!Yd?F^>5W8uP#s2yr>a2r)~9lRaceJ-O!2h)^ZX1L#%lC=R>J1S0xjb zl}0I!x^`UhSDxAMuR{_yJUPvHmy~hxD3=+#;)Q1-qA29cMJd;F@ZWEsDF_m13W5Zh jf*^q=1@J2$K_B@KHikv(zY6s$00000NkvXXu0mjfxNj2P diff --git a/Passepartout/App/macOS/Flags.xcassets/lc.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/lc.imageset/Contents.json deleted file mode 100644 index 7712a0f2..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/lc.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "lc@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "lc@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/lc.imageset/lc@2x.png b/Passepartout/App/macOS/Flags.xcassets/lc.imageset/lc@2x.png deleted file mode 100644 index c164b27014ba3bc64955e794322cbc2ff3a05e92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 659 zcmV;E0&M+>P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0!T?jK~zYI&DY;cQ(+ju@$Wg?oNl#WXGJtfD+6T|m`qpgSCsw*Qs89} z6x|gBUDj2<7$k#jY;KDnCQFlsR7enLK|~}KhzzFIMZ&tU3w2YRU1;mVV#lp{d@i5& zdC&R04-dTO2p2!DrGOrkjg+V`pcGX@Z1rH!>;Q<*3NDMn5UmKEX7E@+6cmJXnBo1~ zF>=`{jv5q%cq9mgLWIL%e3C+tMjhxEe=syOL_8j+QMJHctpX6Q1hH6*d_K?o{5%86 zBUe(;*ua7 z4sXf_gF(F3ZGs%qL*36g(&;n+P5VLG=t44?#ITs-K*Kgbyb?qr5mr`KXwbsoeR%4J zIt>&G1rmt_o(j6MC5TQ1ZH6NK{rw06#Baj!7I3$L|Jr&@C=|kBEYhH=A;f8c(dQ$~ z&dze<0-W#uvv%biTrHp}bLmpIKegt#|`wEG0yy1aS4?>d}ztPicv3R}5;WJ8YV zp_uzdcJc)Sw?XxnofKlARa3`j0H&DS;e&b-*pxv4Eu{bN(8oa z|Hy*q)o@I|Oy|Bn?1#(Ow;qI3wg9c#1q|x{2svqinekq_I`Z8)Gc(?cOJ-Nf zLIj|zZkQI+plrnNuv?#TR{dDkM^o9xHEVOI=k782RaeT1qKF{K8!wyY0rq+?pO!SnY!F04cY(z002ovPDHLkV1knKF24W( diff --git a/Passepartout/App/macOS/Flags.xcassets/lc.imageset/lc@3x.png b/Passepartout/App/macOS/Flags.xcassets/lc.imageset/lc@3x.png deleted file mode 100644 index 56f9642970d6f123ae069d4927a4e65c7d56d74b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1067 zcmV+`1l0S9P)$SK~z|U-I-BL6L%cPKX<*h(ArXaCx$@OiPMRjZezxYK-CAg8Mb7; znCxYVi`jhHmhE9#e3_ZavIMv6=_t6_tPEJfj4wR!Fsg<$BLs+G7J?ZpDG&_!AW2DU zq20rEosFP!_rH<&eY*Vn|NpnfCx+Vm`!omXG-QB!eW@Jy4 zgn=|@#<_28Z7mu<{{fsnQ9M`I^+G*~k`NFj|0eJ6?`LdmtT6t@VYqM(u3UiHz5mUR z$KwnS4;P+rA|wRll``-v#YMhn2mJaiIAkbw!*9({{!j+O;V?m`xTZ@;v46e`wDdiy zs;Wq(QusXZ$Ino+vv^+o799T&(htDn@i09-&8)JSAO5l$fq;x`aT|})NF+ikl|q)G znc(lIhOC7;^4%5={uez+2KHPx{8Bwx~Zt%P1*9Fz`=FxC$vM{ lNIf(ESSi4bjSfPD{sFts4qfMOF-ZUb002ovPDHLkV1ni_2;Kky diff --git a/Passepartout/App/macOS/Flags.xcassets/li.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/li.imageset/Contents.json deleted file mode 100644 index d792d4df..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/li.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "li@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "li@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/li.imageset/li@2x.png b/Passepartout/App/macOS/Flags.xcassets/li.imageset/li@2x.png deleted file mode 100644 index c595cc33f02d19619567b4ec9a77ce0b7e93bf5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 425 zcmV;a0apHrP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0bWT&K~zYI?bOXm0#O)%;rEQrIHpskkOU?DQIw0QZ3_w7x+v-nx{RQk zXx$CClGZKS76fH2`V+D!5K$-dZ=7-3#-Pwb8yE8~zs-3#-{A<7#~ENBHxnWwxeEBz zpCBu-Jd&{7g~(Ee_2~i=YHO$vf3Kn9>u|7f&ty=iSPL^Qci5VHBoWnVcEeYBfSYEmNuoIlD~`9}-Xm72V+Dvx$u$GVa4L{k(p( z(ZUu!+Z-sOFC-D~GBX7cO(VS>mc z@B{lbGC!;TsCxCLVO+S0sG+?3_v%lx2$6CV0u)DPH6Fy72XW>>oOuvu9>m!XRX-Mi*D;KG$D#2LhJ)$`yvfVQCoL+W@qM17eZp_F6z2F*gSBq&RjeXXWqk+ z7`ipZ5kLk1a;qg8f!u0=MzODu&e$r7Y@xDI z5iA#VKsp4a{h`+TK$okG^nJ&xR?v(X?-$aX%h^PuCFGxrr1T=$w9D6(B=45Go8i18 z5<@q4{XbO6<@(?YMsn-;!eOoG;0ka(8M`V-8K7!1>7>ertur%k^0YAAO#dy{TARI@ zUtAb0Vx~5T88W&i^YF;W9HJk4CIKu;Rz@=2FQgK$8DmVzVCh2ccb4COLU6=4Y zAItI)zR%f{31mq^*A-6Z<5X;cCwvqIJdc)*P0bKP16>i!&X-8WWj++jyqf#X_>~M3 z@2N>jxDrSA1R`LZ9w`^@CyPdJWG+4684K_nvMxIW2*O2SYu%?@S?y-iTw znVQ${Un+}G`T}kVJPU*c$eztTFeGS%AweSy2^wKY&$_!jPa5h6D{2r0G^mG=e|4E3~7n?}0r4 O0000X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0}@F@K~zYI-Iq;h6lWO6fA7rfW_R|hCdS>EXgX^Wwc?7caee^`M}FAmq@KXbWu+3$3+YMS{fHpOmP{cj)+d<=iy$k8wbjhSSm zlcnO5+h~%{I~d#RXaH07HqPJgYFc<$E3sR=0Fnwos#S%Z`xm+JZ5|<&mO=8{%Iy2R z%EY}C2C%x53?H6-km2(821?Rx83JIgq;c`PK2%Qy2qN--<(A`-AOM__-t^phY+tk9 z9}rvmG(LJ9I_FDCIa{HuS5`~8Wwn$eFREkTG)AAtNc;)rLK~kRnkN?bnO=aA3BleC zf&s&$Ru^2q)CIF?63WW=j|@`mvnZEUibb1)pOu-bfbGDCJ@ELs%0JVRpMS}rC0xkQ zVCSglWeF@u=8aX zxQ2uP#}lz&E3%SNnRGFHVo)sX>9$0D5$bF9)7BE$oe%%{^$TVrDWvqqno{+P@eH nL@GY_htuQ>L1e|d@?Y~WcS#F-IluAK00000NkvXXu0mjfc$ApZ diff --git a/Passepartout/App/macOS/Flags.xcassets/lk.imageset/lk@3x.png b/Passepartout/App/macOS/Flags.xcassets/lk.imageset/lk@3x.png deleted file mode 100644 index f1e2b591d5c4c65db83ed25acb5ac935db12d899..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1382 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/ls.imageset/ls@3x.png b/Passepartout/App/macOS/Flags.xcassets/ls.imageset/ls@3x.png deleted file mode 100644 index 0573c05fbb9fe3602bf5e7e25cc68efaf042eee2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 605 zcmV-j0;2tiP)l$Qo5Z}gXDp;=J`K4;T*!rjjslt z0RH=rpP@$&L4$XX9)K2Zh|_RGoQ50XG~5uU!M}X^_~;qvKp3K`DydW|q+YKRRaIH4 z2uBDZEMCy*bV9e=g(!;f`~A=~4gG!}kw}Dv4$MM^!yzO|!hAl5&*wuvpU3rjMXS|f zp##t4sQ_-b8}9cz#^W(Kjzc<~hH09tbnHTs$t3psJu;aLOw+{Sa6m4XW2IyFX<3$q zPN#$Ic8mM{j%+pyMN#1OdRge$?T8=cW_ zvCkgDlIqTrLm<0p)TTtJMml(TJ9s z-l;_hK{yWZ(o>Pi2oZ7pDRxh@-%;lZiv%x rL!5>i;xyb4r@_nbKU^L?1Py)xzGEJZU&bAy00000NkvXXu0mjfKN|=$ diff --git a/Passepartout/App/macOS/Flags.xcassets/lt.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/lt.imageset/Contents.json deleted file mode 100644 index 0a71075c..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/lt.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "lt@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "lt@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/lt.imageset/lt@2x.png b/Passepartout/App/macOS/Flags.xcassets/lt.imageset/lt@2x.png deleted file mode 100644 index 33af49e2076871ad58df15492395ab33d6372bb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xxrg*wI zhIn))-`OAXZ{yq(;*xEQEbaF`*d$7Muq!kNwl9ET=4scWCz)5h`y+j!=*e^YW70a+ zai8lF5`NfEc$binkWyivu_*0|;)?_jzF^UE^vPd+#%=rmvt1F~KSPowGU(@$7CM9-cM-|NUoSxcgG6^Xp<)7NGkWJYD@< J);T3K0RZ4yUU2{b diff --git a/Passepartout/App/macOS/Flags.xcassets/lt.imageset/lt@3x.png b/Passepartout/App/macOS/Flags.xcassets/lt.imageset/lt@3x.png deleted file mode 100644 index 7f2c76a21d17e35e78cb88e651621e232b130eba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvPEQxd zkcif|mo{=X1PC-IPJOL2kNbv2(^}^pQx8Y%K63b!;F>K0TZG=kC>16?$EMxn$r!9mR=CyJ44`hw)Gx&g+EvR@;V=4ohb@*6oaR$pUXO@geCwFPf|1h diff --git a/Passepartout/App/macOS/Flags.xcassets/lu.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/lu.imageset/Contents.json deleted file mode 100644 index 80e8f320..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/lu.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "lu@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "lu@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/lu.imageset/lu@2x.png b/Passepartout/App/macOS/Flags.xcassets/lu.imageset/lu@2x.png deleted file mode 100644 index 0175718a24838317925a1f1918dbb0e0c54f3745..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?XxW_!9g zhIn))*Q`$5IjeD^VoPGe!K`JBWehSsRxGkGyrE#)vlWKk-aIRm|Jl{9J9cQ*V$CB- zX%-9ke%8L4b74W}tb>myTtBqvKHDw9{1%t}#&@5HJ(S95zw<=!sfG$79c%9$3|LB1O3(P0*B_<^7FwIz$c17_;0u(baOmmmdw2oPR Q80bs}Pgg&ebxsLQ08WWtegFUf diff --git a/Passepartout/App/macOS/Flags.xcassets/lu.imageset/lu@3x.png b/Passepartout/App/macOS/Flags.xcassets/lu.imageset/lu@3x.png deleted file mode 100644 index dee17ae1468e99f230679c80901986c42d7ef87b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv22U5q zkcif|mp1Y?IPf@Ml;JvjAxI%m@!B%0We>U77I1qNIzj3r^ zRx@-t-Oy<11rfe81?F!zt!8%V?l>XW81O8LD{--a8I*YZ{A)^n!w0*4Z+Hb#*DZyM j2df=9`{tZn;|Jz1-aP)fmdVS3Zej3r^>bP0l+XkKpZ`gR diff --git a/Passepartout/App/macOS/Flags.xcassets/lv.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/lv.imageset/Contents.json deleted file mode 100644 index 42677cf5..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/lv.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "lv@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "lv@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/lv.imageset/lv@2x.png b/Passepartout/App/macOS/Flags.xcassets/lv.imageset/lv@2x.png deleted file mode 100644 index 5a4c14f4ed411c2338e9045748ea1466c7e69f5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?XxqCH(4 zLp(Z@??}x0_2cKs%|<-T94fj!^9^{HFi$wR!ubL+W_bP0l+XkKtBpGk diff --git a/Passepartout/App/macOS/Flags.xcassets/lv.imageset/lv@3x.png b/Passepartout/App/macOS/Flags.xcassets/lv.imageset/lv@3x.png deleted file mode 100644 index c34d91b1b80774da98cbb045dc86bb4a3f90a7ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2s0xPZ!6K zh}O4PHu5?Ih_oj9-(%p~8n^t`Q&u2gQbMh?jXM=sN zY>$fPO#RNmz4SYSHrK*Hj$Wq>5vRohQmq$SvTsfTN+>R!;_6|xd?`riNcG*l<})~g tFTegfNu$M0(FCqT`1QKKd)y;_FrK+6l-i|zG6v`(22WQ%mvv4FO#qg#O{o9? diff --git a/Passepartout/App/macOS/Flags.xcassets/ly.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ly.imageset/Contents.json deleted file mode 100644 index e91cb286..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ly.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ly@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ly@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ly.imageset/ly@2x.png b/Passepartout/App/macOS/Flags.xcassets/ly.imageset/ly@2x.png deleted file mode 100644 index 738e8ad3140ca8ec4c3d84b79069a6d63dfe61cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 399 zcmV;A0dW3_P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0YpheK~zYI?UX%B!%!54-!K_C6$z|X7F&qvN$1$eUDVED+ z{bAfQ5&%@SMxzmp$7AaCdIW$pO(Vt_4F-eCv$$s@wM2AXr##Qeahz|x*=$B(7*Y@f ze=lO&Hm%p|FK4D{QWQl*M3kl}sjB)viDX$uuItK81ON#kXfm0QWmz9`ilWqhBdTul z+qZmof{M)Nb1W7M2qDn#_fZrDvMj@DwW>M_y2+nwC-R%cJtH}s53O}^w1LWOnG&tD t&PFqDE@ObMWQqVRKA6=sB4rwpGViTlAx(+(e8~U+002ovPDHLkV1kb(u+RVi diff --git a/Passepartout/App/macOS/Flags.xcassets/ly.imageset/ly@3x.png b/Passepartout/App/macOS/Flags.xcassets/ly.imageset/ly@3x.png deleted file mode 100644 index e5f51a54da0f84aac775ba10b4f268fe0a34ed77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 468 zcmV;_0W1EAP)Uay-cG9Hh)+wHF2^E{7X7;?2*@r%)D zq$O`EyOzlB&k7`UW!3ve27>`fvR148*joL5-`v)69EWinGm0V>3WcjNK@hOh>0H)E ze(V6)XfzndF_+6Fo6ROgg!z1)l}aVG?KibUW2scS48-&KjP-hr>2!+Y@u)u;?CIA$ ztcYnS@8y}0`~TZj&i|)d*Pw;Ww3Zby4J%?AR>U-v01X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0Q*TqK~zYI?bJPL0znvn;df?YKnq0^K`jD8FjxfASXy}o$pM0g@C4q% z-k%~DkU+q~&MLhkX=FhO38L(*g|V3~Gr<*^7q*x!b{{@IM!Y{f_9JK#V*&;;PQbAU zqO*Vic!-&Ym|YI!>+0~t4jJbp6NTwNgh}BR>c}cINxY@!ib$HjVCJ3(K z`7@-k)Te0znW!-|i{qKpTx>8X<*93QJps6#1HvFAPBI{4aBCe}(h}t3|zde+fZj3d1FDIF$puAL+mx@{HFp5oJWao)5 znEHaoT+tlwXpVO@=8CB=nD}Ov~a+-pbq00000NkvXXu0mjfj+)9e diff --git a/Passepartout/App/macOS/Flags.xcassets/mc.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/mc.imageset/Contents.json deleted file mode 100644 index 47971ff9..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/mc.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "mc@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "mc@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/mc.imageset/mc@2x.png b/Passepartout/App/macOS/Flags.xcassets/mc.imageset/mc@2x.png deleted file mode 100644 index 2cabbfa72606a2c9343df2b216790149fbcfd5c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?XxqCH(4 zLp(a)UNz)xa1da<80+NZt}(Gu;>iLA?d43ma~O)$6JB!sWZO1#dBU!b`|^&SDLWn7 z_N0Mdu}i?UL&-&O(Xp2{dGXJ*jg{tw*(%MrCl8m1`_IG|;>Ob45448C)78&qol`;+ E0I@AN3jhEB diff --git a/Passepartout/App/macOS/Flags.xcassets/mc.imageset/mc@3x.png b/Passepartout/App/macOS/Flags.xcassets/mc.imageset/mc@3x.png deleted file mode 100644 index b6319e7fa59cb66cc539aab516419d9e4896e853..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvR8JSj zkcif|7a4gQ3k<5=XD1 zN!tYpr(_tzc-5+~{udk0Dx-*krHxPSu5xLY|767OrrLD#EYLy*Pgg&ebxsLQ0ERd_ Av;Y7A diff --git a/Passepartout/App/macOS/Flags.xcassets/md.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/md.imageset/Contents.json deleted file mode 100644 index 76682709..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/md.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "md@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "md@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/md.imageset/md@2x.png b/Passepartout/App/macOS/Flags.xcassets/md.imageset/md@2x.png deleted file mode 100644 index b5213fb1e6d2da847b7de94c7fa7afc5741a0b45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 954 zcmV;r14aCaP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x18+%0K~zYI?bh3C990~^@$Z@0-DEbq+w3Nrnhl#^wrN^RZ-O=0*g&O- zc%cuezW6BkKM0EAgAXbep9)fZDF|M$BBBK&u@IAx)P$5=8q;lXb207SH+!4iGslN5 z6;jGhU;_&Np3WS8znKp+=Xd4^PG0^B)G#(;1Vkvw>XiHF$VgWWm4ekv8l&Hz z<=Qu=SYGx#$Q>JNXVlSmvakz^ZAhqj9joAMU0DioXSEsK4XuL;DefYKKzCRGBvU#w zQ{9wAQ_EUgOXDFvSB`AKq*Ruq(=LRB)`-F%%;I#XiS!l#NTqd@CAgOR!R;{M75TVdC2LZ1Om#$$XCD}t|f|Fo;4n)9z267j` z`;(J^$Awc97`pwYtPk^&eB8>`B7D zb(AjI)++#P9PXuhsE_qG_T!lcV;qtn_%WLga{I|A9+dVDXOT&J3#C3}>mcMhNQURg z>s6FeT=87w?dT1DbG}Y27GP`3#|`rZ-nlc)CGQvil#aVGm+f^NVh@kZ|>N)Vbo$L+x@{L=OpKPI7)+2pyG zI+&h3jnk><_D<0A*q21yzY}#oj6<@;fa!&eJjcy$p~n!~j@j)+YV z_Fw1u&=ftFP0qg-B_7kudjgSFi_;ae6@iOI*!za-64thm0M)94tl!H+T_dk3q~Peo3RVXqr%@R@weLl1dodU=23Ak c<6i^$8!3fC_{%conE(I)07*qoM6N<$g82!>dH?_b diff --git a/Passepartout/App/macOS/Flags.xcassets/md.imageset/md@3x.png b/Passepartout/App/macOS/Flags.xcassets/md.imageset/md@3x.png deleted file mode 100644 index 2accb74af242743a6030da0652906e0c520b0425..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1640 zcmV-u2ABDXP)e|Mc3k7u!G>=`>#kJq%em5{UvZY!!#5{lF% zN~A_eQ7A$ctw5p=s1FDb@PY*50Z<=6Z3Pb{NQkAWOe0qjElJ#@CT`luX*xKrojT)* zXT~%3?91JF806G#-4fr1gwL}o&R_KI^X#=f$h)z6B~H)ffIATee#7KI~<8` zdFE=D& ze5;8a+sgo~RG{nt(}2(x5*B3%~c0D}9}X*dSx0x1)V1OR;GeXHE`r=8ouK3BgtzwuEd_XBY|!EwU(a z=|UgzgaUx2gR%qkfI;E%nZ`fELA`d>SJPl(ya!cu;thFR|L%0#3B{g}h&xGaK2NTs z@!mq6ix)irC~e>wfq{J`G^3G*P*(!?2V#v~bn<>&Vx?H;-S_h>xfMI1*b^cP20@>L zTuCF~RnfJ^f1e6o2lRC5RO*e|>FLd`@vYYdyiE#)vQDc{MNlL=q1Y>S%QRTbRxwNq zMHUz^*4j&D0ZD9hVOk&c&*baWH4{k`XwytPq1Y3ms7_ivDykw<*G%STJ&cXIXb6#Bpq7f(D@GR`PI73R2lO9;K+k-JD6LbbrSMna5lkup*HQ- z5Iq|ca&1G)lwj*9_^&|4izS-qf`x2dU6nNjqCbK3te|b<*{j3b6#3IMm2U$;b5YDCcC2yNayK?|P{}TWs zU-%ix-q){#Z&$ve0Yx_OBcBzA^|G?_)W* zz}e<3s@b6N`BL-_yh`8Zac=8AMKrpEyGcdYRqhyCBGUCHUw-J@v;>ahXF7)n?Oq~w;a+mN zW{$o54E@nK4}J0btFxCRn;3O_+xU$yh}kH2o!U=m_;pTtQcU`??DgpcR(?lkxQ}w= zb!KAs&>0!SCzY8v{W&_lN$!arqRBeNmi~GE@%JIZA&Gp}0o=fqT>xGVE+Ci!n+=KZ zY>l3?NvypIc7Ju0v19$5e(7mydNbXj!)*V`Z-3@)YG6o0jZ3{v7>zmI4Ej46j>rw$nw@31H_9C>feFp zS0Hm3?(SW|m|jfXP$9{jNx8ji#vQQe&OzA#IS9v3_^H+;T)`PyT|N{^Bp5DW2^HQr z9YnbgvLduR3zawFK(#QNpIfmd#MaPyuRP2H&PmNX2>MZ&o`DW09N3=$GW_L85J319>e!Z5*g3S;{^-6XCeBnzTyb%5 zZ+eX_{M6dE?e=D0`}bO(hLN2`^2O5i?&kk{(X2UHZHydQIj@u!q mY04Z>`_zV?0MTvP=J*dCy+|KV{ND2b0000X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0`*BmK~zYI&6dw=TvZguKj;0Pq%kiuOlr(8YGSntf(vUY(JJ^0bm>w- z=+0IDfZ{)((yh3#n|5PS=t7Vpu0%^$!NnAnT9TPCR@2OwWahnj_q}^uXj&;Q-XxLn zyELmviowAHG<*BIsL;Py$yx0CS^D_T_IQfe7E6#(y@(1F%~ed1GfJAl~U1 zA8k-a*Lp!i?6VLjQm)o>>=6G8ZoDX-KCy__Jl@N)hB zc;v!N_}_uJ@5~_&|GmI`1--cok5^zhLK{2CTm*p3hInt!4FyvAYGkE~TldjZG5O*I z!D}^ws!RV|0}&Xz@D$lygSobaH{;>dePpGIe!Er5zx0X7@o`ije4&c7)}y%8M2+H| zUBElLfEr72xrMCn5}ujDBFOT1S^M%J&ZH0GP>92k_m|N1l%%l>vl048MB3<~t(1xL z%UB$69b~e6EtJl*#^N}Nw}<{xjf?gSPyDgTjm|t%UY~bgyoMD)#DXen^zcD&1O|$R zAT9)_rg*G-m**EY5P?oV#CtBr@svCj$Q5m-hYymk^{E`IkPixQT#AQ0KKuC@e9fs< zddLy5lL2|6*n!0jh5Vb%^7|~E*f9()w~@q>_boPu_l|vq2@*25Pp$GR)+j|{At^{N z+=gC$_#nEvjVwl(^FLxPHgSJXS&CD1e20#2Se!J-=78+e7OhXZbl-Rsb;`G?@_l4G zhtT~iUK+!xhXk#J*N=l0hwNq#XTCylVw_-k8gD|#o{kAu54n#3*d2ra(Fr6D$UeM< z{L;guitStUc7plf7S5pHXxJzEsY}q_E$uzjqbIA|n58ke)I^SkxTmXV0V;~W5;M5o zL9gDyT)l%oj4{JLdd&nrUV~g=Bc=Gh1ID76#eCOA+U3jjK@I;6NbS8)KW}5McL0!$ z{X^^zuc8OwR0F&xCq@F`R)hN>{>d2sWIPg(J^cwx*&6!3byZjZ0000< KMNUMnLSTY9@s~;f diff --git a/Passepartout/App/macOS/Flags.xcassets/me.imageset/me@3x.png b/Passepartout/App/macOS/Flags.xcassets/me.imageset/me@3x.png deleted file mode 100644 index dd08c87ef8d201a7ef8790fd2f68bff05caeec02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1480 zcmV;(1vmPMP)ZVyVu%Vl(Y*@65u8RyCHb@{SZPbPq1gTtt8`m`x>Rw9SE*JZta8DWYKn-U>brQti zn*(#TB@uC2Tx~p3$2)o*xhNzTh4|0a5yy62+uJ^nT8PUl)Uo>2cI${&L*j0N31Z^4 z5c@wmygFLP4nI{W9Z4^w`@*J#MA=9mt^)1;q4s;Un1UG0Pou_y*rAiLt|-a zq^l7zQ9w@=5JMUY8fPu~rEydv3tdC_&Jr##0JxkY^V%fj6!v^T{F_;Xl2FvB$pU7w zg!pH>Z!UdD#0k(p9YYp0HgU-PRm?<{@YDuE2>f~m@18Q8Zo=s%-o0h~T^Xc+;M4}@ zwkm3WZS!qj;U64@6y?%?Y84ce6wWwwS(7`s1HIHnf3!&Vl^MeMfWm`i_)!_|E>pN~ zh;S~TdvXT-(IRTOP3GVzURh%uFr*0OGE z2WrRzHM*A)qNxSs#Sk&9DV*4ii5)0`h0SOT^`Ggfid|+UHgP0@C0kSYRgcWiZUHS2 zPwv3Qw(o^W7!n7iejDN=2c8$Z6vv|`9BYw!|>tnc;7(L?Qj3a6rq$d%u+1*mC z68(3reN{}`AT@-|7@ZYFO+)F?3b{vWOkLQ^6ZKgzjy*e8$!83sdY$}ZHHwc`2-hqs zrwG?$q$f#EH6gO!y%7Go2F(cDO7KTK!lpqN6y2qWC~%yK8k}uad3$=C&W50p?c#io zD1hElj8_yy&6v!vhi%5V4_5l7l1hkZCx{J8^xJuoW`aMg=q*NgLz*a2JU#LmHNC-X zr;5OF`s*=n_Lhkgfj=baEyVa`P0~z=-k3wg{YN8}5CGRT=*Me>Cuit<+(c&u@p^*q zB1U(9!Jmqb%I<&gmr_Fe`U_mlxA1I4v~JLOLFaER!jm(wE0;HY_zIZ+ROEG7AmTBt&aEFT7U9!l)5xo8%Sd9>C37)BgQ=@^_ah)qTp>`Q+~{(fZxjn6(HsqEXM*i5ex| z|FVIL4b{nXJ6`GyQA!YvEV-9&CYlQffBkRNS3N`{hv^xD#NrUR$RW>dVE(v_xgn3< zH$vqu$J0B{5&XG{dv9rbg}B)+F0}MkW8AOKp<)ZZ#6RA^ab=)&2m$^J4dU0%q2{}| z`7Xhm3y`u~T|w&9677WKmtW%6WAHAI7<)=k^AJj4tpno_THtCK*faFa<1G>aTXcL7 zfrZ!SzA=2#BFswEV>R@sj|1YRkoetY^rx+@aCsF5c14k=*05(gMEgg;lk!`n_@=41`PJD1u$R%SMbBh^!tX*)KUUD>TffZK5==g6lbRPeK$!w iU}$h3Za0aoz<&Xe)G+CmixGMN0000|gW!U_%O?Xx5M eG`?WL!Z1mKXa7E*nWjL?7(8A5T-G@yGywpH!bC~{ diff --git a/Passepartout/App/macOS/Flags.xcassets/mf.imageset/mf@3x.png b/Passepartout/App/macOS/Flags.xcassets/mf.imageset/mf@3x.png deleted file mode 100644 index 5b94a2263d0ae4c51f921f00255def0ba318d445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv6`n4R zArY-_Z|ZU}C5p5@v_9-|n8Rp+K~I9v#-6LZ60!a34y2WMvk9^pM)N5B2yM3Sws^|> z{N?upv-7X5<6keJBVKwdKg>M!@!q?p^B?aFG3&bgRFBu^Rh^_@a_Zyn700&k))JoR zeO-ZXVIfDabB2u5V*#o53l=TM6iu)SY2Lc2p~-xB)pRANs|gW!U_%O?XxN<3X0 zLp(Z@|NQ^|KZ}j6O@J-wz^m4F2`lZl9YNkw5r=vHgTe~ HDWM4fZ;(G# diff --git a/Passepartout/App/macOS/Flags.xcassets/mg.imageset/mg@3x.png b/Passepartout/App/macOS/Flags.xcassets/mg.imageset/mg@3x.png deleted file mode 100644 index a13cbb8dd7923cc9ae2a1a9f969273d6be82bd69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvsh%#5 zArY-_FK*;)2@q&`So!GzlcO@X!A3UYHEng@h#o? zTxHwQZE7{guWfm#z$D?ro%+?$;eGMB;-k0OD*`#BTQ{H)o!1)oa=uu0>#^A6{XaXL uZfLahq7uF=8zR{HUQZV&u)6#C2Jg+6{6X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1t&>FK~zYIz0}K7(^nM0@$dcRMM6lzBalQ0Ppil)2nzMxYPAceV;$RR z9i3^bBUPs^+AdwS&UDl1Ur^g&?5@?KZrYBuI^wHXc?6K6@-h)3k`M?V4fueD9U^p&%UxvAvPEFv`bHtoMAD%6vWzG4@Wj$ zz+s#)tLICUgg{mloDLJifiRLJkmcvU8iQtv&a8xDg>>dU}SM4XXc&zo zv$MRDErtCE@$Yw`V$8aOf~u|PlQi*MAw(j?;~VFdyfn&}WMa!mp7RgW%zA_n)VdaN zrNuiZ!jTfGCNGN9y#jaX}n7!xF{Rz%B4ct)!gu+v} zyN4O}htX-(I5LeyqA^mEwX+JNQ!zY4<8<}-X}RIUH!=Z$UK3+)rJGewZ#*|>*-UoT zdpJ$A2cp+E&aqP+{Bog(m@In|o@Qh;!mo`zWMvpI=ryb?vnLeGio(tQ06LwDmK#0- zV^M^F0?R0$)LbXa7zSWkn+;13P+C|p3()5ca_m$GXD;_LGxM-_{xi@N1+r}^oIZPt z54V@mesdUWngMf)jNn78 z66ehAfdKU<+c|f2V9vfufW&5GZV!yIvfNI6Lp!h4IC0ud81x$M2O?a!b_cauBGr_{ z9nW13Rb8XPHVnYHVL7Q4dnvP}B^+>TAV7UXJLmrFpLZJ18u76inM=)Hs!DA9aJqwy zwRsd|TWD$@q}MZw({3W>@8rma7R%ljRg2pw^6v_ZH&pfc-{3*AN37x{^}TdBEW(W zSy2dvCIRU821z%BIp}I+aaw>0O&;pv15^|`Q&&a|XJu5`7j`7iQH*1_;?i<#TR=u0t^b}-8;Zn1g z?@qMQ^_+kOA^EAJ9I9!?Vot{IJWQpl6j@RD<7z)gkGIk14ZaY-V)>Pq^{;#$O6x z7NjI|gu0rWCZ;UH Ao&W#< diff --git a/Passepartout/App/macOS/Flags.xcassets/mh.imageset/mh@3x.png b/Passepartout/App/macOS/Flags.xcassets/mh.imageset/mh@3x.png deleted file mode 100644 index ba36b4a86a584053440d38af0c66a85bba16beaa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2308 zcmV+f3H$bmP) zv)S9*4=cf#-P{DU|4%!6p69&h_rA~bobw!^>VbVH_&?Xy+zzu`fVB&qC>Lb}L*W|{ z(}JW?1lhT=ku~W($QR=vEyaMN$928qT9C5hM|rxk4zu1%7#0VTVKIJVAc7#VIorX5 zg)PWs*H*=XKy(@f;}ZeWQ;Y}#ND>%yO5EfA>mG148C8Dvl-E<7GC0E~n`^R8qd*WM zzf1GX0K~_sG3r$4wF+`<@jwLV>bM)Bf@CMZ+jKH21k3|uVlv3ON*e$xvJ+9Os>(#KRgw^==8m;l^bL-)>%I~ky<-5RChJis#Onf)q95nC>pte*740zt z4aJwEt9~gqh{x+ECo_RN*Jknl$xA%{L=|J6Nw#lVjzOoSyudiTty)2xK}Fx7XTbm( zd5|4N&8%N`Ir;!WhCk&lQcJd@Q7eJSp%WDZpt2~HyVhq@d$x-oY`KMWi;+0JDyr|^ z0S}*D=;QE59c=#2QZ711IC|>Rg7-|O$;D4r*O8(hk7^I=b5Z8+#*%88_J356v=jq! zQAY282a`d?Z=SxDW$7k#8b!X)x%N=yghrdgc5ai2~K7R*G_y@Ob@PagNi~>*BwCZX7)>F87U1 z!HKdk+ivM#d)`H4!t|vl$V9xB`>~bWfl4`d;W6g%^Y-ypNe+>twyDEOQ-^bEfYof^ zu65bmxi)KFAfhP4-aX2@wH^HXv&%8}Mxthdr&rgLyJUEVEnp}_SNH_E=EOOl=WdX(x-53t%<6d8V?SjJUCB&j@O4h zzQaw_obDtLjCq-h4x&{n@cIInjB0+prtqzS&z>jSw6N@BR%~dy8GRHe?ty0ynKc~{%ix!y!Zhb z79*v3X4*Qs*;!o2nzY{Oe)X#`6+cE^a(v8)I=e@C^Sx${pRp4T&%ZQ?xkuDW`P63G zc~3EK*R)bqYQ;V7r!Y5(Hpei<%ai%*+nt4)tAo>>prXaLx1-0k8aCs)ts1?+lv9mPIgmYwqW2ZW? zB8Ke-;GusJs&l;5Aw#5CO)oT6u^|`%s5_O0DE_& zl&)*OLO3kp@%kBdP0)SCO{O)DB^nBM&@4w&xSL{YN>sn~E&aUyP9y(m zy0ZA_7XtESb@fJyLATe>(C7pJNwo3&rUnekNWPF{TE^0LvZAyqYJqpIp_jdfF3`{x zy|!Ac76fwj(+>=J3Htp!T++XV3TY>cIwDJBOmVx^7x9$gQ?+c9u4x zl82Cx<4m}hyw!K2QC+*Qge8fSpI+u~2O4f#|7+eLSKE`-={*3rHKmw}A7iCu$<&x& zD9neSIM{pW0+;*7VtBeSnit5I+miv&h|BUQ#buRKrAiqjQ}siZ88kqoSUh^Flh@yAWMJ5PQv!~qm>@=#pWWr>DM=Y%Twx=yd=F(g zkw%?wGRTn=9sJ`+6V8!|n-=VBiV9MY?BvOcdW>2aOnQvMighUDB3@sB_fKA8-@6wW zcFnw=ev_I8GIa%A(T-EO8pWy~u`&kR;s%PgT^BktAo- z_Dfck6fipG_6Iq#~YZ<&o?ePMB0;&uWDq}^gYZ~+X;mw{_@Xy4%D>c( eZX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1OQ1yK~zYIwU=#ZTV)i-fA=P9Zj!Z4s;GTgSGw5QF=b2;rzi-%GC>$C zs1!d8Uzi^z>Tt?%ABL-uq|n`q&I1D zuOCisZ*p^Q+M*tKxX*cB|IhiK=bYy%;~P2J`3CoC{VYiSXZY(8Fs|S!iXYa+LDfVz z3-CuDnDgFq)FAYH1mRolw!%21c5zJ2|3dccX1ILN?hM@wJqJMzc`s^~pfG0ZbAK)Z zLW7JIRa+VCax~0g}&vk(WW?TVVAEpai@ZpBuI7`l$CfWd(G<2jSaA zH+1-W4@~W__fxAUq=yBeen#|_cUHhOc>wv{ykA-!(6t|0?iJml!a8U}AW%Dx9KZ50UU+Lw8{UiW5+n z5IPDIP)@r~@(Rf~<7XmT)HucJP{sIZVvI14>hw2b-NqGi}iAtG- zLL746L-@9O5CHx*Xt@WnCm^%|0)5c5T8h>O{%hR_YQOjMvkr)Lg0?}?vnOD3dp!sMt%{a9iU6|=$sI6zO!UZB z+rkZjR8s+P+YDK>lma4K}`ZiXXHaY+dUYfAJDShjWiX?v&kTQ5D`q>p#A4j#l+Z RW{m&<002ovPDHLkV1o0i`bhu) diff --git a/Passepartout/App/macOS/Flags.xcassets/mk.imageset/mk@3x.png b/Passepartout/App/macOS/Flags.xcassets/mk.imageset/mk@3x.png deleted file mode 100644 index 024a361a47067bc58d4f6d4a3add3a1a900dd47d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1457 zcmV;i1y1^jP)Py|xi>17J^HiXVh=idwa(sWw7xZ5s$E7i3QY8qjIQI~Y=W$6ycB%1(C3|C zv1+WVlZ%-<-t_4~^vGfE^@jH^1XH8^l;n zxM?%YT~~TeqBtTp6{}$_G;J!h-B8I4*N%XiYRy|EIw}+!pD5wToYfHi(5T-HmCW$u z$Rx~E3vYw^RaUuFH)KX2GYY95>v*UWT6W33S1OsIU4UG9Q$Y_xcqi1a0CLc~35Jdv zWoA7t*E_!&`|DnUwu68!RO)@#dlU?|lt0x(wQ*?K3$;su{0_k1EY9jdsA!Tt%?CA4 zL*x_4rpl+186I~~Y7C!*yMI9B03@zJMWfu$e!?ghKM&E3ki0Hctaqcggtmh=a+F-^ zx+qUxm1}0WOl6f&(EuH%3lT75A_mu%Li)D%9rYx9AqdQeK!;o_=Yy8(v*t)<*yz}l zqt_vqw13FF@2rt9BmN6?t+bcX0(oQeh87#{3n}DPj@ThH3dt*w_yThaZP&Ty!9nk(I1n)<7zVE@%H)iedz$Lj}%F7ia)CrM~0bTO5`_=NPR#rqp%c0|J z-U*<8m%<68ocd}dCo0>mxha9v(^L><2Oa1IqPy>X82Z-O?L;+$Bc-SW zF#4lxDco+=g9<7+<;fKC@1>DbCSz@=Hsy$&$75%pXIwyX)1x77rNhoT-t*Xs8`>0MDoaG!fXGRKSTcmk|gW!U_%O?Xx5N>9K$5kNkA?00Z zT6fOQ6>oGF>Q2$L_%p|O3KU*pP}EfJeZNQ;SuATNu8-B4oeixcsFKt_&VoEC0!Pn-FCy| u$TF-#%sbSX=gS*^=#HzYWImtrh+n3j@6(buZ=V5O#^CAd=d#Wzp$Pz{DO5WE diff --git a/Passepartout/App/macOS/Flags.xcassets/mm.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/mm.imageset/Contents.json deleted file mode 100644 index fac2fb73..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/mm.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "mm@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "mm@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/mm.imageset/mm@2x.png b/Passepartout/App/macOS/Flags.xcassets/mm.imageset/mm@2x.png deleted file mode 100644 index 8bd69d748287e3ef9054030c9843ceab28b945ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 679 zcmV;Y0$BZtP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0$fQ%K~zYI?UqkS6j2<wD5MokK}zuTIJ!iB;Lt3w4Ig2X5y zBZ@+(ThO7vZXSvt2s%i&JcypGAdDcWus~c%kw^$pGsW5scQx02+fD~-OG{@)gN^9> zn8W+c@ArKlc<=pYM5=>k;0PI%DQH$Q3GlQ2gUr>^L9#Z_JedX&dFK#$XEO{U++fsz zaSD{I41%nz1~m^<19A>8$x1!_F)%ug`eHG~*Zr4EH-Hb|t#KuKsjsJZF+MpYN7de> zr@%BRcUoMWUCer4NqW9^=`W=G|;vQ11l7dE%18WZA)4X^!A3n`Xw&o>2 zD<6TFm3z0Mk+Jv~6Ea3r#)!!%Q5oaWa2uWB&IQ-81PO&h1apH_6<1lM;&!v=GA-&VfuThlh&Ms0g7k?F`U zpeE+5*mz=`3op;xhHwM|(DSLAnx`k2{xY48iErb(5~&0ypVbfwhn!eD?;|oL(OvUz zkEUo$C8wO)qHY>gMkbe;w)@C_|6W5N{PVpR2o8BF`X0Z(7q;u^nGz%F&0Fefu-`|r z>q=nPkGX8`z?2ew&(3u0%-|OP(Q6OwT7ux6<*k`3eR~-IsSjZ$Z`>x{IHLdi88frzm{c?(c& zCs{4=BXkYp&62onGR N002ovPDHLkV1i7WF|hyu diff --git a/Passepartout/App/macOS/Flags.xcassets/mm.imageset/mm@3x.png b/Passepartout/App/macOS/Flags.xcassets/mm.imageset/mm@3x.png deleted file mode 100644 index f4cb181d1c6ec85178485b90de886caa58becdb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 917 zcmV;G18V$CmK~z|U?U-9gR8bU$zcZJd31_ewMN7Ka1+qlL2GbJiv8RflA}G_c zdZ-?vpto+620;Z~-h#9qih3%D=w4`$-BGMaFsw;Row0FjoH@-oJv4d2+nhQ3poBjR zd(NKyueE;mUTf_+l2XT0{sTzL3)Ha=LIg}>9e^lI2pMmf5E3`cQ~rJohVeq&o58)w za5sjRUuj`INM_WA1xC6tct?_#fjtLo*^s={P*;WukZCZm zcvyZ#ekHFuUJ;N3bjbk%au;2Z*eC~L^IiDDKB8(g?zl7^EeVN|xh3q%-i?G5w-YT; zQK@dK=E8^bD5?@SPt%U5s8lso;r;cELyHb!Gut!=IuasEgyZjyar^6S&D?}?sN zl-F+Kd+2*2^dZsS)6ULkI}#EBI+Fe4&ySSVl=8l_F;Nq}_!RiW)|xUJ{0#})&{ehC zdfF(jE$5E+&ai9LQLb_iTOYEmW-G0sRz0C$p0;t?VLb+-YLv&Vj|eMa3euJg`hBFL zP}Nk$iTX-pMb;AnAgGW0D*_$ij<`9#u#a=^&uXenBSBb0Ot+`wPIAY+-j(Dc!=53^ zy*MErw?{K4-<>bYJ(h!)EJ7}gp8dF`ouTDE&!xLxMEJK9Hk$H87;??X9&$eX z;3;ab8{4XtAPr3XjI112VWUZt_~D@Wt~C@)Q`0eLP8~28ZSfL3dXh+O9RNKyZs9m` z0Q2mbgLqM|zS0YVa%56bzx??9o6zO!_}7&5iU5#bzQwfG1-egb4frmj)3lzWMX}00000NkvXXu0mjft2?s5 diff --git a/Passepartout/App/macOS/Flags.xcassets/mn.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/mn.imageset/Contents.json deleted file mode 100644 index 58b5b662..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/mn.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "mn@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "mn@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/mn.imageset/mn@2x.png b/Passepartout/App/macOS/Flags.xcassets/mn.imageset/mn@2x.png deleted file mode 100644 index 684cecc96821d9741fbb9d73bc515c287fe5cd83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 458 zcmV;*0X6=KP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0e?wEK~zYI?bba@LQxzB@JH{9-t_u*5i^R6NFj(^f~X;yU4y2k);>a? zAllmc1d(fFLlHLgLYPB?&=z8PyLz+x?zJJZ@D|+V9vyt9a}NLE$8*kbd{?=A(TCm^ zT;wRVj#FXD%_z3TN9@(FUCsJ{qTkgdfs7%88Ewc!MgPUIjLY~r$!F)o%pH@^PPr| z=^?V*@f`3kL@Y(2zXZ^}4@0_%5Q8`#Yr4<`H?M+g(qM%yF^Hp)u0p|hWf971=-I>^ zPDtY-2D!|mekMS=<%vNYSCd$i!A)yl8;Y3Yrx9DD&{ZgiS5f#`8&-!x>yC%@LBZC& z|1IK~(FWDFV1Cnrd192bQ=6aWAK07*qoM6N<$f|R4i ANdN!< diff --git a/Passepartout/App/macOS/Flags.xcassets/mn.imageset/mn@3x.png b/Passepartout/App/macOS/Flags.xcassets/mn.imageset/mn@3x.png deleted file mode 100644 index 8a5734bfdf4cd99d5d2245e94f1b525fa122d27a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 813 zcmV+|1JeA7P)ilh(EgA^Eh zOMc~Pn`K3cFLj)%ApcDFD^J_B6cHhbmawo&Dv`A1XQsAr6%ipx>TsUkPki))p?;rlZIv9-mKtFRI(g>SPBy z&QCFWBaO5Ls&13mU*zlSnBVzUjO&avYYOi!_Yz5&bnYqPmbk7Psj? zGY!Dpog`Y^Cc0^1pRCVR>FAB250}t)R%m23Ca?7UnZzylv9!VxeRmmv+!KTOM+wZV zM%RHNv$v91d5sOnvgo_Z)TX0fq2|E;C#8IM-&+%4{ z>t+|4WbSS^g;z1w^;em=+{c&a2Jyj)-}zQd%l4Z&_x`7I#>iKs9o(hWvi%mub(F}D zrBweAp>sI6Z;I~i*AiqOB+$An(noVhOOStR_?>N4EY65sRC#kD13)9IVV6}VE^X%h zRflGM!FT5R20q?S2C{oCibz{fndn$D;JXAs<8zqCk`ck+|M}ARx3DG>H2xV8RUjXE rDG+vSW3WE&>1Sxzv8M#m)7SEE;p^su@-&HF00000NkvXXu0mjf5?zcb diff --git a/Passepartout/App/macOS/Flags.xcassets/mo.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/mo.imageset/Contents.json deleted file mode 100644 index 6be1f045..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/mo.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "mo@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "mo@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/mo.imageset/mo@2x.png b/Passepartout/App/macOS/Flags.xcassets/mo.imageset/mo@2x.png deleted file mode 100644 index afeb2408517864105d832b8e2d86115ed7ab4902..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 777 zcmV+k1NQuhP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0=`K^K~zYI?Uv0;R8bg*pF4Nv&N$utm{PHrG8L66Oz1-wDyS5N&^OX5 zBdA4fixv^|2egYmb``Y9Y;B9c78BA?nWG_8sD%dOj2|^#o!i`d?rkAWq)^w<5TYNP z&3WJV$8*kk&%HIvScGm_KHE=D=GHL78fc%0dVvE zR&2Tj1IhOod@(#+>aWJOVOv2V9lIJ3mc@fNy^PKzGO-`IPUZM$+>%Xa zV?RNE3fl%%@p0$qX|6OM;%dubZXM~s?@_RA2>MfWHumFQMiV;*F>Q(Va4j+23&7^Q z0yg+n+%6X$S*A!`&*r=W0AjkA_HZqxEv-HXfF``0tJ_aYd5E}{WS!#8#B$sU32m0v z@(^eD?n4va-#9ImOvBE0Btm~mEK+m1*m#iY!V(Tt*3h)A3R!YdTCfp-f!G+0rB$?6 zY)AEasV&*UQ2aCVM%n>}G~~Eh=hJD<>~12M7s$+MX&uX6=!3do8LHyT#6HeUay|US zslD@~Neh!l0})1NetsuZ#YaL*l9-$Q1!816!Q;0tF@$go;&?~W!sK?Ohw0QDb-{9q z)m(msQ_xJ677vuS1o6RO zBq1@TiBDpJCiuhy31XrT#>5z&j3yW*CI(Fe6BA>wka|N~5JA=gy#QUx(i=;=ZTB)W zK2#Pq3o&x0X=D0-o9}#czW;ybyUZDlT`foXA3z75m}wGTK+H4&uP`TMwqZ`lY{Rq* z30qmhR(86>pik%KYs!*58%+T4b-a?_h89d)EKh|P!ohJhY*zzdFm3WuT^xY@-O;?C z)Lew-l6AaG2v3E)^Uyis<0kLM>HrwZ7<|+f#YJN=*KhhZhDM3_ zCozPhLPbr8(BRy=f}}!(yIk@z$oTyz=MDJN*7*3rDEhR%Vj9P9Z@nS*3q^BXD~2u&kAZ$8b7 z7PC6CgcnvmhT}Sf`~g-}Mv!7EWzxAEt=%znDS3U}W@<~z(S_jbwK$2fB&w}bsr_!O zU%?kIzDmd!$lESnAEak^5K~I(!xdBpr&90gXp-*XYh-Mjy0V2V4u$jgj7+3>{=hz3 zyJPn(L@Bj?zI1_+iBy4*+E6*Qp>oEv8O+=2Ash%&;`0+Q%%XjU$5Z@rxl4tLTKmm9 zHaiY|%E&~jXf0rv=;D?TL(02~*m3w1a*kcJ_FteRL>H2lRgbg3qwTH*`@!*6KK%9Q z9oJgBV|;v~jh0pG3Pj%vl!`s)*nIhCJLQ2O(ULL}V@Vv>#c>_lde8CR58v`gO+E81 zAMgM49lDT&`~muJT<2u}MFvu%w06aK^ZP?&ZM8R|RO}0UezrBO&0m{RCusQy7$%LC zQIeD6>^<@o8X9|^+Cqut=S-rH@oa{%^duQOm%sP(KaP`}oR}FQ0BdXN3px73 z!K-BK98wdYf$cb0y1{+n%7Q@&aOwIG#}$iReX_@UNM+J2uc*%3e7eDdRW$$@LgM0* zbME|67`t|b*uWK48tOu9$Kgz(k5d0UR#q=A>P&`|xGqIqDV0fcs{b-)5`C%^01ppV zZM~iJj0~`OX%nj=wS)t~sQ^OYG9?&bXnc$l@r!)Zd5ZXr5wF5~d$76*T}akMm(WyI zLu5fIt_$(x2&WU5Y47V*o*8$-vAI7ZI}}>+b}0&wm}Du%`^!w@HfnNh{<#h RQ``Um002ovPDHLkV1h+vN1p%y diff --git a/Passepartout/App/macOS/Flags.xcassets/mp.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/mp.imageset/Contents.json deleted file mode 100644 index 5d0fc266..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/mp.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "mp@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "mp@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/mp.imageset/mp@2x.png b/Passepartout/App/macOS/Flags.xcassets/mp.imageset/mp@2x.png deleted file mode 100644 index 4855abeec1dffe4505ce1b059f43aa3391f4bae7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1392 zcmV-$1&{iPP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1tm#DK~zYI&6jCxR8V$wRwajaLx=D-a*El`cM6YQj=!V*2e)oV{uq`^;Rr)x-jwo;6__UPuY! zr1o`6nrs|zk>M34w&)WAnHeClF<8Ue7?GRD50ID~L*#;4DB?CT|4t{JCoiB@Xoyd+ z(S5Owr0zV{u3k!nI)m>Uy_ntOrx{ma4D#@{N{`NR32XULmcNvPQ)XZE0bssxBk9eaXD%w~$|( zz=sts0_~l=9CZs>lz`Xg#WC2;?y@+(FhRCK#qQjlTsxFcPSzvH9d7b!9Akovxq%y! z)vOEs8ArnhcrsSA?OZH7F8MHf{E(0#7e!)XV+aWj!Q~ueK-AFe((py2hR46L60tm$ z!=L7{W=0&DI%%pPKGMQv+${HSSrN+NrX;?;J|yoEg0VR65FZ~;bZj(rw`I8H{tx_> z_8{IZUr3wWM!%_mC&L4#1~T1c_A^)Ndp^CYY;Ng3xM0Fl!buAUJu{uZOh6@CTYz;i+ge z8lt14*|0E%Waqtk`Fs?bdT4e}!>dcDxAsSLk+pG!r^@E=|Ssw_&rDyke2v$NnjO@n^J-s!b+%xWiv^#^cyos$PC zZn5LWL49)ti<80$5Iw{ES+flQbai&o-`@|ws+obqG@|6$yi8`bz6y+nrT zNnW&=Yu3^0r%3uU8vx@-m%z;F@#Ln>qrK)dS?Q@%x8J7SE|8^{uyr)hQdfl_2zb3- z8tNOUHiLQK4n1x^vJLRgiWq83j*z`_GkeZ=QEwX+3Gah05HefEah_#M6gJ`>hcTI{0FV}S{*#FRh)n(PXi>;`5#tYk_940uJfqQqY=C8vdj47BW8d1cyV9&$s*DXZyeJ^KSoFU`54lz6YWNO0IPh{t(5rPQYKHygF`Wg1lBr0!2ba zHI;o<4ABDpZjm#?5-v@M-y`}Bh~Tjkp^|uS?lC�x~9=@OGc4qfX|LqA*YiXhY)B zVCKUUpL2XTlG0WcU0$7E;(vD_9w)1k+E_8iOZnjdq>6P&135I>mie z;%r1_-TTaVji%^qtjmbxW_=>g6MO00x0y*(5=k$yl6})}3DGps;mjmFd@?(#d+6=y zAjyz};1#(2hEygNWFf^SAezgm-EBvmdIwoCUKYd-^O0F#1l72K2&|}($73)p*vij_ zqA>)8pqJOvcXB_|78Q^@s{qOAX8XCBw40mQd`yK(t)|YRC0rW9tI{zi2C^f(T%Q@o zb^1zr)br>sUrAf7i^SCPWG{P~`ELZXw=d}G1KGMnPqICp_Oe%S4eemg-RT%oZsb1= z;kxfKF5j0k6k{sQjXWdz}J%Ad!0XPH= ziNYXLV{~L0v`jVXNzsdB8MG7|1BrHbQ)dfe-7$Eutd~tqnWQdxlI!j=P+R&(;@tr} zl$6U1EaU0lu>(mE`gwM7ILgKc>8jj_ZAvnljE5<03+L&>GVRWb%jlr@VvVOwPQx(4 z0Kl29Zq6!WF6yD>g=4`aKlCb|$f@)j-Ryhq54c?t&)t%N%IokykO^uRgNk$xe-uoJ zQB9ZLz}lk*9@+0=L=o^c-5!qy0H@Q5$z&oSF#&*%K__la=+|^5g-Cl~8o#ehBp~w_ z^eCzrvR0CtY9w7X;9nprX@qrm&qAZo5ZYVKXPSJx?r>f??t6Om2Paa|e4ak51EbMM zUVc8cEvNZZl|E{Ww_4St#pbYcSfKyF8g{Qe#ftn?{{oq4vyo!iLF<;M=(D@XOpn4H zJr5fJKJ{fmi4Yh}abW=f`p{5{@=S=Ah~ZG{`SbQFetgsYRCHNLoS|m^)C}V3^B<7e zv4M2<7SUB(MMOp>3yYWYevAE*HWL&UGP5!-d?zF%5UlWC(xzET;^yKz>2?kws+u_4 z+rgk~Y}G%e^(`+hlJnhqR1Beun#9h}x){_%12D;L=J6jG$j|yp8OSnP<#LjflP@$C z6&LY#{6sw8SW>rER9@J1_s?`_T zvvfz1CKlpSfkv1>d{`i0yKIhQ8n=vyPy_)~Gq(|t{xCc1zg|{WLCZ5o)hvB+2j;Gx z%lfNsXs7svGG1%f;uSA~KveR#hCC(~-htI+V)wt(_~fW_EGdo+NSo8j-nMt>bDN2a zjpK&}CLdY?+q-oXuQ@<@Rn4fr)|BpJ{y*xev4{EmKgX0r%Iw8#?l?$7ZYas(rIv_s z0-2hlr@H3@+AIe-+uu%KcPCMz@5z4B6H0x{=}~=Eva zI;G2(6^s$c_ND<)LQ~HXWXZ+7w=NFc=I>jSC?| z`|V-BJ}m+Ov)Rnrm)FwS(MfGhEt@uOVqjnZV_XcgCwx0bX238j7f+||a3waogTFt& znQe^?G&=QT0y5@{)l8$F*>Mq=CZ%zE@a2S*JUmwtLqq*h_LP?+ zD!yn}C=@sx4r*&_k<>agqJrILF76F_$-;x#B1sWi%HLW?e$j2@=g#0S`|4=2s(qz% ztxsRRPI?gn}+H#BBLWIF1elGZQQ}eQ$Zk%?H)ho)2~aRW>Yh0gMyj9FTc4u0<1>^>C2rTqsCJ?#60TG7qv=N6 zZYQT|N~zznnuMi~vbWjBZ+4yGxFd8_`%B5V$%n6sR(9}uNg!!a&~s=LuJeO@U`ZiV z6+l!}luw)9-d+--oJ{CHhC0ne!J<{1vq;R{aD-m@%6YBt#5CH4Kvo~JF+ax3FPHv_ z?zWF{Z#~H6XsO}hA!gjN5>HSH#V=HFRt{lMapfT3JC4X##i+8fG(8wseFY&(>5?Pz zu`LRBR5Eq_3Y^OS7w-2hJ3GHtSYW` c68@0?0XEBybZx|jNB{r;07*qoM6N<$f~YLGd;kCd diff --git a/Passepartout/App/macOS/Flags.xcassets/mq.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/mq.imageset/Contents.json deleted file mode 100644 index 2820787f..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/mq.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "mq@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "mq@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/mq.imageset/mq@2x.png b/Passepartout/App/macOS/Flags.xcassets/mq.imageset/mq@2x.png deleted file mode 100644 index 62d74b4234bbd0e63fcc2d2c282acfe986a5e93d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx5M eG`?WL!Z1mKXa7E*nWjL?7(8A5T-G@yGywpH!bC~{ diff --git a/Passepartout/App/macOS/Flags.xcassets/mq.imageset/mq@3x.png b/Passepartout/App/macOS/Flags.xcassets/mq.imageset/mq@3x.png deleted file mode 100644 index 5b94a2263d0ae4c51f921f00255def0ba318d445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv6`n4R zArY-_Z|ZU}C5p5@v_9-|n8Rp+K~I9v#-6LZ60!a34y2WMvk9^pM)N5B2yM3Sws^|> z{N?upv-7X5<6keJBVKwdKg>M!@!q?p^B?aFG3&bgRFBu^Rh^_@a_Zyn700&k))JoR zeO-ZXVIfDabB2u5V*#o53l=TM6iu)SY2Lc2p~-xB)pRANsX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0#`{yK~zYI&6dk+6Hye#zdJLTre-otIyFMmMw2cQ;=)CuL=j36K@ePM z7rwGp2o(G)#H9~(r7OG9N)bdsP-x=|q+Jwj(O9ER(i;1S&7=u!l9}tGG!&CM(@Z4! zHW%ic`|;zRbI)8d;qMh3q;^l1iNbYKtL+s&Wr;$ryV=? zNoa2#7EaX!(JT$iPz-eEFgj9^?s}i5#JIb zz9rodL;giv@h;;*Ollm+(}WGtw1B(E61W{o!fM)i5{tZ2$$PzE#j|8}Z`G2nIYKFL zaomG?z-v9sr07*qo IM6N<$f|&*>#{d8T diff --git a/Passepartout/App/macOS/Flags.xcassets/mr.imageset/mr@3x.png b/Passepartout/App/macOS/Flags.xcassets/mr.imageset/mr@3x.png deleted file mode 100644 index 2896bdddd6f5c98cf5baf228746cee4908ee1883..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 947 zcmV;k15EshP)2W8z0Nk|;_AsZG+_Xf6^V zie9`41rbD0y!GHgya@#n@gV4-l!}G6k(QQHY>Pyx62<6ROEg9>CfRh>-HH1-J3Xk; zl>Klfvup_Y-F9c5_xb*lCJyq&pCyI}j;$8suUp*u7i>pzmpAQS&%%#HSdn@FV~b z1n6mafhd@4s(y+2dWAjK75+^)o7!k31NC(M)XYTu?L37NGTVM$&EXpjBn=^Fd}W@o^I_SDAV9lcFL>Xd zV;<7xOu>JQRuqiu@aqVo_4>EPcb;u-@w8CWw@mboppsLVB?aP zc+0F;#qu6eC6kY93kz!X#rWvT8g5T60^sVXhYr8aZ+P;?bu866|F9Y@DOu8G*@&JJ z=?X+xqNbUQ=MFy`teNpU7*+{})x2ZZsA)c38YN=U1lU+wFOm{dDTxCu6PA+|;^5i| zw1kA7w%&tmb*<~;ZjQB$QCFU_6P}fTJH^qqG5W^~H-v21GVlY_-d20Ro(BAEB#0N+=<7>5a~e{%?c69aWj+1MX2i>Kc&@6~c+!VSQlCXGA2 zL3TH3?^PX9fW6B#?)KiNw=oRBwebqh{IAnlL16!fwikK8o#NV#2Q*eBr~i8xQTb}9 zieI037}UzATWO_Y(B_S?Ef8hj%1N3RC#GYDqY7KDv@jYgD~%C=CQp(dxBtUhZ#?Vu zxbdp!s+9ee`d+x49lxyNiLT5>#_V6LhNB9b`&u~ncMX{=Q=&?ltR^z%t3z}14=uAc z0S;8;UDo?z99cKUuEucA%^yukd^_yr)S#b1t=y^9&Kh}xOElS3Jw>Z8j!#Jgpe1Dn z!zwpJ?!1rjb}5RHQZR2m&@C95Ysh^A&UKDbBG)J8M^Wa73l@ybJ!AxaFSQ6K_z#^% V33-*jX#4;G002ovPDHLkV1f#%#3uj% diff --git a/Passepartout/App/macOS/Flags.xcassets/ms.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ms.imageset/Contents.json deleted file mode 100644 index 9da4de8e..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ms.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ms@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ms@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ms.imageset/ms@2x.png b/Passepartout/App/macOS/Flags.xcassets/ms.imageset/ms@2x.png deleted file mode 100644 index 7f357cf36f6d5338418526e4a834b20c0e0228fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1383 zcmV-t1(^DYP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1sq94K~zYIy_S1WRCN@`KX)G>E4v1Wyd)lh55_kliBgi9qgHBSH9iYO z9nHy6k+DpzloUtIk#U9@bH-AKW}R|0Ck+J`e3A-?AZjei8w3~GW!ZIi@Ae0R5=7xh z=f88#_x$eX-t+sN-xV&+UD7mc)e@yWbUd3kpJ3m<3`EgRn@yQAlh$e-xm?P|pMsBVP-P@7cRj2ha=q38hQ!=fMj{%#hbCFsTc+ZQydY&Sh0e_ z!mT7EOhYP_bXnx&BxlbTlQ`^og}#^1GDxGL*n1RX#%$;K@w^uU0m!~kLfE(WNuLo< zO;QrI%VXJ|5JgtjdVGCdI~0V4`ID14lDCg1tDrW~;^7E1V_)xuN3lCFM8JLuW`>Ivg&1Q@Rw=k&O(TNV&?a$`U z<-HkjHmm0#^XDJP2O$_>=FH)wrY^*xLd)G{%aD(IonL*XGW)rLZQD)}A3qIiZ4C{d ze2jJOBI16tk&|=(g}y{P2!M-=f*m{FCoX0@wYzt7U#+HW!8&4h=MfT8%8Y9Xl^z$FD{h28ACce$ZF7IG3asf^zINJZZMOw z=oqr4ODB3ICeFdRx|BPSkx0YFvn?=|qifFt5EA0miN3I~iin7vtXnsgt>2|!(VS!4 zh7DXzSj^&ITChk3GGB`UM=kmL(_9*WtG@GHWnl~LR1WBwc)%}Rf z)YQ2(`#(Qa>WaxHPngNo^_`FLwQCR9@K}Xv)VbfGcZ>@?Kf#}XWf&7cD(Wruv%w^}dQc~QX|LH_tETtLxzkpC|rf@ovT zlPa1FM)v1qfz9&&fCzv4iHT~+#H}HN%En6QCxx=khe6gGAfnMkK>eQpOws?&jp7zg zm;#vPSHc0k61T_IsGjDX`ndES$fHs{2^D)#nwmgi$K51SSpljM60r1T*3F-pT$BdD z9}ydYUTV#iOXbuKjcRp_0>hLN{Qc}WI|3p|<*lvLIlGg8!=d*;o;S$zW{Cp@Q!EpMb-cS002ovPDHLkV1nA#p4R{X diff --git a/Passepartout/App/macOS/Flags.xcassets/ms.imageset/ms@3x.png b/Passepartout/App/macOS/Flags.xcassets/ms.imageset/ms@3x.png deleted file mode 100644 index ab33c26bba6f85c211efaae5b0787aab7e87a4ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2193 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 S<6toW0000|gW!U_%O?Xx7I?Zi zhIn))*R0Rm_sgEynd2}6lN;Nyt3J#x8Bz>h36y~Ffk}V7|B6Z<-rvpqvSU6^&-w_y z|BuC!6A~`Sd$b!E7~PXH2{Z<&&5${CY4*nVhW6*rHv^US$KRN5@X!A6hFOh;3EH-7 zZfzI6_?dgGSY$Wc`FZxMSHsKm4cAQN|83fv6u9@L(y;>{>Kk55wO!oB23Eu))y}}+ X;-K_&Mo?=y(5(!fu6{1-oD!M<;gwuL diff --git a/Passepartout/App/macOS/Flags.xcassets/mu.imageset/mu@3x.png b/Passepartout/App/macOS/Flags.xcassets/mu.imageset/mu@3x.png deleted file mode 100644 index c4f9996bc0277028fe4a725da2a83c535943ada6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvZci7- zkcif|*Ee!DIPkPSbP2i{9es~&o8JSCbq{p<9*Iu-)|c>Msgz}#JrtgOxEox7f zcrd9gD*nR3ANz$tpU3gAfC-Od^7rfWFDl!-Fj%Ks)zWU_->sM-(bT4xaj7L^cMH29 zr}G5^7GCEIOE@>a)wJBVYm@$-Hy&UG5>3aJc=pOYe8|4Efq!Mmr~Q+F&SLO%^>bP0 Hl+XkK@7Pfn diff --git a/Passepartout/App/macOS/Flags.xcassets/mv.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/mv.imageset/Contents.json deleted file mode 100644 index 53757b3a..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/mv.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "mv@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "mv@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/mv.imageset/mv@2x.png b/Passepartout/App/macOS/Flags.xcassets/mv.imageset/mv@2x.png deleted file mode 100644 index 7aa9d896ecf311401f5f21feaf039d3500ebd28b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 388 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O^81Fj{)L zIEHw1Cht*6*fguLQL!aafl>K!)-uLY1|f-34l58&IH&B$1z;p|LJ~iIc(=o&Wx`6tQknYH!J~G}>TbXuM{} zp7td7fF}Z)ae{64>;D_<`T4CdLH5&|&(dsRX<}@Fd5w*wCtj`oE%EQq@6!S+Ut4Y9 z+xhjIq4T$Eacx!YDs0)7wq3GLf#2qqrYRqO*e)*l$23Rs%h6DYbNhE3EqH!cxb1qp z{e|m_JU{?sCG7b3i*f0(2W1^M!r9m+FaNK(jp44(B-uwL-{qSx$G#C2(LJyrldZ>! dMHYk^7$&U_PiwfnA`lp?44$rjF6*2UngD}Jrb_?- diff --git a/Passepartout/App/macOS/Flags.xcassets/mv.imageset/mv@3x.png b/Passepartout/App/macOS/Flags.xcassets/mv.imageset/mv@3x.png deleted file mode 100644 index 66b1e87aaa4c7d4b8a9322cae37d4668d7585f5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 551 zcmV+?0@(eDP)0O)9_|8v{9Eee>42 zj6|kiD9l!S?p*4BXpbhT-M@YAaXqa&ho~NcnofZ)m%ZZyVyA}f8KO7h&&ea)I>^EB zJRci+h+iZ`BtY;+$_k2!^o|n@=nTw-nOL8diUNdg#G|UrbbX;pD<`}%DJ}s*B3&Rcis4H1!Q?HK@3V^`vMOGHFcqySJ9MdEl% zL`0&-Zvd*aa*5+D5fS;hy$C>CMLqexrxFoqh=}~yim(=oQuHLBk;XoWh%`h*lBP-L z)CYbUy9~YVW+2od5%UjXD@yFdpnjsC@X91(&HYTh{Y*{qbE!DlrSatQjQ5eRd|LcQ zWkAR8%cUew<8R{7_N;r^ev_st{!MvY|08dS->P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0rN>jK~zYI?UFrfR6!JmpE)!4BOx0kK?o8H4Mw-Iik%S@iwLIDN+H+_ zf>nNjzre;u6tqx+wOAN1La-2o*oZ>VZ2XAvBe+R+v-i#%3maYa?rxz;u}^#EoO#YW zW5%6S9_L}04jB@|K5G~hgI8D%%4$$P1nqPb<4%@cCk+CeN-PtxP*eCCl(G}nRC(`} z{Z?34+~xeNxxP`1KBYuIBAmwFwyYB?`_YnhfJd&Mvr zTh_u4iMh=4+ABvR;Y{i%TOk9U=Yg5JXSU&4w1(rcrECpnQ^&Ph&Zid1v=lj?S}gE3 zDEAwlsw%hpV(%eiBbaIwd6vz~Lgn&BIsQexRHhLf>yr|t{yk)q;ZCnRF%H87tA zJ_hA_>34~rxsW=p^hH#>GM@z^V8jX~Q@n6tA}c|8u;uxt${Vi;Fujp6y^$3TRUiWv z^1zZ;5(AAE-^AQ2MGrUO#Gi_Jac{@`ZR-?f-9#*3)U?R$A7kKi;UD>*bVZu!iZruh zMBJm1T?2Kuy*^1E2HlM#L!(cJ`?ghNtWq7_F;CMKX{IaEOjo3t@BJ~nDmy#=`~Uy| M07*qoM6N<$f;}z)WdHyG diff --git a/Passepartout/App/macOS/Flags.xcassets/mw.imageset/mw@3x.png b/Passepartout/App/macOS/Flags.xcassets/mw.imageset/mw@3x.png deleted file mode 100644 index 16214d0abe59bd0e1c33854f8a73ee8a87c53f34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 848 zcmV-W1F!svP)Z0AH zP+I6uXz8}leHUE{{)7r{Tqw8@6+zU}QraM~#i9gCN}8mJFUh-i=5%qN8!6?!X$sS7LEBlvJi|2oKKanMiR~@ioP&++u&+9@NFmO=RERb zW@KJzsnHUc^-9~|{nT;2+h0$6G1mVw>y>F04yo`*FxJhun@4^Nh63M>CR}MXIQJhC z01bhU8jhQL8+#+{+@X1{-FqnQ=N%n$njC4)~_*;JJpQ?TYu{X*3=MV?Hms z)5Wor&zj{9Sb_IaM;Ex2Mb^z25on4qr2+yD;ZZ1G^`%ebuvgBf3JA+qh&<`F*iV{{ zxrQqp^f_PeNjT+|j=^$-rD!}eh`>?b-;tEQ>s-BwA|tn%S-%tev0~O{EO7vA;R6zt%A6t7}!6TBK^!B2}Xn zsT#FN)u=_PhW~u-$gp!52>!v;>%-1rAiOfARf|-OTBK^!B2}XnsT!}0h@V}0wmR%2 a2Eo71OewnK6g|5D0000X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0eeYAK~zYI?UXx9!%!H8pOk35wBA~!Rz*cwDpbo7P}h8I_PNS>3^wX#LZej~E% zCU2U=Rf*JV5T&<^sNaW?^AL_kNNZ2j*Lf*d#n#n?L!*qhC=86uFj4zXO7dY0PNGQ) zLy}l|Li$4VO!OC!!9+u>b%707*qoM6N<$g08*L+5i9m diff --git a/Passepartout/App/macOS/Flags.xcassets/mx.imageset/mx@3x.png b/Passepartout/App/macOS/Flags.xcassets/mx.imageset/mx@3x.png deleted file mode 100644 index 45ecb0397e6d55b2840359ff66eac7034b3255d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 742 zcmVf`Ub?s9Q*B9V{$EN&_X`OhwamU0*xyW1P;N=f5(C`R?=k_+aKg z!$@>r9K`@%Iy(oQ@j z3cmFb=BDSkbp94jk6*Ml9dC9Cm$#gjmNL}XHtUN^=({m;8*bxv6(Y+S;+3TFTT7N? z91bsz6W(+w{`Vb<uK5z@1|c0fv5y=<#?6Btskj Y0A#P$Tv=v_4FCWD07*qoM6N<$f=Mh)o&W#< diff --git a/Passepartout/App/macOS/Flags.xcassets/my.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/my.imageset/Contents.json deleted file mode 100644 index 6e53f21b..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/my.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "my@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "my@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/my.imageset/my@2x.png b/Passepartout/App/macOS/Flags.xcassets/my.imageset/my@2x.png deleted file mode 100644 index fd19357d19aaa4b0988e634bf704632eee4609cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 949 zcmV;m14{gfP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x18PY`K~zYI#g|=7TV)u>f2TYBK84akGd8|5%7j3X2`EOTi^+@^GQDud z%w$oMd238|v8zRIjEi0w^~(5>DH=(uh*Ay>(AjKQ_V;QJ zW-?aM+1*jikJoSe=5dEP*c-y2&oX_b7Wq;=*{txN^Q`^-bX*wL?4iiAM~sKo5`Z~h zG#gdkKQ+nWK|im5a35cMyN^iBK(&-&C!xntm1OOP1%p0IDy=ITmX>GYRA6PlXmr>Oa7*OT{ty78Q_cUn3hc6>hOTi?|zLM}HWX47h6(&fck03PVt;Md>k{^^15XLi%o8m8MFroAOXGPR3~ zzt{2ES$E+!0CH&+b!)2x5meE2|ACtP^F8-d19R6LoH#bir{iw6wvC0;e?8SqT}=|F zJ;}9|YQ}wy)Y_9pBGTX^^j!E-vxg$feZry66VdYv+g16ZyK$TGcgBb+23~y4%j{KW zA>eW(sIQGPd)0}{kzgZiE*d!4D@)AzZ3sw#S7ksmo_JZIPczkcOGii^ZBzj&ACh@@`ETf?X?iReZ`l8v9*=GZ@i*8 zr^s@X@X5W8`;t1tKsiKe^jSPrH#O(7snj{KefDgy91?l9OG*h*Av!kpr*&YUrJOQa zQG7nXn2ERtIZy&J%6&(EE2J$6IXJ8 z&L(iAx*B~{n1(WD5wd?*J*7oXTyBX+cW%UfGBIGxKOTu~4|Z_KzLcjPK0`%i4#n%b z`1;sgvkK{ZKYuIRSWX?;2GRIq5@)I)&7P%RIKRlj8MV+?O@L zBWt_yP3p*AK8z&FnkG9Ng1|EYmGIYtUy_?KNXr>3xle9EQK^;5apQdS+Hsm3R(5Q4 z5NYsnX*`UYx)f|y7jGS0$+2de>Y|RSO~hLbnhFUxS~jbT+>Aj0KC|B)AVQ~8$jclc zQa{NTKcupMS3RQc%AhEdhteV^Zyd-*5ENEtU!chmgWD@!6T_IFk15zyD>dQL^mBW6 z;9V)p0xeD}+X_1g_O7AwaC*SR^pr8$ z&sx~?Y!eNwaqN4ck&@j7>?~=eWOo6}lP}Wwmr3FIq_^A<%E$k}uK%d>qZ^x=M1 zXJ24LULTIGXq?v>f9mX;jb6Sxk&MOY zgSMCDtU>^m*v2W{+)nX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0!2wgK~zYI#g$8F6Hyd~zne}|5<4+fqGBJ4@exHd2u4wZ)e0>|=%y51 zD1!Tn3o#WJqPE73=*l8R#Dz$uPziz?E7XdjMo@}`ND)mcS{scoZ1cJ7(oO4F>?c&YlCf1MEVw?nbOwI|^Cw@5i%s>#p1W-KwE0od{ZUY>*Ypur#jwkhbp23KxFW!gXx03o4s2p(L5<#GK%ltm^r0YB$7 zc;71BCpEP)Iq8k(v*K5WwF zve{V2Oq7XDAVe+=4Iymb9?F54Uy;T50;!4B zM+j>wEB8NJS$Xh;_i`@GxcAozVbQVoDZ-cIK7WT$m+q93l&mYm&v*)bRk#w@@CPta2wvlXQ%V|9=0WE!B=X2ps?API!CIolh+`vfvi_OsZulwT8E+uWu1#X(d`BxNMX62@FGFM|YPCb#`8lBu9_*9@{w3N{qJ~xrm z@FXrTNS3^qxyYsHMVUV5I=)B@%khA+8cLJNE>edz(r1w}XL^zOcp7tymL)GTi?re~ z#EY6GFD8oAq8rzjBuic#6KRwlN)08;k{4-_W~`FlL(NjEJZ!f+XaAp}{{!dP(5Kv? R0b~FG002ovPDHLkV1hNLtFQn7 diff --git a/Passepartout/App/macOS/Flags.xcassets/na.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/na.imageset/Contents.json deleted file mode 100644 index 3b73c1eb..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/na.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "na@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "na@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/na.imageset/na@2x.png b/Passepartout/App/macOS/Flags.xcassets/na.imageset/na@2x.png deleted file mode 100644 index 5bb37a9a7eda1028ab0022dbb39a43c713d04696..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1104 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 XpT&*>4$MX+00000NkvXXu0mjf)46BV diff --git a/Passepartout/App/macOS/Flags.xcassets/nc.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/nc.imageset/Contents.json deleted file mode 100644 index 9287c8b3..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/nc.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "nc@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "nc@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/nc.imageset/nc@2x.png b/Passepartout/App/macOS/Flags.xcassets/nc.imageset/nc@2x.png deleted file mode 100644 index 62d74b4234bbd0e63fcc2d2c282acfe986a5e93d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx5M eG`?WL!Z1mKXa7E*nWjL?7(8A5T-G@yGywpH!bC~{ diff --git a/Passepartout/App/macOS/Flags.xcassets/nc.imageset/nc@3x.png b/Passepartout/App/macOS/Flags.xcassets/nc.imageset/nc@3x.png deleted file mode 100644 index 5b94a2263d0ae4c51f921f00255def0ba318d445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv6`n4R zArY-_Z|ZU}C5p5@v_9-|n8Rp+K~I9v#-6LZ60!a34y2WMvk9^pM)N5B2yM3Sws^|> z{N?upv-7X5<6keJBVKwdKg>M!@!q?p^B?aFG3&bgRFBu^Rh^_@a_Zyn700&k))JoR zeO-ZXVIfDabB2u5V*#o53l=TM6iu)SY2Lc2p~-xB)pRANs|gW!U_%O^81FzS1{ zIEHw1ChxiT;!E%6M}mHBj4bzgbM_hYE@7T)7-oFD^6}>QtpCCy%C}qo|6izP zI4Spt=&|zk5p}Di{yY*gQ;hsPU%XuM-dX+BAB8^|n3Pc04`uBn$7_GOi|zzfrMyf_ zSm0&(>BZ~I-93Axw}cxT7_>~tS6f-cKQ1QD`V-g$#3`_=3 LS3j3^P6SkL-%Eh5WFt%}VDOg(J#9YGt#^jLyeB|74!k$mI0we&#c)T;> zZh`_W?gpq(BC?@GWJ8I_h7ysD#E2MXemm|SNMKdT(gXm^trVayW}=Eh zBnxBEdxE&Az`ws7yKZjo!YbFmomKKo@;4H6T6jAxVKflG-q+?WWX>AkZjLgR{;-Ss z@k9v#7!3qOy+R#JEArLvz;9kpt@9rm7>XWcG_gpx4a_pBVThpHri>;YEyn&*Od3rp z!rk0#GK|T6*mc{5S=gCcXKs|BTMlJ3{a4F6I)|3FCkLi&SmhdZEWHTNuEVK%NEP?S z$BZ$giu-V?9(Z<*IyT}J<>MW}&x*pA^UxQTqKX1L@D3vr`A>|S{%zbnkdT=F5hWrU rN<=o4h-@ek*+`6t0fwu%o1owu8X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0y9ZOK~zYI-PTQO6Hye#@&DunnoLZjCSt)?RK$oc=)#p~78Z0>)Ma(q zoeS{`h+n~_=%z&kE4UE^7j3acl2Y5+rfmvInxv*CO=>bTnM{(IjJqP;)M=Uo`t0uI zaQJb#2aeG9k`KTmcFDubgX6CThVSfF_ivtldOY!QVdVCIl#bf0G;U`O7Q{5omV4|A zWF;MKxkt;8nbj~;$+7N2B(=k- z$5~8SA7#ssT0>(w8>Ofg$knr$4N!y<86}RlrBU{Y27gE>j3{Sphz8GOenWL(! zI0YvT!AZGULe@(prB&WfzvYjz)jo*UsFGF_7&Hh5t|8hzXo8AtlxdneN1VqHO&?y5 z51U}^u^m>5y)GZ2Gxu;I;y)bViD!sNYKkqbz|hTSTse6iK@czu1H0YcJ_tZ4*iR_f zzkSy9D&h1bS5Mv8zO#yeecDHU%ho^V&RBktow*-;z^&7FyAKgv9z>U?Wyrx=p7$ev Y0dV2&f(r@~B>(^b07*qoM6N<$f?6sjqyPW_ diff --git a/Passepartout/App/macOS/Flags.xcassets/nf.imageset/nf@3x.png b/Passepartout/App/macOS/Flags.xcassets/nf.imageset/nf@3x.png deleted file mode 100644 index e36482930308429c63716c68997a4f56c5d16cd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1109 zcmV-b1giUqP)e<#UllbkLeO_MC$SD9|p*0tzGC!<5VU8w9% zoQNQb;>AQ1D~K10B3_FZB7!JWL2y(BJGwcoZQ16wVyjJ?E^F7XByD1nCOK)7oU~1j z7hX7_H+JulUi5$Ya-Qe+{Q18xFXxp4?=iq8)%{F={Akm|fz8(6u3zT{;!k|lx#?aO z;GxYHSEz)5md)0S>}3)$#8%L@k*%g}BmYIy(J8|jh8sz%DT1*PqcO%C8J@XL&%{}y zF~%AhnHu6oY#fK;B%Mnci89v6L~NWZp+Ux{gS_Kvq{KiN+UvQ}#Z4lu~b<*8rj*n5I|{cY?pIq}%McpJQ2nEjqBQ-ekz#T!{J<|$SS zM3S>)N=e36hme3O>m2m9@b%TtICJR}(OB_kWPCozLMDo0anR;G!g{$%xbO#=;u4FM zISwj^dHTrnqOoEV(RH0fE=E>Mvm&X4^24}o_2`n0Q?AE`!m_6F#lWY;k`a+;v5CYp z3%oP%8W}AGW*zgoj3hx-ud-auQKCSpQeZIC%QwUK@cLtK3u6mSq|vjR7hAjVS_1@3 zO(-=AI#6eFAXWZGwYu6l*>nm;t|Pv*AdD?Ek#ID`LN-cA;|Z!#6{#u_&V(pSMGm{# z@kxFXt1(iO#a4q(Jz@;9>yvu*lh}a07lpBf8u=-Fj@iXqgc74jl8IxUM`&?8K&F(UrflV;|0&|-2+O+4aO4_;<9))| z!nRggO;Ib^aVRd9b(MS}!;Rc1Zn*);0vFY9sjOA7%68gZ?bxhVR4pS4FV0BMNH=rE zNlXzPTln!88u6+Fw4zSkp9J93e`LlxdH$)xk+5+2Ew^9YSygCCRxM?VvHO+betbY z&#@$@=$SmrF2`PW*EdnW=3zcRjaDx4>e08z6wu zen336h{xGLO-;=|>%DJsajJ)xkH7J+?|p8+C^R7WLJW8tH(XyS=S5;R)c^9Xl`bVI zua*sE7;eO4^Kro4Ec6!NNQV5g->%c0U7YUhG8(h}_Pgb@ZDgy-0&Mzy#*8@*q(uyJ br@j6H9!iqBi(KX?00000NkvXXu0mjf2m%ui diff --git a/Passepartout/App/macOS/Flags.xcassets/ng.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ng.imageset/Contents.json deleted file mode 100644 index 497947a0..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ng.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ng@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ng@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ng.imageset/ng@2x.png b/Passepartout/App/macOS/Flags.xcassets/ng.imageset/ng@2x.png deleted file mode 100644 index 31ca8c399273e98375fa7602ed015aea191ff53a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx5x6)G^7tdQ*FL+YiX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0cA-*MmpxJT|bjULv_+FmxgYydZE>3`5{u057^``>g zC`7UpB3TNNEb3@ysku}u8wXf=NQwqAK`PAl>Ud#OZ7jRhR&yrza1!|>W1q3*0-)j^ z%C4Zw_1sx!?f0tD3P~Igjk?(GBJE+q;~>VFT16)#lE^oSM41dlA1^pltDIjRb9HwI zj>KPCqqVh97(5|M`7dlFB1R&(Mi@jIT|@|lRMW)a8P6!B0{jHf0!LvC}ow|{2FLJ$PYS#LS^sfT^vhu`e%tl-9r z9cBQDwxeX{d`3-`dmb$`-#Ly?-0VH-dYFX<=!W( z6>N5w0e*{EO%R@S8S8c;M#q5pBe6I9#?kkf;r@gl(N_S1<}z7wfYFJ|XtxuY414&s z6R3I@lPBDHe2YAu!ziD+jJS9G5uOZ4$Gdp`95OiND%!0`wEvv2eGwrf54Uf#uy&1w z{)o_*d}(_`AD?jZ>I1CRj1OPaJbA%YG)|9@`OYN(lI)oEr3MXk7qt?T<{oX^;LOSu zCRs#X%{!F<*Y8L^24HuvjcJX@ymO4$AeEaabsilK=pQ`&YppAspITYm2R!ftjVle5kfi_DdfvOi9V1g1A=fB6|}Kd zlP3exxKFLQgbF&YBa6GD7@eTgLyVaqgu>(_f^Ze#sUqSKTle1-BJ#hHJH6qwJ2+ux x{zod2vQdeYjY_0!R3c?#W<(@{XVWg>f?qIG%`)`BU+Vw>002ovPDHLkV1ikU6`ueA diff --git a/Passepartout/App/macOS/Flags.xcassets/nl.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/nl.imageset/Contents.json deleted file mode 100644 index 2f73923d..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/nl.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "nl@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "nl@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/nl.imageset/nl@2x.png b/Passepartout/App/macOS/Flags.xcassets/nl.imageset/nl@2x.png deleted file mode 100644 index 8f28328d264560f40b4352fb20ad09124de39e9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?XxdOckn zLp(Z@ZA65A|2W%u*@%ak!$s%pJOkb(%o7f-aJ~S;jE_${yMO=l`{U(G1=)i~4sL6A zJ#+2VUk58Du6z8mR!Ej|SV@4e316aR(Z7WU*2mYgT@lR5(P`2No-TXcqVna*2CE}Z y@qWn{T+YJGeNcGfz5V|>-Yv>?fB*d!W>{>d^m5z#FDro#WAJqKb6Mw<&;$UHi&??| diff --git a/Passepartout/App/macOS/Flags.xcassets/nl.imageset/nl@3x.png b/Passepartout/App/macOS/Flags.xcassets/nl.imageset/nl@3x.png deleted file mode 100644 index 15ce6d6a912b85cb5ba09398dc6462020dae8414..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvZci7- zkcif|*B0_NFz~npdRZ}Wvb84hvv8f}Idjk>he67}>2OkF^Cp+cakHw0@AzwJ{kGWa z%Jge)xTx(RXGz-w+|CyaSa?B%?<|4bHoK~3ZbRd4xrd5pGYui)V9DMyr)NF7e(CLw v#pXLailcZO4-1%piA<+0*{{{BTH57x>;+gA*!oL>&SLO%^>bP0l+XkKAcjUr diff --git a/Passepartout/App/macOS/Flags.xcassets/no.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/no.imageset/Contents.json deleted file mode 100644 index 04face2b..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/no.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "no@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "no@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/no.imageset/no@2x.png b/Passepartout/App/macOS/Flags.xcassets/no.imageset/no@2x.png deleted file mode 100644 index 6a5ef869e548ef78051638da2f5fe833fee084fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 377 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O^81FzR`_ zIEHw1CfBS^+&QapqGAgpf5O)7Ne|}UJN#hQjpG}h-za4Rg0{%Bg*=*d%Xr@R&iRn3 zDz+wVQJdx!hQ)l2>@eJXOi^df&y}ZqgdSCh8hUmlil(Qeras79z3_(M|G0fYX+Kvk zRz9|1xo^bR<@r4I|NgWV@2~xT^uxo~{%maO>|L^l3LbV@EcUN>ydO$CM}gQu&X J%Q~loCIE|qoF)JO diff --git a/Passepartout/App/macOS/Flags.xcassets/no.imageset/no@3x.png b/Passepartout/App/macOS/Flags.xcassets/no.imageset/no@3x.png deleted file mode 100644 index a14878a795c828a2c18b22bfa319efd4c47e3b0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2rPgj9s2C zjv*1P$u)~JD;pYl4(zXJ<5N~=FaF*Z{3hD(Zlde7=y1^=p)=Y)|NlPq^}N3^dtaJb zKAX%HAt5R8=H$f_YnB~3aN_jg4+&2-+ZJD+zv#r@;GhTTVh__39z79BSkh(8$*g8| z)4AB-)x?8rZnj1@4y#?`EXZ9$!tP2M1Qp=Be&q*u}a- z!oZ{BLvV`T%7&-c_Z?2jOxe6SB_Sd4;Ob}Z-&XU~%iAAa-4@mG)Z5Q8Qi|38T~fl6 zr$D*qx6bm|i>*}wI`ZI#lW)M@xT4(5+f$XYp)s(Z#|-S(wqEy$f5rwfD(;W(M}IGo zu(X^ZdPDNYTGOS0YR)s?+5Y(TUG(|?`uqFsf1f|#F#Tiz!<_%2Pwrme&jZF2gQu&X J%Q~loCIHo^(SiT~ diff --git a/Passepartout/App/macOS/Flags.xcassets/np.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/np.imageset/Contents.json deleted file mode 100644 index 1757ac7d..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/np.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "np@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "np@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/np.imageset/np@2x.png b/Passepartout/App/macOS/Flags.xcassets/np.imageset/np@2x.png deleted file mode 100644 index 9c0ee9267dbd78e1dfa42804924ff5f7064f6c61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 944 zcmV;h15f;kP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x17%4>K~zYI#g>0e)MXsUU*GTjLIVky&dZ%#PBu?b3T?xdOm^a04PDI@ zw7iYfHv0p({IxmSj(N0(8L6`~j-_Wo%vf!0mo}-bQEQli!vbbQJf_Ijo!)uCdH3~r z`h$5zWKef-@9p{H`+WBP?6v3F=ly)y^i=gNYq>*=j)p}NbygN5K~3(*wUn%`;%oP1 zk|i17$@@2`z1F~&mVJ~K*W&e#BnJdQN0gnn+j#MtdbZ_NaI*efazGv^NBB50beiFs z8eVw18jr`99FU1k!PYjhQ3eXxaSbbRhX9%u7W@c>9JoYQLJ_qk@*~i{p4cxn@B%Z`gb*Us z(<3gYrHQK>H;PV(trOa1;+ej|sZ^vE6`?g633YcfcJbo04Wh`jrWf$rYBTRQS79(@PV4yxeWw2F zmxx9QwzUD!&QcD|+rfHM29hLA+iA={O66`B_PUo@x$?39Orm~RVFQC4kUQuzmg8)- z&Il$RO&(uaMpCQM==DfC9p=$rFrGidu5I-QA?5}|m7b2yXe8U^!r0V=;rMapty#nK zp>DD-G*e;uU{*men<~AXk|g@ptrOwF!PsDrD~brX-J&l)Uvz?~Nn0x_EGH6~9aD1z zAxJGOL~Ag_0424WwBlmq1q%Qu3I0sx7pJHycg)Ix$N+L`D%$MqsZ~irmy-kX1n-vM zB_93!6xP>H&JM`<$Oxe;S5R)=oKy^Tb)gIm!T6sq@7B9Np=))qXXl3r1yL!zn^FzK#IwP2!{#x_Tp=7t#l& Sqd%+w0000&g@|g)EWU!TeGx<)!d38O{L8A00AL+41Raq)9>3K#LbI4 z1my14Yy0i?d)Vjm{(L{*@AvcB_oVCRztmmwOn%d?x6bV2e~cutO2kE|EG@|8^-Tpt zM(RSXX+p@d1Qmlj=-l-w>C>O2va&AJnkI%uS|#d1)Pwkt1d%#*g;=uqHF4%lxA;FH zpC8n0+S#G(Ao;s$W|@n5XVcfHstDB>j|)j7trE53!x1k|63xr{n=z)(;DeoOF`E-Y zAaCqwWNFUxkMPTg7ctnl(|H{GU!4?vbPC1hBzHj?8jbA3Oz0U^m5gAi+Q28)U3{~t zoH^!Ve*E!tu(XW}zZp3nBMz20f5+I@!MgG@{Q2i!ELxn&lquRE$-FS*nkP4j(|kU- zgPw{CvQjs&Z{M*%=^8iV9@0qcJZ0U_&(>qCS*CE!qAa$4{4jBG#z5#BGd?pS>-B^u zCo?rC2VHhHvTkIxzm~J?RLU_OsK$Kd2HxLx5FtXiRSy@$hKvkR58|*+CmL6-6wdZ` zA>3|ZZ)y^4&ps=TL`REy5XWcC@D$MDYZoyh({R7Ie(v+)#EJHh6hzNrH|6J}F&Gd| zC)3Nz2}?=>NkU^XVaUy;_p{H?L`4y`Y#GWAKX?sBEH?5T$LZ_nV(s=52qfm`rVaH! z!OqC9F)_m4+$>s)i-qcPjflAY)mMf6^l4A#{9Sv96!Q&ti|n+GqPEr&{EVnJ8}80d zE-x)bl1DBb!-55zD=nqBqGHs6iNwwlM+0F!=XkuViI$cg7UpFT9v;}O+DmbFSs9kQ z?!wv9;`Ncczn|6(8#w#ib7L|p0k?~j?6U2o@sm%PnYxh!2O1_#v`?SDdM!z-MW2&H zSW*)1zCM&ghfoIwChX*UF6Ry1Viql#!@JuaA|}RgvARd#w`m+kMPf5^>CjID$^YVw zkX6DZ)$e;n)$)}RN9-kfHg8?Q^5t_cO89@d;KY>?+|6%1A~Kn^Fq?w==V7%vCgd#t z-{0O|8>dd4BQ-T{LUw#HrWJT8{d{1nVD)$K_wCsft-WE=1WXoV-bxY~pwwE)qZ=h; zS>n+(d4be5ZhW>V5g8c(#1s||-DhS3kj&p~wN>)MljXd>ttL1M$B0)(wDIw%ib70b zA@1&O;!8@<#>S$XJ)7p0E72wyj5yn~I3LLC@D z8ykzNC^#A$iDHl(XCsg88|1PprjeC>S%^|GglB3E9y^A+r-v!&={`RUOGrT0>lvu2 z@jRPB52dzh);&CQKz8qaIRO{!m*J_^yn3~8E6S+pQ=LxH{>m%jm$0xAZG4=*P*hjd z1!*FU#51+5b#0ZP|i0I-34(zeSgs zNn1$?veC%Ng$t2n8MVKERDt6U<1dE8#Itc~J{5b{lb2@> zh>u|cdXPJd4&G4qu>6j;NLPUk1cv^qcv1P1rMJD!!GnPdMk|2dhz*zn026i! ztUqzbEpPF4O)>N4rA)dHsi6P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0gXvSK~zYI?bS^v!(kl2@h|;tHq$~@D=YiQPQ<(p+z6aK(0Z7o%FkIvInUyNgRODT&aL|ZKo z4eTFXu{6<+CN+i15;J4d>y%kd>`DRd?!$jBi3&;6s+hL7lADu>S|wmMWl?I)p|f{} zwe7<{M?jS8o0XA9gRPKAj?{v){*Mp98;9ha=K_YLxXe?g>DKd6e`gW zf+8g(sC1)In+hUa2ttIm5F~KXMO-KdCM1}7$x z9-r${+QBdAJTHtMr6v49;I!70?vzb^5F8SLW5F(y&i|vsT)tszrI@1`9s3Tzy_=_~ zc<>5bXm*B^G0-$kT@xIFplxDWeL}AdqH>rFUook4L$^;qyjzCPvZUa)bM~PV;Sg zp2ECz0M6#_XLjB}?VWr^+ADFo+=_+(2{UpyHJ;Y`%WU5kwx%{SyFgBH9dF-TWfPQ2 z5Rpf3uUAM;h@vnros#@aQq0kahP7Q7EDB^DN#eksIP7zae3_h)+t({zXT&yTXJFWd zF;pbgV!{|It~q0$TcoC;leVr=#;iYN72OE0GvZis^1A0U1H(4rO%dy!i3|_nSh^Xj zf;aIGpTnM$ZK0$fld|e&21h1W2HNBCu(a$X_hKF6wi!x`v(W2xTO#ZYpFuj_e4?PV zfu@&zBqu~sbiEOm%S~BzGuE$mM1w%pqgHC_J8`>}-C90_{BgPf$j(UOQe_j3&wJ?| zn&4SWKgZA4vUithOC+V>b!)9=`seSJ6}zX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1$#+EK~zYI?UZX!lywxwfA701%W_*Z5q1X*5EDhi+v*rts}rdyZHmlj zGTBUJsUwq)DNdQ-NP)^Ugr;bYV`gK*dO^TTCTOH9ilopO${3djW>n_Xg+Xp5` zl(q3gee(JA&Uwyx&U4=XnIlxk#ohAMYQu&kCW68pgW)PkNr$<8yA@YRO?{I@or)>NeKZv2)n$A8r- z+Pg)zZas~^zYp(E_ULygtU9=t+I%JIkcPp2DE zFXZK2`^jpxPj@(+baq-FkSdiD{frl|B^R7Opu95n#r zXk?7>0-jne=F(CeCKEE13Qw&TNs=fmtfZl#qu=NA@=}}=nzn2)_O!N6?*D~dqvqyc zymiEmJVb_?zxe$poWFhA51%S_0Mtg zM>V!?8Gkh@38lT?T&G#$W~{}f2>`)dUS5F1@%j_9`oXWKN=exKGk@Ik0U*@3ll^O6 zX6$R)ri0CK=D6)J{Sa1psW-)wJx~iKD9vpO_dfdygb*TQOFvy^oLK!~H7-oo;J= zaq-PS08tc(iyKdR`aD8H2Gi5h!kvr^nlm%;ijKy8T?*SzH=$A~Nt`y2-=|FJBN%Qp zvg<+@7cbssvq`S64H7?hmA=pTcJA>Fq4Qp!sqJEf@g3KLLls zNls1~5fRD6#%`yg&VnX6nK8As3|h1Z`?^F{R(y?d&rIUuCp;KJjEV|tDJ%PgLx+|S zrtzdHJ)O#7!?;&afPeNLj_EfLkx)od(&0V_4*&@ScL7n+`~5JSPKo^d>r9-ON_>1a zztwaSuy!qD>gw>->ydx-4yo-qoXC!4^k{X5eA6ae@_ zer#HEg2u*sPu#P%@9a>X1tY1Hy@oU%Ow`2)-GBXKCdYe@X779Yq z-j#Wl>!s0>*(w)<)E1lOdCSQ&wVJ!_R)s6^$QOsSO&;OVX}eTD_~E!>^0?Nkw{2cm z${PJ#)e3?@c=(|9=$8UpMG*;`P3E}O)*zfepstxZvGMsQj`bg-_>#u;W&OYL%*dm7 gX5>*kGx8|@24Y(v>J#8F*#H0l07*qoM6N<$f=V;Xwg3PC diff --git a/Passepartout/App/macOS/Flags.xcassets/nu.imageset/nu@3x.png b/Passepartout/App/macOS/Flags.xcassets/nu.imageset/nu@3x.png deleted file mode 100644 index 2504561dc2269f81cc26c019bcb9ad2e2e22c5e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2358 zcmV-63CZ?}P)v^E{v5`_A(`zgMVUxUkZ1)v6k~udk8oDk-US&dAsw+|<;eQt?Zy}nf{h57#bgW6WEoOVh2 zY>#{Mu3f_ZXNUKHW;zIo! zZ}5Fk5H*VzW2~>o#YIJ0+GA8!{)M!($55#hKPwsMi4M5LI{Y$#ZBvMTcNym%ewg;6 zA`8HwLyS6h443$LN=vJlHf=K#CS(e-y?g)3_y;q2Hm?igo+AWlwK%xCa&6Nl^r4{^ zpt?G|yqw9*oWZ$s>q$VD)ZOTG$&|dCz#=nGs`t5dqoR$kb{J zP*|7+=*krW0^HGR=Wzaf2Fb~BI5-TRV9z;``i%za-Zap2P8<@TSo6BaI&z!3IYk!0`{KGd@ql z*_aqw3kpzAnndWQpE9br7}taZii<4(v9WIs0O*q-2q}^zNj7iFWCAm1j$_Tzc<$Q& z3D>f+Ft@cKD3y4=_#%F*SEKOv2cV>+lEa73lAb<|mfgFlnmya5&yo50xX+x4UVnuU znyFj2j+Xp;lIZB@ z<*BElaWi+)ar7vNBIeds1eFT6C!a*_>Dg~hABYhdKj03)2wG4eT0ta}iSCmp;rb>D zj*d8u9c%SbQ`16eX_fW2(P*ZlqknEUrYTQI7=2UP{&L1WDakI&JjU=24z95(Ln{nd?T^|tOPC&qTyE7BY!S;LgE3}J9HWCBC+~psS8%dh=P- z7>4xaK2uK19+O@94O!e}DhSx9=&CSVry`#98E9vR9A2|Yofcvaj_+@19bsg;AuScVqK}&uqcHr=o(YXP)8A#EI6)H2m;k#4R7OAo~Q-(d)>|`_|^} zD+6VCzM#PCMFq+r+a*`8zrwtEJFGw^(+xls6&9etKpJy$sDChuWlrC5;`ovQ6Sqtd zEC8oZuSKUzX2b|D01Ou|Qk{}QMN||md3iXE8N*1O4rkpymhQUD*s*K)@WWC>aqwDU zZj*#dJgopcD04`sqIwb{tSQw2tIXH%f5}cchci}PCABV88p8^dfbF0LqB|Th9?ZMP) zUvaFeY9uA)BO)SJvweFpMx*&Bpi`#^&dtTa!-Lx8%W;1GQFKqs+5Pc+Nj7d=AOOaz zSE)%&MIRbUV@?hVKR<%rf1kTc%Gq4yPDt2lva$;4>9M~#B0+nRnf9WgCxU-hWn}{| zzr2(1@KtEFM==;o{eiY`M{sncCN-6a4atJc+|ojAdOG@$5U#!bHV$rX1Y~CuuCL(l zrZ7g`vzE1M3ute*V@~)F=;FmD7A*Lfz`zw~G+)r&ZM0UiF=x&YykiG4LodR&dBfVx z;tkbHE;9pkcMB?mk)QR+#K8fxvs&sB0L+r@f474mNi(;bl8P5(C_@^Za*qbLgh c>p;@K0QZQXsVf#P9RL6T07*qoM6N<$f^q_I^#A|> diff --git a/Passepartout/App/macOS/Flags.xcassets/nz.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/nz.imageset/Contents.json deleted file mode 100644 index 3b318786..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/nz.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "nz@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "nz@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/nz.imageset/nz@2x.png b/Passepartout/App/macOS/Flags.xcassets/nz.imageset/nz@2x.png deleted file mode 100644 index 2fe78b9dd1f88487ddb6451e193f59f75d1636f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1080 zcmV-81jqY{P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1MNvfK~zYI?UsK`)O8rgUw3@r4i63?eoqo4K;R~j0|_z_q9rw4SlcQcGkSN)ra-pevL9Rls!LKap7t+i;!Ac;1KjvPh~v=sfWnixrVa*B-X5U znKw!*m9Xa-6*ck8h|*NxnSB^-WGr>sp2a{+$+p=aM*< z7Kfd!d`>{Hzbn`Ft4P&;iBm)figz=3<#;tw&!3^==HTK%0Qv_<*?p>xZ9{?RY@KP{ zv6o%$<7mE0;Q@Qa%;v-mD^V+(@EGo4)!7^hhwV_so#5m1^%#vtvqcsnNnuV=yRW0n z<4)r;xq!lG0HcUeKT7MqLsq4Wp z%N&PVQuS1lUZ}3N{zvWnrBA}QkgXBt*q@H@BE#4~i%$s2#hvu@>F0n5lYYMAPA<#M zf`}t}21?5j9PF`uG;Cgw!9g2f?mf9w)kdsgH|{4h5j8*4`)Lt_s$h;bj?voDKa;G!t1(KZ?s-=ez}U@2H|Mae z;frK~eypS|^)R)*VMLZ&@_xZ}xMLbdZjZA+{Sx7cAJOq-JRV05u}?;CW5FrR%wEQv4@8W1fe2gDfhH)y&H8gZj zP@Cw=q!GgNO^>n(sUK~z|U?U-p)ROcChpII2T0fwChSyTjcL<~~YV*_d&ji_i6H)tYB zYl0O@6RHMdP!dmsU_CAb5|h&$K|`}t3ku`_0i>XyLaL%{qB0dGN-*rhA~5~oIaGwO zSgiBI=g&R&yYKhj_c`Ca_j_jq=fd}?HLG9lL@eTJRejsmm`vr*P3m5Tz{$Z}_gJXc zG|~S31^^n5S@*u1IJ<7w!JN9G;sF9y%rFa?rsJq{{Dhvy!M^v1R9+LGQg-K;;vW^U zwD62RXY%#!qIWQRji>3-`BUt+MrV+8suYbzGay(f1UGwQ%CD3GVD36eY-Mif1UNmM z*wI!-Z15c9-zU=?ozFAjf5h_j^*Ue0WV!;b<1HdQwk!yfz_na|HH7Nl24mjc&ar)~ z$vOHO@+nqhPN*vp^WLWC$X{d5gzzu~ViEGfRID}zkaMnvm$T6W3JBgLlqN`&$AP_cxp2TBYZKI>W(Ve!nu5> zZA?VHF z%o?Z;*o^Fj1=vPzAtB}^&R?#BzNP^QW;k0@6lhDs`|r>*XBP4^Cz06N@Xrf11V^TF zrKxoDJ%~|=}Ih=_8)G5>}mJDhY1-7o7cmsIw_5;cZ0Q(u z&Dugrb88o!UHAIO`ri>7ODTzaUnVc1B5yhozK7dfxf;++hVG2-et+_iTDM5Nu64Q-Jb0@fBQM@-{;}m zS_Occvn53fpd;=x3KoL9>*zXdTN@K*r?j&oqXP?5gZ^>B2Z4Xl7_~NX$HsvyRWA>} z{10Mcq(|WM6LGwGzZGvbF(*eYC4ZQf!%$zsnx%3i5)lH8hKh3)?9tiu=>0P2gEE>5?@rV&mh>*Wkp;R>?7K^YlGXw_zQhX4tT@j_~ z{vUxoJ-w*vTSko6T9L8xR7Bf&Dxz(OfxIVO#CY%@c2GblXUNrW00000NkvXXu0mjf Dvz|t- diff --git a/Passepartout/App/macOS/Flags.xcassets/om.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/om.imageset/Contents.json deleted file mode 100644 index 10fafd75..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/om.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "om@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "om@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/om.imageset/om@2x.png b/Passepartout/App/macOS/Flags.xcassets/om.imageset/om@2x.png deleted file mode 100644 index 5685445093eaf4d4d1a5b64d02d084713a3e1dee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 405 zcmV;G0c!qX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0ZK_kK~zYI?bNwS13>_X@o%z+yQYwU7*L2;(87`p~iG!p`( zMIfs@QRu3`e z7ciGr&}OF5lC#+Rd&sJTwX>@hB7{K1<1`)~(c=TS7g_KC+g1kA0cv*z0F2c&s<|B6 z$SCer7FnsNg@{-TV{sWLbB0$cB5a%9(+stIp89=32_yobPfp=vPVh=a0Hovab$9^L zs1gK#`12oWHL@%)&A<>6ZuE|Zt_!XkB%~2(KT}f(@fSk;bq%DW&-ZCi^4c%k5C2(T zouQ5(w+Tsh+zSkn{1Az*d72R7FNF9DA^yGrL5fvtrpr~`00000NkvXXu0mjfxZ|v_ diff --git a/Passepartout/App/macOS/Flags.xcassets/om.imageset/om@3x.png b/Passepartout/App/macOS/Flags.xcassets/om.imageset/om@3x.png deleted file mode 100644 index a1da5c68e89899baa6a3fa5d780f362cec289147..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 646 zcmV;10(t$3P)QAE9nh+wIR zR*kiHK|w4ng;GSk2#TQeBwkeP1AD0f6?_~;&_AGPDOhamgEqU#?s^EcLa}*3yF=N} zX=a$;@cpne?7)b8s^uLu5;}UU!MKubsD3OaDxRXCAj*j5w zXE3{a@uq+I7_mC^e>;oaw}`)ai)&egGKX;|CI}rr!R(>5U-_y=2awx$5b3ytH#dhf zHHC4u9TAITT<*a6{tctE3p9ScH4;E;ZYJE;PWb!=)1d&RLP=!(Mpw zkivueIA6cu&HQF*pr6o@4EnzPe&(y1mBVgX$i^hz!UFSWPJ`=Uy?Tjxy9cplE5+Qn zk1;D#{vxJHY&?gaKETqwyXBuEfNitb*MpJ0N__Y=NV&#R{-OhDJDVu<_E7x%Y4r@( zW&T_nq1J1r;P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0rp8mK~zYI<(9uo8(|p7KbKr0NiaQYdmsdH&>>9}GPEuovKDj@?7tum zPA(2E;*YVTDC*>(AUFy_v$(hkmSD`%tMms+${{^NP405Zbtr~Xk2BDXHTS;LsXDfYH%s0K{S;biGb88TACh5di!O75WZ|j7^{^q(;sS^tM@5OzM$6fhID4qW1Sdo7r09%MoNjyK- zBC4vpI%N3uTf(te`w$}|nz*s?d1z}(aRrjgePAf=YLIg+S$sG2bU+$6H<-oZpbrZs|qG&4j0;$m}4nw{nO!UFj1P0^Fq-0Ao6F)vnE@%Q!dYjYDU z>+fsYO%Tg4kk{AgNhAP>Oia+wG<=f%H^MUzAsB2z00agH9pj&lp|z?%a|oHvw=iK? P00000NkvXXu0mjfW`GI? diff --git a/Passepartout/App/macOS/Flags.xcassets/pa.imageset/pa@3x.png b/Passepartout/App/macOS/Flags.xcassets/pa.imageset/pa@3x.png deleted file mode 100644 index f944e16a2377e9ab878dd5c9c65c0703120abf05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 792 zcmV+z1LypSP)U~KF=BuVk5B@5X`0)ZrWK7*~T6D9QgGqR1WtV96- zK3}BjVk+U$?RRJA1moj-NF)j%gdmf-Eb7zNRu8Y&j)8&32Xm-m`|a&*KrrY)Uthz6 zCPqfyf#<)JH1SkWO)ZP%75x4_3=K8ki#weySX=9b*~}>-P7c&-gb)Ir{|Nxl>sbK6 z-d-A_sE*x>+Q{ziDdO>SOiq44GWiv5cNbEr0z#pblKQBjHWCh}u(9zOv$LOIFlaF| z(}hsTfrEpzlKQBjJ~WCV0n2KrhM?NnpT7-Smin{i8 zMQdmrj!m+;nN+k$AmC8^f%$Bv){7Cf@nS?Nq)Y~v$H%hAsm@4tbrr(Gg6wgsGa@W5 zLYSWiWPWbbDx`-Ba&dvH==H0!l*>U(B#;XR0ZBsE=R;jb$6a5}W&>k1mdsUCBN~oF zet#c{i3y0Or}x@oqodb-^m;V9-OzP+m(5l4sbzGzU_Cm*>*3*|HhoVItUM3%)D*y! zms)cp0HAGdhQ;G4iZ@J8gR|MFa;U-x0AzhW0D#VJhr#It00>J<)ZPf2k{2UtLu<7fe%S3-4?g>KI<}r+G;F0sOeSp(fLLjs8{#+DJ$?i4 W?Codzj0mCt0000X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0{clsK~zYI?UX@iTtygwzklZKZnm48t$rr0*kZo9kL-S=jG52+Z} zP1#+xw|v7~-u(Z}_ul`%kqbv2`datvaevT*fGQwLJ5tX+!$AHCV(}5OxmOUAXd4Hx zdHNHspFFke`c6dn-1u>qlG_Gbun^vnP`PVj*xcaOjUPKYq*IK!Wt%$e(LgbmEwUvY z$y7_f;-YH*M0_sFNZCLdNPt}nFl7Ux$eqy+RY$#Ysed9aPT)r!W+{M};s-tQm&IJO zDBHcVn@n?Sy?-JCU=$tkm<@x3g5KwmlEJP8|3Q7-#5lDtLMZ$lTq6bU+CobOe5g z#aacT3e%}W_}Z7OX_}4AP0FJ6l3i-FY1UnFLTYPNG*VO88Nn zu#lkk)@yuUFEDjwk=MHv>8iy=goj7x*(Aww`4Z-NhkGo*?1!i+%=jUm8=U0L=@VUQ zbSVOmiKqGC$u~IXE>azTm-y1l#1k(PzWqL@%HNQGWS-PLEzJ(*4?fM|Mw*{~|ALX> z12|WwsH>cr`;gJ`sb041O#~o2G0RI6v%RMOkKV4qd%&JZD|;fX?1{AUH^pWh#=*0| Q1ONa407*qoM6N<$g5qwEfdBvi diff --git a/Passepartout/App/macOS/Flags.xcassets/pe.imageset/pe@3x.png b/Passepartout/App/macOS/Flags.xcassets/pe.imageset/pe@3x.png deleted file mode 100644 index 3e10ce301cbc71ef289756189564df30ce507292..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1507 zcmV<91swW`P)b zQ3Zj7kYK@riWO=V1SP<)D zhlaNU7~LXmEl*X9ib^A6Z*%keKb~Ix8%Z> zwSKh9DkLEwf`lDr+(T90BLnM}$uFD+am~(kO*951Cb*QVl|xuCZ+oa^4M2UpKpK|` zsyRw4lbez6J<)b|fS%RpjKQ`#Bq2x`%#23Q8=xkm6zh^Hi3sK$51p+KQ}rfr7a65A#zZzb{dc)T1VFS<{cshVs+E+=X(S@Jq<{xHV=%L( zesN7ksrQF6eiGDO!*2@SS|@|uRU~fk&iMp;1BeSQWzn-5RaBTO9_&+y2q1MCK|}~g zd-(PAX$qw|6pBQwgQLAiHcjNdx6F21D#I1LiCRgig2MJ&r~pO?&v&6zy+w`t$RA`jxLU*<_i9i&!E;WuyWXFw-7pLE|2A zpdB_S5KbQCL^j9qxeV95ZW6@;F8^;{_}dRTb?N7%$gn>aP^o5!`kI$cpKPj#=bs6! zZ-fsN!G*zGF|gARj`j1(^vL21JqCCe=v@QKzQwnba`UiW!=QU!i* z`&n4Ifz9N&jNnvyF~u8{vwu-P8EtPIkq6=^;Tn-qjQdxKI1NA7()lo7u_A(ir3U zSuU4K>~OP0gA!fWFB7Xq_?B-;{(Xs1u|#ZXil3y$8P860(Dd^1_175g-2cB90nn8Y zo#`*|Y#Q zqYcjuJ;Mvdw>f$94}9Y*KP55xFG@2L_+d-z$w8Kn9puE=4_I_zV*9_e?r#BW(KHc& zzLqrK9r+Qb{&JjG&;FM7P=d6xpcIRe=lZn?p6~k{t=$JW+;yOtQ#V%xAY!6Cx#uZH zr(dVEcoT+)km52^(Rt$j98V8?6~C={ZTB`!7XgT-hZsq}zy6!kWbe(^y)bMt4@Dj{ z9*R6@JQR7*cqsCq@qZ)Y3H)^<0bHq^ET28!x4{_G0vq}n`X4TLGciVrM+5)>002ov JPDHLkV1oA%(t`j1 diff --git a/Passepartout/App/macOS/Flags.xcassets/pf.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/pf.imageset/Contents.json deleted file mode 100644 index f258944a..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/pf.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "pf@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "pf@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/pf.imageset/pf@2x.png b/Passepartout/App/macOS/Flags.xcassets/pf.imageset/pf@2x.png deleted file mode 100644 index 399812c747f02c3ef448c990c868bbf652c1f696..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 670 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/pg.imageset/pg@3x.png b/Passepartout/App/macOS/Flags.xcassets/pg.imageset/pg@3x.png deleted file mode 100644 index 131018becc5057bfc23fc2fc7c291d0d8afade1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1112 zcmV-e1gHCnP)q8(P{pO~CT`K0b;5)ysYKi2dLczb5FLe5 zv@mUH6jlVmUg%XX^g=0gH}ghN*3uS5Db@*sQ$eLdYfY+ivr_DwbKUf+4U?Ku?c>Ec ztu@U_&PmdgJn+K#^Su~BNxCii1;keud-~9ti0#&L3Tn2O)t}Ve2 zyPZ&>W^7E$FK}Q#+QlCTV;U{cpC5m^_bhAx?1?Schpo)cI3LnhTA3(`)Y^b| zfX~QXscr(|z-|q)DrFPVj)D8kYnDck5b)O4A10%p5;9+NZ zxgug12BV{+7={_PQPtv*5G>mUJ_X#I@hkx{cz(>HVn3d}gj#uJUGFlWn z8yg#wB#n=cXMOL7wGUxZu%5M*Ff#7LNFN}5n_391DMA2I)^FFJ-{mP70_Wli(;)x{(jZg*5*w*8jZ^5^SPH= zHVay0jv@Jw_92ZU9m!8*F*AYc>S~RQj3|{#DVa>FtE%5{ZO{hlkbB(2&)q z2WHmY9t*-qPa=&Wb?5ibN(4}6XQw1-VPQdjzu%Tfb93{iUmy@DjnobzJ&)9bRPT(q zZPS=er)h6*Cl-t4Twsffi^St`s;a73U0t>Hy95VrgZ*b=`Vq6&wcj1C0dM5|tZi7{ zbx=x+7a}zW(khrpFc_3!7zM=^T-9vMb+5G*eSLlO_VyMOYf0_sIddl|2@0N(nwlDg zLZPCi_AGo;tkeLm7n?{a%#zy0vJt7PtJ^U$0a*J4rgoG_AP`VXON(Y^W)zFX6buG; zjF2U@l@&?SiXoCuYG5AqcRfirmQ89ENd&e_tzwCQDYehd6R14E`~3gE e@ruJBT=^Hb0Hy=byhwlm0000X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x16D~yK~zYI#nxYJQ)L*(@$Y-uv$<~0LKua*Oq^_qF##svNH&5Rox03G zjM1ybJ7QE&FAyjeRfkg?~g&ig$F#!dfe=xGCLs-k!v2N?ywcXC? zIgb}Wu~rO>t^HlT?~^B=JbCiwReKMe``ve}zhbxVEF$D8ZQ9&f1qHjBs`G`0Oh(n_Gl64=nZYoh(vr~_(2S0>75KeV)T+6t01_!h0yLcuO$D_ZG0$b~IPXa}k+9WvT+n83?#V4Mp~3UF13axK01-0NP5_`nQ+n5dbJD0@6r zmpFBrz=k&&J9^@V09S>ed#<1gmw_Dzl@HE*0FuLDg3a3qeY6L$tSLeKH{_pjxKZxM z01z`t=##zlzr7VJ5}h)@wLvZ=dgLg94R0~t^~aP0G9VJ~%=ijS^ql4V#!ZZN9G*77 z4n#8YNSp}pZMqe=Y*Hdc=w|C?Oa5;Yi2k}f+)dA3$00000NkvXXu0mjf DSH-l7 diff --git a/Passepartout/App/macOS/Flags.xcassets/ph.imageset/ph@3x.png b/Passepartout/App/macOS/Flags.xcassets/ph.imageset/ph@3x.png deleted file mode 100644 index 5cbb989473d2554c4ece35259e8703a35694e784..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1382 zcmV-s1)2JZP)qBG5#Ok_sW94F~~gSZZo%8z_`Yr~9;>rPJB&y~hvJg)(#N zFibn}e4Bgjx##zr_q^{p=U%aSfBlV5_tw~a0ZsA%Bczn_x~-3#UH?#avN_RuVn#?Q zhaz$EQU_}b{-)-JGm)BNV%Uh<1?GoQ)c_?&oB6n?hAxLc!SbSK$cTO`8&D8g*@)Dq z=yeD9?7LdNIn;pq0HlusAYH#B-KUYxa?tDmOrB(+VC~afDP75HOCCw6$Y>afh;$wU z&4HM>7$(dKyRqV82dlSLQQzbmcahOBoFkeWlvEHV!<&Pu&aPjt^U0noxIDq}(ijyZ zs#dhv9ey_aP{+=)1~mQt$e);u3{rKqmo@oSoUOLUQ&UXblaWD=o@?d3tyMI&dE%id zChp3Jv=>3zGDPwdVQu|Bjl!~PZ1}#8KCc>AkJ>A>9>7UtQ=z(lpS$AhjVA()k`>^UMA;?Ug5{} zinaGgquYxSmYImGcfo%H*?$p{`Yc#iAd=@B4$|jU z*;;y)P5bKb2leqa0zlv?U`2&6pe)!l#zh zke^b*Rn97vjus!2+YG?H6X zgFOz%1#`r!a$p_{1sJV{3J_*Obyf-e9+lyClhk?Jd5IUU=c9J_-i2g?Cx_*T)Iv#| zJP%Ci=&iX(dvV0h%G{%f)aOD|OLrM3yygU{BE8j!NiX9*eI8p*9=`g9dp0mgctnIL zw3P?&HzG`vkxEL$WXInK$qT{q*yyn^h4LZL`8chs*Wf5W9vAT=WE+>?Uk1u=9m4u1 zsO{)odk_<5A+oZ;cWt2jJ_G415Ge}`2hlzKbnYyny`Tu`55(OB8083n-Vg3S5!RK6 zjJH7dAUqckX-g5-RmchTApJ<^F+}>S2G7vxDLX>T`YotkW4l8h)rgq34w9yWNJ4sR zz?6Xu+(G~%`AJA=fRy>5_l0%$T&||&!#q5ftH)6OFos4%R`$pXYUjYhCqiqCDFb1B zBdlMrwVl=t`Sko$&Uo$`jU#dgBI6xUdmwFT#GSA1_0qNH0PSCYi|*-P4r0WlK z+xwvQqRifpxXp;Kp^=u{O}Hw~Bur#9m;prkazxsTfC8pW05pe-wk?I&iw`2z`y&rV zBkZ$+4EpFUEvGH70M*`;V0qCqVjXdxsiY+*4{zPIL~4o&Gl0MGUs}J|HYVHa@xt7( oy|}UM%N;i93&{UJ;oZOd2S-0{9o+19iU0rr07*qoM6N<$f=$kxdjJ3c diff --git a/Passepartout/App/macOS/Flags.xcassets/pk.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/pk.imageset/Contents.json deleted file mode 100644 index 44615c4a..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/pk.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "pk@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "pk@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/pk.imageset/pk@2x.png b/Passepartout/App/macOS/Flags.xcassets/pk.imageset/pk@2x.png deleted file mode 100644 index ea2a0b3801ba5bc3ed1ea5f500949cccc81a2981..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 552 zcmV+@0@wYCP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0o_SNK~zYI?bbg?TTv9p@$XGze3}?iB_xreywoUklB!EV#f}PfX^Iv9 zAeBO)Dt0t9YloH&LI#(XrueUeh^>>0e`x3up+h8#{}6|lLekKu*d~(BNi+DKNi8M6 z@t(uEA1-IOVl%RF)PAeu26?jhd6Ch;qkN@8=Y!#I+;WkWAh{_)a&sw2g{h)Y4t)@l z(S+~554Xd8br6Fv5S$3|E1e>86v?VpTdEo89K_@DP*YV?T*y$@Fx|I%2(G`&Iu}CF z+R#eiae$d8GsM++aUo;fV<<@l<-3w~ZZ0=tku1c2#5p-TrKz?_(_EvLW~a?bV{Id` zWbBWcdZx1UWeKTFVs2|r69P28h|O#Rz;3nUy6(!`qX(fGq9heEnG8Y*M(&R2y09RS zgqmP`FATusy=SzzTdoEI@N)ST08*L6%G5`^cf1H8vU)1Z&GfhTW4w0p71Xq>0IT6o zOs`He-ZPH1(#o5sZ+Y(Zv$?ZDLQPO-ucujV=Hcr;(&y>oK>&QSQ$&xW_#gOj+MLJ^ z8QCENu=aJ0-WRD3wf$d^ix{*9jC4PNT$YbG8>ikrBcvk6G9zB~y@uO2 zg$eIO#+=n;1;a3ur6Hx-NTbqVxa%<#G6ja753QMPtFywg!vgQzCsgSx(fhDB=lWzK zJ;!@cswstI%z;QOk}+3OSb~b;ava!o5Nv|Q-tI<{`Xr;JE7ajc%Six$XV#rDx3#Do zw>k$ly#8_SHickw=Cc`|-YFcQdR@|=1g5sAbi7<@XIcF*2mhwIuilowa5`C7eNkIP58 zu*$CH)C1XZkTg$<(JL-kc3RTz$JscXZ=4vN8pTKd2Q2?shElFXt)Uh+vkmQL8`}F0 z!8h;A#e*X~*D6pJ;L#-~nrfPORS&KNactlyCO=Q|%C7tkV{WJ6z%HH2~iroKi00000NkvXXu0mjf DtDI)Z diff --git a/Passepartout/App/macOS/Flags.xcassets/pl.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/pl.imageset/Contents.json deleted file mode 100644 index e50e8cc0..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/pl.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "pl@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "pl@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/pl.imageset/pl@2x.png b/Passepartout/App/macOS/Flags.xcassets/pl.imageset/pl@2x.png deleted file mode 100644 index 7e0873d5dd7e8396b118cf9b9e50030b4dcb5619..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx(mY)p zLp(Z@|NQ^|UyF^cEugKDb@dFk8;k`8uLMfa@qtg757k!I%iHs>P@eIla%tqgHTRYO v|9$b3v6N%^)`fQ#bj}KFU(k$(8QmBzf9L<)xqN0S&_)JNS3j3^P6!0DcW`BYZ_sMH zAmNnE(W__zXFRsllUM3Q5`f5@-FE)-Te-l8%-?5nSD#FjnFzFz!PC{xWt~$(69D2K BIynFU diff --git a/Passepartout/App/macOS/Flags.xcassets/pm.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/pm.imageset/Contents.json deleted file mode 100644 index be2e1130..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/pm.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "pm@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "pm@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/pm.imageset/pm@2x.png b/Passepartout/App/macOS/Flags.xcassets/pm.imageset/pm@2x.png deleted file mode 100644 index 62d74b4234bbd0e63fcc2d2c282acfe986a5e93d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx5M eG`?WL!Z1mKXa7E*nWjL?7(8A5T-G@yGywpH!bC~{ diff --git a/Passepartout/App/macOS/Flags.xcassets/pm.imageset/pm@3x.png b/Passepartout/App/macOS/Flags.xcassets/pm.imageset/pm@3x.png deleted file mode 100644 index 5b94a2263d0ae4c51f921f00255def0ba318d445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 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 ABLDyZ diff --git a/Passepartout/App/macOS/Flags.xcassets/pr.imageset/pr@3x.png b/Passepartout/App/macOS/Flags.xcassets/pr.imageset/pr@3x.png deleted file mode 100644 index 12dd0acc6c9611f1fbbeb1454f318f7319196c66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1079 zcmV-71jze|P)^5|K~Z+InSwh>Ap7x+J4>bu_(bP|6_!xzp766ou{MU!Rkw& z=&eUHLeyVXRRjRbuAIQ>ZfDN*Uv!q0Inu2UUzRSX=8>;xsoaCtT%wmIO-SQ^hzKJG zrf+89ij(Y{*@Pktdg#iSF-vPADu&%S&$O}F`)vVbWlmg1#^Wxopl<0=)>j|mg6*C{ zXwpQ=jNFP<4bc=iN^9saMQSk5(lSGchHI*-X7*88iG*{5%*GlsBE31tJDQBdSTM^# zSEH4Mv-RH)vN8f73D~gQMCUVB?wX`uda^bWV^Wcz{YjbTIvLrNeY{M`nUNT?r%Js2 ztd&*sjJXS%1}V4S7#nNSo92PbQEGSjc=UB2{hq@0p=lej*UD7hn|@J$emX+s%Qvv^ z4{+U|!;~k$h^j)0bEK?9qH2bLhH4Y1&MRpL4=d31S%~+$hiDBQW=psS1Nq5PS_k5` zRc5*xt@K?`sIcGEJlF%*PP5zHMzyzBbBshxXyoa|CR!Whe-69yQi;iTj6VE$P9e5A zd_Snr@?L;oID-LEdE(NiIQn*Sci>_!JdDOol#$wpj2y7rxbwD&vvu%znCJHf2!yn) zf2$H;+n|f)!9B=I_U%r>V(&f~VczzeaWxTEV3W(wCYN8c2>>}df<*};)ZEle(#`(& zzoPVeqGNU|g9OJ;MKlj6sZrKk{D?REI+#3iMRT0ExXE4`4Y0Cm)N9XA5r+LLl7QOz z+K%oeLtpd7@n!7&p(&>(AVo%&&NWgb3!2{z;_4ouyj0@QD>hayFtPtwDD6SYqF!DN zeZV@)379_xn3ApTIFhy~&2=)pXCw3u{97lo2^{Mz?D}|w8-e6CVv_|s>dmx2Wx5zhPXfmSL(+ov5wIpy4eDDUB={`Ua=Fjr)iD3ZX|c35`q*TxMs# zgSDOy5#;}>nk{igtV)0_=k~GvY$rt#pB|btF~-PafA;X!nH@~?d-T$y86%9|rJe3} x7J0wZTaRYMYqRV>Z~g6^KO|N8?-xQ6{syf6U80QG(CPpH002ovPDHLkV1lo~5K;gD diff --git a/Passepartout/App/macOS/Flags.xcassets/ps.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ps.imageset/Contents.json deleted file mode 100644 index 74da65e7..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ps.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ps@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ps@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ps.imageset/ps@2x.png b/Passepartout/App/macOS/Flags.xcassets/ps.imageset/ps@2x.png deleted file mode 100644 index 1f98ed6235a7659d228d4c53da15c96b5b78833b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 386 zcmV-|0e$|7P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0XIoRK~zYI&DOC?!$1_r@o!3(4h}Az0(KB_h=e*i1s9>CGkgP!M_!B>@S+l8d?g;K2GBctIlsl|dgS3TVeC~wd{$GvXoz8ML9F7;Z zJw+>*702--!m=U(V(T(ZAv{lr^;(pe0t+n&U@(Ag7kq!iDrH8hRXy+#ZAYzxC?pK21e{G#NXuRRfaz0Pwu!C<{1cdH?_b07*qoM6N<$f&>$tT>t<8 diff --git a/Passepartout/App/macOS/Flags.xcassets/ps.imageset/ps@3x.png b/Passepartout/App/macOS/Flags.xcassets/ps.imageset/ps@3x.png deleted file mode 100644 index 991c51a77b2fd3f8a0592fe9be761a0b339c1241..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 356 zcmV-q0h|7bP)dx#|X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0yarRK~zYI?bT06Q*i*m@$cK-o5MewZ`E>$C3Ci1u|>luLCgMG1mz*R zbchZ?VAP>MUV<(`hi)Dshz?-~EA3Da5g~zvHVmVKm`l@{L(;a{+cWpib?6WbV)M;) z+V}JhzxRG0e!s`>Cvc7@2%KaUIes$e?_tks-pNw0ZVf+{S4Bu>MM!3vAd((ots!Er z12{SkW7jC{T}SmxkQ~|(IYFWdbiV^N9Y@`i0=u1=v<1QL;8&%As4b3B0-XY} zYVDA$TT^R>ksvG?<5=rs9KZ4kxuS&0K$P~Bgm>FAa{-9i;rj`wxuXq|bwvVE_!@@f zf{B$rk=|PeX?9o9dDg?u#buJOH&W(;l5b#rt__hBWJ&?I6~-DCi8PIqdTt{A+L|w2oTkd0MO&* z6aub)4G5|*tw37?bT3RX)KZJ<%&tVJYQ27Gei+fFA`gIkJ}<{;P6t~57nlmc<$Yih z{pj8P6(J6y0CEo)UgU)UR&m5nhM?o=pXbI$eDd9+G5_iRrs>iPD)L6t+aK9O&^lbJwbuM0 zsQX6?j%`dP{-;i1wmv$l;+B~j* zWSdr)G}$Q)C7*u|mvip#d_UZC?>+ZQ9N{|zqP#D%+Un5&`_^Y_v8dKNfGCP0g+@`N z&?t%&8XHDLBdSn?9om{;Sp%6tFieW7ygF`6#G;4e55m402t~kVgK!N5++dKQ%mcc& zK@$-Z_W#IxX|>kF(6gY#q4OhnxDURX0Q=`q*A9k5@Rtt!LlAEknD`b*96`zTq=)L^ zrF+5n^^@48AjYT}2~gx1ItBDW85IyWh44#OX#14fE-+jWFK~mLm(~NdaOP>44Z>FA z2->R4=sUB0$v3jC_GGrzodMI827FiH;w#WN2NmO@qOT-pkyDR>)e250M0_UnV{q;0 zQPfT+n#w6aA<|VtW^NAqxn3eh6Y@4tfar_BLE#QS9wKT9M#rJH668OOa1rE@L&z={ zul9OLD;avacN6n^VOKAiW|_K92vx&!-x?TPC#Rd;wHdDZ;q-a2Inp zW5+hz&Evp55G_sMKMa&ZG!C%_QPEeT#zpsP2OrxFjdqZ<@Z%J8IE=_00e)C~iqiwr zm`z$@ODWC{{Ei`=VEcW2s7qy*{eBYrAyNfs!ScO$k({oGN`g!VWC=>tkRB>W{d{)0 z%`V1?CsM@YDP|VpsMQkc=OBIl4q`GyRgebKI`I-Yi+nMj4fNq9m<>ZZrGpvRWl8W! zPYYX|CaNk*Ioq?Jy`~t$`@cD1`b?lH3Z(J>_gV2Idx!%~0~o+KA-z)KY-4DLB;*^C1{soW!a%QQ77p z^0=GhorghDh(7Te70+DYPsu{&Q6H%!l$-zt)ASPq61#{0Pnzj69f-} zr3FkcfN@Z8^8b*x2tc_EU5`L>HOvRVYJutigkvDv!0Z8SH)t0HCcXu?CGu}vo58&k zULJwuHz1FKB%<&t5hikj_4@a_VZPamB85g#q|hje6dD^wL<4x%JA?@S2CHKwa^VI8 Qr2qf`07*qoM6N<$g0;=+f&c&j diff --git a/Passepartout/App/macOS/Flags.xcassets/pw.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/pw.imageset/Contents.json deleted file mode 100644 index 14f31b4b..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/pw.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "pw@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "pw@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/pw.imageset/pw@2x.png b/Passepartout/App/macOS/Flags.xcassets/pw.imageset/pw@2x.png deleted file mode 100644 index 26eee9fbc1c18b362c274f471bcd7da2b378e211..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 515 zcmV+e0{s1nP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0k}y-K~zYI?bbhN6JZ?2@$Vfmf14QE(j*84EyfH=Nf)IQbc^61Nd*_l z?kG~E5u62aw-ln_W_2h+hqmA#8PcJkqBj^cv{gtnrP$sjjmf=kwg$Z19T%FR&v?AQ z=RWUo+OZ%6IC9ay+y?R?Z0az z1gW8yL_M24D9zInrXTd*bbUwejNUgy9M1Sak(-e@%ZRR!MBp70p@OBtbmS>MS#=h2 z@#GuJg+5thJXmxV5_4}@E(C~qtj`%jBuTdgk!{QwLVh-*wje)j%o#$~cc&~DwuFoK zyO*4XO!B3kuvEBO%dpcLbQU6n%;WMywDy3hvaQBx^55@TMs$S$lpAq!r9749DZ_Oi z)d^-x&rsVzTM$#<;?LT7ZY!_3I{bpm;U&iW3NGn4b={A$mt0A002ovPDHLk FV1ihW>9YU; diff --git a/Passepartout/App/macOS/Flags.xcassets/pw.imageset/pw@3x.png b/Passepartout/App/macOS/Flags.xcassets/pw.imageset/pw@3x.png deleted file mode 100644 index 714ebbe9bd75d8b3d2ee1b35c6a7461bedd33ceb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 699 zcmV;s0!00ZP)9iyr+<9mI;UfE- z=l%Wu&&)f+%s5(mhMutjZ~+I_dk6`%^&UWqoJh8j6UjEjMSR*K`ObOi*o?UB1{ArX zv;-c}SlCCEZy3K*2Ow#97+b2LKi15P*~3gELUyax#L?O_WLR04r^Pu|c9TQF`*^g( zn#UT3M&H~)?o4#@bEU-6pqa2F67)p4Sk*zuJEF91F1eYmvFm(V*rQsR5#@U%;GX76 z^>JkhfMWLyUDYSq;U839n+YWmha<(g&{<0J#w{26v;{6yoDqK@D2cQbKW6u)H?~WI zo(Sz_w`}LEMoFZt^sZQOYe_eLO&7~EEmH3NLb-QTtk~;ZqQ3BzSe|K-o%sV|rD=BM zzZc6gEfRE3ik1G&jEH5K7IA7xvC=dy!A#R4iIh*Q^nYZU7KtRbiLgG7#Gfv3@UJZ^eX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0YOPbK~zYI?Ub=f!%!54zuTrH>R^RZBq_QGg5si6eGdBwzK6~ZK8Gzh zxVSjESa%&YF9@utc9%{(Pa)}p6Gqz$xw+AJdU`+Wc=lL_T=nR>mBQcB0AZRm6Xc%DbE z*TY({07yO(vMi&~XlUhW%}5kQ3{d5euR54h6U(x+L=cg-XyCf8h=|1F zk%UP!^QOU>_ORNvjqm$kTjI+I$8mJyFuHfaORZ{Z-jSu5Dt6zrQi5pwY%~wAR)MCT q$ZsJ}|K`LOB6$jtJcYZoB zN|Gc9App=?qm;t)yj=2fIU|$F1jliRq6pvj>G%7@aST8Z1bIZ77YW0VcDv1BFyQwY z&Dm{_>vwiI*I8#c9HNvW3`5hg=0%bu!S{WB^_uK`e$2XB<5FvbC*x;yuUup_8j&Q4 z=~(k3Qc9M~C6B%x@NjFNo9>*(YQVj%+q{a7Nz)W5%|Fq(oRMa;$>!PxK71d8)dH)< z;g2yE7K_Cqk4W<(T5B4O1`oWuyqF%b_x>eMgHxV=eaHUgT{@i(S(cfOHNPWa7$T*_ zbzLmS=3^8v`+3UF##QV;lKFg&5W@ItIF~^Tf&kZbQA+*Wku1wd)08-l>GgW1BlBDK z*=)vYwfb)m9mk>D?dB0zolg{T>;Bu5qDz=SaP6QHcce1^Beh7`s71;~EmAgWk+M-4 g5gYWIqDz?IFTt*!&2%wJivR!s07*qoM6N<$g3rPNw*UYD diff --git a/Passepartout/App/macOS/Flags.xcassets/qa.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/qa.imageset/Contents.json deleted file mode 100644 index 3ca2d93f..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/qa.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "qa@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "qa@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/qa.imageset/qa@2x.png b/Passepartout/App/macOS/Flags.xcassets/qa.imageset/qa@2x.png deleted file mode 100644 index 54120932eefeb5ff124306fd0869dc345f796508..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 581 zcmV-L0=oT)P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0s2WqK~zYI&6mw@5>XUK4_HV6zcy}^=)zS^R2I7E!ezIbu1)-h>fSW9 zJCm9=sm6__B1xGFro6EUp`xjQ&QQkIrbQ^j$1s2kQf1Td+C0Ite#t$_z4teFJcYzY zAv_rga{?W=&Hk6~m}`PuG>IiLbUU4so}UrVl4p=eKdoAWt+%TrX6C8v>?#!*8(7?~ zlYa4r+=m2~$k29L3I+Z$P{bDsaQDe0uH3qbq3M`wg1z*&;fnx_1;zO^}a$!v1-nQe(+0`_!r0)DB8i zihDH6M+zl+NVn$e>nNpMLAk&&r^=B^VV8U?N$T0c@N)#9Y+4waz|?e%c#3AFs!*cG zz!`C>H7tD-OJpdnr~iorNX&@;e2|IhDJG_;pxY(;>OCK4o{uI+w%)}tR#TXoz}`Pp zDj+T53kA4#|2A%;N!w{*YJ$cn))(N?^{d=@{E!vF3ct3CN{ja+ zog>F9oQozg<0;&xqf$dY;(fsY{!ozd%NOup3@Mc8A>EqKbBi?php%tPZSzClWIMi2 zc6mkduu4X}TW?^A%%2NQ^~Qi?Yld;Np6!Z%IzRl@^#9iwn$AvggO1x)DAD6Lx{owC TOmeXJ00000NkvXXu0mjf11}lq diff --git a/Passepartout/App/macOS/Flags.xcassets/qa.imageset/qa@3x.png b/Passepartout/App/macOS/Flags.xcassets/qa.imageset/qa@3x.png deleted file mode 100644 index b845f0695e541ad6b293cf7da31dd9f28a9d62ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 602 zcmV-g0;T-}_1GMG=(Q>& zDwsIIU2Eav~GMg!uLYetgd0#$JS>i3#S?@@`xhm1CTzMd+cTF!Ki)^qY;ltghZZj)k84u|A5D)17B_7amBKAl; opbLqIGob5Ljx9<+RDXv42BrM}>zTSz&Hw-a07*qoM6N<$f>9b31^@s6 diff --git a/Passepartout/App/macOS/Flags.xcassets/re.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/re.imageset/Contents.json deleted file mode 100644 index 83133245..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/re.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "re@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "re@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/re.imageset/re@2x.png b/Passepartout/App/macOS/Flags.xcassets/re.imageset/re@2x.png deleted file mode 100644 index 62d74b4234bbd0e63fcc2d2c282acfe986a5e93d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx5M eG`?WL!Z1mKXa7E*nWjL?7(8A5T-G@yGywpH!bC~{ diff --git a/Passepartout/App/macOS/Flags.xcassets/re.imageset/re@3x.png b/Passepartout/App/macOS/Flags.xcassets/re.imageset/re@3x.png deleted file mode 100644 index 5b94a2263d0ae4c51f921f00255def0ba318d445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv6`n4R zArY-_Z|ZU}C5p5@v_9-|n8Rp+K~I9v#-6LZ60!a34y2WMvk9^pM)N5B2yM3Sws^|> z{N?upv-7X5<6keJBVKwdKg>M!@!q?p^B?aFG3&bgRFBu^Rh^_@a_Zyn700&k))JoR zeO-ZXVIfDabB2u5V*#o53l=TM6iu)SY2Lc2p~-xB)pRANs|gW!U_%O?Xxl001; zLp(Z@Qw-+pp4IrUo`YF1A>aADqWWFwvLh>MjXOU-c;5Tw+mRKi2?-I0U6we_Y7T5) fKs0uIEWj}HDEFHQ5;yaKwlR3R`njxgN@xNAMi)c1 diff --git a/Passepartout/App/macOS/Flags.xcassets/ro.imageset/ro@3x.png b/Passepartout/App/macOS/Flags.xcassets/ro.imageset/ro@3x.png deleted file mode 100644 index a8c3cb2daaf8920dca28c6b3f7b608c74cf4fa75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvNuDl_ zArY-_&u`>Cy#nn yG+AI)%LS}L%cgHzUVP!&La(fI?%VIb>9>ETB<4F`z``HsItEWyKbLh*2~7ZR091ki diff --git a/Passepartout/App/macOS/Flags.xcassets/rs.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/rs.imageset/Contents.json deleted file mode 100644 index 393bbfe4..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/rs.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "rs@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "rs@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/rs.imageset/rs@2x.png b/Passepartout/App/macOS/Flags.xcassets/rs.imageset/rs@2x.png deleted file mode 100644 index 5ff02e46bef21213f336ff95f569ed3faeb0f8b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1221 zcmV;$1UmbPP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1bRtCK~zYI?UqYuTvZsyf9KwF@0~|xCYdIacha;{u|B%6FPa8w+G2gt zf`X+YmAViSapyu^DdNIK)ukYaxUw5Xp&$iq5-7OvRceWCtVz>0X(lt7*PWUBIxdn_ zZ6`AWDX#o)E}ZXw&-eSzcewWxpB_4DZ^_JtSwp|LWj-lpFc9pqqI_M+)2SI)0uDzK zJZ!A_YSWrb4%k{FlTrc~MHR)5u8jclfsaHGsC6^kQbUjc1xlb0L|QZ~U4U4DaC1QfR92ca90pUtSFC$V)a4pYf1X1>q-xWrxgB5z)=Cd z&?sm&nhL-WxYa5xhC)h#3>@Ovv}qD_g(a3t&;UaSBm#Y)IUy zrIr}<@?1AF>=T2iV!)MfmSn*t)7Q_0ALaXeB+HpV&PKF;Dxys zrZ4|a>c!*usdlEu#*xy%7(GbW6OXfS?HWJNTp_XWUBjx={m-KAxXS+k@x7`DGz+Ck zKu2jhN=O3DMEO2WrOHCFjDP75TB0%Zc#Q0Yi)2TJ@%(^tsfZKqMFdq)KAQ3ofd}}A zzz5}{WEfNcqDKE$L0mg0wZMVE`N#6|hv>`?1)e_sJjG0!SSp3+Orb?0_(Ox_mX2IIuHP|b!dC>>dbVj(hLG&pfm#|Oafi<%FaRhI@(DWij*og z%5gBF5yGC2?s%j+x_J4(5nele0Zq9OxQM_-Q_D{gxF{K>nAlbq0zgV56e!IASl-Nc z0UJD+{L{g6UFNS|WrMF^F@xjzxamdmb2o@@8e;1D45qVi=gyi$X%bJ5HiQ6>`wl)i zyLmpFtQ8ztoW#iH>6RtfyOaEl1yr#>WbYmfe+hklfgNq#OdTF>D%n~Z=xoghrPL|l znOcVQ_yp5mzK89l(a*=&{P|}j+uQl+=rPJ&vqYmU^bEgB&ymCH0s&6d9RoJ(-bKhX z>5K<-9l9S}k8&KoY;(ww4L2lPyD}cqVbQa1U00000NkvXXu0mjfAdN_j diff --git a/Passepartout/App/macOS/Flags.xcassets/rs.imageset/rs@3x.png b/Passepartout/App/macOS/Flags.xcassets/rs.imageset/rs@3x.png deleted file mode 100644 index f559d43e9c9a645e3a9d1eecc48acb1f902ad962..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2058 zcmV+l2=(`gP)*#&*2LS)8Sb(=-i9o3x=3CyulNygjk9|eV{-U1T-ZfX_L@~G`7CswIexh?(7)6SWiFVz}-0w%`KylJ)Wxz^JXpz7*OG2`CcINRtIXK(P(OD%36UClp{a@C^WZiF#wP)kI-B*DFC#> zay6C~jplzj-!8Qbq7hd=poPY;4TNK08v-FTVh&Q>UXEJ;5e*bA1ztA8mYtia$t0$0 z<8@|1Hc-@U4lVP$g-HYgi9m=3nbaVKKmrvMdd`z!NQ`BD7(oJY;SxbDPi_1tU2SP{ zbsxVn4G8kP?q>L{3w(e43ZegQQj7@!8Wf;`NB~+B&9z5bBN42!Z61xawFYt~3~tyU z07o?9!xfE%OJqF?6KAPKf+*C?lp@|b526UDP5|f0bgqr zngC7oZx$;Z8HBW!iOeTco!9+PSDI*ESVU;*b4!v~+0Wl|?=U)Er6eJSBontNm8MA< z2Fg^-D$RrK1{VyQt?O1&$~FFPM+gkA-#Br3ePo+}g&-zG%jwj7-LF#9ijq=|CD$4u z#DW+P(Qx!6)3K0hB+y!u=@6`26{Di+oC*SBuEESq$nhzknX->McoI9*Sb&(vAP`tW zV9#ZQtEL0M0+vaOE~BN}8|D(jU^3M7+YXU#gur|`2;_a z(vql}LLk`G6H*v=_;A&O*qnI{&1Phq)>EGOQ6~vb^8;R~m2d_V{N%N1R-`S~u8DCd z*U9>h8P1*cnXM_h`aG<+BbKzsMM1e%;y7RL6a;&zH_0;em&D(mguLXCAUK zo$V}(mXJCASK^yDF?-iN#F8)4_KzGN8X6+|((l;cv6knZrHhQw;tetRkzIW3siz1h zr}1pqvb@IXZ+;Ec*Ui8;o@UFS$I^^I7JW8+d^cT>KekA=7pf(Y?tObvQed@ZxcALA z5+_ffgMi*Ie2!#$JO05}S$iT)TDUl=G~IhXg|u$ZJ~2~M2b$`5vT9)U_MpOumCrrP z>Bqkc?>OinpzDF{D5bD2XEAa`x}SNHSSm#TOeO4QPM(58!auguu;;cC+U&2ET)PjqGE}fAL71#3@nrN&wq#EZ$F1suTx&$$JFI3 zgkeb9b?Foy)zo@~@)0Vyc9zOVC?BbP&JF|-5f+nG!i{mPoaFIs$aKx1Tb7T z`1POmv*W=JQd1G-VgXkQobE0FoStsN;Su~wm6Bgam<9(=o#2meAI2>e-Yx&Hn-!F> z5GX()v;riN;ZTpSXbD836+(wd<kgp;>VA#0=gy&kw}($*XoWX8h?z=bmwPonq^Z`Z=`~~a&t&Q4D8n~P9C92m654^^XRco<(dfByYI~TSL z@#8bahFRq!bqFdz=m4og(7{~DHDCj$gHm=&OAr92GQDeG1R9}yPzztIT#6KiYa=X! z-@d$`mAN8?U;GLgj}vaZoBZ$pfGAf9rmygFSmlv5k70O8j*Xqbsl1m311$}-Y&@rg zb_Ok4D;OIfmUr`370$&kScW1$aTzb&M%;HmQtQ%Mbu&DZQzwj(4>MEGboR5Nxnq;)Cm$p2?B>*^4ZF;wxPK z^8u;@%jw&GKldI!0)--RX|Szl1Fse{w`8sriTy{0n|io*&yCUP=kZNR;mAuQcMMT^ zV}!P$hY>-Db{z6Q{U!FU2PnV!8?s$HG5VG*6oYUfzbK}N&E&<)f#_$;`88L zBJwKO=N-Zkhvh$iflNmSj%D%sr}v_q3H;Sn5@CYUl*fI~{)pJ}WsA+xe61y8F5TN6 zB+*;NKI4*It?&ldBebGaETa5??Cv3w-HObNi*B#dy=LIHKtPK{W8b4+CLCRb*B>Gh zijLj)VIwIQ@`TkYsV(pO|3m2S3E^?LqYQQj&s%FbFPP zB$}G0f8S?`uU@qnes8K<0=X*x@)ZiljuYf&v9nnc>(=7-FaFcujr6`i?j*JTHvSIP o|3U6F{m+1yz$^c46aNYQ6VYQ|-L;{&q5uE@07*qoM6N<$f|xJaod5s; diff --git a/Passepartout/App/macOS/Flags.xcassets/ru.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ru.imageset/Contents.json deleted file mode 100644 index ba5226c9..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ru.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ru@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ru@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ru.imageset/ru@2x.png b/Passepartout/App/macOS/Flags.xcassets/ru.imageset/ru@2x.png deleted file mode 100644 index a43e6c13125da4b134b42e2c75eb32f52b3ba7e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xxnmk<` zLp(Z@|NQ^|KZ}j6O`xrjb@fcP8;k`8uLMe9_&|`=&po%A#pBtp2y#ub$s60O-gcM-KOU7_rF^iOHV9S kZ`;$XmgOef#0SF+GyX^y_nX|E1au99r>mdKI;Vst0QRI#4*&oF diff --git a/Passepartout/App/macOS/Flags.xcassets/ru.imageset/ru@3x.png b/Passepartout/App/macOS/Flags.xcassets/ru.imageset/ru@3x.png deleted file mode 100644 index 9f5d4658c09b0c5fd304490cea47e1494ad04117..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvMo$;V zkcif|*9>_Z0(e>;hF=ntJd?wdCgmQg)XNX8ZEsb!gr>?`{$GQDzdSU%h7sVWrLvBzaXa07f{nPaC&)uu&GV^i} phjiX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0c=S`K~zYI?bS<50$~)!@&C-c<5)5oY8Qw=$S?}pMVkn6)2HgYwCfv0 z(59eG5JVdzE4mR~wQNeunfHC$M3`M@Gft4t`dR!q=fE*$mKJLpjoB_D%uH;(Zbk`c zPFo-*3PFI0`nDs&Mp#H((_=&&b0zzD)CoN0BC*;BvGM5>w@ip4S^Jo(N^&jx+4b|3 zWrm;$6DlNbyf7yAn0B@(bgiMZ!OHVIzP9m{OQ5^oyP@8QX-LW{MM{*(+a<y*M9HgeZsi+i}a)2kDpemCe4mETLK%fP8vX>n>`L-JC%U$q~I zdp0D>!i9+@D2ci-1yZ1`Mk;MzY2VDa5~4+1Q0>^fvzol*m)w&#_udi9PoDM}1N58T zvL1C2!$6L@fMJn{{4E3yfQ*Qkl1HcKAK!n6SN$QeSR@5?NGMxq7ZbS*1zjVuX=-K; z0EZaJdL;sIGxY)C9MkeEbLx;X6?XeYdZbv?d+fxE=jOZ-Q5cH4O50|6IQf!L9`hje znyOh~UL9~`rr11LAt#$OZEhg%jYvjzNr@g)vPoVwSWUkmD>{_a9!;*Y-dQ6r8+2Si z&!vc5=BKi;}W+f z-t+3@K6A3hXEQ^`1?-w4^*B#jc29T7uLvH0x_D*&Llg#sqNu7|k!=))N_?HXuG6;@ z?3;NU!vBS6PzW4Vvp`@YCdCPD7f_G$L@tfJU^aOr0%y;UwhL&x;Pm__ycxfjWo$%5 zZ%cU7?3ZC|{zxL?8;OW-BqF{sHX{1b`qHRt7z+B?_WAGo|BbP^T8W5nBqF|%i1>yE eo{zeSVelQBJjU+an1Ema0000_apHD diff --git a/Passepartout/App/macOS/Flags.xcassets/sa.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sa.imageset/Contents.json deleted file mode 100644 index f6a16107..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sa.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sa@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sa@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sa.imageset/sa@2x.png b/Passepartout/App/macOS/Flags.xcassets/sa.imageset/sa@2x.png deleted file mode 100644 index 88171bda95518aad5e783e2b0830c8dcb78d6705..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 792 zcmV+z1LypSP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0?kQ8K~zYI?bY8)Q&9lG@$cO)-F7ePoI1DpV`!A9{g@?YWd>!@4mUEVbB@)xj4#)skdqO zN&9QI71XoW)k34AnXLu&NTwW4RGdLgC^R__pc8b2V~a$SDs@GT~kODv-|clvIUlak#88{OAgSPGGC zurLuGBdI01P<4d@a}f^)ZsW9+FuydH_C(S`7Gv|goqUDeTu5J_hiPSsiSQV$mFIc> z;|bqGL+mKp#mK@iYC`2$c`F^?9o1+(kSYLqj7H z53g*z+%n9lGf`vna<1|+rMVRxepdfyAxb>LJO7(C&HW_5TkEnb{Wo_c14%6dN$nTE Wdk#6^-2+7c0000dUSID diff --git a/Passepartout/App/macOS/Flags.xcassets/sa.imageset/sa@3x.png b/Passepartout/App/macOS/Flags.xcassets/sa.imageset/sa@3x.png deleted file mode 100644 index dc09c90ef829bbd8a9126b58f136506db9ea4c48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1425 zcmV;C1#bF@P)62ySLME12V&%bA`N z%pLw1g=r<6=sJpCW8e;ZF@8zl`7y8H5!^VEortoC-H^^Rg_Zas0?!?Mocmq#C`vCO z&oZ2cz9?tS-J9`8d^8KcVbP_~73!eP*TUzX-Gh!Trqr4~bT$i%DhWwJiqlK+hWn_! z`YS`t!>F3Dnl1I~D7KH|PWw0n(Mu>AqPFV@4zrUrr0@D7GIia@=^N-_QuZ`tS!S3ykIvu~#%4_5snLr$-F=)Q zdkK;p;ecl+Nm|p56WMZZJzLLjB-LPJ*^y`Yq5UiBdrncFa}R>pk6NuE*W#ig?-Ax4 zD5J5jo~oJ`csPGP6|Q+~Z+wsGj(a)Ud59E)jZ6MkF8Z3;>;9O%&D$A~I+8SFIv4sH za9Z+W5{YS#Gf)Lnd;# z<3|D!A9{@*U!1za z>bh$gpE((?*vpEtwIpdxyn1voZ%lrdBOM2MdE6>~X#bkkC#op0jY5SAS&1Sk5`x(O zN1Fm&!7F?;>vPtgT1idUVOoV|R^PoTCXqp}h)%6ze!} zv#4k(pEiF&o^=Epr|)3Jv8BBD%K~=0x8bzpk!yA_CVf01DM(hbgSF)!u;I*`s8KV^ z`2eeGmr=SS7mv@)MCqf$keG5|8|EipGRUypa%zH)5)_{)|$D+0n$oDfcy@<-g z3;3q>vzsddP~Ezp>el_&J$|IEklDEp5G6`Wpoy-~mFu1>aUsSV36XdsArfySMB)uC fD!%(ae1`rGE+P~1u#Q@x00000NkvXXu0mjf*w>}F diff --git a/Passepartout/App/macOS/Flags.xcassets/sb.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sb.imageset/Contents.json deleted file mode 100644 index 3dc5434c..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sb.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sb@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sb@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sb.imageset/sb@2x.png b/Passepartout/App/macOS/Flags.xcassets/sb.imageset/sb@2x.png deleted file mode 100644 index 473108503fc1bcb053e75d33bd07bba5930e41c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 859 zcmV-h1ElX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0}x3>K~zYIwboBe(@+4%@o&4XR4_0=VZ#2vA|ZqW(ThjoJm?7%jb?f= zQIP;p*?y zzP{If-g|wo72v~TNFM{JtdxjGH8zSMKt;vo^|lsaP+WZ)43^T+plsM=-@a|Mwia86 z$V3G2`Ru%W>7lKyn4uvLJv}>-WnoQ4MG@%jEhiLm6AYH{{JEQ|D!XM6L7y7GzksJt zUChm;@OUOjr%lu%#9}q%=ZhRVG{wlsFRK_UQ&UhNP+L36nKLoG-j$vnKVE1Y`+PEi zK$N<=Ns5YW79lc`^n+072LQ2H<|S-w%%{9uqQBpXC_+50F+BW}iHRftk;o5AA>!XI zgu@H8v^Xd&wb9;QOgNmh^ug;Rtv)F$lbD&wjL7V4ifB}$xAz+^mz{WAGp(0P?2^Wb zgU>kJIP6^m2?k3T7?>j*UI3t@V+K{#a5`72?b)Ny)U=(hu0+m&p7z`Gt}<1bo#(p* zP*r88udf`xzkooXkb!|Yy1NsoYNpq!s*#r`(Al|@lP3#tIBYaDC|tM@XKHFOYk+KD zGd7cV>{5-Hs7j~g!Eq9Pjy4t&Xa`RXIk-FheK zamHl5d{l*-;}H79pfTnShmE?rN%rnl@Otfh{`_Z8eLk7yW|eSwf%^KqEdcH&Jx>$kk{Mt*gVG;G767ia+|#Nnq7q;S7>U8H_oe_NmOxAZ zQnmm~AO_&v{Zw^j#P%Q-_Ux9C@yi- zKSWPkA2psY=z!O8wCnGX{wKhIv>bU{(Q)b_zRHMEI2I#$@eT5D#F`B+E6Dl4BSlBN ljg!3g4mtD*d6RaQ=Qm5PPa3-H$xQ$N002ovPDHLkV1m$wjcxz{ diff --git a/Passepartout/App/macOS/Flags.xcassets/sb.imageset/sb@3x.png b/Passepartout/App/macOS/Flags.xcassets/sb.imageset/sb@3x.png deleted file mode 100644 index 930e3748634e9edee3fe449596c0b29cfd9ca895..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1109 zcmV-b1giUqP)Ro#vX^b~Vc|uh_+ZRj+yfB_k%U&oF+r^bTA&5) z9@r^sZ|y(Z0-wio?)Ti^=bm%!KM4kUh(MA+mLpr^lWHyRP z$elcC!Q+{anac#BrR8=9xq8(T>KGad(BA$!?&fv5f^>8&aFSKFs_)Y~Q{{5wP2JingjM6KBtkv1^wLNrG+Lw3(~;Erjk`k_4MKYZ)2} z0FaX-v3|Vua*xTHi&OG_b@l_N_7NU&v#mdB5ma>d0OT&^J5L~;9##AH&jb?f34 zuC2|dp}|6V`4eVlWPH8_1_uLKDKs9Mcn3^PEl^sjqpht7qj7O1-o5)i_wP?rTl;s$ z3rv)_Ga{&{Fw)(<0f0x3W<&dEC6I5NR6Y;^3JaAJwST{!zP=eQTzJW*O*s@4D36Jj z%OpmiRt*r=H+|UOOJYjr1{^dP$#!72z^0|138F1EJ!knFUQ92xg^Ac~YV39wBO^iT>&@)l zo6DU${wB&q(x*o1qAOTvJwOYDT%Bk0YOqCLZGRxlXV5-ivHyg5F{kB zk_7}YMdAts;Bx=+3J9W#=yfyHSO2mq1O#Cst5HCpfz>S_&@|Tkh_mim)R(!50JQhP zw|(H5S%&J8K$0Jz_zdy|k-vM0w325a63wLFc+X^*@{jwmJoB;Pk5PPI{>jr9ezN;J b{T=xijifPB+1_Dl00000NkvXXu0mjf8O;=t diff --git a/Passepartout/App/macOS/Flags.xcassets/sc.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sc.imageset/Contents.json deleted file mode 100644 index 282bd27e..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sc.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sc@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sc@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sc.imageset/sc@2x.png b/Passepartout/App/macOS/Flags.xcassets/sc.imageset/sc@2x.png deleted file mode 100644 index 02f57a8cbd0c98a229bbb20415b7265e82f3e3fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 880 zcmV-$1CRWPP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x10_jBK~zYIwU%2*6k!y{f3v&d1=5FB1T*nP!z@Ay%nN29U5F*pbW37I zh z`5D*%-268H!owjg&Fm8-G|)|P*(DGZk}b%i`A(;KSQxBc4LLcmd^t2U#QFred9Z}Q zc+b>CIj{xL`q2#zhRjUaJUbggLMEgYL2?$fuy{xJ#6=fS2vle=4-ABrD`DL_uvn&; z`vnOyd8j;72?664-vHUbOBLouBP1uwC(p|Rvso4M6p*XCjuEnOT;Hz&`M`jmT?__D zNs-O1R)~mDW$qJXS)9bO{gV9P0rmmK$YVM=1ObwgWaHdih>TS4>UQJmY^T@WM#mSC zuD6ffqaY^32p3M3Loo0OSP$G)*d!rgtnIO}{tZ1IemU&)y_e{AinI<&Tpw=XhVd1{ z!tYU8#y5gSL8>-a5SH$O2Y?kA|N4lDkx!nV5ApGep1(i#^0iCE?v!Zj7is({Q6oI% zvtiOl$H$zS01_kZSPwVLqge?20%p&KtStHTsi~s{-hqCGIwc$qk@kLxJ3m^uBD|p2 zFf?&zqyJ_pf?O>+jU{9WSW$P-$PlYX-_8u|+1 zTzYbAO6=f#XVfD4qG!_R61nS@XbkAVtMpd=50UYj4}Sn0pz{d!c#kUp0000 diff --git a/Passepartout/App/macOS/Flags.xcassets/sc.imageset/sc@3x.png b/Passepartout/App/macOS/Flags.xcassets/sc.imageset/sc@3x.png deleted file mode 100644 index b7b57640d5a91b1a55af12cb7a4c0d1ab9951c20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1300 zcmV+v1?&2WP)&A=={LC!JhDw8*_T>*Jfxo|JHKThg} z%f0v9bI+aMKhN`f`##U_`906`JHm5p=pjIuHGR+gtfLh(qlQ0; zj|hkppk;Fz`Xyt5G5#B{4lok~5SW<)USD*E9$S;b(*Po?v?LFxz$? z(0-Z*fD+)K>rg<59N$<$@KZKzjWz*UfNAn!%~LCDegli%zN)R!{Xi;iNr1*gLJT(c z?x}(xjSO0WQlJpH>seIO)H<}jlCYE!ZH#pQ*}yqv;+n0%kTJSqz8BvIId3JZz9jcbWpJQFaL}* zBA3=S^(OpIt(xuI5FU4^H}DOx7{5q>pF}>-{hkkl!}x(0xU#R5}=aE`enZ|x6tC7k{u`mjKFO*UOmI- z)S4MK#HL+8g5ZOa1t$mc1&^gG$fZ?OADdU` zbdZ_~+1Zer3sF(Zh`ciI0!!X&^)6TqtOZn_kzSFUEC*jy1asz$8}drTpc|uVM-@!* zAY=nRM83Idjr+kD6hLC4TQRRh4wmjS+3|2oWKYRnBGZOk2R!ta6d^nu($nRa2Xp;-FyMJyL#N`+Rw_-$NsNagWTckb>loGMV|Sb9Egrq7O>sUXyLrpN=Y~hqm;;3F? zOmHg69yZ>ifKYFo_qTp_oY3{;jK_W-Y}EfT+8SiTuKup@i0~hMx^Xg`-Y=v80000< KMNUMnLSTYmTWPWY diff --git a/Passepartout/App/macOS/Flags.xcassets/sd.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sd.imageset/Contents.json deleted file mode 100644 index e790510e..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sd.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sd@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sd@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sd.imageset/sd@2x.png b/Passepartout/App/macOS/Flags.xcassets/sd.imageset/sd@2x.png deleted file mode 100644 index 55f0018da2533bf41fa54cbd9df27edc902d6ce6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 443 zcmV;s0Yv_ZP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0dPq~K~zYI#nU}YLQx#Y@vnA6?@&Z#6xP%OLbwIyQiIODg1ke4zJaEC z3l6RsxLh2xsa`?UP(xEfT<~G(N$?^Ph)L>ga#8bM^gjIHZ2!agaDL}-D)IW_N7Hb0 z<=y2K=0fL?KETnq0a~+=I0oqinjQxs{mA>UcL4>z!w>*v6`ZJQ2m;PjH4@T~omGv6 zfPGcD!dIKcN3FbNWca8XtjV2NWnT04OjU^Dv*20V@bXzUG=Do&@P(E#zI-H4a4~2m zL2al%Mu(%z(OPmhSrO}EuT(FYhEP?t z>f($yc32-f`3vNLd(+My%(Z?sRp1i%unYm{y^$v%3$(38{136d3IhvKC002ovPDHLkV1l2j!uJ3G diff --git a/Passepartout/App/macOS/Flags.xcassets/sd.imageset/sd@3x.png b/Passepartout/App/macOS/Flags.xcassets/sd.imageset/sd@3x.png deleted file mode 100644 index 2d8a4426d6c2f0d3bf6a7195455d310e22ce892f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 511 zcmV&WA(fU$^|ngpEJO39sbZWIN<;wKuWhpDS**6DYUETX z69MS;dz9)W@>hAf{jSQ1nV3%G12_e)J=tX@+=~P(B@uTb0Zd6`vPgha5*aTN@RUSG zi)fIN$gqe;DT(}uXqJ-5w}^%*iG;|};vf7O`UT?p;@XWS#M%G=002ovPDHLkV1l9s B=IH|gW!U_%O?XxCVIL! zhIn))r<{n|H>E0bb(9^ p2b!1}cvSTNfBty_l4yo8FbMj{Omlk2_7mth22WQ%mvv4FO#luxP2d0k diff --git a/Passepartout/App/macOS/Flags.xcassets/se.imageset/se@3x.png b/Passepartout/App/macOS/Flags.xcassets/se.imageset/se@3x.png deleted file mode 100644 index 5cae31830b9158e4ef016ebb36c50d55cffd51f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 353 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2rPgjAEWH zjv*1PZ*Ls*Vk#6k{_*~cMQo3hIOO)UuruFx6!^kx^_6R%PdneeT`j6#ILbQs;&}48 zszjqyoCYr^-XW6rzy_p@-bxHQ^?~WOob!_T4Vwp1jd}$2YaK55c$7OcC@T0x8BI%b@ z*>bg8FIfJc{91gk^~bN(f%5unZ?&Jti~N69B`4?iM&CI_^D*Ne vh1gri@3FN^&5k?c`uamag=e`5=OcZU`yS?}Pkg8Xh8csWtDnm{r-UW|!8w~I diff --git a/Passepartout/App/macOS/Flags.xcassets/sg.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sg.imageset/Contents.json deleted file mode 100644 index d251c0f2..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sg.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sg@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sg@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sg.imageset/sg@2x.png b/Passepartout/App/macOS/Flags.xcassets/sg.imageset/sg@2x.png deleted file mode 100644 index baf21ab98e2820e4e834ea59a744bf7a9181db6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 490 zcmVX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0iQ`kK~zYI?bXjp0zn+W@$b&Ang0pfr07=Hu7$Tf)F~kftA~iZnm&O& zK*zv~QWT;OAb1XZ0|h~_Qb;P3bz65k6|~@S^|Ig7Ff%Y8=JJ!CHLa^R8k;PzWligf zS%)GPLt0vz8|FVmmWgGvY<9b>7K`|Hb^wq9fjL4X3AI#WrBGn}_(=cw7^B(5pG>k^ zEFvP2`9Ok4M}*GLF*_ZG*(~zL1_0B3pK+~*_4$dszV1FGban>7xLU=wEaEpeL@q8c z+ikweWkfiPdUwaNrnv%9eiw2g0l@V26-<+1E{Cn_e;2LMh__6J>0sar{;P4QD$mJ!$lM`1E z{~(j6Cx-bvA{u4w{vOHaqn}DKu2jG@8JwQ-RVuj;0eHW@=H=i3>*E8pQo+Bwi#?eD zU>gRuVYmiSoEv(*j$W_3_TWrNFqP{0_x2V#Yh+dJNw#f20O>_SfQOkwZ{B$jXCB0v g2XW>>oOuvuKSj@m_Lkpv8~^|S07*qoM6N<$g25`@W&i*H diff --git a/Passepartout/App/macOS/Flags.xcassets/sg.imageset/sg@3x.png b/Passepartout/App/macOS/Flags.xcassets/sg.imageset/sg@3x.png deleted file mode 100644 index bd654121d3bc5d69db9a0d77d98d396c7c877084..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 717 zcmV;;0y6!HP)Y4p(ap(41d)JhO>Apx7~0z_oBSTiViy}55hO_|@tSzRS} zc2?eIKrR~Pc4r5jB#}Kjs!-&|Q4U1C-A>cxrBaAFouvH!ss(#3N^9k>uE@t?O2N?4 zQU0LSisW*Y&DFZys@b{97JJg`RqkuX#*}-#-Hv^03)}iSCa1Gl9~q&3WrYThM->O9 z{u_M18)QNu>c+D*7_7+2HE4gTt8;=L_dE`U_^m?)f2c-A+tEf=q z!AGNMV1Uot+X!Yeh3jj6xm=`!!D0)^^3&HU^WbDp?d@^V(?dEKL}#(k=<{KjnJKl; zUFiGQD)a1TywTyn)Ypg3W+Q)b!Hvg5A(5zLz89nYkEjhnmQ~W^|H->~BbtcX&_vXR zCZaYp5w)R-s0~d-ZD=BDLlaROBJiWyA>M+2?jzEkP#_=Q00000NkvXXu0mjfufjzK diff --git a/Passepartout/App/macOS/Flags.xcassets/sh.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sh.imageset/Contents.json deleted file mode 100644 index 811d8a03..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sh.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sh@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sh@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sh.imageset/sh@2x.png b/Passepartout/App/macOS/Flags.xcassets/sh.imageset/sh@2x.png deleted file mode 100644 index 02a7482873124baef120ec63dc0ea7a4c20b9c75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1433 zcmV;K1!nq*P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1x`ssK~zYIwU&EORCN@`KX)H2k6pk;S8!l4(D0QaJ}S);4HX}gIaVs> zBW-d<%dycWmDFs~)XB1Gbez-zQ%5r$ZBUcQCq57b6;3p??P&IPyN#jn8j-(j=W3@YYHJ;>5YYg|vJEE+MjmidJZJz^=rq`%F(S5(EybvAkeMp8unCz>~tZPSEBIqMcpZ=#bBPhLhFT`89bpl{H5kv-VFc^Xt#V_v=%w^GF}U4N#YtEn?leW6Yl)%NOfcBV4=28>=@mVfkw;PIr-V^ajI*`7&dM zmFVaiqM}S>WmR%BKbq;Y(ow5Hk~)%?*N>c>V2&L#kd$NxK(TGxyU}lU6(}4#jq2(; zmMDiZ`r|(^n`@p(pCEuF$!yw`Mz3D0_`O0!_ru8qdU^Op$e>F{A7W#j%$=Lez=0{~ zbj3tQy3h%c>`WTk@aL*(`t(U-%9LG9oLJhFk<_gzmRq*lp*dl%vw6++CsMri*IfDy z9)s;c0L8`CjoW&!0m6VtpY+jlupsIa@Wahts zJ#Y*v<4ee*6OCG+LqrjhJB;fNJ4#XHzAF^FC}1tj0Ja0r`pe-d5X;ihoEnLkmWX!W zD3INF2{LK{)Ph8v(}ms9P=}(!+345i8F7jb=6pzH?w8mN-grbA!B34w*5LAZ@b?lB zC5eZ4;c_%JMw@4()S|{@1yqpb=fGBc0MTBH&1yj<*Hcw(#Zpnqkc+!{U}>7lZC@jq zb&MS!fav=G-x5f-eu&+%0okWNF4@iH9bX}r-@x5>I7RtQ3s~D9jZ-4W@glvvj^nTM zJFPa?fao{Z<00000NkvXXu0mjf$>g}M diff --git a/Passepartout/App/macOS/Flags.xcassets/sh.imageset/sh@3x.png b/Passepartout/App/macOS/Flags.xcassets/sh.imageset/sh@3x.png deleted file mode 100644 index edd7df44b2e892a9c38aaef32a2fbc0e76a302af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2388 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/sj.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sj.imageset/Contents.json deleted file mode 100644 index b77353e9..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sj.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sj@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sj@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sj.imageset/sj@2x.png b/Passepartout/App/macOS/Flags.xcassets/sj.imageset/sj@2x.png deleted file mode 100644 index c926520dcb509509dd63069b31adb0f19aa21aed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O^81FzS1{ zIEHw1CfBTAyL(n+qhbpqf5O(y$q(k4wH`FhR60EGvAF~Yq?x)JFZnUcpvJ`T@bak^ z2Tp95)maq0g4s>Bi4TU^dYvO~6cFGN5>i#=VA2^WE z;jg%Qt}JtRtK~Ar{q@E*tJf{Op~%z2Q_k)yC0WX0B>}@Ge2fe|Azn6noljZ-1Czni L)z4*}Q$iB}qS~GO diff --git a/Passepartout/App/macOS/Flags.xcassets/sj.imageset/sj@3x.png b/Passepartout/App/macOS/Flags.xcassets/sj.imageset/sj@3x.png deleted file mode 100644 index 1fffa4731dd94245a0210ae33add64b7b14752a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 465 zcmV;?0WSWDP)|0LVV?~x*SLyRrn1|#4 z&y}m6{aU;BH96*!3RoBz80^W>$fT~uz{tbH@L<&ihNCy0qsaYJQsHM7;bUOr;uN5U zBZ+bky)2?N81=|t1EU@pY+%$QgAI&&WUzrzj|?_2Y&^okJ!v9Y8W}k_2&!QV3S?k0 zH6_b@QUMFAlOxRxXI52ZU{+P7nfb_oWyQ8L6d7>m5oUK|!`_Pw0=%3QnM*2QWYEa? zPgfgh1cps!AN9y!1B}bJo~9^|%vp1kVej!9C}PtaqUfI4St7hlC^F!{saqHx32@eD zkPzXc$XrqZ%kO>r$X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0$)i)K~zYI?bb_3R8btq@$b3!GR}ZHPC15RVpBedh$4Edq85QkCW*9( zRzX{pqE)LXA`wCqi)>Lr8;zo}mR9O$5w$SL9D8YEy2u&S<~lR?+}mRCq9T*cD7o;r zI_LjCi_bZS`=Hk!2wVR-{+_|G!WzjsWO3FZi=!oK#=8APZnWBS;c}sD^&y9bAQH9f z&T}oL#Qgk~Sa+{IS8}|#Z?_OC-Gvp_?Yg7-1f&XSZ|j?G>G|nes@dcW#|Ns4 z>p~sok5R0!j-#p)B`+7f{yG4`$r;YqcO{MRj(wlj`uj(sufK^U7c?D=*<*E^4>S!- ziL#{-B`??R$w-7p{^6t{2d0Ok39m+x5(%g!Md%HUSii$KD$0r6y^qMuKrJao4hB%A znKWW9i6N$e1RPbBILeM-O--R>ZzTO@Bf{e$()JKJ7(g)%AZ_&^Q7sJt=vN!iFJA+| z-S>j2@~Zd(RRv&HbEbrVzr07rvo^&CQpRCI<^8}ohENvNN7J)c7xF~p6U)e?HD^C> z(hE4V^(2RyKT=y*NmquCJKnum-fSMscv6NWyqf0D5ps{8<`L6 z2<8{HTTq>7p;B>-D`qjb##|Ue!C^^ieKG2SZ!tPwri1_xLbNaIqh~(t;Nq7KI?J4# zc8BObnnumLwiQ9xnB|we)xBvhE diff --git a/Passepartout/App/macOS/Flags.xcassets/sk.imageset/sk@3x.png b/Passepartout/App/macOS/Flags.xcassets/sk.imageset/sk@3x.png deleted file mode 100644 index 58505d19a4bb85b418629c4e158fec5ce6efb7b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 998 zcmVeUV|C#S&&YoG0I5jYAvJ4PPDYeuloIqP@15Qy2Qf!oh6dR== z#fD*9nNE}Z_)uXFxp4#fl~)whYx@~?g=#9s%uBE3ta&?jp*7SK?|KUvj$ByZvvbjd z*xp(y7Mp*O+8SJ~EvRTT?_Q@cj-USBmatjQWX-kJtC15?u+qqI1ocN0HGOWjRvko+ zjApbdm11CUf~j*U=bZlwqve$^Yzcqdc@@4nbN5qE;NHFs86HOXeTaacz~@KMf>ogF zOkTh7yx25(^@D!CI(gPv_j9oQ8!40NthtKEu|nS=ha%`}8xdX)=80akU={jhmu5Gc z7G+-Hh~fFL9mG)CK@tZJX2zcO?P%3O;(NPt_A1lFW#|Q3wCT^TuuMwiKjXXZ#P!ev zqz8vk(Fu$VO$eV4ciR?(&xcl914=Q|`NnKRDn*&GxSuTBL3C+lf(XA4*TWA2pr)t6 zFc2;m-i}>}3Lh9c)`{Lsud=M-31W)#Qmg+IvAgdlJ@_kF7M^FG#=Z4Xrta7T%${gk zZFNR341;*ea?W)QYzG;4Rj}&svHb17Paioph8!Eq%(Y+@qtSRl2JK?|egA|X;qm0G zO{WFJlgW8|#pR9sGV*8s?()EPkWYUY$GH90%v_4uC0t`W4mwQwgYEKn(2ibE`q$y z#J3NhrftV&8q)Um`@UPs+5P4CqK5zwN3Y(L*S%JGU69Hh?L2buB*VHt_o68Fu1j+7 zB`Jowk2qpzX z{Lekh(I*?}+UQ}0Od?RUdMD_8@j6uz%ssI2007*qoM6N<$g1(UB$^ZZW diff --git a/Passepartout/App/macOS/Flags.xcassets/sl.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sl.imageset/Contents.json deleted file mode 100644 index d10c6c5a..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sl.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sl@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sl@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sl.imageset/sl@2x.png b/Passepartout/App/macOS/Flags.xcassets/sl.imageset/sl@2x.png deleted file mode 100644 index d4351924bdea1b8ff4575b636fb9b90444ce701b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?XxYCT;X zLp(a)UfIam;2_{~F>>dH0E5lk8wG#wa$3hHyp>T|+40~91EKRF*O{&*)Uoojxfv)q zTsUOpc*2LVpW~w7B4wB7HBYBk96Kj8ad*wcDM1W9{Jb_>@>^bTT->?O{@S7wY~Dxq v#m~;3wUQ+$my^-pf0~q22~f6Dy@*+Fr{pfdjq##DhcI}$`njxgN@xNAEb~Zf diff --git a/Passepartout/App/macOS/Flags.xcassets/sl.imageset/sl@3x.png b/Passepartout/App/macOS/Flags.xcassets/sl.imageset/sl@3x.png deleted file mode 100644 index d34606893827c1ee4656b7c3120be140b6cf8617..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv5>FS$ zkcif|*ADVF81Og;vRA#STf@zq!r<2vG_6Bq^{;%U|FfM}MoV)n4g21~<^H`vt7V7c z5njbSj>65pCuTga+3g?tn;c)I$ztaD0e0s#3jM2`Rf diff --git a/Passepartout/App/macOS/Flags.xcassets/sm.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sm.imageset/Contents.json deleted file mode 100644 index 7b0e3341..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sm.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sm@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sm@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sm.imageset/sm@2x.png b/Passepartout/App/macOS/Flags.xcassets/sm.imageset/sm@2x.png deleted file mode 100644 index a4c6ee62062fac41efdb614f68822a1ab4fe5d48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 874 zcmV-w1C{)VP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x10P95K~zYI?UqewTvZgufA`%t?^Zf*^h%v;3^LrcPovm;N<~dMAgf z)hdPT1%#0FYIQa;)7b6+jvr;m_}>2yB%jX{%`9P^zDOamj+#yr-tj7p*T=DK8%@(X z_PnFgSRsr2ud~?0-_Sks6WR;Q)I*<8E7hsERUFUh*t4UIr1NQ}u1@jg`7b!}yu-$z zNlP0+Dh}#HS;}2=9KANr-oEEJG`gRN*7x6l9Q*nhbBi-PyG>!mOVAoV#Z9vp-*?y* zuQ8Z=msjKg&MkkBlnQT;9SrOpxHQw3r%9P9cIgS;88K2r9sp%YbDCf7D5@IS*v1t zeN4h< zgdQdw*@7&kNi@!J+SwOQxm85BSmnr3b=FI#h+(HFU)R1NuO=FNbV}fLUg;fKh zeZSFC;vIpAZBxazgHg;bu~qdLF(e0vb*5KJyb%vk%=}I{mqRE($iR({k=H^z7VY7~ z>n&Wn#Z9#zOZn%YRsY4y837Q!hxA+=8QNSMRu)8K zf+PQ$z!h&4fU>vcZ}CAP{?z?57~L&B0J&X%0228>t}z8gSpWb407*qoM6N<$g1JMh AVgLXD diff --git a/Passepartout/App/macOS/Flags.xcassets/sm.imageset/sm@3x.png b/Passepartout/App/macOS/Flags.xcassets/sm.imageset/sm@3x.png deleted file mode 100644 index 02dc0cd277b5d0969fabce4575e2b73d521a604d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1461 zcmV;m1xosfP)>8AV~-zZghxxNWRel>ZKKs<)#&o z<)&*6QmquOwFPh;K+pO_9>oWZH`?R%6AO#Rkk@=ZPLOu(9bq|i?V3#r!+)v&rQRqU$<^@&?QU&!BxgioYAI`5;>G zDstf>)m{Be{Ow_Ge(?2r+5&Zawd{(`EAPL;&h0x%fAKVud>E_h;?cnsfRGTjj<@S= z&IARLBCnm}sak^RT_Gd4XyysWb4w%ch8TO5jh zNbhe>GuHes*7WWt-2Db8qsQpjHo#EhALw>Jni5G$!eUQiU#&4}19|Jyx9C~d zgB|qn#NB_UgBiY)wvhGQLCTVtcRYACm64)`Dhb9-m2ax|Axi?m4Bm~K@KrUYD#;rQ zayEUI17{AfU0u!IQ+tsSa1`lMnL*<+M-qcfpIN_Tkfk>wN#J#<+TRikMDe+C-buHy#vaBimFYk9 z5oICs?N`U}x;7eB&JUd;E)9^W1UaW}AuYFHb~jRSf+X$jOO67d1Rve~;!-c`O`FlQ z4P>vL=8=Gx57G%9ij~Q08l_x{Vqgt=@k`v&968;_z4R%)^1FO2Z)H&*M@Aur-;ebCjfWe@!4Yee7CnGm&j?qszbGALk98yJJ0x&<-w2Ex5WtQ+kSG z#6vKj!n0WBCWd)cd6o`sf>tHPcMa|2b4_T~*_t2#q}ajik_S;#E%GP-f|Wgt>nsq^ z4PIWenq#-Tzz(s8r1w_*i&rqiUZQg*hkbX^G&4-42S&pYkOZ=#FqV9P1?i_Xfz&vN zE^25!p5|YngqkUb*75i1U2GO}WQ_YTEE_kw8Z8rH*td8S zlnRmBVz9(Ud}Wi)(kwY6N&^*kcLiuMbly3iq7j>&_w+F|G(>-YKk;~+k&zMl`ugzs zeB5$dAHP2`!H&3xo1%@Jn9lK9+F+Ga#3&ay7wWivAi84{%UC~LkSoVimy&eOryd9C<7TRfk$Nf~7K!W$H5_`$ht zT9!wf1%I6I(kvFa>{)gFK$fFc{%b8at$-{yt$-{y{b)c`;OLDGQ4f6&F}y#sPm>`V P00000NkvXXu0mjf3?{%< diff --git a/Passepartout/App/macOS/Flags.xcassets/sn.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sn.imageset/Contents.json deleted file mode 100644 index f4b5a049..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sn.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sn@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sn@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sn.imageset/sn@2x.png b/Passepartout/App/macOS/Flags.xcassets/sn.imageset/sn@2x.png deleted file mode 100644 index 7d9c580d34fed4f46732c5618de920f6cff63446..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419 zcmV;U0bKrxP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0a!^yK~zYI?bE+Y0&y6}@%J5e_zR_>P$C>kh>(VcrpP6SXb}opTOuc+ zrLBL!siCPkY_KKzMT=3R5Cnpi4UuJ#VS{9PVqQa(VaxS3HR3+o9S;vLc)lOWy5x;X zk~E8s?1gSm{NB=pp4p2SNS=LNEZS6c{ zCu!&K+}1S2)a-)oq@AKF!%VKdki?Cdjf4|`%B#fXg95-p)P17O)l$bQO(Nrp)9k?8zA4s(rzlz4lDTXU6Dh=((b^r-+0yE3dE zb+;d~GL^?9YoFhPJryEB@d^u{<}2bOR^|J@mP39!2Ba|tq%nU6@&*WmSfYD|-zWe8 N002ovPDHLkV1mfpz&QW_ diff --git a/Passepartout/App/macOS/Flags.xcassets/sn.imageset/sn@3x.png b/Passepartout/App/macOS/Flags.xcassets/sn.imageset/sn@3x.png deleted file mode 100644 index 1ff8ce74d32e3c683e055d6e22b179a9b19c7bed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 616 zcmV-u0+;=XP)ERw8(SOS zoor(!=e@plqs_?BBH&2RK?z7mz{O;VYw1nge%AKedvaxi2Qw{e#|O8saO1=m5*y?p zKw)V`4VUZ33*0-M|KsF@fK+mc^o0dFTWnSF>busdfeQUw>Kwb%MZV&-kAz+BpZ(0x zu4UD+dKUYR&N_|e`d-^;3i>vG(-El$Z--wZ8ZSKGNz75Js@M51V?vQd2wZ)!QK_R+ijj)%92Vh@%&?)YF&M-^2Kfy zBMcudaejZ856JJl|Ddd|hO*Ov`kN(NVDy$(j*dh8& z0f^refcQ-Th~MX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0Zd6mK~zYI?bFXI1YsP<@%J-!vx{O|*(4V2VG9R8j#4(<9OdSqoRo{) zIr{^YtAnDPwz#bW7ZRy0$;rj6*o4)hJPy+~)6CM$>@w1PE;G&dnWtCtd`66J$}K6G z!ovu`w&Eqg7$0Je4>9NeKw|Zvh(7?COu#_QV~7A!9Wc@5F{G~rS{q=b9qNLWK>~Ic zM#GSdLeM}T@6@ifH89f)7x(&1N;p>G<-N*~d=VlcnD2*BnYX5s`hBhdt4H<`;NWPa zg!C26?!syA>vj9rFt-OMs(pkbNGUhZu$Y1DoprWz2`h*2^yYL|mt_qHAQ7=XAFSXv zb_E%3(+a86mb~GKQp^K;_m9jd|lSvg9phDMk?B&c6IgQAJExL z5C|c3?HK5xQ@0M$$xGLW$Sx6NksidTpcGU@@P$-VP;;llgKWNKI?vChWj>dA|9zkD zJTuSl8OF*CyRQI?|C}(8qX>-H|2hCI0waDSFyc1?BYq<=;x~*NNtRzs+hE+ttww07 zG_4}NmNA?V+)RS&!lxyZN`^DiUI%dpvMDGDnN%`tB$t+DECL+~lS&4SlsS@-eR|TS zlmt)jNU9u44vk;~F$bLlo@lfh5ptk|;G($9Uwri(XVembC`*B|N0-npS3q1c0V+(Mv z4X)Lke$9Rso=nQR2A(r=U@#;*11(^mFyg`n+w1#%2A>3JiUKnKjw& zNXWZW8HG$eJQ^3HTBv5EGbIz=;|b^=DVP8lork^`QsLqbwAN}WD`v#9prRCRzm!?} zI5v0T;YS#mftDJ0H?O6xSboDc?77mi|Lyr&a|kGAX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0cuG^K~zYI?Ug@E!%!5(&ubplCTYP06bF%ZaB}G)xI_deaTGs*AVN2x zi<2Kg5F8vG9o(D*9lErj7A=B*inWU1Qj1ob*rd&MXsOUGuZ3jj86WQ+e(&6Sd2DWH z;fN8|;H&635MTgXqZtmy45wckcuqt~w4g8900722tpPBdvOvxD zoyu7l=99OCJcHCr5=QPNB+?eRO5O9x)dpH7@VqN~h7bY)fNCZTv&b=f@ZE)J{xIBb zM)CVD)@|WoC6|FK1adn@3V~cEY9~Idx?#ilWO&$CaFB0^JP$lM8xx6xO&cu_xg4ADbtl)flsl%--BTV^=LAk&r fG6d;WU_kx=OHb_}uQL+y00000NkvXXu0mjf5D34g diff --git a/Passepartout/App/macOS/Flags.xcassets/sr.imageset/sr@3x.png b/Passepartout/App/macOS/Flags.xcassets/sr.imageset/sr@3x.png deleted file mode 100644 index 8ea5905cebfdd5925029dd47cc8fb647700d3d48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 567 zcmV-70?7S|P)618n~=b>Zm93zsh_i{;B{;n+lM$NneAq7Rnf7RrcLCKdqG; z5WV@}F_8V%QqPZ_vsf#Zz;7q9qaE4B5^t^6@`TpXL^_Q;NV^x&?nTmR(2~LZhM{&g z1_iod*!wBBZlV=wE=8yri}Eb3McJyjsfdEvD{QmRFYZH#2IXFJ*&+zqG z01mvy4Ty!oT0nPY#O&3{R5veoV#=L;knNW~!xtqd5FlW`&BcgQcSiX2=`rJ7MgL)y z?Ki%&oFK4AzQ6a`xLG3{9u6H2J=U++oI`-nW{-a<{Knymluj7pT^WB7iQ2rlhZiIu z;Xn}%l1-yZ&|HvlFByh&BKK002ovPDHLk FV1hu>5D)+W diff --git a/Passepartout/App/macOS/Flags.xcassets/ss.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ss.imageset/Contents.json deleted file mode 100644 index b5a1e806..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ss.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ss@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ss@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ss.imageset/ss@2x.png b/Passepartout/App/macOS/Flags.xcassets/ss.imageset/ss@2x.png deleted file mode 100644 index b6f9e0b8f5e8c175749e33c35c5f911f3a2a3800..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 812 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 KMNUMnLSTZwQd*P% diff --git a/Passepartout/App/macOS/Flags.xcassets/sv.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sv.imageset/Contents.json deleted file mode 100644 index bf1d63a6..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sv.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sv@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sv@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sv.imageset/sv@2x.png b/Passepartout/App/macOS/Flags.xcassets/sv.imageset/sv@2x.png deleted file mode 100644 index a4848bd9c02c254dd4e66ec067616cfe120caea4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 443 zcmV;s0Yv_ZP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0dPq~K~zYI?bORk0#O`*@$WceiA9=*SSc+cDF}%u2)e9V6txL@gL;it zy+P}!RjV%GHi{sj7H-lmk_ZZ2unR?6-g0JI6a=&L5Y=M7)!CdsKW5H3!Zo$Ci6Q3s z)5NCjZvo#~5OWs9oCPtbE-&_nf#>* zy-K4q2--6=1VC}uJt~@b^vL}hqedy8By-=&-GfH4^g<@*pjLVM1WF_asicWS6RdBf z2q*;{g=^L}(nJoUP&)lRNKG@?jomOZ6eIbv$I)GuzEF~b<18p%DTvewvk8Gpo{RSu zjxsIO%GY?E%`^!OM57c$>WkRyZssQY$lvM=x+e&`MtQz@V|Jz&Y<4M#+?})gnI4{? z-G9#Z{s}r&7N*AN?(714htx>1vXzPlea^7dW;6_xueFXLdG@bz#kjh(dX~ZvQjniZ lr2fqlZ$Zpi5OWs9+y`OmaY~PJqa*+T002ovPDHLkV1gGG#oYh^ diff --git a/Passepartout/App/macOS/Flags.xcassets/sv.imageset/sv@3x.png b/Passepartout/App/macOS/Flags.xcassets/sv.imageset/sv@3x.png deleted file mode 100644 index d3ccec7040fba3f4abf063d26000cedc92de2dc4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 652 zcmV;70(1R|P)FhM}0fQUf{tx#+%h^4eMo#}Pazwp*hnmN0>xzBSZ z_no<;HH|!)rUo$3I9Kfw66n<~AcZgDHGC1T;fr_;U&L$FjEHe|ptaf^M9`E{s$#nr ze?f5*LS29YqjC?;sK~DFF63sxXu&O71VTVz74&t8^nqR{9w|Q~ZU*+}AbJMs2bo*z z#4g3k>u>_>bx^i=tgMJ~!7_QdrZax$8J3kN5u9W%d6RTH!;QPsEN$dr_o=vytcaU| zU^}ymQ!JSVsUIIW7&}c%V1SjC&t#HyJb&|`e7x+WXtb=z{sI$I51F0s#wh+F-mn5h zQQZcewI>^p$fszZcXppa&8KQ@l>4>JF)Chqlv1s`ZVs?sm$s*V9PY`d7GuU&U zJ$oB7^B!}<{aFNTK^H_^~c}U?jSX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0=r2>K~zYI#g$7)R8bsU0z_BUjv%r;i=wpP_kWCeb|TZV*8SXG0jE#sd7>Z?xWEgQO2kGT~}|F1$|yqggH95fXv^hVO~%$ zdR7r76*(XvsR0bKmYk=LAU7BM{z=!hJ4%EHV5esDO3lco!8(AbyH2G&4tr)HsX7gL zZ+gfvn?svIs(ii|t-Zd-&nqHDvgM5=n@kiQe8@Q^ox-dPiUUskicI{f7X)GR)`+)i z?M6I63$A4ssVg2#ZUD-6uTip}js{ zn+UL?l;DxHqkmDWUtp&n7GoUFLgr3?(&_M(V!t4Ojr#7KP%FCjMMZda`};<`75~e& z8;O>Re}Y6)MR?ZLL|sLAG?UX#TXpT*na?AOQB@J1KYv_6xFl)){r~^~07*qoM6N<$ Ef-d}G+5i9m diff --git a/Passepartout/App/macOS/Flags.xcassets/sx.imageset/sx@3x.png b/Passepartout/App/macOS/Flags.xcassets/sx.imageset/sx@3x.png deleted file mode 100644 index 34f2fe34acac088069b19538f498414dc93dda7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1176 zcmV;J1ZVq+P)VC2aY}1A9gZO5awQWFp>T!T0dqK%nt#inV}Bu#GK zcYoAJdz)Gdeee04d(Pzwv6H9TBl`~2P~-@*EX!$^`&?-IdmCgaYRGp% zi$#!SSq31HaEErbrc(pg>hfIEqIeKw8c8~GtVjB)y*Y0cEfQrA0E$tytU-XHQ*Hhr z(-e(r3k3m+L$yZ=0u;Pzj~-;2f>rHtg8+r8+JZqOsUMy0GO1J}dVRx#3k9ZH)*!Mh zGx7IDrpCTNtG`0jYbF`hW2!F4Sn)ZQCmaZBH58C)syQt#7jiO*uB;QA{ZD+Cs|bzQ zNc!~{wCBn2Be8%FJRSg1eD|$p^Y6#c3F4ASu8W_^oEedK?BM3@ajc^rq9sO>B_)(a zCkTi(#9)9Wu?YU^I;xghQFZ+Gpktm@(BzCJe5f{8tu(EuG%lhE+rrO*f6%~~GLu`5S8=i_A+3KvPrKN>en$}Y-P0**eVGabrVwrbP zb$!hyYxn6fO~cyj|7Y5A2C<+L)-WDBO8K5Wy!qif^laTmU}-tQWtH?M^(=G!OX>I+=5a62cK(5S z?~sLGp4N*;2I9|0}`w@788$cSiFcAycc$-7EQ7WHA3kGIl7%srw!9I5SaZ z6PpSt1U$x!6q-s(<{aa*QHUxjSxBvdRY}RbY8ARlO0ud|3@Ry6s#P>9DY>s!v8kj) q1I4M565;5t*V=dQJyY~QL;nLfwRuwQ>&w>w00000fhdEP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0We8KK~zYI?Uuc20zni;kI90}8iPu*77B`3Xsw73l6Mgc8y`S?fo(0^ zw-CV?qD9h4u<|EjWndSPgqteOCL;?e)?yFbYQD=o++ko4a0Dde8eVZBL?V775kHZL z9|POo;JS~YTA1c0CL#{-5UL0`fv#roHu%?+ApM5!KoCaT5i55^ch<#H7WqV;N~#iQ zxFC$*$85bVo#zggvdATJR8!SI^V*HXh@q|(?WZ=2!-D>6pRCFTlKAO24U^Wi#qwiG z@1=LPU%L^2hSi{OQ=oiXrfD@v$J2pD0BUzNipe6SWQqEHolHEV4~vK+8jhbwneXQ! zA~M}gbulfcuanI7GZ7J)>?Y@;!;5(}hHcwx&+{f>S}3K~01hA_SM)LZMI_=U67h2} ZBHw1ZD2v8UZ$|(C002ovPDHLkV1jN7p-2D# diff --git a/Passepartout/App/macOS/Flags.xcassets/sy.imageset/sy@3x.png b/Passepartout/App/macOS/Flags.xcassets/sy.imageset/sy@3x.png deleted file mode 100644 index 9de4c98a9a07bea91c1691c4aa55e2c2272fe6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 514 zcmV+d0{#7oP)>#v9U=^bQp$d7nM6+_SCy-$K9dYsCM>-l0)n*?Xf+*-K#@~lky2^H_fa6 z+SwaS46?hhOJ*kXXT8=_tMpbPb)3R)`@}_@o2?sWM`n9V{tMoRN9rg=tyM!HC~Ov( z8=VVOt2Gbxb{)U%vof)QRPx)^3xlR?Qft*%o>)c*L8V>^T&pE=@o>Sx+5x$Z9H;B2 zpzr{MjyMynwk48#UH>!xgyj3xEbYV`lP?uLD{n}%>4 zrx10BFc8=ubY@{iOv8$ph7~alD`FabBa#5zs6&Lo7ja&o877fB*mh diff --git a/Passepartout/App/macOS/Flags.xcassets/sz.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/sz.imageset/Contents.json deleted file mode 100644 index 1e8ba38a..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/sz.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "sz@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "sz@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/sz.imageset/sz@2x.png b/Passepartout/App/macOS/Flags.xcassets/sz.imageset/sz@2x.png deleted file mode 100644 index c46582ac6bf54d466f1242782c7c054cd7b050dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1006 zcmVX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1EWbqK~zYI?Ur3kn^hRcf9GvW_r5Kx&43A!Hb9YP12RZRw=u~Q;vK|A zAciZWVHXAiX7Ng6CdQb|dEt$LaO2cOKx3i?7e&E^h|usE!k7@kpg@_>(hl0Ue2mr$ zW>Y>ikQnR5{&(kj&-1*$^MCT398vnxk6#j@ns`d^l@wQi7(D^mFYSwtuPr9UA6E#! zYO-iA*!t58tz&To8JR0c*FJ3ZCH1-FxPok2d`a4E*OqS0sX10+P*E7p&1H0BgEME( z^2XJxgqD_A?eAx~qXQNej#W=)OQ$_fLRgZxbM749`HdLERu&e2j@ z&BjLVHa9bW;|4-ik6io6Q=`f>T3E)0eQOJVSm?8K2zxlu#%X-KSR+imqLw z`1Wl?TH3)vHXBlYK9POlLmi0-FqCW~J?@xCm(2T<1|nNqF)ZO zy1F`QYikigAcVkZG=g5wg&)3Q?A;f@GfNl=)mnr(^%BYnGqYYVI=B1pK{V-MI^E88o|&b2SU3Ai&o(SYH9kNqo96;{$V#r8RMQ7RRV8|I%zRxYk^Hc#5 z1m{O?VP9JWz-uz`o+L5ja_w;>D=Q0tmX;PWGc(D~&OX$U<;h9`xkdOetMj*i5rY7cZ-b9KS>sT4X z+dMb@)7}HcI&xI0io&Sf&dA0FZoeOd;Q5jg%FX88n{{#VIOUKX237p>a)w{;@|-+Y z=D(~>OY%eQ$r|%op>;m4AR}|;HErMYdG|fHJtLkVe%0jGwmU0bL^%8=`mZE#{coO% cPeAtTPfgKMpR7@8(EtDd07*qoM6N<$g8qQsumAu6 diff --git a/Passepartout/App/macOS/Flags.xcassets/sz.imageset/sz@3x.png b/Passepartout/App/macOS/Flags.xcassets/sz.imageset/sz@3x.png deleted file mode 100644 index ccc29cb8a7f962ddb726f068b93a2aa456ba1fa9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1554 zcmV+t2JQKYP)c8HyVM-yn$Mzlzls0J#NmK7mE z#m4w58d6nNr599OR#hb^MPeHlOl2UrC~LPXZnj3PNY!>Js>o_G5t`9Pfy8O7woZtR zW{zn zyJrFbvDPn)`+W2VgY;%HMB{O`ZQI6M2M;p!{r9Nj<0Qjju7CC!!q;EdbeyPJL=yG& zoDDoePu!%Wq{$>Bcw`r2V`Dg-&I_Th`sX{r9d<3nZs+)8kMY+pzeF`01Xi@O zEY?SUftNJL9eU$*QgZK;bFQwj#F%KvYTlvb6owdxh? zlg1+6zPO05`uYISN@*;q5p;_c#ZiYZKY^+w*}s23u~>{#r%oXVb5}($6vFn=`-I>A z9hUU)ELo}`U^YI^&I9LhxLka4_%PWMCsvKu9+eicyy@wi&n1&da=9F;s**?~D3{A*vspAvV`5?g zfWV3t*1z{p{(I;-(&;#L%>lftSEDNm%IGNB&d#cx?q)>HU+9RfI(glOfPsjP-8x%2 zDJyl1im8 zO%tEbhs9zcoleu*+IsVyu`8Fjba*qOlB{reth9i7QNeBny(nOI+YoMzh-pB-x}0w7 zX3Bz>oP7b$Dt}|OeV|&(^1*DaU+VN5DJCx`~3_K z4ib$4p{)v8tO-n|=96bS}{ba!{7X&PN!U6jjZHf-2{!{MN(r-x(5jsY+}FhK9` zcVivRpx-hNTyxZMzWqf6!G=rCGAwVz_thZJa0R%f{T8v{NJh{^35%(t+8lT^rD9in z=`^qDIxWr30Aw;5a=9F-REk6*!Q|v5*=&}nsVNeP1OR*Y?j;Zi;C8!_WtoPC1{xX~ zkYyRS+l|lXnoj3}SC zw(?#iLOPSFkgr*@hK`O7{CObSa5*=dj$bV|+EJOs9f$_e=mFME_(r=f$V)!&{X548o>8 z|1y{dxA9`O=)TuJ)CBV;wH^|=-!N~8hzNAehd)Dq0AfaC%Nf~~1poj507*qoM6N<$ Ef?5Lhq5uE@ diff --git a/Passepartout/App/macOS/Flags.xcassets/tc.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/tc.imageset/Contents.json deleted file mode 100644 index f051f128..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/tc.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "tc@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "tc@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/tc.imageset/tc@2x.png b/Passepartout/App/macOS/Flags.xcassets/tc.imageset/tc@2x.png deleted file mode 100644 index 68df16ca80d17cd9ed8cab6d0d00d1ec290dc409..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1274 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/td.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/td.imageset/Contents.json deleted file mode 100644 index 385ba7db..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/td.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "td@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "td@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/td.imageset/td@2x.png b/Passepartout/App/macOS/Flags.xcassets/td.imageset/td@2x.png deleted file mode 100644 index 45dee310c616a4ffcc2fcdb96fbdaa702930de47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?XxLOop^ zLp(a)o?9r`;2?73fQi0 zp1SgilXUmP=Q5ssD(;h%PfmQ|QKVuy3Cz8e?-u7N$Z@;3m-!&j0tQc4KbLh*2~7Ya CBsoR^ diff --git a/Passepartout/App/macOS/Flags.xcassets/td.imageset/td@3x.png b/Passepartout/App/macOS/Flags.xcassets/td.imageset/td@3x.png deleted file mode 100644 index 72d25e6569d6b288c649794f7d1cd255608d22b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDvE>9Q7 zkcif|=WTfpJ1{suoGvfhy+EU3!j2-2?Nc15i#RBxSaHquTO}nBXY+$O;V16~S&po| zxAyH9Pip*zb#fj#5 YN7oDQz3_K}FVIyCp00i_>zopr07~*t=>Px# diff --git a/Passepartout/App/macOS/Flags.xcassets/tf.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/tf.imageset/Contents.json deleted file mode 100644 index ac0be50d..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/tf.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "tf@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "tf@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/tf.imageset/tf@2x.png b/Passepartout/App/macOS/Flags.xcassets/tf.imageset/tf@2x.png deleted file mode 100644 index 3b7dc74e0909e92cee47c9c6d1147a6a3eb4ec3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 711 zcmV;&0yzDNP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0(?nCK~zYI?UqYOR8bg!zjK{OgAO(-J{rV8k!c7srP67mQDz9)YieO+ zEkdFPAwgt8o2*T>iOI##Dh|BHboJbSPo`+>}*A!aULXQ91gS*+_w6$cweGx?gTV5)=^V67} zTO`A5#IWIC(TjGOi8t!Pl{kCCicT9+PGo))vv{nqqh~w4v=6+IZ7= z7_CO)@e2=Gd+O2aVt@#=?|eXJJ|J_lJe!5ZAV*6x$+ac{aN~SFeXdDr&gBzt)T1b& ze1I5CWycX%)s#utsOPz(6d?q%9HgvhGm<2*WSY>#NC*MBTasvQ^Zp&;j!f4e*n6rK zkJk^t>-utv3(VBq>!Gf}g;pbxV@X7k1i@gaZ6b2Ae)G@c@%q`hEg697+AiF~KAeqi zv>J)6*$MQyros{t69Fj5O`)^l5XA*%2HtH diff --git a/Passepartout/App/macOS/Flags.xcassets/tf.imageset/tf@3x.png b/Passepartout/App/macOS/Flags.xcassets/tf.imageset/tf@3x.png deleted file mode 100644 index 88c2b3d3824fb79fc38012e3fb770036ce5cc911..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1065 zcmV+^1lIeBP)Y_>OQb?szun9C4FSSui zUm9(Z&_qp|@KBo2G!Ze44^54i&}jQsELa~fc7d1>0qD~W=!_?GLt#yd~^Oe-k#feDiZdgvPbS97*>4S9lN~rUFGmj)-dlRBCt|H$^j{MeqBg=p z)J9l{+6W6#8(|@8BP>L12!}uTE+A)2G{uk@A1xc4U>}mTjR`s`Kl$n2vik=JJp8e& zkpC01XOXpJ_l_)f6r``px3%*ZzG@u_d9I3zn3H0;*0Ps4qxPWqM|wan~#*uYXNunjQeXPQ&N#KMTNt>UP{7>9lPhbq7T(#V(=9S1UFqk_{O~ zsw=I;8MO!kT<)Kw$##P#+YOq(wFiAOQuSn|8*qCUxZFR9AV8c^OLe7{4H-saW29Or zQjvILG-po~V|^ltzM(13HvcT^Tl3atd<%Z8FE#=Y2*7iN=~R_(V*AsnynL{Q**T9g z6&W4((AMSP_^B)OTy+7`R5@AmOo;D+>UN6vHB(WRjmaSOP|sBt$4_0Mt;@maxCczsw9Ns;Y#~*I#6Dx|svWkGT zek?9ZX&Ut!YEJw>-KRavd=y}{qQqsV@ zZxMhuUU{7K6g{FS1jT_0DzXxLN+02ilO@vqJ8d*xxc;{>iucG8JB!vMiUI(Q7p`-$ z$I1776ExW*5uzxtvuM3+yhwcMv9(B44ncs5vTTmNolE{xX8MP3@!RjW85)_UwbOya z>EY2#6Cc(Tuy6NcEd1%Gcfb`gj^Y&w1mJwj2nnWW=6#C{+ou7z)H}|l-f;ki?bFQr z7D+HgbG~JSzzWTviXK^)tiwCEz`A5TeS=c~BtIzq&2Uc60mx@j<2nY*F*)LMz&0TX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0d+}4K~zYI?Uv0-0#O)1&x}LPSY|0CVzdiF4XjOz5TbRsY2ikCgo3Dz zD}4(WJwh!aXjx!Hi-L+?AgC07N+q)_b=pNUO)X~35C`em#;s_5OlRl~5kWDvF;n~~GP=5omk?A=B^#)p50yv+Pa=UJ%h*F9Jjo=^903@#$M3Lg=rbeRE7~YZE?k>%2RvYK@zb5gmh&>oYbi28WM!8R=aK>W4 zDghu}HW-=I9SVxVbs#{iT&A|Sha;0gbh&6(tD3sC6F7ggE-~$9MtW7T=@Tz!XPUTv za9}u~@B-I0>mq&!a&Nq>-Q?eh2v^%2w%=GV)2vQ_I%HkKCa^Ds<|tYpWtpdcvzoq$ q9${&Vo8toGe<{)bvl`JC(en;n9%2MsSNVSc0000cjGiTtt!TVMJ*=%AtR+pF!hm)G}M@80kG zdG6l(z9-&1q=p9GJ*E2ov7XwEkPr6g(@{8kpT3>1Ix06N(7jjb#TPM+mPCYAB95G z`L|!f(2KV7DbsLAgp`D)rU=Gj5DX$hAqJ+WK?sIdR`|ZX4Z!u;8P$BA+Wvm`57SVBMm}X+|08l_1pDfWkCT&F#UcQQv$Nbok8YLdL5xNc;?gcMNGpNF%9`>7*F)A YpZ=|gW!U_%O?Xxwt2cZ zhIn))-;tc~=f~NT#YQ~L94%*i=Na%WVV-bsh4Tf*$IW-!%m4rUcsf-<_TZ6Y+uB{P zoceP3e`5(NGc&U?^Woo)!luo~6m`7C^QTn*e|WjU$|+z%eBaX(6E_DvKOZX@&@LgB z>CHcDg=8s*l>`Wz@Fh;1BJ%H7Y;dK6tfH#sx7LrZugkCgZ*0)Qv&30KQX&kb8mQ`L w&3nb?|M%CkUlHs%b7oG@;@)%B> zArY-_uN!hTI|#Hs^qFC&d6xIcghThd*6eXym%1`yndjWq949qHPO$B+=xnUyd2fC2 zcVcAO)2nx$J2UL^d%yWR2eYzywMtJOAhTcX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0kTO%K~zYI?UlbyR6!JnzjNna2pb8+KZ_e$OrWwf(Zm>HX<`m_G>1Yob$V1&P?XY z#^T*9UjH<~C1Mxv{fsVC;5;Ldenukw42_6<_*ie;sobs%9$||$9ynnm-E77>bKQ;4 z&4qPp7tOvRr3GC2765Z|<#Z1j3ybr+Zm^?Ki`{t%YBA+1rd;jW8-y?sb@=@{>N1k~ z8bl@3XQ!9vGiiknG4G<~By7=a6eDl@WBt(iD6BHeG!!*ZKUVtRU zM-!r}k4_~4B))c1qxo6g1qhmmOV@De+PNJ;^;a2w```OyZ9}W4E58-4yk0Wcnd8^#FBkG;dEf=k~(_5&gV2zU$0G-h@cnYa}iOT2fq*V%4uhc)F5bo?>yO7DYg@&I?6k!<0KhNwq`y&-JiChyAQV1_06$H_-4nc?T-Zdx? z>0jv9p^FK23IBsGN!Jb@M1u~7(3T;IZJWB_>i(LU_vtXwCTXC)%Pz})V3^^3=9%aF zW#)P3oss41SAALlSq5#i2pc$!7GR6Ek!GW9q}ga2X*OEMi0dSijn-fVQPujs*)=?< z6sv0)w=P%JDu@TBNVxeDfLQu5IH_99GM0NpG5L9dwJEf+4o(X7bEuz-jw|Z~Yg3r~ zJhqp3mhXt5{uWxwAo0Pdz|gZ=G(L!yGN``=h~3JzdPMyk(s2Py1w=qP_*blg`k)F< zyq04DE031$EfK6vUIQf~6ku(n#kx z;`JXI4mYQ9x^Lrj-y-}mb0{WWKhil4UfPO;x=z1Z{Dj$g3xLym4@r&HtCXcjYyabs zkUVg~`7|_dXCyPZArLvQfSc zvz7(~-<~4LD@bw_Ei3{C>AH!QGiY%IclZT}-9?2}BU;K3e13=qTS#IAiC+S@Ckh66 zwDKJ-e?iX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0+UHZK~zYI?UqYO6mb~9e>3}Xch$C1Hbr`2QWTkKMiF($2WdzLgP_PH z3ws(BbqG5}59}=!qIBtKhYmuL8bn|rnwlnL>e`_PYNhLIU$fi6TwI-5cT=*1zthZo z|Htq9=Hovj5L;LdWHYyvi*qdCWnN@5iXsNRX4W^;s-@sWlGn#jd^rkyytUG4Wt{-Hc0Ps0CjScogoZdM%BiCD-XpIB zKufoI+PNaY=J*(jE~X&}@}(=h^OAu4zl9{HZIJ6wtjn2a{AAE@C->N^zjAbOECFp++%|7?}W+SE=-eg$UJu-|b>vEKp{ z2^O73;>N*hPVK5wtinW+W52LGvY+l=lj4}5Pu95TAJkrZ%;}84Bme{fa#Nc8jtNR+ z|MnK@i^@n?@jaMn02E}_5fL^LoXF~x5u7=+1+{7{Y}7d^Jzl|+f@g$=IOL{GOSs<7 z{k+QHM2rS4f*|0M-0WEUnWoz%9NyXZR{|5+x9tPR(i>!Bvd!Ai`jw6@3lEOgke}8N za4!Txao%&Bl1N!~!t^30qm4`1uUTNQQ(Co=FoOfNIQ57hv1_n9RfOstQw}iz>!=P% z5~jpcXtkCU0y&bp;N)OV&USERYyOmhI=huP%+|3mxN%!^FsH>qI5E7}r1 Q+5i9m07*qoM6N<$f^H>Wg#Z8m diff --git a/Passepartout/App/macOS/Flags.xcassets/tk.imageset/tk@3x.png b/Passepartout/App/macOS/Flags.xcassets/tk.imageset/tk@3x.png deleted file mode 100644 index 635e76abfb5b519ed30baa4453d18afaff0c260d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1039 zcmV+q1n~QbP)X4hijU$ zSm|nF4cBrmU2U$?)v`H@`olGCDsKJ}thokrWH{Trv^Go8{Bib!tu)1KpuoIC4!C=s z{wQuT?wK5$EYp;8K1w1orlYXa}A9EeChl3p2Ufe=^Ezk zx^H>y=~Jjm1b`i_m6-W<35}!@S=On4@;vXa_AxU%7%lG)I`R37Otod&h$Km@sPW=< zskF3TK+}?&auwcgcD{6kMGxJWP&UdonMw3QDlvGo;yy*mX>knLNcs+!f&qcd+O~jO7i51LC2v*(q=G;eAn!!;t@ zsVEiRZdR0wtrbHfvkM+z^SVl^7Z;P6?ig3+ExgJ1JNDC1b2fRU0_<-1O6W~uXe2l| z!pn7wsIOf}VDO(WF3UQbmi<6yV-uwh_E@9VfSK7r_PyF3_c}9%M9J2;N4d==v%s5& zEX#CWyiRdmKl|6WQ}xInR_jg3u8uM`G%bx=Z@6nkA{u8e-N0_sc(e8-Tk1~WQp1*u z6k+|+PJ$tox0;v4e5csRse0F_fw}p89DL&o)>faxu4pNeDMsb|Yb>7E&DGv4219m+ zM(xo$!>N^%KE&(KoJ5v%BAQHK$Pq6ThZ1Hu9Djf%baY}*bWM}c0h=6KN@zsEu0+Pw zM>L5uzvV|CV1`p`cvN8^=t7ot+|CiQ+!HT;ZpUc!c{;A#8M{2qIf_G>I6Ylz7^gi( zxg&PDQw`JDRuMH~I3o8G|DPXp_Y2cTrW(^mrWy)xe6mGY!QWcIAuN~7Y?=T7002ov JPDHLkV1lX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0`y5lK~zYI#nW3zlVKRZ@&9+)oY{rClq|bQmV|V;sA5pk9)dgS3L!*<}!6#d7iu^$x3mgSMi0MTh^z7xY8 zj+S}1k_7{oqH7<$efUek@FN0%qTwo?&&et}kQByZ?`@`faBvChAb zMIit_zhKAdC=XvI08*gp&fJ1bqfu2AAw^cYrEZ^`tI^1*xelA%a6Geb9F-|NNH&)9*>8*x;on1+i7WO$*8~m zB-nZ)O3$xRPe7nU zX5^-Xh??v3!Yl3Dq()x6PuONN4u=Du&xh0LWPE&_+S*$B`}-Ll9!_-?Sm5djE6oSZ z4?&-Rv4=9j#~@AW~M3Oxj4Ju<;ZAg2Ex z@JEn(m&)z)R#p^9O9ogJl0FJO1fv~G2UrkNy)OAzY%G=;y}R^)tdM$_%8lbzBF_{) zNiq_@LWKYT002ov JPDHLkV1iw?gTVj* diff --git a/Passepartout/App/macOS/Flags.xcassets/tl.imageset/tl@3x.png b/Passepartout/App/macOS/Flags.xcassets/tl.imageset/tl@3x.png deleted file mode 100644 index b202cd597a9b52ebd58658f2bfd31cc8e646b032..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1062 zcmV+>1ljwEP)sPx84FlEsUMe`a~7*YFvT4l_wl>Ao8n3 z{na$P5uI0E3KmO#yKDt}A6$M@$6|+rhZ6eX zDOG+9(-{c_0*sE1vb(#Rcf7f|nM5K{(EkOe%+i+{9Yvxh$j#7QnasZ@EBrX7BEevg zcs$O~&=ApR6o;RMy+1}o! zr>BSY^>uc3cJO+==(-g0K}H1 z6A1`RMf&>s*xK4+W@d)Dxw*VHMNvqn3m>{@P4LwXl@H!7y_C-7=}`y>Os`y#NQ9}Wsfr}ZMJXq6Msq2AiS(1HLM{N)XhNv;Y7A07*qoM6N<$g7&-k(*OVf diff --git a/Passepartout/App/macOS/Flags.xcassets/tm.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/tm.imageset/Contents.json deleted file mode 100644 index 23cb8c9b..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/tm.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "tm@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "tm@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/tm.imageset/tm@2x.png b/Passepartout/App/macOS/Flags.xcassets/tm.imageset/tm@2x.png deleted file mode 100644 index 634da5250a1a67e3d5b98ed617837dd0f6a8fa06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 974 zcmV;<12O!GP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1A|FKK~zYIy_UUiTU8jwf9KqDe0_awCyw)B=c8^&qzXtxt3s&+6d~w< zgt~M>LTqg8UHAup0waQf5g{Q~D$2kR5U6~mB1#$}($-0p*oy5qzP7LLCqqOK1M1@5 zXMI1;PmdnGI^x4?SJp0X96i~uX@pWp%OLK#M4>?ELw;Yjc<=cc9;F0)se;Ie`!!AZ zjjQxBH5%OxgDIc+^)>3>81x6mBZc@!((zOvO-*d=^V{Yf@`DLFn@zgKB{G#!dY(FI zR?=W0R|cSD6^08kVtP9AJfc5ta(cNycVve%bBl~+m+9SaW1IeQqYwZLLZ8L_G~Hmp z)yi{215rvMGG(9oPd4h-7BZvhcz0=g9%j)elR9>!Vo!3xPD*QsGElhKq7z!RgVuvK~}5CGB-Wz;^>?3}aM zxKu!-Vs2k7(0)d+uTymT|7OHAG~K}ElRy6_gCZr&Igh(bmLQqsu^=ISl{$l$U4 zCUIKgq>Ynoz;Qlam|DV+k~ddApw>Se9*7}gQOvp6--p~@E@F)y((I3L>c=MIwny8v zPMV#-<;H_whW7Y>Kui@&A!EEaq`0}qcM~?*qdJ2qCS?e8GG_$k1u|;2(rnH4sm_%c z)0#$Sz<#gE@{Y!F)x)<8L9!Rf(9?oWInB5e^3(t>?1gkoCf$r;;LAinR8|M7h|j{W zE~{tLe47f<(;<)Fa+nEC4(voYP#{ScJ*KJ|F0Z;AWh9mtVR~t-l821Vq(CCk-Al23 zCdIvi$!@B{bdY1*^*FZ*;kr|>9t#UGsC;=ip?op{q3iA_)*%?HugMIS>=~93s zp^QX`xY^55c>g7qCN9zFY%{X{F$dQ#Q~hj>2qzH`*>+RDGo5E_Fvj8GI^Ne+4(!*- z_KI-6OskuWBQlO*GHlY+yX3Y%$9yG2PEFAr>0?!QAx?w?1yIPfIe2GsN!8<6{FeNJ8~od5s;07*qoM6N<$f;}V63IG5A diff --git a/Passepartout/App/macOS/Flags.xcassets/tm.imageset/tm@3x.png b/Passepartout/App/macOS/Flags.xcassets/tm.imageset/tm@3x.png deleted file mode 100644 index 7fdb400711cee255d1f869d16c40a597c8910f9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1823 zcmV+)2jKXLP)AVCNS z=!Q^1i$F*qBvwcuBsT0=vMOa&sX`zXR4PH*s+6XpNuW*Ykf1z>Qz!An&dVN;?HSMG zKF?t>sgu;U+D$$4Tb;T3=IH;|`M#s?yH`B_i?8Q~%GK=5v>isFP^^HRh%`GHYHx3D zx%JvW+ntr-8imo~%AU*^Bujq%LCPVudk0!DPH=aOg&Q_X)`L?$9&XJXL^0Sv&C6m$ z5d@$p*$HUH54>j^Rj5AY7Bt(r@w%_ zeVVf`ALFmNIl2oiyz-wiKi{^4sADTmw~vxg49q2)Y;-z_X~yF3hu5ox_R&J#dYxH) zhUbs2r_f?zg&yg0#4lewjg{SlXd7N}lay%_eUyK|-u8ZyhV|Jh{}|e1X%7SxK7iJH zBmAzIXXl`26g~o-s9KYKBomDkr z_<=^M+hgHooXIyb7}f%t&cB1XVHdM!=9oYACMpaOLg8$3sYVad=IF+Q{3Z7<9K%Mc zSzf*QZvcvZg&l3ZblI)ktIe&B5zCAKFq4v?q7k|p^P@J5UVbFdY7ePRnY3^97N5PE zs|$A{ANf#f0iKs9i*4P+R=UH5x$#vqViZboP$6#}>S8<7Y^_W&bgRmIJjS!P{)@~i zPJF2gYs_cYXlcpyyUFRKKfRu$Vev$EGf#K#LKkKJ11QY=)9oc!`;bVoxd@RUx=?IQa|rZHri+H{O?vuL#D zWicwAf%F4zbPDe7?8GiezLiVjK2}7(ROC+wvRpoxVskFW`aGx=Czi&d1wlPkO14fm z9w3FlOy`+SWQcclVi1wALJBSQ*2I%bi@g~zvMCRQV&IO?cv%q)z{P3;5mdNjDm-71 zJR7nltYJ72j^3oj?gcvOz6H2y6VH3ta+Y&_7Lf+RhlB4I=}0&a&@Jl|MX{Wd8>r@{hxV3{1GlwurC& zvy3(o#~EnO6VZy5dJ}l^^A6`b9pvsBskF|>ks-Eg1tveI@kU>kYa3(8K(G)lo!OfK zqb=owfUlk|QAr7Exg^T;dGgFvMt5za_ln08`ZR-AYxNezXwqBD4>VeOT^O_Y?kn>E z>>m}3_parc-%l+D3SE)xcb9D%n*l9hg(zQOChD_8HK7p>b+N(9QW|bU-tS1_*$}T_ z&|$d^l)Ey98|nmA4cFDF{%|`xpFBc5(Zj5F0_&|82%p{0*^@r)$8w~5Yus@Yjn=#@ z8cNl#))&6b-u^zE&UN_L-lll$F0_4-R=>cZAwlgY11wBfILnN>&44jqjsqb=ge)v5 zyvi7bck|fE6f=1XZFm>nWC62v2I-4s-!wJ@27t4{LliFJxIVE<;~4w)Gd*6TtFHs6 zV;inJPBdO1)zO@%0ry{-I_rGOzq!mvUZdhU=yR`;{Kn(VUaT>6i;h8Mcn~ffLxqy;bFY(?ip(LG+H`ZK72G%C&6uS7U4&5BIF>-E=A75zerf?!?wB|mT{8VH?ZXvxOA*!tk*5KQ+P&tj4*;W> zG_KuP6VC!%|IijOQ{o32G7?JDm59Zp@I771j`|;5Q%oYrH&XO+{2!jX-=c|KkaGY4 N002ovPDHLkV1o4|a4i4; diff --git a/Passepartout/App/macOS/Flags.xcassets/tn.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/tn.imageset/Contents.json deleted file mode 100644 index 0488262b..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/tn.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "tn@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "tn@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/tn.imageset/tn@2x.png b/Passepartout/App/macOS/Flags.xcassets/tn.imageset/tn@2x.png deleted file mode 100644 index 8d5ca25a966a00c08d5492e92f2e9c336197aa3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 691 zcmV;k0!;mhP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0%%D@K~zYI?bc056k#04@$WM`v#)OEuFwlam+~bmLPScUkRa?(VI*0r z9mLr1poG!eB_a~)l8^;~2puYMBMVLNB~S?y4LXE|Lbpgl#Wh^potW# zf2U#Q|Nrx0=3$;kOpx9JMhR;g5s=mv@Hg4PU z+&8bT5=2pOR#gBHs;ei`(Sz$s4X(@8LlXCMX^3j1T)%|7;Wl#SHqbPp14B!DRuZC( zjG<3W1K>PYj-jf|H#EkExKl`b)r@zz2g{C30F1d1(c$-|A(o3cOMsleR_Z#!;lNtB zAM4&cw2?99A2cCk8HevA(xwdn2)iAC`Fo8-I=W3V#HdK*JR)pXJpJt``MF>iMA|wD zw{;>E8E1_@_IUv9!zj}QM=<6>W+8!uAOO<(R8k+^$9AmfkI&WaUV=V)?2qgTA z*Mqan952IqumJn1V&q-fvAt$=oN(YJYIkp_@ zn26XE4540Fe{KkJWsHd-#*3OJ5H(SQ0sJfefJjOq8ZJyz5NKE}AR0(%Rd%;C^k=ub zvuC_$ojSG{(ld=K+4pL`oNwOmeRKAl@7qxZ2zCMr|8ppV1O>N&5%s?o;1;q*G7VWH znTD*9OoKL8#GXZ!cvr8&Q&A48O8VL$=Bcxo7cb9AJ=cxotY3%!zOmX|r!&7Uq3d#J$`Gp-!=f_K{-Cel~?(Kog5$NUhnx(0@KzQ)+@_YecW zIo{KjEv4|lJD|vqdeS>`$DLEXK^L!{1Q?$7XYnl3EGmyD1QG`j;7(gw|Y94WOI}< zA|}rR0E&WET?Ih;mn&GKV<`Cnd|R6+JhTr0ttNzLVFkuB&qFHZ;7a!o$Vibhk~(!3 z(rG*PJ-HqLE18@aLH;-!*OKN_C%>ks{wmiaS5EuUKn%gvjzb z(!X9oEeR6r?!0=gpwa{tefY~6RU4T=!Q)B z2@S4|@xC1L_q>U3^Tu1sPkeC%V@oTRaYME=;No9hE1tmE(n@UY29hUEf)F$2rM~ZB zsIdwC@n+Xdz#M)uRm)28)~>`;u>e#RG59;F?$a}#6E~3k_M2(Q8p$+djbs|KMlubW e1zp?*edJH;3_=q;E-N(v0000|gW!U_%O^81FsgaF zIEHw1zP++B>yQIO!$bR|rBirJ^^}}edLO*FaK}y!jz#(pn14w5Dg6*oYVu|;S9h0n z615WP61e5GTFB^#(+c000Xko1aW&2;+fjci_QYLxA-|2s9=Bh1Qta)|dtDmV+&JI; z+p+_pS6;UzOGGRd+?X=SWv$qaw%=V9zWdIk-7%H2DYIIrP}lm$^}gL%-=&AHnN)7p zVKDn_dHtsiL;6{@sGTx=DT`n4(VHHhe0|%-<-rWwUVl9(V6Pzl*Y15^v+c}+`&osP z&KBLvV_xvA=%M4IrI((!zGf;|lBu%lYBN(=<-aw8p-(L)D^BxjZDM%X`=(30J4R1z zp(4Yg)p6$y+MVOoSFe42(0SgjyXmSQQvZI>=QH6HoGf4nCpPl`O7ostYb$mE7={d< Lu6{1-oD!M*a$0l2LCB|)QY_#BTx!Vmy(U#~+g6jBd$4OEf0Mgzyg0RZr{Sis;^ z zG^2Xe?c%-NW}7f3;*OSGtHCIjqxx$y0e;8Js#c-q^U<23Q7^rlagpS4FgUAUaycA* z9{}L?`!L*0zIRlF87*N%s6+nHY=Q&<06?{@)B{}$RmAD_Zjs~PXu+D@Uw63QGwYQU zWd;!XHvSRjuuBo%kRrSxMR-Gs@P-uO4JpDKQiM082yaLc-VjDa12D@v1PguuT5U|i TPmy7500000NkvXXu0mjfHnzHS diff --git a/Passepartout/App/macOS/Flags.xcassets/tr.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/tr.imageset/Contents.json deleted file mode 100644 index a18ec00f..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/tr.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "tr@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "tr@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/tr.imageset/tr@2x.png b/Passepartout/App/macOS/Flags.xcassets/tr.imageset/tr@2x.png deleted file mode 100644 index 77e74c1c111589941161c12c95418eb974350691..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 693 zcmV;m0!safP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0%}P_K~zYI?Uvt5Q(+j#Kku14XPetJDIsUloi;@5hlquAlN5;wX7w+G zg@MqIi^!`Wx(K1VD(Ir13+YCwz(_(ODyfU|2O$z@%%PdvY&*K`yr+xBw2_`UDZTJ} zb$EH6=X~DpbKdj3V$xOI4WgBel%QMLP=ICH0?E`CNT%{~NwWENVz2ZgRF&k!Hxi$Q zz%>6Kh`PU;g6o}V^>yhr844q3=a}!hOZ35`oPn}#jkBeR9WQ#&>g$l^OdCCNDPl!evFFOi=*i{o{n?at12me*^4n1 zM25oa12N2pxOz(kBUv5#$cO+SIY_Pdnzz>9fZKPs}xsjGxptOEIjMW`sGVIU_`9thNcu2;ORJz>&!`P zr6noIXpDuvej+!!QZpwP3A)z=k_jc8PK@apFpS&)mVx9neIYgMu=qEuMVsb6vIUZ< bO#|`^;p@|g0y<8#00000NkvXXu0mjfSK>*F diff --git a/Passepartout/App/macOS/Flags.xcassets/tr.imageset/tr@3x.png b/Passepartout/App/macOS/Flags.xcassets/tr.imageset/tr@3x.png deleted file mode 100644 index 9b15cb086f6503a2b47d5f1524c9a4d730740f16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 973 zcmV;;12X)HP)e>0tFr!956Hx>wJxTL9BSWpw6Fsw^NgK^aW zFN6@{1p*;~kod5Hgo`mGCO+v4_#h+-th$Mci*j4xX+xKV5JG}zyj3GCo!zb5?UdPQUN}<(!_*87UrYxx_<&6c4mGGu=cbkY~DqN>K|^ZPbEP z8?_+S#zPZCsg#f$3ZXsUfp67HJoDxv1%u@74Po8BgK_2@*&8=2Wi=&b|FcVejnJ+g z%-X#jITR{+pV=Q{bnhX|D_6_0E8RyqK|J&4(bRn$b;YuRx;b!-baxMCY!Ex4BL#z~ ztx^0hzJNJ+ow4_il->BhaUDc%sAu+fy(mi-f75^;W$_}iH*dMIo)TBPmbRv&AOyhhmhICD z0pO$!a=*nvk^t~N(T1e?+_aL%RghWRUMm=9&i9eIcu7dZPX2|br5ONqSu2n1?{d>h z9^vg*@@x3opDC!NU9NXKIW}>js)TlKCG+hCa`E5MR=0!ZBlW>iaHhBNg@bq=T~M?P z%z2JuKy$C?+$VX((^$~vW9pvUFAuaJ485`q4f2^S9U;m1z z%!GM(awfehsiVgj-P1+l)s5VH`dQ?LdND0G4U8W@SuhT)>qHft70Ktt(RBdK{sBhc z+L!;#)`zja|3OI4RgiRdPvO(7$3x@CA0jsvZv;~!NvLhDIFm~>bH1-=xr*ZMJYw7E z8(+sx==qYecpIiP#f;n*Am31k~diwP~wSva(V|eDy`KM@EjGz3R z^r;@q*j1dg0g8eejo|NGi`)<<*>w;no2^9udCESIc;>WFclZE-^)G{5Ji$&XHF3!L z^DfB)he?0(X*qT!sn{!1H5$Rct`py?l_(45BL)2A?kC9Jx=p6{EXL_GI7X%tR#Q@Y v_gih$f>ax|Ak{`KNVTCLae1bjs04olp;#DvX;AfH00000NkvXXu0mjfAPCm; diff --git a/Passepartout/App/macOS/Flags.xcassets/tt.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/tt.imageset/Contents.json deleted file mode 100644 index 39c55a5a..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/tt.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "tt@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "tt@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/tt.imageset/tt@2x.png b/Passepartout/App/macOS/Flags.xcassets/tt.imageset/tt@2x.png deleted file mode 100644 index dccefb1d84b332e56a4ebc24a9108319c390f0a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 963 zcmV;!13dhRP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x19(Y9K~zYIwbV~Yn`Z#W@oy5vY-+2Bom#x7I!?irUlDqXH$@?RKl#?QM0(WKy?5$o=*B@n5tb zU%qTmoKBuRdW0lN6rZuNF{b@~j4LbX4j(S*|JTt1q=P~B&zhDRxg0}7LoB{|gK>EoZB30h z2*4i?AEKR{WMyTA*4EY%%;4Z4fk=b~zaLFil{g5%@AvLee)~3|P>7b6mV%beW*Hb5 zAh@@OF%UpgSt$+zki2`Biu?DGWtryY=7N^VWa#hjXIW8bSXx4(*NcMyD3g;Mn4c#c z4r4NzN-$2RlUF(&hnAMmR8)wA03^o8sd@YuS(a%m+L3fRjl<#K2fdzpuNTV7#6bW) zT))m2!62)vs~C%RB$Z0h+uO^J_4OR`dO@cX2SHVNf9VoU;V{8q5QD)`koWiZaX1|O zWHMoxodxZ`|N6f{{>HdmeEIq{vMf_uTU(Hm$s{(Lji1}vFg$nw8jT`hI0 zyJ$?b6D56p leI4t`ljx?W(H}b&{R`CYg$Ct@bp`+c002ovPDHLkV1lIz$uIx_ diff --git a/Passepartout/App/macOS/Flags.xcassets/tt.imageset/tt@3x.png b/Passepartout/App/macOS/Flags.xcassets/tt.imageset/tt@3x.png deleted file mode 100644 index 2715925380b098ddc6610c5c3ca71c442dc1bcf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1707 zcmV;c22}ZpP)mMl3jhqxujG-&2vY->TK48c)K z&tEJjM|n{`>h)LK-0%C`PoH1=yW3YXVK)C;u2zqoJ$p7>uh&N)V8ezD6AcXwcYUU( z8QHt{mFdAjnMm(VBm=8fc@*8<_$4HepP!FTrxPy7&CR8up@ErSf5pCS8`JmhdB^EI zNN1c*>}oZxzCO;MKTlRxmT*C1V58@LYjlychCqj5ddIjn11NZPSc9n{m{(kg&J?ZJ`!i74Wj-sL> z{9geB@y1)i6kIkS!0Y zZQ3NkUn#%1@TqCm=VMkV}_9B3Z28IeG54>9NJa@Xnn?Dil~O7S;%|*38Td`}gms zxw)Bu_unV@^UoJ$R>XqlJ=xX8$b0XxJ}Qcyo*p6uSXoY+=;d}hO@HR5FL%#Y$imIwcKtuIXOABx3?4Y@yEPeR<_WxbF!#U;6!UH_w(}j zXIvcJ-Q5HS2hXW>xm;vsW@0oNS#{zBt4d23Q~C)O{Z%sF*vS3-d^X0%V=|ct6lATb zsVTHtEhdwRm&?lu`t;Mqmj9b2`A!`F;tL)W6_J>jNLN=E0fMYG+lV^wE|<@#ot&H`BO`;}>(>dct0N#kf6?`q^e#1a`ZUhZ zK0~Ea(ca#UOb{F=CMHNvPv^#W-w|@|94oW47gc-7Z?~f-PT;JlK&@8O*4Bo5(a>Hn8DPvVtq@|@{Fc|O^WUaBWF*F(tH~absX=+0L z&N~aLxoiRGVMz&po;$~`UAr(C4EXr?%&B!cov77n`hNO}u+~=OJ9a#Gz2!@C9uE&b z{E+cWm)N~~H#(gTsUSFxj*gO+md1bj`w44pMW$3fSGnblq#h6VKlp%01_S%{?c?Ib zi%0~)ad>zbl}g3!pMNI2qXXI2t({TRx3`xFKR@ir$+&LceyYxE6Pd*`If)}ZoyRwBaO%`4jvP56)Ze*t z2c=Sp!|g_4vEUmW{ZyUTCo+pPC%90zMm`SJ z>-!&jJd*6}>{o_{hJ=3}CnY60&zw2)%cR8;^j{q&N?aXud?)|_002ovPDHLkV1jS# BIj8^t diff --git a/Passepartout/App/macOS/Flags.xcassets/tv.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/tv.imageset/Contents.json deleted file mode 100644 index a25b8ffe..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/tv.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "tv@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "tv@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/tv.imageset/tv@2x.png b/Passepartout/App/macOS/Flags.xcassets/tv.imageset/tv@2x.png deleted file mode 100644 index 20c6a43d5da7f886011acaa8569786e3ebabf3b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1345 zcmV-H1-|-;P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1olZpK~zYIwUv8J)nypRKflX4NA4F~xY8WhmQq6vcjIiPJlY3`c5hi+I>M)N-Xs!Q;3nHn~+%S(?g* z4GU1I&v>7&22BO zOR?RE{TK;lzr3hVNufinMsjw>|K!O&b7qT`Hbb|quh3eDgh=qsVcWFhl)Y!GN;MWSn-^YkX(@IRtL0%6(NZ!mCe1AAbq*TMXX(eQA4Z_{s;i=BYsdR{f zg|W(3WWmgEn8dsHUV16?pOn6k<0Ko`s}LInCvVvp8D&4l4>i zWr-0=R2rZosJTSRXrJo3lVvfX5&LP6H zh{Ls!obHzk{*H|-^}0&vxDqn&zQ;}7RD$v{aaXk2){M%Hc&J(k8CyZPXA$A!i*b-U z`pnm~dlKPU$YQT+sP*1F?sTHtBGF`WwXG3IJ=;#ec>B=+O7zX(kGXw30IIrGY?I!)>YScu&Zn}pWI5uXjTFAKp;Gm%@O5Dw?*Rg{kde` zeIG-wQ-E*O%loW*R)`UU0GzlN_P;U90F`t|SrPsLj4k0D`noB}00000NkvXXu0mjf Dx;KqG diff --git a/Passepartout/App/macOS/Flags.xcassets/tv.imageset/tv@3x.png b/Passepartout/App/macOS/Flags.xcassets/tv.imageset/tv@3x.png deleted file mode 100644 index 6eb443af54bfeb1f50b8671716a776e975e337c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2141 zcmV-j2%`6iP)37jm=415wt~nY=W&ZsS>f8mQU!C$fk| z^!wNR?tAy%_xb(ickk~#AuVQkweZ2>8qwKV%Dj~?-}mQ}+Oncbfo8NmKD5fPbF)Uc zl%{C3iIGt?r={K;+I>dTq?3R!dc0qGM1QHmnv~!y(`dwhPYSP!Hi`P?^0ZY7LvFUV zVpxE-Yg80NVq%z@G><)d&ah#_F^Y;RpK=)Bpmwe`-NStDjLqg5Hb;3`?8fDDnNQt6 zetbVR%<7HvKX)6dw)|jtZP6mSty)FGv3q(v+6x0@;RQo6uKO!B>FM0`@St?{au&@T zfYG>~jT@)o=-Bimx2Eyq`;nF~ksX?oC}ZZL9WVg@>}*Vv=QB0*09*f*(tZ?x?A$pc z)1!_Onj3_1eG*Ud^SSBc!=n#Z@cXa;jK=k>T=^DSt@7Cc4*Zr~&gW4?huQ;#9c09a zPx+K2wAQMFifb-{#OppGKu)HAhL3EEs`YE^9JS zKtK;J{%u+VK;#URZf*?B%)~T#KEIj1?}Y(0O1O0CzVu6@*=!*&us53gZ0gI)kwg)P z;9w9MYJf2$1oJoF*zR}VwF{R83#h$+A0!Ed!GPS!iK?nv($fn75Jidda*NF;@p>)? z^-k>vayu~aDUjAwYcx`CHrvK3cXt#nE^UgjJ7@;mhL3G)>Sm>=zI^%r4pNt$OXH&Q=T-fG76)7opE8sA4BpN@zHpNis`?NW) zr>+1&kXYiIOhJvFYvtY)*L0!Mszs3!I0pcYMrkXtdcBUZWBVcq0!Wf=6DwU^k?C|8 zLqhC8456X8%%5)?TW;LoNns&!oetTcK#r%}!fLgjK~qL?n?ZVLi`h496W+Q!z7Af? z7se1)TyIziMPy`>?M9tWO;prqR;`+VAP7{XrjomA71rWn^ea|#sK$$#vxYU9`*CPcP z5=*arfK`+O|J@@>rJRKeM{w_6EE_jYLviaW8Dqv^nmrq*=`*+!cZd;Na#*@78H=T^ z$t2m?rOcikkFW2??Af12mwEH?&&t9jA_CRAWo#%+;`Gi5j2h+JricHfN^70){MSL| zUtUUpQwQCNJIq76hnS$}{x6zFp$?Z+aj7faV%y$zpnM$51QC3z7 zk&#LG`F%p-flD}rN8q28g&`~q=h(&UQ6A^eXA|ksv*q)mufs!v4Yv`HnB$&m+uWPd zXHsodb0qh5t~~Pj8D!tSxwuy5VVpXZjB(@WGYI_g>3RuKYPrA)!`38A ziu^nzrHa~K?mWmUqvYYEW*z#!-iOYm`N*p(Y_>YL9uz$mcyK$vSwC%O4W5H~q1Gxv z6e%=Uk>}|Kz+8_Mt~~NVL|~ZyE(xZIgu0*Liy@!msHo;IQ8 z?X6(LZBC6^iUf@R;Ztr^co3vd$HyU;*nx@6`tc*`>Xn=+@qeljHcHW?wFBj;Dg=Mj;c~nlT-VRYiV{ z0RV-p-d18;(o0{F#s}VOnut}&RRFhz3j?qOIqst6$)9so$aNs z$g@g7;tiLZsO5G1&ie>8^(t;vxO2_yO>u3PS7$|9lZ1fziH^+yyXe)ah&cav`a0xL zQrqxy)?pwrK}0FD+Quqb%MGzn|9cF%QSQZ=5`S)2xMQhTvG@KYTRHA95K)p5AGDca z`n&8HvJFpNL90GK4!Nuwm|$nSV)h~6`*Bo%cppn@ z_Sn%~Tf(-%+X-?`r>C}r>*by#nkKN_7}9J*?2r|a3nG8$cYsj$69BlVD>xjy23Jkf zN1;563-c~6r?bL>QfB33(Gb2gPHh$fItZj*lCj~=ySNB^}ZKyt-E027TfFS1#(#rgBQa+~qi4MT- zcXR5n%`VU|UyTrrV*oE*9%siyb87Tb`Z+!TuzT>8dspl*FB<@*W#uvL$9jj#K`6RD)mdO$jP-B2z5_o>!9s)Ry2GzO)yO) z=BGteSUUkQx%-dks0QHt zxWz~-pSXT2qf z?Y>9cG^HHM8AG?DU-6UKn@);ayX!9i0(b@95Zg(j0)f;HNaP080i`b_x`gx}NkSsG T1U%-700000NkvXXu0mjfG=)0H diff --git a/Passepartout/App/macOS/Flags.xcassets/tw.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/tw.imageset/Contents.json deleted file mode 100644 index 6cbb7236..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/tw.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "tw@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "tw@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/tw.imageset/tw@2x.png b/Passepartout/App/macOS/Flags.xcassets/tw.imageset/tw@2x.png deleted file mode 100644 index e67b370ac95b6ce5a5bfd3292f0f4888cb2bfc46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 528 zcmV+r0`L8aP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0mVr~K~zYI?bXjqnqdII@vlFIrEP}Qp^<@;hDf=R9Xbd*1s+7(!9So& zhlCb_r}7Z&9}rL7z0^Eg9RiJY*n|-G1J-+}ToTokT5eRn=?-Iqy*Uec?DT#wFFX(L z!-t3G;nBR{)z=J92N2LlUgPAjm0#azo6Tx8zrUlSy*I{0*Ay~4>%wa7A`%(H~KxUayrz;tLi_2a}UeC=|}I+s(pvrXl3|y3Xe2OG>2+W@bi6 zrF^Wd`B+~skW6}+m>8g57Yorh8oauyvACEe7_|RA9|nVtSj>apzsk|kPq7Lb?1)~k zWqR6)$Mcw>A@hy7{(d9j@H5=*zV-@9Npw{C&c3)*xx^AYwM7?xwo92RtW@bXtiQbYr`|LyZfDJ^gRm;&nXsvkVx#1 z%^nkpj1dT=D3vaRBK`~c)AF)}B%Kio4UGu`CWcZ& SWV@~a0000)u!g0ffj2^?S@vs_jI2%8*|ZDE0^M<~9d|DF zT)pr;@5}Q!U*7Y+M{|_+FKi(<=$$&w%=c2SD~#f>mwHh-vgjI>SXW!jBvI7i@zj*s z<-aI6$eA;BSgnS&aVX>lK%;@y)_P^^{(*vnh@y^oJV0Gt70t~qf1i7MKuSJrU0T>-kqS0t@I(KmC(i8N0EqnKxsjW40@7~PX9J5(ZDkUjvy#ca8 zv|0^)eLkwH^xV8T$?4PmbaZ&Qf8U3}pkrw0Bkk?asIE2=iJZmZ*iInui0SEXTjl@9 zRj!!HeVB(wLK+~SW-l?5b}1!XcXw|^pH$m#P4^|*qHxwd+gXQY&Ma% zZ!Zvw`Ej{+C?YFI-i|CSWw~y)${AyLnj-=CB#>VE6q-7c!stJb=AxSwF7k_f#fQ{kd&$P7Eubu3P zM4H*zl3#2KqTnlv(`mu$eZb)02PP-KlFjCroBM&+8!4Oo-)wr0{O0!USFEh8j-%0c z2*T<+$YgSKb&V?`_b(LOk>4QzQYnc$ccwP1m5ZYOM?f9&f1_Cjl_?cR(H&76l^|-P z5=3oOf~bv35VcVWqBbf))J7$U+NcCk8v-y;Zi!Ox3xKWJwM~4VU;qFB07*qoM6N<$ Ef>;ig*#H0l diff --git a/Passepartout/App/macOS/Flags.xcassets/tz.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/tz.imageset/Contents.json deleted file mode 100644 index d903bbe9..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/tz.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "tz@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "tz@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/tz.imageset/tz@2x.png b/Passepartout/App/macOS/Flags.xcassets/tz.imageset/tz@2x.png deleted file mode 100644 index 39a1615e0a2103d0ea0d8a309459787f1017238d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 695 zcmV;o0!aOdP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0&Gb{K~zYIz18na6LB2C@%P=ff`SzxL}H<5y7hw)(P`@DeAuJP|KLX4 zvz+2BMt&%@)s z@aDzmz9(=m{0-hTP{WbDFxC$Jt)S~5%aF?zz0g+oQ>epsElImP7!o zA6|3UD#B8scz!jkIxZprO0x}xl5td|-g&NmtVl%if);?VRK7w}lc6v$0QzD(5&Z$#CEt8mA_AbaLh2H99kc&M z00;so3Zzod-RnV(-zGFZOLoZzl(DQ%DNHZ6A?pvf0?e@#5dg&psmt36p-?3XZq%6= z!STed2(S>5yr6YMxY1CEL?D~B4uBGcuji0w9_<-m&YmIyp!hcxj&3X1iP)tueT(3W zxqn8$9wLq@ESyD-Keh}oXG_E}g&sGmev{xt(sl%Fib!72dLVqFT7@&n@h4>km?K{C ze?O2Ogg_k(4;%M_od{8cp&`g*426-YJM>MYDvAJbUtqzTOjMyTe+qf_X~hBNhI7hBWuME d{-;9m`~_jR4QS$A!+`(*002ovPDHLkV1j}uIy3+P diff --git a/Passepartout/App/macOS/Flags.xcassets/tz.imageset/tz@3x.png b/Passepartout/App/macOS/Flags.xcassets/tz.imageset/tz@3x.png deleted file mode 100644 index a81ce85c6ded3cc843eb48a91df3122bd778991c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 885 zcmV-*1B(2KP)GK~z|U&Dq;a6k#03@$am&*=`1%8L|`6Dp96I1n%n60lF&aKZu$w z0uh2us4MSOh|U?LG~2n`4%g+j+rf%%Dk)G@f`o|D!Y(>E)5WXxhO6#j_T7E%elYX$ zK73||*;$Q?TRu~Snd=?XT58_=*4iVp_-Sjlt#R&#ckJE+-AQ%44$l{;-sDWFGr+2a zz+y4?_k(FdJpNms>Ku=%&7~8+4sDy@;Vx0aaiF6Es;eO$pZ?q_8>-IK3hA9+a1p-_ zZJXiY?#vbB6jY?n04oTAMPeQp5DHygnXgq4kuoowuZ6^JQNp(2%o$j}KD!c70FgQa ztPlhiia8erIm$%Jyl_4^uCUM@g^7g2ibS?9g-an=JzvayeXxH&B$K(VJ7*5Gwn9Tg zo{RW&Xx<1-HQ>?26REQkqEQ$f&28;q5MnW?s1S4Bil|WVc))f%wD&Z)U&N{$C#fG zu#!l^;=$1N|BlPKNXUR#Jp{(@1(gbNvM!=j;p<8w$;k?GvMN$63UV?kqE=xAkz{OI z1vzOK2^nD3Lq!oO$eFQ-N`=9%%Q2Ie(^il(ZINPEkdv~AT7_kp$tzhY$eC0mWPlZh zig{wruR?@q_sOgk9u73AokSmAAi=reZ|L=>>k4x8yefPt z$9!@a2O|%$ZZk7A6i&9vn6> zAK!5GiIh&V{v71@!|BuFH+W^GP-uU3kgD6g%1c0&ukn)`zH54>`<|{ zjH0nB^`D`8b`E|gW!U_%O?Xxl097< zLp(Z@x2%6tS@U7yKAyu2OzbMezkXZcvT*`$3rfr#Exdb&le?sxyaJw)Nnps$ILOboV_~DRP*X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0m?~4K~zYI?Uq4ER8bU$zw3Kb^I}A$#XPZMB8nD5=)$quR3Oq~Dh#wm z%UUR-7;f9xHW5K^rhS`fBc!--aIoIYXta!4Mbr#UCSlWz=%laXqD5c}nM?HE!XNJH z^8b97bGYXKqyX{wq#E#v4ng+IeD{Ji5#D$Sp%iQ8`Ll$9b}vDIx4tQjZ$QlTM4)5@ zB(G!MvLZnva=955{N;W~|FE_`ZtLW@JE~+>7mZfnV!dW{J5Jl+BN7uM^z~dJ+&f8e z<{```bXW>nrExFCh&*`Au7I=hiI(;l0qYp2$0l*3*L5QGdZg*rJph(6OIWr|?Pu-J zUEh5z5Oa3jEjF7h*JFyco#_USHijC>XY;JCEMvdUaA9N|PMq7$Y`ClS-2WSTi6_4s(s3L_gn@xO*f;KCUA;*%nbe5{ zG}nOP_^@nNHYMleB)yoHO1UCKgG1VDfQoi9^=*SLofm}dQ^9ETT9aWY$>!4KuU`ta zgI#~?cnhzTlt=;nIsyMs&wTU6`v%0^`P@)Nq~IxqQdP4UKN12?dI|y|-<0MMWWV+R Xn>y{XTpHDC00000NkvXXu0mjfu2%XO diff --git a/Passepartout/App/macOS/Flags.xcassets/ug.imageset/ug@3x.png b/Passepartout/App/macOS/Flags.xcassets/ug.imageset/ug@3x.png deleted file mode 100644 index bbed241397f63ea810e0020fcd044bfd80066668..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 691 zcmV;k0!;mhP)C=RACf{pV#gU9B6eSmK z62wJC;2)4KT4`ZAX;X{b6kaSBK|+3D3XOzi1Ts#+)TTlmXVEl?+Bo;L$OfeCE6!Zz z2M5lD!~MMvobz(eRlpRW$Z#pZ!UiBYhkXED%7UbuYy)DnHuCbY51~U!Rj#|vsutDD59$2b;zt_1>`K*? zv~*xx?bT@uXa?DKdns;e2S6!BT!b=r74_cweRU-*9c)$B>9p1hl6SEsm2Yfp5cseJ zM{=Lm%iVjm6>RA==kJCn zI%4kCoJdkq(bL@{rRAm4cBM_muC>Wh-6{D#@l+%!GCMP?#q(ciZLLr!wC}n5W;X+O z1{gQT3FZ~B<8blo#S;L6D?y#sdS_&GbTpN(t*_C4x1aFmFbxe2oU8SaTU3U#`aB|n zX_`8%0gbH%i2uG{Bq_4^W>FRb3lfjTW#rL_Boj$lo_H?1+rPy(=+oL(d!ZM^<#NgJ z(6C5SBpQiIUvHmuc6Lf+JtEewCB8wQIGs-IAP4q4$m8+Qdbt(LvZyGpAoMYWX_^EU z0=lgLP-72yhaEl|v#V~JDGQQrvJHq4z2}%mW&W@q$f|o7vQ2AcLDEgO0Wna_Aelex Ze*nN>Ud5G}U2p&Z002ovPDHLkV1m4sH--QJ diff --git a/Passepartout/App/macOS/Flags.xcassets/um.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/um.imageset/Contents.json deleted file mode 100644 index 8771cd57..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/um.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "um@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "um@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/um.imageset/um@2x.png b/Passepartout/App/macOS/Flags.xcassets/um.imageset/um@2x.png deleted file mode 100644 index e286037a871da3d55e7bbb78bbbe47e5e9b277cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1229 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/un.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/un.imageset/Contents.json deleted file mode 100644 index 39ed99a8..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/un.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "un@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "un@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/un.imageset/un@2x.png b/Passepartout/App/macOS/Flags.xcassets/un.imageset/un@2x.png deleted file mode 100644 index c4697d141c5c69306e78d46e37e374fb4d267e49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 751 zcmVX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0;EYqK~zYI?bc08R8btq@$WhJd1mgYIiq1Jj%5}IRuI*u1Xs~2=tcBm zT11Q9M4NU&5L85~Kr13_j7m$L86TRtGk4}bo)$%g1YOCfg}?nd|Ic~)bHtTf)jPm7 z{%!8ae+>AZ1IX?iKz2vp>i|<1M6se^YvfFcr3>WW((qz7+c8P1h!cflN`wF@1f4M9;gZMMi98K2 zrq+s?KUvHS$#gUYOJW;>h8NQf({BL5)zf`!cM?YW4f-98g_SN(-?s6?l!g~mDVw;q zhSy7eTO-qsR7w`h^?+0Vl3!R47gjt!aId{Aep-s9A0j%GLENwv*JD`uyY@M$OF{A8ZB zX0(40O$uJEbvahH@OmkIj>ha*j;5b*ZX%CTUtdnq(a;50hG1&g;`MqDU5ZSQOn0Q# zifH-?mC{$p?S~CU`VHDa%8O4vc}p@;av8{J)LN3!0fS1(VxtvNYwhEX04#p=0hm5) zVH=Watw)?H)^;N9E`H|5{Tk0c_}GSIYS=~ySX}Y5PM;l8tpz-O<54Q;%uN@llx<8+ z5T_7CilvPnlVyv!=>la}=g}LFYCXsd*;8kI{;`V`g1Kpz`4cYN9f$W@Ap#g5G#Sk6 zbb^$7FI#)+s(Xe2EUfx0Hv)u!O3D0Q@YGn2cbfsJB2(Y^V+i~x^z*70SF_)TU**5~ h1{^?k=K!)hUjPc93j$J#22TJ0002ovPDHLkV1n1{SD^p^ diff --git a/Passepartout/App/macOS/Flags.xcassets/un.imageset/un@3x.png b/Passepartout/App/macOS/Flags.xcassets/un.imageset/un@3x.png deleted file mode 100644 index ce81e498b070e955c825b7aad2a10bde497289cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1396 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 diff --git a/Passepartout/App/macOS/Flags.xcassets/uy.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/uy.imageset/Contents.json deleted file mode 100644 index 126bbc73..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/uy.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "uy@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "uy@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/uy.imageset/uy@2x.png b/Passepartout/App/macOS/Flags.xcassets/uy.imageset/uy@2x.png deleted file mode 100644 index ef2c2eadf06616aba16b86a08ff8640e90f5efbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 730 zcmV<00ww*4P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0*^^VK~zYI&6Z1O6Hyd~|C^blu}PEWHF+4ONHMLCK1_>rp*9N@L=*)@ zP!PAeaU)&0=+=$y#HAZSkQBP8RMb)`vSYW_W{2e}oHx&`_ zd6|B>G@>YivAYouPe4l-kqF%bRa!uYhQo^|7hrcdu@PTWB=B~~8Hs%Ph_%2Y*g8uP zivg&s9QJ4h6v}y+_1*ZoJPxMk4je8I0N}>ieMn@=ibzYkeZ`}Q#2)zm!yqDwEkz_)N4tmo{h$egfSHM#uvko}W~V`j9)eJU+H?Z8uJhRWynO-HAF8#_k3H#A9Lf^`AyMD`8;t z8rDKfI6OF9w{hzJaqv}95eY)b3Tf19YPA|Dic%!lKk(Dv;#*otl9~~0M+hNW;=45) zoeQMgtrYQjnP|iA+f3-;UK3~QY-?#oSVue8@US+K#vh|*LO*!A%KqG}v{b@o(y;XN zg}7@jkZP%j&&#-WO=-x=DmPsdai*0LlnT@IpvR<4FaJNbc1>x11Dq!8T(l&#od5s; M07*qoM6N<$g5#7&P5=M^ diff --git a/Passepartout/App/macOS/Flags.xcassets/uy.imageset/uy@3x.png b/Passepartout/App/macOS/Flags.xcassets/uy.imageset/uy@3x.png deleted file mode 100644 index 886bdb3ad55111916aa6af89d679f3055999837a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1026 zcmV+d1pWJoP)MlM_cC zs{t4p>gUgvrn$Ac3B>gdHhgo4+p?>WWgVTCTDa$NPFFwAqc0qv^_uPh4;7!b#$5E@ zhzbb?gPf^7%&xu9vhj{w&euN3nxc0})3wsu^$W(-Ze&@Dcict&cPA*n_vvZZU$sq8 zb*dw70h2@xEg=MXdF!aD+Cl0qMaY3)>A3g~Ym6gUo8CaDH*mT0XFjehr(knQ+=XXj zTF77REdb=MD`92MX0HDJB+dyfI@;0eBKXJ5q%AYj+3*0{$`9h3aPaxjm$}qrRz&_< zroDd64JT=DJVM4Y56P+7sKY%3-4>*zB(&-fW8*SvX@Wq~N@V>CMn^7_zvBePtPN8) zRBDbyeMfvgFIGzpw)P4}$HrNzchNT(q-?8(GYvkB%Qg7ID^M@WVg1%ONzX9FU3w;_ zLfmdQUw!c&euo+3;(nIsuHg3v*_0<}>j`1X5wuu?h$PTzBnC&-c!HVq*)vJc+DX~& zr_g9L%7;wi@p$OzxPY_wBLd!jG8Q${I}oO&D@aL!2Cq*-C53THg)ConfYqj*sMX2^ zOcE91813iMxA!AL-Gssr4*1B)5)9kJ7#6FzG87`BD?svD2$4cSyPe|l3beXp0Gyrx zzqQ$w4f(0QmagWbY}{0gT5n{m^(9i%O>}p(kgW6LC5vRW1C4-;yN(eEYYF&0*lc!+ zOZEU@{=-J$Gj+;_OyYJ9bLwy@rh-SXSwBOs*RWLEg*}*$P)U$dOlxx;55M?*)UZ-2 zWX6B3W1qZ5>Hcc6O^+c<188r31?xZxnd@J}?Pw!w?LG>&y~^pD%6R+E%8b7#PaJp~ zAp|a$i-GZc_7~pAhvz(Am9noIL8vpX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0fR|IK~zYI?bOdJ!*LwP@#p93Bh6|iN!iqdHcX;W^P@;PI7m5i_7^zu z4{&o4Qm(X%12^Tcl)1V1aT&E`mN36-efRx17}a40K)bsvBgo@8nEY@uFw{4zhKTV$4^Qhmo3w5U>$T0uZ^n zBlh$Pz-F!s$C6}~d5*t;>|NflewJW9x2#>&ZsxoFCpLJ;EQ-#m>CF~^3OV?)e&16-i;AM>2eA^-pY07*qoM6N<$ Ef>TA-1poj5 diff --git a/Passepartout/App/macOS/Flags.xcassets/uz.imageset/uz@3x.png b/Passepartout/App/macOS/Flags.xcassets/uz.imageset/uz@3x.png deleted file mode 100644 index 0c6f0fad1c9cd73be9a3dfb86391459a9afd3cc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 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 Z`T#;!WU207*eL)2002ovPDHLkV1k4G)SmzV diff --git a/Passepartout/App/macOS/Flags.xcassets/vc.imageset/vc@3x.png b/Passepartout/App/macOS/Flags.xcassets/vc.imageset/vc@3x.png deleted file mode 100644 index 0317c7e9c65b8082186fb6fe08fadf1da7035c96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 745 zcmVj6a~?qMJjkG z>OmB$(nDK(VJU*3*n^5EJ&54Ji$@U@4BE8XYOxrpmC{=K3eCfk#>2EEF-=&TnUceP z5ZIahlYhS5o%wgO27l|#X*AN{2}kZ8@A;-Rz8Knq)DFyI*BWmQjntHD4R+Y($}zBY zbwm3<#$qrqr9IxLZYXPuE>ddfBBh2dQflZTE0IjWM-4@+ZwgW}6C+vV;SijA1Bqn* zn3)8$yoTrBWoDKr@?#$Ee1egAcrcVd?pg>Y9ZpV_)-IpB`O+x4U?7l}s5RS+gfdr9>7T9GnGn(M&3DcvqJ!$rT?einw{1lTNazCQbVC$@uE^J86Mq==Y}(6Kvx zWLtKCdjtH6!Hp1H?G?qcsRja-BF0e$XMHlV3#8SNJx)060gnw%c|~962blc>gER0n zEJWLP3Gvnjuv=th2eZ7Ng3EsRItS}b@N!gqmbujrhuq>)x*mX|)iN_nzD23D!rslI zVqfXa-Z!0q+XG_zShbu9o{5sX-}_%+e1*#Xcv#dO6(|vDs)lu@-2Q+wcL|n-5|Ju9 zoUF_4vlyYhQAIX^vIna(KCuKZ(_9DJHmb^|P{eA2OG`yDU-g%X)aCbI4!B{z3yxHY z%B~WnA|PspL(v4!M7fA_4J1JS7p3&jMM@1_q|`9`J>eOx!CNYRM@X?IFGF2cYJC;j bUxxk#+Zdadhg8$d00000NkvXXu0mjfF5XyW diff --git a/Passepartout/App/macOS/Flags.xcassets/ve.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/ve.imageset/Contents.json deleted file mode 100644 index 1ccf5bc3..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/ve.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ve@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ve@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/ve.imageset/ve@2x.png b/Passepartout/App/macOS/Flags.xcassets/ve.imageset/ve@2x.png deleted file mode 100644 index 18053a4a22cdd8d887f25b4d5854eb98554c9aa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 466 zcmV;@0WJQCP)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0f$LMK~zYI?UcVu0$~`(zjxkU{)|9qkSOyHXigY2yD2|`hMT%`8>}HFO}Re z^HcUG4SGoO(%hhXDex0TM2aFJMG=ugto7PQ3!+TvIu1&lVzsHKgX#lBg z5#z211flgSBM2?nT@j>iiYO$4@Vl+VB19XZX&SM!N93QYsPWyWtnmUo!9zS0Dxhf^ zq9}InK_YUr>qTgN2JwqLNgMzWKQAD(HjU%xGEtsrBkAl5N)?`1;*0xy8I@{-C`);V z+?iLEc1(7cKg86oXP7|aHU|nl6#=N+zxm=75h;p@6h%bJC(K53ow6He=Kufz07*qo IM6N<$f(U!Wh5!Hn diff --git a/Passepartout/App/macOS/Flags.xcassets/ve.imageset/ve@3x.png b/Passepartout/App/macOS/Flags.xcassets/ve.imageset/ve@3x.png deleted file mode 100644 index aae4602fe38fb85ed51abe1628cd772e33d84acf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 623 zcmV-#0+9WQP)o06?q0kj z+&4Xr_ul{a@&0$blV&qrIxT?IDABMxga~B21Bk*EaT>0O({M$chAZMUT1G^fu5{a- zK?o9uuQ%ncCCf7Pdi|fzM4>cgS77);AD`E2yexeLBx1)8vA<&%cPCyorJk@8iG~ld z@L`>fc0Y9*%;hfA)~4VOjREj&{svp$4Z_+zWF)?9ekB;_PuHJge^6Ca&pNHzqH3s^_qRLxuEmiek7!4+}Z#fz1 zrYEq6cZ+MJAD4cc_pI>#*EksHWPWjthuJc+EaUM==(=Gkho#mUPQ-|x>80;{8i4lQ zUN&o6J6gMaZYg_-Om2=l4~k}^SZl3Na4#o%4=^+s1=z8~_PIlYQH~w$BGj|bN)&4+ zqN*OWQ$Z&46-yz`ojyYD`GTePGJPV+cm!QHEK2}P<}2vBK{6gOy@x50)R;zkTw^2= zGhO&kMiMbn;~J@(nyK}szFLLZ75x4-rix3ZivEqM;u4uBWeRUrOs#L)73F`U+^@Z` zJA)9E$d9Vs8HCU>|0Ax5({M$chAZMUToI?yG9n5vZFdL}`~;qrpZ#no;NbuO002ov JPDHLkV1g%)8Xo`v diff --git a/Passepartout/App/macOS/Flags.xcassets/vg.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/vg.imageset/Contents.json deleted file mode 100644 index 257af5ff..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/vg.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "vg@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "vg@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/vg.imageset/vg@2x.png b/Passepartout/App/macOS/Flags.xcassets/vg.imageset/vg@2x.png deleted file mode 100644 index 0fc071170ef03b2a43c2fa2cd2a5194cec996a82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1435 zcmV;M1!Ve(P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1yD&uK~zYIwU%pej8_=PfA9P5zMI{5ZL-;HvJpgzX)a2MsG=?{b(_Hq z5k(b)s!D~r)u>UUozZDci{6-as)`ZU%uqvB1fzqg`!%;(#pGg>-6Xr&?C!g7KPX8? zeTn#gd(NEaoZoq#|D1D#KiR>yJ@i9|XW=Q@FY~j(f|N(e9_8 zam+Z)Bbm(xKAPB>@xei;*zY&Nj^-IuxWw#HP8lZ4fua4>N=pM86#g%A%Vx!ox_d1opu`)lKA-_D9--)06R z$t>8EgJh6N*Huz`^eC1wW3k#TtWUj1hQ*70(IWP`9gLf@l^r|IQCsVO1`Oevuy567 zU$4XnC^B%G{Ty7~y zu5hyNkdX28QRx|u1Hiq3;}}z5YcYt%YgQJf9zCAA)D@=$ z1w}0c0df3BXJzWzGBP$+^MgMXdGd4sd_^T3*Ag)g90-7U_z3c%UZu^{X@tFAYD%2^ zQf{Z`*Dg}mmht`;XNw^KL_rXkJ^M8-{<)C%qDpZsUcjLWGyQ*yV%`^LA5YXYm@{V^ zseQg;cg!H-mn>$Ibc6h%4_G*%OEbT;M7m!$Wm$XQE}(kJUXI!Ourj|b<>e1eXR|3! zB;2`E#r*j@S+~x{^5vtMJhwg5HmoOUtiqHXZgRd$q_EnFLLHSM35_s%HHZNCRe^9Q zj3W7|7ff`l?#8Pg zp-~Z(!O)~NPlITwG)<#ZbcnDfkY*21Qs-dFtz zk##bvrc&m0GdRvmutB4^s`=Kf4ieVFoGUltZjgAxk;1<6Qq~yF=-eK1?_MX(WJVG- zT&f_n9u%{@9BC8KQqH8ostD9BR;LUb*e30%zlSKrn@fqvesXK2adzhK# zA>I^fme=YPiHWdrsMO3|mq>|MCf(O~{|rv)!}9G1h?E1=G#F65^78?ilo`jkL<>nu zEXkJ3e11_TIzrEd;(Y9uwv?0=F*T(chcS$-%p@Tg;>dnI_4OeHn4Jq`J}bn;@eV$n zxr=C1DJPxtI9z3A%auLIx(4b(VN$Ikmt7j^aVbno9>vJSZP<1BsB?1a zN=9j`Rm5o2W4D_bIn+$fgd>#PJc&Q3Bg)>Dpb$xNqDV>M1!663Dm)5qX*xNp6pTih z!orH&Rs(6OjvZ~RTog%^bO3$e5{jXgggE%SA`#K@I$QFR*}k*1$sa~|J|K^z*Gr_O p#bdVxsc+D6dSco>LfZT+EVP;_Nm;Hlu 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 Vsk`u>=Jx;q002ovPDHLkV1nvSt-b&N diff --git a/Passepartout/App/macOS/Flags.xcassets/vi.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/vi.imageset/Contents.json deleted file mode 100644 index 8c5e4610..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/vi.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "vi@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "vi@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/vi.imageset/vi@2x.png b/Passepartout/App/macOS/Flags.xcassets/vi.imageset/vi@2x.png deleted file mode 100644 index 6f3bd1bc6f0f23a46960ddbb22650da15cceb063..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1444 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 M07*qoM6N<$f&;M5xc~qF diff --git a/Passepartout/App/macOS/Flags.xcassets/vn.imageset/vn@3x.png b/Passepartout/App/macOS/Flags.xcassets/vn.imageset/vn@3x.png deleted file mode 100644 index d86b6896c7c71d40f9c2dd873f5714be9b3ef1ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 668 zcmV;N0%QG&P)PqDborpYF|J04P+25Dk;XEZ7KpO&53d7j_Q zGxN;MiW>84AO#aHQDZ&^CLYB2-wvqK6lB~q1sON}6A=4SN4pK}Al5>NTW#t#qaVbn z1lTv)h>X-~M?c8zE`)uxt<{cJ5aW1+c`^jRxii5a&fT8#P9*?apDn#0m0Do)PjTf#Yv)aIYk}glg-wo>b3WOi6#BHPsE!h$@syX}zd1NEmk*58@;n$hN zsUSen90}gJcxOvQ8zVR)p}Cr&eyf1oOnVGArtCnmE{Kt9Z&%w<$U(L{2OE#(Zlk(-)Du;->LZTNjE%-^|^z zz1tThwpBLfEC(QV^2^g4{=;5=2|rnQ3nha0hXAmybP%)Z&M1mO+C*PXymLibFK7NJ zmvw=Ee-{5y9wM->sS7VAlw2X$mrKnu3g*4h)C|tRIA*p}zsQC#o^8*SUlM0000X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x15-&vK~zYI#nfL&R9PIr@$b2FaheSKbB7VfXr^eI8J)~O+vJ#Rf9WNp zB8H&dgrIi&hbhIrskV`P|<*=Obz{h@ zM;q&>)(nY&G{Fwv&;kPkP+SZ)8#FXP)nj6$VwM)@X!{JWCm1sFPv5Q?y{r$AcuPTd`ro z=*cxTuw)5TRYCo;u_FNP-zR+t($WH1S#bS2Y}o=USE`4!vU2p~;T1{yLE>%aIC|b7 zPlh1DAne!y+qbLNwxa{Oy5Qcu@SGVlpr{D8ZiOR9;L;;Q^MV4K-Vq}yV|9G({W>M= z)&v6Z-~oL3qV|zHcfu3gZt!^2_tyOs6XR; zZxuphMiUSU&0X*Gq~OL3$&hJq3L$ipfe;Aaf8HngpWC>}X#fBK07*qoM6N<$g3TAP Ai2wiq diff --git a/Passepartout/App/macOS/Flags.xcassets/vu.imageset/vu@3x.png b/Passepartout/App/macOS/Flags.xcassets/vu.imageset/vu@3x.png deleted file mode 100644 index de55b8b39664c251f826921987c4abe5611c66f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1268 zcmVICSOx?qjsL3A8z|Bb*4l1Z<__II=v1){6Oewv4I7|4~ zLP6-2PjZrbdwNg4zw^7l-|w7rrLUm22oiK4v51k@u0>te$+H?zL&2i8pt&OV_2KCA z5Lx;=E6genQXmRYv=%m7BvnNPXla3c``}d*y-s*#f=sQ&%?7!-8Aom|SS+x2FUXNB zFK!@aXoSXWFiNmgYmtGVyLAiF(%|Gt@Ot6kL1<}(zHQ`u3A*R-r`93^K?3n=0VF3w zO$}__D(vj<+jgk8Jf@nbL-khTTP_u&t~ ztC7}enc$J}L_~nY0S69%P6xMdLuo0*{6+717!8H^;7XkyoxhGZZ{W}&*t{8BF4(>u zo;-msDp|cnChiAl2Hpoz>$Ldw$aoY5_V0&Nr(oGKc<=yF)x)l&=!3xms-!EA=cMvx@fv`J*H ziV7GR@iB-5yLZF6b0UW7>O`tu(@XzZ@ERaC=!RUo1~+bqs;(%a4G)V7Z?lPb@W=St zGdL)&o!>#Fm!3@!xuBLH3{e3!HWsy^L0rehp}Ov*9Sv^x!V3BjCb+1+9<``w;%@~7sO#6Gx?HG=64o(NmSf(ENlxgWvLSU^gEu78%5l7b@?B_S3&&guHs4kI`ye3 z)1+NN3>1Sw6v$)8AS6U2h9gJ7V2|laE=~^D$vV?KYn_&PTQvUGvSiqL+}deeW7K5P z=yzk~i`ZCE`ETA7*JsW^dIofv5?r}8xn8Y|fDhbQ_52;?wjQ^>)vwTNzOkCoBYAo7 z=n-7F0J1F7@MoV(>iZ4rn(b%p%RdGqU=D{5L*Ws<=@sdt@)G%@I((R7$mE(ZFd(wi z_cxT0ltU@4rYuvIgfy$z3I+ik(8(#$ZI&{NS(2rX_JB@-i8Vp5ht5tYDS=;_^n|gW!U_%O?Xx5M eG`?WL!Z1mKXa7E*nWjL?7(8A5T-G@yGywpH!bC~{ diff --git a/Passepartout/App/macOS/Flags.xcassets/wf.imageset/wf@3x.png b/Passepartout/App/macOS/Flags.xcassets/wf.imageset/wf@3x.png deleted file mode 100644 index 5b94a2263d0ae4c51f921f00255def0ba318d445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv6`n4R zArY-_Z|ZU}C5p5@v_9-|n8Rp+K~I9v#-6LZ60!a34y2WMvk9^pM)N5B2yM3Sws^|> z{N?upv-7X5<6keJBVKwdKg>M!@!q?p^B?aFG3&bgRFBu^Rh^_@a_Zyn700&k))JoR zeO-ZXVIfDabB2u5V*#o53l=TM6iu)SY2Lc2p~-xB)pRANsX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0f|XOK~zYI?Uhd`gJB%UKbzO4g~&o#OxZ=I{22!q^5$hBn9a zt%5C(yhydRn3efHJnm-9rca?*Tp=8J z`;yrAdKe#ciWLckWx8E{?A#g%W(fr}?*miO=l9`Y`dF&ZVA!ach|n#@2*1x9*0 z>N)h^u(orOcL~T6*1N|D_m8icO-2I?6J3;8%t9m}OIX!Zlv#Lwk?EgY7bXEeeBUNV zCEi6hoy|5%i^aXh&ms+#3=cFiH|8QLCxnV+Z_p7;C{o5LR0&*d_81v91wz$RYv{ z)j=;2Bt?fxB9J_I2`qx3L$OIk;9(Du$ikMTMNqV$pi5S`VV01QWvMgH&U6d5-L?0o zee@3VotDRYGvBvwm^aeaO((yCGk52bqrC5-WZY^M%YxSCI`k*U8E<1n85c04&`#XA zz8Wo!VRU!I5Lz3+!0;p{vtM|+F+pf2PJNh1LwyL9J~x^gHXu7Q&(n(;>K)0MCZ=b< zqqpxlBni-0ay-45DH)>31V+xx8@q7z5j5R^*W)Ufe{^3x?mQSPzab?hAFYnVJFuq8 zmp7*A1^{45TlqZ}sSTp9=OB8|HKXU$Zd7=vH@U17gj9GGT4|(&xsHMIAR2X|9qq6b%`9^&SB5_+_@_!UIiPaJ+Rp zIxnX%`g)d&91GOv$e(yzG9tA>OwDlr!lAiyu5sDLmhJQL=4jpPot*VkGZF3ai)DIPcLSuL%OaFVQ}Ov z0H8lPj!W@p+@$P49YTnNg+&d~wm2-y;^|}y33XjmRx%+lEfbcN|C@&N$9wlR07*qoM6N<$g08(T!~g&Q diff --git a/Passepartout/App/macOS/Flags.xcassets/xk.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/xk.imageset/Contents.json deleted file mode 100644 index 9cc7c031..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/xk.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "xk@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "xk@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/xk.imageset/xk@2x.png b/Passepartout/App/macOS/Flags.xcassets/xk.imageset/xk@2x.png deleted file mode 100644 index a66e0ac1e57d3e6a39819d93261080c3a2e94a37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 749 zcmVX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0-{MoK~zYI-Ih&A6k!<0f3rKYtFBwC?Pe>4AFbsf!6j26iXaT7Yta`S z6fC5mF6B{=bt4W^3;5?8ofRbO;wBb!S(_@PB%l z=l}kn-^}|kyn^f0<8CzVG+V1~!xjT1wLL(L24ThZrh`bL0lO^=fbtR>*Dh4xbmZW4 zs`o^IC3P;xrQ?K z7u&E}B-&c4Xl-(1k_;G)0=YR-N)Ry>#LdP^LScoDt`WRbi_FZ02!s>{hJDmE_mD`a z0GzzsgV|&x5K{1b`av+PAP552FIJGZ!_1wBLj+|dGmubN!M_wG5{(myC3w<1k*qB& zMgZ7j4e~u`ArcdkjUGMoCVc@}K~=RiAyR?>baaiZT5C8kPUE2uw7)urcixVw{X4Lo zPd}$>0qB?!Rnt&4ZN=J@--f}U(q7j`=b7j1%lD_XKP`~eRO$3_>v;d45JAIgigDxU zdjuhM(+vSJi*ekAK2EqMv6^Ck)tuHKx9i_fWc!siNXi*mmfa$A$^DUC7MXI#!m7T~ zd_U#8=aChO`B2WrgZRVQ1fnL66i=_|69kRs>LEhPD4v-@?)O*g%GZ4~2~}Wd)=4a( zZ;M4z81Xsu#-}$Uo^MWGkC*E5PX5f}?L^7ufB^K4mC`@8cTG=16?iakIIUdWGqSAd z#hu)JSIg*}gDW+I$>;uXHep3Xj)~msuO%8cZZ62O`o0#^Tr)@@V&;XnjNY+Q3UXzv z<|w{EZrXVngTxX>JTrw1&ld3Viwl52)SPMD`YuLtOkydTwVsHiGX5d}9RvFQ5qh*e f?yMWB{|4kYYm4VX5Hj>G00000NkvXXu0mjf*CS!6 diff --git a/Passepartout/App/macOS/Flags.xcassets/xk.imageset/xk@3x.png b/Passepartout/App/macOS/Flags.xcassets/xk.imageset/xk@3x.png deleted file mode 100644 index 2499b6b766eecf5ecf589433b1d2b52b299ff7ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1181 zcmV;O1Y-M%P)Ku@gEA3O5b^e+AdG<SRK>3YSpgmsI9%Gm$Xez(&QxP z;=`a2+OlHf!T zW{vIHu{No(xop$HQc$`76|+tb?X9A{%MUMUC`NU0j+I;=z$6-_tD%eC?R6SX|jSk7i|5$~OBr*}xkKNY3GV<#XhjQFPk z2*?Tr9y|W2C}R^5?kI8d+@V?;>Pjdt&EwsV+X;qah=PFAArYF6r<7O9rS(AlYPPN~ z1|S%Y(b_hQC_ucyhm&mgX(&-m#XksVE)YlN5ODym|b0G+n3P7XqNWKS(ef zqq{#iV?WoJMHG1bqc+@5E5l>cJla&wJ1^eDgY{*on!(kMaf~E|2o<|dB|8_VL*m41 zjcA(A<1bxAHx@Qs;fOLBu_u>|04LwLAFEa5aLacol~+naJT4m%Ma3#v&<%q~RK4X~ zZ}B*LyXOW**ANtl*jVh`IK`=Oau8H2s{IqR%(p zeD4Vr5@- z*Q`0gXbz#6jgmZ>tAndJeq|G(m<>g>n2w*7Bg&W8D-+5ru37lzMmaBk)x@z24JfL_ z=|5`l$u2^QE$KfvA6Ys5PDJcX$85N4G0t6I#}}Pd_+;12_m=ZJ>6=QwIm|^?{)n!N zeBHGUfKS@jCx`&}XCfa}&ur+dJ|gW!U_%O?Xxnmk<` zLp(a)UfjsrV8G)N=sc-QbOz%_N1ecp2DV8IhqxSA^De74HL+DPF8?rnUHzrd!Vm6I z&%{_Z%bkxB`rxl<)A~W=(CSOu{3d+TSXGd>Y1!+=8jrNaihG?ZxE_i!Z&Ykid#=-E zaLlBkgPp-Q&Ef4gpbHp0UHx3vIVCg!03MP?Z~y=R diff --git a/Passepartout/App/macOS/Flags.xcassets/yt.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/yt.imageset/Contents.json deleted file mode 100644 index ce85b9dd..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/yt.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "yt@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "yt@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/yt.imageset/yt@2x.png b/Passepartout/App/macOS/Flags.xcassets/yt.imageset/yt@2x.png deleted file mode 100644 index 62d74b4234bbd0e63fcc2d2c282acfe986a5e93d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^N|gW!U_%O?Xx5M eG`?WL!Z1mKXa7E*nWjL?7(8A5T-G@yGywpH!bC~{ diff --git a/Passepartout/App/macOS/Flags.xcassets/yt.imageset/yt@3x.png b/Passepartout/App/macOS/Flags.xcassets/yt.imageset/yt@3x.png deleted file mode 100644 index 5b94a2263d0ae4c51f921f00255def0ba318d445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_!3HGTDo)q{DYhhUcNd2LAh=-f^2tDv6`n4R zArY-_Z|ZU}C5p5@v_9-|n8Rp+K~I9v#-6LZ60!a34y2WMvk9^pM)N5B2yM3Sws^|> z{N?upv-7X5<6keJBVKwdKg>M!@!q?p^B?aFG3&bgRFBu^Rh^_@a_Zyn700&k))JoR zeO-ZXVIfDabB2u5V*#o53l=TM6iu)SY2Lc2p~-xB)pRANsX1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x1Pw_|8GB6O8F>l9SkZmfdz46=mrH!w+q&-GMRiN zBx1IBp%R0;@J5#yL~l-g8{U(q+xp6gMIrVi8P5R0ss!bOY!uj+P&|XJ~K1Ybb_l_dC=?c zK`c&;$mtxrLqHfiTR{6*mny&O6~8Ow8AfB{HaHwk?BDN=yU1G#SC+TrhD?{@GXS7t z1x+r@ZhPvx1G`TSpEoiRi5UI;5_ERngishik=H-MeNL4A^XGi4rsrAf5T7v9u>u(| zcY$Q^X`Cm3@t51a5|@T_`DG*DRP)hGF-Xx8w7&rlZX%ikK4DV9AiDFAQtlR%%yqh&}(;dv!WoG zY=?JA3#N>9-v%XoaS@i5rl8dVCR5%fJoKwX06-)};QLQt(N&_#8|C`769pp{f0CN* z1|Ek?b!a{`x8&=}001`IFeoZ!t+C>E53Uhe=0G_{e>w$;kdNH6bF!f~rYA#%36 z$`r+Se${;)O%^1vW{`~tP4%*h0IRa2o~O7An%jlrV?j~nMnCi)h&@@C34|be={Pip z+AyBXM7^WW-K_jlIb-Gk^YJ!WS7e!A%y>Sp4kDU?t4;1ynF?>Y0R;bdkwX<2o$5xA zLm+Q=Da;kOcgO&2iKhr}y_YL%#1rK%s&5q-*Xy8!h^*)u%{jMh=ncD4Ni&31;ze1} zo&Lk8!^!-l?2Sr9XezWuH(^#|9F6&@}Jz_HoFe^6BHoyCUR9rmme zsVT#F&*w-I@}XHVSy?4^q~+R8M>;&|0*NLNpsiZ%M8s%ZD%*&}BW8I3utk9251-<9 zT_vh~6!+nyEL7p|6|e>Z!hF2v?Q8GvK2{1KtFO;%sIR|U9tzQ$UdOznwYY!g1RO#E zG(8KcgALa0^2j>1ke`|}V>;3?CljWrgF$BU;K7NI!LX>=tiUpa8H7GSb99Z9c>{e5 zscNva^l}#7D>+(92oV_!i`TSTYuS@zVFh~LH!iv3_-xKxm?o@$wLK;Ko~iDfQz6Pw zhr^7$p~33u?e$g1LEtf*R=YAYOp_e8fVSdh^F;HHVudK9#A5LqrP7g%-d^9XcuD~4 z%#kdy+-ZKf!={vMRYfAqOjFa8t-ITkkYNU})I(cwvw8N_KAuvtb&tj3H)dqqkal-_ eN)jaj0Dl4OJf~SUkqJ%!0000I#=Hk#O z#_W>0Te4)E^UI7bPGdBS+cHd?vW=n9c$vn{f$;`bD>yHkGAujpP@27^<&DIZaR7jxo)A=3$*^hDL-h36+{H{3 z1f?l-tOwT2&aW$bd20-hFi`-2t}YXbizTS4dM*Ol!gzY1yz!Tmh_Xe2YTch#000pY z5>!@Fl$9AFmHJ&ETRa*8fL=>qi1Ot6>`m1>Z#t+$4}f6ZIw@LO49LxO_*o+>7yx4~ zt)YB#?`U#N`7(#b*nS-V5FQ?a^XCaFD!LF9BtoYuIQVdyG%D3O zL~-m`2TDp}U^4kZu6qcrg&oel@}oZdT8Sn;&JILzN%Hah_39nUcjgk1!S!V{cA~H_ z02eNV_+Ikpki}`ZdwMSZc0>c!}Av?;;>#=d; zJebWs7d{dKfCh^2^w=(_mt`Uw&gy*EBddA+;q+M!ib(d!2>m>DQ}rhc)+ZA}Bme+| zp%wZ0BB)fX^Na_AA*a^Tblg3)2U4K`aRAV1<9BD?mp3(AL}^@-&JLNXy;^WkFAEHv z?;Nt>=+WC)yH){5CZ`1RzYX_eR-?aDophhJCqbzV$;Rt z`)xsjK>0ZJx`mYy00;?!@wcyVYheZ zjktJi3F>~>hG-sxDFD!97PV#Emv?vB`3rn4jtiWP06?e|edqS!_QE7&V=w{T(l(`f zM`v_0I8?`%jLV3N;(XM8zZEe&2GIb}@H9Z1-XPcOZM^xOVttKQFDC*J(81FT00;vx zgpT`%@luQLmev=Y(aBskzAy25vp1VX?HToQozBKrc#8EW-W6yiLm2zlnIBzzYwjyq07PD^9tF2X@-uPg^a^mbl%W_{wl`T@~PT$0WT zj@7DG9O{;dLR}}B?d;jdH7i$Y(#&Ru|CZKhRVdR>y0K9cH_Z<3THS#G+PrjWqxsS$ zqt7?^u(^Hp=R>R{vR%ERtSwqBb2VNLwc1pZlhcsW-|rCnT=@t_{vC9^L=!j74ie2J z$-7CbRbOU&m`FfE0l;FR+M=Rn{h2d{=XU4f17Hj`o-wa!&Mmb`oCW-Mck)EESf*30 zIHZ{_iAr}4QMKAsotxX3Y%~%ilo!^tuQ_XX znXuVtTTxN-Ev2$Pdm>E$gL}jyVZva;ugmt`SvYx~>m;LUwW)gX;zmVZpFP%(`dH(! zu_~UOU)#05>Z!|w!-38X8#J{_Wqo#EpM7FKiVT4dP`4igzs>se!JBi7W&n5$;E#?D nYo|hSU#ipD!~pqUeMI~Rt@L090(k~=00000NkvXXu0mjf$?z0m diff --git a/Passepartout/App/macOS/Flags.xcassets/zm.imageset/Contents.json b/Passepartout/App/macOS/Flags.xcassets/zm.imageset/Contents.json deleted file mode 100644 index 5def39bf..00000000 --- a/Passepartout/App/macOS/Flags.xcassets/zm.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "zm@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "zm@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Flags.xcassets/zm.imageset/zm@2x.png b/Passepartout/App/macOS/Flags.xcassets/zm.imageset/zm@2x.png deleted file mode 100644 index 8f314bb098d7a7310f7d049c805101b26eeb6198..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 402 zcmV;D0d4+?P)X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x0Y^zhK~zYI?bbg_TTvLm;pgP9UIRkV#cG985GMyGajb(7ousqQeuP5D zE)IT!?nReQf>Y;q(M7vC2t^`DqNqtoygCG{y=|A0D3tr#&f$goz~Nj@7~8OQ%UjP_ zU_0}ifXDS5Qo5${gV}RlTzB_E%tDRP*27$c*D;G7hGV&0uOWHNT(Q=s+2X87waD@S zn^#QTGTEZkV7kO{;AD ze4b#p!&IH=CY4LhI}8qTIK=yiqilCRpf8a78aehy*hi=`68Kc+H2QtEuaL(dsg*g% zaQ2t$@g$J;4LkRMn2^yukA6bleTh`Q7k;i!hGO#v7r)GWSY`d?!}?p+-2eMS``1!P wvJ{dmg(OQM$x=wN6q4*o-XqDkNBG0Y9ZoJt9ui`SMF0Q*07*qoM6N<$g0JDc$N&HU diff --git a/Passepartout/App/macOS/Flags.xcassets/zm.imageset/zm@3x.png b/Passepartout/App/macOS/Flags.xcassets/zm.imageset/zm@3x.png deleted file mode 100644 index 1deb600d648618c9685d68270d1a6326b3fc73cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 600 zcmV-e0;m0nP)A{VbFJt?qAmhLkdQ@i!YOE!Bz8=CNOxZsy--Q3pu)Y#{g+keoT{HX6h$S*uDR$F}JkIf@%}kZ|L;d-a zYh0>xBjC~gZ+Sxvj;u4BWonsjl3UdfMH&T0Rwx8aqrS<-Hp^+2%ADv>$dV7Zu*%^U zU^F8@ChgR9ssK6Xu)9XH!`py|dw(4tS&=um)8S=_kCorYM^>a&V&*4pG>FhdjxVL9 zdfUf01?C#vfjN_-{VRD8p^03*U9zK@*fzenqig!f`oNs&^IShRxL*8dj2f{>)QCl* zMl2FFVv(p3n#lIY$=?y5NYsc$qDCwdHDZyd5sO5P|3)N@dtog6tlB+V*h*~sj(VN# mwlm|h1Cxpx^C^WvQ(pld-HEytoZ0380000X1^@s6RQmj^00006VoOIv0RI60 z0RN!9r;`8x16xT%K~zYI#g$!1n^zcspYO}3OB16tRi{;i4s3-o2VuJPLJZy5RXe>I ztwk?_rKTzDqEJZ_I$`}`zg;?oQFgToC79Z{ZZbNG#EcE04U1#aveKbzt($30<7`aK z&)J1bFl)M-B>KR)IDg;wob#S@Udh^TU2%TxRL|_10Uevgu-sy3QKlxQ=q&1ldz&VL zjEpEPE#>miWiFX6p`Fx1Y~w`~6+%u`y1Tn+ZfWN5k%R1PouaC4V3N^X51t3q%h@{AYBispcd%oRn*%5HJbqGG5rC{B04q0d z@^*U%=P&v(eb|cES6C56j*)fa7fzj`+TozCv5~8zs1qxEYcm=d_WQZGZy(q5C;}jTJK_?1x7RV((?fiI9)QR@OVqrTAn-9Lzm@+xQbKRB zP}$!PDiwnR0}S`|(HV_WoTEg^%S3QraS0)Whz9~i`)1$={=+Y%`nL zam+LAJm43(GaoJ6?E+Ip$<4$;Kb0h%%0k5A%tl3`ISpWb407*qo IM6N<$g54p;UjP6A diff --git a/Passepartout/App/macOS/Flags.xcassets/zw.imageset/zw@3x.png b/Passepartout/App/macOS/Flags.xcassets/zw.imageset/zw@3x.png deleted file mode 100644 index 97d93cee372bde942d0d07f9b2458e40ea63f469..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1319 zcmV+?1=#wDP)~p8^3jj8 z3o`_5p(V7{Cu!20^S&p~FaQ7PdHYIR6kF`)Th90M5go+h;`KyG9*;*$-y1pLi(2=iwLz_07z2=0T>w>Vc))eT)kAm z$zKF1uV{6I@X=Z?Qe9n*wkV&Ui~3Q177De%@H+@VPfrhOwVGuM8~JrV%$*Z#J^v8` zWYb>;?tuZ4lar~huP5b$Qa)5i&~hi(dV%3rMS8byC+X->ke(eb4Gj(CX>uw0s+Xd# z$A`m)U#;b`Sh$;*$nwTU=A@^Ok6W!)^78U<)YftUh!3{yR7lgb*64@>%jw?{E*Cbv ze$t#=E~loZhOOo0>e1{4H;$4)HV+oIO1Z8w_5rO-)V3U^Eh?)shc94wmo~NJ0oP zcK<$ohYlf}&PkI`V;YIdy3LbXkdDE^_tS}8wd$os-pJ2qPFflOdcB_F9Xlu<7~tbj zBmj^mwAQf6!~?aOvAcK2=dGJz^^Xug4|=b|zhw~H{rvvOB}+&=a)kL?w@zBOtE&r@ zO2xygS6M-bT1`yA?Qn=kyLXFQAZ~$Z*C0C2BD!i3j?0LliwINZv$?M9Y~k$f6~1sf zouag~RLEp90R;6Ufxw%D?aUc*TP_zja}oE?BHY&yM@tcZmOl%1v22s@xZOTOOyt6a z3nC#QA?QK8R(*j&fh+B8JfA{zB%Hep8{UJYrOr;7mL44^VT^!@KVUOD%Bx z4q`T&QK?j%et z2FsH|r4=|nH+x%K8%kvko`pAG-&dwOosNRN1vt_i@CsL90+WN-Y&Lf8+{x*t)36DM z38PwIJP%?p7*MK}bS~|L)Np$RjF5Re9x5s-$oM>i&Q-5v2mqwaFEWSf+Uok*KnKyd d!!0yJ#D7^?vO+1Q_lW=i002ovPDHLkV1lZdZDIfb diff --git a/Passepartout/App/macOS/Global/Credits.html b/Passepartout/App/macOS/Global/Credits.html deleted file mode 100644 index f494b57d..00000000 --- a/Passepartout/App/macOS/Global/Credits.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - README · - CHANGELOG · - FAQ -
- Disclaimer · - Privacy policy - - diff --git a/Passepartout/App/macOS/Global/HostImporter.swift b/Passepartout/App/macOS/Global/HostImporter.swift deleted file mode 100644 index 4db66e21..00000000 --- a/Passepartout/App/macOS/Global/HostImporter.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// HostImporter.swift -// Passepartout -// -// Created by Davide De Rosa on 8/18/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore -import SwiftyBeaver - -private let log = SwiftyBeaver.self - -class HostImporter { - private let service = TransientStore.shared.service - - private let windowController: NSWindowController? - - private let viewController: NSViewController? - - private weak var accountDelegate: AccountViewControllerDelegate? - - private let configurationURL: URL - - private var createdTitle: String? - - private var replacedProfile: ConnectionProfile? - - init(withConfigurationURL configurationURL: URL) { - self.configurationURL = configurationURL - log.debug("Parsing configuration URL: \(configurationURL)") - - windowController = WindowManager.shared.showOrganizer() - viewController = windowController?.contentViewController - accountDelegate = viewController as? AccountViewControllerDelegate - } - - func importHost(withPassphrase passphrase: String?) { - let result: OpenVPN.ConfigurationParser.Result - do { - result = try OpenVPN.ConfigurationParser.parsed(fromURL: configurationURL, passphrase: passphrase) - } catch let e as ConfigurationError { - switch e { - case .encryptionPassphrase, .unableToDecrypt(_): - enterPassphraseForHost(at: configurationURL) - - default: - let message = HostImporter.localizedMessage(forError: e) - let alert = Macros.warning(configurationURL.normalizedFilename, message) - _ = alert.presentModally(withOK: L10n.Global.ok, cancel: nil) - } - return - } catch let e { - let message = HostImporter.localizedMessage(forError: e) - let alert = Macros.warning(configurationURL.normalizedFilename, message) - _ = alert.presentModally(withOK: L10n.Global.ok, cancel: nil) - return - } - - if let warning = result.warning { - let message = HostImporter.localizedDetailsMessage(forWarning: warning) - let alert = Macros.warning(configurationURL.normalizedFilename, L10n.ParsedFile.Alerts.PotentiallyUnsupported.message(message)) - - if alert.presentModally(withOK: L10n.Global.ok, cancel: L10n.Global.cancel) { - enterProfileName(forHostWithResult: result) - } - - return - } - - enterProfileName(forHostWithResult: result) - } - - private func enterPassphraseForHost(at url: URL) { - let vc = StoryboardScene.Main.textInputViewController.instantiate() - vc.caption = L10n.ParsedFile.Alerts.EncryptionPassphrase.message - vc.isSecure = true - vc.object = url - vc.delegate = self - present(vc) - } - - private func enterProfileName(forHostWithResult result: OpenVPN.ConfigurationParser.Result) { - guard let title = result.url?.normalizedFilename, let hostname = result.configuration.hostname else { - return - } - - let vc = StoryboardScene.Main.textInputViewController.instantiate() - vc.caption = L10n.Service.Alerts.Rename.title.asCaption - let profile = HostConnectionProfile(hostname: hostname) - let builder = OpenVPNProvider.ConfigurationBuilder(sessionConfiguration: result.configuration) - profile.parameters = builder.build() - vc.text = title - vc.placeholder = L10n.Global.Host.TitleInput.placeholder - vc.object = profile - vc.delegate = self - present(vc) - } - - private func enterCredentials(forProfile profile: ConnectionProfile) { - let vc = StoryboardScene.Service.accountViewController.instantiate() - vc.profile = profile - vc.delegate = self - present(vc) - } - - // MARK: Helpers - - private func present(_ presentedViewController: NSViewController) { - viewController?.presentAsSheet(presentedViewController) - } - - private func dismiss(_ presentedViewController: NSViewController) { - viewController?.dismiss(presentedViewController) - } - - // XXX: copy/paste from iOS - private static func localizedMessage(forError error: Error) -> String { - if let appError = error as? ConfigurationError { - switch appError { - case .malformed(let option): - log.error("Could not parse configuration URL: malformed option, \(option)") - return L10n.ParsedFile.Alerts.Malformed.message(option) - - case .missingConfiguration(let option): - log.error("Could not parse configuration URL: missing configuration, \(option)") - return L10n.ParsedFile.Alerts.Missing.message(option) - - case .unsupportedConfiguration(let option): - log.error("Could not parse configuration URL: unsupported configuration, \(option)") - return L10n.ParsedFile.Alerts.Unsupported.message(option) - - default: - break - } - } - log.error("Could not parse configuration URL: \(error)") - return L10n.ParsedFile.Alerts.Parsing.message(error.localizedDescription) - } - - // XXX: copy/paste from iOS - private static func localizedDetailsMessage(forWarning warning: ConfigurationError) -> String { - switch warning { - case .malformed(let option): - return option - - case .missingConfiguration(let option): - return option - - case .unsupportedConfiguration(let option): - return option - - default: - return "" // XXX: should never get here - } - } -} - -extension HostImporter: TextInputViewControllerDelegate { - func textInputController(_ textInputController: TextInputViewController, shouldEnterText text: String) -> Bool { - - // rename profile - guard let _ = textInputController.object as? ConnectionProfile else { - return true - } - return true//text.rangeOfCharacter(from: CharacterSet.filename.inverted) == nil - } - - func textInputController(_ textInputController: TextInputViewController, didEnterText text: String) { - - // rename profile - if let profile = textInputController.object as? ConnectionProfile { - createdTitle = text - - // overwrite host with existing name? - replacedProfile = nil - if let existingHostId = service.existingHostId(withTitle: text) { - dismiss(textInputController) - - let alert = Macros.warning(text, L10n.Wizards.Host.Alerts.Existing.message) - if alert.presentModally(withOK: L10n.Global.ok, cancel: L10n.Global.cancel) { - guard let existingProfile = service.profile(withContext: profile.context, id: existingHostId) else { - fatalError("ConnectionService.existingHostId() returned a non-existing host profile?") - } - replacedProfile = existingProfile - enterCredentials(forProfile: profile) - } - return - } - enterCredentials(forProfile: profile) - } - // enter passphrase - else { - importHost(withPassphrase: text) - } - - dismiss(textInputController) - } -} - -// enrich delegate -extension HostImporter : AccountViewControllerDelegate { - func accountController(_ accountController: AccountViewController, shouldUpdateCredentials credentials: Credentials, forProfile profile: ConnectionProfile) -> Bool { - return accountDelegate?.accountController(accountController, shouldUpdateCredentials: credentials, forProfile: profile) ?? true - } - - func accountController(_ accountController: AccountViewController, didUpdateCredentials credentials: Credentials, forProfile profile: ConnectionProfile) { - if let replacedProfile = replacedProfile { - service.removeProfile(ProfileKey(replacedProfile)) - } - service.addOrReplaceProfile(profile, credentials: credentials, title: createdTitle) - _ = try? service.save(configurationURL: configurationURL, for: profile) - - accountDelegate?.accountController(accountController, didUpdateCredentials: credentials, forProfile: profile) - } - - func accountControllerDidCancel(_ accountController: AccountViewController) { - accountDelegate?.accountControllerDidCancel(accountController) - } -} diff --git a/Passepartout/App/macOS/Global/IssueReporter.swift b/Passepartout/App/macOS/Global/IssueReporter.swift deleted file mode 100644 index 0660efc5..00000000 --- a/Passepartout/App/macOS/Global/IssueReporter.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// IssueReporter.swift -// Passepartout -// -// Created by Davide De Rosa on 9/5/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import AppKit -import PassepartoutCore - -class IssueReporter: NSObject { - static let shared = IssueReporter() - - override private init() { - super.init() - } - - func present(withIssue issue: Issue) { - if issue.debugLog { - let alert = Macros.warning(L10n.IssueReporter.title, L10n.IssueReporter.message) - alert.present(in: nil, withOK: L10n.IssueReporter.Buttons.accept, cancel: L10n.Global.cancel, handler: { - VPN.shared.requestDebugLog(fallback: TransientStore.shared.debugSnapshot) { - self.composeEmail(withDebugLog: $0, issue: issue) - } - }, cancelHandler: nil) - } else { - composeEmail(withDebugLog: nil, issue: issue) - } - } - - private func composeEmail(withDebugLog debugLog: String?, issue: Issue) { - guard let sharing = NSSharingService(named: .composeEmail) else { - // TODO: show error alert - return - } - sharing.recipients = [AppConstants.IssueReporter.Email.recipient] - sharing.subject = AppConstants.IssueReporter.Email.subject - - var items: [Any] = [] - - // delete temporary files on exit - // NO, they're needed until NSSharingService is dismissed (who knows when?) -// defer { -// for item in items { -// guard let url = item as? URL else { -// continue -// } -// try? FileManager.default.removeItem(at: url) -// } -// } - - let bodyContent = AppConstants.IssueReporter.Email.template - var bodyMetadata = "--\n\n" - bodyMetadata += DebugLog(raw: "").decoratedString() - if let metadata = issue.infrastructureMetadata { - bodyMetadata += "Provider: \(metadata.description)\n" - if let lastUpdated = InfrastructureFactory.shared.modificationDate(forName: metadata.name) { - bodyMetadata += "Last updated: \(lastUpdated)\n" - } - bodyMetadata += "\n" - } - bodyMetadata += "--" - let body = AppConstants.IssueReporter.Email.body(bodyContent, bodyMetadata) - items.append(body) - - if let raw = debugLog { - let attachment = DebugLog(raw: raw).decoratedData() - if let item = attachment.temporaryURL(withFileName: AppConstants.IssueReporter.Filenames.debugLog) { - items.append(item) - } - } - if let url = issue.configurationURL { - do { - let parsedFile = try OpenVPN.ConfigurationParser.parsed(fromURL: url, returnsStripped: true) - if let attachment = parsedFile.strippedLines?.joined(separator: "\n").data(using: .utf8), - let item = attachment.temporaryURL(withFileName: AppConstants.IssueReporter.Filenames.configuration) { - - items.append(item) - } - } catch { - } - } - - guard sharing.canPerform(withItems: items) else { - // TODO: show error alert - return - } - sharing.perform(withItems: items) - } -} - -private extension Data { - func temporaryURL(withFileName fileName: String) -> URL? { - let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()) - let dest = tempURL.appendingPathComponent(fileName) - do { - try write(to: dest) - } catch { - return nil - } - return dest - } -} diff --git a/Passepartout/App/macOS/Global/Macros.swift b/Passepartout/App/macOS/Global/Macros.swift deleted file mode 100644 index 9cca7b18..00000000 --- a/Passepartout/App/macOS/Global/Macros.swift +++ /dev/null @@ -1,178 +0,0 @@ -// -// Macros.swift -// Passepartout -// -// Created by Davide De Rosa on 7/30/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -class Macros { - static func warning(_ title: String, _ message: String) -> NSAlert { - return genericAlert(.warning, title, message) - } - - private static func genericAlert(_ style: NSAlert.Style, _ title: String, _ message: String) -> NSAlert { - let alert = NSAlert() - alert.alertStyle = style - alert.messageText = title - alert.informativeText = message - return alert - } -} - -extension NSAlert { - func present(in window: NSWindow?, withOK okTitle: String, handler: (() -> Void)?) { - present(in: window, withOK: okTitle, cancel: nil, handler: handler, cancelHandler: nil) - } - - func present(in window: NSWindow?, withOK okTitle: String, cancel cancelTitle: String?, dummy dummyTitle: String? = nil, handler: (() -> Void)?, cancelHandler: (() -> Void)?) { - guard let window = window else { - if presentModally(withOK: okTitle, cancel: cancelTitle, dummy: dummyTitle) { - handler?() - } else { - cancelHandler?() - } - return - } - - addButton(withTitle: okTitle) - if let cancelTitle = cancelTitle { - addButton(withTitle: cancelTitle) - } - if let dummyTitle = dummyTitle { - addButton(withTitle: dummyTitle) - } - - beginSheetModal(for: window) { - switch $0 { - case .alertFirstButtonReturn: - handler?() - - default: - cancelHandler?() - } - } - } - - func presentModally(withOK okTitle: String, cancel cancelTitle: String?, dummy dummyTitle: String? = nil) -> Bool { - return presentModallyEx(withOK: okTitle, other1: cancelTitle, other2: dummyTitle) == .alertFirstButtonReturn - } - - func presentModallyEx(withOK okTitle: String, other1 other1Title: String?, other2 other2Title: String? = nil) -> NSApplication.ModalResponse { - addButton(withTitle: okTitle) - if let other1Title = other1Title { - addButton(withTitle: other1Title) - } - if let other2Title = other2Title { - addButton(withTitle: other2Title) - } - return runModal() - } -} - -extension NSViewController { - func presentPurchaseScreen(forProduct product: LocalProduct, delegate: PurchaseViewControllerDelegate? = nil) { - let vc = StoryboardScene.Purchase.initialScene.instantiate() -// vc.feature = product - vc.delegate = delegate - presentAsModalWindow(vc) - } - - func presentBetaFeatureUnavailable(_ title: String) { - let alert = Macros.warning(title, "The requested feature is unavailable in beta.") - alert.present(in: view.window, withOK: "OK", handler: nil) - } -} - -extension NSView { - static func get() -> T { - let name = String(describing: T.self) - guard let nib = NSNib(nibNamed: name, bundle: nil) else { - fatalError() - } - var objects: NSArray? - guard nib.instantiate(withOwner: nil, topLevelObjects: &objects) else { - fatalError() - } - guard let nonOptionalObjects = objects else { - fatalError() - } - for o in nonOptionalObjects { - if let view = o as? T { - return view - } - } - fatalError() - } -} - -extension NSView { - func endEditing() { - window?.makeFirstResponder(nil) - } -} - -extension NSImage { - func tinted(withColor color: NSColor) -> NSImage { - let image = copy() as! NSImage - image.lockFocus() - - color.set() - - let imageRect = NSRect(origin: NSZeroPoint, size: image.size) - imageRect.fill(using: .sourceAtop) - image.unlockFocus() - - return image - } -} - -extension NSMenu { - static func withDescriptibles(_ list: [UIDescriptible]) -> NSMenu { - let menu = NSMenu() - for o in list { - let item = NSMenuItem(title: o.uiDescription, action: nil, keyEquivalent: "") - item.representedObject = o - menu.addItem(item) - } - return menu - } - - static func withString(_ string: String) -> NSMenu { - let menu = NSMenu() - let item = NSMenuItem(title: string, action: nil, keyEquivalent: "") - item.representedObject = string - menu.addItem(item) - return menu - } -} - -extension String { - var asCaption: String { - return "\(self):" - } - - var asContinuation: String { - return "\(self)..." - } -} diff --git a/Passepartout/App/macOS/Global/SwiftGen+Assets.swift b/Passepartout/App/macOS/Global/SwiftGen+Assets.swift deleted file mode 100644 index de7735ee..00000000 --- a/Passepartout/App/macOS/Global/SwiftGen+Assets.swift +++ /dev/null @@ -1,356 +0,0 @@ -// swiftlint:disable all -// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen - -#if os(macOS) - import AppKit -#elseif os(iOS) - import UIKit -#elseif os(tvOS) || os(watchOS) - import UIKit -#endif - -// Deprecated typealiases -@available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0") -internal typealias AssetImageTypeAlias = ImageAsset.Image - -// swiftlint:disable superfluous_disable_command file_length implicit_return - -// MARK: - Asset Catalogs - -// swiftlint:disable identifier_name line_length nesting type_body_length type_name -internal enum Asset { - internal enum Assets { - internal static let statusActive = ImageAsset(name: "StatusActive") - internal static let statusPending = ImageAsset(name: "StatusPending") - } - internal enum Flags { - internal static let ad = ImageAsset(name: "ad") - internal static let ae = ImageAsset(name: "ae") - internal static let af = ImageAsset(name: "af") - internal static let ag = ImageAsset(name: "ag") - internal static let ai = ImageAsset(name: "ai") - internal static let al = ImageAsset(name: "al") - internal static let am = ImageAsset(name: "am") - internal static let ao = ImageAsset(name: "ao") - internal static let aq = ImageAsset(name: "aq") - internal static let ar = ImageAsset(name: "ar") - internal static let `as` = ImageAsset(name: "as") - internal static let at = ImageAsset(name: "at") - internal static let au = ImageAsset(name: "au") - internal static let aw = ImageAsset(name: "aw") - internal static let ax = ImageAsset(name: "ax") - internal static let az = ImageAsset(name: "az") - internal static let ba = ImageAsset(name: "ba") - internal static let bb = ImageAsset(name: "bb") - internal static let bd = ImageAsset(name: "bd") - internal static let be = ImageAsset(name: "be") - internal static let bf = ImageAsset(name: "bf") - internal static let bg = ImageAsset(name: "bg") - internal static let bh = ImageAsset(name: "bh") - internal static let bi = ImageAsset(name: "bi") - internal static let bj = ImageAsset(name: "bj") - internal static let bl = ImageAsset(name: "bl") - internal static let bm = ImageAsset(name: "bm") - internal static let bn = ImageAsset(name: "bn") - internal static let bo = ImageAsset(name: "bo") - internal static let bq = ImageAsset(name: "bq") - internal static let br = ImageAsset(name: "br") - internal static let bs = ImageAsset(name: "bs") - internal static let bt = ImageAsset(name: "bt") - internal static let bv = ImageAsset(name: "bv") - internal static let bw = ImageAsset(name: "bw") - internal static let by = ImageAsset(name: "by") - internal static let bz = ImageAsset(name: "bz") - internal static let ca = ImageAsset(name: "ca") - internal static let cc = ImageAsset(name: "cc") - internal static let cd = ImageAsset(name: "cd") - internal static let cf = ImageAsset(name: "cf") - internal static let cg = ImageAsset(name: "cg") - internal static let ch = ImageAsset(name: "ch") - internal static let ci = ImageAsset(name: "ci") - internal static let ck = ImageAsset(name: "ck") - internal static let cl = ImageAsset(name: "cl") - internal static let cm = ImageAsset(name: "cm") - internal static let cn = ImageAsset(name: "cn") - internal static let co = ImageAsset(name: "co") - internal static let cr = ImageAsset(name: "cr") - internal static let cu = ImageAsset(name: "cu") - internal static let cv = ImageAsset(name: "cv") - internal static let cw = ImageAsset(name: "cw") - internal static let cx = ImageAsset(name: "cx") - internal static let cy = ImageAsset(name: "cy") - internal static let cz = ImageAsset(name: "cz") - internal static let de = ImageAsset(name: "de") - internal static let dj = ImageAsset(name: "dj") - internal static let dk = ImageAsset(name: "dk") - internal static let dm = ImageAsset(name: "dm") - internal static let `do` = ImageAsset(name: "do") - internal static let dz = ImageAsset(name: "dz") - internal static let ec = ImageAsset(name: "ec") - internal static let ee = ImageAsset(name: "ee") - internal static let eg = ImageAsset(name: "eg") - internal static let eh = ImageAsset(name: "eh") - internal static let er = ImageAsset(name: "er") - internal static let esCt = ImageAsset(name: "es-ct") - internal static let es = ImageAsset(name: "es") - internal static let et = ImageAsset(name: "et") - internal static let eu = ImageAsset(name: "eu") - internal static let fi = ImageAsset(name: "fi") - internal static let fj = ImageAsset(name: "fj") - internal static let fk = ImageAsset(name: "fk") - internal static let fm = ImageAsset(name: "fm") - internal static let fo = ImageAsset(name: "fo") - internal static let fr = ImageAsset(name: "fr") - internal static let ga = ImageAsset(name: "ga") - internal static let gbEng = ImageAsset(name: "gb-eng") - internal static let gbNir = ImageAsset(name: "gb-nir") - internal static let gbSct = ImageAsset(name: "gb-sct") - internal static let gbWls = ImageAsset(name: "gb-wls") - internal static let gb = ImageAsset(name: "gb") - internal static let gd = ImageAsset(name: "gd") - internal static let ge = ImageAsset(name: "ge") - internal static let gf = ImageAsset(name: "gf") - internal static let gg = ImageAsset(name: "gg") - internal static let gh = ImageAsset(name: "gh") - internal static let gi = ImageAsset(name: "gi") - internal static let gl = ImageAsset(name: "gl") - internal static let gm = ImageAsset(name: "gm") - internal static let gn = ImageAsset(name: "gn") - internal static let gp = ImageAsset(name: "gp") - internal static let gq = ImageAsset(name: "gq") - internal static let gr = ImageAsset(name: "gr") - internal static let gs = ImageAsset(name: "gs") - internal static let gt = ImageAsset(name: "gt") - internal static let gu = ImageAsset(name: "gu") - internal static let gw = ImageAsset(name: "gw") - internal static let gy = ImageAsset(name: "gy") - internal static let hk = ImageAsset(name: "hk") - internal static let hm = ImageAsset(name: "hm") - internal static let hn = ImageAsset(name: "hn") - internal static let hr = ImageAsset(name: "hr") - internal static let ht = ImageAsset(name: "ht") - internal static let hu = ImageAsset(name: "hu") - internal static let id = ImageAsset(name: "id") - internal static let ie = ImageAsset(name: "ie") - internal static let il = ImageAsset(name: "il") - internal static let im = ImageAsset(name: "im") - internal static let `in` = ImageAsset(name: "in") - internal static let io = ImageAsset(name: "io") - internal static let iq = ImageAsset(name: "iq") - internal static let ir = ImageAsset(name: "ir") - internal static let `is` = ImageAsset(name: "is") - internal static let it = ImageAsset(name: "it") - internal static let je = ImageAsset(name: "je") - internal static let jm = ImageAsset(name: "jm") - internal static let jo = ImageAsset(name: "jo") - internal static let jp = ImageAsset(name: "jp") - internal static let ke = ImageAsset(name: "ke") - internal static let kg = ImageAsset(name: "kg") - internal static let kh = ImageAsset(name: "kh") - internal static let ki = ImageAsset(name: "ki") - internal static let km = ImageAsset(name: "km") - internal static let kn = ImageAsset(name: "kn") - internal static let kp = ImageAsset(name: "kp") - internal static let kr = ImageAsset(name: "kr") - internal static let kw = ImageAsset(name: "kw") - internal static let ky = ImageAsset(name: "ky") - internal static let kz = ImageAsset(name: "kz") - internal static let la = ImageAsset(name: "la") - internal static let lb = ImageAsset(name: "lb") - internal static let lc = ImageAsset(name: "lc") - internal static let li = ImageAsset(name: "li") - internal static let lk = ImageAsset(name: "lk") - internal static let lr = ImageAsset(name: "lr") - internal static let ls = ImageAsset(name: "ls") - internal static let lt = ImageAsset(name: "lt") - internal static let lu = ImageAsset(name: "lu") - internal static let lv = ImageAsset(name: "lv") - internal static let ly = ImageAsset(name: "ly") - internal static let ma = ImageAsset(name: "ma") - internal static let mc = ImageAsset(name: "mc") - internal static let md = ImageAsset(name: "md") - internal static let me = ImageAsset(name: "me") - internal static let mf = ImageAsset(name: "mf") - internal static let mg = ImageAsset(name: "mg") - internal static let mh = ImageAsset(name: "mh") - internal static let mk = ImageAsset(name: "mk") - internal static let ml = ImageAsset(name: "ml") - internal static let mm = ImageAsset(name: "mm") - internal static let mn = ImageAsset(name: "mn") - internal static let mo = ImageAsset(name: "mo") - internal static let mp = ImageAsset(name: "mp") - internal static let mq = ImageAsset(name: "mq") - internal static let mr = ImageAsset(name: "mr") - internal static let ms = ImageAsset(name: "ms") - internal static let mt = ImageAsset(name: "mt") - internal static let mu = ImageAsset(name: "mu") - internal static let mv = ImageAsset(name: "mv") - internal static let mw = ImageAsset(name: "mw") - internal static let mx = ImageAsset(name: "mx") - internal static let my = ImageAsset(name: "my") - internal static let mz = ImageAsset(name: "mz") - internal static let na = ImageAsset(name: "na") - internal static let nc = ImageAsset(name: "nc") - internal static let ne = ImageAsset(name: "ne") - internal static let nf = ImageAsset(name: "nf") - internal static let ng = ImageAsset(name: "ng") - internal static let ni = ImageAsset(name: "ni") - internal static let nl = ImageAsset(name: "nl") - internal static let no = ImageAsset(name: "no") - internal static let np = ImageAsset(name: "np") - internal static let nr = ImageAsset(name: "nr") - internal static let nu = ImageAsset(name: "nu") - internal static let nz = ImageAsset(name: "nz") - internal static let om = ImageAsset(name: "om") - internal static let pa = ImageAsset(name: "pa") - internal static let pe = ImageAsset(name: "pe") - internal static let pf = ImageAsset(name: "pf") - internal static let pg = ImageAsset(name: "pg") - internal static let ph = ImageAsset(name: "ph") - internal static let pk = ImageAsset(name: "pk") - internal static let pl = ImageAsset(name: "pl") - internal static let pm = ImageAsset(name: "pm") - internal static let pn = ImageAsset(name: "pn") - internal static let pr = ImageAsset(name: "pr") - internal static let ps = ImageAsset(name: "ps") - internal static let pt = ImageAsset(name: "pt") - internal static let pw = ImageAsset(name: "pw") - internal static let py = ImageAsset(name: "py") - internal static let qa = ImageAsset(name: "qa") - internal static let re = ImageAsset(name: "re") - internal static let ro = ImageAsset(name: "ro") - internal static let rs = ImageAsset(name: "rs") - internal static let ru = ImageAsset(name: "ru") - internal static let rw = ImageAsset(name: "rw") - internal static let sa = ImageAsset(name: "sa") - internal static let sb = ImageAsset(name: "sb") - internal static let sc = ImageAsset(name: "sc") - internal static let sd = ImageAsset(name: "sd") - internal static let se = ImageAsset(name: "se") - internal static let sg = ImageAsset(name: "sg") - internal static let sh = ImageAsset(name: "sh") - internal static let si = ImageAsset(name: "si") - internal static let sj = ImageAsset(name: "sj") - internal static let sk = ImageAsset(name: "sk") - internal static let sl = ImageAsset(name: "sl") - internal static let sm = ImageAsset(name: "sm") - internal static let sn = ImageAsset(name: "sn") - internal static let so = ImageAsset(name: "so") - internal static let sr = ImageAsset(name: "sr") - internal static let ss = ImageAsset(name: "ss") - internal static let st = ImageAsset(name: "st") - internal static let sv = ImageAsset(name: "sv") - internal static let sx = ImageAsset(name: "sx") - internal static let sy = ImageAsset(name: "sy") - internal static let sz = ImageAsset(name: "sz") - internal static let tc = ImageAsset(name: "tc") - internal static let td = ImageAsset(name: "td") - internal static let tf = ImageAsset(name: "tf") - internal static let tg = ImageAsset(name: "tg") - internal static let th = ImageAsset(name: "th") - internal static let tj = ImageAsset(name: "tj") - internal static let tk = ImageAsset(name: "tk") - internal static let tl = ImageAsset(name: "tl") - internal static let tm = ImageAsset(name: "tm") - internal static let tn = ImageAsset(name: "tn") - internal static let to = ImageAsset(name: "to") - internal static let tr = ImageAsset(name: "tr") - internal static let tt = ImageAsset(name: "tt") - internal static let tv = ImageAsset(name: "tv") - internal static let tw = ImageAsset(name: "tw") - internal static let tz = ImageAsset(name: "tz") - internal static let ua = ImageAsset(name: "ua") - internal static let ug = ImageAsset(name: "ug") - internal static let um = ImageAsset(name: "um") - internal static let un = ImageAsset(name: "un") - internal static let us = ImageAsset(name: "us") - internal static let uy = ImageAsset(name: "uy") - internal static let uz = ImageAsset(name: "uz") - internal static let va = ImageAsset(name: "va") - internal static let vc = ImageAsset(name: "vc") - internal static let ve = ImageAsset(name: "ve") - internal static let vg = ImageAsset(name: "vg") - internal static let vi = ImageAsset(name: "vi") - internal static let vn = ImageAsset(name: "vn") - internal static let vu = ImageAsset(name: "vu") - internal static let wf = ImageAsset(name: "wf") - internal static let ws = ImageAsset(name: "ws") - internal static let xk = ImageAsset(name: "xk") - internal static let ye = ImageAsset(name: "ye") - internal static let yt = ImageAsset(name: "yt") - internal static let za = ImageAsset(name: "za") - internal static let zm = ImageAsset(name: "zm") - internal static let zw = ImageAsset(name: "zw") - } - internal enum Providers { - internal static let csv = ImageAsset(name: "csv") - internal static let hideme = ImageAsset(name: "hideme") - internal static let mullvad = ImageAsset(name: "mullvad") - internal static let nordvpn = ImageAsset(name: "nordvpn") - internal static let oeck = ImageAsset(name: "oeck") - internal static let pia = ImageAsset(name: "pia") - internal static let placeholder = ImageAsset(name: "placeholder") - internal static let protonvpn = ImageAsset(name: "protonvpn") - internal static let surfshark = ImageAsset(name: "surfshark") - internal static let torguard = ImageAsset(name: "torguard") - internal static let tunnelbear = ImageAsset(name: "tunnelbear") - internal static let vyprvpn = ImageAsset(name: "vyprvpn") - internal static let windscribe = ImageAsset(name: "windscribe") - } -} -// swiftlint:enable identifier_name line_length nesting type_body_length type_name - -// MARK: - Implementation Details - -internal struct ImageAsset { - internal fileprivate(set) var name: String - - #if os(macOS) - internal typealias Image = NSImage - #elseif os(iOS) || os(tvOS) || os(watchOS) - internal typealias Image = UIImage - #endif - - internal var image: Image { - let bundle = BundleToken.bundle - #if os(iOS) || os(tvOS) - let image = Image(named: name, in: bundle, compatibleWith: nil) - #elseif os(macOS) - let name = NSImage.Name(self.name) - let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) - #elseif os(watchOS) - let image = Image(named: name) - #endif - guard let result = image else { - fatalError("Unable to load image named \(name).") - } - return result - } -} - -internal extension ImageAsset.Image { - @available(macOS, deprecated, - message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") - convenience init!(asset: ImageAsset) { - #if os(iOS) || os(tvOS) - let bundle = BundleToken.bundle - self.init(named: asset.name, in: bundle, compatibleWith: nil) - #elseif os(macOS) - self.init(named: NSImage.Name(asset.name)) - #elseif os(watchOS) - self.init(named: asset.name) - #endif - } -} - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type diff --git a/Passepartout/App/macOS/Global/SwiftGen+Scenes.swift b/Passepartout/App/macOS/Global/SwiftGen+Scenes.swift deleted file mode 100644 index d2cfbe90..00000000 --- a/Passepartout/App/macOS/Global/SwiftGen+Scenes.swift +++ /dev/null @@ -1,119 +0,0 @@ -// swiftlint:disable all -// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen - -// swiftlint:disable sorted_imports -import Foundation -import AppKit - -// swiftlint:disable superfluous_disable_command -// swiftlint:disable file_length implicit_return - -// MARK: - Storyboard Scenes - -// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name -internal enum StoryboardScene { - internal enum Main: StoryboardType { - internal static let storyboardName = "Main" - - internal static let organizerWindowController = SceneType(storyboard: Main.self, identifier: "OrganizerWindowController") - - internal static let textInputViewController = SceneType(storyboard: Main.self, identifier: "TextInputViewController") - } - internal enum Preferences: StoryboardType { - internal static let storyboardName = "Preferences" - - internal static let initialScene = InitialSceneType(storyboard: Preferences.self) - - internal static let preferencesWindowController = SceneType(storyboard: Preferences.self, identifier: "PreferencesWindowController") - } - internal enum Purchase: StoryboardType { - internal static let storyboardName = "Purchase" - - internal static let initialScene = InitialSceneType(storyboard: Purchase.self) - } - internal enum Service: StoryboardType { - internal static let storyboardName = "Service" - - internal static let initialScene = InitialSceneType(storyboard: Service.self) - - internal static let accountViewController = SceneType(storyboard: Service.self, identifier: "AccountViewController") - - internal static let profileCustomizationContainerViewController = SceneType(storyboard: Service.self, identifier: "ProfileCustomizationContainerViewController") - } -} -// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name - -// MARK: - Implementation Details - -internal protocol StoryboardType { - static var storyboardName: String { get } -} - -internal extension StoryboardType { - static var storyboard: NSStoryboard { - let name = NSStoryboard.Name(self.storyboardName) - return NSStoryboard(name: name, bundle: BundleToken.bundle) - } -} - -internal struct SceneType { - internal let storyboard: StoryboardType.Type - internal let identifier: String - - internal func instantiate() -> T { - let identifier = NSStoryboard.SceneIdentifier(self.identifier) - guard let controller = storyboard.storyboard.instantiateController(withIdentifier: identifier) as? T else { - fatalError("Controller '\(identifier)' is not of the expected class \(T.self).") - } - return controller - } - - @available(macOS 10.15, *) - internal func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController { - return storyboard.storyboard.instantiateController(identifier: identifier, creator: block) - } - - @available(macOS 10.15, *) - internal func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController { - return storyboard.storyboard.instantiateController(identifier: identifier, creator: block) - } -} - -internal struct InitialSceneType { - internal let storyboard: StoryboardType.Type - - internal func instantiate() -> T { - guard let controller = storyboard.storyboard.instantiateInitialController() as? T else { - fatalError("Controller is not of the expected class \(T.self).") - } - return controller - } - - @available(macOS 10.15, *) - internal func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController { - guard let controller = storyboard.storyboard.instantiateInitialController(creator: block) else { - fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") - } - return controller - } - - @available(macOS 10.15, *) - internal func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController { - guard let controller = storyboard.storyboard.instantiateInitialController(creator: block) else { - fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") - } - return controller - } -} - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type diff --git a/Passepartout/App/macOS/Global/SwiftGen+Segues.swift b/Passepartout/App/macOS/Global/SwiftGen+Segues.swift deleted file mode 100644 index 9cff6586..00000000 --- a/Passepartout/App/macOS/Global/SwiftGen+Segues.swift +++ /dev/null @@ -1,47 +0,0 @@ -// swiftlint:disable all -// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen - -// swiftlint:disable sorted_imports -import Foundation -import AppKit - -// swiftlint:disable superfluous_disable_command -// swiftlint:disable file_length - -// MARK: - Storyboard Segues - -// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name -internal enum StoryboardSegue { - internal enum Main: String, SegueType { - case enterAccountSegueIdentifier = "EnterAccountSegueIdentifier" - case renameProfileSegueIdentifier = "RenameProfileSegueIdentifier" - } - internal enum Service: String, SegueType { - case accountSegueIdentifier = "AccountSegueIdentifier" - case customizeSegueIdentifier = "CustomizeSegueIdentifier" - case trustedNetworkAddSegueIdentifier = "TrustedNetworkAddSegueIdentifier" - } -} -// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name - -// MARK: - Implementation Details - -internal protocol SegueType: RawRepresentable {} - -internal extension NSSeguePerforming { - func perform(segue: S, sender: Any? = nil) where S.RawValue == String { - let identifier = NSStoryboardSegue.Identifier(segue.rawValue) - performSegue?(withIdentifier: identifier, sender: sender) - } -} - -internal extension SegueType where RawValue == String { - init?(_ segue: NSStoryboardSegue) { - #if swift(>=4.2) - guard let identifier = segue.identifier else { return nil } - #else - guard let identifier = segue.identifier?.rawValue else { return nil } - #endif - self.init(rawValue: identifier) - } -} diff --git a/Passepartout/App/macOS/Global/TextInputViewController.swift b/Passepartout/App/macOS/Global/TextInputViewController.swift deleted file mode 100644 index f6ccdf56..00000000 --- a/Passepartout/App/macOS/Global/TextInputViewController.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// TextInputViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 7/3/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -protocol TextInputViewControllerDelegate: AnyObject { - func textInputController(_ textInputController: TextInputViewController, shouldEnterText text: String) -> Bool - - func textInputController(_ textInputController: TextInputViewController, didEnterText text: String) -} - -class TextInputViewController: NSViewController { - @IBOutlet private weak var labelTextCaption: NSTextField! - - @IBOutlet private weak var textPlain: NSTextField! - - @IBOutlet private weak var textSecure: NSSecureTextField! - - private var textField: NSTextField { - guard !isSecure else { - return textSecure - } - return textPlain - } - - @IBOutlet private weak var buttonOK: NSButton! - - @IBOutlet private weak var buttonCancel: NSButton! - - var caption = "" - - var text = "" - - var placeholder: String? - - var isSecure = false - - var object: Any? - - weak var delegate: TextInputViewControllerDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - labelTextCaption.stringValue = caption - textField.stringValue = text - textField.placeholderString = placeholder - buttonOK.title = L10n.Global.ok - buttonCancel.title = L10n.Global.cancel - - textPlain.isHidden = isSecure - textSecure.isHidden = !isSecure - } - - @IBAction private func confirm(_ sender: Any?) { - let text = textField.stringValue - if let delegate = delegate { - guard delegate.textInputController(self, shouldEnterText: text) else { - textField.becomeFirstResponder() - return - } - } - delegate?.textInputController(self, didEnterText: text) - } - - override func cancelOperation(_ sender: Any?) { - dismiss(sender) - } -} diff --git a/Passepartout/App/macOS/Global/Theme+Views.swift b/Passepartout/App/macOS/Global/Theme+Views.swift deleted file mode 100644 index 767ccda8..00000000 --- a/Passepartout/App/macOS/Global/Theme+Views.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// Theme+Views.swift -// Passepartout -// -// Created by Davide De Rosa on 7/29/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -extension NSTextField { - func applyVPN(_ theme: Theme, isActive: Bool, with vpnStatus: VPNStatus?, error: OpenVPNProviderError?) { - guard isActive else { - stringValue = L10n.Vpn.unused - textColor = theme.palette.colorSecondaryText - return - } - guard let vpnStatus = vpnStatus else { - stringValue = L10n.Vpn.disabled - textColor = theme.palette.colorSecondaryText - return - } - - switch vpnStatus { - case .connecting: - stringValue = L10n.Vpn.connecting - textColor = theme.palette.colorIndeterminate - - case .connected: - stringValue = L10n.Vpn.active - textColor = theme.palette.colorOn - - case .disconnecting: - stringValue = disconnectionReason(for: error) ?? L10n.Vpn.disconnecting - textColor = theme.palette.colorIndeterminate - - case .disconnected: - stringValue = disconnectionReason(for: error) ?? L10n.Vpn.inactive - textColor = theme.palette.colorOff - } - } - - private func disconnectionReason(for error: OpenVPNProviderError?) -> String? { - guard let error = error else { - return nil - } - let V = L10n.Vpn.Errors.self - switch error { - case .socketActivity, .timeout: - return V.timeout - - case .dnsFailure: - return V.dns - - case .tlsInitialization, .tlsServerVerification, .tlsHandshake: - return V.tls - - case .authentication: - return V.auth - - case .encryptionInitialization, .encryptionData: - return V.encryption - - case .serverCompression, .lzo: - return V.compression - - case .networkChanged: - return V.network - - case .routing: - return V.routing - - case .gatewayUnattainable: - return V.gateway - - default: - return nil - } - } -} diff --git a/Passepartout/App/macOS/Global/Theme.swift b/Passepartout/App/macOS/Global/Theme.swift deleted file mode 100644 index 3e8b2cac..00000000 --- a/Passepartout/App/macOS/Global/Theme.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// Theme.swift -// Passepartout -// -// Created by Davide De Rosa on 7/29/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -struct Theme { - struct Palette { - var colorPrimaryText: NSColor = .labelColor - - var colorSecondaryText: NSColor = .secondaryLabelColor - - var colorOff: NSColor = .red - - var colorOn = NSColor(red: 0.0, green: 0.8, blue: 0.0, alpha: 1.0) - - var colorIndeterminate: NSColor = .orange - - var colorInline = NSColor(red: 0, green: 0.478431, blue: 1, alpha: 1) - } - - static let current = Theme() - - var palette: Palette - - private init() { - palette = Palette() - } -} - -// FIXME: load from index JSON -extension Infrastructure.Metadata { - var logo: NSImage? { - guard let image = ImageAsset.Image(named: name) else { - return Asset.Providers.placeholder.image - } - return image - } -} - -extension ConnectionProfile { - var image: NSImage? { - if let profile = self as? ProviderConnectionProfile { - return profile.infrastructure.metadata?.logo - } else { -// return NSImage(named: NSImage.applicationIconName)//smartBadgeTemplateName) - return nil - } - } -} - -extension PoolGroup { - var logo: NSImage? { - return ImageAsset(name: country.lowercased()).image - } -} - -extension String { - var image: NSImage? { - return ImageAsset(name: lowercased()).image - } -} diff --git a/Passepartout/App/macOS/Global/WindowManager.swift b/Passepartout/App/macOS/Global/WindowManager.swift deleted file mode 100644 index 065ccc52..00000000 --- a/Passepartout/App/macOS/Global/WindowManager.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// WindowManager.swift -// Passepartout -// -// Created by Davide De Rosa on 8/12/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -class WindowManager: NSObject { - static let shared = WindowManager() - - private var organizer: NSWindowController? - - private var preferences: NSWindowController? - - private override init() { - } - - @discardableResult func showOrganizer() -> NSWindowController? { - organizer = presentWindowController(StoryboardScene.Main.organizerWindowController, existing: organizer) - organizer?.window?.title = "Passepartout" - return organizer - } - - @discardableResult func showPreferences() -> NSWindowController? { - preferences = presentWindowController(StoryboardScene.Preferences.preferencesWindowController, existing: preferences) - preferences?.window?.title = L10n.Preferences.title - return preferences - } - - func showAbout() { - NSApp.orderFrontStandardAboutPanel(nil) - NSApp.activate(ignoringOtherApps: true) - } - - // MARK: Helpers - - private func presentWindowController(_ wcScene: SceneType, existing: NSWindowController?) -> NSWindowController? { - var wc: NSWindowController? - if existing == nil { - wc = wcScene.instantiate() - wc?.window?.delegate = self - wc?.window?.center() - wc?.showWindow(nil) - } else { - existing?.window?.makeKeyAndOrderFront(self) - } - NSApp.activate(ignoringOtherApps: true) - return existing ?? wc - } -} - -extension WindowManager: NSWindowDelegate { - func windowWillClose(_ notification: Notification) { - switch notification.object as? NSWindowController { - case organizer: - organizer = nil - -// case preferences: -// preferences = nil - - default: - break - } - } -} diff --git a/Passepartout/App/macOS/Info.plist b/Passepartout/App/macOS/Info.plist deleted file mode 100644 index 24c4a719..00000000 --- a/Passepartout/App/macOS/Info.plist +++ /dev/null @@ -1,75 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDocumentTypes - - - CFBundleTypeExtensions - - ovpn - - CFBundleTypeName - OpenVPN Configuration - CFBundleTypeRole - Viewer - LSHandlerRank - Alternate - - - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.18.0 - CFBundleVersion - 2984 - ITSAppUsesNonExemptEncryption - - LSApplicationCategoryType - public.app-category.utilities - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - LSUIElement - - NSHumanReadableCopyright - $(CFG_COPYRIGHT) - NSMainStoryboardFile - Main - NSPrincipalClass - NSApplication - NSUserActivityTypes - - ConnectVPNIntent - DisableVPNIntent - EnableVPNIntent - MoveToLocationIntent - TrustCellularNetworkIntent - TrustCurrentNetworkIntent - UntrustCellularNetworkIntent - UntrustCurrentNetworkIntent - - com.algoritmico.Passepartout.config - - app_launcher_id - $(CFG_APP_LAUNCHER_ID) - appcenter_secret - $(CFG_APPCENTER_SECRET) - appstore_id - $(CFG_APPSTORE_ID) - group_id - $(CFG_TEAM_ID).group.$(CFG_GROUP_ID) - - - diff --git a/Passepartout/App/macOS/Launcher/AppDelegate.swift b/Passepartout/App/macOS/Launcher/AppDelegate.swift deleted file mode 100644 index f1b78ad3..00000000 --- a/Passepartout/App/macOS/Launcher/AppDelegate.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// AppDelegate.swift -// Passepartout -// -// Created by Davide De Rosa on 12/16/20. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa - -extension Notification.Name { - static let killLauncher = Notification.Name("killLauncher") -} - -@NSApplicationMain -class AppDelegate: NSObject { - @objc func terminate() { - NSApp.terminate(nil) - } -} - -extension AppDelegate: NSApplicationDelegate { - func applicationDidFinishLaunching(_ aNotification: Notification) { - let mainAppIdentifier = "com.algoritmico.ios.Passepartout" // XXX: hardcoded - let runningApps = NSWorkspace.shared.runningApplications - let isRunning = !runningApps.filter { $0.bundleIdentifier == mainAppIdentifier }.isEmpty - - if !isRunning { - DistributedNotificationCenter.default().addObserver(self, selector: #selector(terminate), name: .killLauncher, object: mainAppIdentifier) - - let path = Bundle.main.bundlePath as NSString - var components = path.pathComponents - components.removeLast() - components.removeLast() - components.removeLast() - components.append("MacOS") - components.append("Passepartout") // XXX: hardcoded - - let newPath = NSString.path(withComponents: components) - - NSWorkspace.shared.launchApplication(newPath) - } else { - terminate() - } - } -} diff --git a/Passepartout/App/macOS/Launcher/Assets.xcassets/AppIcon.appiconset/Contents.json b/Passepartout/App/macOS/Launcher/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 3f00db43..00000000 --- a/Passepartout/App/macOS/Launcher/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "images" : [ - { - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Passepartout/App/macOS/Launcher/Base.lproj/Main.storyboard b/Passepartout/App/macOS/Launcher/Base.lproj/Main.storyboard deleted file mode 100644 index bb76ffca..00000000 --- a/Passepartout/App/macOS/Launcher/Base.lproj/Main.storyboard +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/macOS/Launcher/Info.plist b/Passepartout/App/macOS/Launcher/Info.plist deleted file mode 100644 index 9bbcdaef..00000000 --- a/Passepartout/App/macOS/Launcher/Info.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.18.0 - CFBundleVersion - 2984 - ITSAppUsesNonExemptEncryption - - LSApplicationCategoryType - public.app-category.utilities - LSBackgroundOnly - - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(CFG_COPYRIGHT) - NSMainStoryboardFile - Main - NSPrincipalClass - NSApplication - - diff --git a/Passepartout/App/macOS/Launcher/Launcher.entitlements b/Passepartout/App/macOS/Launcher/Launcher.entitlements deleted file mode 100644 index f2ef3ae0..00000000 --- a/Passepartout/App/macOS/Launcher/Launcher.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - - - diff --git a/Passepartout/App/macOS/Menu/MainMenu.xib b/Passepartout/App/macOS/Menu/MainMenu.xib deleted file mode 100644 index 2fcc4300..00000000 --- a/Passepartout/App/macOS/Menu/MainMenu.xib +++ /dev/null @@ -1,679 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/macOS/Menu/StatusMenu.swift b/Passepartout/App/macOS/Menu/StatusMenu.swift deleted file mode 100644 index edd96c5e..00000000 --- a/Passepartout/App/macOS/Menu/StatusMenu.swift +++ /dev/null @@ -1,702 +0,0 @@ -// -// StatusMenu.swift -// Passepartout -// -// Created by Davide De Rosa on 8/14/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import StoreKit -import PassepartoutCore -import Convenience - -class StatusMenu: NSObject { - static let didAddProfile = Notification.Name("didAddProfile") - - static let didRenameProfile = Notification.Name("didRenameProfile") - - static let didRemoveProfile = Notification.Name("didRemoveProfile") - - static let willDeactivateProfile = Notification.Name("willDeactivateProfile") - - static let didActivateProfile = Notification.Name("didActivateProfile") - - static let didUpdateProfile = Notification.Name("didUpdateProfile") - - static let shared = StatusMenu() - - private let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) - - private let service = TransientStore.shared.service - - private var vpn: GracefulVPN { - return GracefulVPN(service: service) - } - - // MARK: Button images - - private let imageStatusActive = Asset.Assets.statusActive.image - - private let imageStatusPending = Asset.Assets.statusPending.image - - // MARK: Menu references - - private let menu = NSMenu() - - private let menuAllProfiles = NSMenu() - - private lazy var itemSwitchProfile = NSMenuItem(title: L10n.Menu.SwitchProfile.title, action: nil, keyEquivalent: "") - - private var itemsAllProfiles: [NSMenuItem] = [] - - private lazy var itemProfileName = NSMenuItem(title: "", action: nil, keyEquivalent: "") - - private var itemsProfile: [NSMenuItem] = [] - - private lazy var itemPool = NSMenuItem(title: "", action: nil, keyEquivalent: "") - - private lazy var itemToggleVPN = NSMenuItem(title: L10n.Service.Cells.Vpn.TurnOn.caption, action: nil, keyEquivalent: "") - - private lazy var itemReconnectVPN = NSMenuItem(title: L10n.Service.Cells.Reconnect.caption, action: #selector(reconnectVPN), keyEquivalent: "") - - private override init() { - super.init() - - let nc = NotificationCenter.default - nc.addObserver(self, selector: #selector(vpnDidUpdate), name: VPN.didChangeStatus, object: nil) - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - func install() { - guard let _ = statusItem.button else { - return - } - setStatusImage(for: .inactive) - - VPN.shared.prepare { - self.rebuild() - self.statusItem.menu = self.menu - self.service.delegate = self - self.reloadVpnStatus() - } - } - - private func rebuild() { - menu.removeAllItems() - - // main actions - - let itemShow = NSMenuItem(title: L10n.Menu.Show.title, action: #selector(showOrganizer), keyEquivalent: "") - let itemPreferences = NSMenuItem(title: L10n.Menu.Preferences.title.asContinuation, action: #selector(showPreferences), keyEquivalent: ",") - itemShow.target = self - itemPreferences.target = self - menu.addItem(itemShow) - menu.addItem(itemPreferences) - menu.addItem(itemSwitchProfile) - reloadProfiles() - menu.addItem(.separator()) - - // active profile - - menu.addItem(itemProfileName) - setActiveProfile(service.activeProfile) - menu.addItem(.separator()) - - // support - - let menuSupport = NSMenu() - let itemCommunity = NSMenuItem(title: L10n.Organizer.Cells.JoinCommunity.caption.asContinuation, action: #selector(joinCommunity), keyEquivalent: "") - let itemDonate = NSMenuItem(title: L10n.Organizer.Cells.Donate.caption, action: nil, keyEquivalent: "") -// let itemGitHubSponsors = NSMenuItem(title: L10n.Organizer.Cells.GithubSponsors.caption.asContinuation, action: #selector(seeGitHubSponsors), keyEquivalent: "") -// let itemTranslate = NSMenuItem(title: L10n.Organizer.Cells.Translate.caption.asContinuation, action: #selector(offerToTranslate), keyEquivalent: "") - - let menuDonate = NSMenu() - ProductManager.shared.listProducts { [weak self] products, error in - self?.addDonations(fromProducts: products ?? [], to: menuDonate) - } - - menuSupport.setSubmenu(menuDonate, for: itemDonate) - let itemFAQ = NSMenuItem(title: L10n.About.Cells.Faq.caption.asContinuation, action: #selector(visitFAQ), keyEquivalent: "") - itemCommunity.target = self -// itemDonate.target = self -// itemGitHubSponsors.target = self -// itemTranslate.target = self - itemFAQ.target = self - menuSupport.addItem(itemDonate) - menuSupport.addItem(itemCommunity) -// menuSupport.addItem(.separator()) -// menuSupport.addItem(itemGitHubSponsors) -// menuSupport.addItem(itemTranslate) - if ProductManager.shared.isEligibleForFeedback() { - let itemReview = NSMenuItem(title: L10n.Organizer.Cells.WriteReview.caption.asContinuation, action: #selector(writeReview), keyEquivalent: "") - itemReview.target = self - menuSupport.addItem(itemReview) - } - menuSupport.addItem(.separator()) - menuSupport.addItem(itemFAQ) - if ProductManager.shared.isEligibleForFeedback() { - let itemReport = NSMenuItem(title: L10n.Service.Cells.ReportIssue.caption.asContinuation, action: #selector(reportConnectivityIssue), keyEquivalent: "") - itemReport.target = self - menuSupport.addItem(itemReport) - } - let itemSupport = NSMenuItem(title: L10n.Menu.Support.title, action: nil, keyEquivalent: "") - menu.setSubmenu(menuSupport, for: itemSupport) - menu.addItem(itemSupport) - - // share - - let menuShare = NSMenu() - let itemTweet = NSMenuItem(title: L10n.About.Cells.ShareTwitter.caption, action: #selector(shareTwitter), keyEquivalent: "") - let itemInvite = NSMenuItem(title: L10n.About.Cells.ShareGeneric.caption.asContinuation, action: #selector(shareGeneric), keyEquivalent: "") - let itemAlternativeTo = NSMenuItem(title: "AlternativeTo".asContinuation, action: #selector(visitAlternativeTo), keyEquivalent: "") - itemTweet.target = self - itemInvite.target = self - itemAlternativeTo.target = self - menuShare.addItem(itemTweet) - menuShare.addItem(itemInvite) - menuShare.addItem(itemAlternativeTo) - let itemShare = NSMenuItem(title: L10n.About.Sections.Share.header, action: nil, keyEquivalent: "") - menu.setSubmenu(menuShare, for: itemShare) - menu.addItem(itemShare) - menu.addItem(.separator()) - - // secondary - - let itemAbout = NSMenuItem(title: L10n.Organizer.Cells.About.caption(GroupConstants.App.name), action: #selector(showAbout), keyEquivalent: "") - let itemQuit = NSMenuItem(title: L10n.Menu.Quit.title(GroupConstants.App.name), action: #selector(quit), keyEquivalent: "q") - itemAbout.target = self - itemQuit.target = self - menu.addItem(itemAbout) - menu.addItem(itemQuit) - } - - private func reloadProfiles() { - for item in itemsAllProfiles { - menuAllProfiles.removeItem(item) - } - itemsAllProfiles.removeAll() - - let sortedProfileKeys = service.allProfileKeys().sorted { - service.screenTitle($0).lowercased() < service.screenTitle($1).lowercased() - } - - for profileKey in sortedProfileKeys { - let title = service.screenTitle(profileKey) - let item = NSMenuItem(title: title, action: #selector(switchActiveProfile(_:)), keyEquivalent: "") - item.representedObject = profileKey - item.target = self - item.state = service.isActiveProfile(profileKey) ? .on : .off - menuAllProfiles.addItem(item) - itemsAllProfiles.append(item) - } - menu.setSubmenu(menuAllProfiles, for: itemSwitchProfile) - } - - func refreshWithCurrentProfile() { - setActiveProfile(service.activeProfile) - } - - func setActiveProfile(_ profile: ConnectionProfile?) { - let startIndex = menu.index(of: itemProfileName) - var i = startIndex + 1 - - for item in itemsProfile { - menu.removeItem(item) - } - itemsProfile.removeAll() - - guard let profile = profile else { - itemProfileName.title = L10n.Menu.ActiveProfile.Title.none -// itemProfileName.image = nil - setStatusImage(for: .inactive) - statusItem.button?.toolTip = nil - return - } - let profileTitle = service.screenTitle(ProfileKey(profile)) - itemProfileName.title = profileTitle -// itemProfileName.image = profile.image - - let needsCredentials = service.needsCredentials(for: profile) - if !needsCredentials { - itemToggleVPN.indentationLevel = 1 - itemReconnectVPN.indentationLevel = 1 - itemToggleVPN.target = self - itemReconnectVPN.target = self - menu.insertItem(itemToggleVPN, at: i) - i += 1 - menu.insertItem(itemReconnectVPN, at: i) - i += 1 - - itemsProfile.append(itemToggleVPN) - itemsProfile.append(itemReconnectVPN) - } else { - let itemMissingCredentials = NSMenuItem(title: L10n.Menu.ActiveProfile.Messages.missingCredentials, action: nil, keyEquivalent: "") - itemMissingCredentials.indentationLevel = 1 - menu.insertItem(itemMissingCredentials, at: i) - i += 1 - itemsProfile.append(itemMissingCredentials) - } - - reloadVpnStatus() - - if !needsCredentials, let providerProfile = profile as? ProviderConnectionProfile { - - // endpoint (port only) - let itemEndpoint = NSMenuItem(title: L10n.Endpoint.title, action: nil, keyEquivalent: "") - itemEndpoint.indentationLevel = 1 - let menuEndpoint = NSMenu() - - // automatic - let itemEndpointAutomatic = NSMenuItem(title: L10n.Endpoint.Cells.AnyProtocol.caption, action: #selector(connectToEndpoint(_:)), keyEquivalent: "") - itemEndpointAutomatic.target = self - if providerProfile.customProtocol == nil { - itemEndpointAutomatic.state = .on - } - menuEndpoint.addItem(itemEndpointAutomatic) - - for proto in profile.protocols { - let item = NSMenuItem(title: proto.description, action: #selector(connectToEndpoint(_:)), keyEquivalent: "") - item.representedObject = proto - item.target = self - if providerProfile.customProtocol == proto { - item.state = .on - } - menuEndpoint.addItem(item) - } - menu.setSubmenu(menuEndpoint, for: itemEndpoint) - menu.insertItem(itemEndpoint, at: i) - i += 1 - itemsProfile.append(itemEndpoint) - - // account - let itemAccount = NSMenuItem(title: L10n.Account.title.asContinuation, action: #selector(editAccountCredentials(_:)), keyEquivalent: "") - menu.insertItem(itemAccount, at: i) - i += 1 - itemAccount.target = self - itemAccount.indentationLevel = 1 - itemsProfile.append(itemAccount) - - // customize - let itemCustomize = NSMenuItem(title: L10n.Menu.ActiveProfile.Items.Customize.title, action: #selector(customizeProfile(_:)), keyEquivalent: "") - menu.insertItem(itemCustomize, at: i) - i += 1 - itemCustomize.target = self - itemCustomize.indentationLevel = 1 - itemsProfile.append(itemCustomize) - - let itemSep1: NSMenuItem = .separator() - menu.insertItem(itemSep1, at: i) - i += 1 - itemsProfile.append(itemSep1) - -// guard poolDescription = providerProfile.pool?.localizedId else { -// fatalError("No pool selected?") -// } - itemPool.title = providerProfile.pool?.localizedId ?? "" - menu.insertItem(itemPool, at: i) - i += 1 - itemsProfile.append(itemPool) - - let infrastructure = providerProfile.infrastructure - for category in infrastructure.categories { - let title = category.name.isEmpty ? L10n.Global.Values.default : category.name.capitalized - let submenu = NSMenu() - let itemCategory = NSMenuItem(title: title, action: nil, keyEquivalent: "") - itemCategory.indentationLevel = 1 - - for group in category.groups.sorted() { - let title = group.localizedCountry - - let itemGroup = NSMenuItem(title: title, action: #selector(connectToGroup(_:)), keyEquivalent: "") - itemGroup.image = group.logo - itemGroup.target = self - itemGroup.representedObject = group - - let submenuGroup = NSMenu() - for pool in group.pools.sortedPools() { - let title = !pool.secondaryId.isEmpty ? pool.secondaryId : L10n.Global.Values.default - let item = NSMenuItem(title: title, action: #selector(connectToPool(_:)), keyEquivalent: "") - if let extraCountry = pool.extraCountries?.first { - item.image = extraCountry.image - } - item.target = self - item.representedObject = pool - submenuGroup.addItem(item) - - if pool.id == providerProfile.poolId { - itemCategory.state = .on - itemGroup.state = .on - item.state = .on - } - } - if submenuGroup.numberOfItems > 1 { - itemGroup.action = nil - itemGroup.submenu = submenuGroup - } - - submenu.addItem(itemGroup) - } - menu.setSubmenu(submenu, for: itemCategory) - menu.insertItem(itemCategory, at: i) - i += 1 - itemsProfile.append(itemCategory) - } - } else { - - // account - let itemAccount = NSMenuItem(title: L10n.Account.title.asContinuation, action: #selector(editAccountCredentials(_:)), keyEquivalent: "") - menu.insertItem(itemAccount, at: i) - i += 1 - itemAccount.target = self - itemAccount.indentationLevel = 1 - itemsProfile.append(itemAccount) - - // customize - let itemCustomize = NSMenuItem(title: L10n.Menu.ActiveProfile.Items.Customize.title, action: #selector(customizeProfile(_:)), keyEquivalent: "") - menu.insertItem(itemCustomize, at: i) - i += 1 - itemCustomize.target = self - itemCustomize.indentationLevel = 1 - itemsProfile.append(itemCustomize) - - let itemSep1: NSMenuItem = .separator() - menu.insertItem(itemSep1, at: i) - i += 1 - itemsProfile.append(itemSep1) - } - } - - // MARK: Actions - - @objc private func showAbout() { - WindowManager.shared.showAbout() - } - - @objc private func showOrganizer() { - WindowManager.shared.showOrganizer() - } - - @objc private func showPreferences() { - WindowManager.shared.showPreferences() - } - - @objc private func switchActiveProfile(_ sender: Any?) { - guard let item = sender as? NSMenuItem else { - return - } - guard let profileKey = item.representedObject as? ProfileKey, let profile = service.profile(withKey: profileKey) else { - return - } - - let wasConnected = (vpn.status == .connected) || (vpn.status == .connecting) - - // XXX: copy/paste from ServiceViewController.activateProfile() - service.activateProfile(profile) - vpn.disconnect(completionHandler: nil) - if wasConnected { - DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) { [weak self] in - self?.vpn.reconnect(completionHandler: nil) - } - } - } - - @objc private func enableVPN() { - vpn.reconnect { _ in - self.reloadVpnStatus() - } - } - - @objc private func disableVPN() { - vpn.disconnect { _ in - self.reloadVpnStatus() - } - } - - @objc private func reconnectVPN() { - vpn.reconnect(completionHandler: nil) - } - - @objc private func connectToGroup(_ sender: Any?) { - guard let item = sender as? NSMenuItem else { - return - } - guard let group = item.representedObject as? PoolGroup else { - return - } - guard let profile = service.activeProfile as? ProviderConnectionProfile else { - return - } - assert(!group.pools.isEmpty) - profile.poolId = group.pools.randomElement()!.id - vpn.reconnect(completionHandler: nil) - - // update menu - setActiveProfile(profile) - } - - @objc private func connectToPool(_ sender: Any?) { - guard let item = sender as? NSMenuItem else { - return - } - guard let pool = item.representedObject as? Pool else { - return - } - guard let profile = service.activeProfile as? ProviderConnectionProfile else { - return - } - profile.poolId = pool.id - vpn.reconnect(completionHandler: nil) - - // update menu - setActiveProfile(profile) - } - - @objc private func connectToEndpoint(_ sender: Any?) { - guard let item = sender as? NSMenuItem else { - return - } - guard let profile = service.activeProfile as? ProviderConnectionProfile else { - return - } - profile.customProtocol = item.representedObject as? EndpointProtocol - vpn.reconnect(completionHandler: nil) - - // update menu - setActiveProfile(profile) - } - - @objc private func editAccountCredentials(_ sender: Any?) { - let organizer = WindowManager.shared.showOrganizer() - guard organizer?.contentViewController?.presentedViewControllers?.isEmpty ?? true else { - return - } - let accountController = StoryboardScene.Service.accountViewController.instantiate() - accountController.profile = service.activeProfile - organizer?.contentViewController?.presentAsSheet(accountController) - } - - @objc private func customizeProfile(_ sender: Any?) { - let organizer = WindowManager.shared.showOrganizer() - guard organizer?.contentViewController?.presentedViewControllers?.isEmpty ?? true else { - return - } - let profileCustomization = StoryboardScene.Service.profileCustomizationContainerViewController.instantiate() - profileCustomization.profile = service.activeProfile - organizer?.contentViewController?.presentAsSheet(profileCustomization) - } - - @objc private func joinCommunity() { - NSWorkspace.shared.open(AppConstants.URLs.subreddit) - } - - @objc private func writeReview() { - let url = Reviewer.urlForReview(withAppId: AppConstants.App.appStoreId) - NSWorkspace.shared.open(url) - } - - @objc private func showDonations() { - NSWorkspace.shared.open(AppConstants.URLs.donate) - } - - @objc private func seeGitHubSponsors() { - NSWorkspace.shared.open(AppConstants.URLs.githubSponsors) - } - - @objc private func offerToTranslate() { - let V = AppConstants.Translations.Email.self - let recipient = V.recipient - let subject = V.subject - let body = V.body(V.template) - - guard let url = URL.mailto(to: recipient, subject: subject, body: body) else { - return - } - NSWorkspace.shared.open(url) - } - - @objc private func visitFAQ() { - NSWorkspace.shared.open(AppConstants.URLs.faq) - } - - @objc private func reportConnectivityIssue() { - let issue = Issue(debugLog: true, profile: TransientStore.shared.service.activeProfile) - IssueReporter.shared.present(withIssue: issue) - } - - @objc private func shareTwitter() { - NSWorkspace.shared.open(AppConstants.URLs.twitterIntent(withMessage: L10n.Share.message)) - } - - @objc private func shareGeneric() { - guard let source = statusItem.button else { - return - } - let message = "\(L10n.Share.message) \(AppConstants.URLs.website)" - let picker = NSSharingServicePicker(items: [message]) - picker.show(relativeTo: source.bounds, of: source, preferredEdge: .minY) - } - - @objc private func visitAlternativeTo() { - NSWorkspace.shared.open(AppConstants.URLs.alternativeTo) - } - - @objc private func quit() { - NSApp.terminate(self) - } - - // MARK: Notifications - - @objc private func vpnDidUpdate() { - reloadVpnStatus() - } - - // MARK: Helpers - - private func addDonations(fromProducts products: [SKProduct], to menu: NSMenu) { - products.sorted { $0.price.decimalValue < $1.price.decimalValue }.forEach { - guard let p = LocalProduct(rawValue: $0.productIdentifier), p.isDonation, let price = $0.localizedPrice else { - return - } - let title = "\($0.localizedTitle) (\(price))" - let item = NSMenuItem(title: title, action: #selector(performDonation(_:)), keyEquivalent: "") - item.target = self - item.representedObject = $0 - menu.addItem(item) - } - } - - @objc private func performDonation(_ item: NSMenuItem) { - guard let product = item.representedObject as? SKProduct else { - return - } - ProductManager.shared.purchase(product) { _, _ in - } - } - - private func reloadVpnStatus() { - if vpn.isEnabled { - itemToggleVPN.title = L10n.Service.Cells.Vpn.TurnOff.caption - itemToggleVPN.action = #selector(disableVPN) - } else { - itemToggleVPN.title = L10n.Service.Cells.Vpn.TurnOn.caption - itemToggleVPN.action = #selector(enableVPN) - } - if let profile = service.activeProfile { - let profileTitle = service.screenTitle(ProfileKey(profile)) - statusItem.button?.toolTip = "\(GroupConstants.App.name)\n\(profileTitle)\n\((vpn.status ?? .disconnected).uiDescription)" - } else { - statusItem.button?.toolTip = nil - } - - switch vpn.status ?? .disconnected { - case .connected: - setStatusImage(for: .active) - - Reviewer.shared.reportEvent() - - case .connecting: - setStatusImage(for: .pending) - - case .disconnected: - setStatusImage(for: .inactive) - - case .disconnecting: - setStatusImage(for: .pending) - } - } -} - -extension StatusMenu: ConnectionServiceDelegate { - func connectionService(didAdd profile: ConnectionProfile) { - TransientStore.shared.serialize(withProfiles: false) // add - reloadProfiles() - - NotificationCenter.default.post(name: StatusMenu.didAddProfile, object: profile) - } - - func connectionService(didRename profile: ConnectionProfile, to newTitle: String) { - TransientStore.shared.serialize(withProfiles: false) // rename - reloadProfiles() - - NotificationCenter.default.post(name: StatusMenu.didRenameProfile, object: profile) - } - - func connectionService(didRemoveProfileWithKey key: ProfileKey) { - TransientStore.shared.serialize(withProfiles: false) // delete - reloadProfiles() - - NotificationCenter.default.post(name: StatusMenu.didRemoveProfile, object: key) - } - - func connectionService(willDeactivate profile: ConnectionProfile) { - TransientStore.shared.serialize(withProfiles: false) // deactivate - reloadProfiles() - setActiveProfile(nil) - - NotificationCenter.default.post(name: StatusMenu.willDeactivateProfile, object: profile) - } - - func connectionService(didActivate profile: ConnectionProfile) { - TransientStore.shared.serialize(withProfiles: false) // activate - reloadProfiles() - setActiveProfile(profile) - - NotificationCenter.default.post(name: StatusMenu.didActivateProfile, object: profile) - } - - func connectionService(didUpdate profile: ConnectionProfile) { - guard let providerProfile = profile as? ProviderConnectionProfile else { - return - } - itemPool.title = providerProfile.pool?.localizedId ?? "" - - NotificationCenter.default.post(name: StatusMenu.didUpdateProfile, object: profile) - } -} - -extension StatusMenu { - fileprivate enum ImageStatus { - case active - - case pending - - case inactive - } - - fileprivate func setStatusImage(for imageStatus: ImageStatus) { - switch imageStatus { - case .active: - statusItem.button?.image = imageStatusActive - statusItem.button?.alphaValue = 1.0 - - case .pending: - statusItem.button?.image = imageStatusPending - statusItem.button?.alphaValue = 1.0 - - case .inactive: - statusItem.button?.image = imageStatusActive - statusItem.button?.alphaValue = 0.5 - } - } -} diff --git a/Passepartout/App/macOS/Providers.xcassets/csv.imageset/Contents.json b/Passepartout/App/macOS/Providers.xcassets/csv.imageset/Contents.json deleted file mode 100644 index 68bb32b9..00000000 --- a/Passepartout/App/macOS/Providers.xcassets/csv.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "csv@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "csv@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Passepartout/App/macOS/Providers.xcassets/csv.imageset/csv@2x.png b/Passepartout/App/macOS/Providers.xcassets/csv.imageset/csv@2x.png deleted file mode 100644 index 1c79287bd13300eb97ba9a1e90c6cf8cca02c16e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3435 zcmV-x4V3bUP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 F^*_8i=R^Pi diff --git a/Passepartout/App/macOS/Providers.xcassets/mullvad.imageset/Contents.json b/Passepartout/App/macOS/Providers.xcassets/mullvad.imageset/Contents.json deleted file mode 100644 index 89ebd272..00000000 --- a/Passepartout/App/macOS/Providers.xcassets/mullvad.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "mullvad@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "mullvad@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Providers.xcassets/mullvad.imageset/mullvad@2x.png b/Passepartout/App/macOS/Providers.xcassets/mullvad.imageset/mullvad@2x.png deleted file mode 100644 index 2ede91d9c0e52ab99a0a2cdbeff572e9f96f4b4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3282 zcmV;@3@!7CP)W1R~)P2!Zg*AOb-GSrd_{n4p4M zVHFo;MOR?){lFLOYIF-lQL%Ja1bjbA)N)0^09CrKwejqSnL7?~lH5rGZk3#>^I@iv zKKD2O)2GkvZh$j(#(tZIh&>Up=db-7r^OKQEJW;$h<(F;*023cr_{pgo{fm-AYy++ zOhClmaSYJ|QjNag^7Kz0DiRZEUUK4)yyT=*BMDN*Ccxm_a{!E9rwkw-3#;2JtZct9 zY%(I+5b?Y=|50K69GcJLpuWo-y7-f#^T&wJUn08rP@&?4Ld6e+ibF)_uPM6Np-{2e zq48_%nlTPsuKob<>ts+l5z&r_JwktN^y-^xq$)aJLn!~8P=SPs zCkYb`L>C6pg<;oZmN_;_H&V^-RVJzlm0J}mjw@80Ai96EZI~|`QjNZ+6V_C>PYiVx zL>z>OLzYf`eR?+GXoW5wq;@qOSpBK(pvVHUY zkNFXCBqE-lHg+OYsJNLhc{m1O2QI!>skqOf%YgYTNm)h`VWNRh@tK42YXDAx+F^Cg zr6^im`;j9@1_jC%Uq5F2?5}55UU@kp4oAd{n$@>dT{L_7eq+i496Dw~oS-qBibI6T z73MS9HJL@2_|&2K?*yBte|H17RyPR|ZQp$Jjha7c{+67=^5b=DZ!1DX=j;34zFb{U#bae^>D&3r~eDi? z;M|n%R5t+;Q}QPTugUb69bdbC^VsI*=G3C{s=M41DlmWId@Pu<0R2-Jo}})?xz#wi z;xe4J;BqWmcmy_aIs37 zcJT_X`%1s7TzP{)#NSobt($gX(cB+B#TA%8Y5qy-jxS$|6J{@qs9U<=a-6YfCC*xM z70#_%g+|FGI7ayJf!i2X^ti#!tUWNB; zuEyPeTaG8*Em1{Ps zSz^W~7Z%M$Q(Ys5x|8NrpQLVi<(0Ve(PjAR%TFO9;ybUb!j^Zk+l6j9kcJQ3#yVWH z%~n-qa=k<2j{s!H^HOuN^&d250HOR}L>C4C0AZp*WfBKv6{5>C8Alh+j;LEWBUG%S z)t!FH3Y@ukC0@091^)BJ8_=}R58R9`@8@+0+;SidSFOltUuh%{vu6#leWWQ-UX}d(Y--Y-)Nm0vu8}#}aVMfixV= z+b?Q_$_i5uhtBWqoXR#=e?s|3(J5mFaOUusfd^+5BJ)kh>e@M2e_b`MUUL~Pue$=D z-o6ZXKD8MU5#Rd9v-rdNGcCim97w}WjaeP2Ecco!6Ux6zPWRHdc~dgDBvc#%07#^H z$tn}yn*mCSedx^LnCXrUKAIc&(8f$`d3P+fd{m4rp9b;7dyDYn*X!^?Wo+UQ2%xD^7d48akt9k`tTqXDwB9YegK;`&c{u40jyu-#X!K1wG{<1 zy<~z9M{@%oyq#jp!C~0)?ig%&cXV8!rtjZ5!oo5o?*IUBP(GPZaR3HppKF=ct{LYM z$`6{qn##m8DwFuh6U!0bSc_jhUV-~IxI6TwHEx_U)j(ISKLp+hSj!4Cc^I;@dxwgb z_$g6$53>xMobIKBisJwP;2htFFmX&}5)VGO1o4fv_|Bc&5^yxQ$H%d8^{7q(|8
  • tHV3>QKY$NiUlbRt>3i0>F;M8kU@#Y(_YR8- zxTB>hYKf`@2vuuE=XY2JCX`=GsC)qcK+*V&=;~ZKvjCgc#t&?IWx0W(*o*sjS}R=5 zdxu$SHKXTo2v{_Mrk!nt_bj{Szl$)@5K>rN9Ss~P^kJquzLak5jZ3|Ff!DxIjaeN6 zH?AIK`K5wT@pk|Kq6>pizTaZuD(&f@;$9P&P;q0d!E1vBU4m{}6H4)tG7s)~IHS`| zN38WYyc7DNT{GI=M*i8ddn?rcqzSB0c}H6THrC{K3EEUU)6G#7dvQ}^)=Bf*N_9TT zsd&!>R;V}(HkXxcAfuN<EjXo7TGH2WH~IkVvvZ(Z#EfnriLT!mja4&9|h`F-XV3GTrf|_qMt` ztPK{l`9tkl=Z;(DNLDh@pipri09ZTTO&MMwRVw!Z0HiQuu+@Oh9FC1u?H979wV`%% z&Qznr)UFK{#GBHk0FljrYb?P603;5{OIDaTM$(N`fY3%+Jb_~=x4prGbEX>T%;DIu z&}+GnS%(!rB@Y#eVTC^(kRgO+U~rD_Lv-;>k&X z1~x9W25Ndqu`iwq{Te{%L{g#Rc;b+}Wb42H;26nkR4SeTh@9qG4&G6^TJ`u+T2ups z4&Oq1TXAmNCS|zKSE%^W{7k5LJ|5_9snl!SZSJnLDptoL=}g6o4xP`A3pQ-M70usd z{&H5Ub$2SfZ-cc;ZSGNr8;--^>f}M&0L||wOdK=Me9eu3_B_+wy=!v)qV@|HVe)W_ zb9{oCg@p2JI|UmuXCFo9JAdx01tm=Wx2@pL9FE)T^STYz^f^=8d4bU7;wW{$gkf$+ z7k~khG$Tu){0no5Qm9zjR`Bk4nP|&n&Sff}1%Ms^p{3KN`4@Bv)-;>$uUDCT)BH@R z+!718q}bQ(EoNH^6DszbsjM=&UeWnZ0E?|;hc-C(974qlgvu?UFN|J@s{$J#1fT}|(8b$PM(y{ULl z&G4TOU|xZcn3C?B+O4pr*{R=4s5okNU;v?w@3!W2pwNdKYw|5CzGs~q8>{j$P}oL? zMdcd~J#@_>d}4UiPW?+y39M;_1Rrm}qur{IXLDYubR3K2E6o_UAbbK%Dt@LdD%y zn-i-}nAoMTfF6lMWrd>ipA_b=J}t1OC8gzMDwMyXGWn*tWB~x!HN&Y=abK4~seHja z`2a8v%H$S8#b?S$-=xzD8?jWQuU+%6Bvc$#skkNj7M{Waj?n!{u~TKD*&3*Li7?S% z%j6@X!w@Fw6(*V$DmK|%o`I(yw3Q`fc!xVQ{}zRbX2L{$>+6RA0CvqFMB}4XU6d#~ zUq+~0p)fhqp-Im`O$>=OE@6BgVd7Jw^JnZCelJ+v!8lFUYrt@ChF$mnkx+3&W%5mh z$>q_vXFD>IZln^%=c!aYPN+bIiu-NpzJk+wqqY;)BPrELa*X6Piq3ZsDn3(~XeN~J zHk%j1L_J}mUZvueaKpcwP;rP*aa5uFdAlZVw7EPk0Bc7j|3_x@vbj9{6NY++J9J~V zP4nHP=zP7R^IH|&cdMfFTOA{Pe^97zp)JEdD#bZA!RmR?89QTV?6+zE16(fj)05Qc QlK=n!07*qoM6N<$f*dSK-T(jq diff --git a/Passepartout/App/macOS/Providers.xcassets/mullvad.imageset/mullvad@3x.png b/Passepartout/App/macOS/Providers.xcassets/mullvad.imageset/mullvad@3x.png deleted file mode 100644 index 4fc64944674e69da9db5157d92d946d032223c4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5168 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 diff --git a/Passepartout/App/macOS/Providers.xcassets/nordvpn.imageset/nordvpn@2x.png b/Passepartout/App/macOS/Providers.xcassets/nordvpn.imageset/nordvpn@2x.png deleted file mode 100644 index a8db1e304d361dabc2f9bae38573d0a22f0db4c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2211 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 diff --git a/Passepartout/App/macOS/Providers.xcassets/oeck.imageset/Contents.json b/Passepartout/App/macOS/Providers.xcassets/oeck.imageset/Contents.json deleted file mode 100644 index e6f2b42a..00000000 --- a/Passepartout/App/macOS/Providers.xcassets/oeck.imageset/Contents.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "oeck@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "oeck-dark@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "oeck@3x.png", - "idiom" : "universal", - "scale" : "3x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "oeck-dark@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Passepartout/App/macOS/Providers.xcassets/oeck.imageset/oeck-dark@2x.png b/Passepartout/App/macOS/Providers.xcassets/oeck.imageset/oeck-dark@2x.png deleted file mode 100644 index ab14971a78337752d32b57d20195cab6e43d97d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1088 zcmV-G1i$-(zu&w-W@8v*L?up9lp0+gEU2hVUm}R23q?;6 z-RL0-iUNrqB8VW6dMbL*AdC{CsRyYD!qU=*R46OESU72>lnyg8&ey{};&7a^=bXLQ z-e-jKy$qba*Z=$cORsOPF{)Zvn%%^Xly zt7mlE5dd|j`lR}AV$o*)Q19xt?D^`K6-w?=KWh4IoqA?BWUp8Mu26QDdVzY5`d`zv zo$5fHvbUN3S)IsV>Uk6Lz1ZTOrFF=zQb#M49aC>^%Gs}uOmgo&_0lS3ulD4Buhgt= zk$Y{6`**ATRmje71h0}G)j2KlugGclsX8OI@Vgbus@|8Ae~x-Mr|mN-WmnZ8`>Q(H zv~yw|gE{SoO)+wtO)Pd*C_1LT<%Cf=r#gi5mOW?;5e!l?ilB1+qufds2ut)w)>SX!gEdP&CNv ziDk#t+Y`G^tapal()7Wi0e(p={6=EOR&_6Fb*<%&huIMSbHY0Q0WOY+0|{exOy2Bd zdE#7ax-AR&!j~k>vjKQ1VZ6fR315^TzQ8NM-4Ss#LA=uBP38JZ#vTG5j))IS#z{(E z50}|3d=FSQ-DDxZhl?--JQfif630zOUJoDcAkH|j7Far+Wg)+Z54IU&3-D+}Y)ep2 z<>dA7Pi?e+3b^)Ek%fHWAKGZYF(SrO6;lU!!kgP@e_g6VrUg#DX}3e2-R(L%VXB?J zkEgi41b9`g^_;mf)a7k1`#w-pOhqea%AQfT1MM7X93wl>-U1wH{5Bhy51a)|JnkF= z_5$01Z-H$QafIoBdVj&iekY8npPL|>0000#&G&6Cs!BA_;bo`k4^{{Vsxy_ur&$(xx zeP%pAxDR*c?6ubZv+g~6@3Z#4Qbk2YMMXtLMMXtLS}b+|J^;fdjg>S)(r`(`B=t(_ zlC)RSHZ$vr7h!WsCp2(^q)Q}SC~1OFB?*O-%i~0Hy=K7NMRe z-M~c+F%AWOOlWfum>WyOrT`buU%=7?frs%`{DQrI$QqTSZ97+EJ|ClUI*^e11 zZ1(`?WzlO$mPR!mk#tfE?L+Zo%Inuj`qT5;A(CF8sI_h2T->0(OneSplCeKCfqQW4 z9UDA+6*HLumIf3oEk6J^0=Yew!!02!0N%q5?;6=c=o=$$V$xHTiV5@r&jLpW?(gxy zBH;T5|MNon74T}og7!rRb&Etr089ejtczhIIev;7osFjNk5Z8=14akwU~=D&1RenP z*2QvZC}#^|C|k#~gvug+4JHDsYVo`g%E{_z62mJ&I+X$72;gztI`=T2Hs09*jF&4= ziNV*BrkmORNdEf-xK+|0X7*0AYY$IhcV_mGTB1kN+_H%*NoMvsFxKbVG&93YlYty~ z(ahFF^4%0O+wOIpuR&m|{A%FxOj(?l4hCNUj_Lr1mEFZ5v5X{a>Kay z3-DDqz8O6tXcSVrq$z_{WXV6H+XDIDE@{4*y&cGYX=vq9R;#p2ntUjTEcs`&?=v+Y zev-7%%-+kjf!VR+ zk4dVkA6zNcFOsrWQm&BnlB5r6Bbg*$ZF0-AjYW*Rnzbz>smrT?C6Zd*5{BGtbDN~@ z+TTXiUdPs6Nt3BPk~T|PFX<~upGaC`X59&GqLM}Ev*jv>VlJ`v#Xh+;l{Y0VGqc|! z$J08%TN%Hri()tM6mW7~%rk)FV(OL}+!;t^IT|8~Zr~x@-tQ^6oANzC**=Z^gFC%t zps)fs7WlIE{vAB4K=UGVtugKpk?&N_omqYHsiZF?EvmhLSJG@V>x~zZC%|m{w)mEX z#xq3jE=sGGtLK4dBrVP%mKbzNnqX$TLOaUirf{#MRXNy+!6-?$6yd04q0Q`oq?;sl zv>r|ru9qZI>>tlUF2fu%>yUJ%-1(pbjcLG4Nh@*F{vhNwo;0)dlCD<1H6TSWQqnR> zqa|Gt%1N#RcQachw*}+ZJna_1Kbd?%CpI!Idqw%xbng9R*07*qoM6N<$f}QOz A^Z)<= diff --git a/Passepartout/App/macOS/Providers.xcassets/oeck.imageset/oeck@2x.png b/Passepartout/App/macOS/Providers.xcassets/oeck.imageset/oeck@2x.png deleted file mode 100644 index 243e3093c21e1bfdaa0c4cabfd79b1bc3ca00349..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1648 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 M07*qoM6N<$f{rY{HUIzs diff --git a/Passepartout/App/macOS/Providers.xcassets/pia.imageset/Contents.json b/Passepartout/App/macOS/Providers.xcassets/pia.imageset/Contents.json deleted file mode 100644 index 8a2923a3..00000000 --- a/Passepartout/App/macOS/Providers.xcassets/pia.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "pia@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "pia@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Providers.xcassets/pia.imageset/pia@2x.png b/Passepartout/App/macOS/Providers.xcassets/pia.imageset/pia@2x.png deleted file mode 100644 index 92ce04c66b72c37510160afa2d1b378e352d0761..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6709 zcmY*ebyO72+Fp=ukd%(4VMz%|=~$2kk!C?c7M5-#R=T@OQX1**4(UaZl5VBz>hIp~ ze&09eyl3Wp=9%Z2cmA1kX2Ra6Dd1wgzybgOxJrt$ntyt+zkz`U03d>Zp%J;WLQVTk@2|Aj#ebpKFX?8F)L!EfkfpfCuXAh!TF4}%029UYw*%+g9!Q&#>z z%l~BJ4DVfB97RDOcXxMgcYbat%o@ZiA|e9f;REsUas6R%IeR*|n0atHI5YlR~ov1P&X~mMVOdHN(=>bjA|Qc6h-sqfRm8k`XYb;aoCq zpJ)D+R*pdwLT1SwYvB@WK=w|3&?*N;nkT3^0gVW$JG?F8V= zOxeznEFs^ilCSw9y%Cn*j-6TFT6*QLL$jIidHrd}R_I#WD`S^8i;{tYY2t$tKC~C-}z@4bU46c2jhV%ea?^fHybxc?!}=k z`POJr)ft%Zfl8W#4dmfO2A28)l(gvx4&y>W=$E`^_c36vz-~dBVV(h2RP)cWO*e}$ zcBO3ZO-`d<3W~ZA<>?u=LCyfN&323S4Q;NRTzHH82-jF%Q62cHG+YyX=SUO#>>|xA z{89ArgqT<0cDVcJ`(pr66h;u-5|Q7S!%DgFLVgrqjH8Q4a4Vd=X_nunT zPx~@Nf67syIuuN~o37?PDWokRR{sR$m8uM7=H_SV5PKv^nG|o{)ZD2<&z3liHrn%X zvl-Kz9Gk@O&PDkmPcy7S&p&hzpExY%l59C#`!q3-+?5YB2uU+*9F;pkTZr>)1qSSI z+uojK(|V#MO%D{f@L=X5QA(5b%gOw5ybgHJs-#tyU+ ze6538 zCEP|8@Si(5)r`2Qi@#U~bb{^}qej}3)no>}Rg4CfFtv3BbTRgW{nFtr=30xG$Vo%_K(fWX zvB$a}k2}BJ+o_|nzP!q>8X`ZE6s}!_?-gW_4$Y2~+Uh`bEWfL4W{-BVU)e0h;-1oU z1D^pIUs|H#rySbe*Y9;jqq{|xdKA1i@LoV9>6gepl4 zEBNEL$!*R~ZzcKGbG0QnxTzOt0|jk2=oL8mtgo`mr=HzPMr+g5FeeAGT0bYst}%!2 z?uDdONW;>F_BSY(-v*kNk$0n5H?!38D0NQF5b1TPQQoK_VA2Lsk}jmb+Xe( z(S#%zr*7FD+h+^ge~TSYy%}R2=V*x_eb*Xb$$9#c7d)e=Gq>o?MD+1v%$tJFxNT@F zHj(0HgS?i}=={+YGzacx{^dyRGF9VM&UcETqm+lh(hNeHE(?TN3v|{ukX6WBt>!p^ zvp2C<)+rlAj&a^3rk!cb8CsUghm)a^>Sp1dAE_`l5FU;NkvTh_)qp(Ul{p9+YLI|r zIr-O_*wWr2a}j^zN*H7a4z2VX9h`W;SdOsPTPv)G}I@$>ChN z(sN)T)f0VP>P(Y1myiB&il_8za}=L5zif6~i0S$gvDdiexdu@(C%(Y=`Jelp1uS*V z#_Adv9sQOzriL6Iu|rvs%Y?7lUCjo|t!X*ybeCv4u-09uW|q@Ki@-z|B47e$*l2KD zn)xrGEn9rZAWj9q(%9t+F&c32DTt~FaW`&ffn)i$IMM@>~tg&-81z0QiH?6BD zcGBnQM}qk?b}P#~w+3Ded|RJQdXJx|k=pkYQ-$K}b_#7oWtj0pr96WA1>}~`#==qS z^>qi??OWdMGg9>Lg#=tM@0QR^GmPAdP%@cYdz~3yF39b_PE`pW z#&J_6zd4fD@nS8zdxxZmSw5AvB1q!Sops0TP5+Lw!9`!SUh2z&4p7(%Yt>?1r&Ez3z?_Lw7?`M!ClTypbHWx3%5 zI@wBfpE ze36KxD4eRnHY%9yp?%?S1~bN|WH3i!O!Sekcyet$1BalTsyYOstaK`(j zUBO`v%=k+l>m*2zor$f2XKs)p?>)owPFFI)&WyU369Ty7efwpK{n+4ff4Gj(1#Knzf>n1Oz(_(f*v^u3y=p8&Vtle_5lJ}KX`nh#*F!_vx;rQdagZ-Pm zfmU(gp>#Som|H<&V&dXPx5e1WgaCsz3M!q0+Tr+hyst&pg`4qL{(H+oqc;8@8V0At zW@^{N$~q<{>dS8S&+%xSe_nW%wza(*gUtJJKU4Y$r{!z7d_@}pGo)y=*Z3`)S_k@U z2?PlcVk%CkSrucx;Ts?0C~lU4#@K+IyQhg>@gk!-aI__!y;EZGCnQ+>RRYN1 zp`^3XJytPwxDj+>4*gBm5tZEfMG7t{WhGX2TIH`qxC{hv(&%mHLG+W{1wWMr`snFA zq;mU{*~pf!?SY1~r(9Gx2i!5(%Tyv$x<4u%nxW-VoGtq*R}vTpeV$}7oH1Ehivm_< z2B%c*Tl`vmTD(U2*k~C0M0csuSc=pPK11sQ);*`ytE{IpMtcujKw184E=BT`-CTVr zxpG`;1k!+`Sw?f$#yFBKNw|~TlyZHyU*NlZNiqt} zdl-(X>Ow+-7?#u>>?2-ry1IYJBHz5>o?$bln&jB|TC zqVBN+tF#t|cgDLI^r$gWJ=x3|?kea>sx%&DNJo5<;OwLNs;xFPy4^`&Mtb$^fs9dX ztKdQCusE}jDqYWqY};{cxtUX%QYy0M0oKf7cb)rN+Qrq_24}YFX4q)QygfR}NNF3} zDHCHoDca_$!6eion$JPJGszY)*?+3ts5fZu{H59(eo?X9ro&f zG7)}t&qqF6ZF4-V|0G|(gj=b*^^+L1t`Bx8y9TF25B~nqI`4RUtS2v40iG&(SV8=X{=HcH^ntVAM5RqP*o?={6 zZ}7z;>&xBhYIGKpvX7~T z^Yy*>s`<(Ct5T-1R#{4Ji7d{zh*ff7G$~b%>8=n2rRaCV<7LgBmhSYow9zK+>YOX-cHN4xDlwm$Y^>D zmJlov*Sx~n2j{hmsrsn1W-lmCr&?gPn@1yf>xeDxy0s?Xb=&Q zJjx^}tAnW5N1{fkWtjHi`)>GF<`zlXvl(nAjtPP;tJfhr1){F?t#LA0bDj|-t7o?6 zecHp%4>2_CJ}hSl7TeKO`BpZd?f4Sd1DPlj^@@ zndRwQU*paR>Cp>(VDVG^bjjpy7fTz?REt4I6brnni&BfY7e{x|vua`E&>4DeXC; z;T(*XLZ8Rne)?<-DQ(Zk0bQ(TX=8eeOJxyf<|%)(lcqj=8?Q4SEXU$R6>x}i`eCk*IN@tS1_ejY~*CVCmJ?21-M+i6*%SagJG?xwZrrBnU zbd9M{A2HzqDlo5~ua&T5S50HUi#_zu zmMMUHG*m8ULs(WzQPBB}zRd5Qo5tPtJdx`3U5CkBhJDEo6R%DR2^r<5e(%k&k;_sO z@weK{Fe`(>pMQ>~%*4{myq|s+vF2#-FmY-*RhFSU^X*Z6CVI-s9=RsLvhdo%fT2%e zDTr?;>TbLVGIm%f#BA`&{A2soGK2!ph?~6h;Gzch~4!?7DN5j zyQN(skMiSZj2w&jQj-~N`(ycP)dX3(otez#BWbAz;SXNe+FBd^NeGVCw zYxj-r6DEypNy%or4-@v^IAQ!^IkFp6*`lpHu4eH1hdMjLb72B7xyLG16(d1mWj7Ss z^i^o-fqJgE)}wzHMxoNet*PR^JyEdU%-A%9#Ud{br0H)x=`ie5TJ_wa&a1`g{h2Au z+UU6cno!I0VouMdD*!1!6Mvi>9x}MsfYhkbkg4KV;+knQrQnFj+Ml5H9Ut1m+2Q(~ zALLuy2XJIkF4Y!CI3@z;rM1{(bd$t%*-+3Itu|s_eaL^FrM>o?_M~q+CxOYm?N(K= z;>lEeZhoHOnkY=Jf*d`WI&VIKMZHKVfjO+`^1k6c0c0(JMSXm5qABT`U;#O>{8MGM z(Dqr`Yt6L8J$B!M*`p7a=?(sChuAx(^S`*$xF9yI+=!lv`ibbw_8>PZmP6Q}JbS1`6(6#xBGx$@9kc)Tjv#YIU*O<^)LIw|1{vox z)osNq#nme}UCizb1bz&Kk``a1pck8hwS(~6_21xVl_Z9O-Y{DAP1BsdA3&~;7l$_Y zlkFMRdp0WI!&~P}*6lVXPK(LeU|3mLIfWUT&^#7}DkdG>(!5^`5Fq5a;d%<*J$%*P zSmkrJU1{$w!AwBdK-%5Ds%YAEI=NeZ{ka&K)=vQh`8B$DTw`Pr$q!jzh6*bga_UBN znnZu2!hG{!GI5NDzYJ*op<^lCoYdk+ddZW1KMsB&d#9-uD@sK)#b zTr+FCCN>iFe{6GETa^A3J`g7C_$X;vWB1&zRY^t}O^Yda=JPhQ1Vgpu$bJyUSJLRV zHao4sU->d)jOk0~HNUa1kg8IA7lvY=EamG1w)n(gWlBd184SJk=6b6HhXf)bH(Nx0 zjl53nSzbYPqpgicxcHlO2Zs%;8;+IprPFEqb@DTggfOl#imw6NjnsvD5g^dL>4_FHHtnhK#9J>-3mZq+A^pw8to0?LqrfOdXInuaoWK0dC^-O*Y6_9lBzfE}Q7tEWMbd>&N=`2@7-0scR#CF zuhq})s;=tZJ5o_z0tp@u9smF!NlA(-|9xuzQ(&P105FAGr{uo@q=k^25CBjghwx?u z^*1IlkyMrg0K6#yfWQy{;N>qAcme>pF#`Z+h5!IC9RR>~%QF03cxe zQ@{Y3**Jf7wX9S%T{Y!ofhG=ij7Fvo#%7G3c8-79006Hi@Nd=5%+-j*)6Ul31?b5~ z_AdtTZ~Y&biHzi56jvKQGEF%}5)lVyGm>wN?2OE0{O}|sB)rb1=0Igp@&9!H8}X4@ zy1F_7nV390JQzLL7#*A~m{_>ExtW+*nOIpF{xTR`yzE_#JQ?g=$p0`DI7H8OT^bLAr=`^V_N*T2Wbh*GW}!zzXtQ~O#g-cO_d*>m+8N+jUQfifJzGh_{u3I zDx~TOw&e$xjk%QaSnhq_vW@K>YF{LjP@;vW=m_Nz&wx9uben4YPKp#~yTzT0jsUm*f{3QL<`E=bm!+SN?d_+tl>J%lajw=~jBN|Y_V~VC zS<#v8bJc#F^UrNrg_@Dx$1CGpaVtMS@e75XoCfi>_sViS-w)$}vJbEOocY@yRQRUx z1H>U1qw@3Nzn{hu6jGJ2YP1Z8V1~vK+baI-PH5E5>P5-$Yis206^1&D!dtNpryWtW z?vd6A`J&BS=H;P5daE;lbE!Q(4KUN}(uORF}LWj3sF@jgW_4&RXsa2;jM@^2x6dDqt zI7x!%N<~*z@S}%2Ag056S6j@j98FBE>`>9>rv$VPD!v7_=hg>J+d zG(NH?@fA0xVYY$f(bB15wTzdcMVN^S@lSLVH24KkKj1T>z&$OWtKyavUVDOQwS@e~ zurLG~qdc(cij=%EF1GBoDq2#+(Nr7~=e#tHU)+BwPC`O*jKIj0Ro_FYcKduR0>(^# zn%Bnd^> zBRsK#n_XC84zug;aclpw9V?L_IvuXPV&`QznX*UvvVS2>1W~*Y1!YM5_?kxb9y12* zw7j}1iG+mY`u5}m53QA5uUSc3VeFmpr{2?e7xTo5#}UD0Eo;;|Zf^C)M2sl+Y`-#P zSfA0`L9y3fx}_zRV}fo9g7CEQ=e<{FZ)d08V#p1N%)`Tdq)wf;_g5Y9(yj+O%4EPPnRjwI>EryS*tlaKPp#2Tp6XQ&`+W{J{ReuuVv`SeSPjz2Z;zu;7KZ`QlhZr z*Io{_+@CxA1jq{|P{auuEILY#^7;`h?tRX@f^*R!ZbIPHPP8WBj(bCJe_p4x>YAHV z*yYI6&I*1wGh&@~*tfw@gc5{$$hXOS89)h?PQZ&C()L^M;?(k@4bauHQZg=si+s~%(XUCDnO zqs?())sdbl1Iwk}qo3)-v9y+psq-_(P1DvH@89(mrG`miZn*(L~po=ylzT2Y0l1m^fl)`SnfQZLvq5HFZ=}-NfiS zFr|J;(yVgxU2U&W+H=>=%7zbPq+lb03Bh7c$jm(SqwnzmgPsqvij}y?j9uZ!;^cgD zVBl9D)IcwSAuc|tn#e2ZOvw}}cj<2u6k7VCfHMJ z2|cQN_XmHs<3Sz+QSxPpa`qv-YFlJy?fT%&IwX=ASPbrwk{OH2}tq8r1> z9y=8KpFqoxHpD?;@0S@1Tk1;MjN))Ndgv;^6pMq0==y2!Z0>2vU9Dn$Y5KabT~o>N zH@x#q!QyqC7T-ITnqRbPY3SC##j}^E?KlhuPIaVT_*9S1Qb^bG2!M%BoL5p<`s<$JrxvYRBj)np+BB}+NQ+Xj# zUII*!g^4L{eSO{d1?gB4Gj3o)Iuy&Z)?{MXa%E-G_$<#kHjo)ZP}4V{y7PHzCBj)_wUK; zs;Xe_j`!MVDjetP!NbMH#k{}2FVzf25;|`xj?}5d&)ai9^pG2)ncN25dTC{_SC=L- zmYl^BC~8%xa=YdL2)67`RF&0<$y;sz_EvD$c&Igl z5Urkd79q?pK3o`m3y_7R4g~lotLwXZH5it1JP}Tm^HI$oN~B8Empkc8F1;s& zAG(B)#8KZh+SDwt#W71XheGF!YY)Zi#6L}Hfl$q4$^7uW^P`m`mP^4+>1yI-c==jw zGei(>hb{hLs=%B z1A-WQQi7!KyB#Nmayo90Vuh;RgJR;nS(K?}Z65BRMH*|gXwqp68M@0*Byk#l{}C5! z2UECbKfc`$$qde)--g*jITkcDVjd)aYV1-R$jEuvx3{v?cm)@*J{Gr&i@RrXrwn7p z2AFOybpkt=9vwa!p*+?x7CuJP$BTRSyMF1FJPpiQ(0h7Rt)-~v>Ji5$$IMuR#VN$k zxTMZpjWbQk;K$Ez78!C$g6O?8cB6*AYow;FoRwAz;Hanp5jeDQ(;P8Xm#wDXTA_z* z`i3C$q{t2`GE-`r6fI7~r>pfQc}6jL^*TJ6mn4J}+!72;hjTz6D=efJ($aLw;IFe` zSZ?c*A}Pa?IUkF6zZ-$CDR=Z&h+=RE#R!&8eS9@lVI2^DLe>K7H(<}FQJ%*z2e|S$ z9|!1SW#pB&9-2IE zTw@bmg9S}RR}@V8#s`>(kZZ|1xbZ7y3{DzEw!0tOf$nKhU!ltRqZ(*>j~L=l1dR2)Q>`Z#Cs?^aw9C^3AR zWPKqNs1G;h(&!=GT{~M}Jmc2y-Dz)A7r1+z7Dq=imV%m%9pK?p$Q_gNOOvw=?n*dE zefZTCV(-9+(92S2LB+kji(|hO*l|r(PO5tZ<_*k2Mdice47#~3e$t%wZ>(yblyw>+ zaY=t$YimYxQxy( zi}9SCV|u`%3 zQ})V}&`DouEQ-`9Ds{a}{SnSvyCzZDc3eX?PQz{;zKFo-B7QK?(2!oNLW6_Z*x-z* zsj4bQbZ^8T-8=6Wf-Uxxy^*bVx?sn+C`85cksFQg)UUxen*jx~55V zMxix^0X0n=JYmed*$8cft{LKCjE(zPCMA2syLdWo@Z-W?H*zW%{k?pmi;uQJ}k zF^I@jQN|0q)JmJCjbmCc=Hdt@7-zm6xP*D=Ze?d6G0=m=U0~Kq|Tqh9MaT6VnF;5(O5x{@q;9FKHOq zP1}s0vj&!OYZWfydu&6@sjV%;=%x#%~ zitDa6*_;fyLSV3UvU4h5k!C9ROJt&d&eq1S0s4{DHey>BN@^Y4cSwk4F@LW8>^V)8ti0DppqXKM4s+}!nX^V#eI}u?G z!AypQWlCV&)6+RjmV}7b#F&n<`#u`I^ZSV~Jl_b9Ar@Y|zEZj+VbTM&vP3`l$#9W# zu7c(Y8cB+il_r?;P>rLkP`1%In_{uNEnA9+!Jl~{ER;PeB;ADHAL2gt1O)dj3hYb%VsapQa0{zO%0Z_3=}cKmN{ z%%HuASRTe)>UAJ_evTy}=P`s3%<5g}S&knE#p7D9&&H?XAObMiHt)BerCi3~D6ICZ zM71RwhjT7f5QTn$ac~fvCG((TL{x2Wr#`903F2|YuHdV8U*zs|2BkP`H~+xeJy+sC z0zZ6{g{1$^#yk z0w8{u6N$l#S=2P4#Q*I;<<6eL_^#pto#+-`!jGk)u`y-7R!`n2&G@doV-CbBJXffq zS^Kk`i3x;QxI#Fm(C#^2*uKf=$2k0aIvj6qVj`j^Zr3LA)9|i67WZ9SgA3h!B^|B! zFt5*3Z{nDH)6P4$lH<#)%=uY^4)w3QtO#^%%xU)fo3qZz(llO^)+;7?`*u`>@& zUzcNYp5b!7|ecLK)T{j zH=8+|0Y-{>>@EB$C_OJh^BW5eO8hl=$K;5zMuXgE+1x`a(nScg2#9=Tt?-AQs0rbN z@@_tOpMIl*JUzs2Pa<&a>zXaL88(EkvC^;l=mb^N>ZtdQ%x}Af52r#j%%CMNaq*Ic z2$LoBZmZRmyX6)Kdl~+47b22;sHTF=8*aDEX>Cjt7>-SdTEaU*|08DPy3(&pd6HBB zqI_6(Q=9$e2m(vBPgS*|=O!p#Ml35LyOs?Q(f;(qHLe&ZezjLi@T%Arx`{nHG`vGK zs%pF;XI5}W@XuXAPwZR-zbuR5l}FwNi!@P&3iZK2RJi^^%n0<@XRFLCMT~`5A-Y!|M=tk&w)hXe5Mb`@aJDMt1Uz9APYZ z6=?kUz|QQ?_|f6Rh>iydCzZH*k}$wxeWw&NZ`!RxsaiI15Bz>-tuc1+mh$pUp5L7K z9`Fvhv_KcY^S^$tGul#cHKhLH2_c5vq3<~8bYfZ0!_hc%5+ihINpan-yS!U z*($MqB3hKPPDWLp6-REe70KsqTiizjeF%+cNJqqXfpoC+RAqnT6-wlfJs7q&^NZt2 z^^a~cR6k6JQ)VRj#GLVl0#}^cC-5=*qSnH+1V-~)tQ|wCAUfpWr0kBEDn3xuEkUG- z{v2dw-_M(wED#yB*2o>q8NtVRo5zNwM7phcj`W~xqd-S!FCd$x%{gy#S*sj3C$_-X z2)Vnvm}HwZ9|Zk~w4Di7Kdl_WGqq9o~;@{zfu zplWc*blsbzlTBz&_LVWzmPC+?U@)LHfa3kNNUiC*E0hc~L=2Bh9Xfw`IMt4Iy@Slb z*Pqn8znflD`0G1*H`-=3G(>-1>7n6yBd^i+H|3!Z-H_A* za8Y?WNgt|K>MYK}VIptkHWIZ)PTw|K2Z^GnVJVrW$93B3Xn68nJ()=yx z@rLNNN=oR6pf3^1V{bGX-j{I8LTGl_K^6AKSUCu*#AcSW$#-!muyB8nFu7MQBt;uE2gj1rCj|% zRc3KJa^&fCk7XW1M#GDWgF$p~P(i2uL%i_j24oMRK^CZjLI@PSyet`(GiGmp<{f7`dQYsGYR`-fKl2)ptuc&no`Sgekil*L zprTro#UbbI&S?L8u>Hhc{ryCbUbuvh<5D>wHh>?$Iz|xE;_ zB6W(_4vu-Jh=MMr7oH7Qg4X^p>`uwbaRIIoj_%ZksPCJEaPytuF1f0fCXHZz{SDZV zI2Wkv_9PTrzbJxWPs3i1bx2R4e_* z)krf8I)Dk$G(P`y+vYVwka&q1cmsP@fako_KtZ20y%eaES@MtU{MzbVKQiU|}ep2B<6dd;&tO)m*8)TY}n^hP`8ASP5_MU~| zT>k*EGtJdloE8s+DT4@%OG6D>N)C;6y_n2_of2H@&O~KRhqQ0*->+jKcl~6FFhFxr z@N{^-d?l2faf}Ca4pm(oQ1Pzp$f!XQy7t+q%0lVOAm7X95q9wQ*+cu}8pEnm&LOMV z_bQ-`^`Fx$m%{e!n8tLqVH-T;K=QlzC=oFI8j}1VuHS_5n%udrX8FGeE|s6}y}5mu zd~TF&#Nc^6+XP%I8hc&zeDq%9+V0%FGwfhFDHd?$fSTXNirH;23s=P)f0sqQf!EF~ zz>p)N4DbHH4MV*8j#T(7BGB|sF<$oYz23Ybog_!D@}s_jo^b)&cYxY^&8i2 z2BLxdP`?u<7y@AzCCbP6+s8o&(_R9EBa5?aL_T#_;3098CpYJ+6KV?L9|$ubuG8hP zDA1vJ-*1(u*8+DQi<8lvo(IE}oqVY7GTQ`(`^_f_0OT>K$LHb+F2%(?IHYPd$`q?- zme@!8%5tz&HT`TllF@5kj#Gmk+m+eKF*apDL7zSKTBgC3{z$DyFcZZZAWqLrE89tZ zyUf)`iMpQf!c8=*N#A}OLpH(c4B7hKbf?qb-d3RZ5Qi{fL;&+|(=g^cV<8+%YPf-Fqw+^HvQT(dly~to^ za|scy8x1)4*f39^p6=c`&dDE>niTmTZ%s;J(7STWKuIx%`m;6Jq8rK@BKIBlHygu>bEC?lnVe2FySFLcsLWrry zTsb0cy~Mi&?k^B6DDw05WWH0Jp+KOauZPofEB3JEx-Ul}#{&+&n+K89?vak8%*!!R z{Cp&tnK**2ngLGr^*i3KFEnIndutCN{4sQp94 zrszWH$s;=1HXFR2$v7j?`YT02ANQI^1TJ|;mhPa|^|xTLlc-I2KRP=zs>rdho(KPj zha00%c>H)%LamXng6vpKy5^jkH^VZVJrN@#wEQj}h8#@@(Ix@a7PN>@aTmg9v_6wox%Y~>2s#as@24y_Y5OOjFAnoF8N=pg!cT;|)^ zWF%nxGGA+u7zbZ5-KN36-HV3n;Oq*Qv+p_@24lG*_;7*BDV-e+s+ne(;O=o|`V7kM zw~%*4-0Nl4OWH?P8gn26E^UhCE)qqTr4@zi>ergzIk^P$ZbJAfGVc8Olhv&=58gx5 z?BQT5C2k7%d(cu(IL^~~-)hG*HTtfvqFO8bZXF*-RDr0vE0X$_CXgw!8~nqUQIueL zSHxy0N1FcU2flws(2$LnYR^N*H~+~RtW($Bvt^#GF2Bi7W;}I$G|`dVqQ#-1(C$Zc zxER$gx7^6BJC6;A*lcF3({9FXfrF$K(xXihpJkYDtpfp0m`5HM5RqtEV3+AX#U3rm z9ppO8W~y=dY}?N6udVDKYQryE*_>%UdA7Rqu#v8*7?(}cXBknmL>8W=T5$qbLH%JS zNi!+MH^0N00~*WAFCqtW9q)ge8GY|6H=+O#n?ueZI`YVrgkGE45^>;Bj**&iE_t+# zJ(z_o9QkHeF2)L=%h$e-)L@|2f6!Q#f}=c&qREX@?#k_oM~%sRRc#$IGfQhJDhH*bv(}*fHVtU~E;E(&4{Zsu8Z>KUK8mxqVJ_sk8`Szq;!XbTO&K6FOBI4gG zn%GC=*jx(KP4abpifetT*o76j3fs`H!AyCl66c3#F@uoOeCF zKG2-rsWH1ztWl5fE|a;Bo0Q4OaBs|LckSLQUYftjxsHUO;=DP&zV+az@ zMySqHPd@>QVWNJq=pwx-gx}S$KwVVWGLACjP{I#(4s+k2 zf(gVDu!IW846>~aYRgI_A>^`*)oflLkey$DT>Q0&sHvyiC%t7P^3#|Pt{r_0>@XYB zJDiFA28%kk*x+iZ`!L5A%w$l7WEB`Ci+k-P**?X?@|Ku|pkhcD>cerG=QssY`YKhu zL$`pSDN>&$Pg=}00T_XL!hlcci zATflyqF8k&XB8#X?rnAGk-Hq!ea!*9qX;2 z;6iziZB3ohg|J-_sleqt<98F1~=J^}n$V z@1^q|%veJf52GwMt$={s!QM9i@I&n@te{OYs_;@p%9^KLOnkgs!hGjkZHYN>ixw20UR(YQC5)DTc<5L5 zC}rD^>hZ2Qon>s_cGT?&*+pbIScLFbI`>re3<6k)_S{e8!)NgsH8feS>unX;OqNfq z)VDnAANy2GJqI;E&ciNVQG@Jr5@;@Vv$CXWkhPpFW-F(NwL}Zp`U5=7ogvaTO9!1I z)FfUEt0#&VD?^)0+C*nE^q)*jGk(`osefz`w06V|5Oh6w__Yoe++6?%U0Qd$oZJ1_ z-$iv5lnUE_6+c}$tt4uy;vh9HS(9=O+AJ5P|ErV?k1Bg=uUFWo>HXl4=t+;yz*65$ z;&O<^y_Ye9Q^GbnIrW6e-i5}QOgDYGXGq=-)WEq~R_eFwEhU_pOLxyg4JK8nS=hpg z5>Gd77CfatTDp}?)7{*aQG(1LY@6c6LZ;Nm4@k^w!v$Ywtr?6{FkH2J(O>?|mjz|{ z(@MT=mSc$9+zc*ASz^`$ho)MSB!%n-S@y}bQF3&H_eW?KpS8fOQ`DL$J$ER| z+WI$-p@1)8YNz-yww2uugHUuswH$4_(toU!-Wdg_4of%YZ^TN2`t ze(loVVl`lR2b0lwwV0a+T1gfy|djngSmUmqMEm!b9DD6O9+O@YsTdvZs zKU5_NeMe)qeKH9(**-qBhxv<%6vL0(E_=EF!R(pNA&y$Kav8}KMlaDS_u?6z4Zf0X z?nt?N4`_{bxE$8v_;>%+$)3UGC@rPpU;q`0RKw7>6^2YA!`=ISSx||u}D6(u>MR*jg+op^Sh8m2j2ura| zOWH#|07dP#af+>C0&&zKK!`!fKV`pSmOhA^+ird@8%B9y`^axql)I<4bdP8R=fKx} zCeh!VQmFZPS5@enl6 zGw#3$s;>1twH2t1swAevO!z)XRMhU&ksybD-X~1oiSwh1kXhJ%zjX!Dy0oiNcc#(M zOyUJ{U1}3PZX;iD3jgs&(pG)`wf3YRMY?|3+>x+q|T_N$)EDcOV|yW`0A=}EFnE+&%{owG8jzv&ZM zs$?|x?sn824YN4ss3eTjplXg0{YyGm#qYjBUW!h|i{}$rxiCvQN11Bas+bU|8!3z5 z8*fGNMc=D(HanL@2?Q2}Nt73QZ#sC`TaWR%pc*~QM>XLR!QK(;UGrVv`-78uC|pn5 z=_s4yR5F}Ow!++stqmRdi(Z(t$cu-%R9Q&6~9mmU|XdAiFxXcdoF`a>jr(UeJws zwpFE(eFbozNlv}>d3b+Px#Zdy8(?kUWf5BTm%qpoCqy=YQ}#7#ds?1 diff --git a/Passepartout/App/macOS/Providers.xcassets/placeholder.imageset/placeholder@3x.png b/Passepartout/App/macOS/Providers.xcassets/placeholder.imageset/placeholder@3x.png deleted file mode 100644 index 1dc8aed71d220bc60830ad8a04efaa44ce362982..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3468 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 diff --git a/Passepartout/App/macOS/Providers.xcassets/protonvpn.imageset/Contents.json b/Passepartout/App/macOS/Providers.xcassets/protonvpn.imageset/Contents.json deleted file mode 100644 index 148aaa0a..00000000 --- a/Passepartout/App/macOS/Providers.xcassets/protonvpn.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "protonvpn@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "protonvpn@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Providers.xcassets/protonvpn.imageset/protonvpn@2x.png b/Passepartout/App/macOS/Providers.xcassets/protonvpn.imageset/protonvpn@2x.png deleted file mode 100644 index f5b035227f227d92da6d12d6baa8a430913afb4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3179 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 diff --git a/Passepartout/App/macOS/Providers.xcassets/surfshark.imageset/Contents.json b/Passepartout/App/macOS/Providers.xcassets/surfshark.imageset/Contents.json deleted file mode 100644 index 901b3e5a..00000000 --- a/Passepartout/App/macOS/Providers.xcassets/surfshark.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "surfshark@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "surfshark@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Passepartout/App/macOS/Providers.xcassets/surfshark.imageset/surfshark@2x.png b/Passepartout/App/macOS/Providers.xcassets/surfshark.imageset/surfshark@2x.png deleted file mode 100644 index 701f3a1fed5e2c31324baf28b5b37c758b4eff90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2422 zcmZWr2{hDQ8~@Lyv2R(&GDu{5$3BK=7(`i;$=HWkvJ=W$X2zB+l~A%q5}A=cV`qv| zDQi?JOA1Anw>L@X9p87p_dW0T-RIodRT7a5fcX6YKuZCDO>T;|005^^0PvZ} zRb2%DsfgmXW4j0O_#d&w0l#*wyz^EbH?t?&&cS-mM;=+YjKKwkuqkc|6dvj53L3l- zQ^bWY*x|6Q0h3>ffIw0QWekjX3X|YI37h9r%0P(9ZL2bu7{pbHD5ojxI=m4R%6Q+js1rq8wLzs|^JZ`Zkk3hLgd~jSD*I2+ zf2$X{!}6fbm6jC>quHq9cKW!>H!o0SW?klR#05A(P^3EUzR!dNR!M1je4qGIQ0V*n2_CJ@H!scM z@veQO4rBBCm3>b7Xp8_fuzs;N<2$p#rtH<% z<5Ug*b=!#$ul6)Qf!t&IkWu{|D@TUq{rRDm8yJUu}&HBh~DdCOu zvS-`MPrwR`pW!Jm=_7-BhK0NkAG^oB`x;Xz_v7B$hVAfcy5;{|37UB#?Azh3O@+c- zYXpp!eqKvzp~KhezTcSUM~vUoSQ|bkgFlzJEbsVTnCILmh?9X5R58)+FD;{E=uLjC zkWkO6V7z7;)%XxLnM7r$k>{{V#E^TY|7ni zYA8bip|PoLxp`WaX)P7>`qY_?zz*l-*{TspoyPmtJNY%9Fgtr8EoF5N(7&WcDqAR` zp5)T!vQ`^a0d_K+0|z1;zu^SX9Dbc~7PET)B}K%R=2?F2(pcGW>l+3fhoAA=JtvS7 zO~$(YH`?p-KP0JcG&c$p43fZ`mp5Z;I<;5cj%6H} z>^znCK;>y%r`_8wnd(d_p;x*)Gjl^PYu-!u6u2H7j9J--_rCUW&h;0vQomWUq)bI! zuo$_R_SD$YTJ7?Al5@YMd6SoE_Z``LTAyn*7Q%u*E}AwswpJLiz6`w(=x*KUn%~ZX zm?uX{a?q-^9`R3#UQE}o>F~Cz;s<+55P?2;>x3Q;qcN5e^<#`B{?oyvjPBeVDTLJ^ zU}1Tchx;HejR5m6IC>KHpstS7;<+)Wp_2>EXSd}rQ|YyZMv@gO>S~~URh7bWkR}WC zf+D-o%U=Dm8`b-$nvUUOI@ z`fpxy>EYhfHu3CN6^Zh(JPUn|7ek1rjm#PfA^e>E;H_!@${>HEp4W?3a+|@5Cz*mX zE!BiqqSJvX7JhrZ3TML8re2A?M@a4s;XTZY&p*Bm31PAx>ZZ+nGLW+Ei;E(rWRYg8 z^o7`}592>zrw{NiUaivsXD{Wjv(!Y;FApZE4P0)-ZERaG*Cz6lZtz7hkBl24CtH<2 z7~N`QzYQ!*i9F2gMRae{ z3Xch?96h2(9~h7jKuEL@g}rGypl;{|n%A0|yYaz30(6grOv_B_4@|T;?2eet74Qnc z0&KBNdc0=2ayIU*KS88HhphOV0zFOtYBEHEC#&<9Cn#!u>M4M+L$6N|V8$h{3-oAI zS~JaxQ^%2}VfYGA9{xkl9L8#eesET^Q=@AU$fuva0}AI?D!@qJmC^nkq0L7CK_0DR z-0wV!qOkzh?9?dOixRN1+{j1++emog=-m=)*`R6ks^*YIG8xnh&PyGJdnWY@ca+a^ zW@Fc)rDZy9e^g%$Hk?o^sIfQ_ENN5P-dWxSo=vGPg`B&WPA_zyBu#+_K(;h-eg)(2 zC_@1YPlc0vPgGpqLnGjqYVyw5(A4POS~#Ckj^T9Z^u7qms{kh;EXZ9ZSjR!Uyt>q9 z&>pOQD&cx9)O~+kvW===+fS6ehIcQeHFPefOMN+s9&_n6zYNL#_^^9CmXpF82Mh~rtLSaxS zslw8ve+WXu$$`|k{}rGy1_u8TobS^S;tE7}D^OwssTiy9knqUk5q=~JpeuYGY{vz~ z{sOJJ3@0iziV_Xzqw8C`b}ugd7wQ}sO9>(10z)ZbQGwxMfB_11=m#V1|2gje;JjNW Qt#UblowXy5X-Q=K36fO=iU0rr diff --git a/Passepartout/App/macOS/Providers.xcassets/surfshark.imageset/surfshark@3x.png b/Passepartout/App/macOS/Providers.xcassets/surfshark.imageset/surfshark@3x.png deleted file mode 100644 index 62c832c1b203aa50e7f3d00c374299954f5c7bc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3591 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 diff --git a/Passepartout/App/macOS/Providers.xcassets/torguard.imageset/Contents.json b/Passepartout/App/macOS/Providers.xcassets/torguard.imageset/Contents.json deleted file mode 100644 index 69d95d66..00000000 --- a/Passepartout/App/macOS/Providers.xcassets/torguard.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "torguard@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "torguard@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Passepartout/App/macOS/Providers.xcassets/torguard.imageset/torguard@2x.png b/Passepartout/App/macOS/Providers.xcassets/torguard.imageset/torguard@2x.png deleted file mode 100644 index 32710a2d7e55a9bbceab654d0b2ba069e6cdf1d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4448 zcmV-m5ufgfP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 diff --git a/Passepartout/App/macOS/Providers.xcassets/tunnelbear.imageset/tunnelbear@3x.png b/Passepartout/App/macOS/Providers.xcassets/tunnelbear.imageset/tunnelbear@3x.png deleted file mode 100644 index 973baf5ced89f9a8d94501d2f194a12f45f4b1b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4755 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 diff --git a/Passepartout/App/macOS/Providers.xcassets/vyprvpn.imageset/Contents.json b/Passepartout/App/macOS/Providers.xcassets/vyprvpn.imageset/Contents.json deleted file mode 100644 index 34292c4b..00000000 --- a/Passepartout/App/macOS/Providers.xcassets/vyprvpn.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "vyprvpn@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "vyprvpn@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Passepartout/App/macOS/Providers.xcassets/vyprvpn.imageset/vyprvpn@2x.png b/Passepartout/App/macOS/Providers.xcassets/vyprvpn.imageset/vyprvpn@2x.png deleted file mode 100644 index 07db8402ca4c3e0974c55426264d695296b681cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3955 zcmZWsXH*kgv^o|lj2{$01K|nw$5u@}Xh*W7(V~`>VTm%I)a#31n z0z#-p;nI=bt8}T6%Ny5vf8KgC>&#xWXZD;q=j{D`-$}5vFyTBebRGl(aiUF8)_~Rh z(^$^|SMA?F`~U-XH!wExlY1+2;)c{5!k`Opxbx#XIrmywO?q6F8$`rlY=Q0&&G=Xem$HD8>Ch-c+$O3jNW=(L&$ zcRa42aCh7cUrv?a2Ukl(E?&)WtEWtcgqI$Kr5?y6_`_pB!OS%*h$Fl#`#AT{bofdM zUux30CD(wX*M}rat}UnKm{8)S)&**f=?{L_7fo~5uRS>tIb2dS**2Wd_(PK+59iHgN@YrU?QYic)bOZ;)h)F!xV(x$uA^as96X!Wb?Hw9w zZwzBBudhpHV+ts;I2>+ZV8HVB?f4>m=&u>5Y|&7fg}Z5C+AS>BcOr1u_hfGvh)2UO zR901$1+Li~A0Pj#${9~Hfa$mTM;xmR4i0AV*VNaO)gBFP4c+l3Vx*WJczDcr>ukDp zrAZD95A!9USkFa!jMpUKM;XFn+?(FStnyU}O#Zy;G?c&GPxb zxVTmd_Cs65Z!X`r=1>iJDFUZiM)x6~<6_~I;AxGi(3_gM7BhPxu~VD;&G#&Sj+D(k8}=IcWKiSa zWp#T2a>}@CQRd%pXFMY5c6y9%%Yq*}78_e2jZA|xv3@5UE=`%b42NVL(hZ%gpy{!* zmThYvoAhPA@R9l)9UYhJQak1hGI6upnwQi)UcZ0OM(FS#s@h%Zao-rfzrQijv6<&< zsey$pXZmQJ#ZMS#N_vd7Q`bMM__OYdJj>7tNZn(_urGcm({r@^6{pg}ce<{e6(9Gi zen+8PAQiC{_siJ)V=q}(SJz$N_~wmai9@aRz|c@aBowOOBvpCO6wDqEj7&f8Mva4_ zMAz3t2mu9iIoerp>G@j^22D!f)<3)szmOmnZq~H(RSs#j6Ne{BJ$&@YNLA<}g_5|^ zU!2q-`VxBp>Vj2tzcc4 zP)gz@uNnj0gK>YCgN+H;%Pv-lm`|Q}KA--_nC&$YYk({#G5^KSe9j4Ee|7)H%F4>j zBH6p>!v}VB8Y`GNJw5Xo5BvgKfTO#a(b-}7JMHEg6Rj<=?C;;b6CnHpWOlAjP{-D)+H{AT^bX>NWB8N?s^ZHlv`bDudrC*cthSFO?=| zQ}X)b$E@PK@Hh|kBs__OoA83Z0**E?Gc$VuXEW3g5EUx&q(eukpW{2$jr&z$MAjJ} z0dw|gKOlt#9f#3{{T$q!zB}{iV+KP|SQwsbk+=7=tV}6}Cx_i%hJ^S;o0*=8Kh53Q zkK;rMRFMMTfousbGdAi8N4rZE8%Ik&`lzu1+9~qDs&y{-*%l=jVy&g?d*3QM1zTxLGA}VnRqLr_+dW`8;47}3vmkJv9?P&E>Ja?G9*quw? zT^rio?hvcM_e<$dG)DIZ3!~F4A!qHnPtRcbTgpAaFfmS#DBKvxG%ZG+4y=dgSZ892 z2OU5MIC}184KSH=iqxkMJ<5cFLb`|ANgK`&)X4D7nk zfab6-#ZS)_wrJTAi3);jZ1o^JYx_hn2M5R2;&|$BM7M#keswf!o3xWfrE6PnYedVK>j3)tg zw=*>6-CrBDSySA7zoK<+n{CVi;^XI$?53R1v9{oLivI7gkb z`&Ld)sl#hVSS)iCDC&CTD2Y_j-W!;Uf*IV6xW>4~NCto}=|**gS&5FZI$1DcEfdF^ zfJ&6Tn8d6oBa>g?BKzhITjlFGI66m2V0uqua(d!BSw=?2d3E4DCRaP7yj&UCEbc{M zkpRazBh=N^Mg5;Ljx~Qw^#F;M?V&D~3b%x?JZ>&y8V_fze|x1gw*xYP@!FI+G>Z+D zN@ieq&&U5;DPEo)qzNF(wTXJyx%{pfpZU2m=htf?&LZSjHsL4Q!dy8Nvu-M}K#_%lzNE!+GvS)^9eF`L0-O_5HP$KxkFs;A@&946n7V`{h$)6KPR~zOOYQ za5Mm=$Y;*?1kS~Iy`V32x~bR>#gavktfBg1Gd?xaPm|8=hcuMfRm80{9iF|~cnG`F zsN4JwN2D+K)KAnndO3LUTNLxf7cRQ`G1+Pa_|-c6*SXlaq7^V9z{N~hKYG2+&yO;NCy%X5LC)vng=!sA zTSo*=PELe%eO>_2xc_*ga0$iz!{PB~OP<&;Mdz^?q=abbmci<3qW1#}*{$VeX%m7d z?(S{=h^I>;Ky6FQDH!%wA`c|)Znm2kKPRN7SrnJa1ElaNmXq0o-cJ3ABr-V>;uq*y zyW|D>m9}>D+0v2{7h@X7=-3!9vvHisb3&^@+6zfGbf%S?V6COW(Pt+N9Y*fDCv>D4 zx@ZXzB#eE(8ljaHFBUG~P3&Gxma58zrWgm$Zg=1xK7?th8_Q<2sG7ss+>8ul(nF{? zBA~tC7m|T0)z&ss%%V5pz+T0Cc^!X+h3Q4u)@K3&30+b4?%|5d>iRma|L*D6OsaZa zz3W?R8Tm2f@#q{d!GV+qBx6r=1r_)1c^bF=G<{A9Vw6v*Bb|n>%9PeFk`i7aj>y4T z1--piE@c8`w#iXZOxEuqAj&F0gEy_L46xXXz~K?PhyDA>D!#l{Ud3Ile1E%2-mxyA zfY2zt^`tI6Y^7_cD23dbW0z7;RBkpNwAnEGCF)EYmsagRnO)Y}xr-4_p~S$`6L%o{ zPj|*_Hteb>%k#73fFF?u8$W0OQup>wHh(`2RyinjGB-3lQy;W>|7*I8gKyi#Yhm~L zRkcEXNk=?oc#Z~D_aiEzrcTy2v-?$9;%Uv()8Zc+_J!D?@`sa=O}C5p_LNvySdL0Q zBsDgM7h$rIH~sk*78cwc-R+=ydU_8?HXee9CqMCF{6{C@rB0s@KR;d_E|fm6w&Zb& zp#t@dPTLA;XyaC8JMImm2IO196bpNvCdeLhj#0l;3D=6KMbNw+`u@7k^=Fbv{Cot0 z-_H>%OuvgNQ5BvodqU719C~)(S~~l&LWH?~Ebb zm@j8}#W^HQPpwL=)ujXfz^8Y3is1PJljlU}n48({;TGpXwK$i)`{)j6EEJ44$HLur zAIi>wsIJZ4*znTU(Xk5{#LC+u@&Fy9gg{*P_vf=HHZ!DgJbd`@IicNG8+*UU>uo6& z>e%Bu-)Z%g{uNMF!MdW)n#H*Wqrfqjd4KDRakUYr-d*+M^R|wZla+mrf*pR=r+oPE z%IWS6MRH-~+w{IZ>-P4vKSI2_A@^Ih9!oWyb3WHj)xIU`N~7CoCEl$w~U0dfM zAEM3g{_Jb^N0!9Fx50QLtHV9dYKfTZSZrKe+!o`cvQi$E1|Rp*NP;>V85y1JeULs{ zXAk@iXmAjVD&Pg4low1NFxX;|s5g=Vaj6uFX&P$oXS?@r2j3P_^6bn=nd=|jI6_tE zTd5qWeU#OCzhWw6s_W&I*wVF^Rs_t3i2@%MMg;aRe5_zgNZ~Y5viyfIHP>4u2aSn| z%%71cAeuJ1%Y*I35tCjgP{;o;>^tlI(Jb5&T^G~_2U}f~=8c^Ntsciy zZV00mwJpN33v56o1B`3~+?)e&+OB>$z<`tyN?HmCRRv}BTS}_h2o-H5gd9Ry8-aMZ l$E*2&0zAFl@b{nm{{Y1Q1`IR57Xku6Xd??$De~U4{{e4qd}#mx diff --git a/Passepartout/App/macOS/Providers.xcassets/vyprvpn.imageset/vyprvpn@3x.png b/Passepartout/App/macOS/Providers.xcassets/vyprvpn.imageset/vyprvpn@3x.png deleted file mode 100644 index 1ee302b25ac93b225c5558b40358ad55e4747336..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6758 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. -// - -import Cocoa -import PassepartoutCore - -protocol OrganizerProfileTableViewDelegate: AnyObject { - func profileTableViewDidRequestAdd(_ profileTableView: OrganizerProfileTableView, sender: NSView) - - func profileTableView(_ profileTableView: OrganizerProfileTableView, didRequestRemove profile: ConnectionProfile) - - func profileTableView(_ profileTableView: OrganizerProfileTableView, didRequestRename profile: HostConnectionProfile) -} - -class OrganizerProfileTableView: NSView { - @IBOutlet private weak var tableView: NSTableView! - - @IBOutlet private weak var buttonAdd: NSButton! - - @IBOutlet private weak var buttonRemove: NSButton! - - @IBOutlet private weak var buttonRename: NSButton! - - private let service = TransientStore.shared.service - - var rows: [ConnectionProfile] = [] - - var selectedRow: Int? - - var selectionBlock: ((ConnectionProfile) -> Void)? - - var deselectionBlock: (() -> Void)? - - private var isAddEnabled: Bool { - get { - return buttonAdd.isEnabled - } - set { - buttonAdd.isEnabled = newValue - } - } - - private var isRemoveEnabled: Bool { - get { - return buttonRemove.isEnabled - } - set { - buttonRemove.isEnabled = newValue - } - } - - private var isRenameEnabled: Bool { - get { - return buttonRename.isEnabled - } - set { - buttonRename.isEnabled = newValue - } - } - - weak var delegate: OrganizerProfileTableViewDelegate? - - override func viewWillMove(toSuperview newSuperview: NSView?) { - super.viewWillMove(toSuperview: newSuperview) - - buttonAdd.image = NSImage(named: NSImage.addTemplateName) - buttonRemove.image = NSImage(named: NSImage.removeTemplateName) - buttonRename.image = NSImage(named: NSImage.actionTemplateName) - } - - func reloadData() { - tableView.reloadData() - if let i = selectedRow { - tableView.selectRowIndexes(IndexSet(integer: i), byExtendingSelection: false) - } - updateButtonsStatus() - } - - // MARK: Actions - - @IBAction private func addProfile(_ sender: Any?) { - delegate?.profileTableViewDidRequestAdd(self, sender: sender as! NSButton) - } - - @IBAction private func removeProfile(_ sender: Any?) { - let index = tableView.selectedRow - guard index != -1 else { - return - } - delegate?.profileTableView(self, didRequestRemove: rows[index]) - } - - @IBAction private func renameProfile(_ sender: Any?) { - let index = tableView.selectedRow - guard index != -1 else { - return - } - guard let hostProfile = rows[index] as? HostConnectionProfile else { - return - } - delegate?.profileTableView(self, didRequestRename: hostProfile) - } - - // MARK: Helpers - - private func updateButtonsStatus() { - let index = tableView.selectedRow - guard index != -1 else { - isRemoveEnabled = false - isRenameEnabled = false - deselectionBlock?() - return - } - isRemoveEnabled = true - isRenameEnabled = (rows[index] as? HostConnectionProfile != nil) - } -} - -class OrganizerProfileTableViewCell: NSTableCellView { - @IBOutlet private weak var imageActive: NSImageView? - - override var objectValue: Any? { - didSet { - guard let objectValue = objectValue else { - return - } - guard let pair = objectValue as? (ConnectionService, ConnectionProfile) else { - fatalError("objectValue is not a (ConnectionService, ConnectionProfile)") - } - imageView?.image = pair.1.image - textField?.stringValue = pair.0.screenTitle(ProfileKey(pair.1)) - - // FIXME: active profile icon - imageActive?.image = NSImage(named: NSImage.menuOnStateTemplateName) - imageActive?.isHidden = !TransientStore.shared.service.isActiveProfile(pair.1) - } - } -} - -extension OrganizerProfileTableView: NSTableViewDataSource, NSTableViewDelegate { - func numberOfRows(in tableView: NSTableView) -> Int { - return rows.count - } - - func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { - return (service, rows[row]) - } - - func tableViewSelectionDidChange(_ notification: Notification) { - updateButtonsStatus() - - let index = tableView.selectedRow - if index != -1 { - selectionBlock?(rows[index]) - } - } -} diff --git a/Passepartout/App/macOS/Scenes/OrganizerProfileTableView.xib b/Passepartout/App/macOS/Scenes/OrganizerProfileTableView.xib deleted file mode 100644 index a6ada462..00000000 --- a/Passepartout/App/macOS/Scenes/OrganizerProfileTableView.xib +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/macOS/Scenes/OrganizerViewController.swift b/Passepartout/App/macOS/Scenes/OrganizerViewController.swift deleted file mode 100644 index 72e02644..00000000 --- a/Passepartout/App/macOS/Scenes/OrganizerViewController.swift +++ /dev/null @@ -1,394 +0,0 @@ -// -// OrganizerViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/6/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import SwiftyBeaver -import PassepartoutCore - -private let log = SwiftyBeaver.self - -class OrganizerViewController: NSViewController { - @IBOutlet private weak var viewProfiles: NSView! - - private lazy var tableProfiles: OrganizerProfileTableView = .get() - - @IBOutlet private weak var buttonRemoveConfiguration: NSButton! - - @IBOutlet private weak var serviceController: ServiceViewController? - - private let service = TransientStore.shared.service - - private var profiles: [ConnectionProfile] = [] - - private var importer: HostImporter? - - private var profilePendingRemoval: ConnectionProfile? - - deinit { - NotificationCenter.default.removeObserver(self) - } - - override func viewDidLoad() { - super.viewDidLoad() - - viewProfiles.addSubview(tableProfiles) - tableProfiles.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableProfiles.topAnchor.constraint(equalTo: viewProfiles.topAnchor), - tableProfiles.bottomAnchor.constraint(equalTo: viewProfiles.bottomAnchor), - tableProfiles.leftAnchor.constraint(equalTo: viewProfiles.leftAnchor), - tableProfiles.rightAnchor.constraint(equalTo: viewProfiles.rightAnchor), - ]) - - buttonRemoveConfiguration.title = L10n.Organizer.Cells.Uninstall.caption - - tableProfiles.selectionBlock = { [weak self] in - self?.serviceController?.setProfile($0) - } - tableProfiles.deselectionBlock = { [weak self] in - self?.serviceController?.setProfile(nil) - } - tableProfiles.delegate = self - reloadProfiles() - tableProfiles.reloadData() - - let nc = NotificationCenter.default - nc.addObserver(self, selector: #selector(menuDidAddProfile(_:)), name: StatusMenu.didAddProfile, object: nil) - nc.addObserver(self, selector: #selector(menuDidRenameProfile(_:)), name: StatusMenu.didRenameProfile, object: nil) - nc.addObserver(self, selector: #selector(menuDidRemoveProfile(_:)), name: StatusMenu.didRemoveProfile, object: nil) - nc.addObserver(self, selector: #selector(menuDidActivateProfile(_:)), name: StatusMenu.didActivateProfile, object: nil) - } - - // MARK: Actions - - @objc private func addProvider(_ sender: Any?) { - guard let item = sender as? NSMenuItem, let metadata = item.representedObject as? Infrastructure.Metadata else { - return - } - do { - try ProductManager.shared.verifyEligible(forProvider: metadata) - } catch ProductError.beta { - presentBetaFeatureUnavailable("Providers") - return - } catch { - presentPurchaseScreen(forProduct: metadata.product) - return - } - // make sure that infrastructure exists locally - guard let _ = InfrastructureFactory.shared.infrastructure(forName: metadata.name) else { - _ = InfrastructureFactory.shared.update(metadata.name, notBeforeInterval: nil) { [weak self] in - guard let _ = $0 else { - self?.alertMissingInfrastructure(forMetadata: metadata, error: $1) - return - } - self?.confirmAddProvider(withMetadata: metadata) - } - return - } - confirmAddProvider(withMetadata: metadata) - } - - private func alertMissingInfrastructure(forMetadata metadata: Infrastructure.Metadata, error: Error?) { - var message = L10n.Wizards.Provider.Alerts.Unavailable.message - if let error = error { - log.error("Unable to download missing \(metadata.description) infrastructure (network error): \(error.localizedDescription)") - message.append(" \(error.localizedDescription)") - } else { - log.error("Unable to download missing \(metadata.description) infrastructure (API error)") - } - - let alert = Macros.warning(metadata.description, message) - _ = alert.presentModally(withOK: L10n.Global.ok, cancel: nil) - } - - private func confirmAddProvider(withMetadata metadata: Infrastructure.Metadata) { - perform(segue: StoryboardSegue.Main.enterAccountSegueIdentifier, sender: metadata.name) - } - - @objc private func addHost() { - let panel = NSOpenPanel() - - panel.title = L10n.Organizer.Alerts.OpenHostFile.title - panel.allowsMultipleSelection = false - panel.canChooseDirectories = false - panel.canChooseFiles = true - panel.canCreateDirectories = false - panel.allowedFileTypes = ["ovpn"] - - guard panel.runModal() == .OK, let url = panel.url else { - return - } - - importer = HostImporter(withConfigurationURL: url) - importer?.importHost(withPassphrase: nil) - } - - @objc private func updateProvidersList() { - InfrastructureFactory.shared.updateIndex { - if let error = $0 { - log.error("Unable to update providers list: \(error)") - return - } - -// ProductManager.shared.listProducts { (products, error) in -// if let error = error { -// log.error("Unable to list products: \(error)") -// return -// } -// } - } - } - - private func confirmRenameProfile(_ profile: ConnectionProfile, to newTitle: String) { - - // rename to existing title -> confirm overwrite existing - if let existingProfile = service.hostProfile(withTitle: newTitle) { - let alert = Macros.warning( - L10n.Service.Alerts.Rename.title, - L10n.Wizards.Host.Alerts.Existing.message - ) - alert.present(in: view.window, withOK: L10n.Global.ok, cancel: L10n.Global.cancel, handler: { - self.doReplaceProfile(profile, to: newTitle, existingProfile: existingProfile) - }, cancelHandler: nil) - return - } - - // do nothing if same title - if newTitle != service.screenTitle(forHostId: profile.id) { - service.renameProfile(profile, to: newTitle) - } - } - - private func doReplaceProfile(_ profile: ConnectionProfile, to newTitle: String, existingProfile: ConnectionProfile) { - let wasActive = service.isActiveProfile(existingProfile) - service.removeProfile(ProfileKey(existingProfile)) - service.renameProfile(profile, to: newTitle) - if wasActive { - service.activateProfile(profile) - } - serviceController?.setProfile(profile) - } - - @IBAction private func confirmVpnProfileDeletion(_ sender: Any?) { - let alert = Macros.warning( - L10n.Organizer.Cells.Uninstall.caption, - L10n.Organizer.Alerts.DeleteVpnProfile.message - ) - alert.present(in: view.window, withOK: L10n.Global.ok, cancel: L10n.Global.cancel, handler: { - VPN.shared.uninstall(completionHandler: nil) - }, cancelHandler: nil) - } - - override func prepare(for segue: NSStoryboardSegue, sender: Any?) { - if let vc = segue.destinationController as? ServiceViewController { - serviceController = vc - } else if let vc = segue.destinationController as? AccountViewController { - - // add provider -> account - if let name = sender as? InfrastructureName { - vc.profile = ProviderConnectionProfile(name: name) - } - // add host -> rename -> account - else { - vc.profile = sender as? ConnectionProfile - } - vc.delegate = self - } else if let vc = segue.destinationController as? TextInputViewController { - guard let profile = sender as? ConnectionProfile else { - return - } - - // rename host - vc.caption = L10n.Service.Alerts.Rename.title.asCaption - vc.text = service.screenTitle(forHostId: profile.id) - vc.placeholder = L10n.Global.Host.TitleInput.placeholder - vc.object = profile - vc.delegate = self - } - } - - // MARK: Notifications - - @objc private func menuDidAddProfile(_ notification: Notification) { - reloadProfiles() - tableProfiles.reloadData() - } - - @objc private func menuDidRenameProfile(_ notification: Notification) { - reloadProfiles() - tableProfiles.reloadData() - } - - @objc private func menuDidRemoveProfile(_ notification: Notification) { - reloadProfiles() - tableProfiles.selectedRow = nil - tableProfiles.reloadData() - } - - @objc private func menuDidActivateProfile(_ notification: Notification) { - guard let profile = notification.object as? ConnectionProfile else { - return - } - for (i, p) in profiles.enumerated() { - if p.id == profile.id { - tableProfiles.selectedRow = i - break - } - } - tableProfiles.reloadData() - } - - // MARK: Helpers - - private func removePendingProfile() { - guard let profile = profilePendingRemoval else { - return - } - - service.removeProfile(ProfileKey(profile)) - profilePendingRemoval = nil - - if profiles.isEmpty || !service.hasActiveProfile() { - serviceController?.setProfile(nil) - VPN.shared.uninstall(completionHandler: nil) - } - } - - private func reloadProfiles() { - let providerIds = service.ids(forContext: .provider) - let hostIds = service.ids(forContext: .host) - profiles.removeAll() - for id in providerIds { - guard let profile = service.profile(withContext: .provider, id: id) else { - continue - } - profiles.append(profile) - } - for id in hostIds { - guard let profile = service.profile(withContext: .host, id: id) else { - continue - } - profiles.append(profile) - } - profiles.sort { - service.screenTitle(ProfileKey($0)).lowercased() < service.screenTitle(ProfileKey($1)).lowercased() - } - - tableProfiles.rows = profiles - for (i, p) in profiles.enumerated() { - if service.isActiveProfile(p) { - tableProfiles.selectedRow = i - break - } - } - } -} - -extension OrganizerViewController: OrganizerProfileTableViewDelegate { - func profileTableViewDidRequestAdd(_ profileTableView: OrganizerProfileTableView, sender: NSView) { - guard let event = NSApp.currentEvent else { - return - } - - let menu = NSMenu() - - let itemProvider = NSMenuItem(title: L10n.Organizer.Menus.provider, action: nil, keyEquivalent: "") - let menuProvider = NSMenu() - let availableMetadata = service.availableProviders() - if !availableMetadata.isEmpty { - for metadata in availableMetadata { - let item = NSMenuItem(title: metadata.description, action: #selector(addProvider(_:)), keyEquivalent: "") -// item.image = metadata.logo - item.representedObject = metadata - menuProvider.addItem(item) - } - } else { - let item = NSMenuItem(title: L10n.Organizer.Menus.Provider.unavailable, action: nil, keyEquivalent: "") - item.isEnabled = false - menuProvider.addItem(item) - } - menuProvider.addItem(.separator()) - let itemProviderUpdateList = NSMenuItem(title: L10n.Wizards.Provider.Cells.UpdateList.caption, action: #selector(updateProvidersList), keyEquivalent: "") - menuProvider.addItem(itemProviderUpdateList) - menu.setSubmenu(menuProvider, for: itemProvider) - menu.addItem(itemProvider) - - let menuHost = NSMenuItem(title: L10n.Organizer.Menus.host.asContinuation, action: #selector(addHost), keyEquivalent: "") - menu.addItem(menuHost) - - NSMenu.popUpContextMenu(menu, with: event, for: sender) - } - - func profileTableView(_ profileTableView: OrganizerProfileTableView, didRequestRemove profile: ConnectionProfile) { - profilePendingRemoval = profile - - let alert = Macros.warning( - L10n.Organizer.Alerts.RemoveProfile.title, - L10n.Organizer.Alerts.RemoveProfile.message(service.screenTitle(ProfileKey(profile))) - ) - alert.present(in: view.window, withOK: L10n.Global.ok, cancel: L10n.Global.cancel, handler: { - self.removePendingProfile() - }, cancelHandler: nil) - } - - func profileTableView(_ profileTableView: OrganizerProfileTableView, didRequestRename profile: HostConnectionProfile) { - perform(segue: StoryboardSegue.Main.renameProfileSegueIdentifier, sender: profile) - } -} - -extension OrganizerViewController: AccountViewControllerDelegate { - func accountController(_ accountController: AccountViewController, shouldUpdateCredentials credentials: Credentials, forProfile profile: ConnectionProfile) -> Bool { - guard profile.requiresCredentials else { - return true - } - return credentials.isValid - } - - func accountController(_ accountController: AccountViewController, didUpdateCredentials credentials: Credentials, forProfile profile: ConnectionProfile) { - - // finish adding provider (host adding is done by HostImporter) - if profile.context == .provider { - service.addOrReplaceProfile(profile, credentials: credentials) - } - } - - func accountControllerDidCancel(_ accountController: AccountViewController) { - } -} - -// rename existing host profile -extension OrganizerViewController: TextInputViewControllerDelegate { - func textInputController(_ textInputController: TextInputViewController, shouldEnterText text: String) -> Bool { - return true//text.rangeOfCharacter(from: CharacterSet.filename.inverted) == nil - } - - func textInputController(_ textInputController: TextInputViewController, didEnterText text: String) { - guard let profile = textInputController.object as? ConnectionProfile else { - return - } - confirmRenameProfile(profile, to: text) - dismiss(textInputController) - } -} diff --git a/Passepartout/App/macOS/Scenes/Preferences/DebugLogViewController.swift b/Passepartout/App/macOS/Scenes/Preferences/DebugLogViewController.swift deleted file mode 100644 index 02833b85..00000000 --- a/Passepartout/App/macOS/Scenes/Preferences/DebugLogViewController.swift +++ /dev/null @@ -1,274 +0,0 @@ -// -// DebugLogViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 7/31/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -class DebugLogViewController: NSViewController { - @IBOutlet private weak var labelExchangedCaption: NSTextField! - - @IBOutlet private weak var labelExchanged: NSTextField! - - @IBOutlet private weak var checkMasking: NSButton! - - @IBOutlet private weak var labelLog: NSTextField! - - @IBOutlet private weak var tableTextLog: NSTableView! - - @IBOutlet private weak var buttonPrevious: NSButton! - - @IBOutlet private weak var buttonNext: NSButton! - - @IBOutlet private weak var buttonCopy: NSButton! - - @IBOutlet private weak var buttonShare: NSButton! - - private let service = TransientStore.shared.service - - private let vpn = VPN.shared - - private var shouldDeleteLogOnDisconnection = false - - private var logLines: [Substring] = [] - - deinit { - NotificationCenter.default.removeObserver(self) - } - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Service.Cells.DebugLog.caption - - checkMasking.title = L10n.Service.Cells.MasksPrivateData.caption - checkMasking.state = (TransientStore.masksPrivateData ? .on : .off) - - labelExchangedCaption.stringValue = L10n.Service.Cells.DataCount.caption.asCaption - labelLog.stringValue = L10n.Service.Cells.DebugLog.caption.asCaption - buttonCopy.title = L10n.DebugLog.Buttons.copy - buttonPrevious.image = NSImage(named: NSImage.touchBarRewindTemplateName) - buttonNext.image = NSImage(named: NSImage.touchBarFastForwardTemplateName) - buttonShare.image = NSImage(named: NSImage.shareTemplateName) - - let nc = NotificationCenter.default - nc.addObserver(self, selector: #selector(vpnDidPrepare), name: VPN.didPrepare, object: nil) - nc.addObserver(self, selector: #selector(vpnDidUpdate), name: VPN.didChangeStatus, object: nil) - nc.addObserver(self, selector: #selector(serviceDidUpdateDataCount), name: ConnectionService.didUpdateDataCount, object: nil) - - if vpn.isPrepared { - startRefreshingLog() - } - refreshDataCount() - } - - @IBAction private func toggleMasking(_ sender: Any?) { - let isOn = (self.checkMasking.state == .on) - let handler = { - TransientStore.masksPrivateData = isOn - self.service.baseConfiguration = TransientStore.baseVPNConfiguration.build() - } - - guard vpn.status == .disconnected else { - let alert = Macros.warning( - L10n.Service.Cells.MasksPrivateData.caption, - L10n.Service.Alerts.MasksPrivateData.Messages.mustReconnect - ) - alert.present(in: view.window, withOK: L10n.Service.Alerts.Buttons.reconnect, cancel: L10n.Global.cancel, handler: { - handler() - self.shouldDeleteLogOnDisconnection = true - - do { - self.vpn.reconnect(configuration: try self.service.vpnConfiguration(), completionHandler: nil) - } catch { - } - }, cancelHandler: { - self.checkMasking.state = (isOn ? .off : .on) - }) - return - } - - handler() - service.eraseVpnLog() - shouldDeleteLogOnDisconnection = false - } - - @IBAction private func copySelection(_ sender: Any?) { - let rows = tableTextLog.selectedRowIndexes - let content = logLines.enumerated().filter { - rows.contains($0.offset) - }.map { - $0.element - }.joined(separator: "\n") - - let pb = NSPasteboard.general - pb.clearContents() - pb.setString(content, forType: .string) - } - - @IBAction private func share(_ sender: Any?) { - let text = logLines.joined(separator: "\n") - guard !text.isEmpty else { - let alert = Macros.warning( - L10n.Service.Cells.DebugLog.caption, - L10n.DebugLog.Alerts.EmptyLog.message - ) - alert.present(in: view.window, withOK: L10n.Global.ok, handler: nil) - return - } - let log = DebugLog(raw: text) - let logString = log.decoratedString() - let picker = NSSharingServicePicker(items: [logString]) - picker.show(relativeTo: buttonShare.bounds, of: buttonShare, preferredEdge: .minY) - } - - @IBAction private func previousSession(_ sender: Any?) { - let visibleRow = firstVisibleRow() - let viewport = logLines[0.. visibleRow else { - return - } - tableTextLog.scrollRowToVisible(row) - } - - private func firstVisibleRow() -> Int { - let range = tableTextLog.rows(in: tableTextLog.visibleRect) - return range.location - } - - private func lastVisibleRow() -> Int { - let range = tableTextLog.rows(in: tableTextLog.visibleRect) - return range.location + range.length - } - - private func startRefreshingLog() { - let fallback: () -> String = { self.service.vpnLog } - - vpn.requestDebugLog(fallback: fallback) { - self.logLines = $0.split(separator: "\n") - - DispatchQueue.main.async { - self.tableTextLog.reloadData() - self.tableTextLog.sizeToFit() - self.refreshLogInBackground() - } - } - } - - private func refreshLogInBackground() { - let fallback: () -> String = { self.service.vpnLog } - let updateBlock = { - DispatchQueue.main.asyncAfter(deadline: .now() + AppConstants.Log.viewerRefreshInterval) { [weak self] in - self?.refreshLogInBackground() - } - } - - // only update if screen is visible - guard let _ = view.window else { - updateBlock() - return - } - - vpn.requestDebugLog(fallback: fallback) { - let wasEmpty = self.logLines.isEmpty - self.logLines = $0.split(separator: "\n") - updateBlock() - if wasEmpty { - self.tableTextLog.reloadData() - self.tableTextLog.sizeToFit() - } - } - } - - // MARK: Notifications - - @objc private func vpnDidPrepare() { - startRefreshingLog() - } - - @objc private func vpnDidUpdate() { - switch vpn.status { - case .disconnected: - if shouldDeleteLogOnDisconnection { - service.eraseVpnLog() - shouldDeleteLogOnDisconnection = false - } - - default: - break - } - - refreshDataCount() - } - - @objc private func serviceDidUpdateDataCount() { - refreshDataCount() - } - - // MARK: Helpers - - private func refreshDataCount() { - if let count = service.vpnDataCount, vpn.status == .connected { - let down = count.0.dataUnitDescription - let up = count.1.dataUnitDescription - labelExchanged.stringValue = "↓\(down) / ↑\(up)" - } else { - labelExchanged.stringValue = L10n.Service.Cells.DataCount.none - } - } -} - -extension DebugLogViewController: NSTableViewDataSource, NSTableViewDelegate { - func tableView(_ tableView: NSTableView, willDisplayCell cell: Any, for tableColumn: NSTableColumn?, row: Int) { - guard let cell = cell as? NSTextFieldCell else { - return - } - cell.font = NSFont(name: "Courier New", size: NSFont.systemFontSize(for: .regular)) - } - - func numberOfRows(in tableView: NSTableView) -> Int { - return logLines.count - } - - func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { - guard row < logLines.count else { - return nil - } - return logLines[row] - } -} diff --git a/Passepartout/App/macOS/Scenes/Preferences/PreferencesGeneralViewController.swift b/Passepartout/App/macOS/Scenes/Preferences/PreferencesGeneralViewController.swift deleted file mode 100644 index 1cf59b8e..00000000 --- a/Passepartout/App/macOS/Scenes/Preferences/PreferencesGeneralViewController.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// PreferencesGeneralViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 5/31/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore -import ServiceManagement - -class PreferencesGeneralViewController: NSViewController { - @IBOutlet private weak var checkLaunchOnLogin: NSButton! - - @IBOutlet private weak var labelLaunchOnLogin: NSTextField! - - @IBOutlet private weak var checkConfirmQuit: NSButton! - - @IBOutlet private weak var labelConfirmQuit: NSTextField! - - @IBOutlet private weak var checkResolveHostname: NSButton! - - @IBOutlet private weak var labelResolveHostname: NSTextField! - - private let service = TransientStore.shared.service - - override func viewDidLoad() { - super.viewDidLoad() - - checkLaunchOnLogin.title = L10n.Preferences.Cells.LaunchesOnLogin.caption - labelLaunchOnLogin.stringValue = L10n.Preferences.Cells.LaunchesOnLogin.footer - checkConfirmQuit.title = L10n.Preferences.Cells.ConfirmQuit.caption - labelConfirmQuit.stringValue = L10n.Preferences.Cells.ConfirmQuit.footer - checkResolveHostname.title = L10n.Service.Cells.VpnResolvesHostname.caption - labelResolveHostname.stringValue = L10n.Service.Sections.VpnResolvesHostname.footer - - checkLaunchOnLogin.state = (service.preferences.launchesOnLogin ?? true) ? .on : .off - checkConfirmQuit.state = (service.preferences.confirmsQuit ?? true) ? .on : .off - checkResolveHostname.state = service.preferences.resolvesHostname ? .on : .off - } - - override func viewWillDisappear() { - super.viewWillDisappear() - - TransientStore.shared.serialize(withProfiles: false) // close preferences - } - - @IBAction private func toggleLaunchesOnLogin(_ sender: Any?) { - service.preferences.launchesOnLogin = (checkLaunchOnLogin.state == .on) - SMLoginItemSetEnabled(AppConstants.App.appLauncherId as CFString, service.preferences.launchesOnLogin ?? true) - } - - @IBAction private func toggleConfirmQuit(_ sender: Any?) { - service.preferences.confirmsQuit = (checkConfirmQuit.state == .on) - } - - @IBAction private func toggleResolvesHostname(_ sender: Any?) { - service.preferences.resolvesHostname = (checkResolveHostname.state == .on) - cycleVPNIfNeeded() - } - - private func cycleVPNIfNeeded() { - let vpn = GracefulVPN(service: service) - guard vpn.isEnabled else { - return - } -// guard vpn.status == .disconnected else { -// confirmVpnReconnection() -// return -// } - vpn.reinstall(completionHandler: nil) - } -} diff --git a/Passepartout/App/macOS/Scenes/Purchase/PurchaseProductView.swift b/Passepartout/App/macOS/Scenes/Purchase/PurchaseProductView.swift deleted file mode 100644 index 08977888..00000000 --- a/Passepartout/App/macOS/Scenes/Purchase/PurchaseProductView.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// PurchaseProductView.swift -// Passepartout -// -// Created by Davide De Rosa on 2/4/21. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import StoreKit - -class PurchaseProductView: NSView { - @IBOutlet private weak var labelTitle: NSTextField? - - @IBOutlet private weak var labelPrice: NSTextField? - - @IBOutlet private weak var labelDescription: NSTextField? - - func fill(product: SKProduct, customDescription: String? = nil) { - fill( - title: product.localizedTitle, - description: customDescription ?? "\(product.localizedDescription)." - ) - labelPrice?.stringValue = product.localizedPrice ?? "" - } - - func fill(title: String, description: String) { - labelTitle?.stringValue = title - labelDescription?.stringValue = description - labelPrice?.stringValue = "" - } -} diff --git a/Passepartout/App/macOS/Scenes/Purchase/PurchaseViewController.swift b/Passepartout/App/macOS/Scenes/Purchase/PurchaseViewController.swift deleted file mode 100644 index ea14f841..00000000 --- a/Passepartout/App/macOS/Scenes/Purchase/PurchaseViewController.swift +++ /dev/null @@ -1,268 +0,0 @@ -// -// PurchaseViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 2/2/21. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import StoreKit -import PassepartoutCore -import SwiftyBeaver -import Convenience - -private let log = SwiftyBeaver.self - -protocol PurchaseViewControllerDelegate: AnyObject { - func purchaseController(_ purchaseController: PurchaseViewController, didPurchase product: LocalProduct?) -} - -class PurchaseViewController: NSViewController { - private struct Columns { - static let product = NSUserInterfaceItemIdentifier("ProductCellIdentifier") - } - - @IBOutlet private weak var tableView: NSTableView! - - @IBOutlet private weak var labelFooter: NSTextField! - - @IBOutlet private weak var labelRestore: NSTextField! - - @IBOutlet private weak var activityPurchase: NSProgressIndicator! - - @IBOutlet private weak var buttonPurchase: NSButton! - - @IBOutlet private weak var buttonRestore: NSButton! - - var feature: LocalProduct? - - weak var delegate: PurchaseViewControllerDelegate? - - private var skFeature: SKProduct? - - private var skPlatformVersion: SKProduct? - - private var skFullVersion: SKProduct? - - private var platformVersionExtra: String? - - private var fullVersionExtra: String? - - var rows: [RowType] = [] - - func reloadModel() { - rows = [] - let pm = ProductManager.shared - if let skPlatformVersion = pm.product(withIdentifier: .fullVersion_macOS) { - self.skPlatformVersion = skPlatformVersion - rows.append(.platformVersion) - - let bullets: [String] = ProductManager.shared.featureProducts(excluding: [.fullVersion, .fullVersion_iOS, .fullVersion_macOS, .siriShortcuts]).map { - return $0.localizedTitle - }.sortedCaseInsensitive() - platformVersionExtra = bullets.joined(separator: "\n") - } - if !pm.hasPurchased(.fullVersion_iOS), let skFullVersion = pm.product(withIdentifier: .fullVersion) { - self.skFullVersion = skFullVersion - rows.append(.fullVersion) - - let bullets: [String] = ProductManager.shared.featureProducts(including: [.fullVersion_iOS, .fullVersion_macOS]).map { - return $0.localizedTitle - }.sortedCaseInsensitive() - fullVersionExtra = bullets.joined(separator: "\n") - } - if let feature = feature, let skFeature = pm.product(withIdentifier: feature) { - self.skFeature = skFeature - rows.append(.feature) - } - } - - // MARK: NSViewController - - override func viewDidLoad() { - super.viewDidLoad() - - title = L10n.Purchase.title - labelFooter.stringValue = L10n.Purchase.Sections.Products.footer - labelRestore.stringValue = L10n.Purchase.Cells.Restore.description - buttonPurchase.title = L10n.Purchase.title - buttonRestore.title = L10n.Purchase.Cells.Restore.title - - tableView.usesAutomaticRowHeights = true - tableView.reloadData() - } - - override func viewWillAppear() { - super.viewWillAppear() - - view.window?.styleMask = [.closable, .titled] - } - - override func viewDidAppear() { - super.viewDidAppear() - - startWaiting() - ProductManager.shared.listProducts { [weak self] (_, _) in - self?.reloadModel() - self?.tableView.reloadData() - self?.stopWaiting() - } - } - - // MARK: Actions - - @IBAction private func doPurchase(_ sender: Any) { - guard tableView.selectedRow != -1 else { - return - } - switch rows[tableView.selectedRow] { - case .feature: - purchaseFeature() - - case .platformVersion: - purchasePlatformVersion() - - case .fullVersion: - purchaseFullVersion() - } - } - - @IBAction private func doRestorePurchases(_ sender: Any) { - startWaiting() - ProductManager.shared.restorePurchases { [weak self] in - self?.stopWaiting() - guard $0 == nil else { - return - } - self?.dismiss(nil) - } - } - - private func purchaseFeature() { - guard let sk = skFeature else { - return - } - purchase(sk) - } - - private func purchasePlatformVersion() { - guard let sk = skPlatformVersion else { - return - } - purchase(sk) - } - - private func purchaseFullVersion() { - guard let sk = skFullVersion else { - return - } - purchase(sk) - } - - private func purchase(_ skProduct: SKProduct) { - startWaiting() - ProductManager.shared.purchase(skProduct) { [weak self] in - self?.stopWaiting() - guard $0 == .success else { - if let error = $1 { - self?.reportPurchaseError(withProduct: skProduct, error: error) - } - return - } - - guard let weakSelf = self else { - return - } - let product = LocalProduct(rawValue: skProduct.productIdentifier) - weakSelf.delegate?.purchaseController(weakSelf, didPurchase: product) - - self?.dismiss(nil) - } - } - - private func reportPurchaseError(withProduct product: SKProduct, error: Error) { - log.error("Unable to purchase \(product): \(error)") - - let alert = Macros.warning(product.localizedTitle, error.localizedDescription) - _ = alert.presentModally(withOK: L10n.Global.ok, cancel: nil) - } - - @objc private func close() { - dismiss(nil) - } - - // MARK: Helpers - - private func startWaiting() { - tableView.isEnabled = false - buttonPurchase.isEnabled = false - buttonRestore.isEnabled = false - activityPurchase.isHidden = false - activityPurchase.startAnimation(nil) - } - - private func stopWaiting() { - activityPurchase.stopAnimation(nil) - tableView.isEnabled = true - buttonPurchase.isEnabled = true - buttonRestore.isEnabled = true - } -} - -extension PurchaseViewController: NSTableViewDataSource, NSTableViewDelegate { - enum RowType { - case feature - - case platformVersion - - case fullVersion - } - - func numberOfRows(in tableView: NSTableView) -> Int { - return rows.count - } - - func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - guard let view = tableView.makeView(withIdentifier: Columns.product, owner: nil) as? PurchaseProductView else { - return nil - } - switch rows[row] { - case .feature: - guard let product = skFeature else { - fatalError("Loaded feature cell, yet no corresponding product?") - } - view.fill(product: product) - - case .platformVersion: - guard let product = skPlatformVersion else { - fatalError("Loaded platform version cell, yet no corresponding product?") - } - view.fill(product: product, customDescription: platformVersionExtra) - - case .fullVersion: - guard let product = skFullVersion else { - fatalError("Loaded full version cell, yet no corresponding product?") - } - view.fill(product: product, customDescription: fullVersionExtra) - } - return view - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/AccountViewController.swift b/Passepartout/App/macOS/Scenes/Service/AccountViewController.swift deleted file mode 100644 index e951f146..00000000 --- a/Passepartout/App/macOS/Scenes/Service/AccountViewController.swift +++ /dev/null @@ -1,148 +0,0 @@ -// -// AccountViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 7/29/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -protocol AccountViewControllerDelegate: AnyObject { - func accountController(_ accountController: AccountViewController, shouldUpdateCredentials credentials: Credentials, forProfile profile: ConnectionProfile) -> Bool - - func accountController(_ accountController: AccountViewController, didUpdateCredentials credentials: Credentials, forProfile profile: ConnectionProfile) - - func accountControllerDidCancel(_ accountController: AccountViewController) -} - -class AccountViewController: NSViewController { - @IBOutlet private weak var labelUsernameCaption: NSTextField! - - @IBOutlet private weak var textUsername: NSTextField! - - @IBOutlet private weak var labelPasswordCaption: NSTextField! - - @IBOutlet private weak var textPassword: NSSecureTextField! - - @IBOutlet private weak var labelGuidance: NSTextField! - - @IBOutlet private weak var buttonGuidance: NSButton! - - @IBOutlet private weak var buttonOK: NSButton! - - @IBOutlet private weak var buttonCancel: NSButton! - - private let service = TransientStore.shared.service - - var profile: ConnectionProfile! - - weak var delegate: AccountViewControllerDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - assert(profile != nil, "Profile not set") - - labelUsernameCaption.stringValue = L10n.Account.Cells.Username.caption.asCaption - if let providerProfile = profile as? ProviderConnectionProfile { - textUsername.placeholderString = providerProfile.infrastructure.defaults.username - } else { - textUsername.placeholderString = L10n.Account.Cells.Username.placeholder - } - labelPasswordCaption.stringValue = L10n.Account.Cells.Password.caption.asCaption - textPassword.placeholderString = L10n.Account.Cells.Password.placeholder - buttonGuidance.title = L10n.Account.Cells.OpenGuide.caption - buttonOK.title = L10n.Global.ok - buttonCancel.title = L10n.Global.cancel - - let credentials = service.credentials(for: profile) - textUsername.stringValue = credentials?.username ?? "" - textPassword.stringValue = credentials?.password ?? "" - - if let guidanceString = guidanceString, !guidanceString.isEmpty { - labelGuidance.stringValue = guidanceString - buttonGuidance.isHidden = (guidanceURL == nil) - } else { - labelGuidance.isHidden = true - buttonGuidance.isHidden = true - } - } - - // MARK: Actions - - @IBAction private func openGuidanceURL(_ sender: Any?) { - guard let url = guidanceURL else { - return - } - NSWorkspace.shared.open(url) - } - - @IBAction private func confirm(_ sender: Any?) { - let username = textUsername.stringValue - let password = textPassword.stringValue - let credentials = Credentials(username, password) - if let delegate = delegate { - guard delegate.accountController(self, shouldUpdateCredentials: credentials, forProfile: profile) else { - return - } - } - - do { - try service.setCredentials(credentials, for: profile) - } catch { - return - } - - delegate?.accountController(self, didUpdateCredentials: credentials, forProfile: profile) - presentingViewController?.dismiss(self) - } - - @IBAction private func delegateAndDismiss(_ sender: Any?) { - delegate?.accountControllerDidCancel(self) - presentingViewController?.dismiss(self) - } - - override func cancelOperation(_ sender: Any?) { - delegateAndDismiss(sender) - } -} - -extension AccountViewController { - private var guidanceString: String? { - return metadata?.guidanceString - } - - private var guidanceURL: URL? { - return metadata?.guidanceURL - } - - private var referralURL: URL? { - return metadata?.referralURL - } - - private var metadata: Infrastructure.Metadata? { - guard let providerProfile = profile as? ProviderConnectionProfile else { - return nil - } - return InfrastructureFactory.shared.metadata(forName: providerProfile.name) - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/Customization/ConfigurationViewController.swift b/Passepartout/App/macOS/Scenes/Service/Customization/ConfigurationViewController.swift deleted file mode 100644 index facb8f49..00000000 --- a/Passepartout/App/macOS/Scenes/Service/Customization/ConfigurationViewController.swift +++ /dev/null @@ -1,314 +0,0 @@ -// -// ConfigurationViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 5/31/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -class ConfigurationViewController: NSViewController, ProfileCustomization { - private struct Columns { - static let name = NSUserInterfaceItemIdentifier("Name") - - static let value = NSUserInterfaceItemIdentifier("Value") - } - - @IBOutlet private weak var labelPresetCaption: NSTextField! - - @IBOutlet private weak var popupPreset: NSPopUpButton! - - @IBOutlet private weak var tableConfiguration: NSTableView! - - private lazy var allPresets: [InfrastructurePreset] = { - guard let providerProfile = profile as? ProviderConnectionProfile else { - return [] - } - let infra = providerProfile.infrastructure - return providerProfile.pool?.supportedPresetIds(in: infra).map { - return infra.preset(for: $0)! - } ?? [] - }() - - private var preset: InfrastructurePreset? { - didSet { - guard let preset = preset else { - return - } - configuration = preset.configuration.sessionConfiguration.builder() - } - } - - private var configuration = OpenVPN.ConfigurationBuilder() - - private let rows: [RowType] = [ - .cipher, - .digest, - .xorMask, - .compressionFraming, - .compressionAlgorithm, - .client, - .tlsWrapping, - .eku, - .keepAlive, - .renegSeconds, - .randomEndpoint - ] - - private var rowMenus: [RowType: NSMenu] = [:] - - // MARK: ProfileCustomization - - var profile: ConnectionProfile? { - didSet { - if let providerProfile = profile as? ProviderConnectionProfile { - preset = providerProfile.preset - } else if let hostProfile = profile as? HostConnectionProfile { - configuration = hostProfile.parameters.sessionConfiguration.builder() - } - } - } - - weak var delegate: ProfileCustomizationDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - labelPresetCaption.stringValue = L10n.Service.Cells.Provider.Preset.caption.asCaption - popupPreset.removeAllItems() - if !allPresets.isEmpty { - for preset in allPresets { - popupPreset.addItem(withTitle: preset.name) - } - } else { - popupPreset.addItem(withTitle: L10n.Global.Values.default) - popupPreset.isEnabled = false - } - - reloadModel() - } - - private func reloadModel() { - if let index = allPresets.firstIndex(where: { $0.id == preset?.id }) { - popupPreset.selectItem(at: index) - } - var availableCiphers: [OpenVPN.Cipher] - let availableDigests: [OpenVPN.Digest] - let availableCF: [OpenVPN.CompressionFraming] - let availableCA: [OpenVPN.CompressionAlgorithm] - if let _ = profile as? HostConnectionProfile { - availableCiphers = configuration.dataCiphers ?? [] - if !availableCiphers.isEmpty { - if let cipher = configuration.cipher, !availableCiphers.contains(cipher) { - availableCiphers.append(cipher) - } - } else { - availableCiphers.append(contentsOf: OpenVPN.Cipher.available) - } - availableDigests = OpenVPN.Digest.available - availableCF = OpenVPN.CompressionFraming.available - availableCA = OpenVPN.CompressionAlgorithm.available - } else { - availableCiphers = [configuration.fallbackCipher] - availableDigests = [configuration.fallbackDigest] - availableCF = [configuration.fallbackCompressionFraming] - availableCA = [configuration.fallbackCompressionAlgorithm] - } - - // editable - rowMenus[.cipher] = NSMenu.withDescriptibles(availableCiphers) - rowMenus[.digest] = NSMenu.withDescriptibles(availableDigests) - rowMenus[.compressionFraming] = NSMenu.withDescriptibles(availableCF) - rowMenus[.compressionAlgorithm] = NSMenu.withDescriptibles(availableCA) - - // single-option menus (unselectable) - rowMenus[.client] = NSMenu.withString(configuration.uiDescriptionForClientCertificate) - rowMenus[.tlsWrapping] = NSMenu.withString(configuration.uiDescriptionForTLSWrap) - rowMenus[.eku] = NSMenu.withString(configuration.uiDescriptionForEKU) - rowMenus[.keepAlive] = NSMenu.withString(configuration.uiDescriptionForKeepAlive) - rowMenus[.renegSeconds] = NSMenu.withString(configuration.uiDescriptionForRenegotiatesAfter) - rowMenus[.randomEndpoint] = NSMenu.withString(configuration.uiDescriptionForRandomizeEndpoint) - rowMenus[.xorMask] = NSMenu.withString(configuration.uiDescriptionForXOR) - } - - // MARK: Actions - - @IBAction private func selectPreset(_ sender: Any?) { - let preset = allPresets[popupPreset.indexOfSelectedItem] - self.preset = preset - reloadModel() - delegate?.profileCustomization(self, didUpdatePreset: preset) - tableConfiguration.reloadData() - } -} - -extension ConfigurationViewController: NSTableViewDataSource, NSTableViewDelegate { - enum RowType: Int { -// case resetOriginal - - case cipher - - case digest - - case compressionFraming - - case compressionAlgorithm - - case client - - case tlsWrapping - - case eku - - case keepAlive - - case renegSeconds - - case randomEndpoint - - case xorMask - } - - func numberOfRows(in tableView: NSTableView) -> Int { - return rows.count - } - - func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { - let V = L10n.Configuration.Cells.self - let rowObject = rows[row] - - switch tableColumn?.identifier { - case Columns.name: - switch rowObject { - case .cipher: - return V.Cipher.caption - - case .digest: - return V.Digest.caption - - case .compressionFraming: - return V.CompressionFraming.caption - - case .compressionAlgorithm: - return V.CompressionAlgorithm.caption - - case .client: - return V.Client.caption - - case .tlsWrapping: - return V.TlsWrapping.caption - - case .eku: - return V.Eku.caption - - case .keepAlive: - return V.KeepAlive.caption - - case .renegSeconds: - return V.RenegotiationSeconds.caption - - case .randomEndpoint: - return V.RandomEndpoint.caption - - case .xorMask: - return "XOR" - } - - case Columns.value: - guard let menu = rowMenus[rowObject], let cell = tableColumn?.dataCell(forRow: row) as? NSPopUpButtonCell else { - break - } - cell.menu = menu - cell.imageDimsWhenDisabled = false - if menu.numberOfItems > 1 { - cell.arrowPosition = .arrowAtBottom - cell.isEnabled = true - } else { - cell.arrowPosition = .noArrow - cell.isEnabled = false - } - switch rowObject { - case .cipher: - return menu.indexOfItem(withRepresentedObject: configuration.fallbackCipher) - - case .digest: - return menu.indexOfItem(withRepresentedObject: configuration.fallbackDigest) - - case .compressionFraming: - return menu.indexOfItem(withRepresentedObject: configuration.fallbackCompressionFraming) - - case .compressionAlgorithm: - return menu.indexOfItem(withRepresentedObject: configuration.fallbackCompressionAlgorithm) - - default: - return 0 - } - - default: - break - } - return nil - } - - func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { - switch tableColumn?.identifier { - case Columns.value: - let rowObject = rows[row] - guard let menu = rowMenus[rowObject], let optionIndex = object as? Int else { - return - } - let optionObject = menu.item(at: optionIndex)?.representedObject - switch rowObject { - case .cipher: - configuration.cipher = optionObject as? OpenVPN.Cipher - - case .digest: - configuration.digest = optionObject as? OpenVPN.Digest - - case .compressionFraming: - guard let option = optionObject as? OpenVPN.CompressionFraming else { - return - } - configuration.compressionFraming = option - if option == .disabled { - configuration.compressionAlgorithm = .disabled - } - - case .compressionAlgorithm: - guard let option = optionObject as? OpenVPN.CompressionAlgorithm else { - return - } - if configuration.compressionFraming == .disabled && option != .disabled { - configuration.compressionFraming = .compLZO - } - configuration.compressionAlgorithm = option - - default: - break - } - delegate?.profileCustomization(self, didUpdateConfiguration: configuration) - - default: - break - } - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/Customization/DNSViewController.swift b/Passepartout/App/macOS/Scenes/Service/Customization/DNSViewController.swift deleted file mode 100644 index 4431bdf5..00000000 --- a/Passepartout/App/macOS/Scenes/Service/Customization/DNSViewController.swift +++ /dev/null @@ -1,222 +0,0 @@ -// -// DNSViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/21/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -class DNSViewController: NSViewController, ProfileCustomization { - @IBOutlet private weak var popupChoice: NSPopUpButton! - - @IBOutlet private weak var viewSettings: NSStackView! - - @IBOutlet private weak var textDNSCustom: NSTextField! - - @IBOutlet private weak var viewDNSAddresses: NSView! - - @IBOutlet private weak var viewDNSDomains: NSView! - - @IBOutlet private weak var labelDNSProtocol: NSTextField! - - @IBOutlet private weak var popupDNSProtocol: NSPopUpButton! - - @IBOutlet private var constraintChoiceBottom: NSLayoutConstraint! - - @IBOutlet private var constraintSettingsTop: NSLayoutConstraint! - - private lazy var tableDNSDomains: TextTableView = .get() - - private lazy var tableDNSAddresses: TextTableView = .get() - - private lazy var currentChoice = profile?.networkChoices?.dns ?? ProfileNetworkChoices.with(profile: profile).dns - - private lazy var clientNetworkSettings = profile?.clientNetworkSettings - - private let networkSettings = ProfileNetworkSettings() - - // MARK: ProfileCustomization - - var profile: ConnectionProfile? - - weak var delegate: ProfileCustomizationDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - labelDNSProtocol.stringValue = L10n.Global.Captions.protocol.asCaption - - tableDNSAddresses.title = L10n.NetworkSettings.Dns.Cells.Addresses.title.asCaption - viewDNSAddresses.addSubview(tableDNSAddresses) - tableDNSAddresses.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableDNSAddresses.topAnchor.constraint(equalTo: viewDNSAddresses.topAnchor), - tableDNSAddresses.bottomAnchor.constraint(equalTo: viewDNSAddresses.bottomAnchor), - tableDNSAddresses.leftAnchor.constraint(equalTo: viewDNSAddresses.leftAnchor), - tableDNSAddresses.rightAnchor.constraint(equalTo: viewDNSAddresses.rightAnchor), - ]) - - tableDNSDomains.title = L10n.NetworkSettings.Dns.Cells.Domains.title.asCaption - viewDNSDomains.addSubview(tableDNSDomains) - tableDNSDomains.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableDNSDomains.topAnchor.constraint(equalTo: viewDNSDomains.topAnchor), - tableDNSDomains.bottomAnchor.constraint(equalTo: viewDNSDomains.bottomAnchor), - tableDNSDomains.leftAnchor.constraint(equalTo: viewDNSDomains.leftAnchor), - tableDNSDomains.rightAnchor.constraint(equalTo: viewDNSDomains.rightAnchor), - ]) - - loadSettings(from: currentChoice) - - popupChoice.removeAllItems() - popupDNSProtocol.removeAllItems() - let menuChoice = NSMenu() - var indexOfChoice = 0 - for (i, choice) in NetworkChoice.choices(for: profile).enumerated() { - let item = NSMenuItem(title: choice.description, action: nil, keyEquivalent: "") - item.representedObject = choice - menuChoice.addItem(item) - if choice == currentChoice { - indexOfChoice = i - } - } - popupChoice.menu = menuChoice - tableDNSAddresses.rowTemplate = AppConstants.Placeholders.dnsAddress - tableDNSDomains.rowTemplate = AppConstants.Placeholders.dnsDomain - let menuProtocol = NSMenu() - var availableProtocols: [DNSProtocol] = [.plain] - if #available(iOS 14, macOS 11, *) { - availableProtocols.append(.https) - availableProtocols.append(.tls) - } - var indexOfDNSProtocol = 0 - for (i, proto) in availableProtocols.enumerated() { - let item = NSMenuItem(title: proto.description, action: nil, keyEquivalent: "") - item.representedObject = proto - menuProtocol.addItem(item) - if proto == networkSettings.dnsProtocol { - indexOfDNSProtocol = i - } - } - popupChoice.menu = menuChoice - popupChoice.selectItem(at: indexOfChoice) - popupDNSProtocol.menu = menuProtocol - popupDNSProtocol.selectItem(at: indexOfDNSProtocol) - } - - // MARK: Actions - - @IBAction private func pickChoice(_ sender: Any?) { - guard let choice = popupChoice.selectedItem?.representedObject as? NetworkChoice else { - return - } - loadSettings(from: choice) - - delegate?.profileCustomization(self, didUpdateDNS: choice, withManualSettings: networkSettings) - } - - @IBAction private func pickProtocol(_ sender: Any?) { - guard let choice = popupChoice.selectedItem?.representedObject as? NetworkChoice else { - return - } - guard let proto = popupDNSProtocol.selectedItem?.representedObject as? DNSProtocol else { - return - } - networkSettings.dnsProtocol = proto - updateProtocolVisibility() - - delegate?.profileCustomization(self, didUpdateDNS: choice, withManualSettings: networkSettings) - } - - func commitManualSettings() { - guard currentChoice == .manual else { - return - } - view.endEditing() - switch networkSettings.dnsProtocol { - case .https: - networkSettings.dnsHTTPSURL = URL(string: textDNSCustom.stringValue) - - case .tls: - networkSettings.dnsTLSServerName = textDNSCustom.stringValue.stripped - - default: - break - } - networkSettings.dnsServers = tableDNSAddresses.rows - networkSettings.dnsSearchDomains = tableDNSDomains.rows - - delegate?.profileCustomization(self, didUpdateDNS: .manual, withManualSettings: networkSettings) - } - - // MARK: Helpers - - private func loadSettings(from choice: NetworkChoice) { - currentChoice = choice - switch currentChoice { - case .client: - if let settings = clientNetworkSettings { - networkSettings.copyDNS(from: settings) - } - - case .server: - break - - case .manual: - if let settings = profile?.manualNetworkSettings { - networkSettings.copyDNS(from: settings) - } - } - - let isManual = (currentChoice == .manual) - popupDNSProtocol.isEnabled = isManual - textDNSCustom.isEnabled = isManual - tableDNSAddresses.reset(withRows: networkSettings.dnsServers ?? [], isAddEnabled: isManual) - tableDNSDomains.reset(withRows: networkSettings.dnsSearchDomains ?? [], isAddEnabled: isManual) - - let isServer = (currentChoice == .server) - constraintChoiceBottom.priority = isServer ? .defaultHigh : .defaultLow - constraintSettingsTop.priority = isServer ? .defaultLow : .defaultHigh - viewSettings.isHidden = isServer - - updateProtocolVisibility() - } - - private func updateProtocolVisibility() { - let isManual = (currentChoice == .manual) - switch networkSettings.dnsProtocol { - case .https: - textDNSCustom.placeholderString = isManual ? AppConstants.Placeholders.dohURL : "" - textDNSCustom.stringValue = networkSettings.dnsHTTPSURL?.absoluteString ?? "" - textDNSCustom.isHidden = false - - case .tls: - textDNSCustom.placeholderString = isManual ? AppConstants.Placeholders.dotServerName : "" - textDNSCustom.stringValue = networkSettings.dnsTLSServerName ?? "" - textDNSCustom.isHidden = false - - default: - textDNSCustom.isHidden = true - } - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/Customization/DefaultGatewayViewController.swift b/Passepartout/App/macOS/Scenes/Service/Customization/DefaultGatewayViewController.swift deleted file mode 100644 index 2d0a3a74..00000000 --- a/Passepartout/App/macOS/Scenes/Service/Customization/DefaultGatewayViewController.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// DefaultGatewayViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/21/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -class DefaultGatewayViewController: NSViewController, ProfileCustomization { - @IBOutlet private weak var popupChoice: NSPopUpButton! - - @IBOutlet private weak var viewSettings: NSView! - - @IBOutlet private weak var checkIPv4: NSButton! - - @IBOutlet private weak var checkIPv6: NSButton! - - @IBOutlet private var constraintChoiceBottom: NSLayoutConstraint! - - @IBOutlet private var constraintSettingsTop: NSLayoutConstraint! - - private lazy var choices = NetworkChoice.choices(for: profile) - - private lazy var currentChoice = profile?.networkChoices?.gateway ?? ProfileNetworkChoices.with(profile: profile).gateway - - private lazy var clientNetworkSettings = profile?.clientNetworkSettings - - private let networkSettings = ProfileNetworkSettings() - - // MARK: ProfileCustomization - - var profile: ConnectionProfile? - - weak var delegate: ProfileCustomizationDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - checkIPv4.title = "IPv4" - checkIPv6.title = "IPv6" - - popupChoice.removeAllItems() - for choice in choices { - popupChoice.addItem(withTitle: choice.description) - if choice == currentChoice { - popupChoice.selectItem(at: popupChoice.numberOfItems - 1) - } - } - loadSettings(from: currentChoice) - } - - // MARK: Actions - - @IBAction private func pickChoice(_ sender: Any?) { - let choice = choices[popupChoice.indexOfSelectedItem] - loadSettings(from: choice) - - delegate?.profileCustomization(self, didUpdateGateway: choice, withManualSettings: networkSettings) - } - - @IBAction private func checkPolicy(_ sender: Any?) { - guard let button = sender as? NSButton else { - return - } - var policies = Set(networkSettings.gatewayPolicies ?? []) - let policy: OpenVPN.RoutingPolicy - switch button { - case checkIPv4: - policy = .IPv4 - - case checkIPv6: - policy = .IPv6 - - default: - return - } - if button.state == .on { - policies.insert(policy) - } else { - policies.remove(policy) - } - networkSettings.gatewayPolicies = Array(policies) - - delegate?.profileCustomization(self, didUpdateGateway: .manual, withManualSettings: networkSettings) - } - - private func loadSettings(from choice: NetworkChoice) { - currentChoice = choice - switch currentChoice { - case .client: - if let settings = clientNetworkSettings { - networkSettings.copyGateway(from: settings) - } - - case .server: - break - - case .manual: - if let settings = profile?.manualNetworkSettings { - networkSettings.copyGateway(from: settings) - } - } - - let policies = networkSettings.gatewayPolicies ?? [] - - checkIPv4.isEnabled = (currentChoice == .manual) - checkIPv4.state = policies.contains(.IPv4) ? .on : .off - checkIPv6.isEnabled = (currentChoice == .manual) - checkIPv6.state = policies.contains(.IPv6) ? .on : .off - - let isServer = (currentChoice == .server) - constraintChoiceBottom.priority = isServer ? .defaultHigh : .defaultLow - constraintSettingsTop.priority = isServer ? .defaultLow : .defaultHigh - viewSettings.isHidden = isServer - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/Customization/EndpointViewController.swift b/Passepartout/App/macOS/Scenes/Service/Customization/EndpointViewController.swift deleted file mode 100644 index d0115fa9..00000000 --- a/Passepartout/App/macOS/Scenes/Service/Customization/EndpointViewController.swift +++ /dev/null @@ -1,123 +0,0 @@ -// -// EndpointViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/19/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -class EndpointViewController: NSViewController, ProfileCustomization { - @IBOutlet private weak var labelAddressCaption: NSTextField! - - @IBOutlet private weak var popupAddress: NSPopUpButton! - - @IBOutlet private weak var labelProtocolCaption: NSTextField! - - @IBOutlet private weak var popupProtocol: NSPopUpButton! - - // MARK: ProfileCustomization - - var profile: ConnectionProfile? - - weak var delegate: ProfileCustomizationDelegate? - - private lazy var dataSource: EndpointDataSource = { - guard let profile = profile else { - fatalError("No profile set") - } - return profile as EndpointDataSource - }() - - override func viewDidLoad() { - super.viewDidLoad() - - labelAddressCaption.stringValue = L10n.Endpoint.Cells.address.asCaption - labelProtocolCaption.stringValue = L10n.Global.Captions.protocol.asCaption - - reloadEndpoints() - } - - func reloadEndpoints() { - popupAddress.removeAllItems() - for address in dataSource.addresses { - popupAddress.addItem(withTitle: address) - if address == dataSource.customAddress { - popupAddress.selectItem(at: popupAddress.numberOfItems - 1) - } - } - popupProtocol.removeAllItems() - for proto in dataSource.protocols { - popupProtocol.addItem(withTitle: proto.rawValue) - if proto == dataSource.customProtocol { - popupProtocol.selectItem(at: popupProtocol.numberOfItems - 1) - } - } - - if dataSource.canCustomizeEndpoint { - popupAddress.insertItem(withTitle: L10n.Endpoint.Cells.AnyAddress.caption, at: 0) - popupProtocol.insertItem(withTitle: L10n.Endpoint.Cells.AnyProtocol.caption, at: 0) - - if dataSource.customAddress == nil { - popupAddress.selectItem(at: 0) - } - if dataSource.customProtocol == nil { - popupProtocol.selectItem(at: 0) - } -// } else { -// popupAddress.isEnabled = false -// popupProtocol.isEnabled = false - } - } - - // MARK: Actions - - @IBAction private func selectAddress(_ sender: Any?) { - guard dataSource.canCustomizeEndpoint else { - return - } - let customAddress: String? - if popupAddress.indexOfSelectedItem == 0 { - customAddress = nil - } else { - customAddress = popupAddress.selectedItem?.title - } - delegate?.profileCustomization(self, didUpdateEndpointWithAddress: customAddress) - } - - @IBAction private func selectProtocol(_ sender: Any?) { - guard dataSource.canCustomizeEndpoint else { - return - } - let customProtocol: EndpointProtocol? - if popupProtocol.indexOfSelectedItem == 0 { - customProtocol = nil - } else { - if let title = popupProtocol.selectedItem?.title { - customProtocol = EndpointProtocol(rawValue: title) - } else { - customProtocol = nil - } - } - delegate?.profileCustomization(self, didUpdateEndpointWithProtocol: customProtocol) - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/Customization/MTUViewController.swift b/Passepartout/App/macOS/Scenes/Service/Customization/MTUViewController.swift deleted file mode 100644 index eaa51957..00000000 --- a/Passepartout/App/macOS/Scenes/Service/Customization/MTUViewController.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// MTUViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 12/28/20. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -class MTUViewController: NSViewController, ProfileCustomization { - @IBOutlet private weak var popupChoice: NSPopUpButton! - - @IBOutlet private weak var viewSettings: NSView! - - @IBOutlet private weak var labelMTUCaption: NSTextField! - - @IBOutlet private weak var popupMTU: NSPopUpButton! - - @IBOutlet private var constraintChoiceBottom: NSLayoutConstraint! - - @IBOutlet private var constraintSettingsTop: NSLayoutConstraint! - - private lazy var choices = NetworkChoice.choices(for: profile) - - private lazy var currentChoice = profile?.networkChoices?.mtu ?? ProfileNetworkChoices.with(profile: profile).mtu - - private lazy var clientNetworkSettings = profile?.clientNetworkSettings - - private let networkSettings = ProfileNetworkSettings() - - // MARK: ProfileCustomization - - var profile: ConnectionProfile? - - weak var delegate: ProfileCustomizationDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - popupChoice.removeAllItems() - for choice in choices { - popupChoice.addItem(withTitle: choice.description) - if choice == currentChoice { - popupChoice.selectItem(at: popupChoice.numberOfItems - 1) - } - } - labelMTUCaption.stringValue = L10n.NetworkSettings.Mtu.Cells.Bytes.caption.asCaption - popupMTU.removeAllItems() - for opt in ProfileNetworkSettings.mtuOptions { - popupMTU.addItem(withTitle: (opt != 0) ? opt.description : L10n.Global.Values.default) - } - loadSettings(from: currentChoice ?? ProfileNetworkChoices.defaultChoice) - } - - // MARK: Actions - - @IBAction private func pickChoice(_ sender: Any?) { - let choice = choices[popupChoice.indexOfSelectedItem] - loadSettings(from: choice) - - delegate?.profileCustomization(self, didUpdateMTU: choice, withManualSettings: networkSettings) - } - - @IBAction private func pickBytes(_ sender: Any?) { - guard let popup = sender as? NSPopUpButton, let title = popup.titleOfSelectedItem else { - return - } - guard let bytes = Int(title) else { - networkSettings.mtuBytes = nil - return - } - networkSettings.mtuBytes = bytes - - delegate?.profileCustomization(self, didUpdateMTU: .manual, withManualSettings: networkSettings) - } - - private func loadSettings(from choice: NetworkChoice) { - currentChoice = choice - switch choice { - case .client: - if let settings = clientNetworkSettings { - networkSettings.copyMTU(from: settings) - } - - case .server: - break - - case .manual: - if let settings = profile?.manualNetworkSettings { - networkSettings.copyMTU(from: settings) - } - } - - let bytes = networkSettings.mtuBytes - - popupMTU.isEnabled = (currentChoice == .manual) - for (i, opt) in popupMTU.itemTitles.enumerated() { - if opt == L10n.Global.Values.default { - if bytes == nil { - popupMTU.selectItem(at: i) - break - } - continue - } - guard let optValue = Int(opt) else { - continue - } - if optValue == bytes { - popupMTU.selectItem(at: i) - break - } - } - - let isServer = (currentChoice == .server) - constraintChoiceBottom.priority = isServer ? .defaultHigh : .defaultLow - constraintSettingsTop.priority = isServer ? .defaultLow : .defaultHigh - viewSettings.isHidden = isServer - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/Customization/ProfileCustomizationViewController.swift b/Passepartout/App/macOS/Scenes/Service/Customization/ProfileCustomizationViewController.swift deleted file mode 100644 index dac4b9d1..00000000 --- a/Passepartout/App/macOS/Scenes/Service/Customization/ProfileCustomizationViewController.swift +++ /dev/null @@ -1,275 +0,0 @@ -// -// ProfileCustomizationViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/19/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -protocol ProfileCustomization: AnyObject { - var profile: ConnectionProfile? { get set } - - var delegate: ProfileCustomizationDelegate? { get set } -} - -protocol ProfileCustomizationDelegate: AnyObject { - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateEndpointWithAddress newAddress: String?) - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateEndpointWithProtocol newEndpointProtocol: EndpointProtocol?) - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdatePreset newPreset: InfrastructurePreset) - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateConfiguration newConfiguration: OpenVPN.ConfigurationBuilder) - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateTrustedNetworks newTrustedNetworks: TrustedNetworks) - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateGateway choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateDNS choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateProxy choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateMTU choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) -} - -class ProfileCustomizationContainerViewController: NSViewController { - @IBOutlet private weak var buttonOK: NSButton! - - @IBOutlet private weak var buttonCancel: NSButton! - - fileprivate weak var endpointController: EndpointViewController? - - fileprivate weak var dnsController: DNSViewController? - - fileprivate weak var proxyController: ProxyViewController? - - var profile: ConnectionProfile? - - // MARK: Pending (provider) - - private var pendingAddress: String? - - private var pendingProtocol: EndpointProtocol? - - private var pendingPreset: InfrastructurePreset? - - // MARK: Pending (host) - - private var pendingParameters: OpenVPN.ConfigurationBuilder? - - // MARK: Pending - - private var pendingTrustedNetworks: TrustedNetworks? - - private var pendingChoices: ProfileNetworkChoices? - - private let pendingManualNetworkSettings = ProfileNetworkSettings() - - override func viewDidLoad() { - super.viewDidLoad() - - buttonOK.title = L10n.Global.ok - buttonCancel.title = L10n.Global.cancel - - pendingAddress = (profile as? ProviderConnectionProfile)?.customAddress - pendingProtocol = (profile as? ProviderConnectionProfile)?.customProtocol - pendingPreset = (profile as? ProviderConnectionProfile)?.preset - pendingTrustedNetworks = profile?.trustedNetworks - pendingParameters = (profile as? HostConnectionProfile)?.parameters.sessionConfiguration.builder() - pendingChoices = ProfileNetworkChoices.with(profile: profile) - } - - override func prepare(for segue: NSStoryboardSegue, sender: Any?) { - guard let customVC = segue.destinationController as? ProfileCustomizationViewController else { - return - } - customVC.containerController = self - customVC.profile = profile - } - - // MARK: Actions - - @IBAction private func commitChanges(_ sender: Any?) { - dnsController?.commitManualSettings() - proxyController?.commitManualSettings() - - if let providerProfile = profile as? ProviderConnectionProfile { - if let pending = pendingPreset { - providerProfile.presetId = pending.id - } - } else if let hostProfile = profile as? HostConnectionProfile, let pendingParameters = pendingParameters { - var builder = hostProfile.parameters.builder() - builder.sessionConfiguration = pendingParameters.build() - hostProfile.parameters = builder.build() - } - profile?.customAddress = pendingAddress - profile?.customProtocol = pendingProtocol - profile?.trustedNetworks = pendingTrustedNetworks - - if let choices = pendingChoices { - let settings = profile?.manualNetworkSettings ?? ProfileNetworkSettings() - if choices.gateway == .manual { - settings.copyGateway(from: pendingManualNetworkSettings) - } - if choices.dns == .manual { - settings.copyDNS(from: pendingManualNetworkSettings) - } - if choices.proxy == .manual { - settings.copyProxy(from: pendingManualNetworkSettings) - } - if choices.mtu == .manual { - settings.copyMTU(from: pendingManualNetworkSettings) - } - profile?.networkChoices = choices - profile?.manualNetworkSettings = settings - } - - TransientStore.shared.serialize(withProfiles: true) // customize - - if let profile = profile, TransientStore.shared.service.isActiveProfile(ProfileKey(profile)) { - let vpn = GracefulVPN(service: TransientStore.shared.service) - if vpn.isEnabled { - switch vpn.status { - case .connected, .connecting: - let alert = Macros.warning( - L10n.Configuration.title, - L10n.Configuration.Alerts.Commit.message - ) - if alert.presentModally( - withOK: L10n.Configuration.Alerts.Commit.Buttons.reconnect, - cancel: L10n.Configuration.Alerts.Commit.Buttons.skip) { - - vpn.reconnect(completionHandler: nil) - } else { - vpn.reinstall(completionHandler: nil) - } - - default: - vpn.reinstall(completionHandler: nil) - } - } - } - - presentingViewController?.dismiss(self) - } -} - -extension ProfileCustomizationContainerViewController: ProfileCustomizationDelegate { - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateEndpointWithAddress newAddress: String?) { - pendingAddress = newAddress - } - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateEndpointWithProtocol newEndpointProtocol: EndpointProtocol?) { - pendingProtocol = newEndpointProtocol - } - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdatePreset newPreset: InfrastructurePreset) { - pendingPreset = newPreset - - // XXX: commit immediately to update endpoints - (profile as? ProviderConnectionProfile)?.presetId = newPreset.id - endpointController?.reloadEndpoints() - } - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateConfiguration newConfiguration: OpenVPN.ConfigurationBuilder) { - pendingParameters = newConfiguration - } - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateTrustedNetworks newTrustedNetworks: TrustedNetworks) { - pendingTrustedNetworks = newTrustedNetworks - } - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateGateway choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) { - pendingChoices?.gateway = choice - pendingManualNetworkSettings.gatewayPolicies = newSettings.gatewayPolicies - } - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateDNS choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) { - pendingChoices?.dns = choice - pendingManualNetworkSettings.dnsProtocol = newSettings.dnsProtocol - pendingManualNetworkSettings.dnsHTTPSURL = newSettings.dnsHTTPSURL - pendingManualNetworkSettings.dnsTLSServerName = newSettings.dnsTLSServerName - pendingManualNetworkSettings.dnsServers = newSettings.dnsServers - pendingManualNetworkSettings.dnsSearchDomains = newSettings.dnsSearchDomains - } - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateProxy choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) { - pendingChoices?.proxy = choice - pendingManualNetworkSettings.proxyAddress = newSettings.proxyAddress - pendingManualNetworkSettings.proxyPort = newSettings.proxyPort - pendingManualNetworkSettings.proxyBypassDomains = newSettings.proxyBypassDomains - } - - func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateMTU choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) { - pendingChoices?.mtu = choice - pendingManualNetworkSettings.mtuBytes = newSettings.mtuBytes - } -} - -// - -class ProfileCustomizationViewController: NSTabViewController { - fileprivate weak var containerController: ProfileCustomizationContainerViewController? - - fileprivate var profile: ConnectionProfile? { - didSet { - for item in tabViewItems { - guard let custom = item.viewController as? ProfileCustomization else { - continue - } - custom.profile = profile - custom.delegate = containerController - - if let vc = custom as? EndpointViewController { - containerController?.endpointController = vc - } else if let vc = custom as? DNSViewController { - containerController?.dnsController = vc - } else if let vc = custom as? ProxyViewController { - containerController?.proxyController = vc - } - } - } - } - - override func viewDidLoad() { - super.viewDidLoad() - - let expectedTabs = 7 - assert(tabViewItems.count == expectedTabs, "Customization tabs misconfigured (expected \(expectedTabs))") - - tabViewItems[0].label = L10n.Endpoint.title - tabViewItems[1].label = L10n.Configuration.title - tabViewItems[2].label = L10n.Service.Sections.Trusted.header - tabViewItems[3].label = L10n.NetworkSettings.Gateway.title - tabViewItems[4].label = L10n.NetworkSettings.Dns.title - tabViewItems[5].label = L10n.NetworkSettings.Proxy.title - tabViewItems[6].label = L10n.NetworkSettings.Mtu.title - - do { - try ProductManager.shared.verifyEligible(forFeature: .trustedNetworks) - } catch ProductError.beta { - tabViewItems.remove(at: 2) - } catch { - } - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/Customization/ProxyViewController.swift b/Passepartout/App/macOS/Scenes/Service/Customization/ProxyViewController.swift deleted file mode 100644 index 17ad0b06..00000000 --- a/Passepartout/App/macOS/Scenes/Service/Customization/ProxyViewController.swift +++ /dev/null @@ -1,154 +0,0 @@ -// -// ProxyViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 6/21/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -class ProxyViewController: NSViewController, ProfileCustomization { - private struct Templates { - static let bypass = "domain.com" - } - - @IBOutlet private weak var popupChoice: NSPopUpButton! - - @IBOutlet private weak var viewSettings: NSStackView! - - @IBOutlet private weak var labelProxyCaption: NSTextField! - - @IBOutlet private weak var textProxyAddress: NSTextField! - - @IBOutlet private weak var textProxyPort: NSTextField! - - @IBOutlet private weak var labelPACCaption: NSTextField! - - @IBOutlet private weak var textPAC: NSTextField! - - @IBOutlet private weak var viewProxyBypass: NSView! - - @IBOutlet private var constraintChoiceBottom: NSLayoutConstraint! - - @IBOutlet private var constraintSettingsTop: NSLayoutConstraint! - - private lazy var tableProxyBypass: TextTableView = .get() - - private lazy var choices = NetworkChoice.choices(for: profile) - - private lazy var currentChoice = profile?.networkChoices?.proxy ?? ProfileNetworkChoices.with(profile: profile).proxy - - private lazy var clientNetworkSettings = profile?.clientNetworkSettings - - private let networkSettings = ProfileNetworkSettings() - - // MARK: ProfileCustomization - - var profile: ConnectionProfile? - - weak var delegate: ProfileCustomizationDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - labelProxyCaption.stringValue = L10n.Global.Captions.address.asCaption - textProxyAddress.placeholderString = L10n.Global.Values.none - textProxyPort.placeholderString = L10n.Global.Values.none - labelPACCaption.stringValue = "PAC".asCaption - textPAC.placeholderString = L10n.Global.Values.none - - tableProxyBypass.title = L10n.NetworkSettings.Proxy.Cells.BypassDomains.title.asCaption - viewProxyBypass.addSubview(tableProxyBypass) - tableProxyBypass.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableProxyBypass.topAnchor.constraint(equalTo: viewProxyBypass.topAnchor), - tableProxyBypass.bottomAnchor.constraint(equalTo: viewProxyBypass.bottomAnchor), - tableProxyBypass.leftAnchor.constraint(equalTo: viewProxyBypass.leftAnchor), - tableProxyBypass.rightAnchor.constraint(equalTo: viewProxyBypass.rightAnchor), - ]) - tableProxyBypass.rowTemplate = Templates.bypass - - loadSettings(from: currentChoice) - - popupChoice.removeAllItems() - for choice in choices { - popupChoice.addItem(withTitle: choice.description) - if choice == currentChoice { - popupChoice.selectItem(at: popupChoice.numberOfItems - 1) - } - } - } - - // MARK: Actions - - @IBAction private func pickChoice(_ sender: Any?) { - let choice = choices[popupChoice.indexOfSelectedItem] - loadSettings(from: choice) - - delegate?.profileCustomization(self, didUpdateProxy: choice, withManualSettings: networkSettings) - } - - func commitManualSettings() { - guard currentChoice == .manual else { - return - } - view.endEditing() - networkSettings.proxyAddress = textProxyAddress.stringValue.stripped - networkSettings.proxyPort = UInt16(textProxyPort.stringValue) - networkSettings.proxyAutoConfigurationURL = URL(string: textPAC.stringValue) - networkSettings.proxyBypassDomains = tableProxyBypass.rows - - delegate?.profileCustomization(self, didUpdateProxy: .manual, withManualSettings: networkSettings) - } - - // MARK: Helpers - - private func loadSettings(from choice: NetworkChoice) { - currentChoice = choice - switch currentChoice { - case .client: - if let settings = clientNetworkSettings { - networkSettings.copyProxy(from: settings) - } - - case .server: - break - - case .manual: - if let settings = profile?.manualNetworkSettings { - networkSettings.copyProxy(from: settings) - } - } - - textProxyAddress.isEnabled = (currentChoice == .manual) - textProxyAddress.stringValue = networkSettings.proxyAddress ?? "" - textProxyPort.isEnabled = (currentChoice == .manual) - textProxyPort.stringValue = networkSettings.proxyPort?.description ?? "" - textPAC.stringValue = networkSettings.proxyAutoConfigurationURL?.absoluteString ?? "" - tableProxyBypass.reset(withRows: networkSettings.proxyBypassDomains ?? [], isAddEnabled: currentChoice == .manual) - - let isServer = (currentChoice == .server) - constraintChoiceBottom.priority = isServer ? .defaultHigh : .defaultLow - constraintSettingsTop.priority = isServer ? .defaultLow : .defaultHigh - viewSettings.isHidden = isServer - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/Customization/TrustedNetworksAddViewController.swift b/Passepartout/App/macOS/Scenes/Service/Customization/TrustedNetworksAddViewController.swift deleted file mode 100644 index 0a307b27..00000000 --- a/Passepartout/App/macOS/Scenes/Service/Customization/TrustedNetworksAddViewController.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// TrustedNetworksAddViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 7/30/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -protocol TrustedNetworksAddViewControllerDelegate: AnyObject { - func trustedController(_ trustedController: TrustedNetworksAddViewController, didEnterSSID ssid: String) -} - -class TrustedNetworksAddViewController: NSViewController { - @IBOutlet private weak var textSSID: NSTextField! - - @IBOutlet private weak var buttonOK: NSButton! - - @IBOutlet private weak var buttonCancel: NSButton! - - weak var delegate: TrustedNetworksAddViewControllerDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - buttonOK.title = L10n.Global.ok - buttonCancel.title = L10n.Global.cancel - - textSSID.stringValue = Utils.currentWifiNetworkName() ?? "" - } - - @IBAction private func confirm(_ sender: Any?) { - let ssid = textSSID.stringValue.stripped - guard !ssid.isEmpty else { - return - } - delegate?.trustedController(self, didEnterSSID: ssid) - presentingViewController?.dismiss(self) - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/Customization/TrustedNetworksViewController.swift b/Passepartout/App/macOS/Scenes/Service/Customization/TrustedNetworksViewController.swift deleted file mode 100644 index cf0a7456..00000000 --- a/Passepartout/App/macOS/Scenes/Service/Customization/TrustedNetworksViewController.swift +++ /dev/null @@ -1,260 +0,0 @@ -// -// TrustedNetworksViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 7/29/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -class TrustedNetworksViewController: NSViewController, ProfileCustomization { - private struct Columns { - static let ssid = NSUserInterfaceItemIdentifier("SSID") - - static let trust = NSUserInterfaceItemIdentifier("Trust") - } - - @IBOutlet private weak var labelTitle: NSTextField! - - @IBOutlet private weak var tableView: NSTableView! - - @IBOutlet private weak var buttonAdd: NSButton! - - @IBOutlet private weak var buttonRemove: NSButton! - - @IBOutlet private weak var checkTrustEthernet: NSButton! - - @IBOutlet private weak var labelTrustEthernetDescription: NSTextField! - - @IBOutlet private weak var checkDisableConnection: NSButton! - - @IBOutlet private weak var labelDisableConnectionDescription: NSTextField! - - private let service = TransientStore.shared.service - - private let model = TrustedNetworksUI() - - // MARK: ProfileCustomization - - var profile: ConnectionProfile? - - private lazy var trustedNetworks = profile?.trustedNetworks ?? TrustedNetworks() - - weak var delegate: ProfileCustomizationDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - labelTitle.stringValue = L10n.Service.Sections.Trusted.header.asCaption - buttonAdd.image = NSImage(named: NSImage.addTemplateName) - buttonRemove.image = NSImage(named: NSImage.removeTemplateName) - checkTrustEthernet.title = L10n.Trusted.Ethernet.title - labelTrustEthernetDescription.stringValue = L10n.Trusted.Ethernet.description - checkDisableConnection.title = L10n.Service.Cells.TrustedPolicy.caption - labelDisableConnectionDescription.stringValue = L10n.Service.Sections.Trusted.footer - - checkTrustEthernet.state = trustedNetworks.includesEthernet ? .on : .off - checkDisableConnection.state = (trustedNetworks.policy == .disconnect) ? .on : .off - model.delegate = self - model.load(from: trustedNetworks) - updateButtons() - - tableView.reloadData() - for column in tableView.tableColumns { - switch column.identifier { - case Columns.ssid: - column.title = "SSID" - column.isEditable = false - - case Columns.trust: - column.title = L10n.Trusted.Columns.Trust.title - - default: - break - } - } - if tableView.numberOfRows > 0 { - tableView.selectRowIndexes(IndexSet(integer: 0), byExtendingSelection: false) - } - } - - // MARK: Actions - - @IBAction private func remove(_ sender: Any?) { - let index = tableView.selectedRow - guard index != -1 else { - return - } - model.removeWifi(at: index) - } - - @IBAction private func toggleTrustEthernet(_ sender: Any?) { - do { - try ProductManager.shared.verifyEligible(forFeature: .trustedNetworks) - } catch { - checkTrustEthernet.state = .off - presentPurchaseScreen(forProduct: .trustedNetworks) - return - } - trustedNetworks.includesEthernet = (checkTrustEthernet.state == .on) - - delegate?.profileCustomization(self, didUpdateTrustedNetworks: trustedNetworks) - } - - @IBAction private func toggleRetainConnection(_ sender: Any?) { - let isOn = (checkDisableConnection.state == .on) - let completionHandler: () -> Void = { - self.trustedNetworks.policy = isOn ? .disconnect : .ignore - } - completionHandler() - - delegate?.profileCustomization(self, didUpdateTrustedNetworks: trustedNetworks) - } - - override func shouldPerformSegue(withIdentifier identifier: NSStoryboardSegue.Identifier, sender: Any?) -> Bool { - if identifier == StoryboardSegue.Service.trustedNetworkAddSegueIdentifier.rawValue { - do { - try ProductManager.shared.verifyEligible(forFeature: .trustedNetworks) - } catch { - presentPurchaseScreen(forProduct: .trustedNetworks) - return false - } - } - return true - } - - override func prepare(for segue: NSStoryboardSegue, sender: Any?) { - if let addVC = segue.destinationController as? TrustedNetworksAddViewController { - addVC.delegate = self - } - } - - // MARK: Helpers - - private func updateButtons() { - buttonRemove.isEnabled = !model.sortedWifis.isEmpty && (tableView.selectedRow != -1) - } -} - -extension TrustedNetworksViewController: NSTableViewDataSource, NSTableViewDelegate { - func numberOfRows(in tableView: NSTableView) -> Int { - return model.sortedWifis.count - } - - func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { - guard row < model.sortedWifis.count else { // XXX - return nil - } - - let wifi = model.sortedWifis[row] - switch tableColumn?.identifier { - case Columns.ssid: - return wifi - - case Columns.trust: - return model.isTrusted(wifi: wifi) - - default: - return nil - } - } - - func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { - guard row < model.sortedWifis.count else { // XXX - return - } - switch tableColumn?.identifier { -// case Columns.ssid: -// guard let ssidName = object as? String else { -// fatalError("Expected a String for trust SSID") -// } -// model.renameWifi(at: row, to: ssidName) - - case Columns.trust: - guard let checkTrust = object as? Bool else { - fatalError("Expected a Bool for trust checkbox state") - } - if checkTrust { - model.enableWifi(at: row) - } else { - model.disableWifi(at: row) - } - - default: - break - } - } - - func tableViewSelectionDidChange(_ notification: Notification) { - updateButtons() - } -} - -extension TrustedNetworksViewController: TrustedNetworksUIDelegate { - func trustedNetworksCouldDisconnect(_: TrustedNetworksUI) -> Bool { - - // VPN untouched - return false - } - - func trustedNetworksShouldConfirmDisconnection(_: TrustedNetworksUI, triggeredAt rowIndex: Int, completionHandler: @escaping () -> Void) { - let alert = Macros.warning( - L10n.Service.Sections.Trusted.header, - L10n.Service.Alerts.Trusted.WillDisconnectTrusted.message - ) - alert.present(in: view.window, withOK: L10n.Global.ok, cancel: L10n.Global.cancel, handler: completionHandler, cancelHandler: nil) - } - - func trustedNetworks(_: TrustedNetworksUI, shouldInsertWifiAt rowIndex: Int) { -// tableView.beginUpdates() -// tableView.insertRows(at: IndexSet(integer: rowIndex), withAnimation: .slideDown) -// tableView.endUpdates() - tableView.reloadData() - - updateButtons() - } - - func trustedNetworks(_: TrustedNetworksUI, shouldReloadWifiAt rowIndex: Int, isTrusted: Bool) { - // - } - - func trustedNetworks(_: TrustedNetworksUI, shouldDeleteWifiAt rowIndex: Int) { -// tableView.beginUpdates() -// tableView.removeRows(at: IndexSet(integer: rowIndex), withAnimation: .slideUp) -// tableView.endUpdates() - tableView.reloadData() - - updateButtons() - } - - func trustedNetworksShouldReinstall(_: TrustedNetworksUI) { - trustedNetworks.includedWiFis = model.trustedWifis - - delegate?.profileCustomization(self, didUpdateTrustedNetworks: trustedNetworks) - } -} - -extension TrustedNetworksViewController: TrustedNetworksAddViewControllerDelegate { - func trustedController(_ trustedController: TrustedNetworksAddViewController, didEnterSSID ssid: String) { - model.addWifi(ssid) - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/HostServiceView.swift b/Passepartout/App/macOS/Scenes/Service/HostServiceView.swift deleted file mode 100644 index a89833ce..00000000 --- a/Passepartout/App/macOS/Scenes/Service/HostServiceView.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// HostServiceView.swift -// Passepartout -// -// Created by Davide De Rosa on 6/13/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -protocol HostServiceViewDelegate: AnyObject { -} - -class HostServiceView: NSView { - @IBOutlet private weak var labelAddressesCaption: NSTextField! - - @IBOutlet private weak var tableAddresses: NSTableView! - - var isEnabled: Bool = true { - didSet { - } - } - - var profile: HostConnectionProfile? { - didSet { - tableAddresses.reloadData() - } - } - - weak var delegate: HostServiceViewDelegate? - - override func viewWillMove(toSuperview newSuperview: NSView?) { - super.viewWillMove(toSuperview: newSuperview) - - labelAddressesCaption.stringValue = L10n.Service.Cells.Addresses.caption.asCaption - } - - func reloadData() { - } -} - -extension HostServiceView: NSTableViewDataSource, NSTableViewDelegate { - func numberOfRows(in tableView: NSTableView) -> Int { - guard let profile = profile else { - return 0 - } - return profile.addresses.count - } - - func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { - guard let profile = profile else { - return nil - } - return profile.addresses[row] - } - - func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/HostServiceView.xib b/Passepartout/App/macOS/Scenes/Service/HostServiceView.xib deleted file mode 100644 index 44a6a5c6..00000000 --- a/Passepartout/App/macOS/Scenes/Service/HostServiceView.xib +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/macOS/Scenes/Service/ProviderServiceView.swift b/Passepartout/App/macOS/Scenes/Service/ProviderServiceView.swift deleted file mode 100644 index 7dc05ead..00000000 --- a/Passepartout/App/macOS/Scenes/Service/ProviderServiceView.swift +++ /dev/null @@ -1,326 +0,0 @@ -// -// ProviderServiceView.swift -// Passepartout -// -// Created by Davide De Rosa on 6/13/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore - -protocol ProviderServiceViewDelegate: AnyObject { - func providerView(_ providerView: ProviderServiceView, didSelectPool pool: Pool) - - func providerViewDidRequestInfrastructureRefresh(_ providerView: ProviderServiceView) -} - -class ProviderServiceView: NSView { - @IBOutlet private weak var labelCategoryCaption: NSTextField! - - @IBOutlet private weak var popupCategory: NSPopUpButton! - - @IBOutlet private weak var labelLocationCaption: NSTextField! - - @IBOutlet private weak var popupLocation: NSPopUpButton! - - @IBOutlet private weak var popupArea: NSPopUpButton! - - @IBOutlet private weak var checkOnlyShowsFavorites: NSButton! - - @IBOutlet private weak var labelLastInfrastructureUpdate: NSTextField! - - @IBOutlet private weak var buttonRefreshInfrastructure: NSButton! - - @IBOutlet private weak var buttonFavorite: NSButton! - - var isEnabled: Bool = true { - didSet { - popupCategory.isEnabled = isEnabled - popupLocation.isEnabled = isEnabled - popupArea.isEnabled = isEnabled - } - } - - var profile: ProviderConnectionProfile? { - didSet { - guard let profile = profile else { - popupCategory.removeAllItems() - popupLocation.removeAllItems() - popupArea.removeAllItems() - return - } - reloadHierarchy(withProfile: profile) - } - } - - var isRefreshingInfrastructure: Bool = false { - didSet { - buttonRefreshInfrastructure.isEnabled = !isRefreshingInfrastructure - } - } - - private var onlyShowsFavorites: Bool = false { - didSet { - guard let profile = profile else { - return - } - reloadHierarchy(withProfile: profile) - } - } - - private var categories: [PoolCategory] = [] - - private var filteredGroupsByCategory: [String: [PoolGroup]] = [:] - - weak var delegate: ProviderServiceViewDelegate? - - override func viewWillMove(toSuperview newSuperview: NSView?) { - super.viewWillMove(toSuperview: newSuperview) - - labelCategoryCaption.stringValue = L10n.Service.Cells.Category.caption.asCaption - labelLocationCaption.stringValue = L10n.Service.Cells.Provider.Pool.caption.asCaption - checkOnlyShowsFavorites.title = L10n.Service.Cells.OnlyShowsFavorites.caption - checkOnlyShowsFavorites.state = .off - buttonRefreshInfrastructure.image = NSImage(named: NSImage.refreshTemplateName) - buttonRefreshInfrastructure.toolTip = L10n.Service.Cells.Provider.Refresh.caption - buttonFavorite.image = NSImage(named: NSImage.bookmarksTemplateName) - - updateFavoriteState() - } - - // MARK: Actions - - @IBAction private func selectCategory(_ sender: Any?) { - loadLocations() - loadAreas() - delegateSelectedPool() - } - - @IBAction private func selectLocation(_ sender: Any?) { - loadAreas() - delegateSelectedPool() - } - - @IBAction private func selectArea(_ sender: Any?) { - guard let pool = popupArea.selectedItem?.representedObject as? Pool else { - return - } - delegate?.providerView(self, didSelectPool: pool) - } - - @IBAction private func refreshInfrastructure(_ sender: Any?) { - delegate?.providerViewDidRequestInfrastructureRefresh(self) - } - - @IBAction private func toggleFavorite(_ sender: Any?) { - guard let category = selectedCategory(), let group = selectedGroup() else { - return - } - let groupId = group.uniqueId(in: category) - let isFavorite = (buttonFavorite.state == .on) - if isFavorite { - profile?.favoriteGroupIds = profile?.favoriteGroupIds ?? [] - profile?.favoriteGroupIds?.append(groupId) - buttonFavorite.toolTip = L10n.Provider.Pool.Actions.unfavorite - } else { - profile?.favoriteGroupIds?.removeAll { $0 == groupId } - buttonFavorite.toolTip = L10n.Provider.Pool.Actions.favorite - } - - // disable favorite while filtering favorites - // - // 1. reload list to select first - // 2. if last, disable filter - if onlyShowsFavorites, let profile = profile, buttonFavorite.state == .off { - if popupLocation.numberOfItems == 1 { - onlyShowsFavorites = false - checkOnlyShowsFavorites.state = .off - } - reloadHierarchy(withProfile: profile) - delegateSelectedPool() - } - if profile?.favoriteGroupIds?.isEmpty ?? true { - checkOnlyShowsFavorites.state = .off - checkOnlyShowsFavorites.isEnabled = false - } else { - checkOnlyShowsFavorites.isEnabled = true - } - } - - @IBAction private func toggleOnlyShowsFavorites(_ sender: Any?) { - onlyShowsFavorites = (checkOnlyShowsFavorites.state == .on) - delegateSelectedPool() - } - - // MARK: Helpers - - func reloadData() { - guard let profile = profile else { - return - } - reloadHierarchy(withProfile: profile) - } - - private func reloadHierarchy(withProfile profile: ProviderConnectionProfile) { - categories = profile.infrastructure.categories.sorted { $0.name.lowercased() < $1.name.lowercased() } - popupCategory.removeAllItems() - filteredGroupsByCategory.removeAll() - - let menu = NSMenu() - categories.forEach { category in - let item = NSMenuItem() - item.title = !category.name.isEmpty ? category.name.capitalized : L10n.Global.Values.default - item.representedObject = category - menu.addItem(item) - - setFilteredGroups(category.groups, forCategory: category) - } - popupCategory.menu = menu - - let (a, b, c) = selectPopupsFromCurrentProfile() - if popupCategory.numberOfItems > 0 { - popupCategory.selectItem(at: a) - loadLocations() - if popupLocation.numberOfItems > 0 { - popupLocation.selectItem(at: b) - loadAreas() - if popupArea.numberOfItems > 0 { - popupArea.selectItem(at: c) - } - } - } - - if let lastInfrastructureUpdate = InfrastructureFactory.shared.modificationDate(forName: profile.name) { - labelLastInfrastructureUpdate.stringValue = L10n.Service.Sections.ProviderInfrastructure.footer(lastInfrastructureUpdate.timestamp) - } - - checkOnlyShowsFavorites.isEnabled = !(profile.favoriteGroupIds?.isEmpty ?? true) - } - - // FIXME: inefficient, cache sorted pools - private func selectPopupsFromCurrentProfile() -> (Int, Int, Int) { - var a = 0, b = 0, c = 0 - for category in categories { - b = 0 - for group in filteredGroups(forCategory: category) { - c = 0 - for pool in group.pools.sortedPools() { - if pool.id == profile?.poolId { - return (a, b, c) - } - c += 1 - } - b += 1 - } - a += 1 - } - return (0, 0, 0) - } - - private func loadLocations() { - guard let category = popupCategory.selectedItem?.representedObject as? PoolCategory else { - return - } - popupLocation.removeAllItems() - - let menu = NSMenu() - filteredGroups(forCategory: category).forEach { - let item = NSMenuItem(title: $0.localizedCountry, action: nil, keyEquivalent: "") - item.image = $0.logo - item.representedObject = $0 // group - menu.addItem(item) - } - popupLocation.menu = menu - } - - private func loadAreas() { - guard let group = popupLocation.selectedItem?.representedObject as? PoolGroup else { - return - } - popupArea.removeAllItems() - - // FIXME: inefficient, cache sorted pools - let menu = NSMenu() - let pools = group.pools.sortedPools() - pools.forEach { - guard !$0.secondaryId.isEmpty || pools.count > 1 else { - return - } - let title = !$0.secondaryId.isEmpty ? $0.secondaryId : L10n.Global.Values.default - let item = NSMenuItem(title: title, action: nil, keyEquivalent: "") - if let extraCountry = $0.extraCountries?.first { - item.image = extraCountry.image - } - item.representedObject = $0 // pool - menu.addItem(item) - } - popupArea.menu = menu - popupArea.isHidden = menu.items.isEmpty - } - - private func selectedCategory() -> PoolCategory? { - return popupCategory.selectedItem?.representedObject as? PoolCategory - } - - private func selectedGroup() -> PoolGroup? { - return popupLocation.selectedItem?.representedObject as? PoolGroup - } - - private func selectedPool() -> Pool? { - guard popupArea.numberOfItems > 0 else { - guard let group = popupLocation.selectedItem?.representedObject as? PoolGroup else { - return nil - } - return group.pools.first - } - return popupArea.itemArray.first?.representedObject as? Pool - } - - private func updateFavoriteState() { - guard let category = selectedCategory(), let group = selectedGroup() else { - return - } - let groupId = group.uniqueId(in: category) - let isFavorite = profile?.favoriteGroupIds?.contains(groupId) ?? false - buttonFavorite.state = isFavorite ? .on : .off - buttonFavorite.toolTip = (isFavorite ? L10n.Provider.Pool.Actions.unfavorite : L10n.Provider.Pool.Actions.favorite) - } - - private func delegateSelectedPool() { - if let pool = selectedPool() { - updateFavoriteState() - delegate?.providerView(self, didSelectPool: pool) - } - } - - private func filteredGroups(forCategory category: PoolCategory) -> [PoolGroup] { - return filteredGroupsByCategory[category.name] ?? [] - } - - private func setFilteredGroups(_ groups: [PoolGroup], forCategory category: PoolCategory) { - filteredGroupsByCategory[category.name] = category.groups.filter { - guard !onlyShowsFavorites else { - return profile?.favoriteGroupIds?.contains($0.uniqueId(in: category)) ?? false - } - return true - }.sorted() - } -} diff --git a/Passepartout/App/macOS/Scenes/Service/ProviderServiceView.xib b/Passepartout/App/macOS/Scenes/Service/ProviderServiceView.xib deleted file mode 100644 index c3f7bfee..00000000 --- a/Passepartout/App/macOS/Scenes/Service/ProviderServiceView.xib +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/macOS/Scenes/Service/ServiceViewController.swift b/Passepartout/App/macOS/Scenes/Service/ServiceViewController.swift deleted file mode 100644 index 742319bc..00000000 --- a/Passepartout/App/macOS/Scenes/Service/ServiceViewController.swift +++ /dev/null @@ -1,386 +0,0 @@ -// -// ServiceViewController.swift -// Passepartout -// -// Created by Davide De Rosa on 7/29/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa -import PassepartoutCore -import SwiftyBeaver -import Convenience - -private let log = SwiftyBeaver.self - -class ServiceViewController: NSViewController { - @IBOutlet private weak var labelWelcome: NSTextField! - - @IBOutlet private weak var viewVPN: NSView! - - @IBOutlet private weak var viewProfile: NSView! - - @IBOutlet private weak var viewFooter: NSView! - - @IBOutlet private weak var labelStatusCaption: NSTextField! - - @IBOutlet private weak var labelStatus: NSTextField! - - @IBOutlet private weak var activityVPN: NSProgressIndicator! - - @IBOutlet private weak var labelServiceDescription: NSTextField! - - @IBOutlet private weak var viewInactive: NSView! - - @IBOutlet private weak var buttonUse: NSButton! - - @IBOutlet private weak var viewActive: NSView! - - @IBOutlet private weak var buttonToggle: NSButton! - - @IBOutlet private weak var buttonReconnect: NSButton! - - @IBOutlet private weak var labelConnectedLocation: NSTextField! - - @IBOutlet private weak var buttonCustomize: NSButton! - - @IBOutlet private weak var buttonAccount: NSButton! - - @IBOutlet private weak var viewProfileContainer: NSView! - - private lazy var viewProvider: ProviderServiceView = .get() - - private lazy var viewHost: HostServiceView = .get() - - private var profile: ConnectionProfile? - - private let service = TransientStore.shared.service - - private lazy var vpn = GracefulVPN(service: service) - - private var isPendingConnection = false - - deinit { - NotificationCenter.default.removeObserver(self) - } - - func setProfile(_ profile: ConnectionProfile?) { - defer { - let hasProfile = (self.profile != nil) - labelWelcome.isHidden = hasProfile - viewProfile.isHidden = !hasProfile - viewProfileContainer.isHidden = !hasProfile - viewFooter.isHidden = !hasProfile - reloadVpnStatus() - - if let profile = self.profile, service.isActiveProfile(profile) { - viewInactive.isHidden = true - viewActive.isHidden = false - buttonToggle.isEnabled = true - } else { - viewActive.isHidden = true - viewInactive.isHidden = false - buttonUse.isEnabled = true - } - } - - if let profile = profile, let currentProfile = self.profile { - guard (profile.context != currentProfile.context) || (profile.id != currentProfile.id) else { - return - } - } - - self.profile = profile - guard let _ = profile else { - return - } - - let view: NSView - if let providerProfile = profile as? ProviderConnectionProfile { - viewProvider.profile = providerProfile - viewProvider.delegate = self - view = viewProvider - } else if let hostProfile = profile as? HostConnectionProfile { - viewHost.profile = hostProfile - viewHost.delegate = self - view = viewHost - } else { - fatalError("Unexpected profile type") - } - - view.translatesAutoresizingMaskIntoConstraints = false - viewProfileContainer.subviews.forEach { - $0.removeFromSuperview() - } - viewProfileContainer.addSubview(view) - NSLayoutConstraint.activate([ - view.topAnchor.constraint(equalTo: viewProfileContainer.topAnchor), -// view.bottomAnchor.constraint(equalTo: viewProfileContainer.bottomAnchor), -// view.centerYAnchor.constraint(equalTo: viewProfileContainer.centerYAnchor), - view.rightAnchor.constraint(equalTo: viewProfileContainer.rightAnchor), - view.leftAnchor.constraint(equalTo: viewProfileContainer.leftAnchor), - ]) - } - - override func viewDidLoad() { - super.viewDidLoad() - - if profile == nil { - setProfile(service.activeProfile) - } - - // enforce on macOS - service.preferences.disconnectsOnSleep = true - - labelWelcome.stringValue = L10n.Service.Welcome.message - labelStatusCaption.stringValue = L10n.Service.Cells.ConnectionStatus.caption.asCaption - labelServiceDescription.stringValue = L10n.Service.Sections.Vpn.footer - buttonUse.title = L10n.Service.Cells.UseProfile.caption - buttonToggle.title = L10n.Service.Cells.Vpn.TurnOn.caption - buttonReconnect.title = L10n.Service.Cells.Reconnect.caption - buttonCustomize.image = NSImage(named: NSImage.actionTemplateName) - buttonAccount.title = L10n.Account.title.asContinuation - - let nc = NotificationCenter.default - nc.addObserver(self, selector: #selector(vpnDidUpdate), name: VPN.didChangeStatus, object: nil) - nc.addObserver(self, selector: #selector(vpnDidReinstall), name: VPN.didReinstall, object: nil) - - vpn.prepare { - self.reloadVpnStatus() - } - } - - // MARK: Actions - - @IBAction private func activateProfile(_ sender: Any?) { - service.activateProfile(uncheckedProfile) - vpn.disconnect(completionHandler: nil) - } - - @IBAction private func toggleVpnService(_ sender: Any?) { - guard let profile = profile else { - return - } - -// let status: VPNStatus -// if service.isActiveProfile(profile) { -// status = vpn.status ?? .disconnected -// } else { -// -// // force reconnection when activating a different profile -// status = .disconnected -// } - service.activateProfile(profile) - - if !vpn.isEnabled { - guard !service.needsCredentials(for: uncheckedProfile) else { - isPendingConnection = true - perform(segue: StoryboardSegue.Service.accountSegueIdentifier) - return - } - vpn.reconnect { _ in - self.reloadVpnStatus() - } - } else { - vpn.disconnect { _ in - self.reloadVpnStatus() - } - } - } - - @IBAction private func reconnectVPN(_ sender: Any?) { - GracefulVPN(service: service).reconnect(completionHandler: nil) - } - -// @IBAction private func cycleConnection(_ sender: Any?) { -// guard vpn.isEnabled else { -// return -// } -//// guard vpn.status == .disconnected else { -//// let alert = Macros.alert( -//// L10n.Service.Cells.ConnectionStatus.caption, -//// L10n.Service.Alerts.ReconnectVpn.message -//// ) -//// alert.addDefaultAction(L10n.Global.ok) { -//// self.vpn.reconnect(configuration: self.currentVpnConfiguration(), completionHandler: nil) -//// } -//// alert.addCancelAction(L10n.Global.cancel) -//// present(alert, animated: true, completion: nil) -//// return -//// } -// vpn.reconnect(completionHandler: nil) -// } - - @IBAction private func customizeProfile(_ sender: Any?) { - perform(segue: StoryboardSegue.Service.customizeSegueIdentifier) - } - - override func prepare(for segue: NSStoryboardSegue, sender: Any?) { - if let accountVC = segue.destinationController as? AccountViewController { - accountVC.profile = profile - accountVC.delegate = self - } else if let customVC = segue.destinationController as? ProfileCustomizationContainerViewController { - customVC.profile = profile - } - } - - // MARK: Notifications - - @objc private func vpnDidUpdate() { - reloadVpnStatus() - - guard let status = vpn.status else { - return - } - log.debug("VPN.status: \(status)") - switch status { - case .connected: - Reviewer.shared.reportEvent() - - default: - break - } - } - - @objc private func vpnDidReinstall() { - viewProvider.reloadData() - viewHost.reloadData() - } - - // MARK: Helpers - - private func reloadVpnStatus() { - labelConnectedLocation.stringValue = "" - guard let profile = profile else { - return - } - let isActive = service.isActiveProfile(profile) - guard isActive && vpn.isEnabled else { - labelStatus.applyVPN(Theme.current, isActive: isActive, with: nil, error: nil) - activityVPN.stopAnimation(nil) - buttonToggle.title = L10n.Service.Cells.Vpn.TurnOn.caption - buttonToggle.isEnabled = true - buttonReconnect.isEnabled = false - return - } - - labelStatus.applyVPN(Theme.current, isActive: isActive, with: vpn.status, error: service.vpnLastError) - buttonToggle.title = vpn.isEnabled ? L10n.Service.Cells.Vpn.TurnOff.caption : L10n.Service.Cells.Vpn.TurnOn.caption - buttonReconnect.isEnabled = true - - // append connected location for providers - if vpn.status == .connected, let providerProfile = profile as? ProviderConnectionProfile { - labelConnectedLocation.stringValue = "(\(providerProfile.pool?.localizedCountry ?? ""))" - } - - switch vpn.status ?? .disconnected { - case .connected: - activityVPN.stopAnimation(nil) - - case .disconnected: - activityVPN.stopAnimation(nil) - - case .connecting: - activityVPN.startAnimation(nil) - - case .disconnecting: - activityVPN.startAnimation(nil) - } - } -} - -extension ServiceViewController: AccountViewControllerDelegate { - func accountController(_ accountController: AccountViewController, shouldUpdateCredentials credentials: Credentials, forProfile profile: ConnectionProfile) -> Bool { - guard profile.requiresCredentials else { - return true - } - return credentials.isValid - } - - func accountController(_ accountController: AccountViewController, didUpdateCredentials credentials: Credentials, forProfile profile: ConnectionProfile) { - if isPendingConnection { - isPendingConnection = false - vpn.reconnect(completionHandler: nil) - } - StatusMenu.shared.refreshWithCurrentProfile() - } - - func accountControllerDidCancel(_ accountController: AccountViewController) { - isPendingConnection = false - } -} - -extension ServiceViewController: ProviderServiceViewDelegate { - func providerView(_ providerView: ProviderServiceView, didSelectPool pool: Pool) { - - // fall back to a supported preset - let supportedPresets = pool.supportedPresetIds(in: uncheckedProviderProfile.infrastructure) - if let presetId = uncheckedProviderProfile.preset?.id, !supportedPresets.contains(presetId), - let fallback = supportedPresets.first { - - uncheckedProviderProfile.presetId = fallback - } - - uncheckedProviderProfile.poolId = pool.id -// vpn.reinstallIfEnabled() - } - - func providerViewDidRequestInfrastructureRefresh(_ providerView: ProviderServiceView) { - let name = uncheckedProviderProfile.name - - viewProvider.isRefreshingInfrastructure = true - let isUpdating = InfrastructureFactory.shared.update(name, notBeforeInterval: AppConstants.Services.minimumUpdateInterval) { (response, error) in - self.viewProvider.isRefreshingInfrastructure = false - guard let _ = response else { - return - } - self.viewProvider.reloadData() - } - if !isUpdating { - viewProvider.isRefreshingInfrastructure = false - } - } -} - -extension ServiceViewController: HostServiceViewDelegate { -} - -private extension ServiceViewController { - private var uncheckedProfile: ConnectionProfile { - guard let profile = profile else { - fatalError("Expected non-nil profile here") - } - return profile - } - - private var uncheckedProviderProfile: ProviderConnectionProfile { - guard let profile = profile as? ProviderConnectionProfile else { - fatalError("Expected ProviderConnectionProfile (found: \(type(of: self.profile)))") - } - return profile - } - - private var uncheckedHostProfile: HostConnectionProfile { - guard let profile = profile as? HostConnectionProfile else { - fatalError("Expected HostConnectionProfile (found: \(type(of: self.profile)))") - } - return profile - } -} diff --git a/Passepartout/App/macOS/Tables/TextTableView.swift b/Passepartout/App/macOS/Tables/TextTableView.swift deleted file mode 100644 index c60d58c8..00000000 --- a/Passepartout/App/macOS/Tables/TextTableView.swift +++ /dev/null @@ -1,159 +0,0 @@ -// -// TextTableView.swift -// Passepartout -// -// Created by Davide De Rosa on 6/20/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Cocoa - -class TextTableView: NSView { - @IBOutlet private weak var labelTitle: NSTextField! - - @IBOutlet private weak var tableView: NSTableView! - - @IBOutlet private weak var buttonAdd: NSButton! - - @IBOutlet private weak var buttonRemove: NSButton! - - var title = "" - - private(set) var rows: [String] = [] - - var selectedRow: Int? { - didSet { - guard let _ = selectedRow else { - tableView.deselectColumn(0) - return - } - } - } - - var selectionBlock: ((String) -> Void)? - - var deselectionBlock: (() -> Void)? - - var updateBlock: (() -> Void)? - - var rowTemplate = "" - - var isEnabled: Bool = true { - didSet { - isAddEnabled = isEnabled - isRemoveEnabled = isEnabled - } - } - - var isAddEnabled: Bool { - get { - return buttonAdd.isEnabled - } - set { - buttonAdd.isEnabled = newValue - } - } - - var isRemoveEnabled: Bool { - get { - return buttonRemove.isEnabled - } - set { - buttonRemove.isEnabled = newValue - } - } - - override func viewWillMove(toSuperview newSuperview: NSView?) { - super.viewWillMove(toSuperview: newSuperview) - - labelTitle.stringValue = title - buttonAdd.image = NSImage(named: NSImage.addTemplateName) - buttonRemove.image = NSImage(named: NSImage.removeTemplateName) - - if let i = selectedRow { - tableView.reloadData() - tableView.selectRowIndexes(IndexSet(integer: i), byExtendingSelection: true) - } - } - - // MARK: Actions - - func reset(withRows rows: [String], isAddEnabled: Bool) { - endEditing() - self.rows = rows - self.isAddEnabled = isAddEnabled - isRemoveEnabled = false - selectedRow = nil - reloadData() - } - - func reloadData() { - tableView.reloadData() - } - - @IBAction private func addElement(_ sender: Any?) { - rows.append(rowTemplate) - tableView.reloadData() - tableView.editColumn(0, row: rows.count - 1, with: nil, select: true) - updateBlock?() - } - - @IBAction private func removeElement(_ sender: Any?) { - let index = tableView.selectedRow - guard index != -1 else { - return - } - rows.remove(at: index) - tableView.reloadData() - updateBlock?() - } -} - -extension TextTableView: NSTableViewDataSource, NSTableViewDelegate { - func numberOfRows(in tableView: NSTableView) -> Int { - return rows.count - } - - func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { - return rows[row] - } - - func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { - defer { - tableView.reloadData() - } - guard let string = object as? String, !string.isEmpty else { - rows.remove(at: row) - return - } - rows[row] = string.stripped - } - - func tableViewSelectionDidChange(_ notification: Notification) { - let index = tableView.selectedRow - guard index != -1 else { - isRemoveEnabled = false - deselectionBlock?() - return - } - isRemoveEnabled = true - selectionBlock?(rows[index]) - } -} diff --git a/Passepartout/App/macOS/Tables/TextTableView.xib b/Passepartout/App/macOS/Tables/TextTableView.xib deleted file mode 100644 index 2f42a6b5..00000000 --- a/Passepartout/App/macOS/Tables/TextTableView.xib +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Passepartout/App/macOS/swiftgen.yml b/Passepartout/App/macOS/swiftgen.yml deleted file mode 100644 index d502e5d3..00000000 --- a/Passepartout/App/macOS/swiftgen.yml +++ /dev/null @@ -1,21 +0,0 @@ -ib: - inputs: - - Base.lproj/Main.storyboard - - Base.lproj/Preferences.storyboard - - Base.lproj/Purchase.storyboard - - Base.lproj/Service.storyboard - #- Base.lproj/Shortcuts.storyboard - outputs: - - templateName: scenes-swift4 - output: Global/SwiftGen+Scenes.swift - - templateName: segues-swift4 - output: Global/SwiftGen+Segues.swift - -xcassets: - inputs: - - Assets.xcassets - - Flags.xcassets - - Providers.xcassets - outputs: - - templateName: swift4 - output: Global/SwiftGen+Assets.swift diff --git a/Passepartout/Shared/Constants.swift b/Passepartout/Shared/Constants.swift new file mode 100644 index 00000000..9ad20bd1 --- /dev/null +++ b/Passepartout/Shared/Constants.swift @@ -0,0 +1,38 @@ +// +// Constants.swift +// Passepartout +// +// Created by Davide De Rosa on 6/7/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +enum Constants { + enum Global { + static let appName = "Passepartout" + + static let appVersionNumber = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String + + static let appBuildNumber = Int(Bundle.main.infoDictionary![kCFBundleVersionKey as String] as! String)! + + static let appVersionString = "\(appVersionNumber) (\(appBuildNumber))" + } +} diff --git a/Passepartout/Tunnel/Info.plist b/Passepartout/Tunnel/Info.plist index 2cca48ad..dd9965da 100644 --- a/Passepartout/Tunnel/Info.plist +++ b/Passepartout/Tunnel/Info.plist @@ -4,8 +4,6 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - PassepartoutTunnel CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -17,9 +15,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.18.0 + 2.0.0 CFBundleVersion - 2984 + 3000 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSExtension diff --git a/Passepartout/Tunnel/OpenVPN/Constants+Tunnel.swift b/Passepartout/Tunnel/OpenVPN/Constants+Tunnel.swift new file mode 100644 index 00000000..14fdb2d1 --- /dev/null +++ b/Passepartout/Tunnel/OpenVPN/Constants+Tunnel.swift @@ -0,0 +1,36 @@ +// +// Constants+Tunnel.swift +// Passepartout +// +// Created by Davide De Rosa on 3/22/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension Constants { + enum OpenVPNTunnel { + static let dnsTimeout = 5000 + + static let sessionMarker = "--- EOF ---" + + static let dataCountInterval = 5000 + } +} diff --git a/Passepartout/Tunnel/PacketTunnelProvider.swift b/Passepartout/Tunnel/OpenVPN/PacketTunnelProvider.swift similarity index 78% rename from Passepartout/Tunnel/PacketTunnelProvider.swift rename to Passepartout/Tunnel/OpenVPN/PacketTunnelProvider.swift index 552da3de..fd9e39d1 100644 --- a/Passepartout/Tunnel/PacketTunnelProvider.swift +++ b/Passepartout/Tunnel/OpenVPN/PacketTunnelProvider.swift @@ -24,15 +24,14 @@ // import Foundation -import PassepartoutConstants -import PassepartoutOpenVPNTunnel +import OpenVPNAppExtension class PacketTunnelProvider: OpenVPNTunnelProvider { override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { - appVersion = "\(GroupConstants.App.name) \(GroupConstants.App.versionString)" - dnsTimeout = GroupConstants.VPN.dnsTimeout - logSeparator = GroupConstants.VPN.sessionMarker - dataCountInterval = GroupConstants.VPN.dataCountInterval + appVersion = "\(Constants.Global.appName) \(Constants.Global.appVersionString)" + dnsTimeout = Constants.OpenVPNTunnel.dnsTimeout + logSeparator = Constants.OpenVPNTunnel.sessionMarker + dataCountInterval = Constants.OpenVPNTunnel.dataCountInterval super.startTunnel(options: options, completionHandler: completionHandler) } } diff --git a/Passepartout/Tunnel/Tunnel-iOS.entitlements b/Passepartout/Tunnel/Tunnel-iOS.entitlements deleted file mode 100644 index 93208e36..00000000 --- a/Passepartout/Tunnel/Tunnel-iOS.entitlements +++ /dev/null @@ -1,18 +0,0 @@ - - - - - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - com.apple.security.application-groups - - group.com.algoritmico.Passepartout - - keychain-access-groups - - $(AppIdentifierPrefix)group.com.algoritmico.Passepartout - - - diff --git a/Passepartout/Tunnel/Tunnel-macOS.entitlements b/Passepartout/Tunnel/Tunnel.entitlements similarity index 88% rename from Passepartout/Tunnel/Tunnel-macOS.entitlements rename to Passepartout/Tunnel/Tunnel.entitlements index a10bddd8..c9c64546 100644 --- a/Passepartout/Tunnel/Tunnel-macOS.entitlements +++ b/Passepartout/Tunnel/Tunnel.entitlements @@ -10,7 +10,7 @@ com.apple.security.application-groups - $(TeamIdentifierPrefix)group.com.algoritmico.Passepartout + group.com.algoritmico.Passepartout com.apple.security.network.client diff --git a/Passepartout/App/macOS/Menu/MainMenu.swift b/Passepartout/Tunnel/WireGuard/PacketTunnelProvider.swift similarity index 79% rename from Passepartout/App/macOS/Menu/MainMenu.swift rename to Passepartout/Tunnel/WireGuard/PacketTunnelProvider.swift index d0f5458f..4188d761 100644 --- a/Passepartout/App/macOS/Menu/MainMenu.swift +++ b/Passepartout/Tunnel/WireGuard/PacketTunnelProvider.swift @@ -1,8 +1,8 @@ // -// MainMenu.swift +// PacketTunnelProvider.swift // Passepartout // -// Created by Davide De Rosa on 8/13/19. +// Created by Davide De Rosa on 3/15/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -23,10 +23,8 @@ // along with Passepartout. If not, see . // -import Cocoa +import Foundation +import WireGuardAppExtension -class MainMenu: NSObject { - @IBAction private func showPreferences(_ sender: Any?) { - WindowManager.shared.showPreferences() - } +class PacketTunnelProvider: WireGuardTunnelProvider { } diff --git a/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutOpenVPNTunnel.xcscheme b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/OpenVPNAppExtension.xcscheme similarity index 83% rename from PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutOpenVPNTunnel.xcscheme rename to PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/OpenVPNAppExtension.xcscheme index 176fa5f3..95f7241c 100644 --- a/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutOpenVPNTunnel.xcscheme +++ b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/OpenVPNAppExtension.xcscheme @@ -1,6 +1,6 @@ @@ -50,9 +50,9 @@ diff --git a/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCore-Package.xcscheme b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCore-Package.xcscheme new file mode 100644 index 00000000..f9b5e0bd --- /dev/null +++ b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCore-Package.xcscheme @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCore.xcscheme b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCore.xcscheme index 79bd10d3..43fa3241 100644 --- a/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCore.xcscheme +++ b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCore.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutProvidersTests.xcscheme b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutProvidersTests.xcscheme new file mode 100644 index 00000000..c453aee2 --- /dev/null +++ b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutProvidersTests.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutServicesTests.xcscheme b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutServicesTests.xcscheme new file mode 100644 index 00000000..133df36b --- /dev/null +++ b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutServicesTests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCoreTests.xcscheme b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutUtilsTests.xcscheme similarity index 88% rename from PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCoreTests.xcscheme rename to PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutUtilsTests.xcscheme index 385157fc..e4c71372 100644 --- a/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCoreTests.xcscheme +++ b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutUtilsTests.xcscheme @@ -1,6 +1,6 @@ diff --git a/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/WireGuardAppExtension.xcscheme b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/WireGuardAppExtension.xcscheme new file mode 100644 index 00000000..c1086649 --- /dev/null +++ b/PassepartoutCore/.swiftpm/xcode/xcshareddata/xcschemes/WireGuardAppExtension.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutCore/Package.swift b/PassepartoutCore/Package.swift index c484dcca..87ad8ddf 100644 --- a/PassepartoutCore/Package.swift +++ b/PassepartoutCore/Package.swift @@ -5,9 +5,8 @@ import PackageDescription let package = Package( name: "PassepartoutCore", - defaultLocalization: "en", platforms: [ - .iOS(.v12), .macOS(.v10_14) + .iOS(.v14), .macOS(.v11) ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -15,48 +14,80 @@ let package = Package( name: "PassepartoutCore", targets: ["PassepartoutCore"]), .library( - name: "PassepartoutOpenVPNTunnel", - targets: ["PassepartoutOpenVPNTunnel"]) + name: "OpenVPNAppExtension", + targets: ["OpenVPNAppExtension"]), + .library( + name: "WireGuardAppExtension", + targets: ["WireGuardAppExtension"]) ], dependencies: [ // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), - .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", from: "4.1.0"), +// .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", from: "4.1.0"), // .package(name: "TunnelKit", url: "https://github.com/passepartoutvpn/tunnelkit", .revision("871e51517c5678d9c683104bd6b0617d5eb2641e")), -// .package(name: "TunnelKit", path: "../../tunnelkit"), - .package(name: "Convenience", url: "https://github.com/keeshux/convenience", .revision("347105ec0ce27cd4255acf9875fd60ad1f213801")), - .package(url: "https://github.com/Cocoanetics/Kvitto", from: "1.0.0") + .package(name: "TunnelKit", path: "../../tunnelkit"), + .package(url: "https://github.com/zoul/generic-json-swift", from: "2.0.0"), + .package(url: "https://github.com/SwiftyBeaver/SwiftyBeaver", from: "1.9.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. - .target( - name: "PassepartoutConstants", - dependencies: []), .target( name: "PassepartoutCore", dependencies: [ - "PassepartoutConstants", + "PassepartoutProfiles", + "PassepartoutProviders" + ]), + .target( + name: "PassepartoutProfiles", + dependencies: [ + "PassepartoutProviders", .product(name: "TunnelKit", package: "TunnelKit"), .product(name: "TunnelKitOpenVPN", package: "TunnelKit"), - .product(name: "TunnelKitLZO", package: "TunnelKit"), - "Convenience", - .product(name: "ConvenienceUI", package: "Convenience", condition: .when(platforms: [.iOS])), - "Kvitto" - ], + .product(name: "TunnelKitWireGuard", package: "TunnelKit"), + .product(name: "TunnelKitLZO", package: "TunnelKit") + ]), + .target( + name: "PassepartoutProviders", + dependencies: ["PassepartoutServices"]), + .target( + name: "PassepartoutServices", + dependencies: ["PassepartoutUtils"], resources: [ .copy("API") ]), .target( - name: "PassepartoutOpenVPNTunnel", + name: "PassepartoutUtils", + dependencies: [ + .product(name: "GenericJSON", package: "generic-json-swift"), + "SwiftyBeaver" + ]), + .target( + name: "OpenVPNAppExtension", dependencies: [ - "PassepartoutConstants", .product(name: "TunnelKitOpenVPNAppExtension", package: "TunnelKit"), .product(name: "TunnelKitLZO", package: "TunnelKit") ]), + .target( + name: "WireGuardAppExtension", + dependencies: [ + .product(name: "TunnelKitWireGuardAppExtension", package: "TunnelKit") + ]), +// .testTarget( +// name: "PassepartoutCoreTests", +// dependencies: ["PassepartoutCore"]), .testTarget( - name: "PassepartoutCoreTests", - dependencies: ["PassepartoutCore"], + name: "PassepartoutProfilesTests", + dependencies: ["PassepartoutProfiles"]), + .testTarget( + name: "PassepartoutProvidersTests", + dependencies: ["PassepartoutProviders"]), + .testTarget( + name: "PassepartoutServicesTests", + dependencies: ["PassepartoutServices"]), + .testTarget( + name: "PassepartoutUtilsTests", + dependencies: ["PassepartoutUtils"], resources: [ .process("Resources") ]) diff --git a/PassepartoutCore/Sources/PassepartoutOpenVPNTunnel/Exports.swift b/PassepartoutCore/Sources/OpenVPNAppExtension/Exports.swift similarity index 100% rename from PassepartoutCore/Sources/PassepartoutOpenVPNTunnel/Exports.swift rename to PassepartoutCore/Sources/OpenVPNAppExtension/Exports.swift diff --git a/PassepartoutCore/Sources/PassepartoutConstants/AppConstants.swift b/PassepartoutCore/Sources/PassepartoutConstants/AppConstants.swift deleted file mode 100644 index a8e130f4..00000000 --- a/PassepartoutCore/Sources/PassepartoutConstants/AppConstants.swift +++ /dev/null @@ -1,331 +0,0 @@ -// -// AppConstants.swift -// Passepartout -// -// Created by Davide De Rosa on 9/15/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation - -public class AppConstants { - public class App { - public static let appLauncherId = GroupConstants.App.config?["app_launcher_id"] as? String ?? "DUMMY_app_launcher_id" - - public static let appStoreId = GroupConstants.App.config?["appstore_id"] as? String ?? "DUMMY_appstore_id" - - public static let tunnelBundleId: String = { - guard let identifier = Bundle.main.infoDictionary?[kCFBundleIdentifierKey as String] as? String else { - fatalError("Missing kCFBundleIdentifierKey from Info.plist") - } - return "\(identifier).Tunnel" - }() - } - - public class Domain { - public static let name = "passepartoutvpn.app" - } - - public class Store { - public static let serviceFilename = "ConnectionService.json" - - public static let apiDirectory = "API/\(Services.version)" - - public static let providersDirectory = "Providers" - - public static let hostsDirectory = "Hosts" - } - - public class Services { - public static let version = "v4" - - public static func apiURL(version: String, path: String) -> URL { - return Repos.api.appendingPathComponent(version).appendingPathComponent(path) - } - - public static let timeout: TimeInterval = 3.0 - - public static let minimumUpdateInterval: TimeInterval = 600.0 // 10 minutes - - private static let connectivityStrings: [String] = [ - "https://www.amazon.com", - "https://www.google.com", - "https://www.twitter.com", - "https://www.facebook.com", - "https://www.instagram.com" - ] - - public static let connectivityURL = URL(string: connectivityStrings.randomElement()!)! - - public static let connectivityTimeout: TimeInterval = 10.0 - } - - public class Log { - public static let debugFormat = "$DHH:mm:ss$d - $M" - - public static let viewerRefreshInterval: TimeInterval = 3.0 - - private static let fileName = "Debug.log" - - public static var fileURL: URL { - return GroupConstants.App.cachesURL.appendingPathComponent(fileName) - } - } - - public class IssueReporter { - public class Email { - public static let recipient = "issues@\(Domain.name)" - - public static let subject = "\(GroupConstants.App.name) - Report issue" - - public static func body(_ description: String, _ metadata: String) -> String { - return "Hi,\n\n\(description)\n\n\(metadata)\n\nRegards" - } - - public static let template = "description of the issue: " - } - - public class Filenames { - public static var debugLog: String { - let fmt = DateFormatter() - fmt.dateFormat = "yyyyMMdd-HHmmss" - let iso = fmt.string(from: Date()) - return "debug-\(iso).txt" - } - - public static let configuration = "profile.ovpn" -// public static let configuration = "profile.ovpn.txt" - } - - public class MIME { - public static let debugLog = "text/plain" - -// public static let configuration = "application/x-openvpn-profile" - public static let configuration = "text/plain" - } - } - - public class Translations { - public class Email { - public static let recipient = "translate@\(Domain.name)" - - public static let subject = "\(GroupConstants.App.name) - Translations" - - public static func body(_ description: String) -> String { - return "Hi,\n\n\(description)\n\nRegards" - } - - public static let template = "I offer to translate to: " - } - - public static let translators: [String: String] = [ - "de": "Christian Lederer, Theodor Tietze", - "el": "Konstantinos Koukoulakis", - "en-US": "Davide De Rosa", - "es": "Davide De Rosa, Elena Vivó", - "fr-FR": "Julien Laniel", - "it": "Davide De Rosa", - "nl": "Norbert de Vreede", - "pl": "Piotr Książek", - "pt-BR": "Helder Santana", - "ru": "Alexander Korobynikov", - "sv": "Henry Gross-Hellsen", - "zh-Hans": "OnlyThen" - ] - } - - public class URLs { - public static let readme = Repos.apple.appendingPathComponent("blob/master/README.md") - - public class iOS { - public static let changelog = Repos.apple.appendingPathComponent("blob/master/Passepartout/App/iOS/CHANGELOG.md") - } - - public class macOS { - public static let changelog = Repos.apple.appendingPathComponent("blob/master/Passepartout/App/macOS/CHANGELOG.md") - } - - public static let filetypes = ["public.content", "public.data"] - - public static let website = URL(string: "https://\(Domain.name)")! - - public static let faq = website.appendingPathComponent("faq") - - public static let disclaimer = website.appendingPathComponent("disclaimer") - - public static let privacyPolicy = website.appendingPathComponent("privacy") - - public static let donate = website.appendingPathComponent("donate") - - public static let subreddit = URL(string: "https://www.reddit.com/r/passepartout")! - - public static let twitch = URL(string: "twitch://stream/keeshux")! - - public static let twitchFallback = URL(string: "https://twitch.tv/keeshux")! - - public static let githubSponsors = URL(string: "https://www.github.com/sponsors/passepartoutvpn")! - - public static let alternativeTo = URL(string: "https://alternativeto.net/software/passepartout-vpn/about/")! - - private static let twitterHashtags = ["OpenVPN", "iOS", "macOS"] - - public static func twitterIntent(withMessage message: String) -> URL { - var text = message - for ht in twitterHashtags { - text = text.replacingOccurrences(of: ht, with: "#\(ht)") - } - var comps = URLComponents(string: "https://twitter.com/intent/tweet")! - comps.queryItems = [ - URLQueryItem(name: "url", value: website.absoluteString), - URLQueryItem(name: "via", value: "keeshux"), - URLQueryItem(name: "text", value: text) - ] - return comps.url! - } - - public static let guidances: [InfrastructureName: String] = [ - .protonvpn: "https://account.protonvpn.com/settings", - .surfshark: "https://my.surfshark.com/vpn/manual-setup/main", - .torguard: "https://torguard.net/clientarea.php?action=changepw", - .windscribe: "https://windscribe.com/getconfig/openvpn" - ] - - public static let referrals: [InfrastructureName: String] = [ - .hideme: "https://member.hide.me/en/checkout?plan=new_default_prices&coupon=6CB-BDB-802&duration=24", - .mullvad: "https://mullvad.net/en/account/create/", - .nordvpn: "https://go.nordvpn.net/SH21Z", - .pia: "https://www.privateinternetaccess.com/pages/buy-vpn/", - .protonvpn: "https://proton.go2cloud.org/SHZ", - .torguard: "https://torguard.net/", - .tunnelbear: "https://www.tunnelbear.com/", - .vyprvpn: "https://www.vyprvpn.com/", - .windscribe: "https://secure.link/kCsD0prd" - ] - - public static let externalResources: [InfrastructureName: String] = [ - .nordvpn: "https://downloads.nordcdn.com/configs/archives/certificates/servers.zip" // 9MB - ] - } - - public class Repos { - private static let githubRoot = URL(string: "https://github.com/passepartoutvpn/")! - - private static let githubRawRoot = URL(string: "https://\(Domain.name)/")! - - private static func github(repo: String) -> URL { - return githubRoot.appendingPathComponent(repo) - } - - private static func githubRaw(repo: String) -> URL { - return githubRawRoot.appendingPathComponent(repo) - } - - public static let apple = github(repo: "passepartout-apple") - - public static let api = githubRaw(repo: "api") - } - - public struct Placeholders { - public static let empty = "" - - public static let address = "0.0.0.0" - - public static let hostname = "example.com" - - public static let dohURL = "https://example.com/dns-query" - - public static let dotServerName = hostname - - public static let dnsAddress = address - - public static let dnsDomain = empty - } - - public struct Credits { - public static let author = "Davide De Rosa" - - public static let softwareArrays: [[String]] = [[ - "Kvitto", - "BSD", - "https://raw.githubusercontent.com/Cocoanetics/Kvitto/develop/LICENSE" - ], [ - "lzo", - "GPLv2", - "https://www.gnu.org/licenses/gpl-2.0.txt" - ], [ - "MBProgressHUD", - "MIT", - "https://raw.githubusercontent.com/jdg/MBProgressHUD/master/LICENSE" - ], [ - "OpenSSL", - "OpenSSL", - "https://www.openssl.org/source/license.txt" - ], [ - "PIATunnel", - "MIT", - "https://raw.githubusercontent.com/pia-foss/tunnel-apple/master/LICENSE" - ], [ - "SSZipArchive", - "MIT", - "https://raw.githubusercontent.com/samsoffes/ssziparchive/master/LICENSE" - ], [ - "SwiftGen", - "MIT", - "https://raw.githubusercontent.com/SwiftGen/SwiftGen/master/LICENCE" - ], [ - "SwiftyBeaver", - "MIT", - "https://raw.githubusercontent.com/SwiftyBeaver/SwiftyBeaver/master/LICENSE" - ], [ - "Circle Icons", - "The logo is taken from the awesome Circle Icons set by Nick Roach." - ], [ - "Country flags", - "The country flags are taken from: https://github.com/lipis/flag-icon-css/" - ], [ - "OpenVPN", - "© 2002-2018 OpenVPN Inc. - OpenVPN is a registered trademark of OpenVPN Inc." - ]] - } - - public struct Rating { - #if os(iOS) - public static let eventCount = 3 - #else - public static let eventCount = 10 - #endif - } - - public struct InApp { - public static let locksBetaFeatures = true - - #if os(iOS) - public static var isBetaFullVersion: Bool { - return ProcessInfo.processInfo.environment["FULL_VERSION"] != nil - } - - public static let lastFullVersionBuild: (Int, LocalProduct) = (2016, .fullVersion_iOS) - #else - public static let isBetaFullVersion = false - - public static let lastFullVersionBuild: (Int, LocalProduct) = (0, .fullVersion_macOS) - #endif - } -} diff --git a/PassepartoutCore/Sources/PassepartoutConstants/GroupConstants.swift b/PassepartoutCore/Sources/PassepartoutConstants/GroupConstants.swift deleted file mode 100644 index 3f97c444..00000000 --- a/PassepartoutCore/Sources/PassepartoutConstants/GroupConstants.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// GroupConstants.swift -// Passepartout -// -// Created by Davide De Rosa on 6/7/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation - -public class GroupConstants { - public class App { - public static let config = Bundle.main.infoDictionary?["com.algoritmico.Passepartout.config"] as? [String: Any] - - public static let name = "Passepartout" - - public static let tunnelKitName = "TunnelKit" - - public static let title = name -// public static let title = "\u{1F511}" - - public static let versionNumber = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String - - public static let buildNumber = Int(Bundle.main.infoDictionary![kCFBundleVersionKey as String] as! String)! - - public static let versionString = "\(versionNumber) (\(buildNumber))" - - public static let groupId = config?["group_id"] as? String ?? "DUMMY_group_id" - - private static var containerURL: URL { - guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupId) else { - print("Unable to access App Group container") - return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - } - return url - } - - public static let documentsURL: URL = { - let url = containerURL.appendingPathComponent("Documents", isDirectory: true) - try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) - return url - }() - - public static let cachesURL: URL = { - let url = containerURL.appendingPathComponent("Library/Caches", isDirectory: true) - try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) - return url - }() - - public static let externalURL = cachesURL.appendingPathComponent("External") - } - - public class VPN { - public static let dnsTimeout = 5000 - - public static let sessionMarker = "--- EOF ---" - - public static let dataCountInterval = 5000 - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/API b/PassepartoutCore/Sources/PassepartoutCore/API deleted file mode 160000 index 3f2b54be..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/API +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3f2b54beafd88eb67433e2dc030baf8bb555793a diff --git a/PassepartoutCore/Sources/PassepartoutCore/AppConstants+Core.swift b/PassepartoutCore/Sources/PassepartoutCore/AppConstants+Core.swift deleted file mode 100644 index e74cd440..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/AppConstants+Core.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// AppConstants+Core.swift -// Passepartout -// -// Created by Davide De Rosa on 11/5/21. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import PassepartoutConstants -import Convenience -import SwiftyBeaver - -extension AppConstants.Log { - public static let level: SwiftyBeaver.Level = .debug - - private static let console: ConsoleDestination = { - let dest = ConsoleDestination() - dest.minLevel = level - dest.useNSLog = true - return dest - }() - - private static let file: FileDestination = { - let dest = FileDestination() - dest.minLevel = level - dest.logFileURL = fileURL - _ = dest.deleteLogFile() - return dest - }() - - public static func configure() { - SwiftyBeaver.addDestination(console) - SwiftyBeaver.addDestination(file) - } -} - -extension AppConstants.Credits { - public static var software: [Software] { - return softwareArrays.map { - switch $0.count { - case 2: - return Software($0[0], notice: $0[1]) - - case 3: - return Software($0[0], license: $0[1], url: $0[2]) - - default: - fatalError("Not enough Software arguments") - } - } - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Exports.swift b/PassepartoutCore/Sources/PassepartoutCore/Exports.swift index ac6c708e..cbad96f0 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Exports.swift +++ b/PassepartoutCore/Sources/PassepartoutCore/Exports.swift @@ -1,3 +1,5 @@ -@_exported import PassepartoutConstants +@_exported import PassepartoutProfiles +@_exported import PassepartoutProviders +@_exported import PassepartoutUtils @_exported import TunnelKit -@_exported import TunnelKitOpenVPN +@_exported import TunnelKitCore diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/DebugLog.swift b/PassepartoutCore/Sources/PassepartoutCore/Extensions/DebugLog+Decorated.swift similarity index 70% rename from PassepartoutCore/Sources/PassepartoutCore/Model/DebugLog.swift rename to PassepartoutCore/Sources/PassepartoutCore/Extensions/DebugLog+Decorated.swift index 6d5d6101..83af27be 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/DebugLog.swift +++ b/PassepartoutCore/Sources/PassepartoutCore/Extensions/DebugLog+Decorated.swift @@ -1,8 +1,8 @@ // -// DebugLog.swift +// DebugLog+Decorated.swift // Passepartout // -// Created by Davide De Rosa on 6/26/18. +// Created by Davide De Rosa on 3/14/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -29,27 +29,9 @@ import UIKit #else import Cocoa #endif -import PassepartoutConstants - -public struct DebugLog { - private let raw: String - - public init(raw: String) { - self.raw = raw - } - - public func string() -> String { - return raw - } - - public func data() -> Data? { - return raw.data(using: .utf8) - } - - public func decoratedString() -> String { - let appName = GroupConstants.App.name - let appVersion = GroupConstants.App.versionString +extension DebugLog { + public func decoratedString(_ appName: String, _ appVersion: String) -> String { var metadata: [String] = [] let osVersion: String let deviceType: String? @@ -72,13 +54,14 @@ public struct DebugLog { var fullText = metadata.joined(separator: "\n") fullText += "\n\n" - fullText += raw + fullText += content return fullText } - public func decoratedData() -> Data { - guard let data = decoratedString().data(using: .utf8) else { - fatalError("Could not encode log metadata to UTF8?") + public func decoratedData(_ appName: String, _ appVersion: String) -> Data { + guard let data = decoratedString(appName, appVersion).data(using: .utf8) else { + assertionFailure("Could not encode log metadata to UTF8?") + return Data() } return data } diff --git a/PassepartoutCore/Sources/PassepartoutCore/Extensions/OnDemand+Rules.swift b/PassepartoutCore/Sources/PassepartoutCore/Extensions/OnDemand+Rules.swift new file mode 100644 index 00000000..66408f5f --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Extensions/OnDemand+Rules.swift @@ -0,0 +1,65 @@ +// +// OnDemand+Rules.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import NetworkExtension + +extension Profile.OnDemand { + var rules: [NEOnDemandRule] { + + // TODO: on-demand, drop when "trusted networks" -> "on-demand" + assert(policy == .excluding) + + var rules: [NEOnDemandRule] = [] + #if os(iOS) + if withMobileNetwork { + let rule = policyRule + rule.interfaceTypeMatch = .cellular + rules.append(rule) + } + #else + if withEthernetNetwork { + let rule = policyRule + rule.interfaceTypeMatch = .ethernet + rules.append(rule) + } + #endif + let SSIDs = Array(withSSIDs.filter { $1 }.keys) + if !SSIDs.isEmpty { + let rule = policyRule + rule.interfaceTypeMatch = .wiFi + rule.ssidMatch = SSIDs + rules.append(rule) + } + let connection = NEOnDemandRuleConnect() + connection.interfaceTypeMatch = .any + rules.append(connection) + return rules + } + + private var policyRule: NEOnDemandRule { + return disconnectsIfNotMatching ? NEOnDemandRuleDisconnect() : NEOnDemandRuleIgnore() + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Extensions/OpenVPNSettings+VPNConfiguration.swift b/PassepartoutCore/Sources/PassepartoutCore/Extensions/OpenVPNSettings+VPNConfiguration.swift new file mode 100644 index 00000000..ec3b874f --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Extensions/OpenVPNSettings+VPNConfiguration.swift @@ -0,0 +1,137 @@ +// +// OpenVPNSettings+VPNConfiguration.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitOpenVPN + +extension Profile.OpenVPNSettings: VPNConfigurationProviding { + func vpnConfiguration(_ parameters: VPNConfigurationParameters) throws -> VPNConfiguration { + var customBuilder = configuration.builder() + + // tolerate widest range of certificates + customBuilder.tlsSecurityLevel = 0 + + // custom endpoint + if let endpoint = customEndpoint { + customBuilder.remotes = [endpoint] + } + + // network settings + customBuilder.applyGateway(from: parameters.networkSettings.gateway) + customBuilder.applyDNS(from: parameters.networkSettings.dns) + customBuilder.applyProxy(from: parameters.networkSettings.proxy) + customBuilder.applyMTU(from: parameters.networkSettings.mtu) + + let customConfiguration = customBuilder.build() + + var cfg = OpenVPN.ProviderConfiguration( + parameters.title, + appGroup: parameters.appGroup, + configuration: customConfiguration + ) + cfg.shouldDebug = true + cfg.debugLogFormat = parameters.preferences.tunnelLogFormat + cfg.masksPrivateData = parameters.preferences.masksPrivateData + cfg.username = parameters.username + + var extra = NetworkExtensionExtra() + extra.passwordReference = parameters.passwordReference + extra.onDemandRules = parameters.onDemandRules + extra.disconnectsOnSleep = !parameters.networkSettings.keepsAliveOnSleep + + pp_log.verbose("Configuration:") + pp_log.verbose(cfg) + pp_log.verbose(extra) + + return (cfg, extra) + } +} + +extension OpenVPN.ConfigurationBuilder { + mutating func applyGateway(from settings: Network.GatewaySettings) { + switch settings.choice { + case .automatic: + break + + case .manual: + var policies: [OpenVPN.RoutingPolicy] = [] + if settings.isDefaultIPv4 { + policies.append(.IPv4) + } + if settings.isDefaultIPv6 { + policies.append(.IPv6) + } + routingPolicies = policies + } + } + + mutating func applyDNS(from settings: Network.DNSSettings) { + switch settings.choice { + case .automatic: + break + + case .manual: + isDNSEnabled = settings.isDNSEnabled + + if settings.isDNSEnabled { + dnsProtocol = settings.dnsProtocol + dnsServers = settings.dnsServers.filter { !$0.isEmpty } + dnsHTTPSURL = settings.dnsHTTPSURL + dnsTLSServerName = settings.dnsTLSServerName + searchDomains = settings.dnsSearchDomains + } + } + } + + mutating func applyProxy(from settings: Network.ProxySettings) { + switch settings.choice { + case .automatic: + break + + case .manual: + isProxyEnabled = settings.isProxyEnabled + + if settings.isProxyEnabled { + if let proxyServer = settings.proxyServer { + httpProxy = proxyServer + httpsProxy = proxyServer + } else if let pac = settings.proxyAutoConfigurationURL { + proxyAutoConfigurationURL = pac + } + proxyBypassDomains = settings.proxyBypassDomains.filter { !$0.isEmpty } + } + } + } + + mutating func applyMTU(from settings: Network.MTUSettings) { + switch settings.choice { + case .automatic: + break + + case .manual: + mtu = settings.mtuBytes + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Extensions/WireGuardSettings+VPNConfiguration.swift b/PassepartoutCore/Sources/PassepartoutCore/Extensions/WireGuardSettings+VPNConfiguration.swift new file mode 100644 index 00000000..a1a9a124 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Extensions/WireGuardSettings+VPNConfiguration.swift @@ -0,0 +1,107 @@ +// +// WireGuardSettings+VPNConfiguration.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitWireGuard + +extension Profile.WireGuardSettings: VPNConfigurationProviding { + func vpnConfiguration(_ parameters: VPNConfigurationParameters) throws -> VPNConfiguration { + var customBuilder = configuration.builder() + + // network settings + customBuilder.applyGateway(from: parameters.networkSettings.gateway) + customBuilder.applyDNS(from: parameters.networkSettings.dns) + customBuilder.applyMTU(from: parameters.networkSettings.mtu) + + let customConfiguration = customBuilder.build() + + var cfg = WireGuard.ProviderConfiguration( + parameters.title, + appGroup: parameters.appGroup, + configuration: customConfiguration + ) + cfg.shouldDebug = true + cfg.debugLogFormat = parameters.preferences.tunnelLogFormat + + var extra = NetworkExtensionExtra() + extra.onDemandRules = parameters.onDemandRules + extra.disconnectsOnSleep = !parameters.networkSettings.keepsAliveOnSleep + + pp_log.verbose("Configuration:") + pp_log.verbose(cfg) + pp_log.verbose(extra) + + return (cfg, extra) + } +} + +extension WireGuard.ConfigurationBuilder { + mutating func applyGateway(from settings: Network.GatewaySettings) { + switch settings.choice { + case .automatic: + break + + case .manual: + for i in 0... -// - -import Foundation -#if os(iOS) -import MessageUI -#endif - -public struct Issue { - public let debugLog: Bool - - public let configurationURL: URL? - - public let infrastructureMetadata: Infrastructure.Metadata? - - public init(debugLog: Bool, configurationURL: URL?, infrastructureMetadata: Infrastructure.Metadata? = nil) { - self.debugLog = debugLog - self.configurationURL = configurationURL - self.infrastructureMetadata = infrastructureMetadata - } - - public init(debugLog: Bool, profile: ConnectionProfile?) { - let url: URL? - if let profile = profile { - url = TransientStore.shared.service.configurationURL(for: profile) - } else { - url = nil - } - var infrastructureMetadata: Infrastructure.Metadata? - if let name = (profile as? ProviderConnectionProfile)?.name { - infrastructureMetadata = InfrastructureFactory.shared.metadata(forName: name) - } - self.init(debugLog: debugLog, configurationURL: url, infrastructureMetadata: infrastructureMetadata) - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Managers/AppManager+Migrations.swift b/PassepartoutCore/Sources/PassepartoutCore/Managers/AppManager+Migrations.swift new file mode 100644 index 00000000..37e7705a --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Managers/AppManager+Migrations.swift @@ -0,0 +1,328 @@ +// +// AppManager+Migrations.swift +// Passepartout +// +// Created by Davide De Rosa on 3/20/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import GenericJSON +import TunnelKitCore +import TunnelKitOpenVPNCore +import TunnelKitManager + +private typealias Map = [String: Any] + +// MARK: Migrate to version 2 + +extension AppManager { + fileprivate enum MigrationError: Error { + case json + + case missingId + + case missingOpenVPNConfiguration + + case missingHostname + + case missingEndpointProtocols + + case missingProviderName + } + + private var appGroup: String { + return "group.com.algoritmico.Passepartout" + } + + func doMigrateToV2() async -> [Profile] { + var migrated: [Profile] = [] + pp_log.info("Migrating data to v2") + + let fm = FileManager.default + + guard let documents = fm.containerURL(forSecurityApplicationGroupIdentifier: appGroup)? + .appendingPathComponent("Documents") else { + + pp_log.info("No data to migrate") + return [] + } + + let cs = documents.appendingPathComponent("ConnectionService.json") + let hostsFolder = documents.appendingPathComponent("Hosts") + let providersFolder = documents.appendingPathComponent("Providers") + + do { + let csJSON = try cs.asJSON() +// pp_log.error(csJSON) + + do { + var authUserPassUUIDs: Set = [] + + for host in try fm.contentsOfDirectory(at: hostsFolder, includingPropertiesForKeys: nil) { + guard host.isFileURL && host.pathExtension == "ovpn" else { + continue + } + + do { + let uuid = host.deletingPathExtension().lastPathComponent + let content = try String(contentsOf: host) + + if content.contains("auth-user-pass") { + authUserPassUUIDs.insert(uuid) + } + } catch { + pp_log.warning("Unable to read host profile .ovpn: \(host)") + } + } + +// print(">>> authUserPassUUIDs: \(authUserPassUUIDs)") + + for host in try fm.contentsOfDirectory(at: hostsFolder, includingPropertiesForKeys: nil) { + guard host.isFileURL && host.pathExtension == "json" else { + continue + } + do { + let json = try host.asJSON() +// pp_log.error(json) + + let result = try migratedV1Profile(csJSON, hostMap: json, authUserPass: authUserPassUUIDs) +// pp_log.info(result.profile) +// print(">>> Account: \(result.profile.username) -> \(result.password)") + + migrated.append(result) + } catch { + pp_log.warning("Unable to migrate host profile: \(host)") + continue + } + } + } catch { + pp_log.warning(error) + } + + do { + for provider in try fm.contentsOfDirectory(at: providersFolder, includingPropertiesForKeys: nil) { + guard provider.isFileURL && provider.pathExtension == "json" else { + continue + } + do { + let json = try provider.asJSON() +// pp_log.error(json) + + let result = try await migratedV1Profile(csJSON, providerMap: json) +// pp_log.info(result.profile) +// print(">>> Account: \(result.profile.username) -> \(result.password)") + + migrated.append(result) + } catch { + pp_log.warning("Unable to migrate provider profile: \(provider)") + continue + } + } + } catch { + pp_log.warning(error) + } + } catch { + pp_log.warning(error) + } + + return migrated + } + + // SHARED + // + // username/password ("username") + // trusted networks ("trustedNetworks") + // network settings ("networkChoices", "manualNetworkSettings") + // + + // HOST + // + // provider configuration ("parameters") -- not crucial + // custom endpoint ("parameters"?) -- not crucial + // ovpn configuration ("parameters" -> "sessionConfiguration") + // + private func migratedV1Profile(_ cs: Map, hostMap: Map, authUserPass: Set) throws -> Profile { + guard let oldUUIDString = hostMap["id"] as? String else { + throw MigrationError.missingId + } + + let name = (cs["hostTitles"] as? Map)?[oldUUIDString] as? String ?? oldUUIDString + let header = Profile.Header(name: name) // new UUID + + // configuration + guard let params = hostMap["parameters"] as? Map else { + throw MigrationError.missingOpenVPNConfiguration + } + guard var ovpn = params["sessionConfiguration"] as? Map else { + throw MigrationError.missingOpenVPNConfiguration + } + guard let hostname = ovpn["hostname"] as? String else { + throw MigrationError.missingHostname + } + guard let rawEps = ovpn["endpointProtocols"] as? [String] else { + throw MigrationError.missingEndpointProtocols + } + let eps = rawEps.compactMap(EndpointProtocol.init(rawValue:)) + var remotes: [String] = [] + eps.forEach { + remotes.append("\(hostname):\($0)") + } + ovpn["remotes"] = remotes + ovpn["authUserPass"] = authUserPass.contains(header.id.uuidString) + let cfg = try JSON(ovpn).decode(OpenVPN.Configuration.self) + + // keychain + let username = hostMap["username"] as? String ?? "" + let password = migratedV1Password(forProfileId: oldUUIDString, profileType: "host", username: username) + + var profile = Profile(header, configuration: cfg) + var account = Profile.Account() + account.username = username + account.password = password + profile.account = account + + // shared + profile.onDemand = migratedV1TrustedNetworks(hostMap) + profile.networkSettings = migratedV1NetworkSettings(hostMap) + + return profile + } + + // HOST + // + // poolId -- not crucial + // presetId + // favoriteGroupIds + // + private func migratedV1Profile(_ cs: Map, providerMap: Map) async throws -> Profile { + guard let name = providerMap["name"] as? String else { + throw MigrationError.missingProviderName + } + + let header = Profile.Header(name: name, providerName: name) + var provider = Profile.Provider(name) + + // keychain + var account = Profile.Account() + account.username = providerMap["username"] as? String ?? "" + account.password = migratedV1Password(forProfileId: name, profileType: "provider", username: account.username) + + // provider configuration + var settings = Profile.Provider.Settings() + if let apiId = providerMap["poolId"] as? String { + settings.serverId = ProviderServer.id(withName: name, vpnProtocol: .openVPN, apiId: apiId) + } + settings.presetId = providerMap["presetId"] as? String + settings.favoriteLocationIds = Set((providerMap["favoriteGroupIds"] as? [String])?.compactMap { + "\(name):\($0.replacingOccurrences(of: "/", with: ":"))" + } ?? []) + settings.account = account + provider.vpnSettings[.openVPN] = settings + + var profile = Profile(header, provider: provider) + + // shared + profile.onDemand = migratedV1TrustedNetworks(providerMap) + profile.networkSettings = migratedV1NetworkSettings(providerMap) + + return profile + } + + private func migratedV1Password(forProfileId profileId: String, profileType: String, username: String) -> String { + let keychain = Keychain(group: appGroup) + let passwordContext = "\(Bundle.main.bundleIdentifier!).\(profileType).\(profileId)" + do { + return try keychain.password(for: username, context: passwordContext) + } catch { + return "" + } + } + + private func migratedV1TrustedNetworks(_ map: Map) -> Profile.OnDemand { + var onDemand = Profile.OnDemand() + onDemand.isEnabled = true + if let trusted = map["trustedNetworks"] as? Map { + onDemand.withMobileNetwork = trusted["includesMobile"] as? Bool ?? false + onDemand.withEthernetNetwork = trusted["includesEthernet"] as? Bool ?? false + onDemand.withSSIDs = trusted["includedWiFis"] as? [String: Bool] ?? [:] + if let rawPolicy = trusted["policy"] as? String, let policy = Profile.OnDemand.Policy(rawValue: rawPolicy) { + onDemand.policy = policy + } + } + return onDemand + } + + private func migratedV1NetworkSettings(_ map: Map) -> Profile.NetworkSettings { + var settings = Profile.NetworkSettings() + + if let choices = map["networkChoices"] as? Map { + settings.gateway.choice = migratedV1Choice(choices, key: "gateway") + settings.dns.choice = migratedV1Choice(choices, key: "dns") + settings.proxy.choice = migratedV1Choice(choices, key: "proxy") + settings.mtu.choice = migratedV1Choice(choices, key: "mtu") + } + + if let manual = map["manualNetworkSettings"] as? Map { + + // gateway + settings.gateway.isDefaultIPv4 = (manual["gatewayPolicies"] as? [String])?.contains("IPv4") ?? false + settings.gateway.isDefaultIPv6 = (manual["gatewayPolicies"] as? [String])?.contains("IPv6") ?? false + + // dns + (manual["dnsProtocol"] as? String).map { + settings.dns.dnsProtocol = DNSProtocol(rawValue: $0) ?? .plain + } + settings.dns.dnsServers = manual["dnsServers"] as? [String] ?? [] + settings.dns.dnsSearchDomains = manual["dnsSearchDomains"] as? [String] ?? [] + (manual["dnsHTTPSURL"] as? String).map { + settings.dns.dnsHTTPSURL = URL(string: $0) + } + settings.dns.dnsTLSServerName = manual["dnsTLSServerName"] as? String + + // proxy + settings.proxy.proxyAddress = manual["proxyAddress"] as? String + settings.proxy.proxyPort = manual["proxyPort"] as? UInt16 + (manual["proxyAutoConfigurationURL"] as? String).map { + settings.proxy.proxyAutoConfigurationURL = URL(string: $0) + } + settings.proxy.proxyBypassDomains = manual["proxyBypassDomains"] as? [String] ?? [] + + // mtu + settings.mtu.mtuBytes = manual["mtuBytes"] as? Int ?? 0 + } + + return settings + } + + private func migratedV1Choice(_ map: Map, key: String) -> Network.Choice { + return (map[key] as? String) == "manual" ? .manual : .automatic + } +} + +private extension URL { + func asJSON() throws -> Map { + let data = try Data(contentsOf: self) + guard let json = try JSONSerialization.jsonObject(with: data) as? Map else { + throw AppManager.MigrationError.json + } + return json + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Managers/AppManager.swift b/PassepartoutCore/Sources/PassepartoutCore/Managers/AppManager.swift new file mode 100644 index 00000000..dc64c154 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Managers/AppManager.swift @@ -0,0 +1,220 @@ +// +// AppManager.swift +// Passepartout +// +// Created by Davide De Rosa on 2/8/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import SwiftyBeaver + +@MainActor +public class AppManager: ObservableObject { + public enum DefaultKey: String { + case activeProfileId + + case launchesOnLogin + + case isShowingFavorites + + case confirmsQuit + + case logFormat + + case tunnelLogFormat + + case masksPrivateData + + case didHandleSubreddit + + // internal use + + case persistenceAuthor + + case didMigrateToV2 + } + + private let defaults: UserDefaults = .standard + + public var logLevel: SwiftyBeaver.Level = .info + + public var logFile: URL? + + // MARK: State + + @Published public private(set) var isDoingMigrations = true + + public init() { + defaults.register(keyedDefaults: [ + .activeProfileId: nil, + .launchesOnLogin: false, + .isShowingFavorites: false, + .confirmsQuit: true, + .logFormat: nil, + .tunnelLogFormat: nil, + .masksPrivateData: true, + .didHandleSubreddit: false, + // + .didMigrateToV2: false + ]) + + // set once + if persistenceAuthor == nil { + persistenceAuthor = UUID().uuidString + } + } + + public func configureLogging() { + let console = ConsoleDestination() + console.minLevel = logLevel +// console.useNSLog = true + if let logFormat = logFormat { + console.format = logFormat + } + SwiftyBeaver.addDestination(console) + + if let fileURL = logFile { + let file = FileDestination() + file.minLevel = logLevel + file.logFileURL = fileURL + if let logFormat = logFormat { + file.format = logFormat + } + _ = file.deleteLogFile() + SwiftyBeaver.addDestination(file) + } + + CoreConfiguration.masksPrivateData = masksPrivateData + } + + public func doMigrations(_ profileManager: ProfileManager) async { +// profileManager.removeAllProfiles() + guard didMigrateToV2 else { + isDoingMigrations = true + let migrated = await doMigrateToV2() + if !migrated.isEmpty { + pp_log.info("Migrating \(migrated.count) profiles") + migrated.forEach { + var profile = $0 + if profileManager.isExistingProfile(withName: profile.header.name) { + profile = profile.renamedUniquely() + } + profileManager.saveProfile(profile, isActive: nil) + } + } else { + pp_log.info("Nothing to migrate!") + } + isDoingMigrations = false + + didMigrateToV2 = true + return + } + isDoingMigrations = false + } + + // MARK: Current state + + public var preferences: AppPreferences { + return DefaultAppPreferences( + activeProfileId: activeProfileId, + logFormat: logFormat, + tunnelLogFormat: tunnelLogFormat, + masksPrivateData: masksPrivateData + ) + } +} + +extension AppManager: AppPreferences { + public var activeProfileId: UUID? { + get { + guard let uuidString = defaults.string(forKey: DefaultKey.activeProfileId.rawValue) else { + return nil + } + return UUID(uuidString: uuidString) + } + set { + defaults.set(newValue?.uuidString, forKey: DefaultKey.activeProfileId.rawValue) + objectWillChange.send() + } + } + + public var logFormat: String? { + get { + defaults.string(forKey: DefaultKey.logFormat.rawValue) + } + set { + defaults.set(newValue, forKey: DefaultKey.logFormat.rawValue) + objectWillChange.send() + } + } + + public var tunnelLogFormat: String? { + get { + defaults.string(forKey: DefaultKey.tunnelLogFormat.rawValue) + } + set { + defaults.set(newValue, forKey: DefaultKey.tunnelLogFormat.rawValue) + objectWillChange.send() + } + } + + public var masksPrivateData: Bool { + get { + defaults.bool(forKey: DefaultKey.masksPrivateData.rawValue) + } + set { + defaults.set(newValue, forKey: DefaultKey.masksPrivateData.rawValue) + CoreConfiguration.masksPrivateData = newValue + + objectWillChange.send() + } + } + + // MARK: Internal use (readonly) + + public private(set) var persistenceAuthor: String? { + get { + defaults.string(forKey: DefaultKey.persistenceAuthor.rawValue) + } + set { + defaults.set(newValue, forKey: DefaultKey.persistenceAuthor.rawValue) + } + } + + public internal(set) var didMigrateToV2: Bool { + get { + defaults.bool(forKey: DefaultKey.didMigrateToV2.rawValue) + } + set { + defaults.set(newValue, forKey: DefaultKey.didMigrateToV2.rawValue) + } + } +} + +private extension UserDefaults { + func register(keyedDefaults: [AppManager.DefaultKey: Any?]) { + let mapped = keyedDefaults.reduce(into: [String: Any]()) { + $0[$1.key.rawValue] = $1.value + } + register(defaults: mapped) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Managers/PersistenceManager.swift b/PassepartoutCore/Sources/PassepartoutCore/Managers/PersistenceManager.swift new file mode 100644 index 00000000..2af5f8a4 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Managers/PersistenceManager.swift @@ -0,0 +1,45 @@ +// +// PersistenceManager.swift +// Passepartout +// +// Created by Davide De Rosa on 4/6/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData + +public class PersistenceManager { + private let author: String? + + public init(author: String?) { + self.author = author + } + + public func profilesPersistence(withName containerName: String) -> Persistence { + let model = PassepartoutDataModels.profiles + return Persistence(withCloudKitName: containerName, model: model, author: author) + } + + public func providersPersistence(withName containerName: String) -> Persistence { + let model = PassepartoutDataModels.providers + return Persistence(withLocalName: containerName, model: model, author: author) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager+Actions.swift b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager+Actions.swift new file mode 100644 index 00000000..9d75f916 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager+Actions.swift @@ -0,0 +1,123 @@ +// +// VPNManager+Actions.swift +// Passepartout +// +// Created by Davide De Rosa on 3/30/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +// IMPORTANT: if active profile is set/modified and it happens to also be +// current profile, this must be updated too. this is done in +// ProfileManager.activateProfile() + +extension VPNManager { + public func connectWithActiveProfile() async throws { + guard currentState.vpnStatus != .connected else { + pp_log.warning("VPN is already connected") + return + } + guard let profileId = profileManager.activeHeader?.id else { + pp_log.warning("No active profile") + return + } + try await connect(with: profileId) + } + + public func connect(with profileId: UUID) async throws { + let result = try profileManager.loadProfile(withId: profileId) + let profile = result.profile + guard !profileManager.isActiveProfile(profileId) || + currentState.vpnStatus != .connected else { + + pp_log.warning("Profile \(profile.logDescription) is already active and connected") + return + } + + if !result.isReady { + try await profileManager.makeProfileReady(profile) + } + + pp_log.info("Connecting to: \(profile.logDescription)") + profileManager.activateProfile(profile) + + let cfg = try vpnConfiguration(withProfile: profile) + await reconnect(cfg) + } + + public func connect(with profileId: UUID, toServer newServerId: String) async throws { + let result = try profileManager.loadProfile(withId: profileId) + var profile = result.profile + guard profile.isProvider else { + assertionFailure("Profile \(profile.logDescription) is not a provider") + throw PassepartoutError.missingProfile + } + + if !result.isReady { + try await profileManager.makeProfileReady(profile) + } + + let oldServerId = profile.providerServerId() + guard let newServer = providerManager.server(withId: newServerId) else { + pp_log.warning("Server \(newServerId) not found") + throw PassepartoutError.missingProviderServer + } + + guard !profileManager.isActiveProfile(profileId) || + currentState.vpnStatus != .connected || + oldServerId != newServer.id else { + + pp_log.info("Profile \(profile.logDescription) is already active and connected to: \(newServer.logDescription)") + return + } + + pp_log.info("Connecting to: \(profile.logDescription) @ \(newServer.logDescription)") + profile.setProviderServer(newServer) + profileManager.activateProfile(profile) + + guard !profileManager.isCurrentProfile(profileId) else { + pp_log.debug("Active profile is current, will reconnect via observation") + return + } + + let cfg = try vpnConfiguration(withProfile: profile) + await reconnect(cfg) + } + + public func modifyActiveProfile(_ block: (inout Profile) -> Void) async throws { + guard var profile = profileManager.activeProfile else { + pp_log.warning("Nothing to modify, no active profile") + return + } + + pp_log.info("Modifying active profile") + block(&profile) + profileManager.activateProfile(profile) + + guard !profileManager.isCurrentProfile(profile.id) else { + pp_log.debug("Active profile is current, will reinstate via observation") + return + } + + let cfg = try vpnConfiguration(withProfile: profile) + await reinstate(cfg) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager+Configuration.swift b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager+Configuration.swift new file mode 100644 index 00000000..2c1abf73 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager+Configuration.swift @@ -0,0 +1,102 @@ +// +// VPNManager+Configuration.swift +// Passepartout +// +// Created by Davide De Rosa on 3/12/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension VPNManager { + func vpnConfigurationWithCurrentProfile() -> VPNConfiguration? { + do { + guard profileManager.isCurrentProfileActive() else { + pp_log.info("Skipping VPN configuration, current profile is not active") + return nil + } + return try vpnConfiguration(withProfile: profileManager.currentProfile.value) + } catch { + return nil + } + } + + func vpnConfiguration(withProfile profile: Profile) throws -> VPNConfiguration { + do { + if profile.requiresCredentials { + guard !profile.account.isEmpty else { + throw PassepartoutError.missingAccount + } + } + + // IMPORTANT: must commit password to keychain (tunnel needs a password reference) + profileManager.savePassword(forProfile: profile) + + let parameters = VPNConfigurationParameters( + profile, + appGroup: profileManager.appGroup, + preferences: appManager.preferences, + passwordReference: profileManager.passwordReference(forProfile: profile) + ) + + var cfg: VPNConfiguration + + switch profile.currentVPNProtocol { + case .openVPN: + let settings: Profile.OpenVPNSettings + if profile.isProvider { + settings = try profile.providerOpenVPNSettings(withManager: providerManager) + } else { + guard let hostSettings = profile.hostOpenVPNSettings else { + fatalError("Host has no OpenVPN settings") + } + settings = hostSettings + } + cfg = try settings.vpnConfiguration(parameters) + + case .wireGuard: + let settings: Profile.WireGuardSettings + if profile.isProvider { + settings = try profile.providerWireGuardSettings(withManager: providerManager) + } else { + guard let hostSettings = profile.hostWireGuardSettings else { + fatalError("Host has no WireGuard settings") + } + settings = hostSettings + } + cfg = try settings.vpnConfiguration(parameters) + } + + // suppress rules if not supported + if !isOnDemandSupported() { + cfg.neExtra?.onDemandRules = [] + } + + return cfg + } catch { + pp_log.error("Unable to build VPNConfiguration: \(error)") + + // UI is certainly interested in configuration errors + lastError = error + + throw error + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager.swift b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager.swift new file mode 100644 index 00000000..032cc623 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManager.swift @@ -0,0 +1,257 @@ +// +// VPNManager.swift +// Passepartout +// +// Created by Davide De Rosa on 2/9/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine +import TunnelKitManager +import TunnelKitOpenVPNManager + +@MainActor +public class VPNManager: ObservableObject, RateLimited { + + // MARK: Initialization + + let appManager: AppManager + + let profileManager: ProfileManager + + let providerManager: ProviderManager + + private let strategy: VPNManagerStrategy + + public var isOnDemandSupported: () -> Bool + + // MARK: State + + public let currentState: ObservableState + + public internal(set) var lastError: Error? { + get { + currentState.lastError + } + set { + currentState.lastError = newValue + } + } + + // MARK: Internals + + private var lastProfile: Profile = .placeholder + + private var cancellables: Set = [] + + public init( + appManager: AppManager, + profileManager: ProfileManager, + providerManager: ProviderManager, + strategy: VPNManagerStrategy + ) { + self.appManager = appManager + self.profileManager = profileManager + self.providerManager = providerManager + self.strategy = strategy + isOnDemandSupported = { true } + + currentState = ObservableState() + observeStrategy() + observeProfileManager() + } + + public func toggle() -> Bool { + guard !isRateLimited("") else { + return false + } + lastActionDate[""] = Date() + + // signal rate limiting flag (e.g. for UI) + if let rateLimitMilliseconds = rateLimitMilliseconds { + isRateLimiting = true + Task { + try await Task.sleep(nanoseconds: UInt64(rateLimitMilliseconds) * NSEC_PER_MSEC) + isRateLimiting = false + } + } + + guard let configuration = vpnConfigurationWithCurrentProfile() else { + return false + } + Task { + pp_log.info("Toggling VPN (enabled: \(currentState.isEnabled) -> \(!currentState.isEnabled))") + currentState.lastError = nil + if !currentState.isEnabled { + await strategy.connect(configuration: configuration) + } else { + await strategy.disconnect() + } + } + return true + } + + func reinstate(_ configuration: VPNConfiguration) async { + pp_log.info("Reinstating VPN") + currentState.lastError = nil + await strategy.reinstate(configuration: configuration) + } + + func reconnect(_ configuration: VPNConfiguration) async { + pp_log.info("Reconnecting VPN") + currentState.lastError = nil + await strategy.connect(configuration: configuration) + } + + public func disable() async { + pp_log.info("Disabling VPN") + currentState.lastError = nil + await strategy.disconnect() + } + + public func uninstall() async { + pp_log.info("Uninstalling VPN") + currentState.lastError = nil + await strategy.removeConfigurations() + } + + public func serverConfiguration(forProtocol vpnProtocol: VPNProtocolType) -> Any? { + return strategy.serverConfiguration(forProtocol: vpnProtocol) + } + + public func debugLogURL(forProtocol vpnProtocol: VPNProtocolType) -> URL? { + return strategy.debugLogURL(forProtocol: vpnProtocol) + } + + // MARK: RateLimited + + public var lastActionDate: [String: Date] = [:] + + public var rateLimitMilliseconds: Int? + + @Published public private(set) var isRateLimiting = false +} + +// MARK: Observation + +extension VPNManager { + private func observeStrategy() { + strategy.observe(into: currentState) + } + + private func observeProfileManager() { + profileManager.willUpdateActiveId + .removeDuplicates() + .sink { newId in + Task { + await self.willUpdateActiveId(newId) + } + }.store(in: &cancellables) + + profileManager.willUpdateCurrentProfile + .removeDuplicates() + .sink { newProfile in + Task { + await self.willUpdateCurrentProfile(newProfile) + } + }.store(in: &cancellables) + } + + private func willUpdateActiveId(_ newId: UUID?) async { + guard let _ = newId else { + pp_log.info("No active profile, disconnecting VPN...") + await disable() + return + } + } + + private func willUpdateCurrentProfile(_ newProfile: Profile) async { + defer { + lastProfile = newProfile + } + // ignore if VPN disabled + guard currentState.isEnabled else { + pp_log.debug("Ignoring updates, VPN is disabled") + return + } + // ignore non-active profiles + guard profileManager.isActiveProfile(newProfile.id) else { + pp_log.debug("Ignoring updates, profile \(newProfile.logDescription) is not active") + return + } + // ignore profile changes, react on changes within same profile + guard newProfile.id == lastProfile.id else { + return + } + + pp_log.debug("Active profile updated: \(newProfile.header.name)") + + var isHandled = false + var shouldReconnect = false + let notDisconnected = (currentState.vpnStatus != .disconnected) + + // do not reconnect if connected + if newProfile.isProvider { + + // server changed? + if newProfile.providerServerId() != lastProfile.providerServerId() { + pp_log.info("Provider server changed: \(newProfile.providerServerId()?.description ?? "nil")") + isHandled = true + shouldReconnect = notDisconnected + } + + // endpoint changed? + else if newProfile.providerCustomEndpoint() != lastProfile.providerCustomEndpoint() { + pp_log.info("Provider endpoint changed: \(newProfile.providerCustomEndpoint()?.description ?? "automatic")") + isHandled = true + shouldReconnect = notDisconnected + } + } else { + + // endpoint changed? + if newProfile.hostCustomEndpoint != lastProfile.hostCustomEndpoint { + pp_log.info("Host endpoint changed: \(newProfile.hostCustomEndpoint?.description ?? "automatic")") + isHandled = true + shouldReconnect = notDisconnected + } + } + + if !isHandled { + if newProfile.onDemand != lastProfile.onDemand { + pp_log.info("On-demand settings changed") + isHandled = true + shouldReconnect = false + } + } + + guard isHandled else { + return + } + guard let cfg = vpnConfigurationWithCurrentProfile() else { + return + } + if shouldReconnect { + await reconnect(cfg) + } else { + await reinstate(cfg) + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy+Mock.swift b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy+Mock.swift new file mode 100644 index 00000000..a68e4530 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy+Mock.swift @@ -0,0 +1,107 @@ +// +// VPNManagerStrategy+Mock.swift +// Passepartout +// +// Created by Davide De Rosa on 2/9/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine +import TunnelKitManager + +extension VPNManager { + + // XXX: mock connect/disconnect tasks overlap, should cancel other pending task + + public class MockStrategy: VPNManagerStrategy { + private var currentState: ObservableState? + + private var dataCountTimer: AnyCancellable? + + public init() { + } + + public func observe(into state: VPNManager.ObservableState) { + currentState = state + } + + public func reinstate(configuration: VPNConfiguration) { + } + + public func connect(configuration: VPNConfiguration) { + guard currentState?.vpnStatus != .connected else { + return + } + Task { + currentState?.isEnabled = true + currentState?.vpnStatus = .connecting + await Task.maybeWait(forMilliseconds: 1000) + currentState?.vpnStatus = .connected + startCountingData() + } + } + + public func disconnect() { + stopCountingData() + guard currentState?.vpnStatus != .disconnected else { + return + } + Task { + currentState?.isEnabled = false + currentState?.vpnStatus = .disconnecting + await Task.maybeWait(forMilliseconds: 1000) + currentState?.vpnStatus = .disconnected + } + } + + private func startCountingData() { + guard currentState?.vpnStatus == .connected else { + return + } + guard dataCountTimer == nil else { + return + } + dataCountTimer = Timer.TimerPublisher(interval: 2.0, runLoop: .main, mode: .common) + .autoconnect() + .sink(receiveValue: { _ in + let previous = self.currentState?.dataCount ?? DataCount(0, 0) + self.currentState?.dataCount = DataCount(previous.received + 4000, previous.sent + 2000) + }) + } + + private func stopCountingData() { + dataCountTimer?.cancel() + dataCountTimer = nil + } + + public func removeConfigurations() { + disconnect() + } + + public func serverConfiguration(forProtocol vpnProtocol: VPNProtocolType) -> Any? { + return nil + } + + public func debugLogURL(forProtocol vpnProtocol: VPNProtocolType) -> URL? { + return nil + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy+TunnelKit.swift b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy+TunnelKit.swift new file mode 100644 index 00000000..2f3a869b --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy+TunnelKit.swift @@ -0,0 +1,278 @@ +// +// VPNManagerStrategy+TunnelKit.swift +// Passepartout +// +// Created by Davide De Rosa on 3/4/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine +import NetworkExtension +import TunnelKitManager +import TunnelKitOpenVPNCore + +extension VPNManager { + public class TunnelKitStrategy: VPNManagerStrategy { + private struct AtomicState: Equatable { + let isEnabled: Bool + + let vpnStatus: VPNStatus + + init(isEnabled: Bool = false, vpnStatus: VPNStatus = .disconnected) { + self.isEnabled = isEnabled + self.vpnStatus = vpnStatus + } + } + + private let appGroup: String + + private let tunnelBundleIdentifier: (VPNProtocolType) -> String + + private let defaults: UserDefaults + + private let vpn: NetworkExtensionVPN + + private let dataCountInterval: TimeInterval + + // MARK: State + + private var currentState: ObservableState? + + private let vpnState = CurrentValueSubject(.init()) + + private var dataCountTimer: AnyCancellable? + + private var cancellables: Set = [] + + // MARK: Protocol specific + + private var currentBundleIdentifier: String? + + public init(appGroup: String, tunnelBundleIdentifier: @escaping (VPNProtocolType) -> String, dataCountInterval: TimeInterval = 3.0) { + self.appGroup = appGroup + self.tunnelBundleIdentifier = tunnelBundleIdentifier + guard let defaults = UserDefaults(suiteName: appGroup) else { + fatalError("No entitlements for group '\(appGroup)'") + } + self.defaults = defaults + vpn = NetworkExtensionVPN() + self.dataCountInterval = dataCountInterval + + registerNotification(withName: VPNNotification.didReinstall) { + self.onVPNReinstall($0) + } + registerNotification(withName: VPNNotification.didChangeStatus) { + self.onVPNStatus($0) + } + registerNotification(withName: VPNNotification.didFail) { + self.onVPNFail($0) + } + Task { + await vpn.prepare() + } + } + + private func registerNotification(withName name: Notification.Name, perform: @escaping (Notification) -> Void) { + NotificationCenter.default.publisher(for: name, object: nil) + .sink(receiveValue: perform) + .store(in: &cancellables) + } + + // MARK: Strategy + + public func observe(into state: VPNManager.ObservableState) { + currentState = state + + // use this to drop redundant NE notifications + vpnState + .removeDuplicates() + .sink { + self.currentState?.isEnabled = $0.isEnabled + self.currentState?.vpnStatus = $0.vpnStatus + }.store(in: &cancellables) + } + + public func reinstate(configuration: VPNConfiguration) async { + guard let vpnType = configuration.neConfiguration as? VPNProtocolProviding else { + fatalError("Configuration must implement VPNProtocolProviding") + } + let bundleIdentifier = tunnelBundleIdentifier(vpnType.vpnProtocol) + currentBundleIdentifier = bundleIdentifier + + pp_log.verbose("Configuration: \(configuration)") + pp_log.info("Reinstating VPN...") + do { + try await vpn.install( + bundleIdentifier, + configuration: configuration.neConfiguration, + extra: configuration.neExtra + ) + } catch { + pp_log.error("Unable to install: \(error)") + } + } + + public func connect(configuration: VPNConfiguration) async { + guard let vpnType = configuration.neConfiguration as? VPNProtocolProviding else { + fatalError("Configuration must implement VPNProtocolProviding") + } + let bundleIdentifier = tunnelBundleIdentifier(vpnType.vpnProtocol) + currentBundleIdentifier = bundleIdentifier + + pp_log.verbose("Configuration: \(configuration)") + pp_log.info("Reconnecting VPN...") + do { + try await vpn.reconnect( + bundleIdentifier, + configuration: configuration.neConfiguration, + extra: configuration.neExtra, + after: .seconds(2) + ) + } catch { + pp_log.error("Unable to connect: \(error)") + } + } + + public func disconnect() async { + await vpn.disconnect() + } + + public func removeConfigurations() async { + await vpn.uninstall() + } + + // MARK: Notifications + + private func onVPNReinstall(_ notification: Notification) { + currentBundleIdentifier = notification.vpnBundleIdentifier + + vpnState.send(AtomicState( + isEnabled: notification.vpnIsEnabled, + vpnStatus: vpnState.value.vpnStatus + )) + } + + private func onVPNStatus(_ notification: Notification) { + var error: Error? + currentBundleIdentifier = notification.vpnBundleIdentifier + + switch notification.vpnStatus { + case .connected: + startCountingData() + + case .disconnecting: + error = lastError(withBundleIdentifier: notification.vpnBundleIdentifier) + + case .disconnected: + error = lastError(withBundleIdentifier: notification.vpnBundleIdentifier) + stopCountingData() + + default: + break + } + + vpnState.send(AtomicState( + isEnabled: notification.vpnIsEnabled, + vpnStatus: notification.vpnStatus + )) + currentState?.lastError = error + } + + private func onVPNFail(_ notification: Notification) { + currentState?.lastError = notification.vpnError + } + + private func onDataCount(_: Date) { + switch vpnState.value.vpnStatus { + case .connected: + currentState?.dataCount = currentDataCount + + default: + currentState?.dataCount = nil + } + } + + private func startCountingData() { + guard dataCountTimer == nil else { + return + } + dataCountTimer = Timer.TimerPublisher(interval: dataCountInterval, runLoop: .main, mode: .common) + .autoconnect() + .sink { + self.onDataCount($0) + } + } + + private func stopCountingData() { + dataCountTimer?.cancel() + dataCountTimer = nil + + currentState?.dataCount = nil + } + + // MARK: Pulled + + public func serverConfiguration(forProtocol vpnProtocol: VPNProtocolType) -> Any? { + switch vpnProtocol { + case .openVPN: + return defaults.openVPNServerConfiguration + + default: + return nil + } + } + + public func debugLogURL(forProtocol vpnProtocol: VPNProtocolType) -> URL? { + switch vpnProtocol { + case .openVPN: + return FileManager.default.openVPNURLForDebugLog(appGroup: appGroup) + + default: + return FileManager.default.wireGuardURLForDebugLog(appGroup: appGroup) + } + } + + // MARK: Callbacks + + private func lastError(withBundleIdentifier bundleIdentifier: String?) -> Error? { + switch bundleIdentifier { + case tunnelBundleIdentifier(.openVPN): + return defaults.openVPNLastError + + case tunnelBundleIdentifier(.wireGuard): + return defaults.wireGuardLastError + + default: + return nil + } + } + + private var currentDataCount: DataCount? { + switch currentBundleIdentifier { + case tunnelBundleIdentifier(.openVPN): + return defaults.openVPNDataCount + + default: + return nil + } + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/Preferences.swift b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy.swift similarity index 62% rename from PassepartoutCore/Sources/PassepartoutCore/Model/Preferences.swift rename to PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy.swift index b69533c6..94b4aa84 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/Preferences.swift +++ b/PassepartoutCore/Sources/PassepartoutCore/Managers/VPNManagerStrategy.swift @@ -1,8 +1,8 @@ // -// Preferences.swift +// VPNManagerStrategy.swift // Passepartout // -// Created by Davide De Rosa on 9/4/18. +// Created by Davide De Rosa on 4/6/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,23 +24,21 @@ // import Foundation +import Combine -public protocol Preferences { - var launchesOnLogin: Bool? { get } +@MainActor +public protocol VPNManagerStrategy { + func observe(into state: VPNManager.ObservableState) - var confirmsQuit: Bool? { get } + func reinstate(configuration: VPNConfiguration) async + + func connect(configuration: VPNConfiguration) async + + func disconnect() async + + func removeConfigurations() async + + func serverConfiguration(forProtocol vpnProtocol: VPNProtocolType) -> Any? - var resolvesHostname: Bool { get } - - var disconnectsOnSleep: Bool { get } -} - -public class EditablePreferences: Preferences, Codable { - public var launchesOnLogin: Bool? = false - - public var confirmsQuit: Bool? = true - - public var resolvesHostname: Bool = true - - public var disconnectsOnSleep: Bool = false + func debugLogURL(forProtocol vpnProtocol: VPNProtocolType) -> URL? } diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionProfile.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionProfile.swift deleted file mode 100644 index 4204480d..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionProfile.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// ConnectionProfile.swift -// Passepartout -// -// Created by Davide De Rosa on 9/2/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import NetworkExtension -import TunnelKit -import TunnelKitOpenVPN - -public enum Context: String, Codable { - case provider - - case host -} - -public protocol ConnectionProfile: AnyObject, EndpointDataSource, CustomStringConvertible { - var context: Context { get } - - var id: String { get } - - var username: String? { get set } - - var requiresCredentials: Bool { get } - - var trustedNetworks: TrustedNetworks! { get set } - - var networkChoices: ProfileNetworkChoices? { get set } - - var manualNetworkSettings: ProfileNetworkSettings? { get set } - - var serviceDelegate: ConnectionServiceDelegate? { get set } - - func generate(from configuration: OpenVPNProvider.Configuration, preferences: Preferences) throws -> OpenVPNProvider.Configuration -} - -public extension ConnectionProfile { - var passwordContext: String { - return "\(Bundle.main.bundleIdentifier!).\(context.rawValue).\(id)" - } - - func password(in keychain: Keychain) -> String? { - guard let username = username else { - return nil - } - return try? keychain.password(for: username, context: passwordContext) - } - - func setPassword(_ password: String?, in keychain: Keychain) throws { - guard let username = username else { - return - } - guard let password = password else { - keychain.removePassword(for: username, context: passwordContext) - return - } - try keychain.set(password: password, for: username, context: passwordContext) - } - - func removePassword(in keychain: Keychain) { - guard let username = username else { - return - } - keychain.removePassword(for: username, context: passwordContext) - } -} - -public extension ConnectionProfile { - var description: String { - return "(\(context):\(id))" - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService+Configurations.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService+Configurations.swift deleted file mode 100644 index ec3b523f..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService+Configurations.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// ConnectionService+Configurations.swift -// Passepartout -// -// Created by Davide De Rosa on 10/22/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import SwiftyBeaver - -private let log = SwiftyBeaver.self - -public extension ConnectionService { - func save(configurationURL: URL, for key: ProfileKey) throws -> URL { - let destinationURL = targetConfigurationURL(for: key) - let fm = FileManager.default - try? fm.removeItem(at: destinationURL) - try fm.copyItem(at: configurationURL, to: destinationURL) - return destinationURL - } - - func save(configurationURL: URL, for profile: ConnectionProfile) throws -> URL { - return try save(configurationURL: configurationURL, for: ProfileKey(profile)) - } - - func configurationURL(for key: ProfileKey) -> URL? { - let url = targetConfigurationURL(for: key) - guard FileManager.default.fileExists(atPath: url.path) else { - return nil - } - return url - } - - func configurationURL(for profile: ConnectionProfile) -> URL? { - return configurationURL(for: ProfileKey(profile)) - } - - func targetConfigurationURL(for key: ProfileKey) -> URL { - return contextURL(key).appendingPathComponent(key.id).appendingPathExtension("ovpn") - } - - func pendingConfigurationURLs() -> [URL] { - do { - let url = FileManager.default.userURL(for: .documentDirectory, appending: nil) - let list = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: []) - return list.filter { $0.pathExtension == "ovpn" } - } catch let e { - log.error("Could not list imported configurations: \(e)") - return [] - } - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService+Migration.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService+Migration.swift deleted file mode 100644 index 5385414f..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService+Migration.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// ConnectionService+Migration.swift -// Passepartout -// -// Created by Davide De Rosa on 10/25/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import SwiftyBeaver -import TunnelKit -import PassepartoutConstants - -private let log = SwiftyBeaver.self - -public extension ConnectionService { - static func migrateJSON(from: URL, to: URL) { - do { - let newData = try migrateJSON(at: from) -// log.verbose(String(data: newData, encoding: .utf8)!) - try newData.write(to: to) - } catch let e { - log.error("Could not migrate service: \(e)") - } - } - - static func migrateJSON(at url: URL) throws -> Data { - let data = try Data(contentsOf: url) - guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { - throw ApplicationError.migration - } - - // put migration logic here - let _ = json["build"] as? Int ?? 0 - - return try JSONSerialization.data(withJSONObject: json, options: []) - } - - func migrateKeychainContext() { - for key in allProfileKeys() { - guard let profile = profile(withKey: key), let username = profile.username else { - continue - } - let keychain = Keychain(group: GroupConstants.App.groupId) - let prefix = "com.algoritmico.ios.Passepartout" - - // profiles - do { - let oldUsername = "\(prefix).\(key.context).\(key.id).\(username)" - let password = try keychain.password(for: oldUsername) - try profile.setPassword(password, in: keychain) - keychain.removePassword(for: oldUsername) - - // tunnel - if isActiveProfile(key) { - let oldTunnelUsername = prefix - let tunnelContext = "\(prefix).Tunnel" - try keychain.set(password: password, for: username, context: tunnelContext) - keychain.removePassword(for: oldTunnelUsername) - } - } catch { - // - } - } - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService.swift deleted file mode 100644 index 3db7ca1e..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/ConnectionService.swift +++ /dev/null @@ -1,683 +0,0 @@ -// -// ConnectionService.swift -// Passepartout -// -// Created by Davide De Rosa on 9/3/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import NetworkExtension -import SwiftyBeaver -import TunnelKit -import TunnelKitOpenVPN -import PassepartoutConstants - -private let log = SwiftyBeaver.self - -public protocol ConnectionServiceDelegate: AnyObject { - func connectionService(didAdd profile: ConnectionProfile) - - func connectionService(didRename profile: ConnectionProfile, to newTitle: String) - - func connectionService(didRemoveProfileWithKey key: ProfileKey) - - func connectionService(willDeactivate profile: ConnectionProfile) - - func connectionService(didActivate profile: ConnectionProfile) - - func connectionService(didUpdate profile: ConnectionProfile) -} - -public class ConnectionService: Codable { - public enum CodingKeys: String, CodingKey { - case build - - case appGroup - - case baseConfiguration - - case activeProfileKey - - case preferences - - case hostTitles - } - - public struct NotificationKeys { - public static let dataCount = "DataCount" - } - - public static let didUpdateDataCount = Notification.Name("ConnectionServiceDidUpdateDataCount") - - public var rootURL: URL { - return GroupConstants.App.documentsURL - } - - var providersURL: URL { - return rootURL.appendingPathComponent(AppConstants.Store.providersDirectory) - } - - var hostsURL: URL { - return rootURL.appendingPathComponent(AppConstants.Store.hostsDirectory) - } - - private var build: Int - - private let appGroup: String - - private let defaults: UserDefaults - - private let keychain: Keychain - - public var baseConfiguration: OpenVPNProvider.Configuration - - private var cache: [ProfileKey: ConnectionProfile] - - // XXX: access needed by +Migration - var hostTitles: [String: String] - - public internal(set) var activeProfileKey: ProfileKey? { - willSet { - if let oldProfile = activeProfile { - delegate?.connectionService(willDeactivate: oldProfile) - } - } - didSet { - if let newProfile = activeProfile { - delegate?.connectionService(didActivate: newProfile) - } - } - } - - public var activeProfile: ConnectionProfile? { - guard let id = activeProfileKey else { - return nil - } - var hit = cache[id] - if let placeholder = hit as? PlaceholderConnectionProfile { - hit = profile(withContext: placeholder.context, id: placeholder.id) - cache[id] = hit - } - return hit - } - - public let preferences: EditablePreferences - - public weak var delegate: ConnectionServiceDelegate? - - public init(withAppGroup appGroup: String, baseConfiguration: OpenVPNProvider.Configuration) { - guard let defaults = UserDefaults(suiteName: appGroup) else { - fatalError("No entitlements for group '\(appGroup)'") - } - build = GroupConstants.App.buildNumber - self.appGroup = appGroup - self.defaults = defaults - keychain = Keychain(group: appGroup) - - self.baseConfiguration = baseConfiguration - activeProfileKey = nil - preferences = EditablePreferences() - - cache = [:] - hostTitles = [:] - - ensureDirectoriesExistence() - } - - // MARK: Codable - - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let appGroup = try container.decode(String.self, forKey: .appGroup) - guard let defaults = UserDefaults(suiteName: appGroup) else { - fatalError("No entitlements for group '\(appGroup)'") - } - build = try container.decode(Int.self, forKey: .build) - self.appGroup = appGroup - self.defaults = defaults - keychain = Keychain(group: appGroup) - - baseConfiguration = try container.decode(OpenVPNProvider.Configuration.self, forKey: .baseConfiguration) - activeProfileKey = try container.decodeIfPresent(ProfileKey.self, forKey: .activeProfileKey) - preferences = try container.decode(EditablePreferences.self, forKey: .preferences) - - cache = [:] - hostTitles = try container.decode([String: String].self, forKey: .hostTitles) - - ensureDirectoriesExistence() - } - - public func encode(to encoder: Encoder) throws { - build = GroupConstants.App.buildNumber - - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(build, forKey: .build) - try container.encode(appGroup, forKey: .appGroup) - try container.encode(baseConfiguration, forKey: .baseConfiguration) - try container.encodeIfPresent(activeProfileKey, forKey: .activeProfileKey) - try container.encode(preferences, forKey: .preferences) - try container.encode(hostTitles, forKey: .hostTitles) - } - - // MARK: Serialization - - public func loadProfiles() { - let fm = FileManager.default - do { - let files = try fm.contentsOfDirectory(at: providersURL, includingPropertiesForKeys: nil, options: []) -// log.debug("Found \(files.count) provider files: \(files)") - for entry in files { - guard let id = ConnectionService.profileId(fromURL: entry) else { - continue - } - let key = ProfileKey(.provider, id) - cache[key] = PlaceholderConnectionProfile(key) - } - } catch let e { - log.warning("Could not list provider contents: \(e) (\(providersURL))") - } - do { - let files = try fm.contentsOfDirectory(at: hostsURL, includingPropertiesForKeys: nil, options: []) -// log.debug("Found \(files.count) host files: \(files)") - for entry in files { - guard let id = ConnectionService.profileId(fromURL: entry) else { - continue - } - let key = ProfileKey(.host, id) - cache[key] = PlaceholderConnectionProfile(key) - } - } catch let e { - log.warning("Could not list host contents: \(e) (\(hostsURL))") - } - - // clean up hostTitles if necessary - let staleHostIds = hostTitles.keys.filter { cache[ProfileKey(.host, $0)] == nil } - staleHostIds.forEach { - hostTitles.removeValue(forKey: $0) - } - } - - public func saveProfiles() { - let encoder = JSONEncoder() - for profile in cache.values { - saveProfile(profile, withEncoder: encoder) - } - } - - private func ensureDirectoriesExistence() { - let fm = FileManager.default - do { - try fm.createDirectory(at: providersURL, withIntermediateDirectories: false, attributes: nil) - } catch let e { - log.warning("Could not create providers folder: \(e) (\(providersURL))") - } - do { - try fm.createDirectory(at: hostsURL, withIntermediateDirectories: false, attributes: nil) - } catch let e { - log.warning("Could not create hosts folder: \(e) (\(hostsURL))") - } - - } - - private func saveProfile(_ profile: ConnectionProfile, withEncoder encoder: JSONEncoder) { - do { - let url = profileURL(ProfileKey(profile)) - var optData: Data? - if let providerProfile = profile as? ProviderConnectionProfile { - optData = try encoder.encode(providerProfile) - } else if let hostProfile = profile as? HostConnectionProfile { - optData = try encoder.encode(hostProfile) - } else if let placeholder = profile as? PlaceholderConnectionProfile { - log.debug("Skipped placeholder \(placeholder)") - } else { - fatalError("Attempting to add an unhandled profile type: \(type(of: profile))") - } - guard let data = optData else { - return - } - try data.write(to: url) - log.debug("Serialized profile \(profile)") - } catch let e { - log.warning("Could not serialize profile \(profile): \(e)") - } - } - - public func profile(withContext context: Context, id: String) -> ConnectionProfile? { - return profile(withKey: ProfileKey(context, id)) - } - - public func profile(withKey key: ProfileKey) -> ConnectionProfile? { - var profile = cache[key] - if let _ = profile as? PlaceholderConnectionProfile { - let decoder = JSONDecoder() - do { - let data = try profileData(key) - switch key.context { - case .provider: - let providerProfile = try decoder.decode(ProviderConnectionProfile.self, from: data) - - // XXX: fix renamed presets, fall back to default - if providerProfile.preset == nil { - providerProfile.presetId = providerProfile.infrastructure.defaults.preset - } - - // XXX: fix renamed pool, fall back to default - if providerProfile.pool == nil, let fallbackPool = providerProfile.infrastructure.defaultPool() { - providerProfile.poolId = fallbackPool.id - } - - // XXX: fix unsupported preset - providerProfile.setSupportedPreset() - - // XXX: patch empty favorites - if providerProfile.favoriteGroupIds == nil { - providerProfile.favoriteGroupIds = [] - } - - profile = providerProfile - - case .host: - let hostProfile = try decoder.decode(HostConnectionProfile.self, from: data) - - profile = hostProfile - } - cache[key] = profile - } catch let e { - log.error("Could not decode profile JSON: \(e)") - -// // drop corrupt cache entry -// cache.removeValue(forKey: key) -// try? FileManager.default.removeItem(at: profileURL(key)) - - return nil - } - } - - // XXX: preload trusted networks in a backwards compatible manner (deserialization) - if profile?.trustedNetworks == nil { - profile?.trustedNetworks = TrustedNetworks() - } - - // propagate delegate - profile?.serviceDelegate = delegate - return profile - } - - public func allProfileKeys() -> [ProfileKey] { - return Array(cache.keys) - } - - public func ids(forContext context: Context) -> [String] { - return cache.keys.filter { $0.context == context }.map { $0.id } - } - - public func contextURL(_ key: ProfileKey) -> URL { - switch key.context { - case .provider: - return providersURL - - case .host: - return hostsURL - } - } - - public func profileURL(_ key: ProfileKey) -> URL { - return contextURL(key).appendingPathComponent(key.id).appendingPathExtension("json") - } - - public func profileData(_ key: ProfileKey) throws -> Data { - return try Data(contentsOf: profileURL(key)) - } - - private static func profileId(fromURL url: URL) -> String? { - guard url.pathExtension == "json" else { - return nil - } - return url.deletingPathExtension().lastPathComponent - } - - // MARK: Profiles - - public func hasProfiles() -> Bool { - return !cache.isEmpty - } - - public func addProfile(_ profile: ConnectionProfile, credentials: Credentials?) -> Bool { - guard cache.index(forKey: ProfileKey(profile)) == nil else { - return false - } - addOrReplaceProfile(profile, credentials: credentials) - return true - } - - public func addOrReplaceProfile(_ profile: ConnectionProfile, credentials: Credentials?, title: String? = nil) { - let key = ProfileKey(profile) - cache[key] = profile - if key.context == .host { - hostTitles[key.id] = title - } - try? setCredentials(credentials, for: profile) - - if cache.count == 1 { - activeProfileKey = key - } - delegate?.connectionService(didAdd: profile) - - // serialization (can fail) - saveProfile(profile, withEncoder: JSONEncoder()) - } - - public func renameProfile(_ key: ProfileKey, to newTitle: String) { - precondition(key.context == .host, "Can only rename a HostConnectionProfile") - guard let profile = cache[key] else { - return - } - - hostTitles[key.id] = newTitle - delegate?.connectionService(didRename: profile, to: newTitle) - } - - public func renameProfile(_ profile: ConnectionProfile, to newTitle: String) { - renameProfile(ProfileKey(profile), to: newTitle) - } - - public func removeProfile(_ key: ProfileKey) { - guard let profile = cache[key] else { - return - } - - if key == activeProfileKey { - activeProfileKey = nil - } - cache.removeValue(forKey: key) - if key.context == .host { - hostTitles.removeValue(forKey: key.id) - } - removeCredentials(for: profile) - - delegate?.connectionService(didRemoveProfileWithKey: key) - - // serialization (can fail) - do { - let fm = FileManager.default - if let cfg = configurationURL(for: key) { - try? fm.removeItem(at: cfg) - } - let url = profileURL(key) - try fm.removeItem(at: url) - log.debug("Deleted removed profile '\(profile.id)'") - } catch let e { - log.warning("Could not delete profile '\(profile.id)': \(e)") - } - } - - public func containsProfile(_ key: ProfileKey) -> Bool { - return cache.index(forKey: key) != nil - } - - public func containsProfile(_ profile: ConnectionProfile) -> Bool { - return containsProfile(ProfileKey(profile)) - } - - public func hasActiveProfile() -> Bool { - return activeProfileKey != nil - } - - public func isActiveProfile(_ key: ProfileKey) -> Bool { - return key == activeProfileKey - } - - public func isActiveProfile(_ profile: ConnectionProfile) -> Bool { - return isActiveProfile(ProfileKey(profile)) - } - - public func activateProfile(_ profile: ConnectionProfile) { - activeProfileKey = ProfileKey(profile) - } - - public func existingHostId(withTitle title: String) -> String? { - for id in hostTitles.keys { - guard let _ = cache[ProfileKey(.host, id)] else { - continue - } - if hostTitles[id] == title { - return id - } - } - return nil - } - - public func hostProfile(withTitle title: String) -> HostConnectionProfile? { - guard let id = existingHostId(withTitle: title) else { - return nil - } - return profile(withContext: .host, id: id) as? HostConnectionProfile - } - - // MARK: Credentials - - public func needsCredentials(for profile: ConnectionProfile) -> Bool { - guard profile.requiresCredentials else { - return false - } - guard let creds = credentials(for: profile) else { - return true - } - return !creds.isValid - } - - public func credentials(for profile: ConnectionProfile) -> Credentials? { - guard let username = profile.username else { - return nil - } - let password = (try? keychain.password(for: username, context: profile.passwordContext)) ?? "" // make password optional - return Credentials(username, password) - } - - public func setCredentials(_ credentials: Credentials?, for profile: ConnectionProfile) throws { - profile.username = credentials?.username - try profile.setPassword(credentials?.password, in: keychain) - } - - public func removeCredentials(for profile: ConnectionProfile) { - profile.removePassword(in: keychain) - } - - // MARK: VPN - - public func vpnConfiguration() throws -> NetworkExtensionVPNConfiguration { - guard let profile = activeProfile else { - throw ApplicationError.missingProfile - } - let creds = credentials(for: profile) - if profile.requiresCredentials { - guard creds != nil else { - throw ApplicationError.missingCredentials - } - } - - var cfg = try profile.generate(from: baseConfiguration, preferences: preferences) - - // override network settings - if let choices = profile.networkChoices, let settings = profile.manualNetworkSettings { - var builder = cfg.builder() - var sessionBuilder = builder.sessionConfiguration.builder() - - // enforce default gateway for providers unless "Manual" - if type(of: profile) == ProviderConnectionProfile.self { - if choices.gateway == .manual { - sessionBuilder.applyGateway(from: choices, settings: settings) - } - } else { - sessionBuilder.applyGateway(from: choices, settings: settings) - } - - sessionBuilder.applyDNS(from: choices, settings: settings) - sessionBuilder.applyProxy(from: choices, settings: settings) - sessionBuilder.applyMTU(from: choices, settings: settings) - builder.sessionConfiguration = sessionBuilder.build() - cfg = builder.build() - } - - let protocolConfiguration = try cfg.generatedTunnelProtocol( - withBundleIdentifier: AppConstants.App.tunnelBundleId, - appGroup: appGroup, - context: profile.passwordContext, - credentials: creds - ) - protocolConfiguration.disconnectOnSleep = preferences.disconnectsOnSleep - - log.verbose("Configuration:") - log.verbose(protocolConfiguration) - - var rules: [NEOnDemandRule] = [] - do { - try ProductManager.shared.verifyEligible(forFeature: .trustedNetworks) - #if os(iOS) - if profile.trustedNetworks.includesMobile { - let rule = policyRule(for: profile) - rule.interfaceTypeMatch = .cellular - rules.append(rule) - } - #else - if profile.trustedNetworks.includesEthernet { - let rule = policyRule(for: profile) - rule.interfaceTypeMatch = .ethernet - rules.append(rule) - } - #endif - let reallyTrustedWifis = Array(profile.trustedNetworks.includedWiFis.filter { $1 }.keys) - if !reallyTrustedWifis.isEmpty { - let rule = policyRule(for: profile) - rule.interfaceTypeMatch = .wiFi - rule.ssidMatch = reallyTrustedWifis - rules.append(rule) - } - } catch { - } - let connection = NEOnDemandRuleConnect() - connection.interfaceTypeMatch = .any - rules.append(connection) - - return NetworkExtensionVPNConfiguration( - title: screenTitle(ProfileKey(profile)), - protocolConfiguration: protocolConfiguration, - onDemandRules: rules - ) - } - - private func policyRule(for profile: ConnectionProfile) -> NEOnDemandRule { - switch profile.trustedNetworks.policy { - case .ignore: - return NEOnDemandRuleIgnore() - - case .disconnect: - return NEOnDemandRuleDisconnect() - } - } - - public var vpnLog: String { - return baseConfiguration.existingLog(in: appGroup) ?? "" - } - - public func eraseVpnLog() { - log.info("Erasing VPN log...") - guard let url = baseConfiguration.urlForLog(in: appGroup) else { - return - } - try? FileManager.default.removeItem(at: url) - } - - public var vpnLastError: OpenVPNProviderError? { - return baseConfiguration.lastError(in: appGroup) - } - - public func clearVpnLastError() { - baseConfiguration.clearLastError(in: appGroup) - } - - public func observeVPNDataCount(milliseconds: Int) { - reportDataCountAndRepeat(after: milliseconds) - } - - private func reportDataCountAndRepeat(after milliseconds: Int) { - reportDataCount() - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(milliseconds)) { [weak self] in - self?.reportDataCountAndRepeat(after: milliseconds) - } - } - - private func reportDataCount() { - guard let dataCount = vpnDataCount else { - return - } - NotificationCenter.default.post(name: ConnectionService.didUpdateDataCount, object: nil, userInfo: [NotificationKeys.dataCount: dataCount]) - } - - public var vpnDataCount: (Int, Int)? { - return baseConfiguration.dataCount(in: appGroup) - } -} - -public extension ConnectionService { - func providerNames() -> [InfrastructureName] { - return ids(forContext: .provider) - } - - func hostIds() -> [String] { - return ids(forContext: .host) - } - - func sortedProviderNames() -> [InfrastructureName] { - return providerNames().sorted() - } - - func sortedHostIds() -> [String] { - return hostIds().sorted { - let title1 = screenTitle(ProfileKey(.host, $0)) - let title2 = screenTitle(ProfileKey(.host, $1)) - return title1.lowercased() < title2.lowercased() - } - } - - func screenTitle(forHostId id: String) -> String { - return screenTitle(ProfileKey(.host, id)) - } - - func screenTitle(forProviderName name: InfrastructureName) -> String { - return screenTitle(ProfileKey(.provider, name)) - } - - func screenTitle(_ key: ProfileKey) -> String { - switch key.context { - case .provider: - if let metadata = InfrastructureFactory.shared.metadata(forName: key.id) { - return metadata.description - } - - case .host: - if let title = hostTitles[key.id] { - return title - } - } - return key.id - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/DataUnit.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/DataUnit.swift deleted file mode 100644 index 8904f16f..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/DataUnit.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// DataUnit.swift -// Passepartout -// -// Created by Davide De Rosa on 3/30/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation - -public enum DataUnit: Int, CustomStringConvertible { - case byte = 1 - - case kilobyte = 1024 - - case megabyte = 1048576 - - case gigabyte = 1073741824 - - fileprivate var showsDecimals: Bool { - switch self { - case .byte, .kilobyte: - return false - - case .megabyte, .gigabyte: - return true - } - } - - fileprivate var boundary: Int { - return Int(0.1 * Double(rawValue)) - } - - // MARK: CustomStringConvertible - - public var description: String { - switch self { - case .byte: - return "B" - - case .kilobyte: - return "kB" - - case .megabyte: - return "MB" - - case .gigabyte: - return "GB" - } - } -} - -public extension Int { - private static let allUnits: [DataUnit] = [ - .gigabyte, - .megabyte, - .kilobyte, - .byte - ] - - var dataUnitDescription: String { - if self == 0 { - return "0B" - } - for u in Int.allUnits { - if self >= u.boundary { - if !u.showsDecimals { - return "\(self / u.rawValue)\(u)" - } - let count = Double(self) / Double(u.rawValue) - return String(format: "%.2f%@", count, u.description) - } - } - fatalError("Number is negative") - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/GracefulVPN.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/GracefulVPN.swift deleted file mode 100644 index a44c7db9..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/GracefulVPN.swift +++ /dev/null @@ -1,140 +0,0 @@ -// -// GracefulVPN.swift -// Passepartout -// -// Created by Davide De Rosa on 9/18/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import SwiftyBeaver -import TunnelKit - -private let log = SwiftyBeaver.self - -public class GracefulVPN { - private let service: ConnectionService - - private var vpn: VPNProvider? { - return VPN.shared - } - - public var isEnabled: Bool { - return vpn?.isEnabled ?? false - } - - public var status: VPNStatus? { - return vpn?.status - } - - public init(service: ConnectionService) { - self.service = service - } - - public func prepare(completionHandler: (() -> Void)?) { - service.clearVpnLastError() - guard let vpn = vpn else { - completionHandler?() - return - } - log.info("Preparing...") - vpn.prepare(completionHandler: completionHandler) - } - - public func reconnect(completionHandler: ((Error?) -> Void)?) { - service.clearVpnLastError() - guard let vpn = vpn else { - completionHandler?(ApplicationError.inactiveProfile) - return - } - do { - log.info("Reconnecting...") - try vpn.reconnect(configuration: service.vpnConfiguration(), delay: nil, completionHandler: completionHandler) - } catch let e { - guard e as? ApplicationError != .externalResources else { - completionHandler?(e) - return - } - log.error("Could not reconnect: \(e)") - } - } - - public func reinstall(completionHandler: ((Error?) -> Void)?) { - service.clearVpnLastError() - guard let vpn = vpn else { - completionHandler?(ApplicationError.inactiveProfile) - return - } - do { - log.info("Reinstalling...") - try vpn.install(configuration: service.vpnConfiguration(), completionHandler: completionHandler) - } catch let e { - guard e as? ApplicationError != .externalResources else { - completionHandler?(e) - return - } - log.error("Could not reinstall: \(e)") - } - } - - public func reinstallIfEnabled() { - guard isEnabled else { - log.warning("Not reinstalling (VPN is disabled)") - return - } - if status != .disconnected { - reconnect(completionHandler: nil) - } else { - reinstall(completionHandler: nil) - } - } - - public func disconnect(completionHandler: ((Error?) -> Void)?) { - guard let vpn = vpn else { - completionHandler?(ApplicationError.inactiveProfile) - return - } - vpn.disconnect(completionHandler: completionHandler) - } - - public func uninstall(completionHandler: (() -> Void)?) { - guard let vpn = vpn else { - completionHandler?() - return - } - vpn.uninstall(completionHandler: completionHandler) - } - - public func requestBytesCount(completionHandler: @escaping ((UInt, UInt)?) -> Void) { - guard let ipc = vpn as? VPNProviderIPC else { - completionHandler(nil) - return - } - ipc.requestBytesCount(completionHandler: completionHandler) - } - - public func requestServerConfiguration(completionHandler: @escaping (Any?) -> Void) { - guard let ipc = vpn as? VPNProviderIPC, vpn?.status == .connected else { - completionHandler(nil) - return - } - ipc.requestServerConfiguration(completionHandler: completionHandler) - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/LocalProduct+Infrastructure.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/LocalProduct+Infrastructure.swift deleted file mode 100644 index 6199ee66..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/LocalProduct+Infrastructure.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// File.swift -// -// -// Created by Davide De Rosa on 04/11/21. -// - -import Foundation -import PassepartoutConstants - -public extension LocalProduct { -// public static var allProviders: [LocalProduct] { -// return InfrastructureFactory.shared.allMetadata.map { -// return LocalProduct(providerMetadata: $0) -// } -// } - - fileprivate init(providerMetadata: Infrastructure.Metadata) { - self.init(rawValue: "\(LocalProduct.providersBundle).\(providerMetadata.inApp ?? providerMetadata.name)")! - } -} - -public extension Infrastructure.Metadata { - var product: LocalProduct { - return LocalProduct(providerMetadata: self) - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/ProfileNetworkSettings.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/ProfileNetworkSettings.swift deleted file mode 100644 index 3f8bca38..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/ProfileNetworkSettings.swift +++ /dev/null @@ -1,251 +0,0 @@ -// -// ProfileNetworkSettings.swift -// Passepartout -// -// Created by Davide De Rosa on 04/28/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import TunnelKit -import TunnelKitOpenVPN - -public enum NetworkChoice: String, Codable { - case client - - case server // erase client settings - - case manual - - public static func choices(for profile: ConnectionProfile?) -> [NetworkChoice] { - if let _ = profile as? HostConnectionProfile { - return [.client, .server, .manual] - } - return [.server, .manual] - } -} - -public struct ProfileNetworkChoices: Codable { - public static let defaultChoice: NetworkChoice = .client - - public var gateway: NetworkChoice - - public var dns: NetworkChoice - - public var proxy: NetworkChoice - - public var mtu: NetworkChoice? - - public init(choice: NetworkChoice) { - gateway = choice - dns = choice - proxy = choice - mtu = choice - } - - public static func with(profile: ConnectionProfile?) -> ProfileNetworkChoices { - if let choices = profile?.networkChoices { - return choices - } - if let _ = profile as? ProviderConnectionProfile { - return ProfileNetworkChoices(choice: .server) - } - return ProfileNetworkChoices(choice: .client) - } -} - -public class ProfileNetworkSettings: Codable, CustomStringConvertible { - public static let mtuOptions: [Int] = [0, 1500, 1400, 1300, 1200] - - public var gatewayPolicies: [OpenVPN.RoutingPolicy]? - - public var dnsProtocol: DNSProtocol? - - public var dnsServers: [String]? - - public var dnsHTTPSURL: URL? - - public var dnsTLSServerName: String? - - public var dnsSearchDomains: [String]? - - public var proxyAddress: String? - - public var proxyPort: UInt16? - - public var proxyServer: Proxy? { - guard let address = proxyAddress, let port = proxyPort, !address.isEmpty, port > 0 else { - return nil - } - return Proxy(address, port) - } - - public var proxyAutoConfigurationURL: URL? - - public var proxyBypassDomains: [String]? - - public var mtuBytes: Int? - - public init() { - gatewayPolicies = [.IPv4, .IPv6] - } - - public init(from configuration: OpenVPN.Configuration) { - gatewayPolicies = configuration.routingPolicies - dnsProtocol = configuration.dnsProtocol - dnsServers = configuration.dnsServers - dnsHTTPSURL = configuration.dnsHTTPSURL - dnsTLSServerName = configuration.dnsTLSServerName - dnsSearchDomains = configuration.searchDomains - proxyAddress = configuration.httpProxy?.address - proxyPort = configuration.httpProxy?.port - proxyAutoConfigurationURL = configuration.proxyAutoConfigurationURL - proxyBypassDomains = configuration.proxyBypassDomains - mtuBytes = configuration.mtu - } - - public func copy(from settings: ProfileNetworkSettings) { - copyGateway(from: settings) - copyDNS(from: settings) - copyProxy(from: settings) - copyMTU(from: settings) - } - - public func copyGateway(from settings: ProfileNetworkSettings) { - gatewayPolicies = settings.gatewayPolicies - } - - public func copyDNS(from settings: ProfileNetworkSettings) { - dnsProtocol = settings.dnsProtocol - dnsServers = settings.dnsServers?.filter { !$0.isEmpty } - dnsHTTPSURL = settings.dnsHTTPSURL - dnsTLSServerName = settings.dnsTLSServerName - dnsSearchDomains = settings.dnsSearchDomains - } - - public func copyProxy(from settings: ProfileNetworkSettings) { - proxyAddress = settings.proxyAddress - proxyPort = settings.proxyPort - proxyAutoConfigurationURL = settings.proxyAutoConfigurationURL - proxyBypassDomains = settings.proxyBypassDomains?.filter { !$0.isEmpty } - } - - public func copyMTU(from settings: ProfileNetworkSettings) { - mtuBytes = settings.mtuBytes - } - - // MARK: CustomStringConvertible - - public var description: String { - let comps: [String] = [ - "gw: \(gatewayPolicies?.description ?? "")", - "dns: {protocol: \(dnsProtocol ?? .fallback), https: \(dnsHTTPSURL?.absoluteString ?? ""), tls: \(dnsTLSServerName?.description ?? ""), servers: \(dnsServers?.description ?? "[]"), domains: \(dnsSearchDomains?.description ?? "[]")}", - "proxy: {address: \(proxyAddress ?? ""), port: \(proxyPort?.description ?? ""), PAC: \(proxyAutoConfigurationURL?.absoluteString ?? ""), bypass: \(proxyBypassDomains?.description ?? "[]")}", - "mtu: {bytes: \(mtuBytes?.description ?? "default")}" - ] - return "{\(comps.joined(separator: ", "))}" - } -} - -extension OpenVPN.ConfigurationBuilder { - public mutating func applyGateway(from choices: ProfileNetworkChoices, settings: ProfileNetworkSettings) { - switch choices.gateway { - case .client: - break - - case .server: - routingPolicies = nil - - case .manual: - routingPolicies = settings.gatewayPolicies - } - } - - public mutating func applyDNS(from choices: ProfileNetworkChoices, settings: ProfileNetworkSettings) { - switch choices.dns { - case .client: - break - - case .server: - dnsProtocol = nil - dnsServers = nil - dnsHTTPSURL = nil - dnsTLSServerName = nil - searchDomains = nil - - case .manual: - dnsProtocol = settings.dnsProtocol - dnsServers = settings.dnsServers?.filter { !$0.isEmpty } - dnsHTTPSURL = settings.dnsHTTPSURL - dnsTLSServerName = settings.dnsTLSServerName - searchDomains = settings.dnsSearchDomains - } - } - - public mutating func applyProxy(from choices: ProfileNetworkChoices, settings: ProfileNetworkSettings) { - switch choices.proxy { - case .client: - break - - case .server: - httpProxy = nil - httpsProxy = nil - proxyAutoConfigurationURL = nil - proxyBypassDomains = nil - - case .manual: - if let proxyServer = settings.proxyServer { - httpProxy = proxyServer - httpsProxy = proxyServer - proxyBypassDomains = settings.proxyBypassDomains?.filter { !$0.isEmpty } - } else if let pac = settings.proxyAutoConfigurationURL { - proxyAutoConfigurationURL = pac - proxyBypassDomains = settings.proxyBypassDomains?.filter { !$0.isEmpty } - } else { - httpProxy = nil - httpsProxy = nil - proxyAutoConfigurationURL = nil - proxyBypassDomains = nil - } - } - } - - public mutating func applyMTU(from choices: ProfileNetworkChoices, settings: ProfileNetworkSettings) { - switch choices.mtu ?? ProfileNetworkChoices.defaultChoice { - case .client: - break - - case .server: - mtu = nil - - case .manual: - mtu = settings.mtuBytes - } - } -} - -extension ConnectionProfile { - public var clientNetworkSettings: ProfileNetworkSettings? { - guard let hostProfile = self as? HostConnectionProfile else { - return nil - } - return ProfileNetworkSettings(from: hostProfile.parameters.sessionConfiguration) - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/HostConnectionProfile.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/HostConnectionProfile.swift deleted file mode 100644 index 196ea3d8..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/HostConnectionProfile.swift +++ /dev/null @@ -1,157 +0,0 @@ -// -// HostConnectionProfile.m -// Passepartout -// -// Created by Davide De Rosa on 9/2/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import TunnelKit -import TunnelKitOpenVPN - -public class HostConnectionProfile: ConnectionProfile, Codable, Equatable { - - // XXX: drop after @transient serviceDelegate - public enum CodingKeys: CodingKey { - case hostname - - case parameters - - case customAddress - - case customProtocol - - case id - - case username - - case trustedNetworks - - case networkChoices - - case manualNetworkSettings - } - - public let hostname: String - - public var parameters: OpenVPNProvider.Configuration - - public var customAddress: String? - - public var customProtocol: EndpointProtocol? - - public init(hostname: String) { - id = UUID().uuidString - self.hostname = hostname - let sessionConfiguration = OpenVPN.ConfigurationBuilder().build() - parameters = OpenVPNProvider.ConfigurationBuilder(sessionConfiguration: sessionConfiguration).build() - - trustedNetworks = TrustedNetworks() - } - - // MARK: ConnectionProfile - - public var context: Context { - return .host - } - - public let id: String - - public var username: String? - - public var requiresCredentials: Bool { - return false - } - - public var trustedNetworks: TrustedNetworks! - - public var networkChoices: ProfileNetworkChoices? - - public var manualNetworkSettings: ProfileNetworkSettings? - - public weak var serviceDelegate: ConnectionServiceDelegate? - - public func generate(from configuration: OpenVPNProvider.Configuration, preferences: Preferences) throws -> OpenVPNProvider.Configuration { - guard let endpointProtocols = parameters.sessionConfiguration.endpointProtocols, !endpointProtocols.isEmpty else { - preconditionFailure("No endpointProtocols") - } - - // XXX: copy paste, error prone - var builder = parameters.builder() - builder.shouldDebug = configuration.shouldDebug - builder.debugLogFormat = configuration.debugLogFormat - builder.masksPrivateData = configuration.masksPrivateData - - if let address = customAddress { - builder.prefersResolvedAddresses = true - builder.resolvedAddresses = [address] - } - - // forcibly override hostname with profile hostname (never nil) - var sessionBuilder = builder.sessionConfiguration.builder() - sessionBuilder.hostname = hostname - sessionBuilder.tlsSecurityLevel = 0 // lowest, tolerate widest range of certificates - if sessionBuilder.mtu == nil { - sessionBuilder.mtu = configuration.sessionConfiguration.mtu - } - - if let proto = customProtocol { - sessionBuilder.endpointProtocols = [proto] - } else { - - // restrict "Any" protocol to UDP, unless there are no UDP endpoints - let allEndpoints = builder.sessionConfiguration.endpointProtocols - var endpoints = allEndpoints?.filter { $0.socketType == .udp } - if endpoints?.isEmpty ?? true { - endpoints = allEndpoints - } - sessionBuilder.endpointProtocols = endpoints - } - - builder.sessionConfiguration = sessionBuilder.build() - - return builder.build() - } -} - -public extension HostConnectionProfile { - static func ==(lhs: HostConnectionProfile, rhs: HostConnectionProfile) -> Bool { - return lhs.id == rhs.id - } -} - -public extension HostConnectionProfile { - var mainAddress: String? { - return hostname - } - - var addresses: [String] { - return [hostname] - } - - var protocols: [EndpointProtocol] { - return parameters.sessionConfiguration.endpointProtocols ?? [] - } - - var canCustomizeEndpoint: Bool { - return true - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/PlaceholderConnectionProfile.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/PlaceholderConnectionProfile.swift deleted file mode 100644 index 82175c8e..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/PlaceholderConnectionProfile.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// PlaceholderConnectionProfile.swift -// Passepartout -// -// Created by Davide De Rosa on 11/6/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import TunnelKit -import TunnelKitOpenVPN - -public class PlaceholderConnectionProfile: ConnectionProfile { - public let context: Context - - public let id: String - - public var username: String? = nil - - public var requiresCredentials: Bool = false - - public var trustedNetworks: TrustedNetworks! { - get { - fatalError("Getting trustedNetworks of a PlaceholderConnectionProfile") - } - set { - fatalError("Setting trustedNetworks of a PlaceholderConnectionProfile") - } - } - - public var networkChoices: ProfileNetworkChoices? - - public var manualNetworkSettings: ProfileNetworkSettings? - - public weak var serviceDelegate: ConnectionServiceDelegate? - - public func generate(from configuration: OpenVPNProvider.Configuration, preferences: Preferences) throws -> OpenVPNProvider.Configuration { - fatalError("Generating configuration from a PlaceholderConnectionProfile") - } - - public var mainAddress: String? = nil - - public var addresses: [String] = [] - - public var protocols: [EndpointProtocol] = [] - - public var canCustomizeEndpoint: Bool = false - - public var customAddress: String? - - public var customProtocol: EndpointProtocol? - - public init(_ context: Context, _ id: String) { - self.context = context - self.id = id - } - - public init(_ key: ProfileKey) { - context = key.context - id = key.id - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/ProfileKey.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/ProfileKey.swift deleted file mode 100644 index 192cc183..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/ProfileKey.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// ProfileKey.swift -// Passepartout -// -// Created by Davide De Rosa on 11/6/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import PassepartoutConstants - -public struct ProfileKey: RawRepresentable, Hashable, Codable, CustomStringConvertible { - private static let separator: Character = "." - - public let context: Context - - public let id: String - - public init(_ context: Context, _ id: String) { - self.context = context - self.id = id - } - - public init(_ profile: ConnectionProfile) { - context = profile.context - id = profile.id - } - - public init(_ name: InfrastructureName) { - context = .provider - id = name - } - - // MARK: RawRepresentable - - public var rawValue: String { - return "\(context)\(ProfileKey.separator)\(id)" - } - - public init?(rawValue: String) { - guard let separatorIndex = rawValue.firstIndex(of: ProfileKey.separator) else { - return nil - } - - let contextValue = rawValue[rawValue.startIndex... -// - -import Foundation -import TunnelKit -import TunnelKitOpenVPN -import PassepartoutConstants - -public class ProviderConnectionProfile: ConnectionProfile, Codable, Equatable { - - // XXX: drop after @transient serviceDelegate - public enum CodingKeys: CodingKey { - case name - - case poolId - - case presetId - - case customAddress - - case customProtocol - - case favoriteGroupIds - - case username - - case trustedNetworks - - case networkChoices - - case manualNetworkSettings - } - - public let name: InfrastructureName - - public var infrastructure: Infrastructure { - guard let infra = InfrastructureFactory.shared.infrastructure(forName: name) else { - fatalError("No infrastructure found for '\(name)'") - } - return infra - } - - public var poolId: String { - didSet { - validateEndpoint() - serviceDelegate?.connectionService(didUpdate: self) - } - } - - public var pool: Pool? { - return infrastructure.pool(for: poolId) - } - - public var presetId: String { - didSet { - validateEndpoint() - serviceDelegate?.connectionService(didUpdate: self) - } - } - - public var preset: InfrastructurePreset? { - return infrastructure.preset(for: presetId) - } - - public var customAddress: String? - - public var customProtocol: EndpointProtocol? - - public var favoriteGroupIds: [String]? - - public init(name: InfrastructureName) { - self.name = name - poolId = "" - presetId = "" - - username = nil - - poolId = infrastructure.defaultPool()?.id ?? infrastructure.defaults.pool - presetId = infrastructure.defaults.preset - - trustedNetworks = TrustedNetworks() - favoriteGroupIds = [] - } - - public func setSupportedPreset() { - guard let pool = pool else { - return - } - let supported = pool.supportedPresetIds(in: infrastructure) - if let current = preset?.id, !supported.contains(current), let fallback = supported.first { - presetId = fallback - } - } - - private func validateEndpoint() { - guard let pool = pool, let preset = preset else { - customAddress = nil - customProtocol = nil - return - } - if let address = customAddress, !pool.hasAddress(address) { - customAddress = nil - } - if let proto = customProtocol, !preset.hasProtocol(proto) { - customProtocol = nil - } - } - - // MARK: ConnectionProfile - - public var context: Context { - return .provider - } - - public var id: String { - return name - } - - public var username: String? - - public var requiresCredentials: Bool { - return true - } - - public var trustedNetworks: TrustedNetworks! - - public var networkChoices: ProfileNetworkChoices? - - public var manualNetworkSettings: ProfileNetworkSettings? - - public weak var serviceDelegate: ConnectionServiceDelegate? - - public func generate(from configuration: OpenVPNProvider.Configuration, preferences: Preferences) throws -> OpenVPNProvider.Configuration { - guard let pool = pool else { - preconditionFailure("Nil pool?") - } - guard let preset = preset else { - preconditionFailure("Nil preset?") - } - -// assert(!pool.numericAddresses.isEmpty) - - // XXX: copy paste, error prone - var builder = preset.configuration.builder() - builder.shouldDebug = configuration.shouldDebug - builder.debugLogFormat = configuration.debugLogFormat - builder.masksPrivateData = configuration.masksPrivateData - - do { - try preset.injectExternalConfiguration(&builder, with: name, pool: pool) - } catch { - throw ApplicationError.externalResources - } - - if let address = customAddress { - builder.prefersResolvedAddresses = true - builder.resolvedAddresses = [address] - } else if builder.sessionConfiguration.hostname == nil || (pool.isResolved ?? false) { - builder.prefersResolvedAddresses = true - builder.resolvedAddresses = pool.addresses() - } else { - builder.prefersResolvedAddresses = !preferences.resolvesHostname - builder.resolvedAddresses = pool.addresses() - } - - var sessionBuilder = builder.sessionConfiguration.builder() - if let proto = customProtocol { - sessionBuilder.endpointProtocols = [proto] - } else { - - // restrict "Any" protocol to UDP, unless there are no UDP endpoints - let allEndpoints = builder.sessionConfiguration.endpointProtocols - var endpoints = allEndpoints?.filter { $0.socketType == .udp } - if endpoints?.isEmpty ?? true { - endpoints = allEndpoints - } - - sessionBuilder.endpointProtocols = endpoints -// sessionBuilder.endpointProtocols = [ -// EndpointProtocol(.udp, 8080), -// EndpointProtocol(.tcp, 443) -// ] - } - sessionBuilder.routingPolicies = [.IPv4, .IPv6] - if sessionBuilder.mtu == nil { - sessionBuilder.mtu = configuration.sessionConfiguration.mtu - } - builder.sessionConfiguration = sessionBuilder.build() - - return builder.build() - } -} - -public extension ProviderConnectionProfile { - static func ==(lhs: ProviderConnectionProfile, rhs: ProviderConnectionProfile) -> Bool { - return lhs.id == rhs.id - } -} - -public extension ProviderConnectionProfile { - var mainAddress: String? { - guard let pool = pool else { - assertionFailure("Getting provider main address but no pool set") - return nil - } - return pool.hostname - } - - var addresses: [String] { - var addrs = pool?.addresses() ?? [] - if let pool = pool, pool.hostname == nil, !(pool.isResolved ?? false), let externalHostname = try? preset?.externalConfiguration(forKey: .hostname, infrastructureName: infrastructure.name, pool: pool) as? String { - addrs.insert(externalHostname, at: 0) - } - return addrs - } - - var protocols: [EndpointProtocol] { - return preset?.configuration.sessionConfiguration.endpointProtocols ?? [] - } - - var canCustomizeEndpoint: Bool { - return true - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/TransientStore.swift b/PassepartoutCore/Sources/PassepartoutCore/Model/TransientStore.swift deleted file mode 100644 index 64a98237..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/TransientStore.swift +++ /dev/null @@ -1,240 +0,0 @@ -// -// TransientStore.swift -// Passepartout -// -// Created by Davide De Rosa on 7/16/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import SwiftyBeaver -import TunnelKit -import TunnelKitOpenVPN -import PassepartoutConstants - -private let log = SwiftyBeaver.self - -public class TransientStore { - private struct Keys { - static let didHandleSubreddit = "DidHandleSubreddit" - - static let masksPrivateData = "MasksPrivateData" - - // migrations - - static let didMigrateHostsRoutingPolicies = "DidMigrateHostsRoutingPolicies" - - static let didMigrateDynamicProviders = "DidMigrateDynamicProviders" - - static let didMigrateHostsToUUID = "DidMigrateHostsToUUID" - - static let didMigrateKeychainContext = "didMigrateKeychainContext" - - static let didMigrateRetainLowMTU = "didMigrateRetainLowMTU" - } - - public static let shared = TransientStore() - - private static var serviceURL: URL { - return GroupConstants.App.documentsURL.appendingPathComponent(AppConstants.Store.serviceFilename) - } - - public let service: ConnectionService - - public static var didHandleSubreddit: Bool { - get { - return UserDefaults.standard.bool(forKey: Keys.didHandleSubreddit) - } - set { - UserDefaults.standard.set(newValue, forKey: Keys.didHandleSubreddit) - } - } - - public static var masksPrivateData: Bool { - get { - return UserDefaults.standard.bool(forKey: Keys.masksPrivateData) - } - set { - UserDefaults.standard.set(newValue, forKey: Keys.masksPrivateData) - } - } - - public static var didMigrateKeychainContext: Bool { - get { - return UserDefaults.standard.bool(forKey: Keys.didMigrateKeychainContext) - } - set { - UserDefaults.standard.set(newValue, forKey: Keys.didMigrateKeychainContext) - } - } - - public static var didMigrateRetainLowMTU: Bool { - get { - return UserDefaults.standard.bool(forKey: Keys.didMigrateRetainLowMTU) - } - set { - UserDefaults.standard.set(newValue, forKey: Keys.didMigrateRetainLowMTU) - } - } - - public static var baseVPNConfiguration: OpenVPNProvider.ConfigurationBuilder { - let sessionBuilder = OpenVPN.ConfigurationBuilder() - var builder = OpenVPNProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build()) - builder.shouldDebug = true -// builder.debugLogFormat = "$Dyyyy-MM-dd HH:mm:ss.SSS$d $L $N.$F:$l - $M" -// builder.debugLogFormat = "$DHH:mm:ss$d $N.$F:$l - $M" - builder.debugLogFormat = AppConstants.Log.debugFormat - builder.masksPrivateData = masksPrivateData - return builder - } - - private init() { - UserDefaults.standard.register(defaults: [ - Keys.didHandleSubreddit: false, - Keys.masksPrivateData: true - ]) - - // this must be graceful - ConnectionService.migrateJSON(from: TransientStore.serviceURL, to: TransientStore.serviceURL) - - let cfg = TransientStore.baseVPNConfiguration.build() - do { - var data = try Data(contentsOf: TransientStore.serviceURL) - if let content = String(data: data, encoding: .utf8) { - log.verbose("Service JSON:") - log.verbose(content) - } - - // pre-parsing migrations - if let migratedData = TransientStore.migratedDataIfNecessary(fromData: data) { - data = migratedData - } - - service = try JSONDecoder().decode(ConnectionService.self, from: data) - service.baseConfiguration = cfg - - // pre-load migrations - - service.loadProfiles() - - // post-load migrations - #if os(iOS) - if !TransientStore.didMigrateKeychainContext { - service.migrateKeychainContext() - TransientStore.didMigrateKeychainContext = true - } - if !TransientStore.didMigrateRetainLowMTU { - for key in service.allProfileKeys() { - guard let profile = service.profile(withKey: key) else { - continue - } - if profile.networkChoices == nil { - profile.networkChoices = ProfileNetworkChoices( - choice: (key.context == .provider) ? .server : .client - ) - } - if profile.manualNetworkSettings == nil { - profile.manualNetworkSettings = ProfileNetworkSettings() - } - if profile.manualNetworkSettings?.mtuBytes == nil { - profile.networkChoices?.mtu = .manual - profile.manualNetworkSettings?.mtuBytes = 1200 - } - } - TransientStore.didMigrateRetainLowMTU = true - } - #endif - } catch let e { - log.error("Could not decode service: \(e)") - service = ConnectionService( - withAppGroup: GroupConstants.App.groupId, - baseConfiguration: cfg - ) - - // fresh install, skip all migrations - TransientStore.didMigrateKeychainContext = true - } - service.observeVPNDataCount(milliseconds: GroupConstants.VPN.dataCountInterval) - } - - public func serialize(withProfiles: Bool) { - try? JSONEncoder().encode(service).write(to: TransientStore.serviceURL) - if withProfiles { - service.saveProfiles() - } - } - - // - - private static func migrateDocumentsToAppGroup() { - var hasMigrated = false - let oldDocumentsURL = FileManager.default.userURL(for: .documentDirectory, appending: nil) - let newDocumentsURL = GroupConstants.App.documentsURL - log.debug("App documentsURL: \(oldDocumentsURL)") - log.debug("Group documentsURL: \(newDocumentsURL)") - let fm = FileManager.default - do { - for c in try fm.contentsOfDirectory(atPath: oldDocumentsURL.path) { - guard c != "Inbox" else { - continue - } - let old = oldDocumentsURL.appendingPathComponent(c) - let new = newDocumentsURL.appendingPathComponent(c) - log.verbose("Move:") - log.verbose("\tFROM: \(old)") - log.verbose("\tTO: \(new)") - try fm.moveItem(at: old, to: new) - hasMigrated = true - } - } catch let e { - hasMigrated = false - log.error("Could not migrate documents to App Group: \(e)") - } - if hasMigrated { - log.debug("Documents migrated to App Group") - } - } - - private static func migratedDataIfNecessary(fromData data: Data) -> Data? { - guard var json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { - return nil - } - - // do JSON migrations here - migrateHostTitles(&json) - - guard let migratedData = try? JSONSerialization.data(withJSONObject: json, options: []) else { - return nil - } - return migratedData - } - - private static func migrateHostTitles(_ json: inout [String: Any]) { - if json["hostTitles"] == nil { - json["hostTitles"] = [:] - } - } -} - -extension TransientStore { - public var debugSnapshot: () -> String { - { self.service.vpnLog } - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Models/AppPreferences.swift b/PassepartoutCore/Sources/PassepartoutCore/Models/AppPreferences.swift new file mode 100644 index 00000000..3a1d98c1 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Models/AppPreferences.swift @@ -0,0 +1,46 @@ +// +// AppPreferences.swift +// Passepartout +// +// Created by Davide De Rosa on 2/10/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public protocol AppPreferences { + var activeProfileId: UUID? { get } + + var logFormat: String? { get } + + var tunnelLogFormat: String? { get } + + var masksPrivateData: Bool { get } +} + +public struct DefaultAppPreferences: AppPreferences { + public let activeProfileId: UUID? + + public let logFormat: String? + + public let tunnelLogFormat: String? + + public let masksPrivateData: Bool +} diff --git a/Passepartout/App/iOS/Scenes/ConfigurationModificationDelegate.swift b/PassepartoutCore/Sources/PassepartoutCore/Models/DebugLog.swift similarity index 74% rename from Passepartout/App/iOS/Scenes/ConfigurationModificationDelegate.swift rename to PassepartoutCore/Sources/PassepartoutCore/Models/DebugLog.swift index 8afcd8d1..19f989f5 100644 --- a/Passepartout/App/iOS/Scenes/ConfigurationModificationDelegate.swift +++ b/PassepartoutCore/Sources/PassepartoutCore/Models/DebugLog.swift @@ -1,8 +1,8 @@ // -// ConfigurationModificationDelegate.swift +// DebugLog.swift // Passepartout // -// Created by Davide De Rosa on 9/6/18. +// Created by Davide De Rosa on 6/26/18. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,10 +24,15 @@ // import Foundation -import PassepartoutCore -protocol ConfigurationModificationDelegate: AnyObject { - func configuration(didUpdate newConfiguration: OpenVPN.Configuration) - - func configurationShouldReinstall() +public struct DebugLog { + public let content: String + + public init(content: String) { + self.content = content + } + + public var contentData: Data? { + return content.data(using: .utf8) + } } diff --git a/PassepartoutCore/Sources/PassepartoutCore/Models/VPNConfiguration.swift b/PassepartoutCore/Sources/PassepartoutCore/Models/VPNConfiguration.swift new file mode 100644 index 00000000..795ff8fb --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Models/VPNConfiguration.swift @@ -0,0 +1,65 @@ +// +// VPNConfiguration.swift +// Passepartout +// +// Created by Davide De Rosa on 3/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitManager +import NetworkExtension + +public typealias VPNConfiguration = (neConfiguration: NetworkExtensionConfiguration, neExtra: NetworkExtensionExtra?) + +protocol VPNConfigurationProviding { + func vpnConfiguration(_ parameters: VPNConfigurationParameters) throws -> VPNConfiguration +} + +struct VPNConfigurationParameters { + let title: String + + let appGroup: String + + let preferences: AppPreferences + + let networkSettings: Profile.NetworkSettings + + let username: String? + + let passwordReference: Data? + + let onDemandRules: [NEOnDemandRule] + + init( + _ profile: Profile, + appGroup: String, + preferences: AppPreferences, + passwordReference: Data? + ) { + title = profile.header.name + self.appGroup = appGroup + self.preferences = preferences + networkSettings = profile.networkSettings + username = !profile.account.username.isEmpty ? profile.account.username : nil + self.passwordReference = passwordReference + onDemandRules = profile.onDemand.rules + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Models/VPNManager+ObservableState.swift b/PassepartoutCore/Sources/PassepartoutCore/Models/VPNManager+ObservableState.swift new file mode 100644 index 00000000..57cb2906 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/Models/VPNManager+ObservableState.swift @@ -0,0 +1,64 @@ +// +// VPNManager+ObservableState.swift +// Passepartout +// +// Created by Davide De Rosa on 3/27/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitManager + +extension VPNManager { + public class ObservableState: ObservableObject { + @Published public internal(set) var isEnabled = false { + didSet { + pp_log.debug("VPN enabled -> \(isEnabled)") + } + } + + @Published public internal(set) var vpnStatus: VPNStatus = .disconnected { + didSet { + pp_log.debug("VPN status: \(vpnStatus)") + } + } + + @Published public internal(set) var lastError: Error? { + didSet { + guard let lastError = lastError else { + return + } + pp_log.debug("Last error: \(lastError)") + } + } + + @Published public internal(set) var dataCount: DataCount? { + didSet { + guard let dataCount = dataCount else { + return + } + pp_log.debug("Data count: \(dataCount)") + } + } + + public init() { + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/PassepartoutCore.swift b/PassepartoutCore/Sources/PassepartoutCore/PassepartoutCore.swift new file mode 100644 index 00000000..9b36bd61 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutCore/PassepartoutCore.swift @@ -0,0 +1,30 @@ +// +// PassepartoutCore.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension PassepartoutError { + public static let missingAccount = Self("missingAccount") +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/de.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/de.lproj/Core.strings deleted file mode 100644 index ce1c0421..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/de.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "OK"; -"global.cancel" = "Abbrechen"; -"global.next" = "Weiter"; -"global.close" = "Schließen"; -"global.host.title_input.message" = "Gültige Zeichen beinhalten Buchstaben und Zahlen sowie Bindestrich \"-\", Unterstrich \"_\" und Punkt \".\"."; -"global.host.title_input.placeholder" = "Mein Profil"; -"global.email_not_configured" = "Es wurde kein Email-Account konfiguriert."; -"global.values.default" = "Default"; - -"global.captions.address" = "Adresse"; -"global.captions.port" = "Port"; -"global.captions.protocol" = "Protokoll"; - -"global.values.enabled" = "Aktiviert"; -"global.values.disabled" = "Deaktiviert"; -"global.values.none" = "Keine"; -"global.values.automatic" = "Automatisch"; -"global.values.manual" = "Manuell"; - -"reddit.title" = "Reddit"; -"reddit.message" = "Wusstest du, daß Passepartout einen Subreddit hat? Abonniere ihn für Updates oder um Features, Probleme, neue Plattformen zu diskutieren - oder was auch immer du möchtest.\n\nDies ist auch ein guter Weg zu zeigen dass dir dieses Projekt etwas bedeutet."; -"reddit.buttons.subscribe" = "Jetzt abbonnieren!"; -"reddit.buttons.remind" = "Später erinnern"; -"reddit.buttons.never" = "Nicht erneut fragen"; - -"vpn.connecting" = "Verbinde"; -"vpn.active" = "Aktiv"; -"vpn.disconnecting" = "Trenne"; -"vpn.inactive" = "Inaktiv"; -"vpn.disabled" = "Deaktiviert"; -"vpn.unused" = "Aus"; - -"vpn.errors.timeout" = "Timeout"; -"vpn.errors.dns" = "DNS fehlgeschlagen"; -"vpn.errors.auth" = "Authentifizierung fehlgeschlagen"; -"vpn.errors.tls" = "TLS fehlgeschlagen"; -"vpn.errors.encryption" = "Verschlüsselung fehlgeschlagen"; -"vpn.errors.compression" = "Komprimierung nicht unterstützt"; -"vpn.errors.network" = "Netzwerk geändert"; -"vpn.errors.routing" = "Kein Routing"; -"vpn.errors.gateway" = "Kein Gateway"; -"vpn.errors.shutdown" = "Server heruntergefahren"; - -"parsed_file.alerts.malformed.message" = "Die Konfigurations-Datei enthält eine ungültige Option (%@)."; -"parsed_file.alerts.missing.message" = "Die Konfigurations-Datei enthält eine benötigte Option nicht (%@)."; -"parsed_file.alerts.unsupported.message" = "Die Konfigurations-Datei enthält eine nicht unterstützte Option (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "Die Konfigurations-Datei ist korrekt, enthält aber möglicherweise eine nicht unterstützte Option (%@).\n\nDie Verbindung kann, abhängig von den Server-Einstellungen, unterbrochen werden."; -"parsed_file.alerts.encryption_passphrase.message" = "Bitte die Verschlüsselungs-Passphrase eingeben."; -"parsed_file.alerts.decryption.message" = "Die Konfiguration enthält einen verschlüsselten Private Key und konnte nicht entschlüsselt werden. Bitte überprüfe ob du die Passphrase eingegeben hast."; -"parsed_file.alerts.parsing.message" = "Fehler beim Verarbeiten der Konfigurationsdatei (%@)."; -"parsed_file.alerts.buttons.report" = "Ein Problem melden"; - -"network_choice.client" = ".ovpn-Datei einlesen"; -"network_choice.server" = "Vom Server holen"; - -"issue_reporter.title" = "Problem melden"; -"issue_reporter.message" = "Das Debug-Log deiner letzten Verbindung ist notwendig um dein Verbindungs-Problem zu untersuchen und ist vollständig anonymisiert.\n\nDie .ovpn-Konfigurations-Datei, sofern vorhanden, wird anonymisiert von jeglichen sensiblen Daten, angehangen.\n\nBitte prüfe im Zweifelsfall die Email-Anhänge."; -"issue_reporter.buttons.accept" = "Ich verstehe"; - -"translations.title" = "Übersetzungen"; - -"share.message" = "Passepartout ist ein Benutzerfreundlicher, Open Source OpenVPN client für iOS und macOS"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Anbieter"; -"organizer.menus.provider.unavailable" = "Keine Anbieter übrig"; -"organizer.menus.host" = "Host"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "Come watch me make Passepartout live on Twitch, join the chat to interact and contribute!"; -"organizer.sections.providers.header" = "Anbieter"; -"organizer.sections.providers.footer" = "Hier findest du einige Anbieter mit voreingestellten Konfigurationsprofilen."; -"organizer.sections.hosts.header" = "Hosts"; -"organizer.sections.hosts.footer" = "Importiere Hosts aus .ovpn Konfigurationsdateien."; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "Erhalte Hilfe von Siri um deine üblichen Interaktionen mit der App zu beschleunigen."; -"organizer.sections.support.header" = "Support"; -"organizer.sections.feedback.header" = "Feedback"; -"organizer.cells.follow_twitch.caption" = "Passepartout auf Twitch ansehen"; -"organizer.cells.profile.value.current" = "In Benutzung"; -"organizer.cells.siri_shortcuts.caption" = "Kurzbefehle verwalten"; -"organizer.cells.join_community.caption" = "Community beitreten"; -"organizer.cells.write_review.caption" = "Rezension schreiben"; -"organizer.cells.donate.caption" = "Spenden"; -"organizer.cells.github_sponsors.caption" = "Unterstütze mich bei GitHub"; -"organizer.cells.translate.caption" = "Übersetzung anbieten"; -"organizer.cells.about.caption" = "Über %@"; -"organizer.cells.uninstall.caption" = "VPN-Konfiguration entfernen"; -"organizer.cells.add_provider.caption" = "Neuen Anbieter hinzufügen"; -"organizer.cells.add_host.caption" = "Aus Dateien hinzufügen"; -"organizer.cells.import_host.caption" = "Vom Import hinzufügen"; -"organizer.alerts.exhausted_providers.message" = "Du hast Profile für alle verfügbaren Anbieter erstellt."; -"organizer.alerts.add_host.message" = "Öffne eine URL zu einer .ovpn-Konfigurationsdatei aus Safari, Mail oder anderen App um ein Host-Profil einzurichten.\n\nDu kannst auch eine .ovpn-Datei mit iTunes Dateifreigabe importieren."; -"organizer.alerts.cannot_donate.message" = "Auf diesem Gerät ist keine Bezahlmethode konfiguriert."; -"organizer.alerts.delete_vpn_profile.message" = "Möchtest du wirklich die VPN-Konfiguration aus deinen Geräte-Einstellungen löschen? Dies behebt möglicherweise manche kaputten VPN-Zustände und beeinflusst nicht deine Anbieter und Hosts-Profile."; -"organizer.alerts.remove_profile.title" = "Profil entfernen"; -"organizer.alerts.remove_profile.message" = "Bist du sicher, dass du das Profil %@ löschen möchtest?"; -"organizer.alerts.open_host_file.title" = "Wähle eine .ovpn-Datei."; - -"wizards.provider.cells.update_list.caption" = "Aktualisiere Liste"; -"wizards.provider.alerts.unavailable.message" = "Die Provider-Infrastruktur konnte nicht heruntergeladen werden. Bitte versuchen Sie es später erneut."; -"wizards.host.sections.existing.header" = "Bestehende Profile"; -"wizards.host.cells.title_input.caption" = "Titel"; -"wizards.host.alerts.existing.message" = "Ein Host-Profil mit identischem Titel existiert bereits. Ersetzen?"; - -"service.welcome.message" = "Willkommen bei Passepartout!\n\nBenutze den Organizer um ein neues Profil hinzuzufügen."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "Die Verbindung wird immer aufgebaut wenn notwendig."; -"service.sections.status.header" = "Verbindung"; -"service.sections.configuration.header" = "Konfiguration"; -"service.sections.provider_infrastructure.footer" = "Zuletzt aktualisiert am %@."; -"service.sections.vpn_survives_sleep.footer" = "Deaktivieren um die Batterielaufzeit zu verbessern, allerdings verzögert sich der Verbindungsaufbau beim Aufwachen."; -"service.sections.vpn_resolves_hostname.footer" = "Bevorzugt in den meisten Netzwerken und benötigt in manchen IPv6 Netzwerken. Deaktivieren wo DNS geblockt ist oder um die Aushandlung zu beschleunigen bei langsam antwortenden DNS."; -"service.sections.trusted.header" = "Vertrauenswürdige Netzwerke"; -"service.sections.trusted.footer" = "Wenn ein vertrauenswürdiges Netzwerk verbunden wird, wird normalerweise die VPN-Verbindung beendet und bleibt deaktiviert. Deaktiviere diese Option um dieses Verhalten zu unterbinden."; -"service.sections.diagnostics.header" = "Diagnose"; -"service.sections.diagnostics.footer" = "Zensier-Status wird aktiv nach erneutem Verbinden. Netzwerk-Daten sind Hostnamen, IP-Adressen, Routingtabellen, SSID. Zugangsdaten und Private Keys werden nie gelogged."; -"service.cells.use_profile.caption" = "Dieses Profil verwenden"; -"service.cells.vpn_service.caption" = "Aktiviert"; -"service.cells.vpn.turn_on.caption" = "Aktiviere VPN"; -"service.cells.vpn.turn_off.caption" = "Deaktiviere VPN"; -"service.cells.connection_status.caption" = "Status"; -"service.cells.host.parameters.caption" = "Parameter"; -"service.cells.provider.pool.caption" = "Ort"; -"service.cells.provider.preset.caption" = "Voreinstellung"; -"service.cells.provider.refresh.caption" = "Infrastruktur neu laden"; -"service.cells.category.caption" = "Kategorie"; -"service.cells.addresses.caption" = "Adressen"; -"service.cells.only_shows_favorites.caption" = "Nur favorisierte Standorte anzeigen"; -"service.cells.vpn_survives_sleep.caption" = "Verbindung aktiv halten trotz Schlafmodus"; -"service.cells.vpn_resolves_hostname.caption" = "Server Hostname auflösen"; -"service.cells.trusted_add_wifi.caption" = "WLAN hinzufügen"; -"service.cells.trusted_mobile.caption" = "Mobilfunknetz"; -"service.cells.trusted_policy.caption" = "Vertrauen deaktiviert VPN"; -"service.cells.test_connectivity.caption" = "Verbindung testen"; -"service.cells.data_count.caption" = "Ausgetauschte Datenmenge"; -"service.cells.data_count.none" = "Nicht verfügbar"; -"service.cells.server_configuration.caption" = "Serverkonfiguration"; -"service.cells.server_network.caption" = "Servernetzwerk"; -"service.cells.debug_log.caption" = "Debug log"; -"service.cells.masks_private_data.caption" = "Netzwerkdaten zensieren"; -"service.cells.reconnect.caption" = "Erneut verbinden"; -"service.cells.report_issue.caption" = "Verbindungsproblem melden"; - -"service.alerts.rename.title" = "Profil umbenennen"; -"service.alerts.credentials_needed.message" = "Du musst zuerst die Account-Zugangsdaten eingeben."; -"service.alerts.reconnect_vpn.message" = "Möchtest du erneut zum VPN verbinden?"; -"service.alerts.trusted.no_network.message" = "Du bist mit keinem WLAN verbunden."; -"service.alerts.trusted.will_disconnect_trusted.message" = "In dem du diesem Netzwerk vertraust, wird das VPN getrennt. Weiter?"; -"service.alerts.trusted.will_disconnect_policy.message" = "Durch das Ändern der Vertrauens-Policy könnte das VPN deaktiviert werden. Weiter?"; -"service.alerts.test_connectivity.title" = "Konnektivität"; -"service.alerts.test_connectivity.messages.success" = "Dein Gerät ist mit dem Internet verbunden!"; -"service.alerts.test_connectivity.messages.failure" = "Dein Gerät hat keine Verbindung mit dem Internet, bitte prüfe deine Profil-Parameter."; -"service.alerts.configuration.disconnected" = "Konfiguration nicht verfügbar, stellen Sie sicher, dass Sie mit dem VPN verbunden sind."; -"service.alerts.masks_private_data.messages.must_reconnect" = "Um das aktuelle Debug-Log sicher zurückzusetzen und die neuen Zensier-Paramenter anzuwenden, musst du das VPN jetzt erneut verbinden."; -"service.alerts.buttons.reconnect" = "Erneut verbinden"; -"service.alerts.download.title" = "Download benötigt"; -"service.alerts.download.message" = "%@ benötigt den Download von zusätzlichen Konfigurationsdateien.\n\nBestätige um mit dem Download zu beginnen."; -"service.alerts.download.failed" = "Herunterladen der Konfigurationsdateien fehlgeschlagen. %@"; -"service.alerts.download.hud.extracting" = "Extrahiere Dateien, bitte warten..."; -"service.alerts.location.message.denied" = "Sie müssen den Standortzugriff zulassen, um diesem Wi-Fi-Netzwerk vertrauen zu können. Gehen Sie zu den iOS-Einstellungen und überprüfen Sie Ihre Standortberechtigungen für Passepartout."; -"service.alerts.location.button.settings" = "Einstellungen"; - -"provider.pool.sections.empty_favorites.footer" = "Wische nach Links um einen Standort zu den Favoriten hinzuzufügen oder zu entfernen."; -"provider.pool.actions.favorite" = "Favorit hinzuzufügen"; -"provider.pool.actions.unfavorite" = "Favorit entfernen"; - -"provider.preset.cells.tech_details.caption" = "Technische Details"; - -"account.title" = "Account"; -"account.sections.credentials.header" = "Zugangsdaten"; -"account.sections.guidance.footer.infrastructure.default.web" = "Benutze deine %@ Web-Zugangsdaten."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Verwenden Sie Ihre %@ service-Anmeldeinformationen, die von den Website-Anmeldeinformationen abweichen können."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Benutze deine %@ Web-Zugangsdaten. Dein Benutzername ist üblicherweise numerischt (ohne Zwischenraum)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Benutze deine %@ Web-Zugangsdaten. Dein Benutzername ist üblicherweise deine Email."; -"account.sections.guidance.footer.infrastructure.pia" = "Benutze deine %@ Web-Zugangsdaten. Dein Benutzername ist üblicherweise numerischt mit einem \"p\" Präfix."; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Deine Zugangsdaten für %@ findest du unter \"Account > OpenVPN / IKEv2 Username\" auf der Webseite."; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Benutze deine %@ Web-Zugangsdaten. Dein Benutzername ist üblicherweise deine Email."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Benutze deine %@ Web-Zugangsdaten. Dein Benutzername ist üblicherweise deine Email."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Deine Zugangsdaten für %@ findest du im OpenVPN Config Generator auf der Webseite."; -"account.sections.registration.footer" = "Beantrage einen Account auf der %@ Webseite."; -"account.cells.username.caption" = "Benutzername"; -"account.cells.username.placeholder" = "Benutzername"; -"account.cells.password.caption" = "Passwort"; -"account.cells.password.placeholder" = "Geheim"; -"account.cells.open_guide.caption" = "Siehe deine Zugangsdaten"; -"account.cells.signup.caption" = "Registrieren bei %@"; - -"endpoint.title" = "Endpoint"; -"endpoint.sections.location_addresses.header" = "Adressen"; -"endpoint.sections.location_protocols.header" = "Protokolle"; -"endpoint.cells.address" = "Adresse"; -"endpoint.cells.protocol" = "Protokoll"; -"endpoint.cells.any_address.caption" = "Automatisch"; -"endpoint.cells.any_protocol.caption" = "Automatisch"; - -"network_settings.title" = "Netzwerk-Einstellungen"; -"network_settings.cells.add_dns_server.caption" = "Adresse hinzufügen"; -"network_settings.cells.add_dns_domain.caption" = "Domäne hinzufügen"; -"network_settings.cells.proxy_bypass.caption" = "Domäne umgehen"; -"network_settings.cells.add_proxy_bypass.caption" = "Zu umgehende Domäne hinzufügen"; - -"configuration.title" = "Konfiguration"; -"configuration.sections.communication.header" = "Kommunikation"; -"configuration.sections.reset.footer" = "Wenn du nach einer Änderung der Kommunikations-Parameter dich nicht mehr verbinden kannst, hier tippen um zur originalen Konfiguration zurückzukehren."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Komprimierung"; -"configuration.sections.network.header" = "Netzwerk"; -"configuration.sections.other.header" = "Andere"; -"configuration.cells.cipher.caption" = "Chiffre"; -"configuration.cells.digest.caption" = "Authentifizierung"; -"configuration.cells.digest.value.embedded" = "Eingebettet"; -"configuration.cells.compression_framing.caption" = "Framing"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "Algorithmus"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "Nicht unterstützt"; -"configuration.cells.reset_original.caption" = "Konfiguration zurücksetzen"; -"configuration.cells.client.caption" = "Client Zertifikat"; -"configuration.cells.client.value.enabled" = "Geprüft"; -"configuration.cells.client.value.disabled" = "Nicht geprüft"; -"configuration.cells.tls_wrapping.caption" = "Wrapping"; -"configuration.cells.tls_wrapping.value.auth" = "Authentifizierung"; -"configuration.cells.tls_wrapping.value.crypt" = "Verschlüsselung"; -"configuration.cells.eku.caption" = "Erweiterte Verifizierung"; -"configuration.cells.keep_alive.caption" = "Keep-alive"; -"configuration.cells.keep_alive.value.seconds" = "%d Sekunden"; -"configuration.cells.renegotiation_seconds.caption" = "erneute Aushandlung"; -"configuration.cells.renegotiation_seconds.value.after" = "nach %@"; -"configuration.cells.random_endpoint.caption" = "Endpunkt zufällig wählen"; -"configuration.alerts.commit.message" = "Neue Parameter werden erst nach einer manuellen Wiederherstellung der Verbindung wirksam. Änderungen in vertrauenswürdigen Netzwerken werden sofort wirksam."; -"configuration.alerts.commit.buttons.reconnect" = "Jetzt erneut verbinden"; -"configuration.alerts.commit.buttons.skip" = "Überspringen"; - -"trusted.columns.trust.title" = "Vertrauen"; -"trusted.ethernet.title" = "Kabelverbindungen vertrauen"; -"trusted.ethernet.description" = "Hier ein Häkchen setzen, um jeder Kabelverbindung zu vertrauen."; - -"preferences.title" = "Einstellungen"; -"preferences.sections.general.header" = "Allgemein"; -"preferences.cells.launches_on_login.caption" = "Bei Anmeldung starten"; -"preferences.cells.launches_on_login.footer" = "Hier ein Häkchen setzen, um die App beim Systemstart oder der Anmeldung automatisch zu starten."; -"preferences.cells.confirm_quit.caption" = "Beenden bestätigen"; -"preferences.cells.confirm_quit.footer" = "Hier ein Häkchen setzen, um einen Hinweis zur Bestätigung des Beendens anzuzeigen."; - -"network_settings.gateway.title" = "Standard-Gateway"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Server"; -"network_settings.dns.cells.domain.caption" = "Domäne"; -"network_settings.dns.cells.domains.title" = "Domänen"; -"network_settings.proxy.title" = "Proxy"; -"network_settings.proxy.cells.bypass_domains.title" = "Domänen umgehen"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "Route"; - -"debug_log.buttons.previous" = "Zurück"; -"debug_log.buttons.next" = "Weiter"; -"debug_log.buttons.copy" = "Kopieren"; -"debug_log.alerts.empty_log.message" = "Das Debug-Log ist leer."; - -"shortcuts.add.title" = "Füge Kurzbefehl hinzu"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "WLAN"; -"shortcuts.add.sections.cellular.header" = "Mobilfunknetz"; -"shortcuts.add.cells.connect.caption" = "Verbinde mit"; -"shortcuts.add.cells.enable_vpn.caption" = "Aktiviere VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Deaktiviere VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Vertraue aktivem WLAN"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Misstraue aktivem WLAN"; -"shortcuts.add.cells.trust_cellular.caption" = "Vertraue Mobilfunknetz"; -"shortcuts.add.cells.untrust_cellular.caption" = "Misstraue Mobilfunknetz"; -"shortcuts.add.alerts.no_profiles.message" = "Es gibt kein Profil mit dem eine Verbindung hergestellt werden kann."; - -"shortcuts.edit.title" = "Kurzbefehle bearbeiten"; -"shortcuts.edit.sections.all.header" = "Existierende Kurzbefehle"; -"shortcuts.edit.cells.add_shortcut.caption" = "Kurzbefehl hinzufügen"; - -"purchase.title" = "Kaufen"; -"purchase.sections.products.footer" = "Jedes Produkt ist ein einmaliger Kauf. Der Kauf eines Providers beinhaltet kein VPN-Abonnement."; -"purchase.cells.full_version.extra_description" = "Alle Anbieter (inklusive Zukünftige)\n%@"; -"purchase.cells.restore.title" = "Einkäufe wiederherstellen"; -"purchase.cells.restore.description" = "Wenn Sie diese App oder Funktion in der Vergangenheit gekauft haben, können Sie Ihre Einkäufe wiederherstellen und dieser Bildschirm wird nicht mehr angezeigt."; - -"donation.title" = "Spenden"; -"donation.sections.one_time.header" = "Einmalig"; -"donation.sections.one_time.footer" = "Wenn du dich erkenntlich zeigen möchtest für meine Arbeit, gibt es hier ein paar Beträge die du direkt spenden kannst.\n\nDu bezahlst pro Spende nur einmal und kannst mehrmals spenden wenn du möchtest."; -"donation.cells.loading.caption" = "Lade Spenden"; -"donation.cells.purchasing.caption" = "Führe Spende durch"; -"donation.alerts.purchase.success.title" = "Danke"; -"donation.alerts.purchase.success.message" = "Das bedeutet mir viel und ich hoffe wirklich dass du die App weiterhin benutzt und unterstützt."; -"donation.alerts.purchase.failure.message" = "Konnte Spende nicht durchführen. %@"; - -"about.title" = "Über"; -"about.sections.web.header" = "Web"; -"about.sections.share.header" = "Teilen"; -"about.cells.credits.caption" = "Credits"; -"about.cells.website.caption" = "Homepage"; -"about.cells.faq.caption" = "FAQ"; -"about.cells.disclaimer.caption" = "Haftungsausschluss"; -"about.cells.privacy_policy.caption" = "Datenschutzrichtlinie"; -"about.cells.share_twitter.caption" = "Darüber Twittern!"; -"about.cells.share_generic.caption" = "Freund einladen"; - -"version.title" = "Version"; -"version.labels.intro" = "Passepartout und TunnelKit sind geschrieben und gewartet von Davide De Rosa (keeshux).\n\nQuellcode für Passepartout und TunnelKit ist öffentlich auf GitHub unter GPLv3 verfügbar, du findest die Links auf der Homepage.\n\nPassepartout ist ein inoffizieller client und auf keine Art und Weise mit OpenVPN Inc. verbunden."; - -"credits.title" = "Credits"; -"credits.sections.licenses.header" = "Lizenzen"; -"credits.sections.notices.header" = "Notizen"; -"credits.sections.translations.header" = "Übersetzungen"; - -"label.license.error" = "Konnte vollständigen Lizenz-Inhalt nicht herunterladen."; - -// iOS only - -"imported_hosts.title" = "Importierte Hosts"; - -// macOS only - -"menu.show.title" = "Anzeigen"; -"menu.switch_profile.title" = "Aktives Profil"; -"menu.active_profile.title.none" = "Keine aktiven Profile"; -"menu.active_profile.items.customize.title" = "Anpassen..."; -"menu.active_profile.messages.missing_credentials" = "Es wurde keine Konto konfiguriert"; -"menu.organizer.title" = "Organizer"; -"menu.preferences.title" = "Einstellungen"; -"menu.support.title" = "Support"; -"menu.quit.title" = "%@ beenden"; -"menu.quit.messages.confirm" = "Wenn das VPN aktiviert wurde, läuft es weiter im Hintergrund. Möchtest du beenden?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/el.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/el.lproj/Core.strings deleted file mode 100644 index d212131c..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/el.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "OK"; -"global.cancel" = "Ακύρωση"; -"global.next" = "Επόμενο"; -"global.close" = "Κλείσιμο"; -"global.host.title_input.message" = "Αποδεκτοί χαρακτήρες είναι οι αλφαριθμητικοί συν τη παύλα \"-\", κάτω παύλα \"_\" και τελεία \".\"."; -"global.host.title_input.placeholder" = "Το προφίλ μου"; -"global.email_not_configured" = "Δεν έχει ρυθμιστεί λογαριασμός ηλεκτρονικού ταχυδρομείου."; -"global.values.default" = "Default"; - -"global.captions.address" = "Διεύθυνση"; -"global.captions.port" = "Θύρα"; -"global.captions.protocol" = "Πρωτόκολλο"; - -"global.values.enabled" = "Ενεργοποιήθηκε"; -"global.values.disabled" = "Απενεργοποιήθηκε"; -"global.values.none" = "Κανένα"; -"global.values.automatic" = "Αυτόματο"; -"global.values.manual" = "Χειροκίνητο"; - -"reddit.title" = "Reddit"; -"reddit.message" = "Γνωρίζατε ότι το Passepartout έχει subreddit? Εγγραφείτε για ενημερώσεις ή για να συζητήσετε προβλήματα της εφαρμογές, νέες δυνατότητες και άλλα.\n\nΕίναι επίσης ένας ωραίος τρόπος να δείξετε ότι ενδιαφέρεστε για τη προσπάθεια αυτή."; -"reddit.buttons.subscribe" = "Εγγραφή τώρα!"; -"reddit.buttons.remind" = "Υπενθύμιση Αργότερα"; -"reddit.buttons.never" = "Μη με ρωτήσεις ξανά"; - -"vpn.connecting" = "Προσπάθεια Σύνδεσης"; -"vpn.active" = "Ενεργό"; -"vpn.disconnecting" = "Αποσυνδέετε"; -"vpn.inactive" = "Μη ενεργό"; -"vpn.disabled" = "Απενεργοποιημένο"; -"vpn.unused" = "Ανενεργό"; - -"vpn.errors.timeout" = "Τέλος χρονικού Ορίου"; -"vpn.errors.dns" = "Το DNS απέτυχε"; -"vpn.errors.auth" = "Το Auth απέτυχε"; -"vpn.errors.tls" = "Το TLS απέτυχε"; -"vpn.errors.encryption" = "Η Κρυπτογράφηση απέτυχε"; -"vpn.errors.compression" = "Η συμπίεση δεν υποστηρίζεται"; -"vpn.errors.network" = "Το δίκτυο άλλαξε"; -"vpn.errors.routing" = "Λείπει η δρομολόγηση"; -"vpn.errors.gateway" = "Δεν υπάρχει πύλη"; -"vpn.errors.shutdown" = "Ο διακομιστής έκλεισε"; - -"parsed_file.alerts.malformed.message" = "Το αρχείο ρυθμίσεων περιέχει μια ακατάλληλη επιλογή (%@)."; -"parsed_file.alerts.missing.message" = "Το αρχείο διαμόρφωσης δεν διαθέτει την απαιτούμενη επιλογή (%@)."; -"parsed_file.alerts.unsupported.message" = "Το αρχείο διαμόρφωσης περιέχει μια επιλογή που δεν υποστηρίζεται (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "Το αρχείο ρυθμίσεων είναι σωστό, αλλά περιέχει μια δυνητικά μη υποστηριζόμενη επιλογή (%@).\n\nΗ δυνατότητα σύνδεσης μπορεί να διακοπεί ανάλογα με τις ρυθμίσεις του διακομιστή."; -"parsed_file.alerts.encryption_passphrase.message" = "Εισαγάγετε το κωδικό κρυπτογράφησης."; -"parsed_file.alerts.decryption.message" = "Η διαμόρφωση περιέχει κρυπτογραφημένο ιδιωτικό κλειδί και δεν ήταν δυνατό να αποκρυπτογραφηθεί. Δείτε πάλι το κωδικό που καταχωρίσατε."; -"parsed_file.alerts.parsing.message" = "Δεν είναι δυνατή η ανάλυση του παρεχόμενου αρχείου ρύθμισης παραμέτρων (%@)."; -"parsed_file.alerts.buttons.report" = "Αναφέρετε ένα πρόβλημα"; - -"network_choice.client" = "Ανάγνωση .ovpn"; -"network_choice.server" = "Λήψη από το διακομιστή"; - -"issue_reporter.title" = "Αναφορά Προβλήματος"; -"issue_reporter.message" = "The debug log of your latest connections is crucial to resolve your connectivity issues and is completely anonymous.\n\nThe .ovpn configuration file, if any, is attached stripped of any sensitive data.\n\nPlease double check the e-mail attachments if unsure."; -"issue_reporter.buttons.accept" = "Καταλαβαίνω"; - -"translations.title" = "Μεταφράσεις"; - -"share.message" = "Το Passepartout είναι φιλικό προς το χρήστη, ανοιχτού κώδικα OpenVPN πρόγραμμα για iOS και macOS"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Πάροχος"; -"organizer.menus.provider.unavailable" = "Δεν απομένουν πάροχοι"; -"organizer.menus.host" = "Εξυπηρετητής"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "Ελάτε να με παρακολουθήσετε ζωντανά το Passepartout στο Twitch, εγγραφείτε στη συνομιλία για να αλληλεπιδράσετε και να συνεισφέρετε!"; -"organizer.sections.providers.header" = "Πάροχοι"; -"organizer.sections.providers.footer" = "Εδώ θα βρείτε ορισμένους παρόχους με προκαθορισμένες ρυθμίσεις προφίλ."; -"organizer.sections.hosts.header" = "Φιλοξενητές"; -"organizer.sections.hosts.footer" = "Εισάγετε φιλοξενητές από ένα raw .ovpn αρχείο."; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "Get help from Siri to speed up your most common interactions with the app."; -"organizer.sections.support.header" = "Υποστήριξη"; -"organizer.sections.feedback.header" = "Ανατροφοδότηση"; -"organizer.cells.follow_twitch.caption" = "Παρακολουθήστε το Passepartout στο Twitch"; -"organizer.cells.profile.value.current" = "Σε χρήση"; -"organizer.cells.siri_shortcuts.caption" = "Διαχείριση Συντομεύσεων"; -"organizer.cells.join_community.caption" = "Συμμετοχή στην κοινότητα"; -"organizer.cells.write_review.caption" = "Γράψτε μια κριτική"; -"organizer.cells.donate.caption" = "Κάντε μια δωρεά"; -"organizer.cells.github_sponsors.caption" = "Υποστηρίξτε με στο GitHub"; -"organizer.cells.translate.caption" = "Βοηθήστε στη μετάφραση"; -"organizer.cells.about.caption" = "Σχετικά με %@"; -"organizer.cells.uninstall.caption" = "Αφαίρεση ρύθμισης VPN"; -"organizer.cells.add_provider.caption" = "Προσθήκη νέου παρόχου"; -"organizer.cells.add_host.caption" = "Προσθήκη από αρχεία"; -"organizer.cells.import_host.caption" = "Προσθήκη από εισαγωγή"; -"organizer.alerts.exhausted_providers.message" = "Έχετε δημιουργήσει προφίλ για οποιονδήποτε διαθέσιμο πάροχο."; -"organizer.alerts.add_host.message" = "Εισάγετε μια διεύθυνση από ένα αρχείο .ovpn στο Safari, το Mail ή άλλη εφαρμογή για να ρυθμίσετε ένα προφίλ διακομιστή.\n\nΜπορείτε επίσης να εισάγετε ένα .ovpn αρχείο από το iTunes File Sharing."; -"organizer.alerts.cannot_donate.message" = "Δεν έχει ρυθμιστεί καμία μέθοδος πληρωμής σε αυτήν τη συσκευή."; -"organizer.alerts.delete_vpn_profile.message" = "Θέλετε πραγματικά να διαγράψετε τη διαμόρφωση VPN από τις ρυθμίσεις της συσκευής σας; Αυτό μπορεί να διορθώσει κάποιες καταστραμμένες καταστάσεις VPN και δεν θα επηρεάσει τα προφίλ του παροχέα και του διακομιστή σας."; -"organizer.alerts.remove_profile.title" = "Κατάργηση προφίλ"; -"organizer.alerts.remove_profile.message" = "Είστε βέβαιοι ότι θέλετε να διαγράψετε το προφίλ %@;"; -"organizer.alerts.open_host_file.title" = "Επιλέξτε ένα αρχείο .ovpn"; - -"wizards.provider.cells.update_list.caption" = "Αναβάθμιση Λίστας"; -"wizards.provider.alerts.unavailable.message" = "Δεν ήταν δυνατή η λήψη της υποδομής του παρόχου, δοκιμάστε ξανά αργότερα."; -"wizards.host.sections.existing.header" = "Υπάρχον Προφίλ"; -"wizards.host.cells.title_input.caption" = "Τίτλος"; -"wizards.host.alerts.existing.message" = "Ένα προφίλ διακομιστή με τον ίδιο τίτλο υπάρχει ήδη. Αντικατέστησέ το;"; - -"service.welcome.message" = "Καλώς Ήλθατε στο Passepartout!\n\nΧρησιμοποιήστε τον διοργανωτή για να προσθέσετε ένα νέο προφίλ."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "Η σύνδεση θα πραγματοποιηθεί όποτε είναι απαραίτητο."; -"service.sections.status.header" = "Σύνδεση"; -"service.sections.configuration.header" = "Ρύθμιση"; -"service.sections.provider_infrastructure.footer" = "Τελευταία ενημέρωση στις %@."; -"service.sections.vpn_survives_sleep.footer" = "Απενεργοποιήστε για να βελτιώσετε τη χρήση της μπαταρίας, εις βάρος των περιστασιακών επιβραδύνσεων που οφείλονται σε επανασύνδεση αφύπνισης."; -"service.sections.vpn_resolves_hostname.footer" = "Προτιμάται στα περισσότερα δίκτυα και απαιτείται σε ορισμένα δίκτυα IPv6. Απενεργοποιήστε το εκεί που μπλοκάρεται το DNS ή για να επιταχύνετε τη επικοινωνία όταν το DNS είναι αργό για να ανταποκριθεί."; -"service.sections.trusted.header" = "Αξιόπιστα δίκτυα"; -"service.sections.trusted.footer" = "Κατά την είσοδο σε ένα αξιόπιστο δίκτυο, το VPN απενεργοποιείται κανονικά και διατηρείται αποσυνδεδεμένο. Απενεργοποιήστε αυτήν την επιλογή για να μην έχετε μια τέτοια συμπεριφορά."; -"service.sections.diagnostics.header" = "Διαγνωστικά"; -"service.sections.diagnostics.footer" = "Η κατάσταση κάλυψης θα είναι αποτελεσματική μετά την επανασύνδεση. Τα δεδομένα δικτύου είναι του διακομιστή, διευθύνσεις IP, δρομολόγηση και SSID. Τα διαπιστευτήρια και τα ιδιωτικά κλειδιά δεν καταγράφονται ανεξάρτητα."; -"service.cells.use_profile.caption" = "Χρησιμοποιήστε αυτό το προφίλ"; -"service.cells.vpn_service.caption" = "Ενεργοποιήθηκε"; -"service.cells.vpn.turn_on.caption" = "Ενεργοποίηση VPN"; -"service.cells.vpn.turn_off.caption" = "Απενεργοποίηση VPN"; -"service.cells.connection_status.caption" = "Κατάσταση"; -"service.cells.host.parameters.caption" = "Παράμετροι"; -"service.cells.provider.pool.caption" = "Τοποθεσία"; -"service.cells.provider.preset.caption" = "Προεπιλογή"; -"service.cells.provider.refresh.caption" = "Ανανέωση της υποδομής"; -"service.cells.category.caption" = "Κατηγορία"; -"service.cells.addresses.caption" = "Διευθύνσεις"; -"service.cells.only_shows_favorites.caption" = "Προβολή αγαπημένων τοποθεσιών μόνο"; -"service.cells.vpn_survives_sleep.caption" = "Κρατήστε ζωντανό στον ύπνο"; -"service.cells.vpn_resolves_hostname.caption" = "Επίλυση του ονόματος σέρβερ διακομιστή"; -"service.cells.trusted_add_wifi.caption" = "Προσθέστε Wi-Fi"; -"service.cells.trusted_mobile.caption" = "Δίκτυο Κινητής"; -"service.cells.trusted_policy.caption" = "Τα αξιόπιστα δίκτυα απενεργοποιούν το VPN"; -"service.cells.test_connectivity.caption" = "Δοκιμή συνδεσιμότητας"; -"service.cells.data_count.caption" = "Ανταλλαγή δεδομένων"; -"service.cells.data_count.none" = "Μη διαθέσιμο"; -"service.cells.server_configuration.caption" = "Ρυθμίσεις Διακομιστή"; -"service.cells.server_network.caption" = "Δίκτυο Διακομιστή"; -"service.cells.debug_log.caption" = "Μητρώο εντοπισμού σφαλμάτων"; -"service.cells.masks_private_data.caption" = "Μάσκα δεδομένα δικτύου"; -"service.cells.reconnect.caption" = "Επανασύνδεση"; -"service.cells.report_issue.caption" = "Αναφορά ζητήματος συνδεσιμότητας"; - -"service.alerts.rename.title" = "Μετονομασία προφίλ"; -"service.alerts.credentials_needed.message" = "Πρέπει πρώτα να εισαγάγετε διαπιστευτήρια λογαριασμού."; -"service.alerts.reconnect_vpn.message" = "Θέλετε να συνδεθείτε ξανά με το VPN;"; -"service.alerts.trusted.no_network.message" = "Δεν είστε συνδεδεμένοι σε κανένα δίκτυο Wi-Fi."; -"service.alerts.trusted.will_disconnect_trusted.message" = "Με εμπιστοσύνη σε αυτό το δίκτυο, το VPN μπορεί να αποσυνδεθεί. Να συνεχίσω;"; -"service.alerts.trusted.will_disconnect_policy.message" = "Αλλάζοντας την πολιτική εμπιστοσύνης, το VPN μπορεί να αποσυνδεθεί. Να συνεχίσω;"; -"service.alerts.test_connectivity.title" = "Συνδεσιμότητα"; -"service.alerts.test_connectivity.messages.success" = "Η συσκευή σας είναι συνδεδεμένη στο Διαδίκτυο!"; -"service.alerts.test_connectivity.messages.failure" = "Η συσκευή σας δεν διαθέτει σύνδεση στο Internet, παρακαλούμε να ελέγξετε τις παραμέτρους του προφίλ σας."; -"service.alerts.configuration.disconnected" = "Μη διαθέσιμη Ρύθμιση, επιβεβαιώστε ότι είστε συνδεδεμένοι στο VPN."; -"service.alerts.masks_private_data.messages.must_reconnect" = "Για να επαναφέρετε με ασφάλεια την τρέχουσα καταγραφή εντοπισμού σφαλμάτων και να εφαρμόσετε τη νέα προτίμηση κάλυψης, πρέπει να συνδεθείτε ξανά με το VPN."; -"service.alerts.buttons.reconnect" = "Επανασύνδεση"; -"service.alerts.download.title" = "Απαιτείται λήψη"; -"service.alerts.download.message" = "%@ απαιτεί τη λήψη πρόσθετων αρχείων ρυθμίσεων.\n\nΕπιβεβαιώστε για να ξεκινήσετε τη λήψη."; -"service.alerts.download.failed" = "Αποτυχία λήψης αρχείων ρυθμίσεων. %@"; -"service.alerts.download.hud.extracting" = "Εξάγοντας τα αρχεία, παρακαλώ να είστε υπομονετικοί..."; -"service.alerts.location.message.denied" = "Πρέπει να επιτρέψετε τη πρόσβαση τοποθεσίας για να εμπιστευτείτε το Wi-Fi δίκτυο. Μεταβείτε στις ρυθμίσεις του iOS και επιθεωρείστε τις ρυθμίσεις για το Passepartout."; -"service.alerts.location.button.settings" = "Ρυθμίσεις"; - -"provider.pool.sections.empty_favorites.footer" = "Σείρετε αριστερά για να προσθέσετε ή να αφαιρέσεται από τα αγαπημένα."; -"provider.pool.actions.favorite" = "Αγαπημένο"; -"provider.pool.actions.unfavorite" = "Δεν προτιμάται"; - -"provider.preset.cells.tech_details.caption" = "Τεχνικές Λεπτομέρειες"; - -"account.title" = "Λογαριασμός"; -"account.sections.credentials.header" = "Διαπιστευτήρια"; -"account.sections.guidance.footer.infrastructure.default.web" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Χρησιμοποιήστε τα διαπιστευτήρια της υπηρεσίας %@, τα οποία ενδέχεται να διαφέρουν από τα διαπιστευτήρια του ιστότοπου."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@. Το όνομα χρήστη είναι συνήθως αριθμητικό (χωρίς διαστήματα)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@. Το όνομα χρήστη είναι συνήθως το ηλεκτρονικό σας ταχυδρομείο."; -"account.sections.guidance.footer.infrastructure.pia" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@. Το όνομα χρήστη είναι συνήθως αριθμητικό με πρόθεμα \"p\"."; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Βρείτε τα διαπιστευτήριά σας %@ στην ενότητα \"Λογαριασμός> OpenVPN / IKEv2 Username \" της ιστοσελίδας."; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@. Το όνομα χρήστη είναι συνήθως το ηλεκτρονικό σας ταχυδρομείο."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Χρησιμοποιήστε τα διαπιστευτήρια ιστοτόπου %@. Το όνομα χρήστη είναι συνήθως το ηλεκτρονικό σας ταχυδρομείο."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Βρείτε τα διαπιστευτήριά σας %@ στο OpenVPN Config Generator στον ιστότοπο."; -"account.sections.registration.footer" = "Πηγαίνετε να αποκτήσετε λογαριασμό στον ιστότοπο %@."; -"account.cells.username.caption" = "Όνομα χρήστη"; -"account.cells.username.placeholder" = "χρήστης"; -"account.cells.password.caption" = "Κωδικός"; -"account.cells.password.placeholder" = "κωδικός"; -"account.cells.open_guide.caption" = "Δείτε τα διαπιστευτήρια σας"; -"account.cells.signup.caption" = "Εγγραφείτε με %@"; - -"endpoint.title" = "Τελικό σημείο"; -"endpoint.sections.location_addresses.header" = "Διεθύνσεις"; -"endpoint.sections.location_protocols.header" = "Πρωτόκολλα"; -"endpoint.cells.address" = "Διεύθυνση"; -"endpoint.cells.protocol" = "Πρωτόκολλο"; -"endpoint.cells.any_address.caption" = "Αυτόματο"; -"endpoint.cells.any_protocol.caption" = "Αυτόματο"; - -"network_settings.title" = "Ρυθμίσεις Δικτύου"; -"network_settings.cells.add_dns_server.caption" = "Προσθήκη Διεύθυνσης"; -"network_settings.cells.add_dns_domain.caption" = "Προσθήκη τομέα αναζήτησης"; -"network_settings.cells.proxy_bypass.caption" = "Παράκαμψη Τομέα"; -"network_settings.cells.add_proxy_bypass.caption" = "Προσθήκη τομέα παράκαμψης"; - -"configuration.title" = "Ρύθμιση"; -"configuration.sections.communication.header" = "Επικοινωνία"; -"configuration.sections.reset.footer" = "Αν καταλήξατε σε κατεστραμένη συνδεσιμότητα μετά την αλλαγή των παραμέτρων επικοινωνίας, πατήστε για να επανέλθετε στην αρχική διαμόρφωση."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Συμπίεση"; -"configuration.sections.network.header" = "Δίκτυο"; -"configuration.sections.other.header" = "Άλλο"; -"configuration.cells.cipher.caption" = "Cipher"; -"configuration.cells.digest.caption" = "Αυθεντικοποίηση"; -"configuration.cells.digest.value.embedded" = "Ενσωματωμένο"; -"configuration.cells.compression_framing.caption" = "Framing"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--συμπίεση"; -"configuration.cells.compression_algorithm.caption" = "Αλγόρυθμος"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "Δεν υποστηρίζεται"; -"configuration.cells.reset_original.caption" = "Επαναφορά ρυθμίσεων"; -"configuration.cells.client.caption" = "Πιστοποιητικό πελάτη"; -"configuration.cells.client.value.enabled" = "Επαληθεύτηκε"; -"configuration.cells.client.value.disabled" = "Δεν επαληθεύτηκε"; -"configuration.cells.tls_wrapping.caption" = "Wrapping"; -"configuration.cells.tls_wrapping.value.auth" = "Αυθεντικοποίηση"; -"configuration.cells.tls_wrapping.value.crypt" = "Κρυπτογράφηση"; -"configuration.cells.eku.caption" = "Εκτεταμένη επαλήθευση"; -"configuration.cells.keep_alive.caption" = "Διατηρήστε ζωντανή"; -"configuration.cells.keep_alive.value.seconds" = "%d δευτερόλεπτα"; -"configuration.cells.renegotiation_seconds.caption" = "Επαναδιαπραγμάτευση"; -"configuration.cells.renegotiation_seconds.value.after" = "μετά από %@"; -"configuration.cells.random_endpoint.caption" = "Τυχαίο τελικό σημείο"; -"configuration.alerts.commit.message" = "Οι νέες παράμετροι δεν θα είναι αποτελεσματικές έως ότου επανασυνδεθείτε χειροκίνητα. Οι αλλαγές στα αξιόπιστα δίκτυα θα εφαρμοστούν αμέσως."; -"configuration.alerts.commit.buttons.reconnect" = "Επανασυνδεθείτε τώρα"; -"configuration.alerts.commit.buttons.skip" = "Παράλειψη"; - -"trusted.columns.trust.title" = "Εμπιστευθείτε"; -"trusted.ethernet.title" = "Εμπιστευθείτε τις ενσύρματες συνδέσεις"; -"trusted.ethernet.description" = "Επιλέξτε για να εμπιστευθείτε οποιαδήποτε ενσύρματη σύνδεση καλωδίου."; - -"preferences.title" = "Προτιμήσεις"; -"preferences.sections.general.header" = "Γενικός"; -"preferences.cells.launches_on_login.caption" = "Εκκίνηση κατά τη σύνδεση"; -"preferences.cells.launches_on_login.footer" = "Επιλέξτε για αυτόματη εκκίνηση της εφαρμογής κατά την εκκίνηση ή τη σύνδεση."; -"preferences.cells.confirm_quit.caption" = "Επιβεβαίωση διακοπής"; -"preferences.cells.confirm_quit.footer" = "Επιλέξτε για παρουσίαση ειδοποίησης ότι επιβεβαιώνεται η διακοπή."; - -"network_settings.gateway.title" = "Προεπιλεγμένη πύλη"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Διακομιστές"; -"network_settings.dns.cells.domain.caption" = "Domain"; -"network_settings.dns.cells.domains.title" = "Τομείς"; -"network_settings.proxy.title" = "Proxy"; -"network_settings.proxy.cells.bypass_domains.title" = "Παράκαμψη τομέων"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "Διαδρομή"; - -"debug_log.buttons.previous" = "Προηγούμενο"; -"debug_log.buttons.next" = "Επόμενο"; -"debug_log.buttons.copy" = "Αντιγραφή"; -"debug_log.alerts.empty_log.message" = "Το αρχείο εντοπισμού σφαλμάτων είναι κενό."; - -"shortcuts.add.title" = "Προσθήκη Συντόμευσης"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "Δίκτυο Κινητής"; -"shortcuts.add.cells.connect.caption" = "Σύνδεση σε"; -"shortcuts.add.cells.enable_vpn.caption" = "Ενεργοποίηση VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Απενεργοποίηση VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Εμπιστέψου το τρέχον Wi-Fi"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Μην εμπιστευθείτε το τρέχον Wi-Fi"; -"shortcuts.add.cells.trust_cellular.caption" = "Εμπιστοσύνη δικτύου κινητής τηλεφωνίας"; -"shortcuts.add.cells.untrust_cellular.caption" = "Μην εμπιστευθείτε το δίκτυο κινητής τηλεφωνίας"; -"shortcuts.add.alerts.no_profiles.message" = "Δεν υπάρχει προφίλ για σύνδεση."; - -"shortcuts.edit.title" = "Διαχείριση συντομεύσεων"; -"shortcuts.edit.sections.all.header" = "Υπάρχουσες συντομεύσεις"; -"shortcuts.edit.cells.add_shortcut.caption" = "Προσθήκη Συντόμευσης"; - -"purchase.title" = "Αγορά"; -"purchase.sections.products.footer" = "Κάθε προϊόν είναι μια αγορά. Οι αγορές παρόχων δεν περιλαμβάνουν τη συνδρομή VPN."; -"purchase.cells.full_version.extra_description" = "Όλοι οι πάροχοι (περιλαμβάνονται και οι μελλοντικοί)\n%@"; -"purchase.cells.restore.title" = "Επαναφορά Αγορών"; -"purchase.cells.restore.description" = "Εαν αγοράσατε την εφαρμογή στο παρελθόν, μπορείτε να κάνετε επαναφορά αγορών και αυτή η οθόνη δε θα εμφανιστεί ξανά."; - -"donation.title" = "Δωρεά"; -"donation.sections.one_time.header" = "Μια Φορά"; -"donation.sections.one_time.footer" = "Αν είστε χαρούμενη με τη δουλειά μου, εδώ είναι λίγα ποσά που μπορείτε να δώσετε αμέσως.\n\nΘα χρεωθείτε μόνο μία φορά και μπορείτε να δώσετε πολλές φορές."; -"donation.cells.loading.caption" = "Φόρτωση δωρεών"; -"donation.cells.purchasing.caption" = "Εκτέλεση δωρεάς"; -"donation.alerts.purchase.success.title" = "Ευχαριστώ"; -"donation.alerts.purchase.success.message" = "Αυτό σημαίνει πολλά για μένα και πραγματικά ελπίζω να συνεχίσετε να χρησιμοποιείτε και να προωθείτε αυτήν την εφαρμογή."; -"donation.alerts.purchase.failure.message" = "Δεν είναι δυνατή η εκτέλεση της δωρεάς. %@"; - -"about.title" = "Περι"; -"about.sections.web.header" = "Web"; -"about.sections.share.header" = "Διαμοιράστε"; -"about.cells.credits.caption" = "Συντελεστές"; -"about.cells.website.caption" = "Αρχική Σελίδα"; -"about.cells.faq.caption" = "Συχνές Ερωτήσεις"; -"about.cells.disclaimer.caption" = "Άρνηση Ευθύνης"; -"about.cells.privacy_policy.caption" = "Πολιτική Απορρήτου"; -"about.cells.share_twitter.caption" = "Tweet γι 'αυτό!"; -"about.cells.share_generic.caption" = "Πρόσκληση Φίλου"; - -"version.title" = "Έκδοση"; -"version.labels.intro" = "Το Passepartout και το TunnelKit γράφονται και συντηρούνται από τον Davide De Rosa (keeshux).\n\nΟ πηγαίος κώδικας για το Passepartout και το TunnelKit είναι δημόσια διαθέσιμε στο GitHub υπό το GPLv3, μπορείτε να βρείτε συνδέσμους στην αρχική σελίδα.\n\nΤο Passepartout είναι ένας μη επίσημος πελάτης και δεν είναι συνδεδεμένος με το OpenVPN Inc."; - -"credits.title" = "Συντελεστές"; -"credits.sections.licenses.header" = "Άδειες"; -"credits.sections.notices.header" = "Σημειώσεις"; -"credits.sections.translations.header" = "Μεταφράσεις"; - -"label.license.error" = "Δεν είναι δυνατή η λήψη πλήρους περιεχομένου άδειας χρήσης."; - -// iOS only - -"imported_hosts.title" = "Εισαγόμενοι διακομιστές"; - -// macOS only - -"menu.show.title" = "Προβολή"; -"menu.switch_profile.title" = "Ενεργό προφίλ"; -"menu.active_profile.title.none" = "Δεν υπάρχει ενεργό προφίλ"; -"menu.active_profile.items.customize.title" = "Προσαρμογή..."; -"menu.active_profile.messages.missing_credentials" = "Δεν έχει διαμορφωθεί λογαριασμός"; -"menu.organizer.title" = "Διοργανωτής"; -"menu.preferences.title" = "Προτιμήσεις"; -"menu.support.title" = "Υποστήριξη"; -"menu.quit.title" = "Διακοπή %@"; -"menu.quit.messages.confirm" = "Το VPN, αν είναι ενεργοποιημένο, θα εξακολουθεί να εκτελείται στο παρασκήνιο. Θέλετε να το διακόψετε;"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/en.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/en.lproj/Core.strings deleted file mode 100644 index e3f6aa01..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/en.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "OK"; -"global.cancel" = "Cancel"; -"global.next" = "Next"; -"global.close" = "Close"; -"global.host.title_input.message" = "Acceptable characters are alphanumerics plus dash \"-\", underscore \"_\" and dot \".\"."; -"global.host.title_input.placeholder" = "My profile"; -"global.email_not_configured" = "No e-mail account is configured."; -"global.values.default" = "Default"; - -"global.captions.address" = "Address"; -"global.captions.port" = "Port"; -"global.captions.protocol" = "Protocol"; - -"global.values.enabled" = "Enabled"; -"global.values.disabled" = "Disabled"; -"global.values.none" = "None"; -"global.values.automatic" = "Automatic"; -"global.values.manual" = "Manual"; - -"reddit.title" = "Reddit"; -"reddit.message" = "Did you know that Passepartout has a subreddit? Subscribe for updates or to discuss issues, features, new platforms or whatever you like.\n\nIt's also a great way to show you care about this project."; -"reddit.buttons.subscribe" = "Subscribe now!"; -"reddit.buttons.remind" = "Remind me later"; -"reddit.buttons.never" = "Don't ask again"; - -"vpn.connecting" = "Connecting"; -"vpn.active" = "Active"; -"vpn.disconnecting" = "Disconnecting"; -"vpn.inactive" = "Inactive"; -"vpn.disabled" = "Disabled"; -"vpn.unused" = "Off"; - -"vpn.errors.timeout" = "Timeout"; -"vpn.errors.dns" = "DNS failed"; -"vpn.errors.auth" = "Auth failed"; -"vpn.errors.tls" = "TLS failed"; -"vpn.errors.encryption" = "Encryption failed"; -"vpn.errors.compression" = "Compression unsupported"; -"vpn.errors.network" = "Network changed"; -"vpn.errors.routing" = "Missing routing"; -"vpn.errors.gateway" = "No gateway"; -"vpn.errors.shutdown" = "Server shutdown"; - -"parsed_file.alerts.malformed.message" = "The configuration file contains a malformed option (%@)."; -"parsed_file.alerts.missing.message" = "The configuration file lacks a required option (%@)."; -"parsed_file.alerts.unsupported.message" = "The configuration file contains an unsupported option (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "The configuration file is correct but contains a potentially unsupported option (%@).\n\nConnectivity may break depending on server settings."; -"parsed_file.alerts.encryption_passphrase.message" = "Please enter the encryption passphrase."; -"parsed_file.alerts.decryption.message" = "The configuration contains an encrypted private key and it could not be decrypted. Double check your entered passphrase."; -"parsed_file.alerts.parsing.message" = "Unable to parse the provided configuration file (%@)."; -"parsed_file.alerts.buttons.report" = "Report an issue"; - -"network_choice.client" = "Read .ovpn"; -"network_choice.server" = "Pull from server"; - -"issue_reporter.title" = "Report issue"; -"issue_reporter.message" = "The debug log of your latest connections is crucial to resolve your connectivity issues and is completely anonymous.\n\nThe .ovpn configuration file, if any, is attached stripped of any sensitive data.\n\nPlease double check the e-mail attachments if unsure."; -"issue_reporter.buttons.accept" = "I understand"; - -"translations.title" = "Translations"; - -"share.message" = "Passepartout is an user-friendly, open source OpenVPN client for iOS and macOS"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Provider"; -"organizer.menus.provider.unavailable" = "No providers left"; -"organizer.menus.host" = "Host"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "Come watch me make Passepartout live on Twitch, join the chat to interact and contribute!"; -"organizer.sections.providers.header" = "Providers"; -"organizer.sections.providers.footer" = "Here you find a few providers with preset configuration profiles."; -"organizer.sections.hosts.header" = "Hosts"; -"organizer.sections.hosts.footer" = "Import hosts from raw .ovpn configuration files."; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "Get help from Siri to speed up your most common interactions with the app."; -"organizer.sections.support.header" = "Support"; -"organizer.sections.feedback.header" = "Feedback"; -"organizer.cells.follow_twitch.caption" = "Watch Passepartout on Twitch"; -"organizer.cells.profile.value.current" = "In use"; -"organizer.cells.siri_shortcuts.caption" = "Manage shortcuts"; -"organizer.cells.join_community.caption" = "Join community"; -"organizer.cells.write_review.caption" = "Write a review"; -"organizer.cells.donate.caption" = "Make a donation"; -"organizer.cells.github_sponsors.caption" = "Support me on GitHub"; -"organizer.cells.translate.caption" = "Offer to translate"; -"organizer.cells.about.caption" = "About %@"; -"organizer.cells.uninstall.caption" = "Remove VPN configuration"; -"organizer.cells.add_provider.caption" = "Add new provider"; -"organizer.cells.add_host.caption" = "Add from Files"; -"organizer.cells.import_host.caption" = "Add from imported"; -"organizer.alerts.exhausted_providers.message" = "You have created profiles for any available provider."; -"organizer.alerts.add_host.message" = "Open an URL to an .ovpn configuration file from Safari, Mail or another app to set up a host profile.\n\nYou can also import an .ovpn with iTunes File Sharing."; -"organizer.alerts.cannot_donate.message" = "There is no payment method configured on this device."; -"organizer.alerts.delete_vpn_profile.message" = "Do you really want to erase the VPN configuration from your device settings? This may fix some broken VPN states and will not affect your provider and host profiles."; -"organizer.alerts.remove_profile.title" = "Remove profile"; -"organizer.alerts.remove_profile.message" = "Are you sure you want to delete profile %@?"; -"organizer.alerts.open_host_file.title" = "Select an .ovpn file"; - -"wizards.provider.cells.update_list.caption" = "Update list"; -"wizards.provider.alerts.unavailable.message" = "Could not download provider infrastructure, please retry later."; -"wizards.host.sections.existing.header" = "Existing profiles"; -"wizards.host.cells.title_input.caption" = "Title"; -"wizards.host.alerts.existing.message" = "A host profile with the same title already exists. Replace it?"; - -"service.welcome.message" = "Welcome to Passepartout!\n\nUse the organizer to add a new profile."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "The connection will be established whenever necessary."; -"service.sections.status.header" = "Connection"; -"service.sections.configuration.header" = "Configuration"; -"service.sections.provider_infrastructure.footer" = "Last updated on %@."; -"service.sections.vpn_survives_sleep.footer" = "Disable to improve battery usage, at the expense of occasional slowdowns due to wake-up reconnections."; -"service.sections.vpn_resolves_hostname.footer" = "Preferred in most networks and required in some IPv6 networks. Disable where DNS is blocked, or to speed up negotiation when DNS is slow to respond."; -"service.sections.trusted.header" = "Trusted networks"; -"service.sections.trusted.footer" = "When entering a trusted network, the VPN is normally shut down and kept disconnected. Disable this option to not enforce such behavior."; -"service.sections.diagnostics.header" = "Diagnostics"; -"service.sections.diagnostics.footer" = "Masking status will be effective after reconnecting. Network data are hostnames, IP addresses, routing, SSID. Credentials and private keys are not logged regardless."; -"service.cells.use_profile.caption" = "Use this profile"; -"service.cells.vpn_service.caption" = "Enabled"; -"service.cells.vpn.turn_on.caption" = "Enable VPN"; -"service.cells.vpn.turn_off.caption" = "Disable VPN"; -"service.cells.connection_status.caption" = "Status"; -"service.cells.host.parameters.caption" = "Parameters"; -"service.cells.provider.pool.caption" = "Location"; -"service.cells.provider.preset.caption" = "Preset"; -"service.cells.provider.refresh.caption" = "Refresh infrastructure"; -"service.cells.category.caption" = "Category"; -"service.cells.addresses.caption" = "Addresses"; -"service.cells.only_shows_favorites.caption" = "Only show favorite locations"; -"service.cells.vpn_survives_sleep.caption" = "Keep alive on sleep"; -"service.cells.vpn_resolves_hostname.caption" = "Resolve provider hostname"; -"service.cells.trusted_add_wifi.caption" = "Add Wi-Fi"; -"service.cells.trusted_mobile.caption" = "Cellular network"; -"service.cells.trusted_policy.caption" = "Trust disables VPN"; -"service.cells.test_connectivity.caption" = "Test connectivity"; -"service.cells.data_count.caption" = "Exchanged data"; -"service.cells.data_count.none" = "Unavailable"; -"service.cells.server_configuration.caption" = "Server configuration"; -"service.cells.server_network.caption" = "Server network"; -"service.cells.debug_log.caption" = "Debug log"; -"service.cells.masks_private_data.caption" = "Mask network data"; -"service.cells.reconnect.caption" = "Reconnect"; -"service.cells.report_issue.caption" = "Report connectivity issue"; - -"service.alerts.rename.title" = "Rename profile"; -"service.alerts.credentials_needed.message" = "You need to enter account credentials first."; -"service.alerts.reconnect_vpn.message" = "Do you want to reconnect to the VPN?"; -"service.alerts.trusted.no_network.message" = "You are not connected to any Wi-Fi network."; -"service.alerts.trusted.will_disconnect_trusted.message" = "By trusting this network, the VPN may be disconnected. Continue?"; -"service.alerts.trusted.will_disconnect_policy.message" = "By changing the trust policy, the VPN may be disconnected. Continue?"; -"service.alerts.test_connectivity.title" = "Connectivity"; -"service.alerts.test_connectivity.messages.success" = "Your device is connected to the Internet!"; -"service.alerts.test_connectivity.messages.failure" = "Your device has no Internet connectivity, please review your profile parameters."; -"service.alerts.configuration.disconnected" = "Configuration unavailable, make sure you are connected to the VPN."; -"service.alerts.masks_private_data.messages.must_reconnect" = "In order to safely reset the current debug log and apply the new masking preference, you must reconnect to the VPN now."; -"service.alerts.buttons.reconnect" = "Reconnect"; -"service.alerts.download.title" = "Download required"; -"service.alerts.download.message" = "%@ requires the download of additional configuration files.\n\nConfirm to start the download."; -"service.alerts.download.failed" = "Failed to download configuration files. %@"; -"service.alerts.download.hud.extracting" = "Extracting files, please be patient..."; -"service.alerts.location.message.denied" = "You must allow location access to trust this Wi-Fi network. Go to iOS settings and review your location permissions for Passepartout."; -"service.alerts.location.button.settings" = "Settings"; - -"provider.pool.sections.empty_favorites.footer" = "Swipe left on a location to add or remove it from Favorites."; -"provider.pool.actions.favorite" = "Favorite"; -"provider.pool.actions.unfavorite" = "Unfavorite"; - -"provider.preset.cells.tech_details.caption" = "Technical details"; - -"account.title" = "Account"; -"account.sections.credentials.header" = "Credentials"; -"account.sections.guidance.footer.infrastructure.default.web" = "Use your %@ website credentials."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Use your %@ service credentials, which may differ from website credentials."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Use your %@ website credentials. Your username is usually numeric (without spaces)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Use your %@ website credentials. Your username is usually your e-mail."; -"account.sections.guidance.footer.infrastructure.pia" = "Use your %@ website credentials. Your username is usually numeric with a \"p\" prefix."; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Find your %@ credentials in the \"Account > OpenVPN / IKEv2 Username\" section of the website."; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Use your %@ website credentials. Your username is usually your e-mail."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Use your %@ website credentials. Your username is usually your e-mail."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Find your %@ credentials in the OpenVPN Config Generator on the website."; -"account.sections.registration.footer" = "Go get an account on the %@ website."; -"account.cells.username.caption" = "Username"; -"account.cells.username.placeholder" = "username"; -"account.cells.password.caption" = "Password"; -"account.cells.password.placeholder" = "secret"; -"account.cells.open_guide.caption" = "See your credentials"; -"account.cells.signup.caption" = "Register with %@"; - -"endpoint.title" = "Endpoint"; -"endpoint.sections.location_addresses.header" = "Addresses"; -"endpoint.sections.location_protocols.header" = "Protocols"; -"endpoint.cells.address" = "Address"; -"endpoint.cells.protocol" = "Protocol"; -"endpoint.cells.any_address.caption" = "Automatic"; -"endpoint.cells.any_protocol.caption" = "Automatic"; - -"network_settings.title" = "Network settings"; -"network_settings.cells.add_dns_server.caption" = "Add address"; -"network_settings.cells.add_dns_domain.caption" = "Add search domain"; -"network_settings.cells.proxy_bypass.caption" = "Bypass domain"; -"network_settings.cells.add_proxy_bypass.caption" = "Add bypass domain"; - -"configuration.title" = "Configuration"; -"configuration.sections.communication.header" = "Communication"; -"configuration.sections.reset.footer" = "If you ended up with broken connectivity after changing the communication parameters, tap to revert to the original configuration."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Compression"; -"configuration.sections.network.header" = "Network"; -"configuration.sections.other.header" = "Other"; -"configuration.cells.cipher.caption" = "Cipher"; -"configuration.cells.digest.caption" = "Authentication"; -"configuration.cells.digest.value.embedded" = "Embedded"; -"configuration.cells.compression_framing.caption" = "Framing"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "Algorithm"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "Unsupported"; -"configuration.cells.reset_original.caption" = "Reset configuration"; -"configuration.cells.client.caption" = "Client certificate"; -"configuration.cells.client.value.enabled" = "Verified"; -"configuration.cells.client.value.disabled" = "Not verified"; -"configuration.cells.tls_wrapping.caption" = "Wrapping"; -"configuration.cells.tls_wrapping.value.auth" = "Authentication"; -"configuration.cells.tls_wrapping.value.crypt" = "Encryption"; -"configuration.cells.eku.caption" = "Extended verification"; -"configuration.cells.keep_alive.caption" = "Keep-alive"; -"configuration.cells.keep_alive.value.seconds" = "%d seconds"; -"configuration.cells.renegotiation_seconds.caption" = "Renegotiation"; -"configuration.cells.renegotiation_seconds.value.after" = "after %@"; -"configuration.cells.random_endpoint.caption" = "Randomize endpoint"; -"configuration.alerts.commit.message" = "New parameters will not be effective until you reconnect manually. Changes in trusted networks will apply immediately."; -"configuration.alerts.commit.buttons.reconnect" = "Reconnect now"; -"configuration.alerts.commit.buttons.skip" = "Skip"; - -"trusted.columns.trust.title" = "Trust"; -"trusted.ethernet.title" = "Trust wired connections"; -"trusted.ethernet.description" = "Check to trust any wired cable connection."; - -"preferences.title" = "Preferences"; -"preferences.sections.general.header" = "General"; -"preferences.cells.launches_on_login.caption" = "Launch on login"; -"preferences.cells.launches_on_login.footer" = "Check to automatically launch the app on boot or login."; -"preferences.cells.confirm_quit.caption" = "Confirm quit"; -"preferences.cells.confirm_quit.footer" = "Check to present a quit confirmation alert."; - -"network_settings.gateway.title" = "Default gateway"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Servers"; -"network_settings.dns.cells.domain.caption" = "Domain"; -"network_settings.dns.cells.domains.title" = "Domains"; -"network_settings.proxy.title" = "Proxy"; -"network_settings.proxy.cells.bypass_domains.title" = "Bypass domains"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "Route"; - -"debug_log.buttons.previous" = "Previous"; -"debug_log.buttons.next" = "Next"; -"debug_log.buttons.copy" = "Copy"; -"debug_log.alerts.empty_log.message" = "The debug log is empty."; - -"shortcuts.add.title" = "Add shortcut"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "Cellular"; -"shortcuts.add.cells.connect.caption" = "Connect to"; -"shortcuts.add.cells.enable_vpn.caption" = "Enable VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Disable VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Trust current Wi-Fi"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Untrust current Wi-Fi"; -"shortcuts.add.cells.trust_cellular.caption" = "Trust cellular network"; -"shortcuts.add.cells.untrust_cellular.caption" = "Untrust cellular network"; -"shortcuts.add.alerts.no_profiles.message" = "There is no profile to connect to."; - -"shortcuts.edit.title" = "Manage shortcuts"; -"shortcuts.edit.sections.all.header" = "Existing shortcuts"; -"shortcuts.edit.cells.add_shortcut.caption" = "Add shortcut"; - -"purchase.title" = "Purchase"; -"purchase.sections.products.footer" = "Every product is a one-time purchase. Provider purchases do not include a VPN subscription."; -"purchase.cells.full_version.extra_description" = "All providers (including future ones)\n%@"; -"purchase.cells.restore.title" = "Restore purchases"; -"purchase.cells.restore.description" = "If you bought this app or feature in the past, you can restore your purchases and this screen won't show again."; - -"donation.title" = "Donate"; -"donation.sections.one_time.header" = "One time"; -"donation.sections.one_time.footer" = "If you want to display gratitude for my free work, here are a couple amounts you can donate instantly.\n\nYou will only be charged once per donation, and you can donate multiple times."; -"donation.cells.loading.caption" = "Loading donations"; -"donation.cells.purchasing.caption" = "Performing donation"; -"donation.alerts.purchase.success.title" = "Thank you"; -"donation.alerts.purchase.success.message" = "This means a lot to me and I really hope you keep using and promoting this app."; -"donation.alerts.purchase.failure.message" = "Unable to perform the donation. %@"; - -"about.title" = "About"; -"about.sections.web.header" = "Web"; -"about.sections.share.header" = "Share"; -"about.cells.credits.caption" = "Credits"; -"about.cells.website.caption" = "Home page"; -"about.cells.faq.caption" = "FAQ"; -"about.cells.disclaimer.caption" = "Disclaimer"; -"about.cells.privacy_policy.caption" = "Privacy policy"; -"about.cells.share_twitter.caption" = "Tweet about it!"; -"about.cells.share_generic.caption" = "Invite a friend"; - -"version.title" = "Version"; -"version.labels.intro" = "Passepartout and TunnelKit are written and maintained by Davide De Rosa (keeshux).\n\nSource code for Passepartout and TunnelKit is publicly available on GitHub under the GPLv3, you can find links in the home page.\n\nPassepartout is a non-official client and is in no way affiliated with OpenVPN Inc."; - -"credits.title" = "Credits"; -"credits.sections.licenses.header" = "Licenses"; -"credits.sections.notices.header" = "Notices"; -"credits.sections.translations.header" = "Translations"; - -"label.license.error" = "Unable to download full license content."; - -// iOS only - -"imported_hosts.title" = "Imported hosts"; - -// macOS only - -"menu.show.title" = "Show"; -"menu.switch_profile.title" = "Active profile"; -"menu.active_profile.title.none" = "No active profile"; -"menu.active_profile.items.customize.title" = "Customize..."; -"menu.active_profile.messages.missing_credentials" = "No account configured"; -"menu.organizer.title" = "Organizer"; -"menu.preferences.title" = "Preferences"; -"menu.support.title" = "Support"; -"menu.quit.title" = "Quit %@"; -"menu.quit.messages.confirm" = "The VPN, if enabled, will still run in the background. Do you want to quit?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/es.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/es.lproj/Core.strings deleted file mode 100644 index e4aef966..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/es.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "OK"; -"global.cancel" = "Cancelar"; -"global.next" = "Siguiente"; -"global.close" = "Salir"; -"global.host.title_input.message" = "Caracteres aceptados son los alfanuméricos más el guión alto \"-\", el guión bajo \"_\" y el punto \".\"."; -"global.host.title_input.placeholder" = "Mi perfil"; -"global.email_not_configured" = "Ningún e-mail configurado."; -"global.values.default" = "Default"; - -"global.captions.address" = "Dirección"; -"global.captions.port" = "Puerta"; -"global.captions.protocol" = "Protocolo"; - -"global.values.enabled" = "Habilitado"; -"global.values.disabled" = "Deshabilitado"; -"global.values.none" = "Ninguno"; -"global.values.automatic" = "Automático"; -"global.values.manual" = "Manual"; - -"reddit.title" = "Reddit"; -"reddit.message" = "Sabías que Passepartout tiene un subreddit? Suscríbete para actualizaciones o comentar problemas, funciones, nuevas plataformas o todo lo que se te ocurra.\n\nTambién es la manera ideal de mostrar interés en este proyecto."; -"reddit.buttons.subscribe" = "Suscribir ahora!"; -"reddit.buttons.remind" = "Recordar más tarde"; -"reddit.buttons.never" = "No preguntar más"; - -"vpn.connecting" = "Conectando"; -"vpn.active" = "Activo"; -"vpn.disconnecting" = "Desconectando"; -"vpn.inactive" = "Inactivo"; -"vpn.disabled" = "Deshabilitado"; -"vpn.unused" = "Desactivada"; - -"vpn.errors.timeout" = "Timeout"; -"vpn.errors.dns" = "DNS fallido"; -"vpn.errors.auth" = "Autentificación fallida"; -"vpn.errors.tls" = "TLS fallido"; -"vpn.errors.encryption" = "Cifrado fallido"; -"vpn.errors.compression" = "Compresión no soportada"; -"vpn.errors.network" = "Cambio de red"; -"vpn.errors.routing" = "Sin rutas"; -"vpn.errors.gateway" = "Sin puerta de enlace"; -"vpn.errors.shutdown" = "Servidor apagado"; - -"parsed_file.alerts.malformed.message" = "El fichero de configuración contiene una opción mal formada (%@)."; -"parsed_file.alerts.missing.message" = "El fichero de configuración falta de una opción necesaria (%@)."; -"parsed_file.alerts.unsupported.message" = "El fichero de configuración contiene una opción no soportada (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "El fichero de configuración es correcto pero contiene una opción potencialmente no soportada (%@).\n\nLa conectividad podría fallar según los parámetros del servidor."; -"parsed_file.alerts.encryption_passphrase.message" = "Por favor introducir la contraseña de cifrado."; -"parsed_file.alerts.decryption.message" = "La configuración contiene una clave privada cifrada que no ha podido ser descifrada. Por favor revisa la contraseña introducida."; -"parsed_file.alerts.parsing.message" = "Imposible importar el fichero de configuración proporcionado (%@)."; -"parsed_file.alerts.buttons.report" = "Reportar una incidencia"; - -"network_choice.client" = "Leer .ovpn"; -"network_choice.server" = "Obtener del servidor"; - -"issue_reporter.title" = "Reportar incidencia"; -"issue_reporter.message" = "El registro de debug de tus últimas conexiones es primordial para resolver tus problemas de conectividad y es completamente anónimo.\n\nSi hay un fichero de configuración .ovpn, se adjuntará sin ningún dato sensible.\n\nSi no estás segur@, por favor revisa los adjuntos del e-mail."; -"issue_reporter.buttons.accept" = "Entendido"; - -"translations.title" = "Traducciones"; - -"share.message" = "Passepartout es un cliente OpenVPN intuitivo, de código abierto para iOS y macOS"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Proveedor"; -"organizer.menus.provider.unavailable" = "No quedan proveedores"; -"organizer.menus.host" = "Host"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "¡Ven a ver cómo hago Passepartout en vivo en Twitch, únete al chat para interactuar y contribuir!"; -"organizer.sections.providers.header" = "Proveedores"; -"organizer.sections.providers.footer" = "Aquí encuentras algunos proveedores con ajustes preconfigurados."; -"organizer.sections.hosts.header" = "Hosts"; -"organizer.sections.hosts.footer" = "Importa un host con ficheros de configuración .ovpn."; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "Déjate ayudar por Siri para acelerar tus interacciones más frecuentes con la aplicación."; -"organizer.sections.support.header" = "Soporte"; -"organizer.sections.feedback.header" = "Feedback"; -"organizer.cells.follow_twitch.caption" = "Ve Passepartout en Twitch"; -"organizer.cells.profile.value.current" = "En uso"; -"organizer.cells.siri_shortcuts.caption" = "Gestionar atajos"; -"organizer.cells.join_community.caption" = "Apuntarse a la comunidad"; -"organizer.cells.write_review.caption" = "Escribir una reseña"; -"organizer.cells.donate.caption" = "Hacer una donación"; -"organizer.cells.github_sponsors.caption" = "Apoyar en GitHub"; -"organizer.cells.translate.caption" = "Ofrecer una traducción"; -"organizer.cells.about.caption" = "Sobre %@"; -"organizer.cells.uninstall.caption" = "Borrar configuración VPN"; -"organizer.cells.add_provider.caption" = "Añadir proveedor"; -"organizer.cells.add_host.caption" = "Añadir desde Ficheros"; -"organizer.cells.import_host.caption" = "Añadir desde importados"; -"organizer.alerts.exhausted_providers.message" = "Has creado perfiles para todos los proveedores disponibles."; -"organizer.alerts.add_host.message" = "Abre el URL de un fichero de configuración .ovpn a través de Safari, Mail u otra aplicación para configurar un host.\n\nTambién puedes importar un .ovpn con iTunes File Sharing."; -"organizer.alerts.cannot_donate.message" = "No hay métodos de pago configurados en este dispositivo."; -"organizer.alerts.delete_vpn_profile.message" = "Realmente quieres eliminar la configuración VPN de tu dispositivo? Ésto puede corregir algunos estados incorrectos del VPN y no afectará tus perfiles."; -"organizer.alerts.remove_profile.title" = "Quitar perfil"; -"organizer.alerts.remove_profile.message" = "¿Seguro que deseas eliminar el perfil %@?"; -"organizer.alerts.open_host_file.title" = "Selecciona un archivo .ovpn"; - -"wizards.provider.cells.update_list.caption" = "Actualizar lista"; -"wizards.provider.alerts.unavailable.message" = "No fue posible bajar la infraestructura del proveedor, por favor reinténtalo más tarde."; -"wizards.host.sections.existing.header" = "Perfiles existentes"; -"wizards.host.cells.title_input.caption" = "Título"; -"wizards.host.alerts.existing.message" = "Ya existe un host con el mismo título. Reemplazar?"; - -"service.welcome.message" = "Bienvenid@ a Passepartout!\n\nUsa el organizador para añadir un nuevo perfil."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "La conexión se establecerá siempre y cuando sea necesario."; -"service.sections.status.header" = "Conexión"; -"service.sections.configuration.header" = "Configuración"; -"service.sections.provider_infrastructure.footer" = "Última actualización: %@."; -"service.sections.vpn_survives_sleep.footer" = "Deshabilitar para mejorar el uso de la batería, a costa de ralentizaciones ocasionales por las reconexiones al despertar el dispositivo."; -"service.sections.vpn_resolves_hostname.footer" = "Preferido en la mayoría de las redes y necesario en algunas redes IPv6. Deshabilitar donde el DNS esté bloqueado, o para acelerar la negociación cuando el DNS sea lento en responder."; -"service.sections.trusted.header" = "Redes de confianza"; -"service.sections.trusted.footer" = "Entrando en una red de confianza, normalmente el VPN es cerrado y mantenido desconectado. Deshabilitar esta opción para no forzar este modo."; -"service.sections.diagnostics.header" = "Diagnósticos"; -"service.sections.diagnostics.footer" = "El estado de ocultación será efectivo tras reconectar. Los datos de red son hostnames, direcciones IP, routing, SSID. Las credenciales y las claves privadas no son registrados a pesar."; -"service.cells.use_profile.caption" = "Usar este perfil"; -"service.cells.vpn_service.caption" = "Habilitado"; -"service.cells.vpn.turn_on.caption" = "Habilitar VPN"; -"service.cells.vpn.turn_off.caption" = "Deshabilitar VPN"; -"service.cells.connection_status.caption" = "Estado"; -"service.cells.host.parameters.caption" = "Parámetros"; -"service.cells.provider.pool.caption" = "Ubicación"; -"service.cells.provider.preset.caption" = "Ajuste"; -"service.cells.provider.refresh.caption" = "Refrescar infraestructura"; -"service.cells.category.caption" = "Categoría"; -"service.cells.addresses.caption" = "Direcciones"; -"service.cells.only_shows_favorites.caption" = "Mostrar solo ubicaciones favoritas"; -"service.cells.vpn_survives_sleep.caption" = "Mantener en modo inactivo"; -"service.cells.vpn_resolves_hostname.caption" = "Resolver hostname del servidor"; -"service.cells.trusted_add_wifi.caption" = "Añadir Wi-Fi"; -"service.cells.trusted_mobile.caption" = "Red móvil"; -"service.cells.trusted_policy.caption" = "Red de confianza deshabilita el VPN"; -"service.cells.test_connectivity.caption" = "Testear conectividad"; -"service.cells.data_count.caption" = "Datos intercambiados"; -"service.cells.data_count.none" = "No disponible"; -"service.cells.server_configuration.caption" = "Configuración del servidor"; -"service.cells.server_network.caption" = "Red del servidor"; -"service.cells.debug_log.caption" = "Registro de debug"; -"service.cells.masks_private_data.caption" = "Ocultar datos de red"; -"service.cells.reconnect.caption" = "Reconectar"; -"service.cells.report_issue.caption" = "Reportar problema de conectividad"; - -"service.alerts.rename.title" = "Renombrar perfil"; -"service.alerts.credentials_needed.message" = "Primero debes introducir las credenciales de tu cuenta."; -"service.alerts.reconnect_vpn.message" = "Quieres reconectarte al VPN?"; -"service.alerts.trusted.no_network.message" = "No estás conectad@ a ninguna red Wi-Fi."; -"service.alerts.trusted.will_disconnect_trusted.message" = "Confiando en esta red, el VPN será desconectado. Continuar?"; -"service.alerts.trusted.will_disconnect_policy.message" = "Cambiando la política de confianza, el VPN podría ser desconectado. Continuar?"; -"service.alerts.test_connectivity.title" = "Conectividad"; -"service.alerts.test_connectivity.messages.success" = "Tu dispositivo está conectado en Internet!"; -"service.alerts.test_connectivity.messages.failure" = "Tu dispositivo no tiene conectividad Internet, por favor revisa los parámetros de tu perfil."; -"service.alerts.configuration.disconnected" = "Configuración no disponible, asegúrate de estar conectad@ a la VPN."; -"service.alerts.masks_private_data.messages.must_reconnect" = "Para resetear el registro de debug y aplicar la nueva preferencia de ocultación, debes reconectarte al VPN."; -"service.alerts.buttons.reconnect" = "Reconectar"; -"service.alerts.download.title" = "Descarga necesaria"; -"service.alerts.download.message" = "%@ requiere la descarga de fichero de configuración adicionales.\n\nConfirmar para empezar la descarga."; -"service.alerts.download.failed" = "Imposible descargar los ficheros de configuración. %@"; -"service.alerts.download.hud.extracting" = "Extrayendo ficheros, por favor ten paciencia..."; -"service.alerts.location.message.denied" = "Debes dar acceso a tu posición para añadir esta red Wi-Fi a las redes de confianza. Mira los ajustes iOS y revisa los permisos de posición para Passepartout."; -"service.alerts.location.button.settings" = "Ajustes"; - -"provider.pool.sections.empty_favorites.footer" = "Desliza a la izquierda de una ubicación para agregarla o quitarla de los Favoritos."; -"provider.pool.actions.favorite" = "Favorita"; -"provider.pool.actions.unfavorite" = "No favorita"; - -"provider.preset.cells.tech_details.caption" = "Detalles técnicos"; - -"account.title" = "Cuenta"; -"account.sections.credentials.header" = "Credenciales"; -"account.sections.guidance.footer.infrastructure.default.web" = "Usa tus credenciales de la web %@."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Usa tus credenciales de servicio %@, que pueden diferir de las credenciales de la web."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Usa tus credenciales de la web %@. Normalmente tu usuario es numérico (sin espacios)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Usa tus credenciales de la web %@. Normalmente tu usuario es tu e-mail."; -"account.sections.guidance.footer.infrastructure.pia" = "Usa tus credenciales de la web %@. Normalmente tu usuario es numérico con un prefijo \"p\"."; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Encuentra tus credenciales %@ en la sección \"Account > OpenVPN / IKEv2 Username\" de la web."; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Usa tus credenciales de la web %@. Normalmente tu usuario es tu e-mail."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Usa tus credenciales de la web %@. Normalmente tu usuario es tu e-mail."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Encuentra tus credenciales %@ en el \"OpenVPN Config Generator\" en la web."; -"account.sections.registration.footer" = "Obten una cuenta en la web de %@."; -"account.cells.username.caption" = "Usuario"; -"account.cells.username.placeholder" = "usuario"; -"account.cells.password.caption" = "Contraseña"; -"account.cells.password.placeholder" = "secreto"; -"account.cells.open_guide.caption" = "Mira tus credenciales"; -"account.cells.signup.caption" = "Registrarse con %@"; - -"endpoint.title" = "Destino"; -"endpoint.sections.location_addresses.header" = "Direcciones"; -"endpoint.sections.location_protocols.header" = "Protocolos"; -"endpoint.cells.address" = "Dirección"; -"endpoint.cells.protocol" = "Protocolo"; -"endpoint.cells.any_address.caption" = "Automática"; -"endpoint.cells.any_protocol.caption" = "Automático"; - -"network_settings.title" = "Ajustes de red"; -"network_settings.cells.add_dns_server.caption" = "Añadir dirección"; -"network_settings.cells.add_dns_domain.caption" = "Añadir dominio"; -"network_settings.cells.proxy_bypass.caption" = "Dominio ignorado"; -"network_settings.cells.add_proxy_bypass.caption" = "Añadir dominio ignorado"; - -"configuration.title" = "Configuración"; -"configuration.sections.communication.header" = "Comunicación"; -"configuration.sections.reset.footer" = "Si acabaste estropeando tu conectividad tras cambiar los parámetros de comunicación, pulsa para volver a la configuración inicial."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Compresión"; -"configuration.sections.network.header" = "Red"; -"configuration.sections.other.header" = "Otro"; -"configuration.cells.cipher.caption" = "Cifrado"; -"configuration.cells.digest.caption" = "Autentificación"; -"configuration.cells.digest.value.embedded" = "Incluida"; -"configuration.cells.compression_framing.caption" = "Marco"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "Algoritmo"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "No soportado"; -"configuration.cells.reset_original.caption" = "Resetear configuración"; -"configuration.cells.client.caption" = "Certificado cliente"; -"configuration.cells.client.value.enabled" = "Verificado"; -"configuration.cells.client.value.disabled" = "No verificado"; -"configuration.cells.tls_wrapping.caption" = "Envoltorio"; -"configuration.cells.tls_wrapping.value.auth" = "Autentificado"; -"configuration.cells.tls_wrapping.value.crypt" = "Cifrado"; -"configuration.cells.eku.caption" = "Verificación extendida"; -"configuration.cells.keep_alive.caption" = "Keep-alive"; -"configuration.cells.keep_alive.value.seconds" = "%d segundos"; -"configuration.cells.renegotiation_seconds.caption" = "Renegociación"; -"configuration.cells.renegotiation_seconds.value.after" = "después de %@"; -"configuration.cells.random_endpoint.caption" = "Aleatorizar destino"; -"configuration.alerts.commit.message" = "Los nuevos parámetros no serán efectivos hasta que te reconectes manualmente. Los cambios en las redes de confianza se aplicarán de inmediato."; -"configuration.alerts.commit.buttons.reconnect" = "Reconectar ahora"; -"configuration.alerts.commit.buttons.skip" = "Omitir"; - -"trusted.columns.trust.title" = "Confianza"; -"trusted.ethernet.title" = "Confiar en conexiones cableadas"; -"trusted.ethernet.description" = "Activa esta opción para confiar en cualquier conexión cableada."; - -"preferences.title" = "Preferencias"; -"preferences.sections.general.header" = "General"; -"preferences.cells.launches_on_login.caption" = "Iniciar al iniciar sesión"; -"preferences.cells.launches_on_login.footer" = "Activa esta opción para que la aplicación se inicie automáticamente al iniciar o al iniciar sesión."; -"preferences.cells.confirm_quit.caption" = "Confirmar salir"; -"preferences.cells.confirm_quit.footer" = "Activa esta opción para que se muestre una alerta de confirmación al salir."; - -"network_settings.gateway.title" = "Puerta de enlace"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Servidores"; -"network_settings.dns.cells.domain.caption" = "Dominio"; -"network_settings.dns.cells.domains.title" = "Dominios"; -"network_settings.proxy.title" = "Proxy"; -"network_settings.proxy.cells.bypass_domains.title" = "Dominios ignorados"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "Ruta"; - -"debug_log.buttons.previous" = "Anterior"; -"debug_log.buttons.next" = "Siguiente"; -"debug_log.buttons.copy" = "Copiar"; -"debug_log.alerts.empty_log.message" = "El registro de debug está vacío."; - -"shortcuts.add.title" = "Añadir atajo"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "Móvil"; -"shortcuts.add.cells.connect.caption" = "Conectar a"; -"shortcuts.add.cells.enable_vpn.caption" = "Habilitar VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Deshabilitar VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Añadir Wi-Fi de confianza"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Borrar Wi-Fi de confianza"; -"shortcuts.add.cells.trust_cellular.caption" = "Añadir red móvil de confianza"; -"shortcuts.add.cells.untrust_cellular.caption" = "Borrar red móvil de confianza"; -"shortcuts.add.alerts.no_profiles.message" = "No hay ningún perfil al que conectarse."; - -"shortcuts.edit.title" = "Gestionar atajos"; -"shortcuts.edit.sections.all.header" = "Atajos existentes"; -"shortcuts.edit.cells.add_shortcut.caption" = "Añadir atajo"; - -"purchase.title" = "Comprar"; -"purchase.sections.products.footer" = "Cada producto es una compra única y no recurrente. La compra de un proveedor no incluye una suscripción al servicio."; -"purchase.cells.full_version.extra_description" = "Todos los proveedores (incluye los futuros)\n%@"; -"purchase.cells.restore.title" = "Restaurar compras"; -"purchase.cells.restore.description" = "Si compraste esta aplicación o funcionalidad anteriormente, puedes restaurar tus compras y esta pantalla no volverá a aparecer."; - -"donation.title" = "Donar"; -"donation.sections.one_time.header" = "Única"; -"donation.sections.one_time.footer" = "Si te gusta mi trabajo, aquí puedes colaborar con una donación.\n\nSólo se te cobrará una vez por donación, y puedes donar las veces que quieras."; -"donation.cells.loading.caption" = "Cargando donaciones"; -"donation.cells.purchasing.caption" = "Efectuando donación"; -"donation.alerts.purchase.success.title" = "Muchas gracias"; -"donation.alerts.purchase.success.message" = "Ésto significa mucho para mí y espero sinceramente que sigas usando y promoviendo esta aplicación."; -"donation.alerts.purchase.failure.message" = "Imposible completar la donación, por favor vuelve a intentarlo. %@"; - -"about.title" = "Información"; -"about.sections.web.header" = "Web"; -"about.sections.share.header" = "Compartir"; -"about.cells.credits.caption" = "Créditos"; -"about.cells.website.caption" = "Página de inicio"; -"about.cells.faq.caption" = "Preguntas frecuentes"; -"about.cells.disclaimer.caption" = "Aviso legal"; -"about.cells.privacy_policy.caption" = "Política de privacidad"; -"about.cells.share_twitter.caption" = "Enviar un Tweet!"; -"about.cells.share_generic.caption" = "Invitar a un amig@"; - -"version.title" = "Versión"; -"version.labels.intro" = "Passepartout y TunnelKit están escritos y son mantenidos por Davide De Rosa (keeshux).\n\nEl código de Passepartout y TunnelKit es público y está disponible en GitHub bajo la GPLv3, encontrarás enlaces en la página de inicio.\n\nPassepartout es un cliente no oficial y no es afiliado de OpenVPN Inc."; - -"credits.title" = "Créditos"; -"credits.sections.licenses.header" = "Licencias"; -"credits.sections.notices.header" = "Avisos"; -"credits.sections.translations.header" = "Traducciones"; - -"label.license.error" = "Imposible descargar el contenido completo de la licencia."; - -// iOS only - -"imported_hosts.title" = "Hosts importados"; - -// macOS only - -"menu.show.title" = "Mostrar"; -"menu.switch_profile.title" = "Perfil activo"; -"menu.active_profile.title.none" = "Ningún perfil activo"; -"menu.active_profile.items.customize.title" = "Personalizar..."; -"menu.active_profile.messages.missing_credentials" = "Ninguna cuenta configurada"; -"menu.organizer.title" = "Organizador"; -"menu.preferences.title" = "Preferencias"; -"menu.support.title" = "Soporte"; -"menu.quit.title" = "Salir de %@"; -"menu.quit.messages.confirm" = "Si la VPN está habilitada, seguirá funcionando en segundo plano. ¿Deseas salir?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/fr.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/fr.lproj/Core.strings deleted file mode 100644 index ec5c2044..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/fr.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "OK"; -"global.cancel" = "Annuler"; -"global.next" = "Suivant"; -"global.close" = "Fermer"; -"global.host.title_input.message" = "Caractères acceptables sont alphanumériques et tiret \"-\", barre de soulignement \"_\" et point \".\"."; -"global.host.title_input.placeholder" = "Mon profile"; -"global.email_not_configured" = "Aucun compte courriel n'est configuré."; -"global.values.default" = "Default"; - -"global.captions.address" = "Adresse"; -"global.captions.port" = "Port"; -"global.captions.protocol" = "Protocole"; - -"global.values.enabled" = "Activer"; -"global.values.disabled" = "Désactiver"; -"global.values.none" = "Aucun"; -"global.values.automatic" = "Automatique"; -"global.values.manual" = "Manuel"; - -"reddit.title" = "Reddit"; -"reddit.message" = "Saviez-vous que Passepartout a un subreddit? Souscrivez pour les mises à jour ou discuter des problèmes, caractéristiques, nouvelles plateformes ou quoi que ce soit.\n\nC'est aussi une très bonne façon de démontrer votre enthousiasme envers le projet."; -"reddit.buttons.subscribe" = "Souscrivez maintenant!"; -"reddit.buttons.remind" = "Me rappeler plus tard"; -"reddit.buttons.never" = "Ne pas me redemander"; - -"vpn.connecting" = "Connection..."; -"vpn.active" = "Actif"; -"vpn.disconnecting" = "Déconnection..."; -"vpn.inactive" = "Inactif"; -"vpn.disabled" = "Désactivé"; -"vpn.unused" = "Désactivé"; - -"vpn.errors.timeout" = "Délais dépassé"; -"vpn.errors.dns" = "Échec DNS"; -"vpn.errors.auth" = "Échec Auth"; -"vpn.errors.tls" = "Échec TLS"; -"vpn.errors.encryption" = "Échec du cryptage"; -"vpn.errors.compression" = "Compression non supportée"; -"vpn.errors.network" = "Réseau modifié"; -"vpn.errors.routing" = "Routage manquant"; -"vpn.errors.gateway" = "Aucune passerelle"; -"vpn.errors.shutdown" = "Arrêt du serveur"; - -"parsed_file.alerts.malformed.message" = "Le fichier de configuration contient une mauvaise option.(%@)."; -"parsed_file.alerts.missing.message" = "Le fichier de configuration ne contient pas une option requise. (%@)."; -"parsed_file.alerts.unsupported.message" = "Le fichier de configuration contient une option non supportée (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "Le fichier de configuration est adéquat, mais contient une option potentiellement non supportée. (%@).\n\nLa connection peut être perdue selon les paramètres du serveur."; -"parsed_file.alerts.encryption_passphrase.message" = "Veuillez entrer le mot de passe d'encryption."; -"parsed_file.alerts.decryption.message" = "Le fichier de configuration contient une clé privée encryptée et n'a pas été décryptée. Veuillez revérifier votre mot de passe."; -"parsed_file.alerts.parsing.message" = "Incapable d'analyser le fichier de configuration fournis. (%@)."; -"parsed_file.alerts.buttons.report" = "Rapporter un problème"; - -"network_choice.client" = "Lire .ovpn"; -"network_choice.server" = "Récupérer depuis le serveur"; - -"issue_reporter.title" = "Rapporter un problème"; -"issue_reporter.message" = "Le journal débogage de votre dernière connection est crucial pour résoudre vos problèmes de connection et est entièrement anonyme.\n\nLe fichier de configuration .ovpn, si disponible, est attaché et supprimé de toute information confidentielle.\n\nVeuillez contre-vérifier les fichiers attachés au courriel si incertain."; -"issue_reporter.buttons.accept" = "Je comprends"; - -"translations.title" = "Traductions"; - -"share.message" = "Passepartout est un client OpenVPN simple d'utilisation et open source pour iOS et macOS"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Fournisseur"; -"organizer.menus.provider.unavailable" = "Aucun fournisseur restant"; -"organizer.menus.host" = "Hôte"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "Venez me regarder faire passer Passepartout en direct sur Twitch, rejoignez le chat pour interagir et contribuer!"; -"organizer.sections.providers.header" = "Fournisseurs"; -"organizer.sections.providers.footer" = "Ici vous pouvez trousers certains fournisseurs avec des profiles déjà configurés."; -"organizer.sections.hosts.header" = "Hôtes"; -"organizer.sections.hosts.footer" = "Importer des hôtes d'un fichier de configuration .ovpn."; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "Obtenez de l'aide de Siri pour accélérer vos intéractions les plus courantes avec l'app."; -"organizer.sections.support.header" = "Support"; -"organizer.sections.feedback.header" = "Commentaires"; -"organizer.cells.follow_twitch.caption" = "Regardez Passepartout sur Twitch"; -"organizer.cells.profile.value.current" = "En utilisation"; -"organizer.cells.siri_shortcuts.caption" = "Gérer les raccourcis"; -"organizer.cells.join_community.caption" = "Rejoindre la communauté"; -"organizer.cells.write_review.caption" = "Écrire un avis"; -"organizer.cells.donate.caption" = "Faire un don"; -"organizer.cells.github_sponsors.caption" = "Me parrainer chez GitHub"; -"organizer.cells.translate.caption" = "Offre de traduction"; -"organizer.cells.about.caption" = "À propos %@"; -"organizer.cells.uninstall.caption" = "Supprimer la configuration VPN"; -"organizer.cells.add_provider.caption" = "Ajouter un nouveau fournisseur"; -"organizer.cells.add_host.caption" = "Ajouter de Fichiers"; -"organizer.cells.import_host.caption" = "Ajouter depuis importé"; -"organizer.alerts.exhausted_providers.message" = "Vous avez créé un profile pour un fournisseur existant."; -"organizer.alerts.add_host.message" = "Ouvrir un URL vers un fichier de configuration .ovpn depuis Safari, Courriels ou un autre app pour installer un profile hôte.\n\nVous pouvez importer une configuration .ovpn avec le transfert de fichiers iTunes."; -"organizer.alerts.cannot_donate.message" = "Il n'y a aucune méthode de paiement configuré sur cet appareil."; -"organizer.alerts.delete_vpn_profile.message" = "Voulez-vous vraiment effacer la configuration VPN de vos paramètres? Ceci peux fixer certains VPN en arrêt et n'affectera pas vos profiles de fournisseurs et hôtes."; -"organizer.alerts.remove_profile.title" = "Supprimer le profil"; -"organizer.alerts.remove_profile.message" = "Voulez-vous vraiment supprimer le profil %@ ?"; -"organizer.alerts.open_host_file.title" = "Sélectionnez un fichier .ovpn"; - -"wizards.provider.cells.update_list.caption" = "Actualiser la liste"; -"wizards.provider.alerts.unavailable.message" = "Impossible de télécharger l'infrastructure du fournisseur, veuillez réessayer plus tard."; -"wizards.host.sections.existing.header" = "Profiles existants"; -"wizards.host.cells.title_input.caption" = "Titre"; -"wizards.host.alerts.existing.message" = "Un profile hôte avec ce même nom existe déjà. Le remplacer?"; - -"service.welcome.message" = "Bienvenue à Passepartout!\n\nUtilisez l'organiseur pour ajouter un nouveau profile."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "La connection sera établie lorsque nécessaire."; -"service.sections.status.header" = "Connection"; -"service.sections.configuration.header" = "Configuration"; -"service.sections.provider_infrastructure.footer" = "Mis à jour : %@."; -"service.sections.vpn_survives_sleep.footer" = "Désactiver pour augmenter l'autonomie de la batterie, au dépends de la rapidité au réveil pour la reconnection."; -"service.sections.vpn_resolves_hostname.footer" = "Préféré dans la plus part des réseaux et requis dans certains réseaux IPv6. Désactiver lorsque le DNS est bloqué ou pour augmenter la rapidité des négociations lorsque le DNS est lent à répondre."; -"service.sections.trusted.header" = "Réseaux de confiance"; -"service.sections.trusted.footer" = "Lors d'une connection à un réseau de confiance, le VPN est normalement fermé. Désactivez cette option pour ne pas autoriser ce comportement."; -"service.sections.diagnostics.header" = "Diagnostiques"; -"service.sections.diagnostics.footer" = "Camouflage du status sera effectif après la reconnection. Les données réseaux sont les noms d'hôtes, adresses IP, routage, SSID. Les identifiants et clés privés ne sont pas enregistrés."; -"service.cells.use_profile.caption" = "Utiliser ce profile"; -"service.cells.vpn_service.caption" = "Activer"; -"service.cells.vpn.turn_on.caption" = "Activer VPN"; -"service.cells.vpn.turn_off.caption" = "Désactiver VPN"; -"service.cells.connection_status.caption" = "Statut"; -"service.cells.host.parameters.caption" = "Paramètres"; -"service.cells.provider.pool.caption" = "Locallisation"; -"service.cells.provider.preset.caption" = "Préréglage"; -"service.cells.provider.refresh.caption" = "Rafraîchir l'infrastructure"; -"service.cells.category.caption" = "Catégorie"; -"service.cells.addresses.caption" = "Adresses"; -"service.cells.only_shows_favorites.caption" = "Afficher uniquement les emplacements favoris"; -"service.cells.vpn_survives_sleep.caption" = "Garder actif lors de la veille"; -"service.cells.vpn_resolves_hostname.caption" = "Résoudre le nom d'hôte du serveur"; -"service.cells.trusted_add_wifi.caption" = "Ajouter Wi-Fi"; -"service.cells.trusted_mobile.caption" = "Réseau cellulaire"; -"service.cells.trusted_policy.caption" = "La confiance désactive le VPN"; -"service.cells.test_connectivity.caption" = "Tester la connection"; -"service.cells.data_count.caption" = "Échanger les données"; -"service.cells.data_count.none" = "Indisponible"; -"service.cells.server_configuration.caption" = "Configuration serveur"; -"service.cells.server_network.caption" = "Serveur réseau"; -"service.cells.debug_log.caption" = "Journal de débogage"; -"service.cells.masks_private_data.caption" = "Masquer les données de réseau"; -"service.cells.reconnect.caption" = "Reconnecter"; -"service.cells.report_issue.caption" = "Rapporter un problème de connection"; - -"service.alerts.rename.title" = "Renommer le profile"; -"service.alerts.credentials_needed.message" = "Vous devez entrer les identifiants de compte premièrement."; -"service.alerts.reconnect_vpn.message" = "Voulez-vous reconnecter le VPN?"; -"service.alerts.trusted.no_network.message" = "Vous n'êtes pas connectés à aucun réseau Wi-Fi."; -"service.alerts.trusted.will_disconnect_trusted.message" = "En faisant confiance à ce réseau, le VPN pourrait être déconnecté. Continuer?"; -"service.alerts.trusted.will_disconnect_policy.message" = "En changeant la stratégie de confiance, le VPN pourrait être déconnecté. Continuer?"; -"service.alerts.test_connectivity.title" = "Connections"; -"service.alerts.test_connectivity.messages.success" = "Votre appareil est connecté à Internet!"; -"service.alerts.test_connectivity.messages.failure" = "Votre appareil n'a aucune connection Invernet, veuillez vérifier vos paramètres de profile."; -"service.alerts.configuration.disconnected" = "Configuration non disponible, vous devez être connecté au VPN."; -"service.alerts.masks_private_data.messages.must_reconnect" = "Pour bien réinitialiser le registre de diagnostique et appliquer les préférences de camouflage, vous devez vous reconnecter au VPN maintenant."; -"service.alerts.buttons.reconnect" = "Reconnecter"; -"service.alerts.download.title" = "Téléchargement requis"; -"service.alerts.download.message" = "%@ requiert le téléchargement de fichiers de configuration supplémentaires.\n\nConfirmer le début du téléchargement."; -"service.alerts.download.failed" = "Échec de téléchargement des fichiers de configuration. %@"; -"service.alerts.download.hud.extracting" = "Extraction des fichiers, veuillez patienter..."; -"service.alerts.location.message.denied" = "Vous devez autoriser la localisation pour faire confiance à ce réseau WiFi. Acceptez les permissions de localisation pour Passepartout dans les réglages."; -"service.alerts.location.button.settings" = "Réglages"; - -"provider.pool.sections.empty_favorites.footer" = "Glissez vers la gauche d'un item pour l'ajouter ou le retirer des Favoris."; -"provider.pool.actions.favorite" = "Favoris"; -"provider.pool.actions.unfavorite" = "Retirer des Favoris"; - -"provider.preset.cells.tech_details.caption" = "Détails techniques"; - -"account.title" = "Compte"; -"account.sections.credentials.header" = "Indetifiants"; -"account.sections.guidance.footer.infrastructure.default.web" = "Utilisez votre identifiants web de %@."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Utilisez vos informations d'identification de service %@, qui peuvent différer des informations d'identification du web."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Utilisez votre identifiants web de %@. Votre nom d'utilisateur est normalement numérique (sans espaces)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Utilisez votre identifiants web de %@. Votre nom d'utilisateur est normalement votre courriel."; -"account.sections.guidance.footer.infrastructure.pia" = "Utilisez votre identifiants web de %@. Votre nom d'utilisateur est normalement numérique avec le préfixe \"p\" "; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Trouvez votre identifiant web %@ dans la section du site web \"Account > OpenVPN / IKEv2 nom d'utilisateur\" "; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Utilisez votre identifiants web de %@. Votre nom d'utilisateur est normalement votre courriel."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Utilisez votre identifiants web de %@. Votre nom d'utilisateur est normalement votre courriel."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Trouver votre identifiant %@ dans la section web Générateur de configuration OpenVPN."; -"account.sections.registration.footer" = "Allez créer un compte sur le site %@."; -"account.cells.username.caption" = "Nom d'utilisateur"; -"account.cells.username.placeholder" = "nom d'utilisateur"; -"account.cells.password.caption" = "Mot de passe"; -"account.cells.password.placeholder" = "secret"; -"account.cells.open_guide.caption" = "Voir vos identifiants"; -"account.cells.signup.caption" = "S'inscrire avec %@"; - -"endpoint.title" = "Extrémité"; -"endpoint.sections.location_addresses.header" = "Adresses"; -"endpoint.sections.location_protocols.header" = "Protocols"; -"endpoint.cells.address" = "Adresse"; -"endpoint.cells.protocol" = "Protocole"; -"endpoint.cells.any_address.caption" = "Automatique"; -"endpoint.cells.any_protocol.caption" = "Automatique"; - -"network_settings.title" = "Paramètres réseaux"; -"network_settings.cells.add_dns_server.caption" = "Ajouter une adresse"; -"network_settings.cells.add_dns_domain.caption" = "Ajouter un domaine"; -"network_settings.cells.proxy_bypass.caption" = "Outrepasser le domaine"; -"network_settings.cells.add_proxy_bypass.caption" = "Ajouter outrepasser le domaine"; - -"configuration.title" = "Configuration"; -"configuration.sections.communication.header" = "Communications"; -"configuration.sections.reset.footer" = "Si vous obtenez une connection erronnée après le changement des parameters de communication, tapotez pour revenir à la configuration initiale."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Compression"; -"configuration.sections.network.header" = "Réseau"; -"configuration.sections.other.header" = "Autre"; -"configuration.cells.cipher.caption" = "Cryptogramme"; -"configuration.cells.digest.caption" = "Authentification"; -"configuration.cells.digest.value.embedded" = "Intégré"; -"configuration.cells.compression_framing.caption" = "Framing"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "Algorithme"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "Non supporté"; -"configuration.cells.reset_original.caption" = "Réinitialiser la configuration"; -"configuration.cells.client.caption" = "Certificat du client"; -"configuration.cells.client.value.enabled" = "Verifié"; -"configuration.cells.client.value.disabled" = "Non vérifié"; -"configuration.cells.tls_wrapping.caption" = "Wrapping"; -"configuration.cells.tls_wrapping.value.auth" = "Authentification"; -"configuration.cells.tls_wrapping.value.crypt" = "Cryptage"; -"configuration.cells.eku.caption" = "Vérification étendue"; -"configuration.cells.keep_alive.caption" = "Garder actif"; -"configuration.cells.keep_alive.value.seconds" = "%d secondes"; -"configuration.cells.renegotiation_seconds.caption" = "Renégociation"; -"configuration.cells.renegotiation_seconds.value.after" = "aprè %@"; -"configuration.cells.random_endpoint.caption" = "Extrémité aléatoire"; -"configuration.alerts.commit.message" = "Vous devez vous reconnecter manuellement pour confirmer les nouveaux paramètres. Les modifications apportées sur les réseaux de confiance seront immédiatement mises en place."; -"configuration.alerts.commit.buttons.reconnect" = "Se reconnecter"; -"configuration.alerts.commit.buttons.skip" = "Passer"; - -"trusted.columns.trust.title" = "Fiables"; -"trusted.ethernet.title" = "Faire confiance aux connexions filaires"; -"trusted.ethernet.description" = "Cochez pour faire confiance à toutes les connexions filaires."; - -"preferences.title" = "Préférences"; -"preferences.sections.general.header" = "Général"; -"preferences.cells.launches_on_login.caption" = "Lancer au démarrage"; -"preferences.cells.launches_on_login.footer" = "Cochez pour lancer automatiquement l'application à la connexion ou au démarrage."; -"preferences.cells.confirm_quit.caption" = "Notification de sortie"; -"preferences.cells.confirm_quit.footer" = "Cochez pour recevoir une demande de confirmation lorsque vous quittez."; - -"network_settings.gateway.title" = "Gateway"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Serveurs"; -"network_settings.dns.cells.domain.caption" = "Domaine"; -"network_settings.dns.cells.domains.title" = "Domaines"; -"network_settings.proxy.title" = "Proxy"; -"network_settings.proxy.cells.bypass_domains.title" = "Outrepasser le domaine"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "Routage"; - -"debug_log.buttons.previous" = "Précédent"; -"debug_log.buttons.next" = "Suivant"; -"debug_log.buttons.copy" = "Copier"; -"debug_log.alerts.empty_log.message" = "Le journal de débogage est vide. "; - -"shortcuts.add.title" = "Ajouter un raccourcis"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "Cellulaire"; -"shortcuts.add.cells.connect.caption" = "Connecter à"; -"shortcuts.add.cells.enable_vpn.caption" = "Activer VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Désactiver VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Faire confiance au présent réseau Wi-Fi"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Retirer le présent réseau Wi-Fi des réseaux de confiance."; -"shortcuts.add.cells.trust_cellular.caption" = "Faire confiance au présent réseau cellulaire"; -"shortcuts.add.cells.untrust_cellular.caption" = "Retirer le présent réseau cellulaire des réseaux de confiance."; -"shortcuts.add.alerts.no_profiles.message" = "Il n'y a aucun profile pour se connecter."; - -"shortcuts.edit.title" = "Gérer les raccourcis"; -"shortcuts.edit.sections.all.header" = "Raccourcis existants"; -"shortcuts.edit.cells.add_shortcut.caption" = "Ajouter un raccourcis"; - -"purchase.title" = "Acheter"; -"purchase.sections.products.footer" = "Chaque produit est un achat unique. Les achats n'incluent pas une souscription à un service de VPN."; -"purchase.cells.full_version.extra_description" = "Tous les fournisseurs (incluant les prochains)\n%@"; -"purchase.cells.restore.title" = "Restaurer les achats"; -"purchase.cells.restore.description" = "Si vous avez acheté l'application ou une fonctionnalité dans le passé, vous pouvez restaurer les achats et ce message ne s'affichera plus."; - -"donation.title" = "Faire un don"; -"donation.sections.one_time.header" = "Une seule fois"; -"donation.sections.one_time.footer" = "Si vous voulez manifester votre gratitude envers mon travail bénévole, voici certains montants pour faire un don instantanément.\n\n Vous n'allez être chargé qu'une seule fois par don et vous pouvez faire un don plus d'une fois."; -"donation.cells.loading.caption" = "Chargement des dons"; -"donation.cells.purchasing.caption" = "Don en cours"; -"donation.alerts.purchase.success.title" = "Merci"; -"donation.alerts.purchase.success.message" = "Ceci signifie beaucoup pour moi et j'espère sincèrement que vous continuerez d'utiliser et de promouvoir cette app."; -"donation.alerts.purchase.failure.message" = "Impossible de faire le don. %@"; - -"about.title" = "À propos"; -"about.sections.web.header" = "Web"; -"about.sections.share.header" = "Partager"; -"about.cells.credits.caption" = "Crédits"; -"about.cells.website.caption" = "Page d'accueil"; -"about.cells.faq.caption" = "FAQ"; -"about.cells.disclaimer.caption" = "Avis de non-responsabilité"; -"about.cells.privacy_policy.caption" = "Politique de la vie privée"; -"about.cells.share_twitter.caption" = "Tweetez!"; -"about.cells.share_generic.caption" = "Inviter un amis"; - -"version.title" = "Version"; -"version.labels.intro" = "Passepartout et TunnelKit sont codés et maintenu par Davide De Rosa (keeshux).\n\nLe code source de Passepartout et TunnelKit est publiquement disponible sur GitHub sous license GPLv3, vous pouvez trouver les liens sur la page d'accueil.\n\nPassepartout est un client non-officiel et n'est aucunement affilié avec OpenVPN Inc."; - -"credits.title" = "Crédits"; -"credits.sections.licenses.header" = "Licenses"; -"credits.sections.notices.header" = "Préavis"; -"credits.sections.translations.header" = "Traductions"; - -"label.license.error" = "Impossible de télécharger le contenu complet de la license."; - -// iOS only - -"imported_hosts.title" = "Hôtes importés"; - -// macOS only - -"menu.show.title" = "Afficher"; -"menu.switch_profile.title" = "Profil actif"; -"menu.active_profile.title.none" = "Pas de profil actif"; -"menu.active_profile.items.customize.title" = "Personnaliser..."; -"menu.active_profile.messages.missing_credentials" = "Pas de compte configuré"; -"menu.organizer.title" = "Organisateur"; -"menu.preferences.title" = "Préférences"; -"menu.support.title" = "Assistance"; -"menu.quit.title" = "Quitter %@"; -"menu.quit.messages.confirm" = "S'il est activé, le VPN fonctionnera en tâche de fond. Voulez-vous quitter ?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/it.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/it.lproj/Core.strings deleted file mode 100644 index 80bfab58..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/it.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "OK"; -"global.cancel" = "Annulla"; -"global.next" = "Avanti"; -"global.close" = "Chiudi"; -"global.host.title_input.message" = "I caratteri ammessi sono gli alfanumerici e il trattino breve \"-\", il trattino basso \"_\" ed il punto \".\"."; -"global.host.title_input.placeholder" = "Il mio profilo"; -"global.email_not_configured" = "Nessun account e-mail configurato."; -"global.values.default" = "Default"; - -"global.captions.address" = "Indirizzo"; -"global.captions.port" = "Porta"; -"global.captions.protocol" = "Protocollo"; - -"global.values.enabled" = "Abilitato"; -"global.values.disabled" = "Disabilitato"; -"global.values.none" = "Nessuno"; -"global.values.automatic" = "Automatico"; -"global.values.manual" = "Manuale"; - -"reddit.title" = "Reddit"; -"reddit.message" = "Sapevi che Passepartout ha un subreddit? Iscriviti per aggiornamenti o per discutere problemi, aggiunte, nuove piattaforme o qualunque cosa tu voglia.\n\nÈ anche un ottimo modo per dimostrare che hai a cuore questo progetto."; -"reddit.buttons.subscribe" = "Iscriviti ora!"; -"reddit.buttons.remind" = "Ricordami più tardi"; -"reddit.buttons.never" = "Non chiedere più"; - -"vpn.connecting" = "Connettendo"; -"vpn.active" = "Attiva"; -"vpn.disconnecting" = "Disconnettendo"; -"vpn.inactive" = "Inattiva"; -"vpn.disabled" = "Disabilitata"; -"vpn.unused" = "Spento"; - -"vpn.errors.timeout" = "Timeout"; -"vpn.errors.dns" = "DNS fallito"; -"vpn.errors.auth" = "Autenticazione fallita"; -"vpn.errors.tls" = "TLS fallito"; -"vpn.errors.encryption" = "Crittografia fallita"; -"vpn.errors.compression" = "Compressione non supportata"; -"vpn.errors.network" = "Rete cambiata"; -"vpn.errors.routing" = "Routing mancante"; -"vpn.errors.gateway" = "Nessun gateway"; -"vpn.errors.shutdown" = "Server arrestato"; - -"parsed_file.alerts.malformed.message" = "La configurazione contiene un'opzione malformata (%@)."; -"parsed_file.alerts.missing.message" = "La configurazione non contiene un'opzione obbligatoria (%@)."; -"parsed_file.alerts.unsupported.message" = "La configurazione contiene un'opzione non supportata (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "La configurazione è corretto ma contiene un'opzione potenzialmente non supportata (%@).\n\nLa connettività potrebbe fallire a seconda delle impostazioni del server."; -"parsed_file.alerts.encryption_passphrase.message" = "Per favore inserisci la passphrase di criptazione."; -"parsed_file.alerts.decryption.message" = "La configurazione contiene una chiave privata criptata e non è stato possibile decriptarla. Controlla la tua passphrase."; -"parsed_file.alerts.parsing.message" = "Impossibile processare il file di configurazione specificato (%@)."; -"parsed_file.alerts.buttons.report" = "Segnala un problema"; - -"network_choice.client" = "Leggi .ovpn"; -"network_choice.server" = "Ottieni dal server"; - -"issue_reporter.title" = "Segnala problema"; -"issue_reporter.message" = "Il debug log delle tue ultime connessioni è cruciale per risolvere i tuoi problemi di connettività ed è completamente anonimo.\n\nIl file di configurazione .ovpn, se presente, è allegato privato di ogni dato sensibile.\n\nPer favore controlla gli allegati dell'e-mail se non sei sicuro/a."; -"issue_reporter.buttons.accept" = "Ho capito"; - -"translations.title" = "Traduzioni"; - -"share.message" = "Passepartout è un client OpenVPN user-friendly ed open source per iOS e macOS"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Provider"; -"organizer.menus.provider.unavailable" = "Nessun altro provider disponibile"; -"organizer.menus.host" = "Host"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "Vieni a vedermi creare Passepartout in diretta su Twitch, unisciti alla chat per interagire e contribuire!"; -"organizer.sections.providers.header" = "Provider"; -"organizer.sections.providers.footer" = "Qui trovi alcuni provider con configurazioni precompilate."; -"organizer.sections.hosts.header" = "Host"; -"organizer.sections.hosts.footer" = "Importa un host da un file di configurazione .ovpn."; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "Chiedi aiuto a Siri per velocizzare le tue interazioni più frequenti con l'app."; -"organizer.sections.support.header" = "Supporto"; -"organizer.sections.feedback.header" = "Feedback"; -"organizer.cells.follow_twitch.caption" = "Guarda Passepartout su Twitch"; -"organizer.cells.profile.value.current" = "In uso"; -"organizer.cells.siri_shortcuts.caption" = "Gestisci comandi rapidi"; -"organizer.cells.join_community.caption" = "Entra nella community"; -"organizer.cells.write_review.caption" = "Scrivi una recensione"; -"organizer.cells.donate.caption" = "Fai una donazione"; -"organizer.cells.github_sponsors.caption" = "Supportami su GitHub"; -"organizer.cells.translate.caption" = "Offri una traduzione"; -"organizer.cells.about.caption" = "Informazioni su %@"; -"organizer.cells.uninstall.caption" = "Rimuovi configurazione VPN"; -"organizer.cells.add_provider.caption" = "Aggiungi provider"; -"organizer.cells.add_host.caption" = "Aggiungi da Files"; -"organizer.cells.import_host.caption" = "Aggiungi da importati"; -"organizer.alerts.exhausted_providers.message" = "Hai creato profili per tutti i provider disponibili."; -"organizer.alerts.add_host.message" = "Apri l'URL di un file di configurazione .ovpn da Safari, Mail o da un'altra app per configurare un host.\n\nPuoi anche importare un file .ovpn con iTunes File Sharing."; -"organizer.alerts.cannot_donate.message" = "Nessun metodo di pagamento configurato su questo dispositivo."; -"organizer.alerts.delete_vpn_profile.message" = "Vuoi veramente cancellare la configurazione VPN dalle impostazioni del tuo dispositivo? Quest'azione potrebbe risolvere alcuni stati erronei della VPN e non altererà i tuoi provider e i tuoi host."; -"organizer.alerts.remove_profile.title" = "Cancella profilo"; -"organizer.alerts.remove_profile.message" = "Sei sicuro di voler cancellare il profilo %@?"; -"organizer.alerts.open_host_file.title" = "Seleziona un file .ovpn"; - -"wizards.provider.cells.update_list.caption" = "Aggiorna lista"; -"wizards.provider.alerts.unavailable.message" = "Non è stato possibile scaricare l'infrastruttura del provider, per favore riprova più tardi."; -"wizards.host.sections.existing.header" = "Profili esistenti"; -"wizards.host.cells.title_input.caption" = "Titolo"; -"wizards.host.alerts.existing.message" = "Esiste già un host con lo stesso titolo. Sostituire?"; - -"service.welcome.message" = "Benvenuto in Passepartout!\n\nUsa il menu per aggiungere un nuovo profilo."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "La connessione sarà stabilita ogni volta che è necessario."; -"service.sections.status.header" = "Connessione"; -"service.sections.configuration.header" = "Configurazione"; -"service.sections.provider_infrastructure.footer" = "Ultimo aggiornamento: %@."; -"service.sections.vpn_survives_sleep.footer" = "Disabilita per migliorare il consumo della batteria, a discapito di rallentamenti occasionali causati dalle riconnessioni."; -"service.sections.vpn_resolves_hostname.footer" = "Preferibile nella maggior parte delle reti e necessario in alcune reti IPv6. Disabilita dove il DNS è bloccato, o per velocizzare la negoziazione quando il DNS tarda a rispondere."; -"service.sections.trusted.header" = "Reti sicure"; -"service.sections.trusted.footer" = "Entrando in una rete sicura, normalmente la VPN viene spenta e mantenuta disconnessa. Disabilita quest'opzione per non imporre questo comportamento."; -"service.sections.diagnostics.header" = "Diagnostica"; -"service.sections.diagnostics.footer" = "Il mascheramento sarà effettivo dopo una riconnessione. I dati di rete sono hostname, indirizzi IP, routing, SSID. Credenziali e chiavi private non sono registrati in ogni caso."; -"service.cells.use_profile.caption" = "Usa questo profilo"; -"service.cells.vpn_service.caption" = "Abilitato"; -"service.cells.vpn.turn_on.caption" = "Abilita VPN"; -"service.cells.vpn.turn_off.caption" = "Disabilita VPN"; -"service.cells.connection_status.caption" = "Stato"; -"service.cells.host.parameters.caption" = "Parametri"; -"service.cells.provider.pool.caption" = "Regione"; -"service.cells.provider.preset.caption" = "Preset"; -"service.cells.provider.refresh.caption" = "Aggiorna infrastruttura"; -"service.cells.category.caption" = "Categoria"; -"service.cells.addresses.caption" = "Indirizzi"; -"service.cells.only_shows_favorites.caption" = "Mostra solo le posizioni preferite"; -"service.cells.vpn_survives_sleep.caption" = "Mantieni attivo in sleep"; -"service.cells.vpn_resolves_hostname.caption" = "Risolvi hostname del server"; -"service.cells.trusted_add_wifi.caption" = "Aggiungi Wi-Fi"; -"service.cells.trusted_mobile.caption" = "Rete cellulare"; -"service.cells.trusted_policy.caption" = "Spegni VPN in rete sicura"; -"service.cells.test_connectivity.caption" = "Verifica connettività"; -"service.cells.data_count.caption" = "Dati scambiati"; -"service.cells.data_count.none" = "Non disponibile"; -"service.cells.server_configuration.caption" = "Configurazione del server"; -"service.cells.server_network.caption" = "Rete del server"; -"service.cells.debug_log.caption" = "Debug log"; -"service.cells.masks_private_data.caption" = "Maschera dati rete"; -"service.cells.reconnect.caption" = "Riconnetti"; -"service.cells.report_issue.caption" = "Segnala problema connettività"; - -"service.alerts.rename.title" = "Rinomina profilo"; -"service.alerts.credentials_needed.message" = "Devi prima inserire le tue credenziali."; -"service.alerts.reconnect_vpn.message" = "Vuoi riconnetterti alla VPN?"; -"service.alerts.trusted.no_network.message" = "Non sei connesso/a a nessuna rete Wi-Fi."; -"service.alerts.trusted.will_disconnect_trusted.message" = "Rendendo questa rete sicura, la VPN potrebbe essere disconnessa. Continuare?"; -"service.alerts.trusted.will_disconnect_policy.message" = "Cambiando la politica delle reti sicure, la VPN potrebbe essere disconnessa. Continuare?"; -"service.alerts.test_connectivity.title" = "Connettività"; -"service.alerts.test_connectivity.messages.success" = "Il tuo dispositivo è connesso a Internet!"; -"service.alerts.test_connectivity.messages.failure" = "Il tuo dispositivo non è connesso a Internet, per favore controlla i parametri del tuo profilo."; -"service.alerts.configuration.disconnected" = "Configurazione non disponibile, assicurati di essere connesso/a alla VPN."; -"service.alerts.masks_private_data.messages.must_reconnect" = "Per azzerare il debug log ed applicare la nuova preferenza di mascheramento, devi riconnetterti alla VPN."; -"service.alerts.buttons.reconnect" = "Riconnetti"; -"service.alerts.download.title" = "Download necessario"; -"service.alerts.download.message" = "%@ richiede lo scaricamento di file di configurazione aggiuntivi.\n\nConferma per avviare lo scaricamento."; -"service.alerts.download.failed" = "Impossibile scaricare i file di configurazione. %@"; -"service.alerts.download.hud.extracting" = "Estraendo i file, un attimo di pazienza..."; -"service.alerts.location.message.denied" = "Devi dare accesso alla tua posizione per aggiungere questa rete Wi-Fi alle reti sicure. Vai alle impostazioni iOS e verifica i permessi sulla posizione per Passepartout."; -"service.alerts.location.button.settings" = "Impostazioni"; - -"provider.pool.sections.empty_favorites.footer" = "Scorri a sinistra su una regione per aggiungerla o rimuoverla dai Preferiti."; -"provider.pool.actions.favorite" = "Preferita"; -"provider.pool.actions.unfavorite" = "Non preferita"; - -"provider.preset.cells.tech_details.caption" = "Dettagli tecnici"; - -"account.title" = "Account"; -"account.sections.credentials.header" = "Credenziali"; -"account.sections.guidance.footer.infrastructure.default.web" = "Usa le credenziali del sito di %@."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Usa le tue credenziali del servizio %@, che potrebbero differire dalle credenziali del sito web."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Usa le credenziali del sito di %@. Il tuo username è generalmente numerico (senza spazi)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Usa le credenziali del sito di %@. Il tuo username è generalmente la tua e-mail."; -"account.sections.guidance.footer.infrastructure.pia" = "Usa le credenziali del sito di %@. Il tuo username è generalmente numerico con un prefisso \"p\"."; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Trova le tue credenziali nella sezione \"Account > OpenVPN / IKEv2 Username\" del sito di %@."; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Usa le credenziali del sito di %@. Il tuo username è generalmente la tua e-mail."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Usa le credenziali del sito di %@. Il tuo username è generalmente la tua e-mail."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Trova le tue credenziali nell'OpenVPN Config Generator sul sito di %@."; -"account.sections.registration.footer" = "Registra un account sul sito di %@."; -"account.cells.username.caption" = "Username"; -"account.cells.username.placeholder" = "username"; -"account.cells.password.caption" = "Password"; -"account.cells.password.placeholder" = "segreto"; -"account.cells.open_guide.caption" = "Vedi le tue credenziali"; -"account.cells.signup.caption" = "Registrati con %@"; - -"endpoint.title" = "Endpoint"; -"endpoint.sections.location_addresses.header" = "Indirizzi"; -"endpoint.sections.location_protocols.header" = "Protocolli"; -"endpoint.cells.address" = "Indirizzo"; -"endpoint.cells.protocol" = "Protocollo"; -"endpoint.cells.any_address.caption" = "Automatico"; -"endpoint.cells.any_protocol.caption" = "Automatico"; - -"network_settings.title" = "Impostazioni di rete"; -"network_settings.cells.add_dns_server.caption" = "Aggiungi indirizzo"; -"network_settings.cells.add_dns_domain.caption" = "Aggiungi dominio"; -"network_settings.cells.proxy_bypass.caption" = "Dominio ignorato"; -"network_settings.cells.add_proxy_bypass.caption" = "Aggiungi dominio ignorato"; - -"configuration.title" = "Configurazione"; -"configuration.sections.communication.header" = "Comunicazione"; -"configuration.sections.reset.footer" = "Se ti trovi con una connettività compromessa dopo aver cambiato i parametri di comunicazione, tocca per tornare alla configurazione originale."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Compressione"; -"configuration.sections.network.header" = "Rete"; -"configuration.sections.other.header" = "Altro"; -"configuration.cells.cipher.caption" = "Cifratura"; -"configuration.cells.digest.caption" = "Autenticazione"; -"configuration.cells.digest.value.embedded" = "Incorporata"; -"configuration.cells.compression_framing.caption" = "Framing"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "Algoritmo"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "Non supportato"; -"configuration.cells.reset_original.caption" = "Ripristina configurazione"; -"configuration.cells.client.caption" = "Certificato client"; -"configuration.cells.client.value.enabled" = "Verificato"; -"configuration.cells.client.value.disabled" = "Non verificato"; -"configuration.cells.tls_wrapping.caption" = "Wrapping"; -"configuration.cells.tls_wrapping.value.auth" = "Autenticazione"; -"configuration.cells.tls_wrapping.value.crypt" = "Criptazione"; -"configuration.cells.eku.caption" = "Verifica estesa"; -"configuration.cells.keep_alive.caption" = "Keep-alive"; -"configuration.cells.keep_alive.value.seconds" = "%d secondi"; -"configuration.cells.renegotiation_seconds.caption" = "Rinegoziazione"; -"configuration.cells.renegotiation_seconds.value.after" = "dopo %@"; -"configuration.cells.random_endpoint.caption" = "Endpoint casuale"; -"configuration.alerts.commit.message" = "I nuovi parametri non saranno effettivi finché non ti riconnetti manualmente. I cambi nelle reti sicure saranno applicati immediatamente."; -"configuration.alerts.commit.buttons.reconnect" = "Riconnetti adesso"; -"configuration.alerts.commit.buttons.skip" = "Ignora"; - -"trusted.columns.trust.title" = "Sicura"; -"trusted.ethernet.title" = "Connessioni cablate sicure"; -"trusted.ethernet.description" = "Seleziona per considerare sicura qualsiasi rete cablata."; - -"preferences.title" = "Preferenze"; -"preferences.sections.general.header" = "Generale"; -"preferences.cells.launches_on_login.caption" = "Apri al login"; -"preferences.cells.launches_on_login.footer" = "Seleziona per aprire automaticamente l'app all'avvio o al login."; -"preferences.cells.confirm_quit.caption" = "Conferma uscita"; -"preferences.cells.confirm_quit.footer" = "Seleziona per confermare l'uscita dall'applicazione."; - -"network_settings.gateway.title" = "Gateway predefinito"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Server"; -"network_settings.dns.cells.domain.caption" = "Dominio"; -"network_settings.dns.cells.domains.title" = "Dominii"; -"network_settings.proxy.title" = "Proxy"; -"network_settings.proxy.cells.bypass_domains.title" = "Dominii ignorati"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "Rotta"; - -"debug_log.buttons.previous" = "Precedente"; -"debug_log.buttons.next" = "Successivo"; -"debug_log.buttons.copy" = "Copia"; -"debug_log.alerts.empty_log.message" = "Il debug log è vuoto."; - -"shortcuts.add.title" = "Aggiungi comando rapido"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "Cellulare"; -"shortcuts.add.cells.connect.caption" = "Connetti a"; -"shortcuts.add.cells.enable_vpn.caption" = "Abilita VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Disabilita VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Aggiungi Wi-Fi sicura"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Rimuovi Wi-Fi sicura"; -"shortcuts.add.cells.trust_cellular.caption" = "Aggiungi rete mobile sicura"; -"shortcuts.add.cells.untrust_cellular.caption" = "Rimuovi rete mobile sicura"; -"shortcuts.add.alerts.no_profiles.message" = "Non c'è nessun profilo a cui connettersi."; - -"shortcuts.edit.title" = "Gestisci comandi rapidi"; -"shortcuts.edit.sections.all.header" = "Comandi esistenti"; -"shortcuts.edit.cells.add_shortcut.caption" = "Aggiungi comando rapido"; - -"purchase.title" = "Acquista"; -"purchase.sections.products.footer" = "Ogni prodotto è un acquisto unico e non ricorrente. L'acquisto di un provider non include una sottoscrizione."; -"purchase.cells.full_version.extra_description" = "Tutti i provider (inclusi quelli futuri)\n%@"; -"purchase.cells.restore.title" = "Ripristina acquisti"; -"purchase.cells.restore.description" = "Se hai comprato quest'applicazione o funzionalità in precedenza, puoi ripristinare i tuoi acquisti in modo che questa schermata non compaia più."; - -"donation.title" = "Donazione"; -"donation.sections.one_time.header" = "Unica"; -"donation.sections.one_time.footer" = "Se vuoi mostrare gratitudine per il mio lavoro a titolo gratuito, qui trovi varie somme da donare all'istante.\n\nLa donazione ti sarà addebitata solo una volta, e puoi effettuare più donazioni."; -"donation.cells.loading.caption" = "Caricando donazioni"; -"donation.cells.purchasing.caption" = "Effettuando donazione"; -"donation.alerts.purchase.success.title" = "Grazie"; -"donation.alerts.purchase.success.message" = "Questo significa molto per me e spero vivamente che tu continui ad usare e promuovere quest'applicazione."; -"donation.alerts.purchase.failure.message" = "Impossibile effettuare la donazione. %@"; - -"about.title" = "Informazioni su"; -"about.sections.web.header" = "Web"; -"about.sections.share.header" = "Condividi"; -"about.cells.credits.caption" = "Credits"; -"about.cells.website.caption" = "Home page"; -"about.cells.faq.caption" = "FAQ"; -"about.cells.disclaimer.caption" = "Disclaimer"; -"about.cells.privacy_policy.caption" = "Privacy policy"; -"about.cells.share_twitter.caption" = "Manda un Tweet!"; -"about.cells.share_generic.caption" = "Invita un amico"; - -"version.title" = "Versione"; -"version.labels.intro" = "Passepartout e TunnelKit sono scritti e mantenuti da Davide De Rosa (keeshux).\n\nIl codice sorgente di Passepartout e TunnelKit è pubblicamente disponibile su GitHub in accordo con la GPLv3, puoi trovare i link nella home page.\n\nPassepartout è un client non ufficiale e non è affiliato ad OpenVPN Inc. in alcuna maniera."; - -"credits.title" = "Credits"; -"credits.sections.licenses.header" = "Licenze"; -"credits.sections.notices.header" = "Notice"; -"credits.sections.translations.header" = "Traduzioni"; - -"label.license.error" = "Impossibile scaricare il contenuto completo della licenza."; - -// iOS only - -"imported_hosts.title" = "Host importati"; - -// macOS only - -"menu.show.title" = "Mostra"; -"menu.switch_profile.title" = "Profilo attivo"; -"menu.active_profile.title.none" = "Nessun profilo attivo"; -"menu.active_profile.items.customize.title" = "Personalizza..."; -"menu.active_profile.messages.missing_credentials" = "Nessun account configurato"; -"menu.organizer.title" = "Organizer"; -"menu.preferences.title" = "Preferenze"; -"menu.support.title" = "Supporto"; -"menu.quit.title" = "Esci da %@"; -"menu.quit.messages.confirm" = "La VPN, se abilitata, continuerà ad essere attiva in background. Vuoi comunque uscire?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/nl.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/nl.lproj/Core.strings deleted file mode 100644 index b3ab7ce8..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/nl.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "OK"; -"global.cancel" = "Afbreken"; -"global.next" = "Volgende"; -"global.close" = "Afsluiten"; -"global.host.title_input.message" = "Toegestane karakters zijn: alfanumerieke en streepjes \"-\", onderliggend streepje \"_\" en punten \".\"."; -"global.host.title_input.placeholder" = "Mijn Profiel"; -"global.email_not_configured" = "Er is geen email adres geconfigureerd."; -"global.values.default" = "Default"; - -"global.captions.address" = "Adress"; -"global.captions.port" = "Port"; -"global.captions.protocol" = "Protocol"; - -"global.values.enabled" = "Ingeschakeld"; -"global.values.disabled" = "Uitgeschakeld"; -"global.values.none" = "Geen"; -"global.values.automatic" = "Automatisch"; -"global.values.manual" = "Handmatig"; - -"reddit.title" = "Reddit"; -"reddit.message" = "Wist je dat Passepartout een eigen subreddit heeft? Schrijf je in voor updates, of discussiëren over problemen, (nieuwe) mogelijkheden, nieuwe platformen of wat je maar wil.\n\nHet is ook een goede manier om te laten zien dat je om dit project geeft."; -"reddit.buttons.subscribe" = "Schfijf je nu in!"; -"reddit.buttons.remind" = "Herinner me later"; -"reddit.buttons.never" = "Vraag dit niet meer"; - -"vpn.connecting" = "Verbinden"; -"vpn.active" = "Actief"; -"vpn.disconnecting" = "Verbinding verbreken"; -"vpn.inactive" = "Inactief"; -"vpn.disabled" = "Uitgeschakeld"; -"vpn.unused" = "Uit"; - -"vpn.errors.timeout" = "Time-out"; -"vpn.errors.dns" = "DNS niet gelukt"; -"vpn.errors.auth" = "Auth niet gelukt"; -"vpn.errors.tls" = "TLS niet gelukt"; -"vpn.errors.encryption" = "Versleuteling mislukt"; -"vpn.errors.compression" = "Compressie wordt niet ondersteund"; -"vpn.errors.network" = "Netwerk veranderd"; -"vpn.errors.routing" = "Ontbrekende routering"; -"vpn.errors.gateway" = "Geen gateway"; -"vpn.errors.shutdown" = "Server is afgesloten"; - -"parsed_file.alerts.malformed.message" = "Het configuratie betand bevat ongeldige optie(s) (%@)."; -"parsed_file.alerts.missing.message" = "Het configuratiebestand mist een vereiste optie (%@)."; -"parsed_file.alerts.unsupported.message" = "Het configuratiebestand bevat een niet-ondersteunde optie (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "Het configuratiebestand is correct maar bevat mogelijk een niet-ondersteunde optie (%@).\n\nConnectiviteit kan hier door niet werken, afhankelijk van de serverinstellingen."; -"parsed_file.alerts.encryption_passphrase.message" = "Voer een coderingswachtwoord in"; -"parsed_file.alerts.decryption.message" = "De configuratie bevat een gecodeerde privésleutel en deze kan niet worden gedecodeerd. Controleer de ingevoerde wachtwoordzin nogmaals."; -"parsed_file.alerts.parsing.message" = "Kan het opgegeven configuratiebestand niet verwerken (%@)."; -"parsed_file.alerts.buttons.report" = "Een probleem melden"; - -"network_choice.client" = "Gebruik .ovpn"; -"network_choice.server" = "Haal op van server"; - -"issue_reporter.title" = "Meld een probleem"; -"issue_reporter.message" = "Het foutopsporingslogboek van uw laatste verbindingen is cruciaal om uw verbindingsproblemen op te lossen en is volledig geanonimiseerd.\n\nHet .ovpn configuratiebestand, indien aanwezig, is ontdaan van alle gevoelige gegevens.\n\nControleer de e-mailbijlagen als je niet zeker bent dat alles verwijderd is."; -"issue_reporter.buttons.accept" = "Ik ga akkoord"; - -"translations.title" = "Vertalingen"; - -"share.message" = "Passepartout is een gebruiksvriendelijke open source OpenVPN-client voor iOS en macOS"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Aanbieder"; -"organizer.menus.provider.unavailable" = "Geen aanbieders meer"; -"organizer.menus.host" = "Host"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "Kom kijken hoe ik Passepartout live maak op Twitch, doe mee aan de chat om te communiceren en bij te dragen!"; -"organizer.sections.providers.header" = "Aanbieders"; -"organizer.sections.providers.footer" = "Hier vind je aan aantal aanbieders met configuratie profielen."; -"organizer.sections.hosts.header" = "Hosts"; -"organizer.sections.hosts.footer" = "Importeer hosts met raw .ovpn configuratie bestanden."; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "Krijg hulp van Siri en versnel de meest gebruikte interacties binnen de app."; -"organizer.sections.support.header" = "Ondersteuning"; -"organizer.sections.feedback.header" = "Terugkoppeling"; -"organizer.cells.follow_twitch.caption" = "Bekijk Passepartout op Twitch"; -"organizer.cells.profile.value.current" = "In gebruik"; -"organizer.cells.siri_shortcuts.caption" = "Beheer snelkoppelingen"; -"organizer.cells.join_community.caption" = "Word lid van de gemeenschap"; -"organizer.cells.write_review.caption" = "Schrijf een beoordeling"; -"organizer.cells.donate.caption" = "Doneer een gift"; -"organizer.cells.github_sponsors.caption" = "Steun me op GitHub"; -"organizer.cells.translate.caption" = "Help met vertalen"; -"organizer.cells.about.caption" = "Over %@"; -"organizer.cells.uninstall.caption" = "Verwijder VPN configuratie"; -"organizer.cells.add_provider.caption" = "Voeg nieuwe aanbieder toe"; -"organizer.cells.add_host.caption" = "Toevoegen vanuit Bestanden"; -"organizer.cells.import_host.caption" = "Toevoegen vanuit geïmporteerd"; -"organizer.alerts.exhausted_providers.message" = "Er zijn profielen gemaakt voor elke beschikbare aanbieder."; -"organizer.alerts.add_host.message" = "Open een URL naar een .ovpn configuratie bestand met Safari, Mail of een andere app om een host profile te configureren.\n\nJe kan ook een .ovpn importeren met behulp van iTunes bestandsdeling."; -"organizer.alerts.cannot_donate.message" = "Er is geen betaalmethode geconfigureerd op dit apparaat."; -"organizer.alerts.delete_vpn_profile.message" = "Wilt u de VPN-configuratie van uw apparaatinstellingen verwijderen? Dit kan enkele problemen met VPN oplossen en heeft geen invloed op uw provider- en hostprofielen."; -"organizer.alerts.remove_profile.title" = "Profiel verwijderen"; -"organizer.alerts.remove_profile.message" = "Weet u zeker dat u profiel %@ wilt verwijderen?"; -"organizer.alerts.open_host_file.title" = "Selecteer een .ovpn-bestand"; - -"wizards.provider.cells.update_list.caption" = "Lijst bijwerken"; -"wizards.provider.alerts.unavailable.message" = "Kon de provider-infrastructuur niet downloaden, probeer het later opnieuw."; -"wizards.host.sections.existing.header" = "Bestaande profielen"; -"wizards.host.cells.title_input.caption" = "Titel"; -"wizards.host.alerts.existing.message" = "Er bestaat al een host profiel met deze titel, wil je hem vervangen?"; - -"service.welcome.message" = "Welkom bij Passepartout!\n\nGebruik de organizer om een nieuw profiel toe te voegen."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "De verbinding zal worden gestart wanneer nodig."; -"service.sections.status.header" = "Verbinding"; -"service.sections.configuration.header" = "Configuratie"; -"service.sections.provider_infrastructure.footer" = "Laatste update was op %@."; -"service.sections.vpn_survives_sleep.footer" = "Uitschakelen om het batterijverbruik te verbeteren, ten koste van incidentele vertragingen als gevolg van het opnieuw opstarten na wake-up."; -"service.sections.vpn_resolves_hostname.footer" = "Voorkeur om dit aan te zetten voor de meeste netwerken en vereist in sommige IPv6-netwerken. Uitschakelen waar DNS wordt geblokkeerd, of om de onderhandelingen te versnellen wanneer DNS traag reageert."; -"service.sections.trusted.header" = "Vertrouwde netwerken"; -"service.sections.trusted.footer" = "Bij het invoeren van een vertrouwd netwerk wordt de VPN uitgeschakeld en niet verbonden gehouden. Schakel deze optie uit om dergelijk gedrag niet af te dwingen."; -"service.sections.diagnostics.header" = "Diagnose"; -"service.sections.diagnostics.footer" = "De maskeerstatus is effectief na opnieuw verbinden. Netwerkgegevens zijn hostnamen, IP-adressen, routing, SSID's. Inloggegevens en privésleutels worden niet geregistreerd."; -"service.cells.use_profile.caption" = "Gebruik dit profiel"; -"service.cells.vpn_service.caption" = "Ingeschakeld"; -"service.cells.vpn.turn_on.caption" = "VPN activeren"; -"service.cells.vpn.turn_off.caption" = "VPN deactiveren"; -"service.cells.connection_status.caption" = "Status"; -"service.cells.host.parameters.caption" = "Parameters"; -"service.cells.provider.pool.caption" = "Locatie"; -"service.cells.provider.preset.caption" = "Voorkeur"; -"service.cells.provider.refresh.caption" = "Vernieuw de infrastructuur"; -"service.cells.category.caption" = "Categorie"; -"service.cells.addresses.caption" = "Adressen"; -"service.cells.only_shows_favorites.caption" = "Alleen favoriete locaties weergeven"; -"service.cells.vpn_survives_sleep.caption" = "Actief tijdens slaapstand"; -"service.cells.vpn_resolves_hostname.caption" = "Haal de naam van de host op"; -"service.cells.trusted_add_wifi.caption" = "Wi-Fi toevoegen"; -"service.cells.trusted_mobile.caption" = "Mobiel netwerk"; -"service.cells.trusted_policy.caption" = "Trust disables VPN"; -"service.cells.test_connectivity.caption" = "Test connectiviteit"; -"service.cells.data_count.caption" = "Gegegevens uitgewisseld"; -"service.cells.data_count.none" = "Niet beschikbaar"; -"service.cells.server_configuration.caption" = "Server configuratie"; -"service.cells.server_network.caption" = "Server netwerk"; -"service.cells.debug_log.caption" = "Foutopsporingslogboek"; -"service.cells.masks_private_data.caption" = "Netwerkgegevens maskeren"; -"service.cells.reconnect.caption" = "Opnieuw verbinden"; -"service.cells.report_issue.caption" = "Probleem met connectiviteit melden"; - -"service.alerts.rename.title" = "Profiel hernoemen"; -"service.alerts.credentials_needed.message" = "Voer eerst de accountgegevens in."; -"service.alerts.reconnect_vpn.message" = "Opnieuw verbinding maken met de VPN?"; -"service.alerts.trusted.no_network.message" = "U bent niet verbonden met een Wi-Fi-netwerk."; -"service.alerts.trusted.will_disconnect_trusted.message" = "Door dit netwerk te vertrouwen, kan de verbinding met de VPN mogelijk worden verbroken. Doorgaan?"; -"service.alerts.trusted.will_disconnect_policy.message" = "Door het vertrouwensbeleid te wijzigen, kan de verbinding met de VPN mogelijk worden verbroken. Doorgaan?"; -"service.alerts.test_connectivity.title" = "Connectiviteit"; -"service.alerts.test_connectivity.messages.success" = "Apparaat is verbonden met internet!"; -"service.alerts.test_connectivity.messages.failure" = "Uw apparaat heeft geen internetverbinding. Controleer uw profielparameters."; -"service.alerts.configuration.disconnected" = "Configuratie niet beschikbaar, zorg ervoor dat u verbonden bent met de VPN."; -"service.alerts.masks_private_data.messages.must_reconnect" = "Om het huidige foutopsporingslogboek veilig opnieuw in te stellen en de nieuwe maskeervoorkeur toe te passen, moet u nu opnieuw verbinding maken met VPN."; -"service.alerts.buttons.reconnect" = "Opnieuw verbinden"; -"service.alerts.download.title" = "Download vereist"; -"service.alerts.download.message" = "%@ vereist het downloaden van extra configuratiebestanden.\n\nBevestig om het downloaden te starten."; -"service.alerts.download.failed" = "Downloaden van configuratiebestanden is mislukt. %@"; -"service.alerts.download.hud.extracting" = "Bestanden uitpakken, even geduld..."; -"service.alerts.location.message.denied" = "Om dit Wi-Fi netwerk te vertrouwen is toestemming tot locatie gegevens nodig. Ga naar Instellingen -> Privacy -> Locatievoorzieningen en check de locatie bevoegdheden voor Passepartout."; -"service.alerts.location.button.settings" = "Instellingen"; - -"provider.pool.sections.empty_favorites.footer" = "Veeg naar links op een locatie om deze toe te voegen of te verwijderen aan Favorieten."; -"provider.pool.actions.favorite" = "Favoriet"; -"provider.pool.actions.unfavorite" = "Geen favoriet"; - -"provider.preset.cells.tech_details.caption" = "Technische details"; - -"account.title" = "Account"; -"account.sections.credentials.header" = "Inloggegevens"; -"account.sections.guidance.footer.infrastructure.default.web" = "Gebruik de inloggegevens van %@."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Gebruik uw %@ service-gegevens, die kunnen verschillen van de gegevens van de website."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Gebruik de inloggegevens van %@. Uw gebruikersnaam is meestal numeriek (zonder ruimte)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Gebruik de inloggegevens van %@. Uw gebruikersnaam is meestal uw e-mailadres."; -"account.sections.guidance.footer.infrastructure.pia" = "Gebruik de inloggegevens van %@. Uw gebruikersnaam is meestal numeriek met \"p\" als voorvoegsel."; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Vind de inloggegevens van %@ in \"Account > OpenVPN / IKEv2 Username\" onderdeel van de website."; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Gebruik de inloggegevens van %@. Uw gebruikersnaam is meestal uw e-mailadres."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Gebruik de inloggegevens van %@ Uw gebruikersnaam is meestal uw e-mailadres."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Vind de inloggegevens van %@ in de OpenVPN Config Generator op de website."; -"account.sections.registration.footer" = "Registreer voor een %@ account op de website."; -"account.cells.username.caption" = "Gebruikersnaam"; -"account.cells.username.placeholder" = "gebruikersnaam"; -"account.cells.password.caption" = "Wachtwoord"; -"account.cells.password.placeholder" = "geheim"; -"account.cells.open_guide.caption" = "Bekijk de inloggegevens"; -"account.cells.signup.caption" = "Registreer bij %@"; - -"endpoint.title" = "Endpoint"; -"endpoint.sections.location_addresses.header" = "Adressen"; -"endpoint.sections.location_protocols.header" = "Protocollen"; -"endpoint.cells.address" = "Adres"; -"endpoint.cells.protocol" = "Protocol"; -"endpoint.cells.any_address.caption" = "Automatisch"; -"endpoint.cells.any_protocol.caption" = "Automatisch"; - -"network_settings.title" = "Netwerk instellingen"; -"network_settings.cells.add_dns_server.caption" = "Voeg adress toe"; -"network_settings.cells.add_dns_domain.caption" = "Zoekdomein toevoegen"; -"network_settings.cells.proxy_bypass.caption" = "Omzeil domein"; -"network_settings.cells.add_proxy_bypass.caption" = "Voeg omzeil optie voor domein toe"; - -"configuration.title" = "Configuratie"; -"configuration.sections.communication.header" = "Communicatie"; -"configuration.sections.reset.footer" = "Tik hier als de connectiviteit niet meer werkt na het aanpassen van instellingen, om terug te gaan naar de originele configuratie."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Compressie"; -"configuration.sections.network.header" = "Netwerk"; -"configuration.sections.other.header" = "Ander"; -"configuration.cells.cipher.caption" = "Cipher"; -"configuration.cells.digest.caption" = "Authenticatie"; -"configuration.cells.digest.value.embedded" = "Embedded"; -"configuration.cells.compression_framing.caption" = "Framing"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "Algoritme"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "Niet ondersteund"; -"configuration.cells.reset_original.caption" = "Reset configuratie"; -"configuration.cells.client.caption" = "Client certificaat"; -"configuration.cells.client.value.enabled" = "Geverifieerd"; -"configuration.cells.client.value.disabled" = "Niet geverifieerd"; -"configuration.cells.tls_wrapping.caption" = "Wrapping"; -"configuration.cells.tls_wrapping.value.auth" = "Authenticatie"; -"configuration.cells.tls_wrapping.value.crypt" = "Versleuteling"; -"configuration.cells.eku.caption" = "Uitgebreide verificatie"; -"configuration.cells.keep_alive.caption" = "Keep-alive"; -"configuration.cells.keep_alive.value.seconds" = "%d seconden"; -"configuration.cells.renegotiation_seconds.caption" = "Renegotiation"; -"configuration.cells.renegotiation_seconds.value.after" = "na %@"; -"configuration.cells.random_endpoint.caption" = "Willekeurig eindpunt"; -"configuration.alerts.commit.message" = "Nieuwe parameters worden pas van kracht als u handmatig opnieuw verbinding maakt. Wijzigingen in vertrouwde netwerken zijn direct van toepassing."; -"configuration.alerts.commit.buttons.reconnect" = "Opnieuw verbinden"; -"configuration.alerts.commit.buttons.skip" = "Overslaan"; - -"trusted.columns.trust.title" = "Vertrouwen"; -"trusted.ethernet.title" = "Bekabelde verbindingen vertrouwen"; -"trusted.ethernet.description" = "Vink aan om alle bekabelde verbindingen te vertrouwen."; - -"preferences.title" = "Voorkeuren"; -"preferences.sections.general.header" = "Algemeen"; -"preferences.cells.launches_on_login.caption" = "Lanceren bij aanmelden"; -"preferences.cells.launches_on_login.footer" = "Vink aan als u wilt dat de app automatisch wordt gelanceerd bij opstarten of aanmelden."; -"preferences.cells.confirm_quit.caption" = "Sluiten bevestigen"; -"preferences.cells.confirm_quit.footer" = "Vink aan om een bevestigingsmelding te sluiten."; - -"network_settings.gateway.title" = "Standaard gateway"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Servers"; -"network_settings.dns.cells.domain.caption" = "Domein"; -"network_settings.dns.cells.domains.title" = "Domeinen"; -"network_settings.proxy.title" = "Proxy"; -"network_settings.proxy.cells.bypass_domains.title" = "Domeinen omzeilen"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "Route"; - -"debug_log.buttons.previous" = "Vorige"; -"debug_log.buttons.next" = "Volgende"; -"debug_log.buttons.copy" = "Kopiëren"; -"debug_log.alerts.empty_log.message" = "Het logboek voor foutopsporing is leeg."; - -"shortcuts.add.title" = "Voeg snelkoppeling toe"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "Mobiel"; -"shortcuts.add.cells.connect.caption" = "Verbind met"; -"shortcuts.add.cells.enable_vpn.caption" = "Activeer VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Deactiveer VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Vertrouw huidig Wi-Fi netwerk"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Untrust current Wi-Fi"; -"shortcuts.add.cells.trust_cellular.caption" = "Vertouw mobiel netwerk"; -"shortcuts.add.cells.untrust_cellular.caption" = "Untrust mobiel netwerk"; -"shortcuts.add.alerts.no_profiles.message" = "Er is geen profiel om verbinding mee te maken."; - -"shortcuts.edit.title" = "Beheer snelkoppelingen"; -"shortcuts.edit.sections.all.header" = "Bestaande snelkoppelingen"; -"shortcuts.edit.cells.add_shortcut.caption" = "Voeg snelkoppeling toe"; - -"purchase.title" = "Aanschaffen"; -"purchase.sections.products.footer" = "Elk product is een eenmalige aankoop. Aankopen van providers bevatten geen VPN-abonnement."; -"purchase.cells.full_version.extra_description" = "Alle providers (inclusief toekomstige)\n%@"; -"purchase.cells.restore.title" = "Herstel Aankopen"; -"purchase.cells.restore.description" = "Als u deze app of functie in het verleden heeft gekocht, kunt u uw aankopen herstellen en wordt dit scherm niet meer getoond."; - -"donation.title" = "Donatie"; -"donation.sections.one_time.header" = "Eenmalig"; -"donation.sections.one_time.footer" = "Als je dankbaarheid wilt tonen voor mijn gratis werk, zijn hier een paar bedragen die je direct kunt doneren.\n\nHet bedrag wordt slechts één keer per donatie in rekening gebracht en u kunt meerdere keren doneren."; -"donation.cells.loading.caption" = "Ophalen donaties"; -"donation.cells.purchasing.caption" = "Doneren"; -"donation.alerts.purchase.success.title" = "Hartelijk dank"; -"donation.alerts.purchase.success.message" = "Dit betekent veel voor mij en ik hoop echt dat je deze app blijft gebruiken en promoten."; -"donation.alerts.purchase.failure.message" = "Donatie mislukt. %@"; - -"about.title" = "Over"; -"about.sections.web.header" = "Web"; -"about.sections.share.header" = "Delen"; -"about.cells.credits.caption" = "Credits"; -"about.cells.website.caption" = "Home page"; -"about.cells.faq.caption" = "FAQ"; -"about.cells.disclaimer.caption" = "Vrijwaring"; -"about.cells.privacy_policy.caption" = "Privacybeleid"; -"about.cells.share_twitter.caption" = "Tweet about it!"; -"about.cells.share_generic.caption" = "Nodig een vriend uit"; - -"version.title" = "Versie"; -"version.labels.intro" = "Passepartout en TunnelKit zijn geschreven en worden onderhouden door Davide De Rosa (keeshux).\n\nDe broncode voor Passepartout en TunnelKit is openbaar beschikbaar op GitHub onder de GPLv3, je kunt links op de startpagina vinden.\n\nPassepartout is een niet-officiële client en is op geen enkele manier verbonden aan OpenVPN Inc."; - -"credits.title" = "Credits"; -"credits.sections.licenses.header" = "Licenties"; -"credits.sections.notices.header" = "Mededelingen"; -"credits.sections.translations.header" = "Vertalingen"; - -"label.license.error" = "Kan volledige licentie-inhoud niet downloaden."; - -// iOS only - -"imported_hosts.title" = "Geïmporteerde hosts"; - -// macOS only - -"menu.show.title" = "Weergeven"; -"menu.switch_profile.title" = "Actief profiel"; -"menu.active_profile.title.none" = "Geen actief profiel"; -"menu.active_profile.items.customize.title" = "Aanpassen..."; -"menu.active_profile.messages.missing_credentials" = "Geen account geconfigureerd"; -"menu.organizer.title" = "Organisator"; -"menu.preferences.title" = "Voorkeuren"; -"menu.support.title" = "Ondersteuning"; -"menu.quit.title" = "%@ afsluiten"; -"menu.quit.messages.confirm" = "De VPN zal, indien geactiveerd, op de achtergrond blijven draaien. Wilt u sluiten?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/pl.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/pl.lproj/Core.strings deleted file mode 100644 index 48c1b07d..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/pl.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "OK"; -"global.cancel" = "Anuluj"; -"global.next" = "Następny"; -"global.close" = "Zamknij"; -"global.host.title_input.message" = "Akceptowane są znaki alfanumeryczne i pauza \"-\", podkreślenie \"_\" oraz kropka \".\"."; -"global.host.title_input.placeholder" = "Mój profil"; -"global.email_not_configured" = "Adres e-mail nie jest skonfigurowany."; -"global.values.default" = "Default"; - -"global.captions.address" = "Adres"; -"global.captions.port" = "Port"; -"global.captions.protocol" = "Protokół"; - -"global.values.enabled" = "Włączony"; -"global.values.disabled" = "Wyłączony"; -"global.values.none" = "Brak"; -"global.values.automatic" = "Automatycznie"; -"global.values.manual" = "Ręcznie"; - -"reddit.title" = "Reddit"; -"reddit.message" = "Wiedziałeś/łaś, że Passepartout ma swój subreddit? Subskrybuj dla aktualizacji, dyskusji o funkcjonalności, nowych platformach lub o czymkolwiek zechcesz.\n\nTo również świetny sposób na okazanie zainteresowania projektem."; -"reddit.buttons.subscribe" = "Subskrybuj!"; -"reddit.buttons.remind" = "Przypomnij mi później"; -"reddit.buttons.never" = "Nie przypominaj"; - -"vpn.connecting" = "Łączenie"; -"vpn.active" = "Aktywne"; -"vpn.disconnecting" = "Rozłączanie"; -"vpn.inactive" = "Nieaktywne"; -"vpn.disabled" = "Wyłączone"; -"vpn.unused" = "Wył"; - -"vpn.errors.timeout" = "Upłynąl limit czasu połączenia"; -"vpn.errors.dns" = "Błąd DNS"; -"vpn.errors.auth" = "Błąd autoryzacji"; -"vpn.errors.tls" = "Błąd TLS"; -"vpn.errors.encryption" = "Błąd szyfrowania"; -"vpn.errors.compression" = "Niewspierana kompresja"; -"vpn.errors.network" = "Sieć zmieniona"; -"vpn.errors.routing" = "Brak routingu"; -"vpn.errors.gateway" = "Brak bramy domyślnej"; -"vpn.errors.shutdown" = "Serwer został zamknięty"; - -"parsed_file.alerts.malformed.message" = "Plik konfiguracyjny zawiera źle zformułowaną opcję (%@)."; -"parsed_file.alerts.missing.message" = "Plik konfiguracyjny nie nawiera potrzebnej opcji (%@)."; -"parsed_file.alerts.unsupported.message" = "Plik konfiguracyjny posiada niewspieraną opcję (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "Plik konfiguracyjny jest poprawny, ale zawiera potencjalnie niewspieraną opcję (%@).\n\nPołączenie może zostać przerwane przez ustawienia serwera."; -"parsed_file.alerts.encryption_passphrase.message" = "Proszę wpisać frazę szyfrującą."; -"parsed_file.alerts.decryption.message" = "Konfiguracja zawiera zaszyfrowany klucz prywatny który nie może zostać odszyfrowany. Sprawdź frazę szyfrującą."; -"parsed_file.alerts.parsing.message" = "Błąd w przetwarzaniu pliku konfiguracyjnego (%@)."; -"parsed_file.alerts.buttons.report" = "Zgłoś błąd"; - -"network_choice.client" = "Otwórz plik .ovpn"; -"network_choice.server" = "Pobierz z serwera"; - -"issue_reporter.title" = "Zgłoś błąd"; -"issue_reporter.message" = "Rejestr debugowania jest potrzebny w rozwiązywaniu problemów z połaczeniem i jest w pełni anonimowy.\n\nPlik konfiguracyjny .ovpn, jeśli używany, jest dołączany bez wrażliwych danych.\n\nSPrawdź załącznik e-maila jeśli masz wątpliwości."; -"issue_reporter.buttons.accept" = "Rozumiem"; - -"translations.title" = "Tłumaczenia"; - -"share.message" = "Passepartout to klient OpenVPN, przyjazny użytkownikowi, open-source, stworzony dla iOS i macOS"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Dostawca"; -"organizer.menus.provider.unavailable" = "Brak dostawców"; -"organizer.menus.host" = "Host"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "Przyjdź i zobacz, jak tworzę Passepartout na żywo na Twitchu, dołącz do czatu, aby współdziałać i udzielać się!"; -"organizer.sections.providers.header" = "Usługodawcy"; -"organizer.sections.providers.footer" = "Tutaj znajdziesz kilku usługodawców z profilami konfiguracyjnymi."; -"organizer.sections.hosts.header" = "Hosty"; -"organizer.sections.hosts.footer" = "Importuj hosty z plików konfiguracyjnych .ovpn."; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "Użyj Siri żeby przyspieszyć najczęstsze akcje w aplikacji."; -"organizer.sections.support.header" = "Wsparcie"; -"organizer.sections.feedback.header" = "Wyraź opinię"; -"organizer.cells.follow_twitch.caption" = "Oglądaj Passepartout na Twitchu"; -"organizer.cells.profile.value.current" = "W użyciu"; -"organizer.cells.siri_shortcuts.caption" = "Zarządzaj skrótami"; -"organizer.cells.join_community.caption" = "Dołącz do społeczności"; -"organizer.cells.write_review.caption" = "Napisz recenzję"; -"organizer.cells.donate.caption" = "Wyślij dotację"; -"organizer.cells.github_sponsors.caption" = "Wesprzyj mnie na GitHub"; -"organizer.cells.translate.caption" = "Zaproponuj tłumaczenie"; -"organizer.cells.about.caption" = "O %@"; -"organizer.cells.uninstall.caption" = "Usuń konfiguracje VPN"; -"organizer.cells.add_provider.caption" = "Dodaj nowego usługodawcę"; -"organizer.cells.add_host.caption" = "Dodaj z Plików"; -"organizer.cells.import_host.caption" = "Dodaj z zaimportowanych"; -"organizer.alerts.exhausted_providers.message" = "Stworzyłeś/aś już profile dla każdego usługodawcy."; -"organizer.alerts.add_host.message" = "Otwórz link z plikiem .ovpn w Safari, Poczcie lub innej aplikacji aby utworzyć profil hosta.\n\nMożesz też zaimportować plik .ovpn używając 'File Sharing' w iTunes."; -"organizer.alerts.cannot_donate.message" = "Żadna metoda płatności nie jest skonfigurowana na tym urządzeniu."; -"organizer.alerts.delete_vpn_profile.message" = "Na pewno chcesz usunąć konfigurację VPN z urządzenia? Może to naprawić błędy z statusem VPN i nie będzie miało wpływu na konfigurację usługodawców/hostów."; -"organizer.alerts.remove_profile.title" = "Usuń profil"; -"organizer.alerts.remove_profile.message" = "Na pewno chcesz usunąć profil %@?"; -"organizer.alerts.open_host_file.title" = "Wybierz plik .ovpn"; - -"wizards.provider.cells.update_list.caption" = "Zaktualizuj listę"; -"wizards.provider.alerts.unavailable.message" = "Nie można pobrać infrastruktury dostawcy, spróbuj ponownie później."; -"wizards.host.sections.existing.header" = "Istniejące profile"; -"wizards.host.cells.title_input.caption" = "Tytuł"; -"wizards.host.alerts.existing.message" = "Profil hosta z taką nazwą już istnieje. Nadpisać profil?"; - -"service.welcome.message" = "Witaj w Passepartout!\n\nUżyj organizera by utworzyć nowy profil."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "Połączenie zostanie nawiązane zgodnie z ustawieniami."; -"service.sections.status.header" = "Połączenie"; -"service.sections.configuration.header" = "Konfiguracja"; -"service.sections.provider_infrastructure.footer" = "Ostatnio aktualizowane %@."; -"service.sections.vpn_survives_sleep.footer" = "Wyłącz dla mniejszego zużycia baterii kosztem wolniejszego działania spowodowanego ponownym połączeniem przy wybudzeniu urządzenia."; -"service.sections.vpn_resolves_hostname.footer" = "Preferowane w większości sieci i potrzebne w niektórych sieciach IPv6. Wyłącz kiedy DNS jest zablokowane, lub żeby przyspieszyć ustanawianie połączenia gdy DNS jest zbyt wolne."; -"service.sections.trusted.header" = "Zaufane sieci"; -"service.sections.trusted.footer" = "Kiedy urządzenie łączy się z zaufaną siecią, VPN jest wyłączane. Wyłącz tę opcję żeby nie wymuszać takiego zachowania."; -"service.sections.diagnostics.header" = "Diagnostyka"; -"service.sections.diagnostics.footer" = "Status maskowania będzie widoczny po ponownym połączeniu. Dane połączenia to nazwy hostów, adresy IP, routing, SSID. Loginy i klucze prywatne nie są zapisywane."; -"service.cells.use_profile.caption" = "Używaj tego profilu"; -"service.cells.vpn_service.caption" = "Włączone"; -"service.cells.vpn.turn_on.caption" = "Włącz VPN"; -"service.cells.vpn.turn_off.caption" = "Wyłącz VPN"; -"service.cells.connection_status.caption" = "Status"; -"service.cells.host.parameters.caption" = "Parametry"; -"service.cells.provider.pool.caption" = "Lokalizacja"; -"service.cells.provider.preset.caption" = "Preset"; -"service.cells.provider.refresh.caption" = "Odśwież infrastrukturę"; -"service.cells.category.caption" = "Kategoria"; -"service.cells.addresses.caption" = "Adresy"; -"service.cells.only_shows_favorites.caption" = "Pokazuj tylko ulubione lokalizacje"; -"service.cells.vpn_survives_sleep.caption" = "Utrzymuj połączenie przy zablokowanym ekranie"; -"service.cells.vpn_resolves_hostname.caption" = "Rozwiązuj nazwy hostów usługodawcy"; -"service.cells.trusted_add_wifi.caption" = "Dodaj Wi-Fi"; -"service.cells.trusted_mobile.caption" = "Sieć komórkowa"; -"service.cells.trusted_policy.caption" = "Wyłącz VPN dla zaufanych sieci"; -"service.cells.test_connectivity.caption" = "Testuj połączenie"; -"service.cells.data_count.caption" = "Pobrane/wysłane dane"; -"service.cells.data_count.none" = "Niedostępne"; -"service.cells.server_configuration.caption" = "Konfiguracja serwera"; -"service.cells.server_network.caption" = "Sieć serwera"; -"service.cells.debug_log.caption" = "Debugowanie"; -"service.cells.masks_private_data.caption" = "Maskuj dane sieci"; -"service.cells.reconnect.caption" = "Połącz ponownie"; -"service.cells.report_issue.caption" = "Zgłoś problemy z połączeniem"; - -"service.alerts.rename.title" = "Zmień nazwę profilu"; -"service.alerts.credentials_needed.message" = "Musisz najpierw wpisać login/hasło."; -"service.alerts.reconnect_vpn.message" = "Czy chcesz połączyć się ponownie z VPN?"; -"service.alerts.trusted.no_network.message" = "Nie jesteś połączony z siecią WiFi."; -"service.alerts.trusted.will_disconnect_trusted.message" = "Ufając tej sieci, połączenie VPN zostanie przerwane, kontynuować?"; -"service.alerts.trusted.will_disconnect_policy.message" = "Aby zmienić ustawienia zaufania, połączenie VPN może zostać przerwane, kontynuować?"; -"service.alerts.test_connectivity.title" = "Połączenie"; -"service.alerts.test_connectivity.messages.success" = "Twoje urządzenie jest połączone z internetem!"; -"service.alerts.test_connectivity.messages.failure" = "Twoje urządzenie nie jest połączone z internetem, sprawdź ustawienia profilu."; -"service.alerts.configuration.disconnected" = "Konfiguracja niedostępna, upewnij się, że jesteś połączony/a z VPN."; -"service.alerts.masks_private_data.messages.must_reconnect" = "Aby bezpiecznie zresetować rejestr debugowania i zastosować nowe ustawienia maskowania, musisz połączyć się z VPN ponownie."; -"service.alerts.buttons.reconnect" = "Połącz ponownie"; -"service.alerts.download.title" = "Potrzebne pobranie"; -"service.alerts.download.message" = "%@ potrzebuje pobrania dodatkowych plików konfiguracyjnych.\n\nPotwierdź aby zacząć pobieranie."; -"service.alerts.download.failed" = "Nie udało się pobrać plików konfiguracyjnych. %@"; -"service.alerts.download.hud.extracting" = "Wypakowywanie plików, proszę czekać ..."; -"service.alerts.location.message.denied" = "Musisz pozwolić na dostęp do lokalizacji żeby zaufać tej sieci Wi-Fi. Przejdź do ustawień prywatności i pozwól Passepartout na wykorzystywanie usług lokalizacji."; -"service.alerts.location.button.settings" = "Ustawienia"; - -"provider.pool.sections.empty_favorites.footer" = "Aby usunąć zakładkę, przesuń w lewo."; -"provider.pool.actions.favorite" = "Dodaj do ulubionych"; -"provider.pool.actions.unfavorite" = "Usuń z ulubionych"; - -"provider.preset.cells.tech_details.caption" = "Dane techniczne"; - -"account.title" = "Konto"; -"account.sections.credentials.header" = "Dane logowania"; -"account.sections.guidance.footer.infrastructure.default.web" = "Użyj loginu do %@."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Użyj poświadczeń usługi %@, które mogą różnić się od poświadczeń witryny."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Użyj loginu do %@. Twoja nazwa użytkownika jest najczęściej ciągiem liczb (bez przestrzeni)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Użyj loginu do %@. Twoja nazwa użytkownika to najczęściej e-mail."; -"account.sections.guidance.footer.infrastructure.pia" = "Użyj loginu do %@. Twoja nazwa użytkownika jesy najczęściej ciągiem liczb poprzedonym prefiksem \"p\"."; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Użyj loginu do %@. Zajdziesz go w sekcji \"Account > OpenVPN / IKEv2 Username\"."; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Użyj loginu do %@. Twoja nazwa użytkownika to najczęściej e-mail."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Użyj loginu do %@. Twoja nazwa użytkownika to najczęściej e-mail."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Użyj loginu do %@ z generatora konfiguracji OpenVPN dostępnego na stronie."; -"account.sections.registration.footer" = "Utwórz konto na stronie: %@"; -"account.cells.username.caption" = "Nazwa użytkownika"; -"account.cells.username.placeholder" = "Nazwa użytkownika"; -"account.cells.password.caption" = "Hasło"; -"account.cells.password.placeholder" = "Ukryte"; -"account.cells.open_guide.caption" = "Zobacz swoje login/hasło"; -"account.cells.signup.caption" = "Zarejstruj się w %@"; - -"endpoint.title" = "Host końcowy"; -"endpoint.sections.location_addresses.header" = "Adresy"; -"endpoint.sections.location_protocols.header" = "Protokoły"; -"endpoint.cells.address" = "Adres"; -"endpoint.cells.protocol" = "Protokół"; -"endpoint.cells.any_address.caption" = "Automatyczny"; -"endpoint.cells.any_protocol.caption" = "Automatyczny"; - -"network_settings.title" = "Ustawienia sieci"; -"network_settings.cells.add_dns_server.caption" = "Dodaj adres"; -"network_settings.cells.add_dns_domain.caption" = "Dodaj domenę wyszukiwania"; -"network_settings.cells.proxy_bypass.caption" = "Pomiń domenę"; -"network_settings.cells.add_proxy_bypass.caption" = "Dodaj domenę"; - -"configuration.title" = "Konfiguracja"; -"configuration.sections.communication.header" = "Komunikacja"; -"configuration.sections.reset.footer" = "Jeśli masz problemy z połączeniem po zmianie konfiguracji komunikacji, kliknij żeby przywrócić domyślną konfigurację."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Kompresja"; -"configuration.sections.network.header" = "Sieć"; -"configuration.sections.other.header" = "Inne"; -"configuration.cells.cipher.caption" = "Szyfr"; -"configuration.cells.digest.caption" = "Uwierzytelnienie"; -"configuration.cells.digest.value.embedded" = "Osadzony"; -"configuration.cells.compression_framing.caption" = "Struktura"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "Algorytm"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "Nieobsługiwane"; -"configuration.cells.reset_original.caption" = "Zresetuj konfigurację"; -"configuration.cells.client.caption" = "Certyfikat klienta"; -"configuration.cells.client.value.enabled" = "Zweryfikowany"; -"configuration.cells.client.value.disabled" = "Niezweryfikowany"; -"configuration.cells.tls_wrapping.caption" = "Wrapping"; -"configuration.cells.tls_wrapping.value.auth" = "Uwierzytelnienie"; -"configuration.cells.tls_wrapping.value.crypt" = "Szyfrowanie"; -"configuration.cells.eku.caption" = "Rozszerzona weryfikacja"; -"configuration.cells.keep_alive.caption" = "Utrzymuj połączenie"; -"configuration.cells.keep_alive.value.seconds" = "%d sekund"; -"configuration.cells.renegotiation_seconds.caption" = "Ponowna negocjacja"; -"configuration.cells.renegotiation_seconds.value.after" = "po %@"; -"configuration.cells.random_endpoint.caption" = "Losowy host końcowy"; -"configuration.alerts.commit.message" = "Nowe parametry zaczną obowiązywać dopiero po ponownym ręcznym połączeniu. Zmiany w sieciach zaufanych zostaną zastosowane natychmiast."; -"configuration.alerts.commit.buttons.reconnect" = "Połącz ponownie teraz"; -"configuration.alerts.commit.buttons.skip" = "Pomiń"; - -"trusted.columns.trust.title" = "Ufaj"; -"trusted.ethernet.title" = "Ufaj połączeniom przewodowym"; -"trusted.ethernet.description" = "Zaznacz, aby traktować każde przewodowe połączenie kablowe jako zaufane."; - -"preferences.title" = "Preferencje"; -"preferences.sections.general.header" = "Ogólne"; -"preferences.cells.launches_on_login.caption" = "Uruchom po zalogowaniu"; -"preferences.cells.launches_on_login.footer" = "Zaznacz, aby automatycznie uruchamiać aplikację przy restarcie systemu lub logowaniu."; -"preferences.cells.confirm_quit.caption" = "Potwierdź zakończenie pracy"; -"preferences.cells.confirm_quit.footer" = "Zaznacz, aby wyświetlić monit o potwierdzeniu zakończenia."; - -"network_settings.gateway.title" = "Domyślna brama sieciowa"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Serwery"; -"network_settings.dns.cells.domain.caption" = "Domena"; -"network_settings.dns.cells.domains.title" = "Domeny"; -"network_settings.proxy.title" = "Proxy"; -"network_settings.proxy.cells.bypass_domains.title" = "Pomiń domeny"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "Trasowanie"; - -"debug_log.buttons.previous" = "Poprzedni"; -"debug_log.buttons.next" = "Następny"; -"debug_log.buttons.copy" = "Kopiuj"; -"debug_log.alerts.empty_log.message" = "Rejestr debugowania jest pusty."; - -"shortcuts.add.title" = "Dodaj skrót"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "Dane komórkowe"; -"shortcuts.add.cells.connect.caption" = "Połącz z"; -"shortcuts.add.cells.enable_vpn.caption" = "Włącz VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Wyłącz VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Zaufaj obecnie połączonej sieci Wi-Fi"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Przestań ufać obecnie połączonej sieci Wi-Fi"; -"shortcuts.add.cells.trust_cellular.caption" = "Ufaj danym komórkowym"; -"shortcuts.add.cells.untrust_cellular.caption" = "Przestań ufać danym komórkowym"; -"shortcuts.add.alerts.no_profiles.message" = "Brak wybranego profilu połączenia."; - -"shortcuts.edit.title" = "Zarządzaj skrótami"; -"shortcuts.edit.sections.all.header" = "Istniejące skróty"; -"shortcuts.edit.cells.add_shortcut.caption" = "Dodaj skrót"; - -"purchase.title" = "Kup"; -"purchase.sections.products.footer" = "Każdy produkt to zakup jednorazowy. Kuipno usługodawcy nie zawiera subskrypcji VPN."; -"purchase.cells.full_version.extra_description" = "Wszyscy usługodawcy (włączając przyszłych)\n%@"; -"purchase.cells.restore.title" = "Przywróć zakup"; -"purchase.cells.restore.description" = "Jeśli kupiłeś tą aplikację lub funkcję wcześniej, możesz przywrócić swoje zakupy i ten ekran nie będzie wyświetlony ponownie."; - -"donation.title" = "Dotacja"; -"donation.sections.one_time.header" = "Jeden raz"; -"donation.sections.one_time.footer" = "Jeśli chcesz docenić moją pracę, poniżej znajdziesz kilka kwot do wyboru dotacji.\n\nTwoje konto zostanie obciążone tylko raz na jedną dotację, możesz wysłać dotację kilka razy."; -"donation.cells.loading.caption" = "Ładowanie dotacji"; -"donation.cells.purchasing.caption" = "Wykonywanie dotacji"; -"donation.alerts.purchase.success.title" = "Dziękuję"; -"donation.alerts.purchase.success.message" = "To dla mnie dużo znaczy, mam nadzięję że będziesz używać aplikacji i przyczynisz się do jej rozpowrzechnienia."; -"donation.alerts.purchase.failure.message" = "Nie można dokonać dotacji. %@"; - -"about.title" = "O programie"; -"about.sections.web.header" = "Strona WWW"; -"about.sections.share.header" = "Udostępnij"; -"about.cells.credits.caption" = "Twórcy"; -"about.cells.website.caption" = "Strona domowa"; -"about.cells.faq.caption" = "Pytania i odpowiedzi"; -"about.cells.disclaimer.caption" = "Disclaimer"; -"about.cells.privacy_policy.caption" = "Polityka prywatności"; -"about.cells.share_twitter.caption" = "Wyślij tweeta!"; -"about.cells.share_generic.caption" = "Zaproś znajomego"; - -"version.title" = "Wersja"; -"version.labels.intro" = "Passepartout i TunnelKit są stworzone i utrzymywane przez Davide De Rosa (keeshux).\n\nKod źródłowy Passepartout i TunnelKit jest publicznie dostępny na licencji GPLv3, linki możesz znaleźć na stronie domowej.\n\nPassepartout nie jest oficjanlnym klientem i nie jest powiązany z OpenVPN Inc."; - -"credits.title" = "Twórcy"; -"credits.sections.licenses.header" = "Licencje"; -"credits.sections.notices.header" = "Dodatki"; -"credits.sections.translations.header" = "Tłumaczenia"; - -"label.license.error" = "Nie udało się pobrać danych licencyjnych."; - -// iOS only - -"imported_hosts.title" = "Zaimportowane hosty"; - -// macOS only - -"menu.show.title" = "Pokaż"; -"menu.switch_profile.title" = "Aktywny profil"; -"menu.active_profile.title.none" = "Brak aktywnych profili"; -"menu.active_profile.items.customize.title" = "Personalizuj"; -"menu.active_profile.messages.missing_credentials" = "Brak skonfigurowanych kont"; -"menu.organizer.title" = "Organzator"; -"menu.preferences.title" = "Preferencje"; -"menu.support.title" = "Obsługa techniczna"; -"menu.quit.title" = "Zakończ %@"; -"menu.quit.messages.confirm" = "Jeśli sieć VPN, jest włączona, będzie nadal działać w tle. Chcesz zakończyć?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/pt.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/pt.lproj/Core.strings deleted file mode 100644 index 69f1dd93..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/pt.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "OK"; -"global.cancel" = "Cancelar"; -"global.next" = "Próximo"; -"global.close" = "Fechar"; -"global.host.title_input.message" = "Caracteres aceitos são alfanuméricos, mais traço \"-\", underline \"_\" e ponto \".\"."; -"global.host.title_input.placeholder" = "Meu perfil"; -"global.email_not_configured" = "Nenhuma conta de email configurada."; -"global.values.default" = "Default"; - -"global.captions.address" = "Endereço"; -"global.captions.port" = "Porta"; -"global.captions.protocol" = "Protocolo"; - -"global.values.enabled" = "Ativado"; -"global.values.disabled" = "Desativado"; -"global.values.none" = "Nenhum"; -"global.values.automatic" = "Automático"; -"global.values.manual" = "Manual"; - -"reddit.title" = "Reddit"; -"reddit.message" = "Você sabia que Passepartout tem um subreddit? Siga-nos para atualizações ou para discutir problemas, novas funcionalidades, ou qualquer outro tópico.\n\nÉ uma boa maneira de mostrar seu interesse pelo projeto."; -"reddit.buttons.subscribe" = "Seguir!"; -"reddit.buttons.remind" = "Lembrar-me depois"; -"reddit.buttons.never" = "Não perguntar novamente"; - -"vpn.connecting" = "Conectando"; -"vpn.active" = "Ativa"; -"vpn.disconnecting" = "Desconectando"; -"vpn.inactive" = "Inativo"; -"vpn.disabled" = "Desativado"; -"vpn.unused" = "Desativado"; - -"vpn.errors.timeout" = "Timeout"; -"vpn.errors.dns" = "Falha no DNS"; -"vpn.errors.auth" = "Falha na autenticação"; -"vpn.errors.tls" = "Falha no TLS"; -"vpn.errors.encryption" = "Falha na criptografia"; -"vpn.errors.compression" = "Compressão não suportada"; -"vpn.errors.network" = "Rede alterada"; -"vpn.errors.routing" = "Rota necessária"; -"vpn.errors.gateway" = "Sem gateway"; -"vpn.errors.shutdown" = "Servidor desligado"; - -"parsed_file.alerts.malformed.message" = "O arquivo de configuração possui uma opção não formatada corretamente (%@)."; -"parsed_file.alerts.missing.message" = "O arquivo não possui todas configurações requeridas (%@)."; -"parsed_file.alerts.unsupported.message" = "O arquivo de configuração possui uma opção não suportada (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "O arquivo de configuração está correto, mas provavelmente possui uma opção não suportada (%@).\n\nSua conexão poderá ser instável dependendo as configurações do servidor."; -"parsed_file.alerts.encryption_passphrase.message" = "Por favor, digite sua senha de criptografia."; -"parsed_file.alerts.decryption.message" = "Sua configiração possui uma chave privada criptografada que talvez não possa ser descriptografada. Verifique novamente sua senha de criptografia."; -"parsed_file.alerts.parsing.message" = "Não foi possível processar as configurações do arquivo (%@)."; -"parsed_file.alerts.buttons.report" = "Reportar problema"; - -"network_choice.client" = "Ler .ovpn"; -"network_choice.server" = "Puxar do servidor"; - -"issue_reporter.title" = "Reportar problema"; -"issue_reporter.message" = "O log de suas últimas conexões é crucial para resolver problemas de conectividade e é completamemnte anônimo.\n\nO arquivo de conexão .ovpn, se existente, é anexado sem nenhum dado sensitivo.\n\nPor favor, verique o anexos de email se necessário."; -"issue_reporter.buttons.accept" = "Eu concordo"; - -"translations.title" = "Traduções"; - -"share.message" = "Passepartout é um cliente OpenVPN fácil e open-source para iOS e macOS"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Fornecedor"; -"organizer.menus.provider.unavailable" = "Não restam fornecedores"; -"organizer.menus.host" = "Anfitreão"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "Venha me ver fazer o Passepartout ao vivo no Twitch, entre no chat para interagir e contribuir!"; -"organizer.sections.providers.header" = "Provedores"; -"organizer.sections.providers.footer" = "Aqui você encontra um provedor com o perfil pré-configurado."; -"organizer.sections.hosts.header" = "Hosts"; -"organizer.sections.hosts.footer" = "Importar hosts de um arquivo de configuração .ovpn."; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "Peça ajuda para Siri para agilar tarefas comum do aplicativo."; -"organizer.sections.support.header" = "Suporte"; -"organizer.sections.feedback.header" = "Feedback"; -"organizer.cells.follow_twitch.caption" = "Assistir Passepartout no Twitch"; -"organizer.cells.profile.value.current" = "Ativo"; -"organizer.cells.siri_shortcuts.caption" = "Gerenciar atalhos"; -"organizer.cells.join_community.caption" = "Participar da comunidade"; -"organizer.cells.write_review.caption" = "Escrever avaliação"; -"organizer.cells.donate.caption" = "Fazer doação"; -"organizer.cells.github_sponsors.caption" = "Contribuir no GitHub"; -"organizer.cells.translate.caption" = "Ajudar-nos na tradução"; -"organizer.cells.about.caption" = "Sobre %@"; -"organizer.cells.uninstall.caption" = "Remover configuração VPN"; -"organizer.cells.add_provider.caption" = "Adicionar novo perfil"; -"organizer.cells.add_host.caption" = "Adicionar dos Arquivos"; -"organizer.cells.import_host.caption" = "Adicionar dos importados"; -"organizer.alerts.exhausted_providers.message" = "Você criou um perfil para qualquer provedor disponível."; -"organizer.alerts.add_host.message" = "Abre uma URL para um arquivo de configuração .ovpn no Safari, Mail ou outro aplicativo.\n\nVocê pode também importar um .ovpn com o compartilhamento de arquivos do iTunes."; -"organizer.alerts.cannot_donate.message" = "Nenhum meio de pagamento configurado nesse dispositivo."; -"organizer.alerts.delete_vpn_profile.message" = "Tem certeza que deseja remover as configurações de VPN do seu dispositivo? Isso poderá corrigir problemas com o estado atual, sem afetar seu provedor e perfis do host."; -"organizer.alerts.remove_profile.title" = "Remover perfil"; -"organizer.alerts.remove_profile.message" = "Tem a certeza de que pretende eliminar o perfil %@?"; -"organizer.alerts.open_host_file.title" = "Selecionar um ficheiro .ovpn"; - -"wizards.provider.cells.update_list.caption" = "Atualizar lista"; -"wizards.provider.alerts.unavailable.message" = "Não foi possível baixar a infraestrutura do provedor, tente novamente mais tarde."; -"wizards.host.sections.existing.header" = "Perfis existentes"; -"wizards.host.cells.title_input.caption" = "Título"; -"wizards.host.alerts.existing.message" = "Já existe um perfil com esse nome, deseja substituí-lo?"; - -"service.welcome.message" = "Bem-vindo ao Passepartout!\n\nUse o organizador para adicionar um novo perfil."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "A conexão será estabelecida assim que necessária."; -"service.sections.status.header" = "Conexão"; -"service.sections.configuration.header" = "Configuração"; -"service.sections.provider_infrastructure.footer" = "Última atualização em %@."; -"service.sections.vpn_survives_sleep.footer" = "Desative para melhorar o consumo de bateria, o que poderá ocasionar queda de performance quando o restabelecimento de conexão for realizado."; -"service.sections.vpn_resolves_hostname.footer" = "Recomendado para maioria das redes e requirido em algumas redes IPv6. Desative se o DNS estiver bloqueado, ou para acelerar o DNS quando o mesmo está devagar."; -"service.sections.trusted.header" = "Redes seguras"; -"service.sections.trusted.footer" = "Ao entrar em uma rede segura, a VPN é normalmente é desconectada e mantido inativa. Desative essa opção para não forçar esse comportamento."; -"service.sections.diagnostics.header" = "Diagnóstico"; -"service.sections.diagnostics.footer" = "O status será escondido após reconectado. Os dados da rede são hostnames, endereços de IP, rotas, SSID. Credenciais e chaves privadas não será logadas em nenhum dos casos."; -"service.cells.use_profile.caption" = "Usar esse perfil"; -"service.cells.vpn_service.caption" = "Ativado"; -"service.cells.vpn.turn_on.caption" = "Ativar VPN"; -"service.cells.vpn.turn_off.caption" = "Desativar VPN"; -"service.cells.connection_status.caption" = "Status"; -"service.cells.host.parameters.caption" = "Parâmetros"; -"service.cells.provider.pool.caption" = "Localização"; -"service.cells.provider.preset.caption" = "Pré-definição"; -"service.cells.provider.refresh.caption" = "Atualizar infraestrutura"; -"service.cells.category.caption" = "Categoria"; -"service.cells.addresses.caption" = "Endereços"; -"service.cells.only_shows_favorites.caption" = "Mostrar apenas os locais preferidos"; -"service.cells.vpn_survives_sleep.caption" = "Manter ativo em modo descanço"; -"service.cells.vpn_resolves_hostname.caption" = "Resolver hostname do servidor"; -"service.cells.trusted_add_wifi.caption" = "Adicionar Wi-Fi"; -"service.cells.trusted_mobile.caption" = "Rede celular"; -"service.cells.trusted_policy.caption" = "Trust disables VPN"; -"service.cells.test_connectivity.caption" = "Testar conexão"; -"service.cells.data_count.caption" = "Dados transferidos"; -"service.cells.data_count.none" = "Indisponível"; -"service.cells.server_configuration.caption" = "Configuração do servidor"; -"service.cells.server_network.caption" = "Rede do servidor"; -"service.cells.debug_log.caption" = "Log de Debug"; -"service.cells.masks_private_data.caption" = "Esconder dados da rede"; -"service.cells.reconnect.caption" = "Reconectar"; -"service.cells.report_issue.caption" = "Reportar problemas de conexão"; - -"service.alerts.rename.title" = "Renomear perfil"; -"service.alerts.credentials_needed.message" = "Primeiramente você precisa preencher suas credenciais."; -"service.alerts.reconnect_vpn.message" = "Deseja reconectar à VPN?"; -"service.alerts.trusted.no_network.message" = "Você não está conectado em nenhuma rede Wi-Fi."; -"service.alerts.trusted.will_disconnect_trusted.message" = "Ao confiar nessa rede, sua VPN provavelmente será desconectada. Deseja continuar?"; -"service.alerts.trusted.will_disconnect_policy.message" = "Ao alterar a política de segurança, sua VPN provavelmente será desconectada. Deseja continuar?"; -"service.alerts.test_connectivity.title" = "Conectividade"; -"service.alerts.test_connectivity.messages.success" = "Seu dispositivo está conectado à Internet!"; -"service.alerts.test_connectivity.messages.failure" = "Seu dispositivo não está conectado à Internet, por favor, verifique sua configurações."; -"service.alerts.configuration.disconnected" = "Configuração indisponível, verifique se você está conectado em uma VPN."; -"service.alerts.masks_private_data.messages.must_reconnect" = "Para garantir uma restauração segura do seu log de debug, você precisa reconectar à VPN."; -"service.alerts.buttons.reconnect" = "Reconectar"; -"service.alerts.download.title" = "Download requirido"; -"service.alerts.download.message" = "%@ requer o download de arquivos de configuração adicionais.\n\nConfirme para iniciar."; -"service.alerts.download.failed" = "Erro no download do arquivo de configuração. %@"; -"service.alerts.download.hud.extracting" = "Extraindo arquivos, seja paciente..."; -"service.alerts.location.message.denied" = "Você precisa autorizar o compartilhamento de localização. Acesse ajustes do iOS e verifique permissões de localização do Passepartout."; -"service.alerts.location.button.settings" = "Ajustes"; - -"provider.pool.sections.empty_favorites.footer" = "Deslize para a esquerda em um local para adicioná-lo ou removê-lo dos Favoritos."; -"provider.pool.actions.favorite" = "Favorito"; -"provider.pool.actions.unfavorite" = "Não favorito"; - -"provider.preset.cells.tech_details.caption" = "Detalhes técnicos"; - -"account.title" = "Conta"; -"account.sections.credentials.header" = "Credenciais"; -"account.sections.guidance.footer.infrastructure.default.web" = "Utilize %@ credenciais do site."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Utilize suas credenciais de serviço %@, que podem diferir das credenciais do site."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Utilize %@ credenciais do site. Seu usuário é normalmente numérico (sem espaços)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Utilize %@ credenciais do site. Seu usuário é normalmente o seu email."; -"account.sections.guidance.footer.infrastructure.pia" = "Utilize %@ credenciais do site. Seu usuário é normalmente numérico com prefixo \"p\"."; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Encontre %@ credenciais na sessão \"Account > OpenVPN / IKEv2 Username\" do site."; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Utilize %@ credenciais do site. Seu usuário é normalmente o seu email."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Utilize %@ credenciais do site. Seu usuário é normalmente o seu email."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Encontre %@ credenciais no gerador de configuração OpenVPN do site."; -"account.sections.registration.footer" = "Registrar em %@ website."; -"account.cells.username.caption" = "Usuário"; -"account.cells.username.placeholder" = "usuário"; -"account.cells.password.caption" = "Senha"; -"account.cells.password.placeholder" = "senha secreta"; -"account.cells.open_guide.caption" = "Ver sua credenciais"; -"account.cells.signup.caption" = "Registrar com %@"; - -"endpoint.title" = "Endereço"; -"endpoint.sections.location_addresses.header" = "Endereços"; -"endpoint.sections.location_protocols.header" = "Protocolos"; -"endpoint.cells.address" = "Endereço"; -"endpoint.cells.protocol" = "Protocolo"; -"endpoint.cells.any_address.caption" = "Automático"; -"endpoint.cells.any_protocol.caption" = "Automático"; - -"network_settings.title" = "Configurações de rede"; -"network_settings.cells.add_dns_server.caption" = "Adicionar endereço"; -"network_settings.cells.add_dns_domain.caption" = "Adicionar domínio"; -"network_settings.cells.proxy_bypass.caption" = "Domínio ignorado"; -"network_settings.cells.add_proxy_bypass.caption" = "Adicionar domínio ignorado"; - -"configuration.title" = "Configuração"; -"configuration.sections.communication.header" = "Comunicação"; -"configuration.sections.reset.footer" = "Se você foi desconectado após mudar parâmetros de comunicação, toque para restaurar a configuração original."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Compressão"; -"configuration.sections.network.header" = "Rede"; -"configuration.sections.other.header" = "Outro"; -"configuration.cells.cipher.caption" = "Criptografada"; -"configuration.cells.digest.caption" = "Autenticação"; -"configuration.cells.digest.value.embedded" = "Agregado"; -"configuration.cells.compression_framing.caption" = "Framing"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "Algorítimo"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "Não suportado"; -"configuration.cells.reset_original.caption" = "Restaurar configuração"; -"configuration.cells.client.caption" = "Certificado do cliente"; -"configuration.cells.client.value.enabled" = "Verificado"; -"configuration.cells.client.value.disabled" = "Não verificado"; -"configuration.cells.tls_wrapping.caption" = "Wrapping"; -"configuration.cells.tls_wrapping.value.auth" = "Autenticação"; -"configuration.cells.tls_wrapping.value.crypt" = "Criptografia"; -"configuration.cells.eku.caption" = "Verificação extendida"; -"configuration.cells.keep_alive.caption" = "Manter ativo"; -"configuration.cells.keep_alive.value.seconds" = "%d segundos"; -"configuration.cells.renegotiation_seconds.caption" = "Renegociando"; -"configuration.cells.renegotiation_seconds.value.after" = "depois de %@"; -"configuration.cells.random_endpoint.caption" = "Destino randômico"; -"configuration.alerts.commit.message" = "Os novos parâmetros não serão efetivos até que se volte a ligar manualmente. As alterações às redes de confiança serão aplicadas de imediato."; -"configuration.alerts.commit.buttons.reconnect" = "Voltar a ligar agora"; -"configuration.alerts.commit.buttons.skip" = "Ignorar"; - -"trusted.columns.trust.title" = "Confiar"; -"trusted.ethernet.title" = "Confiar em ligações com fios"; -"trusted.ethernet.description" = "Assinale para confiar em qualquer ligação com cabo."; - -"preferences.title" = "Preferências"; -"preferences.sections.general.header" = "Geral"; -"preferences.cells.launches_on_login.caption" = "Iniciar ao iniciar a sessão"; -"preferences.cells.launches_on_login.footer" = "Assinale para executar automaticamente a aplicação ao arrancar ou com o início de sessão."; -"preferences.cells.confirm_quit.caption" = "Confirmar a saída"; -"preferences.cells.confirm_quit.footer" = "Assinale para apresentar um alerta de confirmação da saída."; - -"network_settings.gateway.title" = "Gateway padrão"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Servidores"; -"network_settings.dns.cells.domain.caption" = "Domínio"; -"network_settings.dns.cells.domains.title" = "Domínios"; -"network_settings.proxy.title" = "Proxy"; -"network_settings.proxy.cells.bypass_domains.title" = "Fazer um bypass aos domínios"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "Rota"; - -"debug_log.buttons.previous" = "Anterior"; -"debug_log.buttons.next" = "Próximo"; -"debug_log.buttons.copy" = "Copiar"; -"debug_log.alerts.empty_log.message" = "O log está vazio."; - -"shortcuts.add.title" = "Adicionar atalho"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "Celular"; -"shortcuts.add.cells.connect.caption" = "Conectar à"; -"shortcuts.add.cells.enable_vpn.caption" = "Ativar VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Desativar VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Confiar na Wi-Fi atual"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Não confiar na Wi-Fi atual"; -"shortcuts.add.cells.trust_cellular.caption" = "Confiar em rede celular"; -"shortcuts.add.cells.untrust_cellular.caption" = "Não confiar em rede celular"; -"shortcuts.add.alerts.no_profiles.message" = "Ainda não existe nenhum perfil para se conectar."; - -"shortcuts.edit.title" = "Configuração de atalhos"; -"shortcuts.edit.sections.all.header" = "Atalhos existentes"; -"shortcuts.edit.cells.add_shortcut.caption" = "Adicionar atalho"; - -"purchase.title" = "Comprar"; -"purchase.sections.products.footer" = "Todo produto é uma compra única. As compras do fornecedor não incluem uma assinatura VPN."; -"purchase.cells.full_version.extra_description" = "Todos os provedores (incluindo os futuros)\n%@"; -"purchase.cells.restore.title" = "Restaurar compras"; -"purchase.cells.restore.description" = "Se você comprou este aplicativo ou recurso no passado, pode restaurar suas compras e essa tela não será exibida novamente."; - -"donation.title" = "Doar"; -"donation.sections.one_time.header" = "Uma vez"; -"donation.sections.one_time.footer" = "Se você deseja mostrar gratidão pelo meu trabalho, aqui estão alguns valores do qual você pode contribuir.\n\nVocé só será cobrado uma única vez, ou doar mais vezes caso desejar."; -"donation.cells.loading.caption" = "Carregando doações"; -"donation.cells.purchasing.caption" = "Efetuando doação"; -"donation.alerts.purchase.success.title" = "Obrigado"; -"donation.alerts.purchase.success.message" = "Isso significa muito para mim! Espero que você continue usando e promovendo esse aplicativo."; -"donation.alerts.purchase.failure.message" = "Não foi possível realizar doação. %@"; - -"about.title" = "Sobre"; -"about.sections.web.header" = "Web"; -"about.sections.share.header" = "Compartilhar"; -"about.cells.credits.caption" = "Créditos"; -"about.cells.website.caption" = "Home page"; -"about.cells.faq.caption" = "FAQ"; -"about.cells.disclaimer.caption" = "Disclaimer"; -"about.cells.privacy_policy.caption" = "Política de privacidade"; -"about.cells.share_twitter.caption" = "Tweet sobre isso!"; -"about.cells.share_generic.caption" = "Convide um amigo"; - -"version.title" = "Versão"; -"version.labels.intro" = "Passepartout e TunnelKit são desenvolvidos e mantidos por Davide De Rosa (keeshux).\n\nO código de fonte está disponível no GitHub sobre a licença GPLv3, você pode encontrar links na home page.\n\nPassepartout não é um cliente oficial e não possui nenhuma ligação com a OpenVPN Inc."; - -"credits.title" = "Créditos"; -"credits.sections.licenses.header" = "Licenças"; -"credits.sections.notices.header" = "Notices"; -"credits.sections.translations.header" = "Traduções"; - -"label.license.error" = "Não foi possível realizar o download da licença."; - -// iOS only - -"imported_hosts.title" = "Hosts importados"; - -// macOS only - -"menu.show.title" = "Mostrar"; -"menu.switch_profile.title" = "Perfil ativo"; -"menu.active_profile.title.none" = "Sem perfil ativo"; -"menu.active_profile.items.customize.title" = "Personalizar..."; -"menu.active_profile.messages.missing_credentials" = "Não há uma conta configurada"; -"menu.organizer.title" = "Organizador"; -"menu.preferences.title" = "Preferências"; -"menu.support.title" = "Apoio"; -"menu.quit.title" = "Sair %@"; -"menu.quit.messages.confirm" = "A VPN, se ativa, ainda vai ser executada em segundo plano. Quer sair?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/ru.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/ru.lproj/Core.strings deleted file mode 100644 index d7eac536..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/ru.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "Ок"; -"global.cancel" = "Отменить"; -"global.next" = "Далее"; -"global.close" = "Закрыть"; -"global.host.title_input.message" = "Разрешены буквы латиницы, дэш \"-\", нижнее подчеркивание \"_\" и точка \".\"."; -"global.host.title_input.placeholder" = "Мой профиль"; -"global.email_not_configured" = "E-mail аккаунт не создан."; -"global.values.default" = "По умолчанию"; - -"global.captions.address" = "Адрес"; -"global.captions.port" = "порт"; -"global.captions.protocol" = "Протокол"; - -"global.values.enabled" = "Включен"; -"global.values.disabled" = "Выключен"; -"global.values.none" = "Нет"; -"global.values.automatic" = "Автоматически"; -"global.values.manual" = "Вручную"; - -"reddit.title" = "Reddit"; -"reddit.message" = "А Вы знали, что Passepartout имеет свой сабреддит? Подписывайтесь для получения обновлений, обсуждения проблем, функций, новых платформ или чего угодно.\n\nЭто также отличный способ показать поддержку проекта."; -"reddit.buttons.subscribe" = "Подписаться сейчас!"; -"reddit.buttons.remind" = "Напомнить позже"; -"reddit.buttons.never" = "Больше не спрашивать"; - -"vpn.connecting" = "Подключается"; -"vpn.active" = "Активен"; -"vpn.disconnecting" = "Отключается"; -"vpn.inactive" = "Не активен"; -"vpn.disabled" = "Отключен"; -"vpn.unused" = "Выкл"; - -"vpn.errors.timeout" = "Тайм-аут"; -"vpn.errors.dns" = "Ошибка DNS"; -"vpn.errors.auth" = "Ошибка аутентификации"; -"vpn.errors.tls" = "Ошибка TSL"; -"vpn.errors.encryption" = "Ошибка расшифровки"; -"vpn.errors.compression" = "Сжатие не поддерживается"; -"vpn.errors.network" = "Изменение сети"; -"vpn.errors.routing" = "Отсутствует маршрутизация"; -"vpn.errors.gateway" = "Нет шлюза"; -"vpn.errors.shutdown" = "Сервер выключен"; - -"parsed_file.alerts.malformed.message" = "Файл конфигурации содержит неверную опцию (%@)."; -"parsed_file.alerts.missing.message" = "Файл конфигурации не содержит необходимую опцию (%@)."; -"parsed_file.alerts.unsupported.message" = "Файл конфигурации содержит неподдерживаемую опцию (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "Файл конфигурации верный, но возможно содержит неподдерживаемую опцию (%@).\n\nСоединение может прерваться - зависит от настроек сервера."; -"parsed_file.alerts.encryption_passphrase.message" = "Пожалуйста, введите кодовую фразу шифрования"; -"parsed_file.alerts.decryption.message" = "Конфигурация содержит зашифрованный приватный ключ, он не может быть расшифрован. Перепроверьте кодовую фразу."; -"parsed_file.alerts.parsing.message" = "Не получается разобрать предоставленный файл конфигурации (%@)."; -"parsed_file.alerts.buttons.report" = "Сообщить о проблеме"; - -"network_choice.client" = "Читать .ovpn"; -"network_choice.server" = "Вытащить с сервера"; - -"issue_reporter.title" = "Сообщить о проблеме"; -"issue_reporter.message" = "Журнал отладки Вашего последнего соединения необходим для разрешения проблем подключения, и является полностью анонимным.\n\n .ovpn файл, если есть, прикреплён без каких-либо конфиденциальных данных .\n\nПожалуйста, перепроверьте прикреплённые файлы, если не уверены."; -"issue_reporter.buttons.accept" = "Я понимаю"; - -"translations.title" = "Переводы"; - -"share.message" = "Passepartout - это простой в использовании OpenVPN клиент для iOS и macOS, с открытым исходным кодом"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Провайдер"; -"organizer.menus.provider.unavailable" = "Не осталось провайдеров"; -"organizer.menus.host" = "Хост"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "Приходите посмотреть, как я делаю Passepartout в прямом эфире на Twitch, присоединяйтесь к чату, чтобы общаться и вносить свой вклад!"; -"organizer.sections.providers.header" = "Провайдеры"; -"organizer.sections.providers.footer" = "Здесь Вы найдёте несколько провайдеров с уже созданными профилями."; -"organizer.sections.hosts.header" = "Хосты|Hosts"; -"organizer.sections.hosts.footer" = "Импорт хостов из .ovpn файлов"; -"organizer.sections.siri.header" = "Сири"; -"organizer.sections.siri.footer" = "Получить помощь Сири, чтобы ускорить частые действия с приложением."; -"organizer.sections.support.header" = "Поддержка"; -"organizer.sections.feedback.header" = "Отзыв"; -"organizer.cells.follow_twitch.caption" = "Смотрите Паспарту на Twitch"; -"organizer.cells.profile.value.current" = "Используется"; -"organizer.cells.siri_shortcuts.caption" = "Управлять коммандами"; -"organizer.cells.join_community.caption" = "Вступить в сообщество"; -"organizer.cells.write_review.caption" = "Написать отзыв"; -"organizer.cells.donate.caption" = "Сделать пожертвование"; -"organizer.cells.github_sponsors.caption" = "Поддержите меня на GitHub"; -"organizer.cells.translate.caption" = "Помощь с переводом"; -"organizer.cells.about.caption" = "Об %@"; -"organizer.cells.uninstall.caption" = "Удалить VPN конфигурацию"; -"organizer.cells.add_provider.caption" = "Добавить нового провайдера"; -"organizer.cells.add_host.caption" = "Добавить из файлов"; -"organizer.cells.import_host.caption" = "Добавить из импортированных"; -"organizer.alerts.exhausted_providers.message" = "Вы создали профили для всех доступных провайдеров."; -"organizer.alerts.add_host.message" = "Откройте ссылку на .ovpn файл конфигурации через Safari, Почту или другое приложение для добавление хост профиля.\n\nВы также можете импортировать .ovpn файл через общие файлы iTunes."; -"organizer.alerts.cannot_donate.message" = "На этом усторйстве не выбран способ платежа."; -"organizer.alerts.delete_vpn_profile.message" = "Вы действительно хотите убрать VPN конфигурацию из настроек устройства? Это может исправить несколько VPN ошибок, но не изменит установки приложения."; -"organizer.alerts.remove_profile.title" = "Удалить профиль"; -"organizer.alerts.remove_profile.message" = "Вы точно хотите удалить профиль %@?"; -"organizer.alerts.open_host_file.title" = "Выберите файл .ovpn"; - -"wizards.provider.cells.update_list.caption" = "Обновить список"; -"wizards.provider.alerts.unavailable.message" = "Не удалось загрузить инфраструктуру провайдера, повторите попытку позже."; -"wizards.host.sections.existing.header" = "Существующие профили"; -"wizards.host.cells.title_input.caption" = "Название"; -"wizards.host.alerts.existing.message" = "Хост профиль с этим названием уже существует. Заменить?"; - -"service.welcome.message" = "Добро пожаловать в Passepartout!\n\nИспользуйте организатор для добавления нового профиля."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "Соединение будет установлено при необходимости."; -"service.sections.status.header" = "Соединение"; -"service.sections.configuration.header" = "Конфигурация"; -"service.sections.provider_infrastructure.footer" = "Последнее обновление %@."; -"service.sections.vpn_survives_sleep.footer" = "Отключите для уменьшения расхода заряда аккумулятора, может привести к временным замедлениям в связи с повторным подключением после \"пробуждения\"."; -"service.sections.vpn_resolves_hostname.footer" = "Предпочтительно в большинстве сетей и необходимо в некоторых IPv6 сетях. Отключите если  DNS заблокирован, или для увеличения скорости в случае медленных ответов DNS."; -"service.sections.trusted.header" = "Доверенные сети"; -"service.sections.trusted.footer" = "При подключении к доверенным сетям VPN обычно выключается, и остаётся отключенным. Отключите эту опцию чтобы оставлять VPN подключенным."; -"service.sections.diagnostics.header" = "Диагностика"; -"service.sections.diagnostics.footer" = "Маскировка включится после повторного подключения. Информация о сети - это названия хост профилей, IP адрес, маршрутизация и SSID. Данные для входа и приватные ключи не собираются."; -"service.cells.use_profile.caption" = "Использовать это профиль."; -"service.cells.vpn_service.caption" = "Включен"; -"service.cells.vpn.turn_on.caption" = "Включить VPN"; -"service.cells.vpn.turn_off.caption" = "Отключить VPN"; -"service.cells.connection_status.caption" = "Статус"; -"service.cells.host.parameters.caption" = "Параметры"; -"service.cells.provider.pool.caption" = "Местоположение"; -"service.cells.provider.preset.caption" = "Пресет"; -"service.cells.provider.refresh.caption" = "Обновить инфраструктуру"; -"service.cells.category.caption" = "Категория"; -"service.cells.addresses.caption" = "Адреса"; -"service.cells.only_shows_favorites.caption" = "Показывать только места из избранного"; -"service.cells.vpn_survives_sleep.caption" = "Оставлять включенным во время сна"; -"service.cells.vpn_resolves_hostname.caption" = "Разрешить имя хоста сервера"; -"service.cells.trusted_add_wifi.caption" = "Добавить Wi-Fi"; -"service.cells.trusted_mobile.caption" = "Мобильная сеть"; -"service.cells.trusted_policy.caption" = "Дов. сеть отключает VPN"; -"service.cells.test_connectivity.caption" = "Проверить подключение"; -"service.cells.data_count.caption" = "Переданная информация"; -"service.cells.data_count.none" = "Недоступно"; -"service.cells.server_configuration.caption" = "Конфигурация сервера"; -"service.cells.server_network.caption" = "Сеть сервера"; -"service.cells.debug_log.caption" = "Журнал отладки"; -"service.cells.masks_private_data.caption" = "Маскировать информацию сети"; -"service.cells.reconnect.caption" = "Переподключиться"; -"service.cells.report_issue.caption" = "Сообщить о проблеме подкл."; - -"service.alerts.rename.title" = "Переименовать профиль"; -"service.alerts.credentials_needed.message" = "Сначала нужно ввести данные аккаунта."; -"service.alerts.reconnect_vpn.message" = "Хотите заново подключиться к VPN?"; -"service.alerts.trusted.no_network.message" = "Вы не подключены к Wi-Fi."; -"service.alerts.trusted.will_disconnect_trusted.message" = "При доверии этой сети VPN может быть отключен. Продолжить?"; -"service.alerts.trusted.will_disconnect_policy.message" = "При изменении установок доверия VPN может быть отключен. Продолжить?"; -"service.alerts.test_connectivity.title" = "Связь"; -"service.alerts.test_connectivity.messages.success" = "Ваше устройство подключено к интернету!"; -"service.alerts.test_connectivity.messages.failure" = "Ваше устройство не подключено к интернету, пожалйста проверьте установки Вашего профиля."; -"service.alerts.configuration.disconnected" = "Конфигурация недоступна, проверьте подключение к VPN."; -"service.alerts.masks_private_data.messages.must_reconnect" = "Для безопасного сброса журнала отладки и изменения маскировки информации сети Вы должны заново подключиться к VPN."; -"service.alerts.buttons.reconnect" = "Переподключить"; -"service.alerts.download.title" = "Необходимо скачивание"; -"service.alerts.download.message" = "%@ необходимы дополнительные файлы конфигурации.\n\nПодтвердите для скачивания."; -"service.alerts.download.failed" = "Не удалось скачать файлы конфигурации.%@"; -"service.alerts.download.hud.extracting" = "Извлечение файлов, пожалуста подождите..."; -"service.alerts.location.message.denied" = "Вам нужно разрешить использование геопозиции для добавления этой Wi-Fi сети в доверенные. Перейдите в настройки iOS, и измените разрешения геолокации для Passepartout."; -"service.alerts.location.button.settings" = "Настройки"; - -"provider.pool.sections.empty_favorites.footer" = "Свайп в лево на локации, чтобы добавить или убрать из избранного."; -"provider.pool.actions.favorite" = "Добавить в избранное"; -"provider.pool.actions.unfavorite" = "Убрать из избранного"; - -"provider.preset.cells.tech_details.caption" = "Техническая информация"; - -"account.title" = "Аккаунт"; -"account.sections.credentials.header" = "Данные для входа"; -"account.sections.guidance.footer.infrastructure.default.web" = "Используйте Ваши данные для входа с веб-сайта %@."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Используйте свои учетные данные %@ service, которые могут отличаться от учетных данных веб-сайта."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Используйте Ваши данные для входа с веб-сайта %@. Ваш логин обычно числовой с (без пробелов)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Используйте данные для входа на %@ веб-сайт. Ваш логин обычно Ваш e-mail."; -"account.sections.guidance.footer.infrastructure.pia" = "Используйте Ваши данные для входа с веб-сайта %@. Ваш логин обычно числовой с приставкой \"p\"."; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Найдите Ваши данные для входа %@ \"Account > OpenVPN / IKEv2 Username\" секции веб-сайта."; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Используйте данные для входа на %@ веб-сайт. Ваш логин обычно Ваш e-mail."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Используйте данные для входа на %@ веб-сайт. Ваш логин обычно Ваш e-mail."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Найдите Ваши данные для входа %@ в OpenVPN Config Generator на веб-сайте."; -"account.sections.registration.footer" = "Создайте аккаунт на %@ веб-сайте."; -"account.cells.username.caption" = "Логин"; -"account.cells.username.placeholder" = "логин"; -"account.cells.password.caption" = "Пароль"; -"account.cells.password.placeholder" = "пароль"; -"account.cells.open_guide.caption" = "Проверьте Ваши данные"; -"account.cells.signup.caption" = "Зарегистрируйтесь с %@"; - -"endpoint.title" = "Конечная точка"; -"endpoint.sections.location_addresses.header" = "Адреса"; -"endpoint.sections.location_protocols.header" = "Протоколы"; -"endpoint.cells.address" = "Адрес"; -"endpoint.cells.protocol" = "Протокол"; -"endpoint.cells.any_address.caption" = "Автоматически"; -"endpoint.cells.any_protocol.caption" = "Автоматически"; - -"network_settings.title" = "Сетевые настройки"; -"network_settings.cells.add_dns_server.caption" = "Добавить адрес"; -"network_settings.cells.add_dns_domain.caption" = "Добавить домен поиска"; -"network_settings.cells.proxy_bypass.caption" = "Обход домена"; -"network_settings.cells.add_proxy_bypass.caption" = "Добавить обходной домен"; - -"configuration.title" = "Конфигурация"; -"configuration.sections.communication.header" = "Связь"; -"configuration.sections.reset.footer" = "Если после изменения параметров связи у Вас разорвалось соединение, нажмите, чтобы вернуться к исходной конфигурации."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Компресия"; -"configuration.sections.network.header" = "Сеть"; -"configuration.sections.other.header" = "Другое"; -"configuration.cells.cipher.caption" = "Шифруем"; -"configuration.cells.digest.caption" = "Аутентификация"; -"configuration.cells.digest.value.embedded" = "Внедрена"; -"configuration.cells.compression_framing.caption" = "Фрейминг"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "Алгоритм"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "Неподдерживаемое"; -"configuration.cells.reset_original.caption" = "Сброс конфигурации"; -"configuration.cells.client.caption" = "Сертификат клиента"; -"configuration.cells.client.value.enabled" = "Проверено"; -"configuration.cells.client.value.disabled" = "Не проверено"; -"configuration.cells.tls_wrapping.caption" = "Упаковываем"; -"configuration.cells.tls_wrapping.value.auth" = "Аутентификация"; -"configuration.cells.tls_wrapping.value.crypt" = "Шифрование"; -"configuration.cells.eku.caption" = "Расширенная проверка"; -"configuration.cells.keep_alive.caption" = "Поддерживаем"; -"configuration.cells.keep_alive.value.seconds" = "%d секунд"; -"configuration.cells.renegotiation_seconds.caption" = "Перезаключение"; -"configuration.cells.renegotiation_seconds.value.after" = "после %@"; -"configuration.cells.random_endpoint.caption" = "Рандомная конечная точка"; -"configuration.alerts.commit.message" = "Новые параметры не вступят в силу до ручного переподключения. Изменения в доверенных сетях вступят в силу сразу."; -"configuration.alerts.commit.buttons.reconnect" = "Переподключиться"; -"configuration.alerts.commit.buttons.skip" = "Пропустить"; - -"trusted.columns.trust.title" = "Доверенные"; -"trusted.ethernet.title" = "Доверенные проводные подключения"; -"trusted.ethernet.description" = "Включите, чтобы добавить в доверенные проводное подключение."; - -"preferences.title" = "Настройки"; -"preferences.sections.general.header" = "Общие"; -"preferences.cells.launches_on_login.caption" = "Запускать при входе"; -"preferences.cells.launches_on_login.footer" = "Включите, чтобы приложение автоматически запускалось при загрузке или входе."; -"preferences.cells.confirm_quit.caption" = "Подтверждать выход"; -"preferences.cells.confirm_quit.footer" = "Включите, чтобы выход надо было подтверждать."; - -"network_settings.gateway.title" = "Шлюз по умолчанию"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Серверы"; -"network_settings.dns.cells.domain.caption" = "Домен"; -"network_settings.dns.cells.domains.title" = "Домены"; -"network_settings.proxy.title" = "Прокси"; -"network_settings.proxy.cells.bypass_domains.title" = "Обходные домены"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "байты"; - -"server_network.cells.route.caption" = "Маршрут"; - -"debug_log.buttons.previous" = "Предыдущий"; -"debug_log.buttons.next" = "Следующий"; -"debug_log.buttons.copy" = "Копировать"; -"debug_log.alerts.empty_log.message" = "Журнал отладки пуст."; - -"shortcuts.add.title" = "Создать команду"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "Мобильная сеть"; -"shortcuts.add.cells.connect.caption" = "Подключиться к"; -"shortcuts.add.cells.enable_vpn.caption" = "Включи VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Выключи VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Доверять текущему Wi-Fi"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Не доверять текущему Wi-Fi"; -"shortcuts.add.cells.trust_cellular.caption" = "Доверять мобильной сети"; -"shortcuts.add.cells.untrust_cellular.caption" = "Не доверять мобильной сети"; -"shortcuts.add.alerts.no_profiles.message" = "Нет профиля для подключения."; - -"shortcuts.edit.title" = "Управлять командами"; -"shortcuts.edit.sections.all.header" = "Существующие команды"; -"shortcuts.edit.cells.add_shortcut.caption" = "Создать команду"; - -"purchase.title" = "Покупка"; -"purchase.sections.products.footer" = "Каждый продукт является разовой покупкой. Покупка провайдера не включает подписку на VPN."; -"purchase.cells.full_version.extra_description" = "Все провайдеры (включая добавленных в будущем)\n%@"; -"purchase.cells.restore.title" = "Восстановить покупки"; -"purchase.cells.restore.description" = "Если Вы купили это приложение или совершили встроенные покупки в прошлом, вы можете восстановить ваши покупки, и этот баннер больше не появится."; - -"donation.title" = "Пожертвовать"; -"donation.sections.one_time.header" = "Один раз"; -"donation.sections.one_time.footer" = "Если Вы хотите поблагодарить мою бесплатную работу, здесь есть несколько сумм, которые Вы можете пожертвовать прямо сейчас.\n\nСумма будет списана только один раз, а Вы можете пожертвовать несколько раз."; -"donation.cells.loading.caption" = "Загружаем пожертвования"; -"donation.cells.purchasing.caption" = "Исполняется"; -"donation.alerts.purchase.success.title" = "Спасибо"; -"donation.alerts.purchase.success.message" = "Это значит многое для меня, и, я надеюсь, Вы продолжить использовать и рассказывать об этом приложении."; -"donation.alerts.purchase.failure.message" = "Не получается совершить пожертвование. %@"; - -"about.title" = "О нас"; -"about.sections.web.header" = "Веб"; -"about.sections.share.header" = "Поделиться"; -"about.cells.credits.caption" = "Благодарности"; -"about.cells.website.caption" = "Домашняя страница"; -"about.cells.faq.caption" = "FAQ"; -"about.cells.disclaimer.caption" = "Предупреждение"; -"about.cells.privacy_policy.caption" = "Политика конфиденциальности"; -"about.cells.share_twitter.caption" = "Твитнуть о нас!"; -"about.cells.share_generic.caption" = "Пригласить друга"; - -"version.title" = "Версия"; -"version.labels.intro" = "Passepartout и TunnelKit написаны и установлены Davide De Rosa (keeshux).\n\nИсходные коды для Passepartout и TunnelKit публично доступны на GitHub под GPLv3, вы можете найти ссылки на домашней странице.\n\nPassepartout является неофициальным клиентом, и никаким образом не связан с OpenVPN Inc."; - -"credits.title" = "Благодарность"; -"credits.sections.licenses.header" = "Лицензии"; -"credits.sections.notices.header" = "Упоминания"; -"credits.sections.translations.header" = "Переводы"; - -"label.license.error" = "Не получается загрузить полную лицензию."; - -// iOS only - -"imported_hosts.title" = "Импортированные хост профили"; - -// macOS only - -"menu.show.title" = "Показать"; -"menu.switch_profile.title" = "Активный профиль"; -"menu.active_profile.title.none" = "Нет активных профилей"; -"menu.active_profile.items.customize.title" = "Настроить..."; -"menu.active_profile.messages.missing_credentials" = "Нет настроенных аккаунтов"; -"menu.organizer.title" = "Организатор"; -"menu.preferences.title" = "Настройки"; -"menu.support.title" = "Поддержка"; -"menu.quit.title" = "Выйти из %@"; -"menu.quit.messages.confirm" = "Если включить VPN, он всё равно будет работать в фоновом режиме. Вы точно хотите выйти?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/sv.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/sv.lproj/Core.strings deleted file mode 100644 index c42fc1d5..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/sv.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "OK"; -"global.cancel" = "Avbryt"; -"global.next" = "Nästa"; -"global.close" = "Stäng"; -"global.host.title_input.message" = "Godtagbara tecken är alfanumeriska plus bindestreck \" - \", understrykning \" _ \"och punkt \". \"."; -"global.host.title_input.placeholder" = "Min profil"; -"global.email_not_configured" = "Inget e-postkonto är konfigurerat."; -"global.values.default" = "Default"; - -"global.captions.address" = "Adress"; -"global.captions.port" = "Port"; -"global.captions.protocol" = "Protokoll"; - -"global.values.enabled" = "Aktiverad"; -"global.values.disabled" = "Inaktiverad"; -"global.values.none" = "Ingen"; -"global.values.automatic" = "Automatiskt"; -"global.values.manual" = "Manuellt"; - -"reddit.title" = "Reddit"; -"reddit.message" = "Visste du att Passepartout har en subreddit? Prenumerera på uppdateringar eller diskutera problem, funktioner, nya plattformar eller vad du vill. \n\nDet är också ett bra sätt att visa dig bryr dig om detta projekt."; -"reddit.buttons.subscribe" = "Prenumerera nu!"; -"reddit.buttons.remind" = "Påminn mig senare"; -"reddit.buttons.never" = "Fråga inte igen"; - -"vpn.connecting" = "Anslutning"; -"vpn.active" = "Aktiv"; -"vpn.disconnecting" = "Koppla från"; -"vpn.inactive" = "Inaktiv"; -"vpn.disabled" = "Inaktiverad"; -"vpn.unused" = "Av"; - -"vpn.errors.timeout" = "Timeout"; -"vpn.errors.dns" = "DNS misslyckades"; -"vpn.errors.auth" = "Auth failed"; -"vpn.errors.tls" = "TLS misslyckades"; -"vpn.errors.encryption" = "Kryptering misslyckades"; -"vpn.errors.compression" = "Komprimering utan stöd"; -"vpn.errors.network" = "Nätverk ändrat"; -"vpn.errors.routing" = "Saknad routing"; -"vpn.errors.gateway" = "Ingen gateway"; -"vpn.errors.shutdown" = "Servern stängs av"; - -"parsed_file.alerts.malformed.message" = "Konfigurationsfilen innehåller ett felaktigt val (%@)."; -"parsed_file.alerts.missing.message" = "Konfigurationsfilen saknar ett obligatoriskt val (%@)."; -"parsed_file.alerts.unsupported.message" = "Konfigurationsfilen innehåller ett stöd som inte stöds (%@)."; -"parsed_file.alerts.potentially_unsupported.message" = "Konfigurationsfilen är korrekt men innehåller ett eventuellt stöd som inte stöds (%@). \n\nConnectivity kan bryta beroende på serverinställningar."; -"parsed_file.alerts.encryption_passphrase.message" = "Var god ange krypteringslösenfrasen."; -"parsed_file.alerts.decryption.message" = "Konfigurationen innehåller en krypterad privat nyckel och den kan inte dekrypteras. Kontrollera din inmatade lösenfras."; -"parsed_file.alerts.parsing.message" = "Det gick inte att analysera den angivna konfigurationsfilen (%@)."; -"parsed_file.alerts.buttons.report" = "Rapportera ett problem"; - -"network_choice.client" = "Läs .ovpn"; -"network_choice.server" = "Dra från server"; - -"issue_reporter.title" = "Rapportera problem"; -"issue_reporter.message" = "Felsökningsloggen för dina senaste anslutningar är avgörande för att lösa dina anslutningsproblem och är helt anonym. \n\nHanteringsfilen .ovpn är eventuellt bifogad av någon känslig data. \n\nPresentera dubbelkolla e-postbilagorna om du är osäker. "; -"issue_reporter.buttons.accept" = "Jag förstår"; - -"translations.title" = "Översättningar"; - -"share.message" = "Passepartout är en användarvänlig öppen källkod OpenVPN-klient för iOS och macOS"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "Leverantör"; -"organizer.menus.provider.unavailable" = "Inga leverantörer kvar"; -"organizer.menus.host" = "Värd"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "Kom och se mig göra Passepartout live på Twitch, gå med i chatten för att interagera och bidra!"; -"organizer.sections.providers.header" = "Leverantörer"; -"organizer.sections.providers.footer" = "Här hittar du några leverantörer med förinställda konfigurationsprofiler."; -"organizer.sections.hosts.header" = "Värdar"; -"organizer.sections.hosts.footer" = "Importera värdar från .ovpn konfigurationsfiler."; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "Få hjälp från Siri för att påskynda dina vanligaste interaktioner med appen."; -"organizer.sections.support.header" = "Support"; -"organizer.sections.feedback.header" = "Feedback"; -"organizer.cells.follow_twitch.caption" = "Se Passepartout on Twitch"; -"organizer.cells.profile.value.current" = "Under användning"; -"organizer.cells.siri_shortcuts.caption" = "Hantera genvägar"; -"organizer.cells.join_community.caption" = "Gå med i communityn"; -"organizer.cells.write_review.caption" = "Skriv en recension"; -"organizer.cells.donate.caption" = "Gör en donation"; -"organizer.cells.github_sponsors.caption" = "Stötta mig på GitHub"; -"organizer.cells.translate.caption" = "Erbjuda att översätta"; -"organizer.cells.about.caption" = "Om %@"; -"organizer.cells.uninstall.caption" = "Ta bort VPN-konfiguration"; -"organizer.cells.add_provider.caption" = "Lägg till ny leverantör"; -"organizer.cells.add_host.caption" = "Lägg till från Filer"; -"organizer.cells.import_host.caption" = "Lägg till från importerad"; -"organizer.alerts.exhausted_providers.message" = "Du har skapat profiler för alla tillgängliga leverantörer."; -"organizer.alerts.add_host.message" = "Öppna en URL till en .ovpn konfigurationsfil från Safari, Mail eller en annan app för att skapa en värdprofil. \n\nDu kan också importera en .ovpn med iTunes Fildelning."; -"organizer.alerts.cannot_donate.message" = "Det finns ingen betalningsmetod konfigurerad på den här enheten."; -"organizer.alerts.delete_vpn_profile.message" = "Vill du verkligen radera VPN-konfigurationen från enhetens inställningar? Detta kan fixa några trasiga VPN tillstånd och påverkar inte dina leverantörs- och värdprofiler."; -"organizer.alerts.remove_profile.title" = "Ta bort profil"; -"organizer.alerts.remove_profile.message" = "Är det säkert att du vill ta bort profilen %@?"; -"organizer.alerts.open_host_file.title" = "Välj en .ovpn-fil"; - -"wizards.provider.cells.update_list.caption" = "Uppdatera listan"; -"wizards.provider.alerts.unavailable.message" = "Det gick inte att ladda ner leverantörens infrastruktur, försök igen senare."; -"wizards.host.sections.existing.header" = "Befintliga profiler"; -"wizards.host.cells.title_input.caption" = "Namn"; -"wizards.host.alerts.existing.message" = "En värdprofil med samma namn finns redan. Byt ut det?"; - -"service.welcome.message" = "Välkommen till Passepartout! \n\nAnvänd arrangören för att lägga till en ny profil."; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "Anslutningen kommer att upprättas vid behov."; -"service.sections.status.header" = "Koppling"; -"service.sections.configuration.header" = "Konfiguration"; -"service.sections.provider_infrastructure.footer" = "Senast uppdaterad på %@."; -"service.sections.vpn_survives_sleep.footer" = "Inaktivera för att förbättra batterianvändningen, på bekostnad av tillfälliga avmattningar på grund av återuppkoppling."; -"service.sections.vpn_resolves_hostname.footer" = "Föredragna i de flesta nätverk och krävs i vissa IPv6-nätverk. Inaktivera var DNS blockeras eller för att påskynda förhandlingar när DNS är långsamt att svara."; -"service.sections.trusted.header" = "Tillförlitliga nätverk"; -"service.sections.trusted.footer" = "När du advänder ett betrott nätverk, VPN:et stängs normalt och hålls bortkopplat. Avaktivera detta alternativet för att inte genomdriva sådant beteende."; -"service.sections.diagnostics.header" = "Diagnostics"; -"service.sections.diagnostics.footer" = "Masking status kommer att fungera efter återanslutning. Nätverksdata är värdnamn, IP-adresser, routing, SSID. Referenser och privata nycklar loggas inte oavsett."; -"service.cells.use_profile.caption" = "Använd den här profilen"; -"service.cells.vpn_service.caption" = "Aktiverad"; -"service.cells.vpn.turn_on.caption" = "Aktivera VPN"; -"service.cells.vpn.turn_off.caption" = "Inaktivera VPN"; -"service.cells.connection_status.caption" = "Status"; -"service.cells.host.parameters.caption" = "Parametrar"; -"service.cells.provider.pool.caption" = "Plats"; -"service.cells.provider.preset.caption" = "Förinställt"; -"service.cells.provider.refresh.caption" = "Uppdatera infrastruktur"; -"service.cells.category.caption" = "Kategori"; -"service.cells.addresses.caption" = "Adresser"; -"service.cells.only_shows_favorites.caption" = "Visa endast favoritplatser"; -"service.cells.vpn_survives_sleep.caption" = "Håll dig levande i sömnen"; -"service.cells.vpn_resolves_hostname.caption" = "Lösa server värdnamn"; -"service.cells.trusted_add_wifi.caption" = "Lägg till Wi-Fi"; -"service.cells.trusted_mobile.caption" = "Mobilt nätverk"; -"service.cells.trusted_policy.caption" = "Förtroende inaktiverar VPN"; -"service.cells.test_connectivity.caption" = "Testanslutning"; -"service.cells.data_count.caption" = "Utbyttad data"; -"service.cells.data_count.none" = "Otillgänglig"; -"service.cells.server_configuration.caption" = "Server konfiguration"; -"service.cells.server_network.caption" = "Server nätverk"; -"service.cells.debug_log.caption" = "Debug log"; -"service.cells.masks_private_data.caption" = "Mask nätverksdata"; -"service.cells.reconnect.caption" = "Återanslut"; -"service.cells.report_issue.caption" = "Rapportera anslutningsproblem"; - -"service.alerts.rename.title" = "Byt namn på profil"; -"service.alerts.credentials_needed.message" = "Du måste ange kontouppgifterna först."; -"service.alerts.reconnect_vpn.message" = "Vill du återansluta till VPN?"; -"service.alerts.trusted.no_network.message" = "Du är inte ansluten till något Wi-Fi-nätverk."; -"service.alerts.trusted.will_disconnect_trusted.message" = "Genom att lita på detta nätverk kan VPN kopplas bort. Fortsätt?"; -"service.alerts.trusted.will_disconnect_policy.message" = "Genom att byta tillitspolicy kan VPN kopplas bort. Fortsätt?"; -"service.alerts.test_connectivity.title" = "Anslutningar"; -"service.alerts.test_connectivity.messages.success" = "Din enhet är ansluten till Internet!"; -"service.alerts.test_connectivity.messages.failure" = "Din enhet har ingen Internetanslutning, var god granska dina profilparametrar."; -"service.alerts.configuration.disconnected" = "Konfiguration otillgänglig. Se till att du är kopplad till VPN:et."; -"service.alerts.masks_private_data.messages.must_reconnect" = "För att säkert återställa den aktuella felsökningsloggen och tillämpa den nya maskeringspreferensen måste du återansluta till VPN nu."; -"service.alerts.buttons.reconnect" = "Reconnect"; -"service.alerts.download.title" = "Ladda ner krävs"; -"service.alerts.download.message" = "%@ kräver nedladdning av ytterligare konfigurationsfiler. \n\nKontrollera för att starta nedladdningen."; -"service.alerts.download.failed" = "Misslyckades med att ladda ner konfigurationsfiler. %@"; -"service.alerts.download.hud.extracting" = "Extraherar filer, var så tålmodig ..."; -"service.alerts.location.message.denied" = "Du måste tillåta lägetillgång för att förlita detta nätverk. Öppna iOS inställningar och se över tillstånd för Passepartout."; -"service.alerts.location.button.settings" = "Inställningar"; - -"provider.pool.sections.empty_favorites.footer" = "Dra åt vänster på en plats för att lägga till eller ta bort den från favoriter."; -"provider.pool.actions.favorite" = "Favorit"; -"provider.pool.actions.unfavorite" = "Inte favorit"; - -"provider.preset.cells.tech_details.caption" = "Tekniska detaljer"; - -"account.title" = "Konto"; -"account.sections.credentials.header" = "Referenser"; -"account.sections.guidance.footer.infrastructure.default.web" = "Använd dina %@ webbplatsuppgifter."; -"account.sections.guidance.footer.infrastructure.default.specific" = "Använd dina %@ service-referenser, som kan skilja sig från webbplatsens referenser."; -"account.sections.guidance.footer.infrastructure.mullvad" = "Använd dina %@ webbplatsuppgifter. Ditt användarnamn är vanligtvis numeriskt (utan utrymmen)."; -"account.sections.guidance.footer.infrastructure.nordvpn" = "Använd dina %@ webbplatsuppgifter. Ditt användarnamn är vanligtvis ditt e-postmeddelande."; -"account.sections.guidance.footer.infrastructure.pia" = "Använd dina %@ webbplatsuppgifter. Ditt användarnamn är vanligtvis numeriskt med ett prefix för \" p \"."; -"account.sections.guidance.footer.infrastructure.protonvpn" = "Hitta dina %@ credentials i avsnittet \" Konto> OpenVPN / IKEv2 Användarnamn \"på webbplatsen."; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "Använd dina %@ webbplatsuppgifter. Ditt användarnamn är vanligtvis ditt e-postmeddelande."; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "Använd dina %@ webbplatsuppgifter. Ditt användarnamn är vanligtvis ditt e-postmeddelande."; -"account.sections.guidance.footer.infrastructure.windscribe" = "Hitta din %@ credentials i OpenVPN Config Generator på webbplatsen."; -"account.sections.registration.footer" = "Hämta ett konto på %@ webbplatsen."; -"account.cells.username.caption" = "Användarnamn"; -"account.cells.username.placeholder" = "användarnamn"; -"account.cells.password.caption" = "Lösenord"; -"account.cells.password.placeholder" = "hemlighet"; -"account.cells.open_guide.caption" = "Visa dina uppgifter"; -"account.cells.signup.caption" = "Registrera med %@"; - -"endpoint.title" = "Slutpunkt"; -"endpoint.sections.location_addresses.header" = "Adresser"; -"endpoint.sections.location_protocols.header" = "Protokoll"; -"endpoint.cells.address" = "Adress"; -"endpoint.cells.protocol" = "Protokoll"; -"endpoint.cells.any_address.caption" = "Automatiskt"; -"endpoint.cells.any_protocol.caption" = "Automatiskt"; - -"network_settings.title" = "Nätverksinställningar"; -"network_settings.cells.add_dns_server.caption" = "Lägg till adress"; -"network_settings.cells.add_dns_domain.caption" = "Lägg till domän"; -"network_settings.cells.proxy_bypass.caption" = "Bypass-domän"; -"network_settings.cells.add_proxy_bypass.caption" = "Add bypass domain"; - -"configuration.title" = "Konfiguration"; -"configuration.sections.communication.header" = "Communication"; -"configuration.sections.reset.footer" = "Om du slutade med bruten anslutning efter att ha ändrat kommunikationsparametrarna trycker du på för att återgå till den ursprungliga konfigurationen."; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "Kompression"; -"configuration.sections.network.header" = "Nätverk"; -"configuration.sections.other.header" = "Other"; -"configuration.cells.cipher.caption" = "Cipher"; -"configuration.cells.digest.caption" = "Autentisering"; -"configuration.cells.digest.value.embedded" = "Inbäddad"; -"configuration.cells.compression_framing.caption" = "Inramning"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "Algoritm"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "Utan stöd"; -"configuration.cells.reset_original.caption" = "Återställ konfiguration"; -"configuration.cells.client.caption" = "Klientcertifikat"; -"configuration.cells.client.value.enabled" = "Verified"; -"configuration.cells.client.value.disabled" = "Ej verifierad"; -"configuration.cells.tls_wrapping.caption" = "Omslagning"; -"configuration.cells.tls_wrapping.value.auth" = "Autentisering"; -"configuration.cells.tls_wrapping.value.crypt" = "Kryptering"; -"configuration.cells.eku.caption" = "Förlängd verifering"; -"configuration.cells.keep_alive.caption" = "hål-vid-liv"; -"configuration.cells.keep_alive.value.seconds" = "%d seconds"; -"configuration.cells.renegotiation_seconds.caption" = "Omförhandling"; -"configuration.cells.renegotiation_seconds.value.after" = "efter %@"; -"configuration.cells.random_endpoint.caption" = "Omställ slutpunkt på slumpmässigt sätt"; -"configuration.alerts.commit.message" = "Nya parametrar kommer inte att införas förrän du återansluter manuellt. Ändringar i betrodda nätverk kommer att införas omedelbart."; -"configuration.alerts.commit.buttons.reconnect" = "Återanslut nu"; -"configuration.alerts.commit.buttons.skip" = "Hoppa över"; - -"trusted.columns.trust.title" = "Betrodda"; -"trusted.ethernet.title" = "Lita på kabelanslutna uppkopplingar"; -"trusted.ethernet.description" = "Markera för att lita på alla kabelanslutna uppkopplingar."; - -"preferences.title" = "Inställningar"; -"preferences.sections.general.header" = "Allmänt"; -"preferences.cells.launches_on_login.caption" = "Öppna vid inloggning"; -"preferences.cells.launches_on_login.footer" = "Markera för att starta appen automatiskt efter omstart eller vid inloggning."; -"preferences.cells.confirm_quit.caption" = "Bekräfta lämna"; -"preferences.cells.confirm_quit.footer" = "Markera för att visa en uppmaning att bekräfta att man vill lämna appen."; - -"network_settings.gateway.title" = "Normal gateway"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "Servrar"; -"network_settings.dns.cells.domain.caption" = "Domain"; -"network_settings.dns.cells.domains.title" = "Domäner"; -"network_settings.proxy.title" = "Proxy"; -"network_settings.proxy.cells.bypass_domains.title" = "Kringgå domäner"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "Rutt"; - -"debug_log.buttons.previous" = "Previous"; -"debug_log.buttons.next" = "Next"; -"debug_log.buttons.copy" = "Kopiera"; -"debug_log.alerts.empty_log.message" = "Felsökningsloggen är tom."; - -"shortcuts.add.title" = "Lägg till genväg"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "Cellular"; -"shortcuts.add.cells.connect.caption" = "Anslut till"; -"shortcuts.add.cells.enable_vpn.caption" = "Aktivera VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "Inaktivera VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "Lita på nuvarande Wi-Fi"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "Avaktivera nuvarande Wi-Fi"; -"shortcuts.add.cells.trust_cellular.caption" = "Lita på mobilnätverk"; -"shortcuts.add.cells.untrust_cellular.caption" = "Untrust cellular network"; -"shortcuts.add.alerts.no_profiles.message" = "Det finns ingen profil att ansluta till."; - -"shortcuts.edit.title" = "Hantera genvägar"; -"shortcuts.edit.sections.all.header" = "Befintliga genvägar"; -"shortcuts.edit.cells.add_shortcut.caption" = "Lägg till genväg"; - -"purchase.title" = "Köp"; -"purchase.sections.products.footer" = "Varje produkt är ett engångsköp. Leverantörsköp inkluderar inte ett VPN-abonnemang."; -"purchase.cells.full_version.extra_description" = "Alla leverantörer (inklusive framtida)\n%@"; -"purchase.cells.restore.title" = "Återställ köp"; -"purchase.cells.restore.description" = "Om du köpte den här appen eller funktionen tidigare kan du återställa dina inköp och den här skärmen visas inte igen."; - -"donation.title" = "Donera"; -"donation.sections.one_time.header" = "En gång"; -"donation.sections.one_time.footer" = "Om du vill visa tacksamhet för mitt fria arbete, här är några belopp du kan donera direkt. \n\nDu betalas endast en gång per donation, och du kan donera flera gånger. "; -"donation.cells.loading.caption" = "Laddar donationer"; -"donation.cells.purchasing.caption" = "Performing donation"; -"donation.alerts.purchase.success.title" = "Tack"; -"donation.alerts.purchase.success.message" = "Detta betyder mycket för mig och jag hoppas verkligen att du fortsätter att använda och marknadsföra denna app."; -"donation.alerts.purchase.failure.message" = "Kan inte göra donationen. %@"; - -"about.title" = "About"; -"about.sections.web.header" = "Web"; -"about.sections.share.header" = "Dela"; -"about.cells.credits.caption" = "Credits"; -"about.cells.website.caption" = "Hemsida"; -"about.cells.faq.caption" = "FAQ"; -"about.cells.disclaimer.caption" = "Disclaimer"; -"about.cells.privacy_policy.caption" = "Sekretesspolicy"; -"about.cells.share_twitter.caption" = "Tweet om det!"; -"about.cells.share_generic.caption" = "Bjud in en vän"; - -"version.title" = "Version"; -"version.labels.intro" = "Passepartout och TunnelKit skrivs och underhålls av Davide De Rosa (keeshux). \n\nKällkod för Passepartout och TunnelKit är offentligt tillgänglig på GitHub under GPLv3, du kan hitta länkar på hemsidan. \n\nPassepartout är en icke-officiell klient och är inte på något sätt ansluten till OpenVPN Inc. "; - -"credits.title" = "Credits"; -"credits.sections.licenses.header" = "Licenses"; -"credits.sections.notices.header" = "Meddelanden"; -"credits.sections.translations.header" = "Translations"; - -"label.license.error" = "Kan inte ladda ner fullständigt licensinnehåll."; - -// iOS only - -"imported_hosts.title" = "Importerade värdar"; - -// macOS only - -"menu.show.title" = "Visa"; -"menu.switch_profile.title" = "Aktiv profil"; -"menu.active_profile.title.none" = "Ingen aktiv profil"; -"menu.active_profile.items.customize.title" = "Anpassa"; -"menu.active_profile.messages.missing_credentials" = "Inget konto har konfigurerats"; -"menu.organizer.title" = "Organisatör"; -"menu.preferences.title" = "Inställningar"; -"menu.support.title" = "Support"; -"menu.quit.title" = "Lämna %@"; -"menu.quit.messages.confirm" = "Om ett VPN är aktiverat kommer detta fortfarande att köra i bakgrunden. Vill du lämna?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Resources/zh-Hans.lproj/Core.strings b/PassepartoutCore/Sources/PassepartoutCore/Resources/zh-Hans.lproj/Core.strings deleted file mode 100644 index 6a89c91e..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Resources/zh-Hans.lproj/Core.strings +++ /dev/null @@ -1,359 +0,0 @@ -// -// Core.strings -// Passepartout -// -// Created by Davide De Rosa on 6/13/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -"global.ok" = "好的"; -"global.cancel" = "取消"; -"global.next" = "下一步"; -"global.close" = "关闭"; -"global.host.title_input.message" = "可输入的字符为字母、数字以及破折号\"-\",下划线\"_\"和点\".\"。"; -"global.host.title_input.placeholder" = "我的配置"; -"global.email_not_configured" = "未配置e-mail账户。"; -"global.values.default" = "默认"; - -"global.captions.address" = "地址"; -"global.captions.port" = "端口"; -"global.captions.protocol" = "协议"; - -"global.values.enabled" = "已启用"; -"global.values.disabled" = "未启用"; -"global.values.none" = "无"; -"global.values.automatic" = "自动设置"; -"global.values.manual" = "手动设置"; - -"reddit.title" = "Reddit"; -"reddit.message" = "你知道Passepartout有一个subreddit吗?可以在上面讨论更新、问题、功能、新的平台等n任何你想要的。\n\n这同样是表达你对此项目关注的地方。"; -"reddit.buttons.subscribe" = "立即订阅!"; -"reddit.buttons.remind" = "稍后提醒我"; -"reddit.buttons.never" = "不要再问"; - -"vpn.connecting" = "连接中"; -"vpn.active" = "活动的"; -"vpn.disconnecting" = "断开中"; -"vpn.inactive" = "不活动"; -"vpn.disabled" = "未启用"; -"vpn.unused" = "关"; - -"vpn.errors.timeout" = "超时"; -"vpn.errors.dns" = "DNS解析失败"; -"vpn.errors.auth" = "身份验证失败"; -"vpn.errors.tls" = "TLS连接失败"; -"vpn.errors.encryption" = "加密失败"; -"vpn.errors.compression" = "压缩方式不支持"; -"vpn.errors.network" = "网络更改"; -"vpn.errors.routing" = "缺少路由"; -"vpn.errors.gateway" = "无网关"; -"vpn.errors.shutdown" = "服务器关闭"; - -"parsed_file.alerts.malformed.message" = "此配置文件包含格式错误的选项(%@)。"; -"parsed_file.alerts.missing.message" = "此配置文件缺少必须的选项(%@)。"; -"parsed_file.alerts.unsupported.message" = "此配置文件包含不支持的选项(%@)。"; -"parsed_file.alerts.potentially_unsupported.message" = "配置文件正确但包含不支持的选项(%@)。\n\n根据服务端的设置连接可能会断开。"; -"parsed_file.alerts.encryption_passphrase.message" = "请输入加密密码"; -"parsed_file.alerts.decryption.message" = "该配置包含加密私钥且不能被解密。请再次检查输入的密钥。"; -"parsed_file.alerts.parsing.message" = "无法解析提供的配置文件(%@)。"; -"parsed_file.alerts.buttons.report" = "报告问题"; - -"network_choice.client" = "读取.ovpn文件"; -"network_choice.server" = "从服务端拉取"; - -"issue_reporter.title" = "提交问题"; -"issue_reporter.message" = "最后一次连接的debug日志对于解决连接问题是很重要的,并且它是匿名的。\n\n如果有.ovpn文件,那么已经去除了敏感信息后才会y添加。\n\n如果不确定的话,请检查e-mail附件。"; -"issue_reporter.buttons.accept" = "我已知悉"; - -"translations.title" = "翻译"; - -"share.message" = "Passepartout是iOS和macOS平台下的用户友的、开源的OpenVPN客户端"; - -// //////////////////// - -"organizer.title" = "%@"; -"organizer.menus.provider" = "提供商"; -"organizer.menus.provider.unavailable" = "没有提供商了"; -"organizer.menus.host" = "主机"; -/* //////////////////// */ -"organizer.sections.twitch.header" = "Twitch"; -"organizer.sections.twitch.footer" = "快来看我让Passepartout在Twitch上直播,加入聊天互动并做出贡献!"; -"organizer.sections.providers.header" = "提供商"; -"organizer.sections.providers.footer" = "在这里你可以找到预设的新的提供商配置。"; -"organizer.sections.hosts.header" = "主机"; -"organizer.sections.hosts.footer" = "从原始的.ovpn配置文件导入。"; -"organizer.sections.siri.header" = "Siri"; -"organizer.sections.siri.footer" = "通过Siri来帮助你加快常用的操作。"; -"organizer.sections.support.header" = "支持"; -"organizer.sections.feedback.header" = "反馈"; -"organizer.cells.follow_twitch.caption" = "在Twitch上观看路路通"; -"organizer.cells.profile.value.current" = "使用中"; -"organizer.cells.siri_shortcuts.caption" = "管理捷径"; -"organizer.cells.join_community.caption" = "加入社区"; -"organizer.cells.write_review.caption" = "写下想法"; -"organizer.cells.donate.caption" = "提供捐助"; -"organizer.cells.github_sponsors.caption" = "在GitHub上支持我"; -"organizer.cells.translate.caption" = "提供翻译"; -"organizer.cells.about.caption" = "关于 %@"; -"organizer.cells.uninstall.caption" = "移除VPN配置"; -"organizer.cells.add_provider.caption" = "添加新的提供商配置"; -"organizer.cells.add_host.caption" = "从文件添加"; -"organizer.cells.import_host.caption" = "从导入中添加"; -"organizer.alerts.exhausted_providers.message" = "你已针对每个可用的提供商创建了配置。"; -"organizer.alerts.add_host.message" = "从Safari、邮件或其他App中打开.ovpn文件的URL来设置主机配置。\n\n你同样可以通过iTunes文件分享来导入.ovpnf文件。"; -"organizer.alerts.cannot_donate.message" = "此设备未设置付款方式。"; -"organizer.alerts.delete_vpn_profile.message" = "你确定要从此设备移除VPN配置吗?这可能会固定一些断开的VPN状态,不会影响已经存在的配置。"; -"organizer.alerts.remove_profile.title" = "删除配置"; -"organizer.alerts.remove_profile.message" = "确定要删除配置%@吗?"; -"organizer.alerts.open_host_file.title" = "选择.ovpn文件"; - -"wizards.provider.cells.update_list.caption" = "更新列表"; -"wizards.provider.alerts.unavailable.message" = "无法下载提供商基础架构,请稍后重试。"; -"wizards.host.sections.existing.header" = "已存在的配置"; -"wizards.host.cells.title_input.caption" = "名称"; -"wizards.host.alerts.existing.message" = "已经存在同名的配置。要替换它吗?"; - -"service.welcome.message" = "欢迎使用Passepartout!\n\n使用分类页面来添加一个新的配置。"; -"service.sections.vpn.header" = "VPN"; -"service.sections.vpn.footer" = "必要时连接将会建立。"; -"service.sections.status.header" = "连接"; -"service.sections.configuration.header" = "配置"; -"service.sections.provider_infrastructure.footer" = "最后在%@时更新"; -"service.sections.vpn_survives_sleep.footer" = "禁用以减少电池消耗,由于存在可能的唤醒时重连消耗。"; -"service.sections.vpn_resolves_hostname.footer" = "推荐在大部分的网络中打开,并要求在IPv6环境下。当DNS被阻断或相应缓慢时禁用。"; -"service.sections.trusted.header" = "可信网络"; -"service.sections.trusted.footer" = "当进入可信网络后,VPN将会断开并保持断开状态。禁用此选项可关闭此功能。"; -"service.sections.diagnostics.header" = "分析数据"; -"service.sections.diagnostics.footer" = "在重连后状态隐藏才有效。网络数据包括主机名、IP地址、路由、SSID、认证方式,但私钥不会出现在日志中。"; -"service.cells.use_profile.caption" = "使用此配置"; -"service.cells.vpn_service.caption" = "已启用"; -"service.cells.vpn.turn_on.caption" = "启用VPN"; -"service.cells.vpn.turn_off.caption" = "禁用VPN"; -"service.cells.connection_status.caption" = "状态"; -"service.cells.host.parameters.caption" = "参数"; -"service.cells.provider.pool.caption" = "位置"; -"service.cells.provider.preset.caption" = "预设"; -"service.cells.provider.refresh.caption" = "刷新基础设置"; -"service.cells.category.caption" = "类别"; -"service.cells.addresses.caption" = "地址"; -"service.cells.only_shows_favorites.caption" = "仅显示收藏的地点"; -"service.cells.vpn_survives_sleep.caption" = "休眠时保持连接"; -"service.cells.vpn_resolves_hostname.caption" = "解析服务器主机名"; -"service.cells.trusted_add_wifi.caption" = "新增Wi-Fi"; -"service.cells.trusted_mobile.caption" = "蜂窝网络"; -"service.cells.trusted_policy.caption" = "信任网络中禁用VPN"; -"service.cells.test_connectivity.caption" = "测试连接性"; -"service.cells.data_count.caption" = "已交换数据量"; -"service.cells.data_count.none" = "不可用"; -"service.cells.server_configuration.caption" = "服务端配置"; -"service.cells.server_network.caption" = "服务端网络"; -"service.cells.debug_log.caption" = "调试日志"; -"service.cells.masks_private_data.caption" = "隐藏网络数据"; -"service.cells.reconnect.caption" = "重连"; -"service.cells.report_issue.caption" = "报告连接问题"; - -"service.alerts.rename.title" = "重命名配置"; -"service.alerts.credentials_needed.message" = "你需要先输入账户的认证信息。"; -"service.alerts.reconnect_vpn.message" = "要重连VPN吗?"; -"service.alerts.trusted.no_network.message" = "你没有连接到Wi-Fi网络。"; -"service.alerts.trusted.will_disconnect_trusted.message" = "信任此网络后,VPN会断开。要继续吗?"; -"service.alerts.trusted.will_disconnect_policy.message" = "改变信任策略后,VPN可能会断开,要继续吗?"; -"service.alerts.test_connectivity.title" = "连接性"; -"service.alerts.test_connectivity.messages.success" = "你的设备连接到了网络!"; -"service.alerts.test_connectivity.messages.failure" = "你的设备没有连接到网络,请检查你的配置参数。"; -"service.alerts.configuration.disconnected" = "配置不可用,请确认你已连接到VPN。"; -"service.alerts.masks_private_data.messages.must_reconnect" = "为了安全地重置当前调试日志并应用新的掩码设置,你现在必须重连VPN。"; -"service.alerts.buttons.reconnect" = "重连"; -"service.alerts.download.title" = "下载必须的内容"; -"service.alerts.download.message" = "%@要求下载额外的配置文件。\n\n确认以开始下载。"; -"service.alerts.download.failed" = "下载配置文件失败。 %@"; -"service.alerts.download.hud.extracting" = "提取文件中,请等待..."; -"service.alerts.location.message.denied" = "你必须允许位置访问以信任此Wi-Fi。请至iOS设置对Passepartout的位置权限。"; -"service.alerts.location.button.settings" = "设置"; - -"provider.pool.sections.empty_favorites.footer" = "向左轻扫以将其从最喜爱列表中移除或添加。"; -"provider.pool.actions.favorite" = "最喜爱"; -"provider.pool.actions.unfavorite" = "不喜爱"; - -"provider.preset.cells.tech_details.caption" = "技术细节"; - -"account.title" = "账户"; -"account.sections.credentials.header" = "认证方式"; -"account.sections.guidance.footer.infrastructure.default.web" = "使用你的%@网站认证信息。"; -"account.sections.guidance.footer.infrastructure.default.specific" = "使用您的%@服务凭据,该凭据可能与网站凭据不同。"; -"account.sections.guidance.footer.infrastructure.mullvad" = "使用你的%@网站认证信息。你的用户名一般是数字 (没有空格)。"; -"account.sections.guidance.footer.infrastructure.nordvpn" = "使用你的%@网站认证信息。你的用户名一般是你的e-mail。"; -"account.sections.guidance.footer.infrastructure.pia" = "使用你的%@网站认证信息。你的用户名一般是数字且带有前缀\"p\"。"; -"account.sections.guidance.footer.infrastructure.protonvpn" = "在网站的\"Account > OpenVPN / IKEv2 Username\"部分找到你的%@的认证信息。"; -"account.sections.guidance.footer.infrastructure.tunnelbear" = "使用你的%@网站认证信息。你的用户名一般是你的e-mail。"; -"account.sections.guidance.footer.infrastructure.vyprvpn" = "使用你的%@网站认证信息。你的用户名一般是你的e-mail。"; -"account.sections.guidance.footer.infrastructure.windscribe" = "在网站上的OpenVPN配置生成器中找到你的%@认证信息。 "; -"account.sections.registration.footer" = "在%@网站上获取一个账户"; -"account.cells.username.caption" = "用户名"; -"account.cells.username.placeholder" = "username"; -"account.cells.password.caption" = "密码"; -"account.cells.password.placeholder" = "secret"; -"account.cells.open_guide.caption" = "查看你的认证信息"; -"account.cells.signup.caption" = "以%@注册"; - -"endpoint.title" = "服务端"; -"endpoint.sections.location_addresses.header" = "地址"; -"endpoint.sections.location_protocols.header" = "协议"; -"endpoint.cells.address" = "地址"; -"endpoint.cells.protocol" = "协议"; -"endpoint.cells.any_address.caption" = "自动"; -"endpoint.cells.any_protocol.caption" = "自动"; - -"network_settings.title" = "网络设置"; -"network_settings.cells.add_dns_server.caption" = "添加地址"; -"network_settings.cells.add_dns_domain.caption" = "添加搜索域名"; -"network_settings.cells.proxy_bypass.caption" = "旁路域名"; -"network_settings.cells.add_proxy_bypass.caption" = "添加旁路域名"; - -"configuration.title" = "配置"; -"configuration.sections.communication.header" = "通信"; -"configuration.sections.reset.footer" = "如果你在更改连接参数后变成断开状态,点按已恢复到原始的配置。"; -"configuration.sections.tls.header" = "TLS"; -"configuration.sections.compression.header" = "压缩"; -"configuration.sections.network.header" = "网络"; -"configuration.sections.other.header" = "其他"; -"configuration.cells.cipher.caption" = "加密"; -"configuration.cells.digest.caption" = "身份验证"; -"configuration.cells.digest.value.embedded" = "已包含"; -"configuration.cells.compression_framing.caption" = "分帧"; -"configuration.cells.compression_framing.value.lzo" = "--comp-lzo"; -"configuration.cells.compression_framing.value.compress" = "--compress"; -"configuration.cells.compression_algorithm.caption" = "算法"; -"configuration.cells.compression_algorithm.value.lzo" = "LZO"; -"configuration.cells.compression_algorithm.value.other" = "未支持"; -"configuration.cells.reset_original.caption" = "重置配置"; -"configuration.cells.client.caption" = "客户端证书"; -"configuration.cells.client.value.enabled" = "已验证"; -"configuration.cells.client.value.disabled" = "未验证"; -"configuration.cells.tls_wrapping.caption" = "组包"; -"configuration.cells.tls_wrapping.value.auth" = "身份验证"; -"configuration.cells.tls_wrapping.value.crypt" = "加密"; -"configuration.cells.eku.caption" = "扩展身份验证"; -"configuration.cells.keep_alive.caption" = "保持活跃状态"; -"configuration.cells.keep_alive.value.seconds" = "%d秒"; -"configuration.cells.renegotiation_seconds.caption" = "重协商"; -"configuration.cells.renegotiation_seconds.value.after" = "在%@之后"; -"configuration.cells.random_endpoint.caption" = "随机的服务端"; -"configuration.alerts.commit.message" = "除非手动重新连接,否则新参数将无效。受信任的网络中的更改将立即生效。"; -"configuration.alerts.commit.buttons.reconnect" = "立即重新连接"; -"configuration.alerts.commit.buttons.skip" = "跳过"; - -"trusted.columns.trust.title" = "信任"; -"trusted.ethernet.title" = "信任有线连接"; -"trusted.ethernet.description" = "选中以信任所有有线连接。"; - -"preferences.title" = "偏好设置"; -"preferences.sections.general.header" = "一般"; -"preferences.cells.launches_on_login.caption" = "登录时启动"; -"preferences.cells.launches_on_login.footer" = "选中以在启动或登录时自动启动应用。"; -"preferences.cells.confirm_quit.caption" = "确认退出"; -"preferences.cells.confirm_quit.footer" = "选中以显示退出确认提醒。"; - -"network_settings.gateway.title" = "默认网关"; -"network_settings.dns.title" = "DNS"; -"network_settings.dns.cells.addresses.title" = "服务器"; -"network_settings.dns.cells.domain.caption" = "域名"; -"network_settings.dns.cells.domains.title" = "域"; -"network_settings.proxy.title" = "代理"; -"network_settings.proxy.cells.bypass_domains.title" = "旁路域"; -"network_settings.mtu.title" = "MTU"; -"network_settings.mtu.cells.bytes.caption" = "Bytes"; - -"server_network.cells.route.caption" = "路由"; - -"debug_log.buttons.previous" = "上一步"; -"debug_log.buttons.next" = "下一步"; -"debug_log.buttons.copy" = "复制"; -"debug_log.alerts.empty_log.message" = "日志为空"; - -"shortcuts.add.title" = "添加捷径"; -"shortcuts.add.sections.vpn.header" = "VPN"; -"shortcuts.add.sections.wifi.header" = "Wi-Fi"; -"shortcuts.add.sections.cellular.header" = "蜂窝网络"; -"shortcuts.add.cells.connect.caption" = "连接到"; -"shortcuts.add.cells.enable_vpn.caption" = "开启VPN"; -"shortcuts.add.cells.disable_vpn.caption" = "关闭VPN"; -"shortcuts.add.cells.trust_current_wifi.caption" = "信任当前Wi-Fi"; -"shortcuts.add.cells.untrust_current_wifi.caption" = "不信任当前Wi-Fi"; -"shortcuts.add.cells.trust_cellular.caption" = "信任蜂窝网络"; -"shortcuts.add.cells.untrust_cellular.caption" = "不信任蜂窝网络"; -"shortcuts.add.alerts.no_profiles.message" = "没有可以连接的配置。"; - -"shortcuts.edit.title" = "管理捷径"; -"shortcuts.edit.sections.all.header" = "已经存在的捷径"; -"shortcuts.edit.cells.add_shortcut.caption" = "添加捷径"; - -"purchase.title" = "购买"; -"purchase.sections.products.footer" = "每件产品都是一次性的购买。 购买的提供商并不包含VPN订阅。"; -"purchase.cells.full_version.extra_description" = "所有的提供商(包括未来添加的)\n%@"; -"purchase.cells.restore.title" = "恢复购买"; -"purchase.cells.restore.description" = "如果你购买过此应用或其特征, 你可以恢复购买,此页面将不在显示。"; - -"donation.title" = "捐助"; -"donation.sections.one_time.header" = "一次性"; -"donation.sections.one_time.footer" = "如果你想对我免费的工作表示感谢,这里是一些你可以捐助的数额。\n\n每次捐助只需要付款一次,你可以捐助多次。"; -"donation.cells.loading.caption" = "加载捐助中"; -"donation.cells.purchasing.caption" = "展示捐助页面中"; -"donation.alerts.purchase.success.title" = "感谢"; -"donation.alerts.purchase.success.message" = "这对于我意味着很多,希望你保持使用并使它更好。"; -"donation.alerts.purchase.failure.message" = "无法展现捐助内容。%@"; - -"about.title" = "关于"; -"about.sections.web.header" = "网络"; -"about.sections.share.header" = "分享"; -"about.cells.credits.caption" = "评分"; -"about.cells.website.caption" = "主页"; -"about.cells.faq.caption" = "常见问题"; -"about.cells.disclaimer.caption" = "免责声明"; -"about.cells.privacy_policy.caption" = "隐私政策"; -"about.cells.share_twitter.caption" = "发送关于它的推特!"; -"about.cells.share_generic.caption" = "邀请朋友"; - -"version.title" = "版本"; -"version.labels.intro" = "Passepartout和TunnelKit由Davide De Rosa (keeshux)编写并维护。\n\nPassepartout和TunnelKit的源代码在GitHub上以GPLv3许可证面向大众开放,你可以在主页上找到链接。\n\nPassepartout不是官方的客户端,同OpenVPN Inc也没有关系。"; - -"credits.title" = "评分"; -"credits.sections.licenses.header" = "许可证"; -"credits.sections.notices.header" = "注意"; -"credits.sections.translations.header" = "翻译"; - -"label.license.error" = "不能下载到完整的许可证内容。"; - -// iOS only - -"imported_hosts.title" = "导入主机配置"; - -// macOS only - -"menu.show.title" = "显示"; -"menu.switch_profile.title" = "有效配置"; -"menu.active_profile.title.none" = "无有效配置"; -"menu.active_profile.items.customize.title" = "自定义......"; -"menu.active_profile.messages.missing_credentials" = "未配置账户"; -"menu.organizer.title" = "分类页面"; -"menu.preferences.title" = "偏好设置"; -"menu.support.title" = "支持"; -"menu.quit.title" = "退出%@"; -"menu.quit.messages.confirm" = "VPN(如果启用)仍将在后台运行。您要退出吗?"; diff --git a/PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure+External.swift b/PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure+External.swift deleted file mode 100644 index 7c4b1c41..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure+External.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// Infrastructure+External.swift -// Passepartout -// -// Created by Davide De Rosa on 11/4/21. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import PassepartoutConstants -import DTFoundation - -extension InfrastructureName { - public var externalURL: URL { - return GroupConstants.App.externalURL.appendingPathComponent(self) - } - - public func importExternalResources(from url: URL, completionHandler: @escaping () -> Void) { - var task: () -> Void - switch self { - case .nordvpn: - task = { - let archive = DTZipArchive(atPath: url.path) - archive?.uncompress(toPath: self.externalURL.path, completion: nil) - } - - default: - task = {} - } - execute(task: task, completionHandler: completionHandler) - } - - private func execute(task: @escaping () -> Void, completionHandler: @escaping () -> Void) { - let queue: DispatchQueue = .global(qos: .background) - queue.async { - task() - DispatchQueue.main.async { - completionHandler() - } - } - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure.swift b/PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure.swift deleted file mode 100644 index 093265c6..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// Infrastructure.swift -// Passepartout -// -// Created by Davide De Rosa on 6/11/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import PassepartoutConstants - -public class Infrastructure: Codable { - public struct Defaults: Codable { - public let username: String? - - public let pool: String - - public let preset: String - } - - public let build: [String: Int] - - public var buildNumber: Int { - var num: Int? - #if os(iOS) - num = build["ios"] - #else - num = build["macos"] - #endif - return num ?? 0 - } - - public let name: InfrastructureName - - public let categories: [PoolCategory] - - public let presets: [InfrastructurePreset] - - public let defaults: Defaults - - public static func from(url: URL) throws -> Infrastructure { - let json = try Data(contentsOf: url) - return try JSONDecoder().decode(Infrastructure.self, from: json) - } - - public func defaultPool() -> Pool? { - return pool(withPrefix: defaults.pool) - } - - public func pool(for identifier: String) -> Pool? { - for cat in categories { - for group in cat.groups { - guard let found = group.pools.first(where: { $0.id == identifier }) else { - continue - } - return found - } - } - return nil - } - - public func pool(withPrefix prefix: String) -> Pool? { - for cat in categories { - for group in cat.groups { - guard let found = group.pools.first(where: { $0.id.hasPrefix(prefix) }) else { - continue - } - return found - } - } - return nil - } - - public func preset(for identifier: String) -> InfrastructurePreset? { - return presets.first { $0.id == identifier } - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Services/InfrastructureFactory.swift b/PassepartoutCore/Sources/PassepartoutCore/Services/InfrastructureFactory.swift deleted file mode 100644 index 07d9d711..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Services/InfrastructureFactory.swift +++ /dev/null @@ -1,363 +0,0 @@ -// -// InfrastructureFactory.swift -// Passepartout -// -// Created by Davide De Rosa on 9/2/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import SwiftyBeaver -import PassepartoutConstants - -private let log = SwiftyBeaver.self - -// TODO: retain max N infrastructures at a time (LRU) - -public class InfrastructureFactory { - public static let shared = InfrastructureFactory() - - private let cachePath: URL - - fileprivate var cachedMetadata: [Infrastructure.Metadata] - - private var cachedInfrastructures: [InfrastructureName: Infrastructure] - - private var lastUpdate: [InfrastructureName: Date] - - private init() { - cachePath = GroupConstants.App.cachesURL - cachedMetadata = [] - cachedInfrastructures = [:] - lastUpdate = [:] - } - - // MARK: Storage - - public func preload() { - loadMetadata() - loadInfrastructures() - } - - public func loadMetadata() { - let decoder = JSONDecoder() - - // pick cache if newer - if Utils.isFile(at: cacheMetadataURL, newerThanFileAt: bundledMetadataURL) { - do { - let indexData = try Data(contentsOf: cacheMetadataURL) - cachedMetadata = try decoder.decode([Infrastructure.Metadata].self, from: indexData) - log.debug("Loaded metadata from cache: \(cachedMetadata)") - return - } catch let e { - log.warning("No index in cache: \(e)") - } - } else { - log.warning("Bundle is newer than cache, superseding cache for index") - } - - // fall back to bundled index - guard let bundleURL = bundledMetadataURL else { - fatalError("Unable to build index bundleURL") - } - do { - let indexData = try Data(contentsOf: bundleURL) - cachedMetadata = try decoder.decode([Infrastructure.Metadata].self, from: indexData) - log.debug("Loaded index from bundle: \(cachedMetadata)") - } catch let e { - log.error("Unable to load index from bundle: \(e)") - } - } - - public func loadInfrastructures() { - let apiPath = cachePath.appendingPathComponent(AppConstants.Store.apiDirectory) - let providersPath = apiPath.appendingPathComponent(WebServices.Group.providers.rawValue) - - log.debug("Loading cache from: \(providersPath)") - let cacheProvidersEntries: [URL] - do { - cacheProvidersEntries = try FileManager.default.contentsOfDirectory(at: providersPath, includingPropertiesForKeys: nil) - } catch let e { - log.warning("Error loading cache or nothing cached: \(e)") - - cachedMetadata.forEach { - guard let infra = bundledInfrastructure(withName: $0.name) else { - log.warning("Missing infrastructure \($0.name) from bundle") - return - } - cachedInfrastructures[$0.name] = infra - log.debug("Loaded infrastructure \($0.name) from bundle") - } - return - } - - let decoder = JSONDecoder() - for entry in cacheProvidersEntries { - let name = entry.lastPathComponent - - // skip *.json (index.json presumably) - guard !name.hasSuffix(".json") else { - continue - } - - // pick cache if newer - if Utils.isFile(at: cacheURL(forName: name), newerThanFileAt: name.bundleURL) { - let infraPath = WebServices.Endpoint.providerNetwork(name).apiURL(relativeTo: cachePath) - do { - let infraData = try Data(contentsOf: infraPath) - let infra = try decoder.decode(Infrastructure.self, from: infraData) - cachedInfrastructures[name] = infra - log.debug("Loaded infrastructure \(name) from cache") - continue - } catch let e { - log.warning("Unable to load infrastructure \(entry.lastPathComponent): \(e)") -// if let json = String(data: data, encoding: .utf8) { -// log.warning(json) -// } - } - } else { - log.warning("Bundle is newer than cache, superseding cache for \(name)") - } - - // fall back to bundle - guard let infra = bundledInfrastructure(withName: name) else { - log.warning("Missing infrastructure \(name) from bundle") - continue - } - cachedInfrastructures[name] = infra - log.debug("Loaded infrastructure \(name) from bundle") - } - - // fill up with bundled - cachedMetadata.forEach { - if cachedInfrastructures[$0.name] == nil { - guard let infra = bundledInfrastructure(withName: $0.name) else { - log.warning("Missing infrastructure \($0.name) from bundle") - return - } - cachedInfrastructures[$0.name] = infra - log.debug("Loaded infrastructure \($0.name) from bundle") - } - } - } - - public var allMetadata: [Infrastructure.Metadata] { - return cachedMetadata - } - - public func metadata(forName name: InfrastructureName) -> Infrastructure.Metadata? { - return cachedMetadata.first(where: { $0.name == name}) - } - - public func infrastructure(forName name: InfrastructureName) -> Infrastructure? { - return cachedInfrastructures[name] - } - - private func bundledInfrastructure(withName name: InfrastructureName) -> Infrastructure? { - guard let url = name.bundleURL else { - return nil - } - do { - return try Infrastructure.from(url: url) - } catch let e { - fatalError("Cannot parse JSON for infrastructure '\(name)': \(e)") - } - } - - // MARK: Web services - - public func updateIndex(completionHandler: @escaping (Error?) -> Void) { - WebServices.shared.providersIndex { - if let response = $0 { - self.saveIndex(with: response) - } - completionHandler($1) - } - } - - public func update(_ name: InfrastructureName, notBeforeInterval minInterval: TimeInterval?, completionHandler: @escaping ((Infrastructure, Date)?, Error?) -> Void) -> Bool { - let ifModifiedSince = modificationDate(forName: name) - - if let lastInfrastructureUpdate = lastUpdate[name] { - log.debug("Last update for \(name): \(lastUpdate)") - - if let minInterval = minInterval { - let elapsed = -lastInfrastructureUpdate.timeIntervalSinceNow - guard elapsed >= minInterval else { - log.warning("Skipping update, only \(elapsed) seconds elapsed (< \(minInterval))") - return false - } - } - } - - WebServices.shared.providerNetwork(with: name, ifModifiedSince: ifModifiedSince) { (response, error) in - if error == nil { - self.lastUpdate[name] = Date() - } - - guard let response = response else { - log.error("No response from web service") - DispatchQueue.main.async { - completionHandler(nil, error) - } - return - } - if response.isCached { - log.debug("Cache is up to date") - DispatchQueue.main.async { - completionHandler(nil, error) - } - return - } - guard let infra = response.value, let lastModified = response.lastModified else { - log.error("No response from web service or missing Last-Modified") - DispatchQueue.main.async { - completionHandler(nil, error) - } - return - } - let appBuild = GroupConstants.App.buildNumber - guard appBuild >= infra.buildNumber else { - log.error("Response requires app build >= \(infra.build) (found \(appBuild))") - DispatchQueue.main.async { - completionHandler(nil, error) - } - return - } - - var isNewer = true - if let bundleDate = self.bundleModificationDate(forName: name) { - log.verbose("Bundle date: \(bundleDate)") - log.verbose("Web date: \(lastModified)") - - isNewer = lastModified > bundleDate - } - guard isNewer else { - log.warning("Web service infrastructure is older than bundle, discarding") - DispatchQueue.main.async { - completionHandler(nil, error) - } - return - } - - self.save(name, with: infra, lastModified: lastModified) - - DispatchQueue.main.async { - completionHandler((infra, lastModified), nil) - } - } - return true - } - - private func saveIndex(with metadata: [Infrastructure.Metadata]) { - cachedMetadata = metadata - - let fm = FileManager.default - let url = cacheMetadataURL - do { - let parent = url.deletingLastPathComponent() - try fm.createDirectory(at: parent, withIntermediateDirectories: true, attributes: nil) - let data = try JSONEncoder().encode(metadata) - try data.write(to: url) - } catch let e { - log.error("Error saving index to cache: \(e)") - } - } - - private func save(_ name: InfrastructureName, with infrastructure: Infrastructure, lastModified: Date) { - cachedInfrastructures[name] = infrastructure - - let fm = FileManager.default - let url = cacheURL(forName: name) - do { - let parent = url.deletingLastPathComponent() - try fm.createDirectory(at: parent, withIntermediateDirectories: true, attributes: nil) - let data = try JSONEncoder().encode(infrastructure) - try data.write(to: url) - try fm.setAttributes([.modificationDate: lastModified], ofItemAtPath: url.path) - } catch let e { - log.error("Error saving infrastructure \(name) to cache: \(e)") - } - } - - // MARK: URLs - - private var cacheMetadataURL: URL { - return WebServices.Endpoint.providersIndex.apiURL(relativeTo: cachePath) - } - - private func cacheURL(forName name: InfrastructureName) -> URL { - return WebServices.Endpoint.providerNetwork(name).apiURL(relativeTo: cachePath) - } - - private var bundledMetadataURL: URL? { - return WebServices.Endpoint.providersIndex.bundleURL(in: Bundle.module) - } - - // MARK: Modification dates - - public func modificationDate(forName name: InfrastructureName) -> Date? { - let optBundleDate = bundleModificationDate(forName: name) - guard let cacheDate = cacheModificationDate(forName: name) else { - return optBundleDate - } - guard let bundleDate = optBundleDate else { - return cacheDate - } - return max(cacheDate, bundleDate) - } - - private func cacheModificationDate(forName name: InfrastructureName) -> Date? { - let url = cacheURL(forName: name) - return FileManager.default.modificationDate(of: url.path) - } - - private func bundleModificationDate(forName name: InfrastructureName) -> Date? { - guard let url = name.bundleURL else { - return nil - } - return FileManager.default.modificationDate(of: url.path) - } -} - -extension Infrastructure { - public var metadata: Metadata? { - return InfrastructureFactory.shared.metadata(forName: name) - } -} - -private extension InfrastructureName { - var bundleURL: URL? { - return WebServices.Endpoint.providerNetwork(self).bundleURL(in: Bundle.module) - } -} - -extension ConnectionService { - public func availableProviders() -> [Infrastructure.Metadata] { - let names = Set(ids(forContext: .provider)) - return InfrastructureFactory.shared.cachedMetadata.filter { !names.contains($0.name) } - } - - public func hasAvailableProviders() -> Bool { - var allNames = Set(InfrastructureFactory.shared.cachedMetadata.map { $0.name }) - allNames.subtract(ids(forContext: .provider)) - return !allNames.isEmpty - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Services/InfrastructurePreset.swift b/PassepartoutCore/Sources/PassepartoutCore/Services/InfrastructurePreset.swift deleted file mode 100644 index 786e76ae..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Services/InfrastructurePreset.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// InfrastructurePreset.swift -// Passepartout -// -// Created by Davide De Rosa on 8/30/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import TunnelKit -import TunnelKitOpenVPN -import PassepartoutConstants - -// supports a subset of OpenVPNTunnelProvider.Configuration -// ignores new JSON keys - -public class InfrastructurePreset: Codable { - public enum ExternalKey: String, Codable { - case ca - - case client - - case key - - case wrapKeyData = "wrap.key.data" - - case hostname - } - - public enum PresetKeys: String, CodingKey { - case id - - case name - - case comment - - case configuration = "cfg" - - case external - } - - public enum ConfigurationKeys: String, CodingKey { - case endpointProtocols = "ep" - - case cipher - - case digest = "auth" - - case ca - - case clientCertificate = "client" - - case clientKey = "key" - - case compressionFraming = "frame" - - case compressionAlgorithm = "compression" - - case keepAliveSeconds = "ping" - - case keepAliveTimeoutSeconds = "pingTimeout" - - case renegotiatesAfterSeconds = "reneg" - - case tlsWrap = "wrap" - - case checksEKU = "eku" - - case randomizeEndpoint = "random" - - case usesPIAPatches = "pia" - } - - public let id: String - - public let name: String - - public let comment: String - - public let configuration: OpenVPNProvider.Configuration - - public let external: [ExternalKey: String]? - - public func hasProtocol(_ proto: EndpointProtocol) -> Bool { - return configuration.sessionConfiguration.endpointProtocols?.firstIndex(of: proto) != nil - } - - public func externalConfiguration(forKey key: ExternalKey, infrastructureName: InfrastructureName, pool: Pool) throws -> Any? { - guard let pattern = external?[key] else { - return nil - } - let baseURL = infrastructureName.externalURL - switch key { - case .ca: - let filename = pattern.replacingOccurrences(of: "${id}", with: pool.id) - let caURL = baseURL.appendingPathComponent(filename) - return OpenVPN.CryptoContainer(pem: try String(contentsOf: caURL)) - - case .wrapKeyData: - let filename = pattern.replacingOccurrences(of: "${id}", with: pool.id) - let tlsKeyURL = baseURL.appendingPathComponent(filename) - let file = try String(contentsOf: tlsKeyURL) - return OpenVPN.StaticKey(file: file, direction: .client) - - case .hostname: - return pattern.replacingOccurrences(of: "${id}", with: pool.id) - - default: - break - } - return nil - } - - public func injectExternalConfiguration(_ configuration: inout OpenVPNProvider.ConfigurationBuilder, with infrastructureName: InfrastructureName, pool: Pool) throws { - guard let external = external, !external.isEmpty else { - return - } - - var sessionBuilder = configuration.sessionConfiguration.builder() - if let _ = external[.ca] { - sessionBuilder.ca = try externalConfiguration(forKey: .ca, infrastructureName: infrastructureName, pool: pool) as? OpenVPN.CryptoContainer - } - if let _ = external[.wrapKeyData] { - if let dummyWrap = sessionBuilder.tlsWrap { - if let staticKey = try externalConfiguration(forKey: .wrapKeyData, infrastructureName: infrastructureName, pool: pool) as? OpenVPN.StaticKey { - sessionBuilder.tlsWrap = OpenVPN.TLSWrap(strategy: dummyWrap.strategy, key: staticKey) - } - } - } - if let _ = external[.hostname] { - sessionBuilder.hostname = try externalConfiguration(forKey: .hostname, infrastructureName: infrastructureName, pool: pool) as? String - } - configuration.sessionConfiguration = sessionBuilder.build() - } - - // MARK: Codable - - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: PresetKeys.self) - id = try container.decode(String.self, forKey: .id) - name = try container.decode(String.self, forKey: .name) - comment = try container.decode(String.self, forKey: .comment) - if let rawExternal = try container.decodeIfPresent([String: String].self, forKey: .external) { - var remapped: [ExternalKey: String] = [:] - for entry in rawExternal { - guard let key = ExternalKey(rawValue: entry.key) else { - continue - } - remapped[key] = entry.value - } - external = remapped - } else { - external = nil - } - - let cfgContainer = try container.nestedContainer(keyedBy: ConfigurationKeys.self, forKey: .configuration) - - var sessionBuilder = OpenVPN.ConfigurationBuilder() - sessionBuilder.cipher = try cfgContainer.decode(OpenVPN.Cipher.self, forKey: .cipher) - if let digest = try cfgContainer.decodeIfPresent(OpenVPN.Digest.self, forKey: .digest) { - sessionBuilder.digest = digest - } - sessionBuilder.compressionFraming = try cfgContainer.decode(OpenVPN.CompressionFraming.self, forKey: .compressionFraming) - sessionBuilder.compressionAlgorithm = try cfgContainer.decodeIfPresent(OpenVPN.CompressionAlgorithm.self, forKey: .compressionAlgorithm) ?? .disabled - sessionBuilder.ca = try cfgContainer.decodeIfPresent(OpenVPN.CryptoContainer.self, forKey: .ca) - sessionBuilder.clientCertificate = try cfgContainer.decodeIfPresent(OpenVPN.CryptoContainer.self, forKey: .clientCertificate) - sessionBuilder.clientKey = try cfgContainer.decodeIfPresent(OpenVPN.CryptoContainer.self, forKey: .clientKey) - sessionBuilder.tlsWrap = try cfgContainer.decodeIfPresent(OpenVPN.TLSWrap.self, forKey: .tlsWrap) - sessionBuilder.keepAliveInterval = try cfgContainer.decodeIfPresent(TimeInterval.self, forKey: .keepAliveSeconds) - sessionBuilder.keepAliveTimeout = try cfgContainer.decodeIfPresent(TimeInterval.self, forKey: .keepAliveTimeoutSeconds) - sessionBuilder.renegotiatesAfter = try cfgContainer.decodeIfPresent(TimeInterval.self, forKey: .renegotiatesAfterSeconds) - sessionBuilder.endpointProtocols = try cfgContainer.decode([EndpointProtocol].self, forKey: .endpointProtocols) - sessionBuilder.checksEKU = try cfgContainer.decodeIfPresent(Bool.self, forKey: .checksEKU) ?? false - sessionBuilder.randomizeEndpoint = try cfgContainer.decodeIfPresent(Bool.self, forKey: .randomizeEndpoint) ?? false - sessionBuilder.usesPIAPatches = try cfgContainer.decodeIfPresent(Bool.self, forKey: .usesPIAPatches) ?? false - - // default to server settings - sessionBuilder.routingPolicies = nil - - let builder = OpenVPNProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build()) - configuration = builder.build() - } - - public func encode(to encoder: Encoder) throws { - guard let ca = configuration.sessionConfiguration.ca else { - fatalError("Could not encode nil ca") - } - guard let endpointProtocols = configuration.sessionConfiguration.endpointProtocols else { - fatalError("Could not encode nil endpointProtocols") - } - - var container = encoder.container(keyedBy: PresetKeys.self) - try container.encode(id, forKey: .id) - try container.encode(name, forKey: .name) - try container.encode(comment, forKey: .comment) - if let external = external { - var rawExternal: [String: String] = [:] - for entry in external { - rawExternal[entry.key.rawValue] = entry.value - } - try container.encode(rawExternal, forKey: .external) - } - - var cfgContainer = container.nestedContainer(keyedBy: ConfigurationKeys.self, forKey: .configuration) - try cfgContainer.encode(configuration.sessionConfiguration.cipher, forKey: .cipher) - try cfgContainer.encode(configuration.sessionConfiguration.digest, forKey: .digest) - try cfgContainer.encode(configuration.sessionConfiguration.compressionFraming, forKey: .compressionFraming) - try cfgContainer.encodeIfPresent(configuration.sessionConfiguration.compressionAlgorithm, forKey: .compressionAlgorithm) - try cfgContainer.encodeIfPresent(ca, forKey: .ca) - try cfgContainer.encodeIfPresent(configuration.sessionConfiguration.clientCertificate, forKey: .clientCertificate) - try cfgContainer.encodeIfPresent(configuration.sessionConfiguration.clientKey, forKey: .clientKey) - try cfgContainer.encodeIfPresent(configuration.sessionConfiguration.tlsWrap, forKey: .tlsWrap) - try cfgContainer.encodeIfPresent(configuration.sessionConfiguration.keepAliveInterval, forKey: .keepAliveSeconds) - try cfgContainer.encodeIfPresent(configuration.sessionConfiguration.keepAliveTimeout, forKey: .keepAliveTimeoutSeconds) - try cfgContainer.encodeIfPresent(configuration.sessionConfiguration.renegotiatesAfter, forKey: .renegotiatesAfterSeconds) - try cfgContainer.encode(endpointProtocols, forKey: .endpointProtocols) - try cfgContainer.encodeIfPresent(configuration.sessionConfiguration.checksEKU, forKey: .checksEKU) - try cfgContainer.encodeIfPresent(configuration.sessionConfiguration.randomizeEndpoint, forKey: .randomizeEndpoint) - try cfgContainer.encodeIfPresent(configuration.sessionConfiguration.usesPIAPatches, forKey: .usesPIAPatches) - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Services/Pool.swift b/PassepartoutCore/Sources/PassepartoutCore/Services/Pool.swift deleted file mode 100644 index e7bcce15..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Services/Pool.swift +++ /dev/null @@ -1,185 +0,0 @@ -// -// Pool.swift -// Passepartout -// -// Created by Davide De Rosa on 6/11/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import TunnelKit - -public class Pool: Codable, Hashable { - public enum CodingKeys: String, CodingKey { - case id - - case country - - case extraCountries = "extra_countries" - - case area - - case num - - case tags - -// case location - - case hostname - - case isResolved = "resolved" - - case numericAddresses = "addrs" - } - - public let id: String - - public let country: String - - public let extraCountries: [String]? - - public let area: String? - - public let num: Int? - - public let tags: [String]? - -// public let location: (Double, Double) - - public let hostname: String? - - public let isResolved: Bool? - - public let numericAddresses: [UInt32]? - - // XXX: inefficient but convenient field (not serialized) - public func category(in infrastructure: Infrastructure) -> PoolCategory? { - for category in infrastructure.categories { - for group in category.groups { - for pool in group.pools { - if pool.id == id { - return category - } - } - } - } - return nil - } - - public func supportedPresetIds(in infrastructure: Infrastructure) -> [String] { - let poolCategory = category(in: infrastructure) - return poolCategory?.presets ?? infrastructure.presets.map { $0.id } - } - - public func hasAddress(_ address: String) -> Bool { - guard let numericAddresses = numericAddresses else { - return false - } - guard let ipv4 = DNSResolver.ipv4(fromString: address) else { - return false - } - return numericAddresses.contains(ipv4) - } - - // XXX: inefficient, can't easily use lazy on struct - public func addresses() -> [String] { - var addrs = numericAddresses?.map { DNSResolver.string(fromIPv4: $0) } ?? [] - if let hostname = hostname { - addrs.insert(hostname, at: 0) - } - return addrs - } - - // MARK: Equatable - - public static func == (lhs: Pool, rhs: Pool) -> Bool { - return lhs.id == rhs.id - } - - // MARK: Hashable - - public func hash(into hasher: inout Hasher) { - id.hash(into: &hasher) - } -} - -extension Pool { - public var localizedCountry: String { - return Utils.localizedCountry(country) - } - - public var localizedId: String { - var comps: [String] = [localizedCountry] - if let secondaryId = optionalSecondaryId { - comps.append(secondaryId) - } - return comps.joined(separator: " - ") - } - - public var secondaryId: String { - return optionalSecondaryId ?? "" - } - - private var optionalSecondaryId: String? { - var comps: [String] = [] - if let extraCountries = extraCountries { - comps.append(contentsOf: extraCountries.map { Utils.localizedCountry($0) }) - } - if let area = area { -// comps.append(area.uppercased()) - comps.append(area.capitalized) - } - if let num = num { - comps.append("#\(num)") - } - guard !comps.isEmpty else { - return nil - } - var str = comps.joined(separator: " ") - if let tags = tags { - let suffix = tags.map { $0.uppercased() }.joined(separator: ",") - str = "\(str) (\(suffix))" - } - return str - } -} - -public extension Array where Element: Pool { - func sortedPools() -> [Element] { - return sorted { - guard let larea = $0.area else { - guard let lnum = $0.num else { - return true - } - guard let rnum = $1.num else { - return false - } - guard lnum != rnum else { - return $0.secondaryId < $1.secondaryId - } - return lnum < rnum - } - guard let rarea = $1.area else { - return false - } - return larea < rarea - } - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Services/PoolGroup.swift b/PassepartoutCore/Sources/PassepartoutCore/Services/PoolGroup.swift deleted file mode 100644 index e93895a7..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Services/PoolGroup.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// PoolGroup.swift -// Passepartout -// -// Created by Davide De Rosa on 4/6/19. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation - -public class PoolGroup: Codable, Hashable, Comparable, CustomStringConvertible { - public let country: String - - public let pools: [Pool] - - private var id: String { - return country - } - - private var localizedId: String { - return Utils.localizedCountry(country) - } - - // MARK: Equatable - - public static func ==(lhs: PoolGroup, rhs: PoolGroup) -> Bool { - return lhs.localizedId == rhs.localizedId - } - - // MARK: Hashable - - public func hash(into hasher: inout Hasher) { - id.hash(into: &hasher) - } - - // MARK: Comparable - - public static func <(lhs: PoolGroup, rhs: PoolGroup) -> Bool { - return lhs.localizedId < rhs.localizedId - } - - // MARK: CustomStringConvertible - - public var description: String { - return country - } -} - -extension PoolGroup { - public var localizedCountry: String { - return Utils.localizedCountry(country) - } -} - -extension PoolGroup { - public func uniqueId(in category: PoolCategory) -> String { - var components: [String] = [] - components.append(category.name) - components.append(country) - return components.joined(separator: "/") - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Services/WebServices.swift b/PassepartoutCore/Sources/PassepartoutCore/Services/WebServices.swift deleted file mode 100644 index 78ac80cc..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Services/WebServices.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// WebServices.swift -// Passepartout -// -// Created by Davide De Rosa on 9/14/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import Convenience -import PassepartoutConstants - -public class WebServices { - public enum Group: String { - case providers - } - - public enum Endpoint: Convenience.Endpoint { - case providersIndex - - case providerNetwork(InfrastructureName) - - var pathName: String { - switch self { - case .providersIndex: - return "\(Group.providers.rawValue)/index" - - case .providerNetwork(let name): - return "\(Group.providers.rawValue)/\(name)/net" - } - } - - var fileType: String { - return "json" - } - - var path: String { - return "\(pathName).\(fileType)" - } - - public func apiURL(relativeTo url: URL) -> URL { - return url.appendingPathComponent(AppConstants.Store.apiDirectory).appendingPathComponent(path) - } - - public func bundleURL(in bundle: Bundle) -> URL? { - return bundle.url(forResource: "\(AppConstants.Store.apiDirectory)/\(pathName)", withExtension: fileType) - } - - // MARK: Endpoint - - public var url: URL { - return AppConstants.Services.apiURL(version: AppConstants.Services.version, path: path) - } - } - - public static let shared = WebServices() - - private let ws: ReadonlyWebServices - - private init() { - ws = ReadonlyWebServices() - ws.timeout = AppConstants.Services.timeout - } - - public func providersIndex(completionHandler: @escaping ([Infrastructure.Metadata]?, Error?) -> Void) { - let request = ws.get(WebServices.Endpoint.providersIndex) - ws.parse([Infrastructure.Metadata].self, request: request) { - completionHandler($0?.value, $1) - } - } - - public func providerNetwork(with name: InfrastructureName, ifModifiedSince lastModified: Date?, completionHandler: @escaping (Response?, Error?) -> Void) { - var request = ws.get(WebServices.Endpoint.providerNetwork(name)) - if let lastModified = lastModified { - request.addValue(ResponseParser.lastModifiedString(date: lastModified), forHTTPHeaderField: "If-Modified-Since") - } - ws.parse(Infrastructure.self, request: request, completionHandler: completionHandler) - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/SwiftGen+Strings.swift b/PassepartoutCore/Sources/PassepartoutCore/SwiftGen+Strings.swift deleted file mode 100644 index 9c221550..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/SwiftGen+Strings.swift +++ /dev/null @@ -1,1289 +0,0 @@ -// swiftlint:disable all -// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen - -import Foundation - -// swiftlint:disable superfluous_disable_command file_length implicit_return - -// MARK: - Strings - -// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length -// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces -public enum L10n { - - public enum About { - /// About - public static let title = L10n.tr("Core", "about.title") - public enum Cells { - public enum Credits { - /// Credits - public static let caption = L10n.tr("Core", "about.cells.credits.caption") - } - public enum Disclaimer { - /// Disclaimer - public static let caption = L10n.tr("Core", "about.cells.disclaimer.caption") - } - public enum Faq { - /// FAQ - public static let caption = L10n.tr("Core", "about.cells.faq.caption") - } - public enum PrivacyPolicy { - /// Privacy policy - public static let caption = L10n.tr("Core", "about.cells.privacy_policy.caption") - } - public enum ShareGeneric { - /// Invite a friend - public static let caption = L10n.tr("Core", "about.cells.share_generic.caption") - } - public enum ShareTwitter { - /// Tweet about it! - public static let caption = L10n.tr("Core", "about.cells.share_twitter.caption") - } - public enum Website { - /// Home page - public static let caption = L10n.tr("Core", "about.cells.website.caption") - } - } - public enum Sections { - public enum Share { - /// Share - public static let header = L10n.tr("Core", "about.sections.share.header") - } - public enum Web { - /// Web - public static let header = L10n.tr("Core", "about.sections.web.header") - } - } - } - - public enum Account { - /// Account - public static let title = L10n.tr("Core", "account.title") - public enum Cells { - public enum OpenGuide { - /// See your credentials - public static let caption = L10n.tr("Core", "account.cells.open_guide.caption") - } - public enum Password { - /// Password - public static let caption = L10n.tr("Core", "account.cells.password.caption") - /// secret - public static let placeholder = L10n.tr("Core", "account.cells.password.placeholder") - } - public enum Signup { - /// Register with %@ - public static func caption(_ p1: Any) -> String { - return L10n.tr("Core", "account.cells.signup.caption", String(describing: p1)) - } - } - public enum Username { - /// Username - public static let caption = L10n.tr("Core", "account.cells.username.caption") - /// username - public static let placeholder = L10n.tr("Core", "account.cells.username.placeholder") - } - } - public enum Sections { - public enum Credentials { - /// Credentials - public static let header = L10n.tr("Core", "account.sections.credentials.header") - } - public enum Guidance { - public enum Footer { - public enum Infrastructure { - /// Use your %@ website credentials. Your username is usually numeric (without spaces). - public static func mullvad(_ p1: Any) -> String { - return L10n.tr("Core", "account.sections.guidance.footer.infrastructure.mullvad", String(describing: p1)) - } - /// Use your %@ website credentials. Your username is usually your e-mail. - public static func nordvpn(_ p1: Any) -> String { - return L10n.tr("Core", "account.sections.guidance.footer.infrastructure.nordvpn", String(describing: p1)) - } - /// Use your %@ website credentials. Your username is usually numeric with a "p" prefix. - public static func pia(_ p1: Any) -> String { - return L10n.tr("Core", "account.sections.guidance.footer.infrastructure.pia", String(describing: p1)) - } - /// Find your %@ credentials in the "Account > OpenVPN / IKEv2 Username" section of the website. - public static func protonvpn(_ p1: Any) -> String { - return L10n.tr("Core", "account.sections.guidance.footer.infrastructure.protonvpn", String(describing: p1)) - } - /// Use your %@ website credentials. Your username is usually your e-mail. - public static func tunnelbear(_ p1: Any) -> String { - return L10n.tr("Core", "account.sections.guidance.footer.infrastructure.tunnelbear", String(describing: p1)) - } - /// Use your %@ website credentials. Your username is usually your e-mail. - public static func vyprvpn(_ p1: Any) -> String { - return L10n.tr("Core", "account.sections.guidance.footer.infrastructure.vyprvpn", String(describing: p1)) - } - /// Find your %@ credentials in the OpenVPN Config Generator on the website. - public static func windscribe(_ p1: Any) -> String { - return L10n.tr("Core", "account.sections.guidance.footer.infrastructure.windscribe", String(describing: p1)) - } - public enum Default { - /// Use your %@ service credentials, which may differ from website credentials. - public static func specific(_ p1: Any) -> String { - return L10n.tr("Core", "account.sections.guidance.footer.infrastructure.default.specific", String(describing: p1)) - } - /// Use your %@ website credentials. - public static func web(_ p1: Any) -> String { - return L10n.tr("Core", "account.sections.guidance.footer.infrastructure.default.web", String(describing: p1)) - } - } - } - } - } - public enum Registration { - /// Go get an account on the %@ website. - public static func footer(_ p1: Any) -> String { - return L10n.tr("Core", "account.sections.registration.footer", String(describing: p1)) - } - } - } - } - - public enum Configuration { - /// Configuration - public static let title = L10n.tr("Core", "configuration.title") - public enum Alerts { - public enum Commit { - /// New parameters will not be effective until you reconnect manually. Changes in trusted networks will apply immediately. - public static let message = L10n.tr("Core", "configuration.alerts.commit.message") - public enum Buttons { - /// Reconnect now - public static let reconnect = L10n.tr("Core", "configuration.alerts.commit.buttons.reconnect") - /// Skip - public static let skip = L10n.tr("Core", "configuration.alerts.commit.buttons.skip") - } - } - } - public enum Cells { - public enum Cipher { - /// Cipher - public static let caption = L10n.tr("Core", "configuration.cells.cipher.caption") - } - public enum Client { - /// Client certificate - public static let caption = L10n.tr("Core", "configuration.cells.client.caption") - public enum Value { - /// Not verified - public static let disabled = L10n.tr("Core", "configuration.cells.client.value.disabled") - /// Verified - public static let enabled = L10n.tr("Core", "configuration.cells.client.value.enabled") - } - } - public enum CompressionAlgorithm { - /// Algorithm - public static let caption = L10n.tr("Core", "configuration.cells.compression_algorithm.caption") - public enum Value { - /// LZO - public static let lzo = L10n.tr("Core", "configuration.cells.compression_algorithm.value.lzo") - /// Unsupported - public static let other = L10n.tr("Core", "configuration.cells.compression_algorithm.value.other") - } - } - public enum CompressionFraming { - /// Framing - public static let caption = L10n.tr("Core", "configuration.cells.compression_framing.caption") - public enum Value { - /// --compress - public static let compress = L10n.tr("Core", "configuration.cells.compression_framing.value.compress") - /// --comp-lzo - public static let lzo = L10n.tr("Core", "configuration.cells.compression_framing.value.lzo") - } - } - public enum Digest { - /// Authentication - public static let caption = L10n.tr("Core", "configuration.cells.digest.caption") - public enum Value { - /// Embedded - public static let embedded = L10n.tr("Core", "configuration.cells.digest.value.embedded") - } - } - public enum Eku { - /// Extended verification - public static let caption = L10n.tr("Core", "configuration.cells.eku.caption") - } - public enum KeepAlive { - /// Keep-alive - public static let caption = L10n.tr("Core", "configuration.cells.keep_alive.caption") - public enum Value { - /// %d seconds - public static func seconds(_ p1: Int) -> String { - return L10n.tr("Core", "configuration.cells.keep_alive.value.seconds", p1) - } - } - } - public enum RandomEndpoint { - /// Randomize endpoint - public static let caption = L10n.tr("Core", "configuration.cells.random_endpoint.caption") - } - public enum RenegotiationSeconds { - /// Renegotiation - public static let caption = L10n.tr("Core", "configuration.cells.renegotiation_seconds.caption") - public enum Value { - /// after %@ - public static func after(_ p1: Any) -> String { - return L10n.tr("Core", "configuration.cells.renegotiation_seconds.value.after", String(describing: p1)) - } - } - } - public enum ResetOriginal { - /// Reset configuration - public static let caption = L10n.tr("Core", "configuration.cells.reset_original.caption") - } - public enum TlsWrapping { - /// Wrapping - public static let caption = L10n.tr("Core", "configuration.cells.tls_wrapping.caption") - public enum Value { - /// Authentication - public static let auth = L10n.tr("Core", "configuration.cells.tls_wrapping.value.auth") - /// Encryption - public static let crypt = L10n.tr("Core", "configuration.cells.tls_wrapping.value.crypt") - } - } - } - public enum Sections { - public enum Communication { - /// Communication - public static let header = L10n.tr("Core", "configuration.sections.communication.header") - } - public enum Compression { - /// Compression - public static let header = L10n.tr("Core", "configuration.sections.compression.header") - } - public enum Network { - /// Network - public static let header = L10n.tr("Core", "configuration.sections.network.header") - } - public enum Other { - /// Other - public static let header = L10n.tr("Core", "configuration.sections.other.header") - } - public enum Reset { - /// If you ended up with broken connectivity after changing the communication parameters, tap to revert to the original configuration. - public static let footer = L10n.tr("Core", "configuration.sections.reset.footer") - } - public enum Tls { - /// TLS - public static let header = L10n.tr("Core", "configuration.sections.tls.header") - } - } - } - - public enum Credits { - /// Credits - public static let title = L10n.tr("Core", "credits.title") - public enum Sections { - public enum Licenses { - /// Licenses - public static let header = L10n.tr("Core", "credits.sections.licenses.header") - } - public enum Notices { - /// Notices - public static let header = L10n.tr("Core", "credits.sections.notices.header") - } - public enum Translations { - /// Translations - public static let header = L10n.tr("Core", "credits.sections.translations.header") - } - } - } - - public enum DebugLog { - public enum Alerts { - public enum EmptyLog { - /// The debug log is empty. - public static let message = L10n.tr("Core", "debug_log.alerts.empty_log.message") - } - } - public enum Buttons { - /// Copy - public static let copy = L10n.tr("Core", "debug_log.buttons.copy") - /// Next - public static let next = L10n.tr("Core", "debug_log.buttons.next") - /// Previous - public static let previous = L10n.tr("Core", "debug_log.buttons.previous") - } - } - - public enum Donation { - /// Donate - public static let title = L10n.tr("Core", "donation.title") - public enum Alerts { - public enum Purchase { - public enum Failure { - /// Unable to perform the donation. %@ - public static func message(_ p1: Any) -> String { - return L10n.tr("Core", "donation.alerts.purchase.failure.message", String(describing: p1)) - } - } - public enum Success { - /// This means a lot to me and I really hope you keep using and promoting this app. - public static let message = L10n.tr("Core", "donation.alerts.purchase.success.message") - /// Thank you - public static let title = L10n.tr("Core", "donation.alerts.purchase.success.title") - } - } - } - public enum Cells { - public enum Loading { - /// Loading donations - public static let caption = L10n.tr("Core", "donation.cells.loading.caption") - } - public enum Purchasing { - /// Performing donation - public static let caption = L10n.tr("Core", "donation.cells.purchasing.caption") - } - } - public enum Sections { - public enum OneTime { - /// If you want to display gratitude for my free work, here are a couple amounts you can donate instantly.\n\nYou will only be charged once per donation, and you can donate multiple times. - public static let footer = L10n.tr("Core", "donation.sections.one_time.footer") - /// One time - public static let header = L10n.tr("Core", "donation.sections.one_time.header") - } - } - } - - public enum Endpoint { - /// Endpoint - public static let title = L10n.tr("Core", "endpoint.title") - public enum Cells { - /// Address - public static let address = L10n.tr("Core", "endpoint.cells.address") - /// Protocol - public static let `protocol` = L10n.tr("Core", "endpoint.cells.protocol") - public enum AnyAddress { - /// Automatic - public static let caption = L10n.tr("Core", "endpoint.cells.any_address.caption") - } - public enum AnyProtocol { - /// Automatic - public static let caption = L10n.tr("Core", "endpoint.cells.any_protocol.caption") - } - } - public enum Sections { - public enum LocationAddresses { - /// Addresses - public static let header = L10n.tr("Core", "endpoint.sections.location_addresses.header") - } - public enum LocationProtocols { - /// Protocols - public static let header = L10n.tr("Core", "endpoint.sections.location_protocols.header") - } - } - } - - public enum Global { - /// Cancel - public static let cancel = L10n.tr("Core", "global.cancel") - /// Close - public static let close = L10n.tr("Core", "global.close") - /// No e-mail account is configured. - public static let emailNotConfigured = L10n.tr("Core", "global.email_not_configured") - /// Next - public static let next = L10n.tr("Core", "global.next") - /// OK - public static let ok = L10n.tr("Core", "global.ok") - public enum Captions { - /// Address - public static let address = L10n.tr("Core", "global.captions.address") - /// Port - public static let port = L10n.tr("Core", "global.captions.port") - /// Protocol - public static let `protocol` = L10n.tr("Core", "global.captions.protocol") - } - public enum Host { - public enum TitleInput { - /// Acceptable characters are alphanumerics plus dash "-", underscore "_" and dot ".". - public static let message = L10n.tr("Core", "global.host.title_input.message") - /// My profile - public static let placeholder = L10n.tr("Core", "global.host.title_input.placeholder") - } - } - public enum Values { - /// Automatic - public static let automatic = L10n.tr("Core", "global.values.automatic") - /// Default - public static let `default` = L10n.tr("Core", "global.values.default") - /// Disabled - public static let disabled = L10n.tr("Core", "global.values.disabled") - /// Enabled - public static let enabled = L10n.tr("Core", "global.values.enabled") - /// Manual - public static let manual = L10n.tr("Core", "global.values.manual") - /// None - public static let `none` = L10n.tr("Core", "global.values.none") - } - } - - public enum ImportedHosts { - /// Imported hosts - public static let title = L10n.tr("Core", "imported_hosts.title") - } - - public enum IssueReporter { - /// The debug log of your latest connections is crucial to resolve your connectivity issues and is completely anonymous.\n\nThe .ovpn configuration file, if any, is attached stripped of any sensitive data.\n\nPlease double check the e-mail attachments if unsure. - public static let message = L10n.tr("Core", "issue_reporter.message") - /// Report issue - public static let title = L10n.tr("Core", "issue_reporter.title") - public enum Buttons { - /// I understand - public static let accept = L10n.tr("Core", "issue_reporter.buttons.accept") - } - } - - public enum Label { - public enum License { - /// Unable to download full license content. - public static let error = L10n.tr("Core", "label.license.error") - } - } - - public enum Menu { - public enum ActiveProfile { - public enum Items { - public enum Customize { - /// Customize... - public static let title = L10n.tr("Core", "menu.active_profile.items.customize.title") - } - } - public enum Messages { - /// No account configured - public static let missingCredentials = L10n.tr("Core", "menu.active_profile.messages.missing_credentials") - } - public enum Title { - /// No active profile - public static let `none` = L10n.tr("Core", "menu.active_profile.title.none") - } - } - public enum Organizer { - /// Organizer - public static let title = L10n.tr("Core", "menu.organizer.title") - } - public enum Preferences { - /// Preferences - public static let title = L10n.tr("Core", "menu.preferences.title") - } - public enum Quit { - /// Quit %@ - public static func title(_ p1: Any) -> String { - return L10n.tr("Core", "menu.quit.title", String(describing: p1)) - } - public enum Messages { - /// The VPN, if enabled, will still run in the background. Do you want to quit? - public static let confirm = L10n.tr("Core", "menu.quit.messages.confirm") - } - } - public enum Show { - /// Show - public static let title = L10n.tr("Core", "menu.show.title") - } - public enum Support { - /// Support - public static let title = L10n.tr("Core", "menu.support.title") - } - public enum SwitchProfile { - /// Active profile - public static let title = L10n.tr("Core", "menu.switch_profile.title") - } - } - - public enum NetworkChoice { - /// Read .ovpn - public static let client = L10n.tr("Core", "network_choice.client") - /// Pull from server - public static let server = L10n.tr("Core", "network_choice.server") - } - - public enum NetworkSettings { - /// Network settings - public static let title = L10n.tr("Core", "network_settings.title") - public enum Cells { - public enum AddDnsDomain { - /// Add search domain - public static let caption = L10n.tr("Core", "network_settings.cells.add_dns_domain.caption") - } - public enum AddDnsServer { - /// Add address - public static let caption = L10n.tr("Core", "network_settings.cells.add_dns_server.caption") - } - public enum AddProxyBypass { - /// Add bypass domain - public static let caption = L10n.tr("Core", "network_settings.cells.add_proxy_bypass.caption") - } - public enum ProxyBypass { - /// Bypass domain - public static let caption = L10n.tr("Core", "network_settings.cells.proxy_bypass.caption") - } - } - public enum Dns { - /// DNS - public static let title = L10n.tr("Core", "network_settings.dns.title") - public enum Cells { - public enum Addresses { - /// Servers - public static let title = L10n.tr("Core", "network_settings.dns.cells.addresses.title") - } - public enum Domain { - /// Domain - public static let caption = L10n.tr("Core", "network_settings.dns.cells.domain.caption") - } - public enum Domains { - /// Domains - public static let title = L10n.tr("Core", "network_settings.dns.cells.domains.title") - } - } - } - public enum Gateway { - /// Default gateway - public static let title = L10n.tr("Core", "network_settings.gateway.title") - } - public enum Mtu { - /// MTU - public static let title = L10n.tr("Core", "network_settings.mtu.title") - public enum Cells { - public enum Bytes { - /// Bytes - public static let caption = L10n.tr("Core", "network_settings.mtu.cells.bytes.caption") - } - } - } - public enum Proxy { - /// Proxy - public static let title = L10n.tr("Core", "network_settings.proxy.title") - public enum Cells { - public enum BypassDomains { - /// Bypass domains - public static let title = L10n.tr("Core", "network_settings.proxy.cells.bypass_domains.title") - } - } - } - } - - public enum Organizer { - /// %@ - public static func title(_ p1: Any) -> String { - return L10n.tr("Core", "organizer.title", String(describing: p1)) - } - public enum Alerts { - public enum AddHost { - /// Open an URL to an .ovpn configuration file from Safari, Mail or another app to set up a host profile.\n\nYou can also import an .ovpn with iTunes File Sharing. - public static let message = L10n.tr("Core", "organizer.alerts.add_host.message") - } - public enum CannotDonate { - /// There is no payment method configured on this device. - public static let message = L10n.tr("Core", "organizer.alerts.cannot_donate.message") - } - public enum DeleteVpnProfile { - /// Do you really want to erase the VPN configuration from your device settings? This may fix some broken VPN states and will not affect your provider and host profiles. - public static let message = L10n.tr("Core", "organizer.alerts.delete_vpn_profile.message") - } - public enum ExhaustedProviders { - /// You have created profiles for any available provider. - public static let message = L10n.tr("Core", "organizer.alerts.exhausted_providers.message") - } - public enum OpenHostFile { - /// Select an .ovpn file - public static let title = L10n.tr("Core", "organizer.alerts.open_host_file.title") - } - public enum RemoveProfile { - /// Are you sure you want to delete profile %@? - public static func message(_ p1: Any) -> String { - return L10n.tr("Core", "organizer.alerts.remove_profile.message", String(describing: p1)) - } - /// Remove profile - public static let title = L10n.tr("Core", "organizer.alerts.remove_profile.title") - } - } - public enum Cells { - public enum About { - /// About %@ - public static func caption(_ p1: Any) -> String { - return L10n.tr("Core", "organizer.cells.about.caption", String(describing: p1)) - } - } - public enum AddHost { - /// Add from Files - public static let caption = L10n.tr("Core", "organizer.cells.add_host.caption") - } - public enum AddProvider { - /// Add new provider - public static let caption = L10n.tr("Core", "organizer.cells.add_provider.caption") - } - public enum Donate { - /// Make a donation - public static let caption = L10n.tr("Core", "organizer.cells.donate.caption") - } - public enum FollowTwitch { - /// Watch Passepartout on Twitch - public static let caption = L10n.tr("Core", "organizer.cells.follow_twitch.caption") - } - public enum GithubSponsors { - /// Support me on GitHub - public static let caption = L10n.tr("Core", "organizer.cells.github_sponsors.caption") - } - public enum ImportHost { - /// Add from imported - public static let caption = L10n.tr("Core", "organizer.cells.import_host.caption") - } - public enum JoinCommunity { - /// Join community - public static let caption = L10n.tr("Core", "organizer.cells.join_community.caption") - } - public enum Profile { - public enum Value { - /// In use - public static let current = L10n.tr("Core", "organizer.cells.profile.value.current") - } - } - public enum SiriShortcuts { - /// Manage shortcuts - public static let caption = L10n.tr("Core", "organizer.cells.siri_shortcuts.caption") - } - public enum Translate { - /// Offer to translate - public static let caption = L10n.tr("Core", "organizer.cells.translate.caption") - } - public enum Uninstall { - /// Remove VPN configuration - public static let caption = L10n.tr("Core", "organizer.cells.uninstall.caption") - } - public enum WriteReview { - /// Write a review - public static let caption = L10n.tr("Core", "organizer.cells.write_review.caption") - } - } - public enum Menus { - /// Host - public static let host = L10n.tr("Core", "organizer.menus.host") - /// Provider - public static let provider = L10n.tr("Core", "organizer.menus.provider") - public enum Provider { - /// No providers left - public static let unavailable = L10n.tr("Core", "organizer.menus.provider.unavailable") - } - } - public enum Sections { - public enum Feedback { - /// Feedback - public static let header = L10n.tr("Core", "organizer.sections.feedback.header") - } - public enum Hosts { - /// Import hosts from raw .ovpn configuration files. - public static let footer = L10n.tr("Core", "organizer.sections.hosts.footer") - /// Hosts - public static let header = L10n.tr("Core", "organizer.sections.hosts.header") - } - public enum Providers { - /// Here you find a few providers with preset configuration profiles. - public static let footer = L10n.tr("Core", "organizer.sections.providers.footer") - /// Providers - public static let header = L10n.tr("Core", "organizer.sections.providers.header") - } - public enum Siri { - /// Get help from Siri to speed up your most common interactions with the app. - public static let footer = L10n.tr("Core", "organizer.sections.siri.footer") - /// Siri - public static let header = L10n.tr("Core", "organizer.sections.siri.header") - } - public enum Support { - /// Support - public static let header = L10n.tr("Core", "organizer.sections.support.header") - } - public enum Twitch { - /// Come watch me make Passepartout live on Twitch, join the chat to interact and contribute! - public static let footer = L10n.tr("Core", "organizer.sections.twitch.footer") - /// Twitch - public static let header = L10n.tr("Core", "organizer.sections.twitch.header") - } - } - } - - public enum ParsedFile { - public enum Alerts { - public enum Buttons { - /// Report an issue - public static let report = L10n.tr("Core", "parsed_file.alerts.buttons.report") - } - public enum Decryption { - /// The configuration contains an encrypted private key and it could not be decrypted. Double check your entered passphrase. - public static let message = L10n.tr("Core", "parsed_file.alerts.decryption.message") - } - public enum EncryptionPassphrase { - /// Please enter the encryption passphrase. - public static let message = L10n.tr("Core", "parsed_file.alerts.encryption_passphrase.message") - } - public enum Malformed { - /// The configuration file contains a malformed option (%@). - public static func message(_ p1: Any) -> String { - return L10n.tr("Core", "parsed_file.alerts.malformed.message", String(describing: p1)) - } - } - public enum Missing { - /// The configuration file lacks a required option (%@). - public static func message(_ p1: Any) -> String { - return L10n.tr("Core", "parsed_file.alerts.missing.message", String(describing: p1)) - } - } - public enum Parsing { - /// Unable to parse the provided configuration file (%@). - public static func message(_ p1: Any) -> String { - return L10n.tr("Core", "parsed_file.alerts.parsing.message", String(describing: p1)) - } - } - public enum PotentiallyUnsupported { - /// The configuration file is correct but contains a potentially unsupported option (%@).\n\nConnectivity may break depending on server settings. - public static func message(_ p1: Any) -> String { - return L10n.tr("Core", "parsed_file.alerts.potentially_unsupported.message", String(describing: p1)) - } - } - public enum Unsupported { - /// The configuration file contains an unsupported option (%@). - public static func message(_ p1: Any) -> String { - return L10n.tr("Core", "parsed_file.alerts.unsupported.message", String(describing: p1)) - } - } - } - } - - public enum Preferences { - /// Preferences - public static let title = L10n.tr("Core", "preferences.title") - public enum Cells { - public enum ConfirmQuit { - /// Confirm quit - public static let caption = L10n.tr("Core", "preferences.cells.confirm_quit.caption") - /// Check to present a quit confirmation alert. - public static let footer = L10n.tr("Core", "preferences.cells.confirm_quit.footer") - } - public enum LaunchesOnLogin { - /// Launch on login - public static let caption = L10n.tr("Core", "preferences.cells.launches_on_login.caption") - /// Check to automatically launch the app on boot or login. - public static let footer = L10n.tr("Core", "preferences.cells.launches_on_login.footer") - } - } - public enum Sections { - public enum General { - /// General - public static let header = L10n.tr("Core", "preferences.sections.general.header") - } - } - } - - public enum Provider { - public enum Pool { - public enum Actions { - /// Favorite - public static let favorite = L10n.tr("Core", "provider.pool.actions.favorite") - /// Unfavorite - public static let unfavorite = L10n.tr("Core", "provider.pool.actions.unfavorite") - } - public enum Sections { - public enum EmptyFavorites { - /// Swipe left on a location to add or remove it from Favorites. - public static let footer = L10n.tr("Core", "provider.pool.sections.empty_favorites.footer") - } - } - } - public enum Preset { - public enum Cells { - public enum TechDetails { - /// Technical details - public static let caption = L10n.tr("Core", "provider.preset.cells.tech_details.caption") - } - } - } - } - - public enum Purchase { - /// Purchase - public static let title = L10n.tr("Core", "purchase.title") - public enum Cells { - public enum FullVersion { - /// All providers (including future ones)\n%@ - public static func extraDescription(_ p1: Any) -> String { - return L10n.tr("Core", "purchase.cells.full_version.extra_description", String(describing: p1)) - } - } - public enum Restore { - /// If you bought this app or feature in the past, you can restore your purchases and this screen won't show again. - public static let description = L10n.tr("Core", "purchase.cells.restore.description") - /// Restore purchases - public static let title = L10n.tr("Core", "purchase.cells.restore.title") - } - } - public enum Sections { - public enum Products { - /// Every product is a one-time purchase. Provider purchases do not include a VPN subscription. - public static let footer = L10n.tr("Core", "purchase.sections.products.footer") - } - } - } - - public enum Reddit { - /// Did you know that Passepartout has a subreddit? Subscribe for updates or to discuss issues, features, new platforms or whatever you like.\n\nIt's also a great way to show you care about this project. - public static let message = L10n.tr("Core", "reddit.message") - /// Reddit - public static let title = L10n.tr("Core", "reddit.title") - public enum Buttons { - /// Don't ask again - public static let never = L10n.tr("Core", "reddit.buttons.never") - /// Remind me later - public static let remind = L10n.tr("Core", "reddit.buttons.remind") - /// Subscribe now! - public static let subscribe = L10n.tr("Core", "reddit.buttons.subscribe") - } - } - - public enum ServerNetwork { - public enum Cells { - public enum Route { - /// Route - public static let caption = L10n.tr("Core", "server_network.cells.route.caption") - } - } - } - - public enum Service { - public enum Alerts { - public enum Buttons { - /// Reconnect - public static let reconnect = L10n.tr("Core", "service.alerts.buttons.reconnect") - } - public enum Configuration { - /// Configuration unavailable, make sure you are connected to the VPN. - public static let disconnected = L10n.tr("Core", "service.alerts.configuration.disconnected") - } - public enum CredentialsNeeded { - /// You need to enter account credentials first. - public static let message = L10n.tr("Core", "service.alerts.credentials_needed.message") - } - public enum Download { - /// Failed to download configuration files. %@ - public static func failed(_ p1: Any) -> String { - return L10n.tr("Core", "service.alerts.download.failed", String(describing: p1)) - } - /// %@ requires the download of additional configuration files.\n\nConfirm to start the download. - public static func message(_ p1: Any) -> String { - return L10n.tr("Core", "service.alerts.download.message", String(describing: p1)) - } - /// Download required - public static let title = L10n.tr("Core", "service.alerts.download.title") - public enum Hud { - /// Extracting files, please be patient... - public static let extracting = L10n.tr("Core", "service.alerts.download.hud.extracting") - } - } - public enum Location { - public enum Button { - /// Settings - public static let settings = L10n.tr("Core", "service.alerts.location.button.settings") - } - public enum Message { - /// You must allow location access to trust this Wi-Fi network. Go to iOS settings and review your location permissions for Passepartout. - public static let denied = L10n.tr("Core", "service.alerts.location.message.denied") - } - } - public enum MasksPrivateData { - public enum Messages { - /// In order to safely reset the current debug log and apply the new masking preference, you must reconnect to the VPN now. - public static let mustReconnect = L10n.tr("Core", "service.alerts.masks_private_data.messages.must_reconnect") - } - } - public enum ReconnectVpn { - /// Do you want to reconnect to the VPN? - public static let message = L10n.tr("Core", "service.alerts.reconnect_vpn.message") - } - public enum Rename { - /// Rename profile - public static let title = L10n.tr("Core", "service.alerts.rename.title") - } - public enum TestConnectivity { - /// Connectivity - public static let title = L10n.tr("Core", "service.alerts.test_connectivity.title") - public enum Messages { - /// Your device has no Internet connectivity, please review your profile parameters. - public static let failure = L10n.tr("Core", "service.alerts.test_connectivity.messages.failure") - /// Your device is connected to the Internet! - public static let success = L10n.tr("Core", "service.alerts.test_connectivity.messages.success") - } - } - public enum Trusted { - public enum NoNetwork { - /// You are not connected to any Wi-Fi network. - public static let message = L10n.tr("Core", "service.alerts.trusted.no_network.message") - } - public enum WillDisconnectPolicy { - /// By changing the trust policy, the VPN may be disconnected. Continue? - public static let message = L10n.tr("Core", "service.alerts.trusted.will_disconnect_policy.message") - } - public enum WillDisconnectTrusted { - /// By trusting this network, the VPN may be disconnected. Continue? - public static let message = L10n.tr("Core", "service.alerts.trusted.will_disconnect_trusted.message") - } - } - } - public enum Cells { - public enum Addresses { - /// Addresses - public static let caption = L10n.tr("Core", "service.cells.addresses.caption") - } - public enum Category { - /// Category - public static let caption = L10n.tr("Core", "service.cells.category.caption") - } - public enum ConnectionStatus { - /// Status - public static let caption = L10n.tr("Core", "service.cells.connection_status.caption") - } - public enum DataCount { - /// Exchanged data - public static let caption = L10n.tr("Core", "service.cells.data_count.caption") - /// Unavailable - public static let `none` = L10n.tr("Core", "service.cells.data_count.none") - } - public enum DebugLog { - /// Debug log - public static let caption = L10n.tr("Core", "service.cells.debug_log.caption") - } - public enum Host { - public enum Parameters { - /// Parameters - public static let caption = L10n.tr("Core", "service.cells.host.parameters.caption") - } - } - public enum MasksPrivateData { - /// Mask network data - public static let caption = L10n.tr("Core", "service.cells.masks_private_data.caption") - } - public enum OnlyShowsFavorites { - /// Only show favorite locations - public static let caption = L10n.tr("Core", "service.cells.only_shows_favorites.caption") - } - public enum Provider { - public enum Pool { - /// Location - public static let caption = L10n.tr("Core", "service.cells.provider.pool.caption") - } - public enum Preset { - /// Preset - public static let caption = L10n.tr("Core", "service.cells.provider.preset.caption") - } - public enum Refresh { - /// Refresh infrastructure - public static let caption = L10n.tr("Core", "service.cells.provider.refresh.caption") - } - } - public enum Reconnect { - /// Reconnect - public static let caption = L10n.tr("Core", "service.cells.reconnect.caption") - } - public enum ReportIssue { - /// Report connectivity issue - public static let caption = L10n.tr("Core", "service.cells.report_issue.caption") - } - public enum ServerConfiguration { - /// Server configuration - public static let caption = L10n.tr("Core", "service.cells.server_configuration.caption") - } - public enum ServerNetwork { - /// Server network - public static let caption = L10n.tr("Core", "service.cells.server_network.caption") - } - public enum TestConnectivity { - /// Test connectivity - public static let caption = L10n.tr("Core", "service.cells.test_connectivity.caption") - } - public enum TrustedAddWifi { - /// Add Wi-Fi - public static let caption = L10n.tr("Core", "service.cells.trusted_add_wifi.caption") - } - public enum TrustedMobile { - /// Cellular network - public static let caption = L10n.tr("Core", "service.cells.trusted_mobile.caption") - } - public enum TrustedPolicy { - /// Trust disables VPN - public static let caption = L10n.tr("Core", "service.cells.trusted_policy.caption") - } - public enum UseProfile { - /// Use this profile - public static let caption = L10n.tr("Core", "service.cells.use_profile.caption") - } - public enum Vpn { - public enum TurnOff { - /// Disable VPN - public static let caption = L10n.tr("Core", "service.cells.vpn.turn_off.caption") - } - public enum TurnOn { - /// Enable VPN - public static let caption = L10n.tr("Core", "service.cells.vpn.turn_on.caption") - } - } - public enum VpnResolvesHostname { - /// Resolve provider hostname - public static let caption = L10n.tr("Core", "service.cells.vpn_resolves_hostname.caption") - } - public enum VpnService { - /// Enabled - public static let caption = L10n.tr("Core", "service.cells.vpn_service.caption") - } - public enum VpnSurvivesSleep { - /// Keep alive on sleep - public static let caption = L10n.tr("Core", "service.cells.vpn_survives_sleep.caption") - } - } - public enum Sections { - public enum Configuration { - /// Configuration - public static let header = L10n.tr("Core", "service.sections.configuration.header") - } - public enum Diagnostics { - /// Masking status will be effective after reconnecting. Network data are hostnames, IP addresses, routing, SSID. Credentials and private keys are not logged regardless. - public static let footer = L10n.tr("Core", "service.sections.diagnostics.footer") - /// Diagnostics - public static let header = L10n.tr("Core", "service.sections.diagnostics.header") - } - public enum ProviderInfrastructure { - /// Last updated on %@. - public static func footer(_ p1: Any) -> String { - return L10n.tr("Core", "service.sections.provider_infrastructure.footer", String(describing: p1)) - } - } - public enum Status { - /// Connection - public static let header = L10n.tr("Core", "service.sections.status.header") - } - public enum Trusted { - /// When entering a trusted network, the VPN is normally shut down and kept disconnected. Disable this option to not enforce such behavior. - public static let footer = L10n.tr("Core", "service.sections.trusted.footer") - /// Trusted networks - public static let header = L10n.tr("Core", "service.sections.trusted.header") - } - public enum Vpn { - /// The connection will be established whenever necessary. - public static let footer = L10n.tr("Core", "service.sections.vpn.footer") - /// VPN - public static let header = L10n.tr("Core", "service.sections.vpn.header") - } - public enum VpnResolvesHostname { - /// Preferred in most networks and required in some IPv6 networks. Disable where DNS is blocked, or to speed up negotiation when DNS is slow to respond. - public static let footer = L10n.tr("Core", "service.sections.vpn_resolves_hostname.footer") - } - public enum VpnSurvivesSleep { - /// Disable to improve battery usage, at the expense of occasional slowdowns due to wake-up reconnections. - public static let footer = L10n.tr("Core", "service.sections.vpn_survives_sleep.footer") - } - } - public enum Welcome { - /// Welcome to Passepartout!\n\nUse the organizer to add a new profile. - public static let message = L10n.tr("Core", "service.welcome.message") - } - } - - public enum Share { - /// Passepartout is an user-friendly, open source OpenVPN client for iOS and macOS - public static let message = L10n.tr("Core", "share.message") - } - - public enum Shortcuts { - public enum Add { - /// Add shortcut - public static let title = L10n.tr("Core", "shortcuts.add.title") - public enum Alerts { - public enum NoProfiles { - /// There is no profile to connect to. - public static let message = L10n.tr("Core", "shortcuts.add.alerts.no_profiles.message") - } - } - public enum Cells { - public enum Connect { - /// Connect to - public static let caption = L10n.tr("Core", "shortcuts.add.cells.connect.caption") - } - public enum DisableVpn { - /// Disable VPN - public static let caption = L10n.tr("Core", "shortcuts.add.cells.disable_vpn.caption") - } - public enum EnableVpn { - /// Enable VPN - public static let caption = L10n.tr("Core", "shortcuts.add.cells.enable_vpn.caption") - } - public enum TrustCellular { - /// Trust cellular network - public static let caption = L10n.tr("Core", "shortcuts.add.cells.trust_cellular.caption") - } - public enum TrustCurrentWifi { - /// Trust current Wi-Fi - public static let caption = L10n.tr("Core", "shortcuts.add.cells.trust_current_wifi.caption") - } - public enum UntrustCellular { - /// Untrust cellular network - public static let caption = L10n.tr("Core", "shortcuts.add.cells.untrust_cellular.caption") - } - public enum UntrustCurrentWifi { - /// Untrust current Wi-Fi - public static let caption = L10n.tr("Core", "shortcuts.add.cells.untrust_current_wifi.caption") - } - } - public enum Sections { - public enum Cellular { - /// Cellular - public static let header = L10n.tr("Core", "shortcuts.add.sections.cellular.header") - } - public enum Vpn { - /// VPN - public static let header = L10n.tr("Core", "shortcuts.add.sections.vpn.header") - } - public enum Wifi { - /// Wi-Fi - public static let header = L10n.tr("Core", "shortcuts.add.sections.wifi.header") - } - } - } - public enum Edit { - /// Manage shortcuts - public static let title = L10n.tr("Core", "shortcuts.edit.title") - public enum Cells { - public enum AddShortcut { - /// Add shortcut - public static let caption = L10n.tr("Core", "shortcuts.edit.cells.add_shortcut.caption") - } - } - public enum Sections { - public enum All { - /// Existing shortcuts - public static let header = L10n.tr("Core", "shortcuts.edit.sections.all.header") - } - } - } - } - - public enum Translations { - /// Translations - public static let title = L10n.tr("Core", "translations.title") - } - - public enum Trusted { - public enum Columns { - public enum Trust { - /// Trust - public static let title = L10n.tr("Core", "trusted.columns.trust.title") - } - } - public enum Ethernet { - /// Check to trust any wired cable connection. - public static let description = L10n.tr("Core", "trusted.ethernet.description") - /// Trust wired connections - public static let title = L10n.tr("Core", "trusted.ethernet.title") - } - } - - public enum Version { - /// Version - public static let title = L10n.tr("Core", "version.title") - public enum Labels { - /// Passepartout and TunnelKit are written and maintained by Davide De Rosa (keeshux).\n\nSource code for Passepartout and TunnelKit is publicly available on GitHub under the GPLv3, you can find links in the home page.\n\nPassepartout is a non-official client and is in no way affiliated with OpenVPN Inc. - public static let intro = L10n.tr("Core", "version.labels.intro") - } - } - - public enum Vpn { - /// Active - public static let active = L10n.tr("Core", "vpn.active") - /// Connecting - public static let connecting = L10n.tr("Core", "vpn.connecting") - /// Disabled - public static let disabled = L10n.tr("Core", "vpn.disabled") - /// Disconnecting - public static let disconnecting = L10n.tr("Core", "vpn.disconnecting") - /// Inactive - public static let inactive = L10n.tr("Core", "vpn.inactive") - /// Off - public static let unused = L10n.tr("Core", "vpn.unused") - public enum Errors { - /// Auth failed - public static let auth = L10n.tr("Core", "vpn.errors.auth") - /// Compression unsupported - public static let compression = L10n.tr("Core", "vpn.errors.compression") - /// DNS failed - public static let dns = L10n.tr("Core", "vpn.errors.dns") - /// Encryption failed - public static let encryption = L10n.tr("Core", "vpn.errors.encryption") - /// No gateway - public static let gateway = L10n.tr("Core", "vpn.errors.gateway") - /// Network changed - public static let network = L10n.tr("Core", "vpn.errors.network") - /// Missing routing - public static let routing = L10n.tr("Core", "vpn.errors.routing") - /// Server shutdown - public static let shutdown = L10n.tr("Core", "vpn.errors.shutdown") - /// Timeout - public static let timeout = L10n.tr("Core", "vpn.errors.timeout") - /// TLS failed - public static let tls = L10n.tr("Core", "vpn.errors.tls") - } - } - - public enum Wizards { - public enum Host { - public enum Alerts { - public enum Existing { - /// A host profile with the same title already exists. Replace it? - public static let message = L10n.tr("Core", "wizards.host.alerts.existing.message") - } - } - public enum Cells { - public enum TitleInput { - /// Title - public static let caption = L10n.tr("Core", "wizards.host.cells.title_input.caption") - } - } - public enum Sections { - public enum Existing { - /// Existing profiles - public static let header = L10n.tr("Core", "wizards.host.sections.existing.header") - } - } - } - public enum Provider { - public enum Alerts { - public enum Unavailable { - /// Could not download provider infrastructure, please retry later. - public static let message = L10n.tr("Core", "wizards.provider.alerts.unavailable.message") - } - } - public enum Cells { - public enum UpdateList { - /// Update list - public static let caption = L10n.tr("Core", "wizards.provider.cells.update_list.caption") - } - } - } - } -} -// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length -// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces - -// MARK: - Implementation Details - -extension L10n { - private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { - let format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table) - return String(format: format, locale: Locale.current, arguments: args) - } -} - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type diff --git a/PassepartoutCore/Sources/PassepartoutCore/UI/TrustedNetworksUI.swift b/PassepartoutCore/Sources/PassepartoutCore/UI/TrustedNetworksUI.swift deleted file mode 100644 index 404cba11..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/UI/TrustedNetworksUI.swift +++ /dev/null @@ -1,219 +0,0 @@ -// -// TrustedNetworksUI.swift -// Passepartout -// -// Created by Davide De Rosa on 6/21/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation - -public protocol TrustedNetworksUIDelegate: AnyObject { - func trustedNetworksCouldDisconnect(_: TrustedNetworksUI) -> Bool - - func trustedNetworksShouldConfirmDisconnection(_: TrustedNetworksUI, triggeredAt rowIndex: Int, completionHandler: @escaping () -> Void) - - func trustedNetworks(_: TrustedNetworksUI, shouldInsertWifiAt rowIndex: Int) - - func trustedNetworks(_: TrustedNetworksUI, shouldReloadWifiAt rowIndex: Int, isTrusted: Bool) - - func trustedNetworks(_: TrustedNetworksUI, shouldDeleteWifiAt rowIndex: Int) - - func trustedNetworksShouldReinstall(_: TrustedNetworksUI) -} - -public class TrustedNetworksUI { - public enum RowType { - case trustsMobile - - case trustedWiFi - - case addCurrentWiFi - } - - public private(set) var trustedWifis: [String: Bool] - - public private(set) var sortedWifis: [String] - - #if os(iOS) - private let hasMobileNetwork: Bool - - public private(set) var trustsMobileNetwork: Bool - - public private(set) var rows: [RowType] - #endif - - public weak var delegate: TrustedNetworksUIDelegate? - - public init() { - trustedWifis = [:] - sortedWifis = [] - - #if os(iOS) - hasMobileNetwork = Utils.hasCellularData() - trustsMobileNetwork = false - rows = [] - #endif - } - - public func load(from trustedNetworks: TrustedNetworks) { - trustedWifis = trustedNetworks.includedWiFis - sortedWifis = trustedWifis.keys.sorted() - - #if os(iOS) - trustsMobileNetwork = trustedNetworks.includesMobile - rows.removeAll() - if hasMobileNetwork { - rows.append(.trustsMobile) - } - for _ in sortedWifis { - rows.append(.trustedWiFi) - } - rows.append(.addCurrentWiFi) - #endif - } - - #if os(iOS) - public func setMobile(_ isTrusted: Bool) { - let completionHandler: () -> Void = { - self.trustsMobileNetwork = isTrusted - self.delegate?.trustedNetworksShouldReinstall(self) - } - guard !(isTrusted && mightDisconnect()) else { - delegate?.trustedNetworksShouldConfirmDisconnection(self, triggeredAt: 0, completionHandler: completionHandler) - return - } - completionHandler() - } - #endif - - public func wifi(at rowIndex: Int) -> (String, Bool) { - let index = indexForWifi(at: rowIndex) - let wifiName = sortedWifis[index] - let isTrusted = trustedWifis[wifiName] ?? false - return (wifiName, isTrusted) - } - - public func addCurrentWifi() -> Bool { - guard let currentWifi = Utils.currentWifiNetworkName() else { - return false - } - addWifi(currentWifi) - return true - } - - public func addWifi(_ wifiToAdd: String) { - var index = 0 - var isDuplicate = false - for wifi in sortedWifis { - guard wifiToAdd != wifi else { - isDuplicate = true - break - } - guard wifiToAdd > wifi else { - break - } - index += 1 - } - - guard !(trustedWifis[wifiToAdd] ?? false) else { - return - } - - let isTrusted = false - let rowIndex = rowIndexForWifi(at: index) - trustedWifis[wifiToAdd] = isTrusted - - if !isDuplicate { - sortedWifis.insert(wifiToAdd, at: index) - #if os(iOS) - rows.insert(.trustedWiFi, at: rowIndex) - #endif - delegate?.trustedNetworks(self, shouldInsertWifiAt: rowIndex) - } else { - delegate?.trustedNetworks(self, shouldReloadWifiAt: rowIndex, isTrusted: isTrusted) - } - - delegate?.trustedNetworksShouldReinstall(self) - } - - public func removeWifi(at rowIndex: Int) { - let index = indexForWifi(at: rowIndex) - let removedWifi = sortedWifis.remove(at: index) - trustedWifis.removeValue(forKey: removedWifi) - #if os(iOS) - rows.remove(at: rowIndex) - #endif - - delegate?.trustedNetworks(self, shouldDeleteWifiAt: rowIndex) - delegate?.trustedNetworksShouldReinstall(self) - } - - public func enableWifi(at rowIndex: Int) { - let index = indexForWifi(at: rowIndex) - let wifi = sortedWifis[index] - - let completionHandler: () -> Void = { - self.trustedWifis[wifi] = true - - self.delegate?.trustedNetworks(self, shouldReloadWifiAt: rowIndex, isTrusted: true) - self.delegate?.trustedNetworksShouldReinstall(self) - } - guard !mightDisconnect() else { - delegate?.trustedNetworksShouldConfirmDisconnection(self, triggeredAt: rowIndex, completionHandler: completionHandler) - return - } - completionHandler() - } - - public func disableWifi(at rowIndex: Int) { - let index = indexForWifi(at: rowIndex) - let wifi = sortedWifis[index] - - trustedWifis[wifi] = false - - delegate?.trustedNetworks(self, shouldReloadWifiAt: rowIndex, isTrusted: false) - delegate?.trustedNetworksShouldReinstall(self) - } - - public func isTrusted(wifi: String) -> Bool { - return trustedWifis[wifi] ?? false - } - - private func indexForWifi(at rowIndex: Int) -> Int { - #if os(iOS) - return hasMobileNetwork ? (rowIndex - 1) : rowIndex - #else - return rowIndex - #endif - } - - private func rowIndexForWifi(at index: Int) -> Int { - #if os(iOS) - return index + (hasMobileNetwork ? 1 : 0) - #else - return index - #endif - } - - private func mightDisconnect() -> Bool { - return delegate?.trustedNetworksCouldDisconnect(self) ?? false - } -} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Utils.swift b/PassepartoutCore/Sources/PassepartoutCore/Utils.swift deleted file mode 100644 index d2e82858..00000000 --- a/PassepartoutCore/Sources/PassepartoutCore/Utils.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// Utils.swift -// Passepartout -// -// Created by Davide De Rosa on 6/16/18. -// Copyright (c) 2022 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -#if os(iOS) -import SystemConfiguration.CaptiveNetwork -#else -import CoreWLAN -#endif -import StoreKit -import SwiftyBeaver -import PassepartoutConstants - -private let log = SwiftyBeaver.self - -public class Utils { - fileprivate static let timestampFormatter: DateFormatter = { - let fmt = DateFormatter() - fmt.dateStyle = .medium - fmt.timeStyle = .medium - return fmt - }() - - fileprivate static let componentsFormatter: DateComponentsFormatter = { - let fmt = DateComponentsFormatter() - fmt.unitsStyle = .full - return fmt - }() - - #if targetEnvironment(simulator) - public static func hasCellularData() -> Bool { - return true - } - #else - public static func hasCellularData() -> Bool { - var addrs: UnsafeMutablePointer? - guard getifaddrs(&addrs) == 0 else { - return false - } - var isFound = false - var cursor = addrs?.pointee - while let ifa = cursor { - let name = String(cString: ifa.ifa_name) - if name == "pdp_ip0" { - isFound = true - break - } - cursor = ifa.ifa_next?.pointee - } - freeifaddrs(addrs) - return isFound - } - #endif - - #if targetEnvironment(simulator) - public static func currentWifiNetworkName() -> String? { -// return nil - return ["My Home Network", "Safe Wi-Fi", "Friend's House"].randomElement() - } - #else - public static func currentWifiNetworkName() -> String? { - #if os(iOS) - guard let interfaceNames = CNCopySupportedInterfaces() as? [CFString] else { - return nil - } - for name in interfaceNames { - guard let iface = CNCopyCurrentNetworkInfo(name) as? [String: Any] else { - continue - } - guard let ssid = iface[kCNNetworkInfoKeySSID as String] as? String else { - continue - } - return ssid - } - return nil - #else - return CWWiFiClient.shared().interface()?.ssid() - #endif - } - #endif - - public static func regex(_ pattern: String) -> NSRegularExpression { - return try! NSRegularExpression(pattern: pattern, options: []) - } - - public static func checkConnectivityURL(_ url: URL, timeout: TimeInterval, completionHandler: @escaping (Bool) -> Void) { - let session = URLSession(configuration: .ephemeral) - let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: timeout) - - log.info("Check connectivity via \(url)") - session.dataTask(with: request) { (_, response, error) in - if let response = response as? HTTPURLResponse { - log.debug("Response code: \(response.statusCode)") - } - if let error = error { - log.error("Connectivity failed: \(error)") - } else { - log.info("Connectivity succeeded!") - } - DispatchQueue.main.async { - completionHandler(error == nil) - } - }.resume() - } - - public static func localizedCountry(_ code: String) -> String { - return Locale.current.localizedString(forRegionCode: code) ?? code - } - - public static func isFile(at url1: URL, newerThanFileAt url2: URL?) -> Bool { - guard let date1 = FileManager.default.modificationDate(of: url1.path) else { - return false - } - guard let url2 = url2, let date2 = FileManager.default.modificationDate(of: url2.path) else { - return true - } - return date1 > date2 - } - - private init() { - } -} - -public extension Date { - var timestamp: String { - return Utils.timestampFormatter.string(from: self) - } -} - -public extension TimeInterval { - var localized: String { - guard let str = Utils.componentsFormatter.string(from: self) else { - fatalError("Could not format a TimeInterval?") - } - return str - } -} - -public extension Sequence { - func stableSorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element] { - return try enumerated().sorted { - return try areInIncreasingOrder($0.element, $1.element) || - ($0.offset < $1.offset && !areInIncreasingOrder($1.element, $0.element)) - }.map { $0.element } - } -} - -public extension StringProtocol where Index == String.Index { - func nsRange(from range: Range) -> NSRange { - return NSRange(range, in: self) - } -} - -public extension CharacterSet { - static let filename: CharacterSet = { - var chars: CharacterSet = .decimalDigits - let english = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - let symbols = "-_." - chars.formUnion(CharacterSet(charactersIn: english)) - chars.formUnion(CharacterSet(charactersIn: english.lowercased())) - chars.formUnion(CharacterSet(charactersIn: symbols)) - return chars - }() -} - -public extension URL { - private static let illegalCharacterFallback = "_" - - var normalizedFilename: String { - let filename = deletingPathExtension().lastPathComponent - return filename.components(separatedBy: CharacterSet.filename.inverted).joined(separator: URL.illegalCharacterFallback) - } -} - -public extension Array where Element: CustomStringConvertible { - func sortedCaseInsensitive() -> [Element] { - return sorted { $0.description.lowercased() < $1.description.lowercased() } - } -} - -public extension Infrastructure.Metadata { - var guidanceString: String? { - let prefix = "account.sections.guidance.footer.infrastructure" - let key = "\(prefix).\(name)" - var format = NSLocalizedString(key, tableName: "Core", bundle: Bundle.module, comment: "") - - // i.e. key not found - if format == key { - let purpose = name.credentialsPurpose - let defaultKey = "\(prefix).default.\(purpose)" - format = NSLocalizedString(defaultKey, tableName: "Core", bundle: Bundle.module, comment: "") - } - - return String(format: format, locale: .current, description) - } - - var guidanceURL: URL? { - guard let string = AppConstants.URLs.guidances[name] else { - return nil - } - return URL(string: string) - } - - var referralURL: URL? { - guard let string = AppConstants.URLs.referrals[name] else { - return nil - } - return URL(string: string) - } -} - -public extension String { - var stripped: String { - return trimmingCharacters(in: .whitespacesAndNewlines) - } -} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataClass.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataClass.swift new file mode 100644 index 00000000..12264597 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// CDProfile+CoreDataClass.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + +@objc(CDProfile) +public class CDProfile: NSManagedObject { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataProperties.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataProperties.swift new file mode 100644 index 00000000..1350470c --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataProperties.swift @@ -0,0 +1,30 @@ +// +// CDProfile+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + + +extension CDProfile { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDProfile") + } + + @NSManaged public var json: Data? + @NSManaged public var name: String? + @NSManaged public var providerName: String? + @NSManaged public var uuid: UUID? + @NSManaged public var lastUpdate: Date? + +} + +extension CDProfile : Identifiable { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Network.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Network.swift new file mode 100644 index 00000000..00c1db7f --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Network.swift @@ -0,0 +1,136 @@ +// +// Network.swift +// Passepartout +// +// Created by Davide De Rosa on 2/15/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitCore +import NetworkExtension + +public enum Network { +} + +extension Network { + public enum Choice: String, Codable { + case automatic // OpenVPN pulls from server + + case manual + + public static let defaultChoice: Choice = .automatic + } +} + +public protocol NetworkChoiceRepresentable { + var choice: Network.Choice { get set } +} + +public protocol GatewaySettingsProviding { + var isDefaultIPv4: Bool { get } + + var isDefaultIPv6: Bool { get } +} + +public protocol DNSSettingsProviding { + var dnsProtocol: DNSProtocol { get } + + var dnsServers: [String] { get } + + var dnsHTTPSURL: URL? { get } + + var dnsTLSServerName: String? { get } + + var dnsSearchDomains: [String] { get } +} + +public protocol ProxySettingsProviding { + var proxyServer: Proxy? { get } + + var proxyAutoConfigurationURL: URL? { get } + + var proxyBypassDomains: [String] { get } +} + +public protocol MTUSettingsProviding { + var mtuBytes: Int { get } +} + +// + +extension Network { + public struct GatewaySettings: Codable, Equatable, NetworkChoiceRepresentable, GatewaySettingsProviding { + public var choice: Network.Choice + + public var isDefaultIPv4 = true + + public var isDefaultIPv6 = true + } +} + +extension Network { + public struct DNSSettings: Codable, Equatable, NetworkChoiceRepresentable, DNSSettingsProviding { + public var choice: Network.Choice + + public var isDNSEnabled = true + + public var dnsProtocol: DNSProtocol = .plain + + public var dnsServers: [String] = [] + + public var dnsSearchDomains: [String] = [] + + public var dnsHTTPSURL: URL? + + public var dnsTLSServerName: String? + } +} + +extension Network { + public struct ProxySettings: Codable, Equatable, NetworkChoiceRepresentable, ProxySettingsProviding { + public var choice: Network.Choice + + public var isProxyEnabled = true + + public var proxyAddress: String? + + public var proxyPort: UInt16? + + public var proxyAutoConfigurationURL: URL? + + public var proxyBypassDomains: [String] = [] + + public var proxyServer: Proxy? { + guard let address = proxyAddress, let port = proxyPort, !address.isEmpty, port > 0 else { + return nil + } + return Proxy(address, port) + } + } +} + +extension Network { + public struct MTUSettings: Codable, Equatable, NetworkChoiceRepresentable, MTUSettingsProviding { + public var choice: Network.Choice + + public var mtuBytes = 0 + } +} diff --git a/Passepartout/App/macOS/Scenes/Preferences/PreferencesViewController.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Account.swift similarity index 60% rename from Passepartout/App/macOS/Scenes/Preferences/PreferencesViewController.swift rename to PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Account.swift index 76458b4e..c640b07d 100644 --- a/Passepartout/App/macOS/Scenes/Preferences/PreferencesViewController.swift +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Account.swift @@ -1,8 +1,8 @@ // -// PreferencesViewController.swift +// Profile+Account.swift // Passepartout // -// Created by Davide De Rosa on 5/31/19. +// Created by Davide De Rosa on 4/6/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -23,21 +23,26 @@ // along with Passepartout. If not, see . // -import Cocoa -import PassepartoutCore +import Foundation -class PreferencesViewController: NSViewController { - @IBOutlet private weak var tabView: NSTabView! - - override func viewDidLoad() { - super.viewDidLoad() +extension Profile { + public struct Account: Codable, Equatable { + public var username: String - let labels = [ - L10n.Preferences.Sections.General.header, - L10n.Service.Sections.Diagnostics.header - ] - tabView.tabViewItems.enumerated().forEach { - $1.label = labels[$0] + public var password: String + + public var isEmpty: Bool { + username.isEmpty && password.isEmpty + } + + public init() { + username = "" + password = "" + } + + public init(_ username: String, _ password: String) { + self.username = username + self.password = password } } } diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Header.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Header.swift new file mode 100644 index 00000000..6c959e57 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Header.swift @@ -0,0 +1,71 @@ +// +// Profile+Header.swift +// Passepartout +// +// Created by Davide De Rosa on 2/17/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutProviders + +extension Profile { + public struct Header: Codable, Identifiable, Hashable { + public let uuid: UUID + + public var name: String + + public let providerName: ProviderName? + + public let lastUpdate: Date? + + public init( + uuid: UUID = UUID(), + name: String = "", + providerName: ProviderName? = nil, + lastUpdate: Date? = nil + ) { + self.uuid = uuid + self.name = name + self.providerName = providerName + self.lastUpdate = lastUpdate ?? Date() + } + + // MARK: Hashable + + public static func ==(lhs: Self, rhs: Self) -> Bool { + lhs.uuid == rhs.uuid && + lhs.name == rhs.name && + lhs.providerName == rhs.providerName + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(uuid) + hasher.combine(name) + hasher.combine(providerName) + } + + // MARK: Identifiable + + public var id: UUID { + return uuid + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Host.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Host.swift new file mode 100644 index 00000000..e2eb7f28 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Host.swift @@ -0,0 +1,34 @@ +// +// Profile+Host.swift +// Passepartout +// +// Created by Davide De Rosa on 3/10/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension Profile { + public struct Host: Codable, Equatable { + public var ovpnSettings: OpenVPNSettings? + + public var wgSettings: WireGuardSettings? + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+NetworkSettings.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+NetworkSettings.swift new file mode 100644 index 00000000..76d39ea8 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+NetworkSettings.swift @@ -0,0 +1,54 @@ +// +// Profile+NetworkSettings.swift +// Passepartout +// +// Created by Davide De Rosa on 2/15/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKit + +extension Profile { + public struct NetworkSettings: Codable, Equatable { + public var gateway: Network.GatewaySettings + + public var dns: Network.DNSSettings + + public var proxy: Network.ProxySettings + + public var mtu: Network.MTUSettings + + public var resolvesHostname = true + + public var keepsAliveOnSleep = true + + public init(choice: Network.Choice) { + gateway = Network.GatewaySettings(choice: choice) + dns = Network.DNSSettings(choice: choice) + proxy = Network.ProxySettings(choice: choice) + mtu = Network.MTUSettings(choice: choice) + } + + public init() { + self.init(choice: .defaultChoice) + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+OnDemand.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+OnDemand.swift new file mode 100644 index 00000000..abe1c6db --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+OnDemand.swift @@ -0,0 +1,62 @@ +// +// Profile+OnDemand.swift +// Passepartout +// +// Created by Davide De Rosa on 2/17/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension Profile { + public struct OnDemand: Codable, Equatable { + public enum Policy: String, Codable { + case any + + case including + + case excluding // "trusted networks" + } + + public enum OtherNetwork: String, Codable { + + @available(iOS 14, *) + case mobile + + @available(macOS 11, *) + case ethernet + } + + // hardcode this to keep "Trusted networks" semantics + public var isEnabled = true + + // hardcode this to keep "Trusted networks" semantics + public var policy: Policy = .excluding + + public var withSSIDs: [String: Bool] = [:] + + public var withOtherNetworks: Set = [] + + public var disconnectsIfNotMatching = true + + public init() { + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/VPN+Shared.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+OpenVPNSettings.swift similarity index 65% rename from PassepartoutCore/Sources/PassepartoutCore/VPN+Shared.swift rename to PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+OpenVPNSettings.swift index 3de24e6b..d15f10b9 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/VPN+Shared.swift +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+OpenVPNSettings.swift @@ -1,8 +1,8 @@ // -// VPN+Shared.swift +// Profile+OpenVPNSettings.swift // Passepartout // -// Created by Davide De Rosa on 9/20/21. +// Created by Davide De Rosa on 2/16/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,14 +24,19 @@ // import Foundation -import TunnelKit import TunnelKitOpenVPN -import PassepartoutConstants +import PassepartoutProviders -public extension VPN { - #if os(macOS) || !targetEnvironment(simulator) - static let shared = OpenVPNProvider(bundleIdentifier: AppConstants.App.tunnelBundleId) - #else - static let shared = MockVPNProvider() - #endif +extension Profile { + public struct OpenVPNSettings: Codable, Equatable, VPNProtocolProviding { + public var vpnProtocol: VPNProtocolType { + return .openVPN + } + + public var configuration: OpenVPN.Configuration + + public var account: Profile.Account? + + public var customEndpoint: Endpoint? + } } diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Provider.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Provider.swift new file mode 100644 index 00000000..c7c32894 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+Provider.swift @@ -0,0 +1,55 @@ +// +// Profile+Provider.swift +// Passepartout +// +// Created by Davide De Rosa on 2/15/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutProviders +import TunnelKitCore + +extension Profile { + public struct Provider: Codable, Equatable { + public struct Settings: Codable, Equatable { + public var account: Profile.Account? + + public var serverId: String? + + public var presetId: String? + + public var favoriteLocationIds: Set? + + public var customEndpoint: Endpoint? + + public init() { + } + } + + public let name: ProviderName + + public var vpnSettings: [VPNProtocolType: Settings] = [:] + + public init(_ name: ProviderName) { + self.name = name + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+WireGuardSettings.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+WireGuardSettings.swift new file mode 100644 index 00000000..8e873fc5 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile+WireGuardSettings.swift @@ -0,0 +1,38 @@ +// +// Profile+WireGuardSettings.swift +// Passepartout +// +// Created by Davide De Rosa on 2/17/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitWireGuard +import PassepartoutProviders + +extension Profile { + public struct WireGuardSettings: Codable, Equatable, VPNProtocolProviding { + public var vpnProtocol: VPNProtocolType { + return .wireGuard + } + + public var configuration: WireGuard.Configuration + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile.swift b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile.swift new file mode 100644 index 00000000..c5f4f413 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/DataModels/Profile.swift @@ -0,0 +1,140 @@ +// +// Profile.swift +// Passepartout +// +// Created by Davide De Rosa on 2/11/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutProviders +import TunnelKitOpenVPN +import TunnelKitWireGuard + +public protocol ProfileSubtype { + var vpnProtocols: [VPNProtocolType] { get } + + func requiresCredentials(forProtocol vpnProtocol: VPNProtocolType) -> Bool +} + +public struct Profile: Identifiable, Codable, Equatable { + public var header: Header + + public var currentVPNProtocol: VPNProtocolType + + public var networkSettings = Profile.NetworkSettings() + + public var onDemand = Profile.OnDemand() + + var host: Host? + + var provider: Provider? + + init(_ header: Header) { + self.header = header + currentVPNProtocol = .openVPN + } + + init(_ id: UUID = UUID(), name: String) { + header = Header( + uuid: id, + name: name, + providerName: nil + ) + currentVPNProtocol = .openVPN + } + + init(_ id: UUID = UUID(), name: String, configuration: OpenVPN.Configuration) { + let header = Header( + uuid: id, + name: name, + providerName: nil + ) + self.init(header, configuration: configuration) + } + + init(_ id: UUID = UUID(), name: String, configuration: WireGuard.Configuration) { + let header = Header( + uuid: id, + name: name, + providerName: nil + ) + self.init(header, configuration: configuration) + } + + init(_ id: UUID = UUID(), name: String, provider: Profile.Provider) { + let header = Header( + uuid: id, + name: name, + providerName: provider.name + ) + self.init(header, provider: provider) + } + + public init(_ header: Header, configuration: OpenVPN.Configuration) { + self.header = header + currentVPNProtocol = .openVPN + host = Host() + host?.ovpnSettings = OpenVPNSettings(configuration: configuration) + } + + public init(_ header: Header, configuration: WireGuard.Configuration) { + self.header = header + currentVPNProtocol = .wireGuard + host = Host() + host?.wgSettings = WireGuardSettings(configuration: configuration) + } + + public init(_ header: Header, provider: Profile.Provider) { + guard let firstVPNProtocol = provider.vpnProtocols.first else { + fatalError("No VPN protocols defined in provider") + } + self.header = header + currentVPNProtocol = firstVPNProtocol + self.provider = provider + } + + // MARK: Identifiable + + public var id: UUID { + return header.id + } +} + +extension Profile { + public static let placeholder = Profile( + UUID(uuidString: "00000000-0000-0000-0000-000000000000")!, + name: "" + ) + + public static func isPlaceholder(_ id: UUID) -> Bool { + id == placeholder.id + } + + public var isPlaceholder: Bool { + header.id == Self.placeholder.id + } +} + +extension Profile.Header { + public var isPlaceholder: Bool { + id == Profile.placeholder.id + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Host+Extensions.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Host+Extensions.swift new file mode 100644 index 00000000..9af732ef --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Host+Extensions.swift @@ -0,0 +1,106 @@ +// +// Host+Extensions.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitCore +import PassepartoutProviders + +extension Profile { + public func hostAccount() -> Profile.Account? { + switch currentVPNProtocol { + case .openVPN: + return host?.ovpnSettings?.account + + case .wireGuard: + return nil + } + } + + public mutating func setHostAccount(_ account: Profile.Account?) { + switch currentVPNProtocol { + case .openVPN: + host?.ovpnSettings?.account = account + + case .wireGuard: + break + } + } + + public var hostOpenVPNSettings: OpenVPNSettings? { + get { + guard host != nil else { + fatalError("Not a host") + } + return host?.ovpnSettings + } + set { + guard host != nil else { + fatalError("Not a host") + } + host?.ovpnSettings = newValue + } + } + + public var hostWireGuardSettings: WireGuardSettings? { + get { + guard host != nil else { + fatalError("Not a host") + } + return host?.wgSettings + } + set { + guard host != nil else { + fatalError("Not a host") + } + host?.wgSettings = newValue + } + } + + public var hostCustomEndpoint: Endpoint? { + switch currentVPNProtocol { + case .openVPN: + return host?.ovpnSettings?.customEndpoint + + case .wireGuard: + return nil + } + } +} + +extension Profile.Host: ProfileSubtype { + public var vpnProtocols: [VPNProtocolType] { + if let _ = ovpnSettings { + return [.openVPN] + } else if let _ = wgSettings { + return [.wireGuard] + } else { + fatalError("No VPN settings found") + } + } + + public func requiresCredentials(forProtocol vpnProtocol: VPNProtocolType) -> Bool { + return vpnProtocol == .openVPN && (ovpnSettings?.configuration.authUserPass ?? false) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/NetworkSettings+Extensions.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/NetworkSettings+Extensions.swift new file mode 100644 index 00000000..4b3f0a78 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/NetworkSettings+Extensions.swift @@ -0,0 +1,65 @@ +// +// NetworkSettings+Extensions.swift +// Passepartout +// +// Created by Davide De Rosa on 3/27/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutUtils + +extension Profile.NetworkSettings { + public var isAutomaticGateway: Bool { + get { + gateway.choice == .automatic + } + set { + gateway.choice = newValue ? .automatic : .manual + } + } + + public var isAutomaticDNS: Bool { + get { + dns.choice == .automatic + } + set { + dns.choice = newValue ? .automatic : .manual + } + } + + public var isAutomaticProxy: Bool { + get { + proxy.choice == .automatic + } + set { + proxy.choice = newValue ? .automatic : .manual + } + } + + public var isAutomaticMTU: Bool { + get { + mtu.choice == .automatic + } + set { + mtu.choice = newValue ? .automatic : .manual + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/OnDemand+Extensions.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/OnDemand+Extensions.swift new file mode 100644 index 00000000..2392ac4c --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/OnDemand+Extensions.swift @@ -0,0 +1,59 @@ +// +// OnDemand+Extensions.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import NetworkExtension +import PassepartoutUtils + +extension Profile.OnDemand { + + @available(iOS 14, *) + public var withMobileNetwork: Bool { + get { + withOtherNetworks.contains(.mobile) + } + set { + if newValue { + withOtherNetworks.insert(.mobile) + } else { + withOtherNetworks.remove(.mobile) + } + } + } + + @available(macOS 11, *) + public var withEthernetNetwork: Bool { + get { + withOtherNetworks.contains(.ethernet) + } + set { + if newValue { + withOtherNetworks.insert(.ethernet) + } else { + withOtherNetworks.remove(.ethernet) + } + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/OpenVPNSettings+Network.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/OpenVPNSettings+Network.swift new file mode 100644 index 00000000..89dad833 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/OpenVPNSettings+Network.swift @@ -0,0 +1,95 @@ +// +// OpenVPNSettings+Network.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitCore +import TunnelKitOpenVPN + +extension Profile.OpenVPNSettings: GatewaySettingsProviding { + + // route-gateway + public var isDefaultIPv4: Bool { + return configuration.routingPolicies?.contains(.IPv4) ?? false + } + + // ifconfig-ipv6 + public var isDefaultIPv6: Bool { + return configuration.routingPolicies?.contains(.IPv6) ?? false + } +} + +extension Profile.OpenVPNSettings: DNSSettingsProviding { + + // not a dhcp-option + public var dnsProtocol: DNSProtocol { + return .plain + } + + // dhcp-option DNS + public var dnsServers: [String] { + return configuration.dnsServers ?? [] + } + + // dhcp-option DOMAIN/DOMAIN-SEARCH + public var dnsSearchDomains: [String] { + return configuration.searchDomains ?? [] + } + + // not a dhcp-option + public var dnsHTTPSURL: URL? { + return nil + } + + // not a dhcp-option + public var dnsTLSServerName: String? { + return nil + } +} + +extension Profile.OpenVPNSettings: ProxySettingsProviding { + + // dhcp-option PROXY_HTTP[S] + public var proxyServer: Proxy? { + return configuration.httpsProxy ?? configuration.httpProxy + } + + // dhcp-option PROXY_AUTO_CONFIG_URL + public var proxyAutoConfigurationURL: URL? { + return configuration.proxyAutoConfigurationURL + } + + // dhcp-option PROXY_BYPASS + public var proxyBypassDomains: [String] { + return configuration.proxyBypassDomains ?? [] + } +} + +extension Profile.OpenVPNSettings: MTUSettingsProviding { + public var mtuBytes: Int { + + // tun-mtu + return configuration.mtu ?? 0 + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+Logging.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+Logging.swift new file mode 100644 index 00000000..d7d0977a --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+Logging.swift @@ -0,0 +1,38 @@ +// +// PassepartoutProfiles+Logging.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension Profile.Header { + public var logDescription: String { + return "{\(id), '\(name)'}" + } +} + +extension Profile { + public var logDescription: String { + return header.logDescription + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+StrippableContent.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+StrippableContent.swift new file mode 100644 index 00000000..0546bc41 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+StrippableContent.swift @@ -0,0 +1,61 @@ +// +// PassepartoutProfiles+StrippableContent.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutUtils + +extension Profile.Account: StrippableContent { + public var stripped: Self { + Self( + username.stripped, + password.stripped + ) + } +} + +extension Profile.OnDemand: StrippableContent { + public var stripped: Self { + var copy = self + copy.withSSIDs = copy.withSSIDs.reduce(into: [String: Bool]()) { + guard let strippedKey = $1.key.strippedNotEmpty else { + return + } + $0[strippedKey] = $1.value + } + return copy + } +} + +extension Profile.NetworkSettings: StrippableContent { + public var stripped: Self { + var copy = self + copy.dns.dnsServers = copy.dns.dnsServers.compactMap(\.strippedNotEmpty) + copy.dns.dnsSearchDomains = copy.dns.dnsSearchDomains.compactMap(\.strippedNotEmpty) + copy.dns.dnsTLSServerName = copy.dns.dnsTLSServerName?.strippedNotEmpty + copy.proxy.proxyAddress = copy.proxy.proxyAddress?.strippedNotEmpty + copy.proxy.proxyBypassDomains = copy.proxy.proxyBypassDomains.compactMap(\.strippedNotEmpty) + return copy + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProviders+TunnelKit.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProviders+TunnelKit.swift new file mode 100644 index 00000000..795f09e0 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/PassepartoutProviders+TunnelKit.swift @@ -0,0 +1,108 @@ +// +// PassepartoutProviders+TunnelKit.swift +// Passepartout +// +// Created by Davide De Rosa on 2/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine +import TunnelKitOpenVPN +import TunnelKitWireGuard +import PassepartoutProviders +import PassepartoutUtils + +extension ProviderServer.Preset { + public var openVPNConfiguration: OpenVPN.Configuration? { + guard vpnProtocol == .openVPN else { + return nil + } + guard let json = vpnConfiguration["cfg"] else { + pp_log.error("Unable to parse preset OpenVPN configuration: no cfg") + return nil + } + do { + return try json.decode(OpenVPN.Configuration.self) + } catch { + pp_log.error("Unable to parse preset OpenVPN configuration: \(error)") + return nil + } + } + + public var openVPNEndpoints: [EndpointProtocol] { + guard vpnProtocol == .openVPN else { + return [] + } + guard let json = vpnConfiguration["endpoints"]?.arrayValue else { + pp_log.error("Unable to parse preset OpenVPN configuration: no endpoints") + return [] + } + let endpoints = json + .compactMap(\.stringValue) + .compactMap(EndpointProtocol.init) + + guard !endpoints.isEmpty else { + pp_log.error("Unable to parse preset OpenVPN configuration: empty/malformed endpoints") + return [] + } + return endpoints + } + + public var wireGuardConfiguration: WireGuard.Configuration? { + guard vpnProtocol == .wireGuard else { + return nil + } + do { + return try vpnConfiguration.decode(WireGuard.Configuration.self) + } catch { + pp_log.error("Unable to parse preset WireGuard configuration: \(error)") + return nil + } + } +} + +extension OpenVPN.ConfigurationBuilder { + public mutating func setRemotes( + from preset: ProviderServer.Preset, + with server: ProviderServer, + excludingHostname: Bool + ) throws { + var remotes: [Endpoint] = [] + + let endpoints = preset.openVPNEndpoints + if !excludingHostname, let hostname = server.hostname { + endpoints.forEach { ep in + remotes.append(.init(hostname, ep)) + } + } + endpoints.forEach { ep in + server.ipAddresses.forEach { addr in + remotes.append(.init(addr, ep)) + } + } + guard !remotes.isEmpty else { + pp_log.warning("Excluding hostname but server has no ipAddresses either") + throw PassepartoutError.missingProviderServer + } + + self.remotes = remotes + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Profile+Extensions.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Profile+Extensions.swift new file mode 100644 index 00000000..7cbe9ae0 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Profile+Extensions.swift @@ -0,0 +1,106 @@ +// +// Profile+Extensions.swift +// Passepartout +// +// Created by Davide De Rosa on 3/13/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutProviders +import PassepartoutUtils +import TunnelKitOpenVPN +import TunnelKitWireGuard + +extension Profile { + public var isProvider: Bool { + return provider != nil + } + + public var vpnProtocols: [VPNProtocolType] { + if isProvider { + return provider?.vpnProtocols ?? [] + } else { + return host?.vpnProtocols ?? [] + } + } + + public var requiresCredentials: Bool { + if isProvider { + return provider?.requiresCredentials(forProtocol: currentVPNProtocol) ?? false + } else { + return host?.requiresCredentials(forProtocol: currentVPNProtocol) ?? false + } + } + + public var account: Profile.Account { + get { + if isProvider { + return providerAccount() ?? .init() + } else { + return hostAccount() ?? .init() + } + } + set { + if isProvider { + setProviderAccount(newValue) + } else { + setHostAccount(newValue) + } + } + } +} + +extension Profile.Header { + public func renamed(to newName: String) -> Self { + var header = self + header.name = newName + return header + } + + public func renamedUniquely() -> Self { + let suffix: String + if let lastUpdate = lastUpdate { + suffix = lastUpdate.timestamp + } else { + guard let leadingUUID = id.uuidString.components(separatedBy: "-").first else { + assertionFailure("UUID format?") + return self + } + suffix = leadingUUID.lowercased() + } + let newName = "\(name).\(suffix)" + return renamed(to: newName) + } +} + +extension Profile { + public func renamed(to newName: String) -> Self { + var profile = self + profile.header = profile.header.renamed(to: newName) + return profile + } + + public func renamedUniquely() -> Self { + var profile = self + profile.header = profile.header.renamedUniquely() + return profile + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Provider+Extensions.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Provider+Extensions.swift new file mode 100644 index 00000000..4dda517a --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/Provider+Extensions.swift @@ -0,0 +1,153 @@ +// +// Provider+Extensions.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitCore +import PassepartoutProviders +import PassepartoutUtils + +extension Profile { + public init(_ providerMetadata: ProviderMetadata, server: ProviderServer) { + guard let vpnProtocol = server.presets?.first?.vpnProtocol else { + fatalError("Server has no presets") + } + + var provider = Provider(providerMetadata.name) + var settings = Provider.Settings() + settings.serverId = server.id + settings.presetId = server.presetIds.first + provider.vpnSettings[vpnProtocol] = settings + + self.init(name: providerMetadata.fullName, provider: provider) + } + + @MainActor + public func providerServer(_ providerManager: ProviderManager) -> ProviderServer? { + guard let serverId = provider?.vpnSettings[currentVPNProtocol]?.serverId else { + return nil + } + return providerManager.server(withId: serverId) + } + + public func providerServerId() -> String? { + return provider?.vpnSettings[currentVPNProtocol]?.serverId + } + + public mutating func setProviderServer(_ server: ProviderServer) { + let oldServerId = provider?.vpnSettings[currentVPNProtocol]?.serverId + provider?.vpnSettings[currentVPNProtocol]?.serverId = server.id + provider?.vpnSettings[currentVPNProtocol]?.customEndpoint = nil + + // if server changed, pick first preset + if server.id != oldServerId { + provider?.vpnSettings[currentVPNProtocol]?.presetId = server.presetIds.first + } + } + + public func providerPreset(_ server: ProviderServer) -> ProviderServer.Preset? { + guard let presetId = provider?.vpnSettings[currentVPNProtocol]?.presetId else { + return nil + } + return server.preset(withId: presetId) + } + + public mutating func setProviderPreset(_ preset: ProviderServer.Preset) { + provider?.vpnSettings[currentVPNProtocol]?.presetId = preset.id + } + + public func providerFavoriteLocationIds() -> Set? { + return provider?.vpnSettings[currentVPNProtocol]?.favoriteLocationIds + } + + public mutating func setProviderFavoriteLocationIds(_ ids: Set?) { + provider?.vpnSettings[currentVPNProtocol]?.favoriteLocationIds = ids + } + + public func providerCustomEndpoint() -> Endpoint? { + return provider?.vpnSettings[currentVPNProtocol]?.customEndpoint + } + + public mutating func setProviderCustomEndpoint(_ endpoint: Endpoint?) { + provider?.vpnSettings[currentVPNProtocol]?.customEndpoint = endpoint + } + + public func providerAccount() -> Profile.Account? { + return provider?.vpnSettings[currentVPNProtocol]?.account + } + + public mutating func setProviderAccount(_ account: Profile.Account?) { + provider?.vpnSettings[currentVPNProtocol]?.account = account + } +} + +extension Profile { + + @MainActor + public func providerOpenVPNSettings(withManager providerManager: ProviderManager) throws -> Profile.OpenVPNSettings { + guard let _ = provider else { + fatalError("Not a provider") + } + + // infer remotes from preset + server + guard let server = providerServer(providerManager) else { + throw PassepartoutError.missingProviderServer + } + guard let preset = providerPreset(server) else { + throw PassepartoutError.missingProviderPreset + } + guard var builder = preset.openVPNConfiguration?.builder() else { + fatalError("Preset \(preset.id) has no OpenVPN configuration") + } + try builder.setRemotes(from: preset, with: server, excludingHostname: !networkSettings.resolvesHostname) + + // enforce default gateway + builder.routingPolicies = [.IPv4, .IPv6] + + // apply provider settings (username, custom endpoint) + let cfg = builder.build() + return OpenVPNSettings( + configuration: cfg, + account: providerAccount(), + customEndpoint: providerCustomEndpoint() + ) + } + + public func providerWireGuardSettings(withManager providerManager: ProviderManager) throws -> Profile.WireGuardSettings { + guard let _ = provider else { + fatalError("Not a provider") + } + fatalError("WireGuard not yet implemented for providers") + } +} + +extension Profile.Provider: ProfileSubtype { + public var vpnProtocols: [VPNProtocolType] { + return vpnSettings.keys.sorted() + } + + public func requiresCredentials(forProtocol vpnProtocol: VPNProtocolType) -> Bool { + return name.requiresCredentials(forProtocol: vpnProtocol) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/ProviderManager+Extensions.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/ProviderManager+Extensions.swift new file mode 100644 index 00000000..d46a2297 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/ProviderManager+Extensions.swift @@ -0,0 +1,41 @@ +// +// ProviderManager+Extensions.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine +import PassepartoutProviders + +extension ProviderManager { + public func fetchRemoteProviderPublisher(forProfile profile: Profile) -> AnyPublisher { + guard let provider = profile.provider else { + fatalError("Not a provider") + } + return fetchProviderPublisher( + withName: provider.name, + vpnProtocol: profile.currentVPNProtocol, + priority: .remote + ) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/SessionProxy+Communication.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/VPNProtocolType+Network.swift similarity index 55% rename from PassepartoutCore/Sources/PassepartoutCore/Model/SessionProxy+Communication.swift rename to PassepartoutCore/Sources/PassepartoutProfiles/Extensions/VPNProtocolType+Network.swift index a883fd27..d34c7898 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/SessionProxy+Communication.swift +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/VPNProtocolType+Network.swift @@ -1,8 +1,8 @@ // -// SessionProxy+Communication.swift +// VPNProtocolType+Extensions.swift // Passepartout // -// Created by Davide De Rosa on 9/4/18. +// Created by Davide De Rosa on 2/11/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -25,18 +25,35 @@ import Foundation import TunnelKitOpenVPN +import TunnelKitWireGuard +import PassepartoutProviders -public extension OpenVPN.ConfigurationBuilder { -// mutating func copyCommunication(from other: OpenVPN.ConfigurationBuilder) { -// cipher = other.cipher -// digest = other.digest -// compressionFraming = other.compressionFraming -// } - - func canCommunicate(with other: OpenVPN.Configuration) -> Bool { - return - (cipher == other.cipher) && - ((digest == other.digest) || fallbackCipher.embedsDigest) && - (compressionFraming == other.compressionFraming) +extension OpenVPN.ProviderConfiguration: VPNProtocolProviding { + public var vpnProtocol: VPNProtocolType { + return .openVPN + } +} + +extension WireGuard.ProviderConfiguration: VPNProtocolProviding { + public var vpnProtocol: VPNProtocolType { + return .wireGuard + } +} + +extension VPNProtocolType { + public var supportsGateway: Bool { + return true + } + + public var supportsDNS: Bool { + return true + } + + public var supportsProxy: Bool { + return self == .openVPN + } + + public var supportsMTU: Bool { + return true } } diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/WireGuardSettings+Network.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/WireGuardSettings+Network.swift new file mode 100644 index 00000000..6af3381d --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Extensions/WireGuardSettings+Network.swift @@ -0,0 +1,56 @@ +// +// WireGuardSettings+Network.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitCore +import TunnelKitWireGuard + +extension Profile.WireGuardSettings: DNSSettingsProviding { + public var dnsProtocol: DNSProtocol { + return .plain + } + + public var dnsServers: [String] { + return configuration.dnsServers + } + + public var dnsSearchDomains: [String] { + return configuration.dnsSearchDomains + } + + public var dnsHTTPSURL: URL? { + return nil + } + + public var dnsTLSServerName: String? { + return nil + } +} + +extension Profile.WireGuardSettings: MTUSettingsProviding { + public var mtuBytes: Int { + return Int(configuration.mtu ?? 0) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager+Keychain.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager+Keychain.swift new file mode 100644 index 00000000..8249dde9 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager+Keychain.swift @@ -0,0 +1,118 @@ +// +// ProfileManager+Keychain.swift +// Passepartout +// +// Created by Davide De Rosa on 4/8/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitManager +import PassepartoutUtils + +extension ProfileManager { + public func savePassword(forProfile profile: Profile) { + guard !profile.isPlaceholder else { + assertionFailure("Placeholder") + return + } + guard let keychainEntry = profile.keychainEntry else { + return + } + let password = profile.account.password + guard !password.isEmpty else { + keychain.removePassword( + for: keychainEntry, + context: appGroup, + userDefined: profile.id.uuidString + ) + return + } + do { + try keychain.set( + password: password, + for: keychainEntry, + context: appGroup, + userDefined: profile.id.uuidString, + label: keychainLabel(profile.header.name, profile.currentVPNProtocol) + ) + } catch { + pp_log.error("Unable to save password to keychain: \(error)") + } + } + + public func passwordReference(forProfile profile: Profile) -> Data? { + guard !profile.isPlaceholder else { + assertionFailure("Placeholder") + return nil + } + guard let keychainEntry = profile.keychainEntry else { + return nil + } + do { + return try keychain.passwordReference( + for: keychainEntry, + context: appGroup, + userDefined: profile.id.uuidString + ) + } catch { + pp_log.debug("Unable to load password reference from keychain: \(error)") + return nil + } + } +} + +private extension Profile { + var keychainEntry: String? { + return "\(id.uuidString):\(currentVPNProtocol.description):\(account.username)" + } +} + +extension Keychain { + func debugAllPasswords(matching id: UUID, context: String) { + var query = allPasswordsQuery(id, context) + query[kSecReturnAttributes as String] = true + + var list: CFTypeRef? + switch SecItemCopyMatching(query as CFDictionary, &list) { + case errSecSuccess: + break + + default: + return + } + guard let list = list else { + pp_log.debug("Keychain items: none") + return + } + pp_log.debug("Keychain items: \(list)") + } + + func removeAllPasswords(matching id: UUID, context: String) { + _ = SecItemDelete(allPasswordsQuery(id, context) as CFDictionary) + } + + private func allPasswordsQuery(_ id: UUID, _ context: String) -> [String: Any] { + var query = [String: Any]() + setScope(query: &query, context: context, userDefined: id.uuidString) + query[kSecClass as String] = kSecClassGenericPassword + return query + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager+Processing.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager+Processing.swift new file mode 100644 index 00000000..74b631a6 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager+Processing.swift @@ -0,0 +1,49 @@ +// +// ProfileManager+Processing.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitOpenVPN +import TunnelKitWireGuard + +extension ProfileManager { + public func profile(withHeader header: Profile.Header, fromURL url: URL, passphrase: String?) throws -> Profile { + let contents = try String(contentsOf: url) + return try profile(withHeader: header, fromContents: contents, originalURL: url, passphrase: passphrase) + } + + public func profile(withHeader header: Profile.Header, fromContents contents: String, originalURL: URL?, passphrase: String?) throws -> Profile { + do { + let ovpn = try OpenVPN.ConfigurationParser.parsed(fromContents: contents, passphrase: passphrase, originalURL: originalURL) + return Profile(header, configuration: ovpn.configuration) + } catch let ovpnError as OpenVPN.ConfigurationError { + do { + let wg = try WireGuard.Configuration(wgQuickConfig: contents) + return Profile(header, configuration: wg) + } catch { + throw ovpnError + } + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager.swift new file mode 100644 index 00000000..4fc0c242 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManager.swift @@ -0,0 +1,406 @@ +// +// ProfileManager.swift +// Passepartout +// +// Created by Davide De Rosa on 2/25/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine +import TunnelKitManager +import PassepartoutProviders +import PassepartoutUtils + +@MainActor +public class ProfileManager: ObservableObject { + public typealias LoadResult = (isReady: Bool, profile: Profile) + + // MARK: Initialization + + private let providerManager: ProviderManager + + public let appGroup: String + + let keychainLabel: (String, VPNProtocolType) -> String + + let keychain: Keychain + + private let strategy: ProfileManagerStrategy + + public var availabilityFilter: ((Profile.Header) -> Bool)? + + public var activeProfileId: UUID? { + willSet { + willUpdateActiveId.send(newValue) + objectWillChange.send() + } + didSet { + pp_log.debug("Active profile updated: \(activeProfileId?.uuidString ?? "nil")") + } + } + + // MARK: Observables + + public let currentProfile: ObservableProfile + + public let willUpdateActiveId = PassthroughSubject() + + public let willUpdateCurrentProfile = PassthroughSubject() + + public let didCreateProfile = PassthroughSubject() + + private var cancellables: Set = [] + + public init( + providerManager: ProviderManager, + appGroup: String, + keychainLabel: @escaping (String, VPNProtocolType) -> String, + strategy: ProfileManagerStrategy + ) { + guard let _ = UserDefaults(suiteName: appGroup) else { + fatalError("No entitlements for group '\(appGroup)'") + } + self.providerManager = providerManager + self.appGroup = appGroup + self.keychainLabel = keychainLabel + keychain = Keychain(group: appGroup) + self.strategy = strategy + + currentProfile = ObservableProfile() + observeUpdates() + } +} + +// MARK: Index + +extension ProfileManager { + private var allHeaders: [UUID: Profile.Header] { + strategy.allHeaders + } + + private var availableHeaders: [Profile.Header] { + if let availabilityFilter = availabilityFilter { + return allHeaders.values.filter(availabilityFilter) + } + return Array(allHeaders.values) + } + + public var headers: [Profile.Header] { + availableHeaders + } + + public var activeHeader: Profile.Header? { + availableHeaders.first { + $0.id == activeProfileId + } + } + + public func isActiveProfile(_ id: UUID) -> Bool { + id == activeProfileId + } + + // existence in persistent storage (skips availability) + public func isExistingProfile(withId id: UUID) -> Bool { + allHeaders[id] != nil + } + + // existence in persistent storage (skips availability) + public func isExistingProfile(withName name: String) -> Bool { + allHeaders.contains { + $0.value.name == name + } + } +} + +// MARK: Profiles + +extension ProfileManager { + public var activeProfile: Profile? { + guard let activeHeader = activeHeader else { + return nil + } + return profile(withId: activeHeader.id) + } + + public func activateProfile(_ profile: Profile) { + saveProfile(profile, isActive: true) + } + + public func loadProfile(withId id: UUID) throws -> LoadResult { + guard let profile = profile(withId: id) else { + pp_log.error("Profile not found: \(id)") + throw PassepartoutError.missingProfile + } + pp_log.info("Found profile: \(profile.logDescription)") + return (isProfileReady(profile), profile) + } + + private func profile(withId id: UUID) -> Profile? { + pp_log.debug("Searching profile \(id)") + + // IMPORTANT: fetch live copy first (see intents) + if isCurrentProfile(id) { + pp_log.debug("Profile \(currentProfile.value.logDescription) found in memory") + return currentProfile.value + } + + guard let profile = strategy.profile(withId: id) else { + assertionFailure("Profile in headers yet not found in persistent store") + return nil + } + guard availabilityFilter?(profile.header) ?? true else { + pp_log.warning("Profile \(profile.logDescription) not available due to filter") + return nil + } + guard !profile.vpnProtocols.isEmpty else { + assertionFailure("Ditching profile, no OpenVPN/WireGuard settings found") + return nil + } + + pp_log.debug("Profile \(profile.logDescription) found") + keychain.debugAllPasswords(matching: id, context: appGroup) + + return profile + } + + public func saveProfile(_ profile: Profile, isActive: Bool?) { + guard !profile.isPlaceholder else { + assertionFailure("Placeholder") + return + } + + pp_log.info("Writing profile \(profile.logDescription) to persistent store") + strategy.saveProfile(profile) + + if let isActive = isActive { + if isActive { + pp_log.info("\tActivating profile...") + activeProfileId = profile.id + } else if activeProfileId == profile.id { + pp_log.info("\tDeactivating profile...") + activeProfileId = nil + } + } + + // IMPORTANT: refresh live copy if just saved + if isCurrentProfile(profile.id) { + pp_log.info("Saved profile is also current profile, updating...") + currentProfile.value = profile + } + } + + public func removeProfiles(withIds ids: [UUID]) { + pp_log.info("Deleting profiles with ids \(ids)") + + pp_log.info("\tDeleting passwords from keychain...") + for id in ids { + keychain.removeAllPasswords(matching: id, context: appGroup) + } + + pp_log.info("\tDeleting from persistent store...") + strategy.removeProfiles(withIds: ids) + } + + @available(*, deprecated, message: "only use for testing") + public func removeAllProfiles() { + let ids = Array(allHeaders.keys) + removeProfiles(withIds: ids) + } + + public func persist() { + pp_log.info("Persisting profiles") + saveCurrentProfile() + } +} + +// MARK: Observation + +extension ProfileManager { + public func loadCurrentProfile(withId id: UUID) throws -> LoadResult { + if isExistingProfile(withId: currentProfile.value.id) { + pp_log.info("Committing changes of former current profile \(currentProfile.value.logDescription)") + saveCurrentProfile() + } + do { + let result = try loadProfile(withId: id) + pp_log.info("Current profile: \(result.profile.logDescription)") + currentProfile.value = result.profile + return result + } catch { + currentProfile.value = .placeholder + throw error + } + } + + public func isCurrentProfileActive() -> Bool { + currentProfile.value.id == activeProfileId + } + + public func isCurrentProfile(_ id: UUID) -> Bool { + id == currentProfile.value.id + } + + public func activateCurrentProfile() { + activateProfile(currentProfile.value) + } + + public func saveCurrentProfile() { + guard !currentProfile.value.isPlaceholder else { + pp_log.debug("No current profile or deleted, not persisting") + return + } + saveProfile( + currentProfile.value, + isActive: currentProfile.value.id == activeProfileId + ) + } +} + +extension ProfileManager { + private func observeUpdates() { + strategy.willUpdateProfiles() + .dropFirst() + .sink { + self.willUpdateProfiles($0) + }.store(in: &cancellables) + + currentProfile.$value + .dropFirst() + .sink { + self.willUpdateCurrentProfile($0) + }.store(in: &cancellables) + } + + private func willUpdateProfiles(_ newHeaders: [UUID: Profile.Header]) { + pp_log.debug("Profiles updated: \(newHeaders)") + defer { + objectWillChange.send() + } + + // IMPORTANT: invalidate current profile if deleted + if !currentProfile.value.isPlaceholder && newHeaders.keys.contains(currentProfile.value.id) { + pp_log.info("\tCurrent profile deleted, invalidating...") + if isCurrentProfileActive() { + pp_log.info("\tCurrent profile was also active, deactivating...") + activeProfileId = nil + } + currentProfile.value = .placeholder + } + + let newProfile = strategy.profile(withId: currentProfile.value.id) + if let newProfile = newProfile, newProfile != currentProfile.value { + pp_log.info("Current profile remotely updated") + currentProfile.value = newProfile + } + + // IMPORTANT: defer task to avoid recursive saves + // FIXME: Core Data, not sure about this workaround + Task { + fixDuplicateNames(in: newHeaders) + } + } + + private func willUpdateCurrentProfile(_ newProfile: Profile) { + pp_log.debug("Current profile updated: \(newProfile.logDescription)") + // observe current profile explicitly (no objectWillChange) + + willUpdateCurrentProfile.send(newProfile) + } + + private func fixDuplicateNames(in newHeaders: [UUID: Profile.Header]) { + var allNames = newHeaders.values.map(\.name) + let distinctNames = Set(allNames) + distinctNames.forEach { + guard let i = allNames.firstIndex(of: $0) else { + return + } + allNames.remove(at: i) + } + let duplicates = Set(allNames) + + pp_log.debug("Duplicate profile names: \(duplicates)") + + var renamedProfiles: [Profile] = [] + duplicates.forEach { name in + let headers = newHeaders.values.filter { + $0.name == name + } + guard headers.count > 1 else { + assertionFailure("Name '\(name)' marked as duplicate but headers.count not > 1") + return + } + +// headers.removeFirst() + headers.forEach { dupHeader in + let uniqueHeader = dupHeader.renamedUniquely() + pp_log.debug("Renaming duplicate profile \(dupHeader.logDescription) to \(uniqueHeader.logDescription)") + guard var uniqueProfile = profile(withId: uniqueHeader.id) else { + pp_log.warning("Skipping profile \(dupHeader.logDescription) renaming, not found") + return + } + uniqueProfile.header = uniqueHeader + renamedProfiles.append(uniqueProfile) + } + } + strategy.saveProfiles(renamedProfiles) + pp_log.debug("Duplicates successfully renamed!") + } +} + +// MARK: Readiness + +extension ProfileManager { + private func isProfileReady(_ profile: Profile) -> Bool { + isProfileProviderAvailable(profile) + } + + public func makeProfileReady(_ profile: Profile) async throws { + try await fetchProfileProviderIfMissing(profile) + } + + private func isProfileProviderAvailable(_ profile: Profile) -> Bool { + guard let providerName = profile.header.providerName else { + return true // host + } + return providerManager.isAvailable(providerName, vpnProtocol: profile.currentVPNProtocol) + } + + private func fetchProfileProviderIfMissing(_ profile: Profile) async throws { + guard let providerName = profile.header.providerName else { + return // host + } + if providerManager.isAvailable(providerName, vpnProtocol: profile.currentVPNProtocol) { + return + } + do { + pp_log.info("Importing missing provider \(providerName)...") + try await providerManager.fetchProviderPublisher( + withName: providerName, + vpnProtocol: profile.currentVPNProtocol, + priority: .remoteThenBundle + ).async() + pp_log.info("Finished!") + } catch { + pp_log.error("Unable to import missing provider: \(error)") + throw PassepartoutError.missingProfile + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy+CoreData.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy+CoreData.swift new file mode 100644 index 00000000..91c804ac --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy+CoreData.swift @@ -0,0 +1,66 @@ +// +// ProfileManagerStrategy+CoreData.swift +// Passepartout +// +// Created by Davide De Rosa on 4/9/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine +import PassepartoutUtils + +extension ProfileManager { + public class CoreDataStrategy: ProfileManagerStrategy { + private let profileRepository: ProfileRepository + + private let fetchedHeaders: FetchedValueHolder<[UUID: Profile.Header]> + + public init(persistence: Persistence) { + profileRepository = ProfileRepository(persistence.context) + fetchedHeaders = profileRepository.headers() + } + + public var allHeaders: [UUID: Profile.Header] { + fetchedHeaders.value + } + + public func profile(withId id: UUID) -> Profile? { + profileRepository.profile(withId: id).value + } + + public func saveProfiles(_ profiles: [Profile]) { + do { + try profileRepository.saveProfiles(profiles) + } catch { + pp_log.error("Unable to save profile: \(error)") + } + } + + public func removeProfiles(withIds ids: [UUID]) { + profileRepository.removeProfiles(withIds: ids) + } + + public func willUpdateProfiles() -> AnyPublisher<[UUID : Profile.Header], Never> { + fetchedHeaders.$value + .eraseToAnyPublisher() + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift new file mode 100644 index 00000000..b9712e91 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift @@ -0,0 +1,46 @@ +// +// ProfileManagerStrategy.swift +// Passepartout +// +// Created by Davide De Rosa on 4/9/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine + +@MainActor +public protocol ProfileManagerStrategy { + var allHeaders: [UUID: Profile.Header] { get } + + func profile(withId: UUID) -> Profile? + + func saveProfiles(_ profiles: [Profile]) + + func removeProfiles(withIds ids: [UUID]) + + func willUpdateProfiles() -> AnyPublisher<[UUID: Profile.Header], Never> +} + +extension ProfileManagerStrategy { + public func saveProfile(_ profile: Profile) { + saveProfiles([profile]) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/TrustedNetworks.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Models/ObservableProfile.swift similarity index 73% rename from PassepartoutCore/Sources/PassepartoutCore/Model/TrustedNetworks.swift rename to PassepartoutCore/Sources/PassepartoutProfiles/Models/ObservableProfile.swift index 519772d7..da446bda 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/TrustedNetworks.swift +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Models/ObservableProfile.swift @@ -1,8 +1,8 @@ // -// TrustPolicy.swift +// ObservableProfile.swift // Passepartout // -// Created by Davide De Rosa on 11/21/19. +// Created by Davide De Rosa on 3/27/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,18 +24,16 @@ // import Foundation +import PassepartoutUtils -public struct TrustedNetworks: Codable { - #if os(iOS) - public var includesMobile = false - #else - public var includesEthernet = false - #endif +public class ObservableProfile: ValueHolder, ObservableObject { + @Published public var value: Profile - public var includedWiFis: [String: Bool] = [:] - - public var policy: TrustPolicy = .disconnect + public var name: String { + value.header.name + } public init() { + value = .placeholder } } diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/PassepartoutProfiles.swift b/PassepartoutCore/Sources/PassepartoutProfiles/PassepartoutProfiles.swift new file mode 100644 index 00000000..f281e5e5 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/PassepartoutProfiles.swift @@ -0,0 +1,45 @@ +// +// PassepartoutProfiles.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutUtils + +extension PassepartoutError { + public static let missingProfile = Self("missingProfile") + + public static let missingProviderServer = Self("missingProviderServer") + + public static let missingProviderPreset = Self("missingProviderPreset") +} + +extension PassepartoutDataModels { + public static let profiles: NSManagedObjectModel = { + guard let model = NSManagedObjectModel.mergedModel(from: [.module]) else { + fatalError("Could not load PassepartoutProfiles model") + } + return model + }() +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Pickers/Picker+Network.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Pickers/Picker+Network.swift new file mode 100644 index 00000000..0fd209b8 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Pickers/Picker+Network.swift @@ -0,0 +1,44 @@ +// +// Picker+Network.swift +// Passepartout +// +// Created by Davide De Rosa on 2/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitCore +import PassepartoutProviders + +extension Network.DNSSettings { + public static func availableProtocols(forVPNProtocol vpnProtocol: VPNProtocolType) -> [DNSProtocol] { + switch vpnProtocol { + case .openVPN: + return [.plain, .https, .tls] + + case .wireGuard: + return [.plain] + } + } +} + +extension Network.MTUSettings { + public static let availableBytes: [Int] = [0, 1500, 1400, 1300, 1200] +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/OpenVPNOptions.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Pickers/Picker+OpenVPN.swift similarity index 98% rename from PassepartoutCore/Sources/PassepartoutCore/Model/OpenVPNOptions.swift rename to PassepartoutCore/Sources/PassepartoutProfiles/Pickers/Picker+OpenVPN.swift index 8c653caf..37d78921 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/OpenVPNOptions.swift +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Pickers/Picker+OpenVPN.swift @@ -1,5 +1,5 @@ // -// OpenVPNOptions.swift +// Picker+OpenVPN.swift // Passepartout // // Created by Davide De Rosa on 6/22/19. diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Profiles.xcdatamodeld/Model.xcdatamodel/contents b/PassepartoutCore/Sources/PassepartoutProfiles/Profiles.xcdatamodeld/Model.xcdatamodel/contents new file mode 100644 index 00000000..773ded00 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Profiles.xcdatamodeld/Model.xcdatamodel/contents @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Repositories/ProfileMapper.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Repositories/ProfileMapper.swift new file mode 100644 index 00000000..7b0a49f1 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Repositories/ProfileMapper.swift @@ -0,0 +1,92 @@ +// +// ProfileMapper.swift +// Passepartout +// +// Created by Davide De Rosa on 3/18/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutUtils + +struct ProfileMapper: DTOMapper, ModelMapper { + private let context: NSManagedObjectContext + + init(_ context: NSManagedObjectContext) { + self.context = context + } + + func toDTO(_ ws: Profile) throws -> CDProfile { + let profile = ProfileHeaderMapper(context).toDTO(ws) + do { + profile.json = try JSONEncoder().encode(ws) + } catch { + assertionFailure("Unable to encode profile: \(error)") + throw error + } + return profile + } + + static func toModel(_ dto: CDProfile) throws -> Profile? { + guard let json = dto.json else { + Utils.assertCoreDataDecodingFailed(#file, #function, #line) + return nil + } + do { + return try JSONDecoder().decode(Profile.self, from: json) + } catch { + assertionFailure("Unable to decode profile: \(error)") + throw error + } + } +} + +struct ProfileHeaderMapper: DTOMapper, ModelMapper { + private let context: NSManagedObjectContext + + init(_ context: NSManagedObjectContext) { + self.context = context + } + + func toDTO(_ ws: Profile) -> CDProfile { + let profile = CDProfile(context: context) + profile.uuid = ws.header.id + profile.name = ws.header.name + profile.providerName = ws.header.providerName + profile.lastUpdate = Date() + return profile + } + + static func toModel(_ dto: CDProfile) -> Profile.Header? { + guard let uuid = dto.uuid, + let name = dto.name else { + + Utils.assertCoreDataDecodingFailed(#file, #function, #line) + return nil + } + return Profile.Header( + uuid: uuid, + name: name, + providerName: dto.providerName, + lastUpdate: dto.lastUpdate + ) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift b/PassepartoutCore/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift new file mode 100644 index 00000000..8eced4be --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift @@ -0,0 +1,131 @@ +// +// ProfileRepository.swift +// Passepartout +// +// Created by Davide De Rosa on 3/19/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutUtils + +class ProfileRepository: Repository { + private let context: NSManagedObjectContext + + required init(_ context: NSManagedObjectContext) { + self.context = context + } + + func headers() -> FetchedValueHolder<[UUID: Profile.Header]> { + let request: NSFetchRequest = CDProfile.fetchRequest() + request.sortDescriptors = [ + .init(keyPath: \CDProfile.uuid, ascending: true), + .init(keyPath: \CDProfile.lastUpdate, ascending: false) + ] + request.propertiesToFetch = [ + "uuid", + "name", + "providerName" + ] + return .init( + context: context, + request: request, + mapping: { + $0.reduce(into: [UUID: Profile.Header]()) { + guard let dto = $1 as? CDProfile else { + return + } + guard let header = ProfileHeaderMapper.toModel(dto) else { + return + } + $0[header.id] = header + } + }, + initial: [:] + ) + } + + func profile(withId id: UUID) -> FetchedValueHolder { + let request: NSFetchRequest = CDProfile.fetchRequest() + request.sortDescriptors = [ + .init(keyPath: \CDProfile.lastUpdate, ascending: false) + ] + request.predicate = NSPredicate( + format: "uuid == %@", + id.uuidString + ) + return .init( + context: context, + request: request, + mapping: { + guard let dto = $0.first as? CDProfile else { + return nil + } + do { + return try ProfileMapper.toModel(dto) + } catch { + pp_log.error("Unable to map CDProfile: \(error)") + return nil + } + }, + initial: nil + ) + } + + func saveProfiles(_ profiles: [Profile]) throws { + let request = CDProfile.fetchRequest() + request.predicate = NSPredicate( + format: "uuid in %@ OR name in %@", // replace with same name + profiles.map(\.id.uuidString), + profiles.map(\.header.name) + ) + do { + // dedup + let existing = try context.fetch(request) + existing.forEach(context.delete) + + try profiles.forEach { + _ = try ProfileMapper(context).toDTO($0) + } + try context.save() + } catch { + context.rollback() + throw error + } + } + + func removeProfiles(withIds ids: [UUID]) { + let request = CDProfile.fetchRequest() + request.predicate = NSPredicate( + format: "any uuid in %@", + ids.map(\.uuidString) + ) + do { + try context.fetch(request).forEach { + context.delete($0) + } + try context.save() + } catch { + pp_log.error("Unable to remove profiles: \(error)") + context.rollback() + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataClass.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataClass.swift new file mode 100644 index 00000000..54a39d19 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// CDInfrastructure+CoreDataClass.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + +@objc(CDInfrastructure) +public class CDInfrastructure: NSManagedObject { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataProperties.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataProperties.swift new file mode 100644 index 00000000..7572a354 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataProperties.swift @@ -0,0 +1,47 @@ +// +// CDInfrastructure+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + + +extension CDInfrastructure { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDInfrastructure") + } + + @NSManaged public var lastUpdate: Date? + @NSManaged public var vpnProtocol: String? + @NSManaged public var categories: NSSet? + @NSManaged public var defaults: CDInfrastructureDefaultSettings? + @NSManaged public var provider: CDProvider? + +} + +// MARK: Generated accessors for categories +extension CDInfrastructure { + + @objc(addCategoriesObject:) + @NSManaged public func addToCategories(_ value: CDInfrastructureCategory) + + @objc(removeCategoriesObject:) + @NSManaged public func removeFromCategories(_ value: CDInfrastructureCategory) + + @objc(addCategories:) + @NSManaged public func addToCategories(_ values: NSSet) + + @objc(removeCategories:) + @NSManaged public func removeFromCategories(_ values: NSSet) + +} + +extension CDInfrastructure : Identifiable { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataClass.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataClass.swift new file mode 100644 index 00000000..b78a55f6 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// CDInfrastructureCategory+CoreDataClass.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + +@objc(CDInfrastructureCategory) +public class CDInfrastructureCategory: NSManagedObject { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataProperties.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataProperties.swift new file mode 100644 index 00000000..df36533f --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataProperties.swift @@ -0,0 +1,81 @@ +// +// CDInfrastructureCategory+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + + +extension CDInfrastructureCategory { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDInfrastructureCategory") + } + + @NSManaged public var name: String? + @NSManaged public var infrastructure: CDInfrastructure? + @NSManaged public var locations: NSSet? + @NSManaged public var presets: NSSet? + @NSManaged public var servers: NSSet? + +} + +// MARK: Generated accessors for locations +extension CDInfrastructureCategory { + + @objc(addLocationsObject:) + @NSManaged public func addToLocations(_ value: CDInfrastructureLocation) + + @objc(removeLocationsObject:) + @NSManaged public func removeFromLocations(_ value: CDInfrastructureLocation) + + @objc(addLocations:) + @NSManaged public func addToLocations(_ values: NSSet) + + @objc(removeLocations:) + @NSManaged public func removeFromLocations(_ values: NSSet) + +} + +// MARK: Generated accessors for presets +extension CDInfrastructureCategory { + + @objc(addPresetsObject:) + @NSManaged public func addToPresets(_ value: CDInfrastructurePreset) + + @objc(removePresetsObject:) + @NSManaged public func removeFromPresets(_ value: CDInfrastructurePreset) + + @objc(addPresets:) + @NSManaged public func addToPresets(_ values: NSSet) + + @objc(removePresets:) + @NSManaged public func removeFromPresets(_ values: NSSet) + +} + +// MARK: Generated accessors for servers +extension CDInfrastructureCategory { + + @objc(addServersObject:) + @NSManaged public func addToServers(_ value: CDInfrastructureServer) + + @objc(removeServersObject:) + @NSManaged public func removeFromServers(_ value: CDInfrastructureServer) + + @objc(addServers:) + @NSManaged public func addToServers(_ values: NSSet) + + @objc(removeServers:) + @NSManaged public func removeFromServers(_ values: NSSet) + +} + +extension CDInfrastructureCategory : Identifiable { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataClass.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataClass.swift new file mode 100644 index 00000000..a6643303 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// CDInfrastructureDefaultSettings+CoreDataClass.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + +@objc(CDInfrastructureDefaultSettings) +public class CDInfrastructureDefaultSettings: NSManagedObject { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataProperties.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataProperties.swift new file mode 100644 index 00000000..a35ce90a --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataProperties.swift @@ -0,0 +1,28 @@ +// +// CDInfrastructureDefaultSettings+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + + +extension CDInfrastructureDefaultSettings { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDInfrastructureDefaultSettings") + } + + @NSManaged public var countryCode: String? + @NSManaged public var usernamePlaceholder: String? + @NSManaged public var infrastructure: CDInfrastructure? + +} + +extension CDInfrastructureDefaultSettings : Identifiable { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataClass.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataClass.swift new file mode 100644 index 00000000..d08dc5b7 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// CDInfrastructureLocation+CoreDataClass.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + +@objc(CDInfrastructureLocation) +public class CDInfrastructureLocation: NSManagedObject { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataProperties.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataProperties.swift new file mode 100644 index 00000000..b472d2e4 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataProperties.swift @@ -0,0 +1,45 @@ +// +// CDInfrastructureLocation+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + + +extension CDInfrastructureLocation { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDInfrastructureLocation") + } + + @NSManaged public var countryCode: String? + @NSManaged public var category: CDInfrastructureCategory? + @NSManaged public var servers: NSSet? + +} + +// MARK: Generated accessors for servers +extension CDInfrastructureLocation { + + @objc(addServersObject:) + @NSManaged public func addToServers(_ value: CDInfrastructureServer) + + @objc(removeServersObject:) + @NSManaged public func removeFromServers(_ value: CDInfrastructureServer) + + @objc(addServers:) + @NSManaged public func addToServers(_ values: NSSet) + + @objc(removeServers:) + @NSManaged public func removeFromServers(_ values: NSSet) + +} + +extension CDInfrastructureLocation : Identifiable { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataClass.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataClass.swift new file mode 100644 index 00000000..21bad0ab --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// CDInfrastructurePreset+CoreDataClass.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + +@objc(CDInfrastructurePreset) +public class CDInfrastructurePreset: NSManagedObject { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataProperties.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataProperties.swift new file mode 100644 index 00000000..c2cdc072 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataProperties.swift @@ -0,0 +1,48 @@ +// +// CDInfrastructurePreset+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + + +extension CDInfrastructurePreset { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDInfrastructurePreset") + } + + @NSManaged public var comment: String? + @NSManaged public var id: String? + @NSManaged public var name: String? + @NSManaged public var vpnConfiguration: Data? + @NSManaged public var vpnProtocol: String? + @NSManaged public var category: NSSet? + +} + +// MARK: Generated accessors for category +extension CDInfrastructurePreset { + + @objc(addCategoryObject:) + @NSManaged public func addToCategory(_ value: CDInfrastructureCategory) + + @objc(removeCategoryObject:) + @NSManaged public func removeFromCategory(_ value: CDInfrastructureCategory) + + @objc(addCategory:) + @NSManaged public func addToCategory(_ values: NSSet) + + @objc(removeCategory:) + @NSManaged public func removeFromCategory(_ values: NSSet) + +} + +extension CDInfrastructurePreset : Identifiable { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataClass.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataClass.swift new file mode 100644 index 00000000..457c5446 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// CDInfrastructureServer+CoreDataClass.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + +@objc(CDInfrastructureServer) +public class CDInfrastructureServer: NSManagedObject { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataProperties.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataProperties.swift new file mode 100644 index 00000000..e5ba5a35 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataProperties.swift @@ -0,0 +1,36 @@ +// +// CDInfrastructureServer+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + + +extension CDInfrastructureServer { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDInfrastructureServer") + } + + @NSManaged public var area: String? + @NSManaged public var countryCode: String? + @NSManaged public var extraCountryCodes: String? + @NSManaged public var hostname: String? + @NSManaged public var ipAddresses: String? + @NSManaged public var apiId: String? + @NSManaged public var serverIndex: Int16 + @NSManaged public var tags: String? + @NSManaged public var uniqueId: String? + @NSManaged public var category: CDInfrastructureCategory? + @NSManaged public var location: CDInfrastructureLocation? + +} + +extension CDInfrastructureServer : Identifiable { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataClass.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataClass.swift new file mode 100644 index 00000000..9a5d523b --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// CDProvider+CoreDataClass.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + +@objc(CDProvider) +public class CDProvider: NSManagedObject { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataProperties.swift b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataProperties.swift new file mode 100644 index 00000000..bb514a1b --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataProperties.swift @@ -0,0 +1,46 @@ +// +// CDProvider+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import Foundation +import CoreData + + +extension CDProvider { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDProvider") + } + + @NSManaged public var fullName: String? + @NSManaged public var name: String? + @NSManaged public var infrastructures: NSSet? + @NSManaged public var lastUpdate: Date? + +} + +// MARK: Generated accessors for infrastructures +extension CDProvider { + + @objc(addInfrastructuresObject:) + @NSManaged public func addToInfrastructures(_ value: CDInfrastructure) + + @objc(removeInfrastructuresObject:) + @NSManaged public func removeFromInfrastructures(_ value: CDInfrastructure) + + @objc(addInfrastructures:) + @NSManaged public func addToInfrastructures(_ values: NSSet) + + @objc(removeInfrastructures:) + @NSManaged public func removeFromInfrastructures(_ values: NSSet) + +} + +extension CDProvider : Identifiable { + +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Identifiable.swift b/PassepartoutCore/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Identifiable.swift new file mode 100644 index 00000000..26276fab --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Identifiable.swift @@ -0,0 +1,46 @@ +// +// PassepartoutProviders+Identifiable.swift +// Passepartout +// +// Created by Davide De Rosa on 3/25/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +// primary keys within infrastructure (pinned: vpnProtocol) + +extension ProviderCategory: Identifiable { + public var id: String { + return "\(providerMetadata.name):\(name)" + } +} + +extension ProviderLocation: Identifiable { + public var id: String { + return "\(providerMetadata.name):\(categoryName):\(countryCode)" + } +} + +extension ProviderServer { + public var locationId: String { + return "\(providerMetadata.name):\(categoryName):\(countryCode)" + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Logging.swift b/PassepartoutCore/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Logging.swift new file mode 100644 index 00000000..ac055a4f --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Logging.swift @@ -0,0 +1,32 @@ +// +// PassepartoutProviders+Logging.swift +// Passepartout +// +// Created by Davide De Rosa on 3/25/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension ProviderServer { + public var logDescription: String { + return "{'\(categoryName)', \(countryCode), '\(apiId)', \(id)}" + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure+CredentialsPurpose.swift b/PassepartoutCore/Sources/PassepartoutProviders/Extensions/ProviderName+Credentials.swift similarity index 75% rename from PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure+CredentialsPurpose.swift rename to PassepartoutCore/Sources/PassepartoutProviders/Extensions/ProviderName+Credentials.swift index 3006c280..b1332cd9 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure+CredentialsPurpose.swift +++ b/PassepartoutCore/Sources/PassepartoutProviders/Extensions/ProviderName+Credentials.swift @@ -1,8 +1,8 @@ // -// Infrastructure+CredentialsPurpose.swift +// ProviderName+Credentials.swift // Passepartout // -// Created by Davide De Rosa on 7/30/21. +// Created by Davide De Rosa on 3/25/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,18 +24,13 @@ // import Foundation -import PassepartoutConstants -extension Infrastructure { - public enum CredentialsPurpose { - case web - - case specific +extension ProviderName { + public func requiresCredentials(forProtocol vpnProtocol: VPNProtocolType) -> Bool { + return vpnProtocol == .openVPN } -} -extension InfrastructureName { - public var credentialsPurpose: Infrastructure.CredentialsPurpose { + public var credentialsPurpose: CredentialsPurpose { switch self { case .protonvpn, .surfshark, .torguard, .windscribe: return .specific diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Extensions/VPNProtocolType+Extensions.swift b/PassepartoutCore/Sources/PassepartoutProviders/Extensions/VPNProtocolType+Extensions.swift new file mode 100644 index 00000000..ea59a0e7 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Extensions/VPNProtocolType+Extensions.swift @@ -0,0 +1,32 @@ +// +// VPNProtocolType+Extensions.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension VPNProtocolType: Comparable { + public static func <(lhs: Self, rhs: Self) -> Bool { + return lhs.description < rhs.description + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Managers/ProviderManager.swift b/PassepartoutCore/Sources/PassepartoutProviders/Managers/ProviderManager.swift new file mode 100644 index 00000000..9104ba04 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Managers/ProviderManager.swift @@ -0,0 +1,268 @@ +// +// ProviderManager.swift +// Passepartout +// +// Created by Davide De Rosa on 3/13/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine +import PassepartoutServices +import PassepartoutUtils + +enum ProviderManagerError: LocalizedError { + case outdatedBuild(Int, Int) + + var errorDescription: String? { + switch self { + case .outdatedBuild(let current, let min): + return "Build is outdated (found \(current), required \(min))" + } + } +} + +@MainActor +public class ProviderManager: ObservableObject, RateLimited { + public enum FetchPriority { + case bundle + + case remote + + case remoteThenBundle + } + + private let appBuild: Int + + private let bundleServices: WebServices + + private let webServices: WebServices + + private let persistence: Persistence + + private let providerRepository: ProviderRepository + + private let infrastructureRepository: InfrastructureRepository + + private let serverRepository: ServerRepository + + public init(appBuild: Int, bundleServices: WebServices, webServices: WebServices, persistence: Persistence) { + self.appBuild = appBuild + self.bundleServices = bundleServices + self.webServices = webServices + self.persistence = persistence + providerRepository = ProviderRepository(persistence.context) + infrastructureRepository = InfrastructureRepository(persistence.context) + serverRepository = ServerRepository(persistence.context) + + _ = allProviders() + } + + // MARK: Queries + + public func allProviders() -> [ProviderMetadata] { + providerRepository.allProviders() + } + + public func provider(withName name: ProviderName) -> ProviderMetadata? { + providerRepository.provider(withName: name) + } + + public func isAvailable(_ name: ProviderName, vpnProtocol: VPNProtocolType) -> Bool { + infrastructureRepository.lastInfrastructureUpdate(withName: name, vpnProtocol: vpnProtocol) != nil + } + + public func defaultUsername(_ name: ProviderName, vpnProtocol: VPNProtocolType) -> String? { + infrastructureRepository.defaultUsername(forProviderWithName: name, vpnProtocol: vpnProtocol) + } + + public func lastUpdate(_ name: ProviderName, vpnProtocol: VPNProtocolType) -> Date? { + infrastructureRepository.lastInfrastructureUpdate(withName: name, vpnProtocol: vpnProtocol) + } + + public func categories(_ name: ProviderName, vpnProtocol: VPNProtocolType) -> [ProviderCategory] { + serverRepository.categories(forProviderWithName: name, vpnProtocol: vpnProtocol) + } + + public func servers(forLocation location: ProviderLocation) -> [ProviderServer] { + serverRepository.servers(forLocation: location) + } + +// @available(*, deprecated, message: "only use for migration, server is not cached") + public func server(_ name: ProviderName, vpnProtocol: VPNProtocolType, serverId: String) -> ProviderServer? { + serverRepository.server(forProviderWithName: name, vpnProtocol: vpnProtocol, serverId: serverId) + } + +// public func anyServer(forProviderWithName providerName: ProviderName, vpnProtocol: VPNProtocolType, countryCode: String) -> ProviderServer? { +// serverRepository.anyServer(forProviderWithName: providerName, vpnProtocol: vpnProtocol, countryCode: countryCode) +// } + + public func anyDefaultServer(_ name: ProviderName, vpnProtocol: VPNProtocolType) -> ProviderServer? { + serverRepository.anyDefaultServer(forProviderWithName: name, vpnProtocol: vpnProtocol) + } + + public func server(withId id: String) -> ProviderServer? { + serverRepository.server(withId: id) + } + + // MARK: Modification + + public func fetchProvidersIndexPublisher(priority: FetchPriority) -> AnyPublisher { + guard !isRateLimited(indexActionName) else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + let publisher = priority.publisher(remote: { + self.webServices.providersIndex() + }, bundle: { + self.bundleServices.providersIndex() + }) + + return publisher + .receive(on: DispatchQueue.main) + .tryMap { index in + self.saveLastAction(self.indexActionName) + try self.providerRepository.mergeIndex(index) + }.eraseToAnyPublisher() + } + + public func fetchProviderPublisher(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, priority: FetchPriority) -> AnyPublisher { + guard !isRateLimited(providerName) else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + let publisher = priority.publisher(remote: { + let ifModifiedSince = self.infrastructureRepository.lastInfrastructureUpdate(withName: providerName, vpnProtocol: vpnProtocol) + return self.webServices.providerNetwork(with: providerName, vpnProtocol: vpnProtocol, ifModifiedSince: ifModifiedSince) + }, bundle: { + self.bundleServices.providerNetwork(with: providerName, vpnProtocol: vpnProtocol, ifModifiedSince: nil) + }) + + return publisher + .receive(on: DispatchQueue.main) + .flatMap { pub -> AnyPublisher in + self.saveLastAction(providerName) + + // ignores empty responses (e.g. HTTP 304) + guard let infrastructure = pub.value else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + guard self.appBuild >= infrastructure.buildNumber else { + pp_log.error("Infrastructure requires app build >= \(infrastructure.buildNumber) (app is \(self.appBuild))") + return Fail(error: ProviderManagerError.outdatedBuild(self.appBuild, infrastructure.buildNumber)) + .eraseToAnyPublisher() + } + + do { + try self.infrastructureRepository.saveInfrastructure( + infrastructure, + vpnProtocol: vpnProtocol, + lastUpdate: pub.lastModified ?? Date() + ) + } catch { + pp_log.error("Unable to persist \(providerName) infrastructure (\(vpnProtocol)): \(error)") + } + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + }.eraseToAnyPublisher() + } + + public func reset() { + persistence.truncate() + } + + // MARK: RateLimited + + private let indexActionName = "" + + public var lastActionDate: [String: Date] = [:] + + public var rateLimitMilliseconds: Int? +} + +private struct InfrastructureKey: Hashable { + let providerName: ProviderName + + let vpnProtocol: VPNProtocolType + + init(_ providerName: ProviderName, _ vpnProtocol: VPNProtocolType) { + self.providerName = providerName + self.vpnProtocol = vpnProtocol + } +} + +private struct LocationKey: Hashable { + let providerName: ProviderName + + let vpnProtocol: VPNProtocolType + + let categoryName: String + + let countryCode: String + + init( + _ providerName: ProviderName, + _ vpnProtocol: VPNProtocolType, + _ categoryName: String, + _ countryCode: String + ) { + self.providerName = providerName + self.vpnProtocol = vpnProtocol + self.categoryName = categoryName + self.countryCode = countryCode + } +} + +private extension ProviderLocation { + var key: LocationKey { + return .init(providerMetadata.name, vpnProtocol, categoryName, countryCode) + } +} + +private extension ProviderManager.FetchPriority { + func publisher( + remote: @escaping () -> AnyPublisher, + bundle: @escaping () -> AnyPublisher + ) -> AnyPublisher { + switch self { + case .bundle: + return bundle() + + case .remote: + return remote() + + case .remoteThenBundle: + return remote() + .catch { error -> AnyPublisher in + pp_log.warning("Unable to fetch remotely: \(error)") + pp_log.warning("Falling back to bundle") + return bundle() + }.eraseToAnyPublisher() + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/TrustPolicy.swift b/PassepartoutCore/Sources/PassepartoutProviders/Models/CredentialsPurpose.swift similarity index 85% rename from PassepartoutCore/Sources/PassepartoutCore/Model/TrustPolicy.swift rename to PassepartoutCore/Sources/PassepartoutProviders/Models/CredentialsPurpose.swift index 1cbd577e..3b5d37ab 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/TrustPolicy.swift +++ b/PassepartoutCore/Sources/PassepartoutProviders/Models/CredentialsPurpose.swift @@ -1,8 +1,8 @@ // -// TrustPolicy.swift +// CredentialsPurpose.swift // Passepartout // -// Created by Davide De Rosa on 9/2/18. +// Created by Davide De Rosa on 7/30/21. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -25,8 +25,8 @@ import Foundation -public enum TrustPolicy: String, Codable { - case ignore +public enum CredentialsPurpose { + case web - case disconnect + case specific } diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/PoolCategory.swift b/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderCategory.swift similarity index 72% rename from PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/PoolCategory.swift rename to PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderCategory.swift index 9c9faf6b..eaf18692 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/Profiles/PoolCategory.swift +++ b/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderCategory.swift @@ -1,8 +1,8 @@ // -// PoolCategory.swift +// ProviderCategory.swift // Passepartout // -// Created by Davide De Rosa on 4/11/19. +// Created by Davide De Rosa on 3/15/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,17 +24,14 @@ // import Foundation +import PassepartoutUtils -public struct PoolCategory: Codable { +public struct ProviderCategory { + public let providerMetadata: ProviderMetadata + + public let vpnProtocol: VPNProtocolType + public let name: String - public let groups: [PoolGroup] - - public let presets: [String]? - - public init(name: String, groups: [PoolGroup], presets: [String]?) { - self.name = name - self.groups = groups - self.presets = presets - } + public let locations: [ProviderLocation] } diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderLocation.swift b/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderLocation.swift new file mode 100644 index 00000000..cbaf724e --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderLocation.swift @@ -0,0 +1,39 @@ +// +// ProviderLocation.swift +// Passepartout +// +// Created by Davide De Rosa on 3/15/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutUtils + +public struct ProviderLocation { + public let providerMetadata: ProviderMetadata + + public let vpnProtocol: VPNProtocolType + + public let categoryName: String + + public let countryCode: String + + public let onlyServer: ProviderServer? +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderMetadata.swift b/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderMetadata.swift new file mode 100644 index 00000000..b67df478 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderMetadata.swift @@ -0,0 +1,29 @@ +// +// ProviderMetadata.swift +// Passepartout +// +// Created by Davide De Rosa on 3/15/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutServices + +public typealias ProviderMetadata = WSProvidersIndex.Metadata diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderName.swift b/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderName.swift new file mode 100644 index 00000000..33917b59 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderName.swift @@ -0,0 +1,29 @@ +// +// ProviderName.swift +// Passepartout +// +// Created by Davide De Rosa on 3/15/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutServices + +public typealias ProviderName = WSProviderName diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderServer.swift b/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderServer.swift new file mode 100644 index 00000000..73311d4a --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Models/ProviderServer.swift @@ -0,0 +1,86 @@ +// +// ProviderServer.swift +// Passepartout +// +// Created by Davide De Rosa on 3/15/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import GenericJSON +import CryptoKit +import PassepartoutUtils + +public struct ProviderServer: Identifiable { + public struct Preset { + public let id: String + + public let name: String + + public let comment: String + + public let vpnProtocol: VPNProtocolType + + public let vpnConfiguration: JSON + } + + public let providerMetadata: ProviderMetadata + + public let id: String + + public let apiId: String + + public let categoryName: String + + public let countryCode: String + + public let extraCountryCodes: [String]? + + public let serverIndex: Int? + + public let details: String? + + public let hostname: String? + + public let ipAddresses: [String] + + public let presetIds: [String] + + public internal(set) var presets: [Preset]? + + public func preset(withId presetId: String) -> Preset? { + return presets?.first { + $0.id == presetId + } + } +} + +extension ProviderServer { + public static func id(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, apiId: String) -> String? { + let idSource = "\(providerName):\(vpnProtocol.rawValue):\(apiId)" + guard let data = idSource.data(using: .utf8) else { + return nil + } + let sha = SHA256.hash(data: data) + return sha.map { + String(format: "%02X", $0) + }.joined() + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Models/VPNProtocolType.swift b/PassepartoutCore/Sources/PassepartoutProviders/Models/VPNProtocolType.swift new file mode 100644 index 00000000..c086d260 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Models/VPNProtocolType.swift @@ -0,0 +1,37 @@ +// +// VPNProtocolType.swift +// Passepartout +// +// Created by Davide De Rosa on 3/20/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutServices + +public typealias VPNProtocolType = WSVPNProtocol + +extension VPNProtocolType { + public static let allTypes: [VPNProtocolType] = [.openVPN, .wireGuard] +} + +public protocol VPNProtocolProviding { + var vpnProtocol: VPNProtocolType { get } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/PassepartoutProviders.swift b/PassepartoutCore/Sources/PassepartoutProviders/PassepartoutProviders.swift new file mode 100644 index 00000000..a30aa4b7 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/PassepartoutProviders.swift @@ -0,0 +1,37 @@ +// +// PassepartoutProviders.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutUtils + +extension PassepartoutDataModels { + public static let providers: NSManagedObjectModel = { + guard let model = NSManagedObjectModel.mergedModel(from: [.module]) else { + fatalError("Could not load PassepartoutProviders model") + } + return model + }() +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Providers.xcdatamodeld/Model.xcdatamodel/contents b/PassepartoutCore/Sources/PassepartoutProviders/Providers.xcdatamodeld/Model.xcdatamodel/contents new file mode 100644 index 00000000..f244a318 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Providers.xcdatamodeld/Model.xcdatamodel/contents @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Repositories/CategoryMapper.swift b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/CategoryMapper.swift new file mode 100644 index 00000000..40fa26d7 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/CategoryMapper.swift @@ -0,0 +1,80 @@ +// +// CategoryMapper.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutServices +import PassepartoutUtils +import CoreData + +struct CategoryMapper: DTOMapper, ModelMapper { + private let context: NSManagedObjectContext + + private let vpnProtocol: VPNProtocolType + + init(_ context: NSManagedObjectContext, _ vpnProtocol: VPNProtocolType) { + self.context = context + self.vpnProtocol = vpnProtocol + } + + func toDTO(_ ws: WSProviderCategory) -> CDInfrastructureCategory { + let category = CDInfrastructureCategory(context: context) + let locations = ws.locations.compactMap(LocationMapper(context).toDTO) + + category.name = ws.name + locations.forEach { + $0.category = category + $0.servers?.forEach { + ($0 as? CDInfrastructureServer)?.category = category + } + } + category.addToLocations(Set(locations) as NSSet) + + return category + } + + static func toModel(_ dto: CDInfrastructureCategory) -> ProviderCategory? { + guard let infrastructureDTO = dto.infrastructure, + let providerDTO = infrastructureDTO.provider, + let providerMetadata = ProviderMapper.toModel(providerDTO), + let vpnProtocolString = infrastructureDTO.vpnProtocol, + let vpnProtocol = VPNProtocolType(rawValue: vpnProtocolString), + let name = dto.name, + let locations = dto.locations else { + + Utils.assertCoreDataDecodingFailed(#file, #function, #line) + return nil + } + + let locationModels = (locations.allObjects as? [CDInfrastructureLocation])? + .compactMap(LocationMapper.toModel) ?? [] + + return ProviderCategory( + providerMetadata: providerMetadata, + vpnProtocol: vpnProtocol, + name: name, + locations: locationModels + ) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Repositories/DefaultSettingsMapper.swift b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/DefaultSettingsMapper.swift new file mode 100644 index 00000000..b74acad2 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/DefaultSettingsMapper.swift @@ -0,0 +1,44 @@ +// +// DefaultSettingsMapper.swift +// Passepartout +// +// Created by Davide De Rosa on 3/16/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutServices +import PassepartoutUtils + +struct DefaultSettingsMapper: DTOMapper { + private let context: NSManagedObjectContext + + init(_ context: NSManagedObjectContext) { + self.context = context + } + + func toDTO(_ ws: WSProviderInfrastructure.Defaults) -> CDInfrastructureDefaultSettings { + let dto = CDInfrastructureDefaultSettings(context: context) + dto.usernamePlaceholder = ws.usernamePlaceholder + dto.countryCode = ws.countryCode + return dto + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Repositories/InfrastructureMapper.swift b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/InfrastructureMapper.swift new file mode 100644 index 00000000..f55e7828 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/InfrastructureMapper.swift @@ -0,0 +1,111 @@ +// +// InfrastructureMapper.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutServices +import PassepartoutUtils + +struct InfrastructureMapper: DTOMapper { + private let context: NSManagedObjectContext + + private let providerName: ProviderName + + private let vpnProtocol: VPNProtocolType + + init(_ context: NSManagedObjectContext, _ providerName: ProviderName, _ vpnProtocol: VPNProtocolType) { + self.context = context + self.providerName = providerName + self.vpnProtocol = vpnProtocol + } + + func toDTO(_ ws: WSProviderInfrastructure) -> CDInfrastructure { + let infrastructure = CDInfrastructure(context: context) + infrastructure.vpnProtocol = vpnProtocol.rawValue + + let defaults = DefaultSettingsMapper(context).toDTO(ws.defaults) + infrastructure.defaults = defaults + defaults.infrastructure = infrastructure + + let presets = ws.presets.compactMap(PresetMapper(context).toDTO) + + let categories = ws.categories.compactMap(CategoryMapper(context, vpnProtocol).toDTO) + infrastructure.addToCategories(Set(categories) as NSSet) + + var categoryByName: [String: CDInfrastructureCategory] = [:] + categories.forEach { category in + category.infrastructure = infrastructure + categoryByName[category.name!] = category + } + + ws.categories.forEach { categoryWS in + var presetsDTO: [CDInfrastructurePreset]? + if let wsPresets = categoryWS.supportedPresetIds { + presetsDTO = presets.filter { + guard let presetId = $0.id else { + assertionFailure("Preset in category '\(categoryWS.name)' supported presets has no id") + return false + } + return wsPresets.contains(presetId) + } + } else { + presetsDTO = presets + } + guard let category = categoryByName[categoryWS.name] else { + assertionFailure("Cannot find CDInfrastructureCategory with name '\(categoryWS.name)' in map") + return + } + presetsDTO.map { + category.addToPresets(Set($0) as NSSet) + $0.forEach { presetDTO in + presetDTO.addToCategory(category) + } + } + } + + // do this at the very end, when all entities have their parent chain set + categories.forEach { + $0.servers?.forEach { + guard let server = $0 as? CDInfrastructureServer else { + return + } + guard let apiId = server.apiId else { + return + } + guard let uniqueId = ProviderServer.id( + withName: providerName, + vpnProtocol: vpnProtocol, + apiId: apiId + ) else { + Utils.assertCoreDataDecodingFailed(#file, #function, #line) + return + } + server.uniqueId = uniqueId + } + } + + return infrastructure + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Repositories/InfrastructureRepository.swift b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/InfrastructureRepository.swift new file mode 100644 index 00000000..d5f9eaed --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/InfrastructureRepository.swift @@ -0,0 +1,143 @@ +// +// InfrastructureRepository.swift +// Passepartout +// +// Created by Davide De Rosa on 3/16/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutServices +import PassepartoutUtils + +class InfrastructureRepository: Repository { + private let context: NSManagedObjectContext + + required init(_ context: NSManagedObjectContext) { + self.context = context + } + + func saveInfrastructure( + _ infrastructure: WSProviderInfrastructure, + vpnProtocol: VPNProtocolType, + lastUpdate: Date + ) throws { + do { + let provider = try providerDTO(forName: infrastructure.name) ?? { + let provider = CDProvider(context: context) + provider.name = infrastructure.name + provider.fullName = infrastructure.fullName + provider.lastUpdate = Date() + return provider + }() + + let request = CDInfrastructure.fetchRequest() + request.predicate = NSPredicate( + format: "provider.name == %@ AND vpnProtocol == %@", + infrastructure.name, + vpnProtocol.rawValue + ) + + let dto = InfrastructureMapper( + context, + infrastructure.name, + vpnProtocol + ).toDTO(infrastructure) + dto.provider = provider + dto.lastUpdate = lastUpdate + provider.addToInfrastructures(dto) + + try context.save() + } catch { + context.rollback() + throw error + } + } + + func defaultUsername(forProviderWithName name: ProviderName, vpnProtocol: VPNProtocolType) -> String? { + let request = CDInfrastructure.fetchRequest() + request.sortDescriptors = [ + .init(keyPath: \CDInfrastructure.lastUpdate, ascending: false) + ] + request.predicate = NSPredicate( + format: "provider.name == %@ AND vpnProtocol == %@", + name, + vpnProtocol.rawValue + ) + request.relationshipKeyPathsForPrefetching = [ + "defaults" + ] + do { + guard let infrastructureDTO = try context.fetch(request).first else { + Utils.logFetchNotFound(#file, #function, #line) + return nil + } + return infrastructureDTO.defaults?.usernamePlaceholder + } catch { + Utils.logFetchError(#file, #function, #line, error) + return nil + } + } + + func lastInfrastructureUpdate( + withName name: ProviderName, + vpnProtocol: VPNProtocolType + ) -> Date? { + let request = CDInfrastructure.fetchRequest() + request.sortDescriptors = [ + .init(keyPath: \CDInfrastructure.lastUpdate, ascending: false) + ] + request.predicate = NSPredicate( + format: "provider.name == %@ AND vpnProtocol == %@", + name, + vpnProtocol.rawValue + ) + request.relationshipKeyPathsForPrefetching = [ + "provider", + "provider.infrastructures" + ] + do { + let infrastructures = try context.fetch(request) + guard !infrastructures.isEmpty else { + Utils.logFetchNotFound(#file, #function, #line) + return nil + } + let recent = infrastructures.first! + return recent.lastUpdate + } catch { + context.rollback() + Utils.logFetchError(#file, #function, #line, error) + return nil + } + } + + private func providerDTO(forName name: ProviderName) throws -> CDProvider? { + let request = CDProvider.fetchRequest() + request.sortDescriptors = [ + .init(keyPath: \CDProvider.lastUpdate, ascending: false) + ] + request.predicate = NSPredicate( + format: "name == %@", + name + ) + return try context.fetch(request).first + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Repositories/LocationMapper.swift b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/LocationMapper.swift new file mode 100644 index 00000000..1fd95646 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/LocationMapper.swift @@ -0,0 +1,77 @@ +// +// LocationMapper.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutServices +import PassepartoutUtils + +struct LocationMapper: DTOMapper, ModelMapper { + private let context: NSManagedObjectContext + + init(_ context: NSManagedObjectContext) { + self.context = context + } + + func toDTO(_ ws: WSProviderLocation) -> CDInfrastructureLocation { + let location = CDInfrastructureLocation(context: context) + location.countryCode = ws.countryCode + + let servers = ws.servers.compactMap(ServerMapper(context).toDTO) + servers.forEach { + $0.location = location + } + location.addToServers(Set(servers) as NSSet) + + return location + } + + static func toModel(_ dto: CDInfrastructureLocation) -> ProviderLocation? { + guard let infrastructureDTO = dto.category?.infrastructure, + let providerDTO = infrastructureDTO.provider, + let providerMetadata = ProviderMapper.toModel(providerDTO), + let vpnProtocolString = infrastructureDTO.vpnProtocol, + let vpnProtocol = VPNProtocolType(rawValue: vpnProtocolString), + let categoryName = dto.category?.name, + let countryCode = dto.countryCode else { + + Utils.assertCoreDataDecodingFailed(#file, #function, #line) + return nil + } + + var server: ProviderServer? + if dto.servers?.count == 1, let serverDTO = dto.servers?.anyObject() as? CDInfrastructureServer { + server = ServerMapper.toModel(serverDTO) + } + + return ProviderLocation( + providerMetadata: providerMetadata, + vpnProtocol: vpnProtocol, + categoryName: categoryName, + countryCode: countryCode, + onlyServer: server + ) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Repositories/PresetMapper.swift b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/PresetMapper.swift new file mode 100644 index 00000000..93141849 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/PresetMapper.swift @@ -0,0 +1,99 @@ +// +// PresetMapper.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutServices +import PassepartoutUtils +import GenericJSON + +struct PresetMapper: DTOMapper, ModelMapper { + private let context: NSManagedObjectContext + + init(_ context: NSManagedObjectContext) { + self.context = context + } + + func toDTO(_ ws: WSProviderPreset) -> CDInfrastructurePreset { + let preset = CDInfrastructurePreset(context: context) + preset.id = ws.id + preset.name = ws.name + preset.comment = ws.comment + if let ovpn = ws.encodedOpenVPNConfiguration { + preset.vpnProtocol = VPNProtocolType.openVPN.rawValue + preset.vpnConfiguration = ovpn + } else if let wg = ws.encodedWireGuardConfiguration { + preset.vpnProtocol = VPNProtocolType.wireGuard.rawValue + preset.vpnConfiguration = wg + } + return preset + } + + static func toModel(_ dto: CDInfrastructurePreset) -> ProviderServer.Preset? { + guard let id = dto.id, + let name = dto.name, + let comment = dto.comment, + let vpnProtocolString = dto.vpnProtocol, + let vpnProtocol = VPNProtocolType(rawValue: vpnProtocolString), + let vpnConfiguration = dto.decodedConfiguration else { + + Utils.assertCoreDataDecodingFailed(#file, #function, #line) + return nil + } + + return ProviderServer.Preset( + id: id, + name: name, + comment: comment, + vpnProtocol: vpnProtocol, + vpnConfiguration: vpnConfiguration + ) + } +} + +private extension WSProviderPreset { + var encodedOpenVPNConfiguration: Data? { + return try? jsonOpenVPNConfiguration?.encoded() + } + + var encodedWireGuardConfiguration: Data? { + return try? jsonWireGuardConfiguration?.encoded() + } +} + +private extension CDInfrastructurePreset { + var decodedConfiguration: JSON? { + guard let configuration = vpnConfiguration else { + return nil + } + do { + let raw = try JSONSerialization.jsonObject(with: configuration) + return try JSON(raw) + } catch { + pp_log.error("Unable to decode vpnConfiguration: \(error)") + return nil + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ProviderMapper.swift b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ProviderMapper.swift new file mode 100644 index 00000000..de02e3c2 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ProviderMapper.swift @@ -0,0 +1,72 @@ +// +// ProviderMapper.swift +// Passepartout +// +// Created by Davide De Rosa on 3/15/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutServices +import PassepartoutUtils + +struct ProviderMapper: DTOMapper, ModelMapper { + private let context: NSManagedObjectContext + + init(_ context: NSManagedObjectContext) { + self.context = context + } + + func toDTO(_ ws: WSProvidersIndex.Metadata) -> CDProvider { + let provider = CDProvider(context: context) + provider.name = ws.name + provider.fullName = ws.fullName + provider.lastUpdate = Date() + + ws.supportedVPNProtocols.forEach { + let infra = CDInfrastructure(context: context) + infra.vpnProtocol = $0.rawValue + infra.lastUpdate = nil + infra.provider = provider + provider.addToInfrastructures(infra) + } + + return provider + } + + static func toModel(_ dto: CDProvider) -> ProviderMetadata? { + guard let name = dto.name, + let fullName = dto.fullName else { + + Utils.assertCoreDataDecodingFailed(#file, #function, #line) + return nil + } + + var protos: [VPNProtocolType] = [] + if let infraDTOs = dto.infrastructures?.allObjects as? [CDInfrastructure] { + protos = infraDTOs + .compactMap(\.vpnProtocol) + .compactMap(VPNProtocolType.init(rawValue:)) + } + + return ProviderMetadata(name: name, fullName: fullName, supportedVPNProtocols: protos) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ProviderRepository.swift b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ProviderRepository.swift new file mode 100644 index 00000000..77c5e8e6 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ProviderRepository.swift @@ -0,0 +1,137 @@ +// +// ProviderRepository.swift +// Passepartout +// +// Created by Davide De Rosa on 3/15/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutServices +import PassepartoutUtils + +class ProviderRepository: Repository { + private let context: NSManagedObjectContext + + required init(_ context: NSManagedObjectContext) { + self.context = context + } + + func allProviders() -> [ProviderMetadata] { + let request = CDProvider.fetchRequest() + request.sortDescriptors = [ + .init(keyPath: \CDProvider.name, ascending: true), + .init(keyPath: \CDProvider.lastUpdate, ascending: false) + ] + request.relationshipKeyPathsForPrefetching = [ + "infrastructures" + ] + do { + let providers = try context.fetch(request) + guard !providers.isEmpty else { + return [] + } + return providers.compactMap(ProviderMapper.toModel) + } catch { + Utils.logFetchError(#file, #function, #line, error) + return [] + } + } + + func provider(withName name: ProviderName) -> ProviderMetadata? { + let request = CDProvider.fetchRequest() + request.sortDescriptors = [ + .init(keyPath: \CDProvider.lastUpdate, ascending: false) + ] + request.predicate = NSPredicate(format: "name == %@", name) + request.relationshipKeyPathsForPrefetching = [ + "infrastructures" + ] + do { + let providers = try context.fetch(request) + guard !providers.isEmpty else { + Utils.logFetchNotFound(#file, #function, #line) + return nil + } + let recent = providers.first! + return ProviderMapper.toModel(recent) + } catch { + Utils.logFetchError(#file, #function, #line, error) + return nil + } + } + + func mergeIndex(_ index: WSProvidersIndex) throws { + let request = CDProvider.fetchRequest() + request.propertiesToFetch = [ + "name", + "fullName" + ] + do { + let providers = try context.fetch(request) + + let indexNames = index.metadata.map(\.name) + let existingNames = providers.compactMap(\.name) + pp_log.debug("Fetched providers: \(indexNames)") + pp_log.debug("Existing providers: \(existingNames)") + + let newNames = Set(indexNames).subtracting(existingNames) + pp_log.info("New providers: \(newNames)") + + // add new + index.metadata.filter { + newNames.contains($0.name) + }.forEach { + _ = ProviderMapper(context).toDTO($0) + pp_log.info("Creating new provider metadata: \($0)") + } + + // update existing + providers.forEach { dto in + guard let ws = index.metadata.first(where: { + $0.name == dto.name + }) else { + // delete if not in new index + context.delete(dto) + return + } + dto.fullName = ws.fullName + dto.lastUpdate = Date() + } + + try context.save() + } catch { + context.rollback() + throw error + } + } + + private func reassignInfrastructures(from oldProvider: CDProvider, to newProvider: CDProvider) { + oldProvider.infrastructures?.forEach { + guard let infra = $0 as? CDInfrastructure else { + return + } + pp_log.debug("Reassigning provider infrastructure: \(infra)") + oldProvider.removeFromInfrastructures(infra) + newProvider.addToInfrastructures(infra) + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ServerMapper.swift b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ServerMapper.swift new file mode 100644 index 00000000..5dbcd866 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ServerMapper.swift @@ -0,0 +1,202 @@ +// +// ServerMapper.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutServices +import PassepartoutUtils + +struct ServerMapper: DTOMapper, ModelMapper { + private let context: NSManagedObjectContext + + init(_ context: NSManagedObjectContext) { + self.context = context + } + + func toDTO(_ ws: WSProviderServer) -> CDInfrastructureServer { + let server = CDInfrastructureServer(context: context) + + server.apiId = ws.id + server.countryCode = ws.countryCode + server.extraCountryCodes = ws.encodedExtraCountryCodes + server.area = ws.area + if let serverIndex = ws.serverIndex { + server.serverIndex = Int16(serverIndex) + } else { + server.serverIndex = 0 + } + server.tags = ws.encodedTags + + server.hostname = ws.hostname + if let addrs = ws.numericAddresses { + server.ipAddresses = ws.encodedIPAddresses + + // strip hostname if found in IP addresses + if let hostname = server.hostname, let numericHostname = Utils.ipv4(fromString: hostname), addrs.contains(numericHostname) { + server.hostname = nil + } + } + + return server + } + + static func toModel(_ dto: CDInfrastructureServer) -> ProviderServer? { + guard let uniqueId = dto.uniqueId, + let apiId = dto.apiId, + let categoryDTO = dto.category, + let categoryName = categoryDTO.name, + let infrastructureDTO = categoryDTO.infrastructure, + let providerDTO = infrastructureDTO.provider, + let providerMetadata = ProviderMapper.toModel(providerDTO), + let countryCode = dto.countryCode else { + + Utils.assertCoreDataDecodingFailed(#file, #function, #line) + return nil + } + guard let presetDTOs = categoryDTO.presets?.allObjects as? [CDInfrastructurePreset], !presetDTOs.isEmpty else { + Utils.assertCoreDataDecodingFailed( + #file, #function, #line, + "Category '\(categoryName)' of server \(apiId) has no presets" + ) + return nil + } + + let supportedPresetIds = presetDTOs + .sorted() + .compactMap(\.id) + + return ProviderServer( + providerMetadata: providerMetadata, + id: uniqueId, + apiId: apiId, + categoryName: categoryName, + countryCode: countryCode, + extraCountryCodes: dto.decodedExtraCountryCodes, + serverIndex: dto.serverIndex != 0 ? Int(dto.serverIndex) : nil, + details: dto.details, + hostname: dto.hostname, + ipAddresses: dto.decodedIPAddresses, + presetIds: supportedPresetIds + ) + } + + static func toModelWithPresets(_ dto: CDInfrastructureServer) -> ProviderServer? { + guard var server = toModel(dto), + let categoryDTO = dto.category, + let categoryName = categoryDTO.name else { + + Utils.assertCoreDataDecodingFailed(#file, #function, #line) + return nil + } + guard let presetDTOs = dto.category?.presets?.allObjects as? [CDInfrastructurePreset], !presetDTOs.isEmpty else { + Utils.assertCoreDataDecodingFailed( + #file, #function, #line, + "Category '\(categoryName)' has no presets" + ) + return nil + } + + server.presets = presetDTOs + .sorted() + .compactMap(PresetMapper.toModel) + + return server + } +} + +private extension WSProviderServer { + var encodedExtraCountryCodes: String? { + return extraCountryCodes?.joined(separator: ",") + } + + var encodedTags: String? { + return tags?.joined(separator: ",") + } + + var encodedIPAddresses: String? { + guard let addrs = numericAddresses, !addrs.isEmpty else { + return nil + } + return numericAddresses? + .map(Utils.string(fromIPv4:)) + .joined(separator: ",") + } +} + +private extension CDInfrastructureServer { + var decodedExtraCountryCodes: [String]? { + return extraCountryCodes? + .components(separatedBy: ",") + } + + var decodedTags: [String]? { + return tags? + .components(separatedBy: ",") + } + + var decodedIPAddresses: [String] { + return ipAddresses? + .components(separatedBy: ",") ?? [] + } +} + +private extension CDInfrastructureServer { + var details: String? { + var comps: [String] = [] + if let extraCountryCodes = decodedExtraCountryCodes { + comps.append(contentsOf: extraCountryCodes.map { + $0.localizedAsCountryCode + }) + } + if let area = area { +// comps.append(area.uppercased()) + comps.append(area.capitalized) + } + if serverIndex != 0 { + comps.append("#\(serverIndex)") + } + guard !comps.isEmpty else { + return nil + } + var str = comps.joined(separator: " ") + if let tags = tags { + let suffix = tags.map { $0.uppercased() }.joined(separator: ",") + str = "\(str) (\(suffix))" + } + guard !str.isEmpty else { + return nil + } + return str + } +} + +extension CDInfrastructurePreset: Comparable { + public static func <(lhs: CDInfrastructurePreset, rhs: CDInfrastructurePreset) -> Bool { + guard let lname = lhs.name, let rname = rhs.name else { + fatalError("CDPreset has no name?") + } + return lname.lowercased() < rname.lowercased() + } +} diff --git a/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ServerRepository.swift b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ServerRepository.swift new file mode 100644 index 00000000..34a2a774 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutProviders/Repositories/ServerRepository.swift @@ -0,0 +1,190 @@ +// +// ServerRepository.swift +// Passepartout +// +// Created by Davide De Rosa on 3/15/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import PassepartoutServices +import PassepartoutUtils + +class ServerRepository: Repository { + private let context: NSManagedObjectContext + + required init(_ context: NSManagedObjectContext) { + self.context = context + } + + func categories(forProviderWithName name: ProviderName, vpnProtocol: VPNProtocolType) -> [ProviderCategory] { + let request = CDInfrastructureCategory.fetchRequest() + request.predicate = NSPredicate( + format: "infrastructure.provider.name == %@ AND infrastructure.vpnProtocol == %@", + name, + vpnProtocol.rawValue + ) + request.relationshipKeyPathsForPrefetching = [ + "infrastructure", + "infrastructure.provider", + "locations", + "locations.servers", + "presets" + ] + do { + let categoryDTOs = try context.fetch(request) + return categoryDTOs.compactMap(CategoryMapper.toModel) + } catch { + Utils.logFetchError(#file, #function, #line, error) + return [] + } + } + + func servers(forLocation location: ProviderLocation) -> [ProviderServer] { + let request = CDInfrastructureServer.fetchRequest() + request.predicate = NSPredicate( + format: "category.infrastructure.provider.name == %@ AND category.name == %@ AND category.infrastructure.vpnProtocol == %@ AND countryCode == %@", + location.providerMetadata.name, + location.categoryName, + location.vpnProtocol.rawValue, + location.countryCode + ) + request.relationshipKeyPathsForPrefetching = [ + "category", + "category.infrastructure", + "category.infrastructure.provider", + "category.presets" + ] + do { + let serverDTOs = try context.fetch(request) + + // just preset ids, no full ProviderServer.Preset + return serverDTOs.compactMap(ServerMapper.toModel) + } catch { + Utils.logFetchError(#file, #function, #line, error) + return [] + } + } + + func server(forProviderWithName providerName: ProviderName, vpnProtocol: VPNProtocolType, serverId: String) -> ProviderServer? { + let request = CDInfrastructureServer.fetchRequest() + request.predicate = NSPredicate( + format: "serverId == %@ AND category.infrastructure.provider.name == %@ AND category.infrastructure.vpnProtocol == %@", + serverId, + providerName, + vpnProtocol.rawValue + ) + request.relationshipKeyPathsForPrefetching = [ + "category", + "category.infrastructure", + "category.infrastructure.provider", + "category.presets" + ] + do { + guard let serverDTO = try context.fetch(request).first else { + Utils.logFetchNotFound(#file, #function, #line) + return nil + } + return ServerMapper.toModelWithPresets(serverDTO) + } catch { + Utils.logFetchError(#file, #function, #line, error) + return nil + } + } + + func anyServer(forProviderWithName providerName: ProviderName, vpnProtocol: VPNProtocolType, countryCode: String) -> ProviderServer? { + let request = CDInfrastructureServer.fetchRequest() + request.predicate = NSPredicate( + format: "countryCode == %@ AND category.infrastructure.provider.name == %@ AND category.infrastructure.vpnProtocol == %@", + countryCode, + providerName, + vpnProtocol.rawValue + ) + request.relationshipKeyPathsForPrefetching = [ + "category", + "category.infrastructure", + "category.infrastructure.provider", + "category.presets" + ] + do { + try Utils.randomizeFetchResults(request, in: context) + guard let serverDTO = try context.fetch(request).first else { + Utils.logFetchNotFound(#file, #function, #line) + return nil + } + return ServerMapper.toModelWithPresets(serverDTO) + } catch { + Utils.logFetchError(#file, #function, #line, error) + return nil + } + } + + func anyDefaultServer(forProviderWithName providerName: ProviderName, vpnProtocol: VPNProtocolType) -> ProviderServer? { + let request = CDInfrastructureServer.fetchRequest() + let format = "countryCode == category.infrastructure.defaults.countryCode AND category.infrastructure.provider.name == %@ AND category.infrastructure.vpnProtocol == %@" + request.predicate = NSPredicate( + format: format, + providerName, + vpnProtocol.rawValue + ) + request.relationshipKeyPathsForPrefetching = [ + "category", + "category.infrastructure", + "category.infrastructure.provider", + "category.infrastructure.defaults", + "category.presets" + ] + do { + try Utils.randomizeFetchResults(request, in: context) + guard let serverDTO = try context.fetch(request).first else { + Utils.logFetchNotFound(#file, #function, #line) + return nil + } + return ServerMapper.toModelWithPresets(serverDTO) + } catch { + Utils.logFetchError(#file, #function, #line, error) + return nil + } + } + + func server(withId id: String) -> ProviderServer? { + let request = CDInfrastructureServer.fetchRequest() + request.predicate = NSPredicate( + format: "uniqueId == %@", + id + ) + request.relationshipKeyPathsForPrefetching = [ + "category", + "category.infrastructure", + "category.presets" + ] + do { + guard let serverDTO = try context.fetch(request).first else { + Utils.logFetchNotFound(#file, #function, #line) + return nil + } + return ServerMapper.toModelWithPresets(serverDTO) + } catch { + Utils.logFetchError(#file, #function, #line, error) + return nil + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutServices/API b/PassepartoutCore/Sources/PassepartoutServices/API new file mode 160000 index 00000000..ba2b959d --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutServices/API @@ -0,0 +1 @@ +Subproject commit ba2b959d493b00359f1f3e2040e35ce6a31e2c74 diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/Credentials.swift b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderCategory.swift similarity index 63% rename from PassepartoutCore/Sources/PassepartoutCore/Model/Credentials.swift rename to PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderCategory.swift index 0ab4fe3c..2a30d588 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/Credentials.swift +++ b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderCategory.swift @@ -1,8 +1,8 @@ // -// Credentials.swift +// WSProviderCategory.swift // Passepartout // -// Created by Davide De Rosa on 6/7/18. +// Created by Davide De Rosa on 4/11/19. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,22 +24,24 @@ // import Foundation -import TunnelKitOpenVPN -public typealias Credentials = OpenVPN.Credentials - -public extension Credentials { -// var isEmpty: Bool { -// return username.isEmpty || password.isEmpty -// } - - var isValid: Bool { - return !username.isEmpty +public struct WSProviderCategory: Codable { + enum CodingKeys: String, CodingKey { + case name + + case locations + + case supportedPresetIds = "presets" } - func trimmed() -> Credentials { - let trimmedUsername = username.stripped - let trimmedPassword = password.stripped - return Credentials(trimmedUsername, trimmedPassword) + public let name: String + + public let locations: [WSProviderLocation] + + public var supportedPresetIds: [String]? + + public init(name: String, locations: [WSProviderLocation]) { + self.name = name + self.locations = locations } } diff --git a/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderInfrastructure.swift b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderInfrastructure.swift new file mode 100644 index 00000000..c7dcd64d --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderInfrastructure.swift @@ -0,0 +1,62 @@ +// +// WSProviderInfrastructure.swift +// Passepartout +// +// Created by Davide De Rosa on 6/11/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public struct WSProviderInfrastructure: Codable { + public struct Defaults: Codable { + enum CodingKeys: String, CodingKey { + case usernamePlaceholder = "username" + + case countryCode = "country" + } + + public let usernamePlaceholder: String? + + public let countryCode: String + } + + public let build: [String: Int] + + public var buildNumber: Int { + var num: Int? + #if os(iOS) + num = build["ios"] + #else + num = build["macos"] + #endif + return num ?? 0 + } + + public let name: WSProviderName + + public let fullName: String + + public let categories: [WSProviderCategory] + + public let presets: [WSProviderPreset] + + public let defaults: Defaults +} diff --git a/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderLocation.swift b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderLocation.swift new file mode 100644 index 00000000..02896210 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderLocation.swift @@ -0,0 +1,43 @@ +// +// WSProviderLocation.swift +// Passepartout +// +// Created by Davide De Rosa on 4/6/19. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public struct WSProviderLocation: Codable { + enum CodingKeys: String, CodingKey { + case countryCode = "country" + + case servers + } + + public let countryCode: String + + public let servers: [WSProviderServer] + + public init(countryCode: String, servers: [WSProviderServer]) { + self.countryCode = countryCode + self.servers = servers + } +} diff --git a/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderName.swift b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderName.swift new file mode 100644 index 00000000..f7eb913c --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderName.swift @@ -0,0 +1,28 @@ +// +// WSProviderName.swift +// Passepartout +// +// Created by Davide De Rosa on 11/24/19. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public typealias WSProviderName = String diff --git a/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderPreset.swift b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderPreset.swift new file mode 100644 index 00000000..cc8294d1 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderPreset.swift @@ -0,0 +1,61 @@ +// +// WSProviderPreset.swift +// Passepartout +// +// Created by Davide De Rosa on 8/30/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import GenericJSON + +public struct WSProviderPreset: Codable { + enum CodingKeys: String, CodingKey { + case id + + case name + + case comment + + case external + + case jsonOpenVPNConfiguration = "ovpn" + + case jsonWireGuardConfiguration = "wg" + } + + public let id: String + + public let name: String + + public let comment: String + + public var external: [String: String]? + + public var jsonOpenVPNConfiguration: JSON? + + public var jsonWireGuardConfiguration: JSON? + + public init(id: String, name: String, comment: String) { + self.id = id + self.name = name + self.comment = comment + } +} diff --git a/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderServer.swift b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderServer.swift new file mode 100644 index 00000000..e6b9c444 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProviderServer.swift @@ -0,0 +1,69 @@ +// +// WSProviderServer.swift +// Passepartout +// +// Created by Davide De Rosa on 6/11/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public struct WSProviderServer: Codable { + enum CodingKeys: String, CodingKey { + case id + + case countryCode = "country" + + case extraCountryCodes = "extra_countries" + + case area + + case serverIndex = "num" + + case tags + + case hostname + + case numericAddresses = "addrs" + } + + public let id: String + + public let countryCode: String + + public var extraCountryCodes: [String]? + + public var area: String? + + public var serverIndex: Int? + + public var tags: [String]? + +// public var geo: (Double, Double)? + + public var hostname: String? + + public var numericAddresses: Set? + + public init(id: String, countryCode: String) { + self.id = id + self.countryCode = countryCode + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure+Metadata.swift b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProvidersIndex.swift similarity index 54% rename from PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure+Metadata.swift rename to PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProvidersIndex.swift index 6668554b..5921026f 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Services/Infrastructure+Metadata.swift +++ b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSProvidersIndex.swift @@ -1,5 +1,5 @@ // -// Infrastructure+Metadata.swift +// WSProvidersIndex.swift // Passepartout // // Created by Davide De Rosa on 11/24/19. @@ -24,35 +24,39 @@ // import Foundation -import SwiftyBeaver -import PassepartoutConstants -private let log = SwiftyBeaver.self +public struct WSProvidersIndex: Codable { + public struct Metadata: Codable, CustomStringConvertible { + enum CodingKeys: String, CodingKey { + case name + + case fullName + + case supportedVPNProtocols = "vpn" + } + + public let name: WSProviderName + + public let fullName: String + + public let supportedVPNProtocols: [WSVPNProtocol] + + public init(name: WSProviderName, fullName: String, supportedVPNProtocols: [WSVPNProtocol]) { + self.name = name + self.fullName = fullName + self.supportedVPNProtocols = supportedVPNProtocols + } -extension Infrastructure { - public struct Metadata: Codable, Hashable, Comparable, CustomStringConvertible { - public let name: InfrastructureName - - public let inApp: String? - // MARK: CustomStringConvertible - public let description: String - - // MARK: Hashable - - public func hash(into hasher: inout Hasher) { - name.hash(into: &hasher) - } - - public static func ==(lhs: Infrastructure.Metadata, rhs: Infrastructure.Metadata) -> Bool { - return lhs.name == rhs.name - } - - // MARK: Comparable - - public static func <(lhs: Infrastructure.Metadata, rhs: Infrastructure.Metadata) -> Bool { - return lhs.name < rhs.name + public var description: String { + return fullName } } + + public let metadata: [Metadata] + + public init(metadata: [Metadata]) { + self.metadata = metadata + } } diff --git a/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSVPNProtocol.swift b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSVPNProtocol.swift new file mode 100644 index 00000000..06e9e2ad --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutServices/DataModels/WSVPNProtocol.swift @@ -0,0 +1,44 @@ +// +// WSVPNProtocol.swift +// Passepartout +// +// Created by Davide De Rosa on 3/20/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public enum WSVPNProtocol: String, Codable { + case openVPN = "ovpn" + + case wireGuard = "wg" +} + +extension WSVPNProtocol: CustomStringConvertible { + public var description: String { + switch self { + case .openVPN: + return "OpenVPN" + + case .wireGuard: + return "WireGuard" + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutServices/DefaultWebServices.swift b/PassepartoutCore/Sources/PassepartoutServices/DefaultWebServices.swift new file mode 100644 index 00000000..9db94b45 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutServices/DefaultWebServices.swift @@ -0,0 +1,106 @@ +// +// DefaultWebServices.swift +// Passepartout +// +// Created by Davide De Rosa on 9/14/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine +import PassepartoutUtils + +public class DefaultWebServices: WebServices { + private enum Group: String { + case providers + } + + private enum Endpoint: GenericWebEndpoint { + case providersIndex + + case providerNetwork(WSProviderName, WSVPNProtocol) + + private var pathName: String { + switch self { + case .providersIndex: + return "\(Group.providers.rawValue)/index" + + case .providerNetwork(let providerName, let vpnProtocol): + return "\(Group.providers.rawValue)/\(providerName)/\(vpnProtocol.filename)" + } + } + + private var fileType: String { + return "json" + } + + // MARK: GenericWebEndpoint + + var path: String { + return "\(pathName).\(fileType)" + } + } + + private let ws: GenericWebServices + + public init(_ version: String, _ root: URL, timeout: TimeInterval?, queue: DispatchQueue = .main) { + ws = GenericWebServices(version, root, timeout: timeout) + } + + public func providersIndex() -> AnyPublisher { + let request = ws.get(Endpoint.providersIndex) + return ws.parse(WSProvidersIndex.self, request: request) + .tryMap { + guard let value = $0.value else { + throw WebError.emptyResponse + } + return value + }.eraseToAnyPublisher() + } + + public func providerNetwork(with name: WSProviderName, vpnProtocol: WSVPNProtocol, ifModifiedSince lastModified: Date?) -> AnyPublisher, Error> { + var request = ws.get(Endpoint.providerNetwork(name, vpnProtocol)) + if let lastModified = lastModified { + request.addValue(GenericWebParser.lastModifiedString(date: lastModified), forHTTPHeaderField: "If-Modified-Since") + } + return ws.parse(WSProviderInfrastructure.self, request: request) + } +} + +extension DefaultWebServices { + public static func bundledServices(withVersion version: String) -> DefaultWebServices { + guard let apiURL = Bundle.module.url(forResource: "API", withExtension: nil) else { + fatalError("Could not find API in bundle") + } + return DefaultWebServices(version, apiURL, timeout: nil) + } +} + +private extension WSVPNProtocol { + var filename: String { + switch self { + case .openVPN: + return "ovpn" + + case .wireGuard: + return "wg" + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutConstants/InfrastructureName.swift b/PassepartoutCore/Sources/PassepartoutServices/Extensions/WSProviderName+Extensions.swift similarity index 92% rename from PassepartoutCore/Sources/PassepartoutConstants/InfrastructureName.swift rename to PassepartoutCore/Sources/PassepartoutServices/Extensions/WSProviderName+Extensions.swift index 93ed10ab..ebf4c08b 100644 --- a/PassepartoutCore/Sources/PassepartoutConstants/InfrastructureName.swift +++ b/PassepartoutCore/Sources/PassepartoutServices/Extensions/WSProviderName+Extensions.swift @@ -1,5 +1,5 @@ // -// InfrastructureName.swift +// WSProviderName+Extensions.swift // Passepartout // // Created by Davide De Rosa on 11/24/19. @@ -25,9 +25,7 @@ import Foundation -public typealias InfrastructureName = String - -extension InfrastructureName { +extension WSProviderName { public static let hideme = "hideme" public static let mullvad = "mullvad" diff --git a/PassepartoutCore/Sources/PassepartoutServices/WebServices.swift b/PassepartoutCore/Sources/PassepartoutServices/WebServices.swift new file mode 100644 index 00000000..6569c4c3 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutServices/WebServices.swift @@ -0,0 +1,63 @@ +// +// WebServices.swift +// Passepartout +// +// Created by Davide De Rosa on 9/14/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine +import PassepartoutUtils + +public enum WebError: GenericWebServicesError, LocalizedError { + case http(Int) + + case emptyResponse + + case unknown + + public static func httpStatus(_ status: Int) -> WebError { + return .http(status) + } + + public var errorDescription: String? { + switch self { + case .http(let status): + return "HTTP \(status)" + + case .emptyResponse: + return "Empty response" + + default: + return nil + } + } +} + +public protocol WebServices { + func providersIndex() -> AnyPublisher + + func providerNetwork( + with name: WSProviderName, + vpnProtocol: WSVPNProtocol, + ifModifiedSince lastModified: Date? + ) -> AnyPublisher, Error> +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Exports.swift b/PassepartoutCore/Sources/PassepartoutUtils/Exports.swift new file mode 100644 index 00000000..0e5be140 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Exports.swift @@ -0,0 +1,3 @@ +import SwiftyBeaver + +public let pp_log = SwiftyBeaver.self diff --git a/PassepartoutCore/Sources/PassepartoutUtils/PassepartoutDataModels.swift b/PassepartoutCore/Sources/PassepartoutUtils/PassepartoutDataModels.swift new file mode 100644 index 00000000..b2d1f19a --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/PassepartoutDataModels.swift @@ -0,0 +1,29 @@ +// +// PassepartoutDataModels.swift +// Passepartout +// +// Created by Davide De Rosa on 3/18/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public enum PassepartoutDataModels { +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/PassepartoutError.swift b/PassepartoutCore/Sources/PassepartoutUtils/PassepartoutError.swift new file mode 100644 index 00000000..3cacdcab --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/PassepartoutError.swift @@ -0,0 +1,38 @@ +// +// PassepartoutError.swift +// Passepartout +// +// Created by Davide De Rosa on 6/12/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public struct PassepartoutError: Error, Equatable { + private let string: String + + public init(_ string: String) { + self.string = string + } + + public static func ==(lhs: Self, rhs: Self) -> Bool { + lhs.string == rhs.string + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/FetchedValueHolder.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/FetchedValueHolder.swift new file mode 100644 index 00000000..40299274 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/FetchedValueHolder.swift @@ -0,0 +1,85 @@ +// +// FetchedValueHolder.swift +// Passepartout +// +// Created by Davide De Rosa on 4/8/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import Combine + +public class FetchedValueHolder: NSObject, ValueHolder, NSFetchedResultsControllerDelegate { + @Published public var value: V + + private let controller: NSFetchedResultsController + + private let mapping: ([NSFetchRequestResult]) -> V? + + public convenience init( + context: NSManagedObjectContext, + request: NSFetchRequest, + mapping: @escaping ([NSFetchRequestResult]) -> V?, + initial: V + ) { + let controller = NSFetchedResultsController( + fetchRequest: request, + managedObjectContext: context, + sectionNameKeyPath: nil, + cacheName: nil + ) + self.init(controller: controller, mapping: mapping, initial: initial) + } + + public init( + controller: NSFetchedResultsController, + mapping: @escaping ([NSFetchRequestResult]) -> V?, + initial: V + ) { + self.controller = controller + self.mapping = mapping + value = initial + super.init() + + controller.delegate = self + do { + try controller.performFetch() + } catch { + pp_log.error("Unable to perform initial fetch: \(error)") + } + mapResults() + } + + public func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + mapResults() + } + + private func mapResults() { + guard let results = controller.fetchedObjects else { + return + } + pp_log.verbose("Results: \(results)") + guard let newValue = mapping(results) else { + return + } + value = newValue + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebEndpoint.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebEndpoint.swift new file mode 100644 index 00000000..52d0962f --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebEndpoint.swift @@ -0,0 +1,30 @@ +// +// GenericWebEndpoint.swift +// Passepartout +// +// Created by Davide De Rosa on 11/20/19. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public protocol GenericWebEndpoint { + var path: String { get } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebParser.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebParser.swift new file mode 100644 index 00000000..7636d0d7 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebParser.swift @@ -0,0 +1,59 @@ +// +// GenericWebParser.swift +// Passepartout +// +// Created by Davide De Rosa on 11/20/19. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public class GenericWebParser { + private static let lmFormatter: DateFormatter = { + let fmt = DateFormatter() + fmt.locale = Locale(identifier: "en") + fmt.timeZone = TimeZone(abbreviation: "GMT") + fmt.dateFormat = "EEE, dd LLL yyyy HH:mm:ss zzz" + return fmt + }() + + public static func lastModifiedDate(string: String) -> Date? { + return lmFormatter.date(from: string) + } + + public static func lastModifiedString(date: Date) -> String { + return lmFormatter.string(from: date) + } + + public static func lastModifiedString(ofFileURL url: URL) -> String? { + guard url.isFileURL else { + return nil + } + do { + let attrs = try FileManager.default.attributesOfItem(atPath: url.path) + guard let date = attrs[.modificationDate] as? Date else { + return nil + } + return lastModifiedString(date: date) + } catch { + return nil + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/Model/EndpointDataSource.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebResponse.swift similarity index 61% rename from PassepartoutCore/Sources/PassepartoutCore/Model/EndpointDataSource.swift rename to PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebResponse.swift index 2c17991d..0030b963 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/Model/EndpointDataSource.swift +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebResponse.swift @@ -1,8 +1,8 @@ // -// EndpointDataSource.swift +// GenericWebResponse.swift // Passepartout // -// Created by Davide De Rosa on 9/5/18. +// Created by Davide De Rosa on 11/20/19. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,25 +24,22 @@ // import Foundation -import TunnelKit -import TunnelKitCore -public protocol EndpointDataSource { - var mainAddress: String? { get } +public struct GenericWebResponse { + public let value: T? - var addresses: [String] { get } + public let lastModifiedString: String? - var protocols: [EndpointProtocol] { get } + public var lastModified: Date? { + guard let string = lastModifiedString else { + return nil + } + return GenericWebParser.lastModifiedDate(string: string) + } - var canCustomizeEndpoint: Bool { get } - - var customAddress: String? { get set } - - var customProtocol: EndpointProtocol? { get set } -} + public let isCached: Bool -public extension EndpointDataSource { - var usesCustomEndpoint: Bool { - return (customAddress != nil) || (customProtocol != nil) + public static func empty() -> GenericWebResponse { + return GenericWebResponse(value: nil, lastModifiedString: nil, isCached: false) } } diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebServices.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebServices.swift new file mode 100644 index 00000000..16becdd8 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/GenericWebServices.swift @@ -0,0 +1,122 @@ +// +// GenericWebServices.swift +// Passepartout +// +// Created by Davide De Rosa on 11/20/19. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine + +public protocol GenericWebServicesError: Error { + static func httpStatus(_ status: Int) -> Self + + static var unknown: Self { get } +} + +public class GenericWebServices { + private let version: String? + + private let root: URL + + private let timeout: TimeInterval? + + public init(_ version: String?, _ root: URL, timeout: TimeInterval?) { + self.version = version + self.root = root + self.timeout = timeout + } + + public func get(_ endpoint: GenericWebEndpoint) -> URLRequest { + var request = URLRequest(url: url(forEndpoint: endpoint), cachePolicy: .reloadIgnoringCacheData) + if let timeout = timeout { + request.timeoutInterval = timeout + } + return request + } + + public func parse(_ type: T.Type, request: URLRequest) -> AnyPublisher, Error> { + pp_log.debug("GET \(request.url!)") + pp_log.debug("Request headers: \(request.allHTTPHeaderFields?.description ?? "none")") + + let session = URLSession(configuration: .ephemeral) + return session.dataTaskPublisher(for: request) + .handleEvents(receiveCompletion: { result in + switch result { + case .failure(let error): + pp_log.error("Error (response): \(error.localizedDescription)") + + default: + break + } + }).tryMap { (output: (Data, URLResponse)) in + let data = output.0 + let response = output.1 + + let value: T + var lastModifiedString: String? + + if let httpResponse = response as? HTTPURLResponse { + let statusCode = httpResponse.statusCode + pp_log.debug("Response status: \(statusCode)") + if let responseHeaders = httpResponse.allHeaderFields as? [String: String] { + pp_log.debug("Response headers: \(responseHeaders)") + } + + // 304: cache hit + if statusCode == 304 { + pp_log.debug("Response is cached") + return GenericWebResponse(value: nil, lastModifiedString: nil, isCached: true) + } + + // 200: cache miss + guard statusCode == 200 else { + pp_log.error("Error (HTTP): \(statusCode)") + throw ErrorType.httpStatus(statusCode) + } + + lastModifiedString = httpResponse.allHeaderFields["Last-Modified"] as? String + } else { + lastModifiedString = GenericWebParser.lastModifiedString(ofFileURL: request.url!) + } + + if let lastModifiedString = lastModifiedString { + pp_log.debug("Last modified: \(lastModifiedString)") + } + + do { + value = try JSONDecoder().decode(type, from: data) + } catch { + pp_log.error("Error (parsing): \(error)") + throw error + } + + return GenericWebResponse(value: value, lastModifiedString: lastModifiedString, isCached: false) + }.eraseToAnyPublisher() + } + + private func url(forEndpoint endpoint: GenericWebEndpoint) -> URL { + guard let version = version else { + return root.appendingPathComponent(endpoint.path) + } + return root.appendingPathComponent("\(version)/\(endpoint.path)") + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/KeyedCache.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/KeyedCache.swift new file mode 100644 index 00000000..8f8e5743 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/KeyedCache.swift @@ -0,0 +1,79 @@ +// +// KeyedCache.swift +// Passepartout +// +// Created by Davide De Rosa on 3/17/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public class KeyedCache { + private let query: String + + private var store: [K: V] = [:] + + public var isEmpty: Bool { + return store.isEmpty + } + + public var storeValues: [V] { + return Array(store.values) + } + + public init(_ query: String) { + self.query = query + } + + public func set(_ store: [K: V]) { + self.store = store + } + + public func put(_ key: K, value: V) { + store[key] = value + } + + public func put(_ key: K, valueBlock: (K) -> V?) -> V? { + if let cachedValue = store[key] { + return cachedValue + } + guard let value = valueBlock(key) else { + return nil + } + store[key] = value + pp_log.debug("Cache MISS [\(query)]") + return value + } + + public func forget(where condition: (K) -> Bool) { + let removedKeys = store.keys.filter(condition) + removedKeys.forEach { + store.removeValue(forKey: $0) + } + } + + public func forget(_ key: K) { + store.removeValue(forKey: key) + } + + public func clear() { + store.removeAll() + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/Mapper.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/Mapper.swift new file mode 100644 index 00000000..004f1347 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/Mapper.swift @@ -0,0 +1,42 @@ +// +// Mapper.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public protocol DTOMapper { + associatedtype WS + + associatedtype DTO + + func toDTO(_ ws: WS) throws -> DTO +} + +public protocol ModelMapper { + associatedtype DTO + + associatedtype Model + + static func toModel(_ dto: DTO) throws -> Model +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/Persistence.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/Persistence.swift new file mode 100644 index 00000000..24f19d05 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/Persistence.swift @@ -0,0 +1,101 @@ +// +// Persistence.swift +// Passepartout +// +// Created by Davide De Rosa on 3/14/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData +import Combine + +public class Persistence { + private let container: NSPersistentContainer + + public var context: NSManagedObjectContext { + container.viewContext + } + + public var coordinator: NSPersistentStoreCoordinator { + container.persistentStoreCoordinator + } + + public convenience init(withLocalName containerName: String, model: NSManagedObjectModel, author: String?) { + let container = NSPersistentContainer(name: containerName, managedObjectModel: model) + self.init(withContainer: container, author: author) + } + + public convenience init(withCloudKitName containerName: String, model: NSManagedObjectModel, author: String?) { + let container = NSPersistentCloudKitContainer(name: containerName, managedObjectModel: model) + self.init(withContainer: container, author: author) + } + + private init(withContainer container: NSPersistentContainer, author: String?) { + self.container = container + + guard let desc = container.persistentStoreDescriptions.first else { + fatalError("Could not read persistent store description") + } + pp_log.debug("Container description: \(desc)") + + // report remote notifications (do this BEFORE loadPersistentStores) + // + // https://stackoverflow.com/a/69507329/784615 +// desc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey) +// desc.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) + + container.loadPersistentStores { + if let error = $1 { + fatalError("Could not load persistent store: \(error)") + } + } + container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump + container.viewContext.automaticallyMergesChangesFromParent = true + + if let author = author { + pp_log.debug("Setting transaction author: \(author)") + container.viewContext.transactionAuthor = author + } + } + +// public func remoteChangesPublisher() -> AnyPublisher { +// NotificationCenter.default.publisher( +// for: .NSPersistentStoreRemoteChange, +// object: coordinator +// ).flatMap { _ in +// Just(()) +// }.eraseToAnyPublisher() +// } + + public func truncate() { + let coordinator = container.persistentStoreCoordinator + container.persistentStoreDescriptions.forEach { + do { + try $0.url.map { + try coordinator.destroyPersistentStore(at: $0, ofType: NSSQLiteStoreType) + try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: $0, options: nil) + } + } catch { + pp_log.warning("Could not truncate persistent store: \(error)") + } + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/RateLimited.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/RateLimited.swift new file mode 100644 index 00000000..aedcfc3d --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/RateLimited.swift @@ -0,0 +1,57 @@ +// +// RateLimited.swift +// Passepartout +// +// Created by Davide De Rosa on 4/6/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +@MainActor +public protocol RateLimited: AnyObject { + associatedtype ActionID: Hashable + + var lastActionDate: [ActionID: Date] { get set } + + var rateLimitMilliseconds: Int? { get } +} + +extension RateLimited { + public func saveLastAction(_ id: ActionID) { + lastActionDate[id] = Date() + } + + public func isRateLimited(_ id: ActionID) -> Bool { + guard let lastActionDate = lastActionDate[id] else { + return false + } + pp_log.debug("Last action date: \(lastActionDate)") + if let rateLimitMilliseconds = rateLimitMilliseconds { + let elapsedNanoseconds = UInt64(-lastActionDate.timeIntervalSinceNow) * NSEC_PER_SEC + let rateLimitNanoseconds = UInt64(rateLimitMilliseconds) * NSEC_PER_MSEC + guard elapsedNanoseconds >= rateLimitNanoseconds else { + pp_log.warning("Rate limited, only \(elapsedNanoseconds) nsec elapsed (< \(rateLimitNanoseconds))") + return true + } + } + return false + } +} diff --git a/PassepartoutCore/Sources/PassepartoutCore/ApplicationError.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/Repository.swift similarity index 78% rename from PassepartoutCore/Sources/PassepartoutCore/ApplicationError.swift rename to PassepartoutCore/Sources/PassepartoutUtils/Reusable/Repository.swift index 6bfa6f8d..f5859932 100644 --- a/PassepartoutCore/Sources/PassepartoutCore/ApplicationError.swift +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/Repository.swift @@ -1,8 +1,8 @@ // -// ApplicationError.swift +// Repository.swift // Passepartout // -// Created by Davide De Rosa on 6/12/18. +// Created by Davide De Rosa on 3/15/22. // Copyright (c) 2022 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -25,14 +25,8 @@ import Foundation -public enum ApplicationError: String, Error { - case missingProfile - - case missingCredentials - - case migration - - case inactiveProfile +public protocol Repository { + associatedtype Context - case externalResources + init(_ context: Context) } diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/SSIDReader.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/SSIDReader.swift new file mode 100644 index 00000000..4e45d926 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/SSIDReader.swift @@ -0,0 +1,68 @@ +// +// SSIDReader.swift +// Passepartout +// +// Created by Davide De Rosa on 2/24/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreLocation +import Combine + +public class SSIDReader: NSObject, ObservableObject, CLLocationManagerDelegate { + private let manager = CLLocationManager() + + private let publisher = PassthroughSubject() + + private var cancellables: Set = [] + + public func requestCurrentSSID(onSSID: @escaping (String) -> Void) { + publisher + .sink(receiveValue: onSSID) + .store(in: &cancellables) + + switch manager.authorizationStatus { + case .authorizedAlways, .authorizedWhenInUse, .denied: + notifyCurrentSSID() + return + + default: + manager.delegate = self + manager.requestWhenInUseAuthorization() + } + } + + private func notifyCurrentSSID() { + let currentSSID = Utils.currentWifiNetworkName() ?? "" + publisher.send(currentSSID) + cancellables.removeAll() + } + + public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { + switch manager.authorizationStatus { + case .authorizedWhenInUse, .authorizedAlways, .denied: + notifyCurrentSSID() + + default: + cancellables.removeAll() + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/StrippableContent.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/StrippableContent.swift new file mode 100644 index 00000000..1ac0ad4e --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/StrippableContent.swift @@ -0,0 +1,32 @@ +// +// StrippableContent.swift +// Passepartout +// +// Created by Davide De Rosa on 4/6/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public protocol StrippableContent { + associatedtype T + + var stripped: T { get } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Reusable/ValueHolder.swift b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/ValueHolder.swift new file mode 100644 index 00000000..fb4aa8bc --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Reusable/ValueHolder.swift @@ -0,0 +1,32 @@ +// +// ValueHolder.swift +// Passepartout +// +// Created by Davide De Rosa on 4/8/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public protocol ValueHolder { + associatedtype T + + var value: T { get } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Async.swift b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Async.swift new file mode 100644 index 00000000..e9fa27dc --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Async.swift @@ -0,0 +1,68 @@ +// +// Utils+Async.swift +// Passepartout +// +// Created by Davide De Rosa on 2/25/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import Combine + +// https://medium.com/geekculture/from-combine-to-async-await-c08bf1d15b77 + +enum AsyncPublisherError: Error { + case discarded +} + +extension Publisher { + public func async() async throws -> Output { + try await withCheckedThrowingContinuation { continuation in + var cancellable: AnyCancellable? + var isResumed = false + + cancellable = first() + .sink { result in + switch result { + case .finished: + if !isResumed { + continuation.resume(with: .failure(AsyncPublisherError.discarded)) + } + + case let .failure(error): + continuation.resume(throwing: error) + } + cancellable?.cancel() + } receiveValue: { value in + continuation.resume(with: .success(value)) + isResumed = true + } + } + } +} + +extension Task where Success == Never, Failure == Never { + public static func maybeWait(forMilliseconds msec: Int?) async { + guard let msec = msec else { + return + } + try? await sleep(nanoseconds: UInt64(msec) * NSEC_PER_MSEC) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Codable.swift b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Codable.swift new file mode 100644 index 00000000..9183dee9 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Codable.swift @@ -0,0 +1,38 @@ +// +// Utils+Codable.swift +// Passepartout +// +// Created by Davide De Rosa on 3/13/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import GenericJSON + +extension JSON { + public func decode(_ type: T.Type) throws -> T { + let data = try JSONEncoder().encode(self) + return try JSONDecoder().decode(type, from: data) + } + + public func encoded() throws -> Data { + return try JSONEncoder().encode(self) + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+CoreData.swift b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+CoreData.swift new file mode 100644 index 00000000..920b2082 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+CoreData.swift @@ -0,0 +1,37 @@ +// +// Utils+CoreData.swift +// Passepartout +// +// Created by Davide De Rosa on 3/16/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import CoreData + +extension Utils { + public static func randomizeFetchResults(_ request: NSFetchRequest, in context: NSManagedObjectContext) throws { + let count = try context.count(for: request) + if count > 0 { + request.fetchOffset = Int.random(in: 0... +// + +import Foundation + +private let timestampFormatter: DateFormatter = { + let fmt = DateFormatter() + fmt.dateStyle = .medium + fmt.timeStyle = .medium + return fmt +}() + +private let componentsFormatter: DateComponentsFormatter = { + let fmt = DateComponentsFormatter() + fmt.unitsStyle = .full + return fmt +}() + +extension Date { + public var timestamp: String { + return timestampFormatter.string(from: self) + } +} + +extension TimeInterval { + public var localizedDescription: String { + guard let str = componentsFormatter.string(from: self) else { + fatalError("Could not format a TimeInterval?") + } + return str + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+FileManager.swift b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+FileManager.swift new file mode 100644 index 00000000..ac03e8c7 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+FileManager.swift @@ -0,0 +1,51 @@ +// +// Utils+FileManager.swift +// Passepartout +// +// Created by Davide De Rosa on 9/12/19. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +extension FileManager { + public func userURL(for searchPath: SearchPathDirectory, appending: String?) -> URL { + let paths = urls(for: searchPath, in: .userDomainMask) + var directory = paths[0] + if let appending = appending { + directory.appendPathComponent(appending) + } + return directory + } + +// public func creationDate(of path: String) -> Date? { +// guard let attrs = try? attributesOfItem(atPath: path) else { +// return nil +// } +// return attrs[.creationDate] as? Date +// } +// +// public func modificationDate(of path: String) -> Date? { +// guard let attrs = try? attributesOfItem(atPath: path) else { +// return nil +// } +// return attrs[.modificationDate] as? Date +// } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Logging.swift b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Logging.swift new file mode 100644 index 00000000..8733d528 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Logging.swift @@ -0,0 +1,47 @@ +// +// Utils+Logging.swift +// Passepartout +// +// Created by Davide De Rosa on 3/24/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +// XXX: these should be aliases like #define to print original caller line +extension Utils { + public static func assertCoreDataDecodingFailed( + _ file: String, + _ function: String, + _ line: Int, + _ message: String? = nil + ) { +// assertionFailure(message ?? "Cannot decode entity required fields - \(file):\(function):\(line)") + pp_log.warning(message ?? "Cannot decode entity required fields - \(file):\(function):\(line)") + } + + public static func logFetchError(_ file: String, _ function: String, _ line: Int, _ error: Error) { + pp_log.error("Unable to fetch: \(error) - \(file):\(function):\(line)") + } + + public static func logFetchNotFound(_ file: String, _ function: String, _ line: Int) { + pp_log.debug("Not found - \(file):\(function):\(line)") + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Network.swift b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Network.swift new file mode 100644 index 00000000..19b15496 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Network.swift @@ -0,0 +1,132 @@ +// +// Utils+Network.swift +// Passepartout +// +// Created by Davide De Rosa on 2/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +#if os(iOS) +import SystemConfiguration.CaptiveNetwork +#else +import CoreWLAN +#endif + +extension Utils { + #if targetEnvironment(simulator) + public static func hasCellularData() -> Bool { + return true + } + #else + public static func hasCellularData() -> Bool { + var addrs: UnsafeMutablePointer? + guard getifaddrs(&addrs) == 0 else { + return false + } + var isFound = false + var cursor = addrs?.pointee + while let ifa = cursor { + let name = String(cString: ifa.ifa_name) + if name == "pdp_ip0" { + isFound = true + break + } + cursor = ifa.ifa_next?.pointee + } + freeifaddrs(addrs) + return isFound + } + #endif + + #if targetEnvironment(simulator) + public static func currentWifiNetworkName() -> String? { +// return nil + return ["My Home Network", "Safe Wi-Fi", "Friend's House"].randomElement() + } + #else + public static func currentWifiNetworkName() -> String? { + #if os(iOS) + guard let interfaceNames = CNCopySupportedInterfaces() as? [CFString] else { + return nil + } + for name in interfaceNames { + guard let iface = CNCopyCurrentNetworkInfo(name) as? [String: Any] else { + continue + } + guard let ssid = iface[kCNNetworkInfoKeySSID as String] as? String else { + continue + } + return ssid + } + return nil + #else + return CWWiFiClient.shared().interface()?.ssid() + #endif + } + #endif +} + +extension Utils { + public static func string(fromIPv4 ipv4: UInt32) -> String { + var remainder = ipv4 + var groups: [UInt32] = [] + var base: UInt32 = 1 << 24 + while base > 0 { + groups.append(remainder / base) + remainder %= base + base >>= 8 + } + return groups.map { "\($0)" }.joined(separator: ".") + } + + public static func ipv4(fromString string: String) -> UInt32? { + var addr = in_addr() + let result = string.withCString { + inet_pton(AF_INET, $0, &addr) + } + guard result > 0 else { + return nil + } + return CFSwapInt32BigToHost(addr.s_addr) + } +} + +//extension Utils { +// public static func checkConnectivityURL(_ url: URL, timeout: TimeInterval, completionHandler: @escaping (Bool) -> Void) { +// let session = URLSession(configuration: .ephemeral) +// let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: timeout) +// +// pp_log.info("Check connectivity via \(url)") +// session.dataTask(with: request) { (_, response, error) in +// if let response = response as? HTTPURLResponse { +// pp_log.debug("Response code: \(response.statusCode)") +// } +// if let error = error { +// pp_log.error("Connectivity failed: \(error)") +// } else { +// pp_log.info("Connectivity succeeded!") +// } +// DispatchQueue.main.async { +// completionHandler(error == nil) +// } +// }.resume() +// } +//} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Strings.swift b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Strings.swift new file mode 100644 index 00000000..f7bd5c73 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+Strings.swift @@ -0,0 +1,92 @@ +// +// Utils+Strings.swift +// Passepartout +// +// Created by Davide De Rosa on 2/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +#if os(iOS) +import UIKit +#else +import AppKit +#endif + +extension Utils { + public static func copyToPasteboard(_ string: String) { + #if os(iOS) + let pb = UIPasteboard.general + pb.string = string + #else + let pb = NSPasteboard.general + pb.clearContents() + pb.setString(string, forType: .string) + #endif + } +} + +extension String: StrippableContent { + public var stripped: String { + return trimmingCharacters(in: .whitespacesAndNewlines) + } + + public var strippedNotEmpty: String? { + let string = trimmingCharacters(in: .whitespacesAndNewlines) + guard !string.isEmpty else { + return nil + } + return string + } +} + +extension StringProtocol where Index == String.Index { + public func nsRange(from range: Range) -> NSRange { + return NSRange(range, in: self) + } +} + +extension String { + public var localizedAsCountryCode: String { + return Locale.current.localizedString(forRegionCode: self) ?? self + } +} + +extension CharacterSet { + public static let filename: CharacterSet = { + var chars: CharacterSet = .decimalDigits + let english = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + let symbols = "-_." + chars.formUnion(CharacterSet(charactersIn: english)) + chars.formUnion(CharacterSet(charactersIn: english.lowercased())) + chars.formUnion(CharacterSet(charactersIn: symbols)) + return chars + }() +} + +extension NSRegularExpression { + public convenience init(_ pattern: String) { + do { + try self.init(pattern: pattern, options: []) + } catch { + fatalError("Could not create NSRegularExpression: \(error)") + } + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+URL.swift b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+URL.swift new file mode 100644 index 00000000..9a82ec50 --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils+URL.swift @@ -0,0 +1,105 @@ +// +// Utils+URL.swift +// Passepartout +// +// Created by Davide De Rosa on 6/16/18. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import StoreKit + +#if os(iOS) +import UIKit +#else +import AppKit +#endif + +extension URL { + private static let illegalCharacterFallback = "_" + + public var normalizedFilename: String { + let filename = deletingPathExtension().lastPathComponent + return filename.components(separatedBy: CharacterSet.filename.inverted).joined(separator: URL.illegalCharacterFallback) + } + + @discardableResult + public static func openURL(_ url: URL) -> Bool { + #if os(iOS) + guard UIApplication.shared.canOpenURL(url) else { + return false + } + UIApplication.shared.open(url) + return true + #else + return NSWorkspace.shared.open(url) + #endif + } + + public static func mailto(to: String, subject: String, body: String) -> URL? { + guard let escapedSubject = subject.addingPercentEncoding(withAllowedCharacters: .alphanumerics) else { + return nil + } + guard let escapedBody = body.addingPercentEncoding(withAllowedCharacters: .alphanumerics) else { + return nil + } + return URL(string: "mailto:\(to)?subject=\(escapedSubject)&body=\(escapedBody)") + } + + public func trailingContent(bytes: UInt64) -> String { + var file: FileHandle? + defer { + try? file?.close() + } + do { + file = try FileHandle(forReadingFrom: self) + guard let size = try file?.seekToEnd() else { + pp_log.error("Cannot seek") + return "" + } + + var offset: UInt64 + if bytes < size { + offset = size - bytes + } else { + offset = 0 + } + + try file?.seek(toOffset: offset) + guard let data = try file?.readToEnd() else { + pp_log.error("No data") + return "" + } + guard let string = String(data: data, encoding: .utf8) else { + pp_log.error("Cannot encode string") + return "" + } + return string + } catch { + pp_log.error("Error while reading file: \(error)") + return "" + } + } + + public func trailingLines(bytes: UInt64) -> [String] { + let content = trailingContent(bytes: bytes) + return content.components(separatedBy: "\n") + } +} diff --git a/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils.swift b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils.swift new file mode 100644 index 00000000..f06158ab --- /dev/null +++ b/PassepartoutCore/Sources/PassepartoutUtils/Utils/Utils.swift @@ -0,0 +1,29 @@ +// +// Utils.swift +// Passepartout +// +// Created by Davide De Rosa on 2/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public enum Utils { +} diff --git a/PassepartoutCore/Sources/WireGuardAppExtension/Exports.swift b/PassepartoutCore/Sources/WireGuardAppExtension/Exports.swift new file mode 100644 index 00000000..467a01fb --- /dev/null +++ b/PassepartoutCore/Sources/WireGuardAppExtension/Exports.swift @@ -0,0 +1 @@ +@_exported import TunnelKitWireGuardAppExtension diff --git a/PassepartoutCore/Tests/PassepartoutCoreTests/ConnectionServiceTests.swift b/PassepartoutCore/Tests/PassepartoutCoreTests/ConnectionServiceTests.swift deleted file mode 100644 index fdd0ca59..00000000 --- a/PassepartoutCore/Tests/PassepartoutCoreTests/ConnectionServiceTests.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// ConnectionServiceTests.swift -// Passepartout -// -// Created by Davide De Rosa on 10/25/18. -// Copyright (c) 2020 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import XCTest -import TunnelKit -@testable import PassepartoutCore - -class ConnectionServiceTests: XCTestCase { - let url = Bundle.module.url(forResource: "ConnectionService", withExtension: "json")! - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testParse() { - let jsonData = try! Data(contentsOf: url) - XCTAssertNoThrow(try JSONSerialization.jsonObject(with: jsonData, options: [])) - } - - func testPathExtension() { - XCTAssertTrue(privateTestPathExtension("file:///foo/bar/johndoe.json")) - XCTAssertFalse(privateTestPathExtension("file:///foo/bar/break.json.johndoe.json")) - } - - private func privateTestPathExtension(_ string: String) -> Bool { - let url = URL(string: string)! - let filename = url.lastPathComponent - guard let extRange = filename.range(of: ".json") else { - return false - } - guard url.pathExtension == "json" else { - return false - } - let name1 = String(filename[filename.startIndex... +// + +import XCTest +import PassepartoutCore + +class CoreTests: XCTestCase { + override func setUp() { + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + } diff --git a/PassepartoutCore/Tests/PassepartoutCoreTests/InfrastructureTests.swift b/PassepartoutCore/Tests/PassepartoutCoreTests/InfrastructureTests.swift deleted file mode 100644 index b9b1a6c7..00000000 --- a/PassepartoutCore/Tests/PassepartoutCoreTests/InfrastructureTests.swift +++ /dev/null @@ -1,130 +0,0 @@ -// -// InfrastructureTests.swift -// Passepartout -// -// Created by Davide De Rosa on 6/11/18. -// Copyright (c) 2020 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import XCTest -import TunnelKit -@testable import PassepartoutCore -import DTFoundation - -class InfrastructureTests: XCTestCase { - private var infra: Infrastructure! - - override func setUp() { - InfrastructureFactory.shared.preload() - infra = InfrastructureFactory.shared.infrastructure(forName: .pia) - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testParsing() { - print(infra.categories) - XCTAssertEqual(infra.categories.count, 1) - } - - func testIdentifier() { - let id = "italy" - guard let pool = infra.pool(for: id) else { - XCTAssert(false) - return - } - print(pool) - XCTAssertEqual(pool.id, id) - } - - func testStableSort() { - let original: [EndpointProtocol] = [ - EndpointProtocol(.udp, 1194), - EndpointProtocol(.udp, 8080), - EndpointProtocol(.udp, 9201), - EndpointProtocol(.udp, 53), - EndpointProtocol(.udp, 1197), - EndpointProtocol(.udp, 198), - EndpointProtocol(.tcp, 443), - EndpointProtocol(.tcp, 110), - EndpointProtocol(.tcp, 80), - EndpointProtocol(.tcp, 500), - EndpointProtocol(.tcp, 501), - EndpointProtocol(.tcp, 502) - ] - var preferredType: SocketType - - preferredType = .udp - let sorted1 = original.stableSorted { - return ($0.socketType == preferredType) && ($1.socketType != preferredType) - } - XCTAssertEqual(sorted1, original) - - preferredType = .tcp - let sorted2 = original.stableSorted { - return ($0.socketType == preferredType) && ($1.socketType != preferredType) - } - XCTAssertNotEqual(sorted2, original) - } - - func testLastModified() { - let fmt = DateFormatter() - fmt.timeZone = TimeZone(abbreviation: "GMT") - fmt.dateFormat = "EEE, dd LLL yyyy HH:mm:ss zzz" - - let lmString = "Wed, 23 Oct 2019 17:06:54 GMT" - - fmt.locale = Locale(identifier: "en") - XCTAssertNotNil(fmt.date(from: lmString)) - fmt.locale = Locale(identifier: "fr-FR") - XCTAssertNil(fmt.date(from: lmString)) - } - - func testProvidersIndex() { - let ifactory = InfrastructureFactory.shared - XCTAssertNotNil(ifactory.metadata(forName: "nordvpn")) - XCTAssertNil(ifactory.metadata(forName: "expressvpn")) - - let update = expectation(description: "updateIndex") - ifactory.updateIndex { _ in - update.fulfill() - } - waitForExpectations(timeout: 10.0) { _ in - print(ifactory.allMetadata) - } - } - - func testExtraction() { - let archive = DTZipArchive(atPath: Bundle.module.path(forResource: "example", ofType: "zip")!) - let exp = expectation(description: "Extraction") - let toPath = FileManager.default.temporaryDirectory.path - archive?.uncompress(toPath: toPath) { error in - if let error = error { - print("Extraction failed:", error) - } else { - print("Extraction succeeded!") - } - exp.fulfill() - XCTAssertNil(error) - } - waitForExpectations(timeout: 10.0, handler: nil) - } -} diff --git a/PassepartoutCore/Tests/PassepartoutCoreTests/Resources/ConnectionService.json b/PassepartoutCore/Tests/PassepartoutCoreTests/Resources/ConnectionService.json deleted file mode 100644 index 142883f8..00000000 --- a/PassepartoutCore/Tests/PassepartoutCoreTests/Resources/ConnectionService.json +++ /dev/null @@ -1 +0,0 @@ -{"appGroup":"group.com.algoritmico.Passepartout","activeProfileId":"host.edu","tunnelConfiguration":{"endpointProtocols":["UDP:1194"],"compressionFraming":0,"digest":"SHA1","ca":"","lastErrorKey":"LastVPNError","debugLogFormat":"$DHH:mm:ss$d - $M","usesPIAPatches":false,"cipher":"AES-128-CBC","prefersResolvedAddresses":false,"shouldDebug":true,"mtu":1250,"debugLogKey":"LastVPNLog"},"preferences":{"trustPolicy":"ignore","trustsMobileNetwork":false,"disconnectsOnSleep":false,"trustedWifis":{},"resolvesHostname":true},"profiles":[{"provider":{"username":"p0000000","id":"provider.PIA","poolId":"ca-vancouver","name":"PIA","presetId":"recommended"}},{"host":{"username":"","title":"edu","hostname":"1.2.4.5","parameters":{"endpointProtocols":["UDP:1194","TCP:1194","TCP:443"],"compressionFraming":1,"digest":"SHA256","ca":"bogus+ca","clientCertificate":"bogus+client","usesPIAPatches":false,"tlsWrap":{"key":{"dir":1,"data":"bogus+static+key"},"strategy":"auth"},"cipher":"AES-256-CBC","prefersResolvedAddresses":false,"clientKey":"bogus+key","mtu":1500,"shouldDebug":false}}},{"host":{"username":"","title":"vps-udp-tc","hostname":"8.8.4.4","parameters":{"shouldDebug":false,"endpointProtocols":["UDP:1198"],"compressionFraming":1,"digest":"SHA512","ca":"bogus+ca","renegotiatesAfterSeconds":0,"usesPIAPatches":false,"tlsWrap":{"key":{"dir":1,"data":"bogus+static+key"},"strategy":"crypt"},"cipher":"AES-192-CBC","prefersResolvedAddresses":false,"clientKey":"bogus+key","mtu":1500,"keepAliveSeconds":25}}}]} diff --git a/PassepartoutCore/Tests/PassepartoutCoreTests/Resources/example.zip b/PassepartoutCore/Tests/PassepartoutCoreTests/Resources/example.zip deleted file mode 100644 index 1959b19fc217af2506b1a1aa721f8ad977c1dcd5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 794 zcmWIWW@Zs#U|`^2*z4x%e^2`N&iyv8T!_ z6H6<&85mi#RJd50KVgBZ zX4~YA?z0|z|CprKeOPveK+S8OPma%Tf4h9ia?>-}^V{&$yNfAQZ?Aow z-y^2m^Op;%zU_Hwvo0s>Px5UKQK|dC6F-P|UHi4Mc#6iK6rbeNZ~k=8-5hh?`d3HT zda)M_%llW~i&|)Yn8CU_TH@8Cs7PFxBtG2Su3D|Qbdd0K@r*;2KN}bm5BUbyONr0wWx7Bo}>`gwO zOVv)RSGdGPKCF)r3sPRWu7M>#B*?@ijAQD$4=3eQ^}d=Vdav|;6cff}aU~{m=DOl_ z3*uJq%3?W{?h$>eXk~z_$f7I!E4oYt&i`mgJNkLf5{dZ5)vo^(*1lRh$zPjywbpqS zF?m+2$Nf8ZcqOhddA`X*;W2Byknt)fv7;GpUh$b}JE~rK{KI?e8n3R2VN1h)_!um` z^!`F`jtf)F$u_Y~A)yluqSTdE&15NDx?|IYRSz$_7PQ=Z?Y2kQNv)zUhPP4k(d`e+ zzqtdv8JXmmaV1;{V1i~~0H#oeC5<2!JQ=e>k}+Bm4)A7W11Vz!LSG=g9hd_c7yz>> BRa*c6 diff --git a/PassepartoutCore/Tests/PassepartoutProfilesTests/ProfilesTests.swift b/PassepartoutCore/Tests/PassepartoutProfilesTests/ProfilesTests.swift new file mode 100644 index 00000000..5c947a65 --- /dev/null +++ b/PassepartoutCore/Tests/PassepartoutProfilesTests/ProfilesTests.swift @@ -0,0 +1,39 @@ +// +// ProfilesTests.swift +// Passepartout +// +// Created by Davide De Rosa on 4/7/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import XCTest +@testable import PassepartoutProfiles +import SwiftyBeaver + +@MainActor +class ProfilesTests: XCTestCase { + + @MainActor + override func setUp() { + } + + override func tearDown() { + } +} diff --git a/PassepartoutCore/Tests/PassepartoutProvidersTests/ProvidersTests.swift b/PassepartoutCore/Tests/PassepartoutProvidersTests/ProvidersTests.swift new file mode 100644 index 00000000..d5b0c7b7 --- /dev/null +++ b/PassepartoutCore/Tests/PassepartoutProvidersTests/ProvidersTests.swift @@ -0,0 +1,174 @@ +// +// ProvidersTests.swift +// Passepartout +// +// Created by Davide De Rosa on 3/13/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import XCTest +import CoreData +import Combine +@testable import PassepartoutProviders +import PassepartoutServices +import PassepartoutUtils +import SwiftyBeaver + +@MainActor +class ProvidersTests: XCTestCase { + private static let persistence: Persistence = { + let model = NSManagedObjectModel.mergedModel(from: [.module])! + return Persistence(withLocalName: "Providers", model: model, author: nil) + }() + + private var manager: ProviderManager! + + private var cancellables: Set = [] + + @MainActor + override func setUp() { + pp_log.addDestination(ConsoleDestination()) + + manager = ProviderManager( + appBuild: 10000, + bundleServices: DefaultWebServices.bundledServices(withVersion: "v5"), + webServices: DefaultWebServices("v5", URL(string: "https://passepartoutvpn.app/api/")!, timeout: nil), + persistence: ProvidersTests.persistence + ) +// manager.reset() + } + + override func tearDown() { +// manager.reset() + } + +// func testLoadBundledProviders() throws { +// let exp = expectation(description: "Load bundle") +// +// manager.loadBundledProvidersPublisher(vpnProtocol: .openVPN) +// .sink { +// switch $0 { +// case .finished: +// exp.fulfill() +// +// case .failure(let error): +// pp_log.error("Unable to load bundled providers: \(error)") +// exp.fulfill() +// } +// } receiveValue: { +// pp_log.debug("Loaded \($0.count) providers") +// }.store(in: &cancellables) +// +// waitForExpectations(timeout: 10.0, handler: nil) +// } + + func testFetchRemoteIndex() throws { + let exp = expectation(description: "Remote index") + + manager.fetchProvidersIndexPublisher(priority: .remote) + .sink { + switch $0 { + case .finished: + exp.fulfill() + + case .failure(let error): + pp_log.error("Unable to load remote provider: \(error)") + exp.fulfill() + } + } receiveValue: { + pp_log.debug("Loaded index") + }.store(in: &cancellables) + + waitForExpectations(timeout: 10.0, handler: nil) + } + + func testFetchRemoteProvider() async { + do { + try await manager.fetchProviderPublisher(withName: .hideme, vpnProtocol: .openVPN, priority: .remote).async() + pp_log.debug("Loaded provider") + } catch { + XCTFail("Unable to load remote provider: \(error)") + } + } + + func testListProviders() { + let providers = manager.allProviders() + providers.forEach { + pp_log.debug("\($0.name) -> \($0.fullName)") + } + } + + func testListCategories() async { + await fetchProvider(.surfshark) + let categories = manager.categories(.surfshark, vpnProtocol: .openVPN) + categories.forEach { + pp_log.debug("Category: \($0.name)") + $0.locations.forEach { + pp_log.debug("\t\($0)") + } + } + } + + func testListServers() async { + await fetchProvider(.nordvpn) + manager.allProviders().filter({ $0.name == .nordvpn }).forEach { + let location = ProviderLocation( + providerMetadata: $0, + vpnProtocol: .openVPN, + categoryName: "", + countryCode: "ES", + onlyServer: nil + ) + + let servers = manager.servers(forLocation: location) + pp_log.debug("\($0.fullName): Servers [\(location.countryCode)] (\(servers.count)): \(servers)") + } + } + + func testServerId() async { + await fetchProvider(.nordvpn) + guard let server = manager.server(.nordvpn, vpnProtocol: .openVPN, serverId: "es143") else { + return + } + pp_log.debug(server) + } + + func testDefaultServer() async { + await fetchProvider(.protonvpn) + guard let server = manager.anyDefaultServer(.protonvpn, vpnProtocol: .openVPN) else { + return + } + pp_log.debug(server) + } + + func testServerUniqueId() async { + await fetchProvider(.nordvpn) + guard let server = manager.server(withId: "BEA03D24A5854DD17395057DEFBE7D6BEEA981227ACF8949E487443E6B5EF9C7") else { + return + } + pp_log.debug(server) + XCTAssertEqual(server.apiId, "es143") + } + + private func fetchProvider(_ name: ProviderName) async { + try? await manager.fetchProvidersIndexPublisher(priority: .bundle).async() + try? await manager.fetchProviderPublisher(withName: name, vpnProtocol: .openVPN, priority: .bundle).async() + } +} diff --git a/PassepartoutCore/Tests/PassepartoutServicesTests/ServicesTests.swift b/PassepartoutCore/Tests/PassepartoutServicesTests/ServicesTests.swift new file mode 100644 index 00000000..091c1077 --- /dev/null +++ b/PassepartoutCore/Tests/PassepartoutServicesTests/ServicesTests.swift @@ -0,0 +1,98 @@ +// +// ServicesTests.swift +// Passepartout +// +// Created by Davide De Rosa on 6/11/18. +// Copyright (c) 2020 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import XCTest +import Combine +@testable import PassepartoutServices +import PassepartoutUtils +import SwiftyBeaver + +class ServicesTests: XCTestCase { + let wsLocal = DefaultWebServices.bundledServices(withVersion: "v5") + + let wsRemote = DefaultWebServices("v5", URL(string: "https://passepartoutvpn.app/api/")!, timeout: nil) + + private var cancellables: Set = [] + + override func setUp() { + SwiftyBeaver.addDestination(ConsoleDestination()) + } + + override func tearDown() { + } + + func testLastModified() { + let fmt = DateFormatter() + fmt.timeZone = TimeZone(abbreviation: "GMT") + fmt.dateFormat = "EEE, dd LLL yyyy HH:mm:ss zzz" + + let lmString = "Wed, 23 Oct 2019 17:06:54 GMT" + + fmt.locale = Locale(identifier: "en") + XCTAssertNotNil(fmt.date(from: lmString)) + fmt.locale = Locale(identifier: "fr-FR") + XCTAssertNil(fmt.date(from: lmString)) + } + + func testLocalIndex() { + let exp = expectation(description: "") + wsLocal.providersIndex() + .sink { + switch $0 { + case .finished: + break + + case .failure(let error): + pp_log.debug(error) + exp.fulfill() + } + } receiveValue: { + pp_log.debug($0) + exp.fulfill() + }.store(in: &cancellables) + + waitForExpectations(timeout: 10.0, handler: nil) + } + + func testRemoteIndex() { + let exp = expectation(description: "") + wsRemote.providersIndex() + .sink { + switch $0 { + case .finished: + break + + case .failure(let error): + pp_log.debug(error) + exp.fulfill() + } + } receiveValue: { + pp_log.debug($0) + exp.fulfill() + }.store(in: &cancellables) + + waitForExpectations(timeout: 10.0, handler: nil) + } +} diff --git a/PassepartoutCore/Tests/PassepartoutUtilsTests/Resources/Debug.log b/PassepartoutCore/Tests/PassepartoutUtilsTests/Resources/Debug.log new file mode 100644 index 00000000..c9fd816e --- /dev/null +++ b/PassepartoutCore/Tests/PassepartoutUtilsTests/Resources/Debug.log @@ -0,0 +1,15 @@ +17:52:58.836 DEBUG PassepartoutApp.initializeManagers():85 - Logging to: Optional(file:///Users/keeshux/Library/Developer/CoreSimulator/Devices/E70FACB1-8B61-4EF1-A728-3CA7832FE6A6/data/Containers/Shared/AppGroup/A6A53064-6DA4-4FB3-BCA6-0E4F06080EDB/Library/Caches/Debug.log) +17:52:59.095 DEBUG ProductManager.listProducts():124 - In-app products: ["com.algoritmico.ios.Passepartout.features.trusted_networks", "com.algoritmico.ios.Passepartout.features.full_mac_version", "com.algoritmico.ios.Passepartout.features.full_multi_version", "com.algoritmico.ios.Passepartout.donations.Big", "com.algoritmico.ios.Passepartout.donations.Tiny", "com.algoritmico.ios.Passepartout.features.full_version", "com.algoritmico.ios.Passepartout.donations.Medium", "com.algoritmico.ios.Passepartout.features.all_providers", "com.algoritmico.ios.Passepartout.donations.Small", "com.algoritmico.ios.Passepartout.donations.Huge", "com.algoritmico.ios.Passepartout.features.siri", "com.algoritmico.ios.Passepartout.donations.Maxi"] +17:52:59.123 ERROR ProductManager.reloadReceipt():255 - Could not parse App Store receipt! +17:53:23.048 DEBUG ProfileManager.profile():155 - Loading profile and account with id CBE39E2C-0EB3-47C6-9709-83A68A620ED2... +17:53:23.074 DEBUG ProfileManager.password():230 - Unable to load password from keychain: notFound +17:53:23.230 DEBUG KeyedCache.put():61 - Cache MISS [servers] +17:53:23.234 DEBUG KeyedCache.put():61 - Cache MISS [lastUpdate] +17:53:25.055 DEBUG ProfileManager.persistProfile():263 - Writing profile CBE39E2C-0EB3-47C6-9709-83A68A620ED2 to persistent store... +17:53:25.061 DEBUG ProfileManager.persistProfile():265 - Saving associated password to keychain +17:53:25.889 DEBUG ProfileManager.profile():155 - Loading profile and account with id BF151D7D-4CD3-4403-8E4D-245CB9EF3CED... +17:53:25.893 DEBUG ProfileManager.password():230 - Unable to load password from keychain: notFound +17:53:25.923 DEBUG KeyedCache.put():61 - Cache MISS [servers] +17:53:25.926 DEBUG KeyedCache.put():61 - Cache MISS [lastUpdate] +17:53:27.144 DEBUG ProfileManager.persistProfile():263 - Writing profile BF151D7D-4CD3-4403-8E4D-245CB9EF3CED to persistent store... +17:53:27.150 DEBUG ProfileManager.persistProfile():265 - Saving associated password to keychain diff --git a/PassepartoutCore/Tests/PassepartoutCoreTests/UtilsTests.swift b/PassepartoutCore/Tests/PassepartoutUtilsTests/UtilsTests.swift similarity index 70% rename from PassepartoutCore/Tests/PassepartoutCoreTests/UtilsTests.swift rename to PassepartoutCore/Tests/PassepartoutUtilsTests/UtilsTests.swift index 23dddb52..efec327c 100644 --- a/PassepartoutCore/Tests/PassepartoutCoreTests/UtilsTests.swift +++ b/PassepartoutCore/Tests/PassepartoutUtilsTests/UtilsTests.swift @@ -24,31 +24,18 @@ // import XCTest -@testable import PassepartoutCore +import PassepartoutUtils +import SwiftyBeaver class UtilsTests: XCTestCase { override func setUp() { + pp_log.addDestination(ConsoleDestination()) } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } - func testDataUnitDescription() { - XCTAssertEqual(0.dataUnitDescription, "0B") - XCTAssertEqual(1.dataUnitDescription, "1B") - XCTAssertEqual(1024.dataUnitDescription, "1kB") - XCTAssertEqual(1025.dataUnitDescription, "1kB") - XCTAssertEqual(548575.dataUnitDescription, "0.52MB") - XCTAssertEqual(1048575.dataUnitDescription, "1.00MB") - XCTAssertEqual(1048576.dataUnitDescription, "1.00MB") - XCTAssertEqual(1048577.dataUnitDescription, "1.00MB") - XCTAssertEqual(600000000.dataUnitDescription, "0.56GB") - XCTAssertEqual(1073741823.dataUnitDescription, "1.00GB") - XCTAssertEqual(1073741824.dataUnitDescription, "1.00GB") - XCTAssertEqual(1073741825.dataUnitDescription, "1.00GB") - } - func testLanguageLocalization() { let languages = ["en", "it", "de", "pt-BR", "ru"] let english = Locale(identifier: "en") @@ -61,6 +48,19 @@ class UtilsTests: XCTestCase { XCTAssertEqual(languagesIT, ["en", "it", "pt-BR", "ru", "de"]) } + func testTrailing() { + let file = Bundle.module.url(forResource: "Debug", withExtension: "log")! + + for len in [10, 100, 1000] { + let last = file.trailingContent(bytes: UInt64(len)) + XCTAssertEqual(last.count, len) + pp_log.debug(last) + } + XCTAssertNotEqual(file.trailingContent(bytes: 100000).count, 100000) + + pp_log.debug(file.trailingLines(bytes: 1000)) + } + private func privateSortedLanguages(_ languages: [String], with locale: Locale) -> [String] { return languages.sorted { return locale.localizedString(forLanguageCode: $0)! < locale.localizedString(forLanguageCode: $1)! diff --git a/PassepartoutCore/swiftgen.yml b/PassepartoutCore/swiftgen.yml deleted file mode 100644 index b6c950c3..00000000 --- a/PassepartoutCore/swiftgen.yml +++ /dev/null @@ -1,8 +0,0 @@ -strings: - inputs: - - Sources/PassepartoutCore/Resources/en.lproj/Core.strings - outputs: - - templateName: structured-swift4 - output: Sources/PassepartoutCore/SwiftGen+Strings.swift - params: - publicAccess: true diff --git a/fastlane/Matchfile b/fastlane/Matchfile index 7885fa90..f33d9b34 100644 --- a/fastlane/Matchfile +++ b/fastlane/Matchfile @@ -2,7 +2,8 @@ type "development" # The default type, can be: appstore, adhoc, enterprise or de app_identifier [ "com.algoritmico.ios.Passepartout", - "com.algoritmico.ios.Passepartout.Tunnel" + "com.algoritmico.ios.Passepartout.Tunnel.OpenVPN", + "com.algoritmico.ios.Passepartout.Tunnel.WireGuard" ] # For all available options run `fastlane match --help`