diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 24e8ab2e6..68a169237 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -11,6 +11,7 @@ on: env: # Make the git branch for a PR available to our Fastfile MX_GIT_BRANCH: ${{ github.event.pull_request.head.ref }} + MapTilerAPIKey: ${{ secrets.MAPTILER_API_KEY }} jobs: build: diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 00d4fa2f9..3fcc19ed3 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -12,6 +12,7 @@ on: env: # Make the git branch for a PR available to our Fastfile MX_GIT_BRANCH: ${{ github.event.pull_request.head.ref }} + MapTilerAPIKey: ${{ secrets.MAPTILER_API_KEY }} jobs: tests: diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index c952f6a4a..ea55db429 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -11,6 +11,7 @@ on: env: # Make the git branch for a PR available to our Fastfile MX_GIT_BRANCH: ${{ github.event.pull_request.head.ref }} + MapTilerAPIKey: ${{ secrets.MAPTILER_API_KEY }} jobs: check-secret: diff --git a/CHANGES_BWI.md b/CHANGES_BWI.md new file mode 100644 index 000000000..8db2a7557 --- /dev/null +++ b/CHANGES_BWI.md @@ -0,0 +1,706 @@ +Changes in BWI project 1.17.0 (2022-02-14) +=================================================== + +Upstream merge ✨: + +- v1.6.12h + +Features ✨: +- Emoji history (#1601) +- Advertising new Features (#2469) + + +Improvements πŸ™Œ: +- Beta app has flexible server backends (#2699) + +Bugfix πŸ›: +- DMs don't have roles & rights any more (#1919) + +Translations πŸ—£: + +SDK API changes ⚠️: + +Build 🧱: + +Changes in BWI project 1.16.0 (2022-01-12) +=================================================== + +Upstream merge ✨: + +- v1.6.11h + +Features ✨: +- Downtime warnings (#2597) +- Forward messages +- Mention users + +Improvements πŸ™Œ: +- Removed hardened usage + + +Bugfix πŸ›: +- Reset passphrase dialog flow not cancelable +- Fixed a crash when trying to access the personal notes room + +Translations πŸ—£: + +SDK API changes ⚠️: + +Build 🧱: + +Changes in BWI project 1.14.0 (2021-11-04) +=================================================== + +Upstream merge ✨: + +- v1.6.5h + +Features ✨: +- Happy Birthday (#2538) +- Spaces in a deactivated state + +Improvements πŸ™Œ: +- Integration of Matomo Tracking for Beta App (#2519, #2525) +- Register button in login screen (#2536) + +Bugfix πŸ›: +- Some crash fixes regarding Realm and iOS 15 +- Crosssigning in Security settings deactivated (#2456) +- Translated and cleared error messages on user invite (#2470) + +Translations πŸ—£: + +SDK API changes ⚠️: + +Build 🧱: + +Changes in BWI project 1.13.0 (2021-10-06) +=================================================== + +Upstream merge ✨: + +- v1.5.4h + +Features ✨: +- Voice Messages activated +- Rules & Rights (#2310) +- Reset room rights (#2312) +- New Notification setting menu + +Improvements πŸ™Œ: +- Camera: The quality of video when filming in-app is significantly higher. +- Mark iOS 11 as deprecated and show different version check alerts. +- Moved converted voice messages to their own folder. Cleaning up all temporary files on on reload and logout. +- Popping the user back to the home screen after leaving a room. + +Bugfix πŸ›: +- The last admin of a room is not able to unpower himself (#2101) +- Fixed home view being clipped when search is active. + +Translations πŸ—£: + +SDK API changes ⚠️: + +Build 🧱: + +Changes in BWI project 1.12.0 (2021-09-15) +=================================================== + +Upstream merge ✨: + +- v1.5.1h + +Features ✨: +- Voice Messages +- Personal Notes room (#2223) +- Feature Announce Banner (#2214) +- Show/Hide Timeline events (#1552) + + +Improvements πŸ™Œ: + +Bugfix πŸ›: +- Corrected behaviour when faceID/toucgID is available but deactivated for the app (#2247) + +Translations πŸ—£: + +SDK API changes ⚠️: + +Build 🧱: + +Changes in BWI project 1.11.0 (2021-08-12) +=================================================== + +Upstream merge ✨: + +- v1.4.7h + +Features ✨: +- Show/Hide password while typing (#1967) +- Password forgotten? Infotext for what to do (#1818) + + +Improvements πŸ™Œ: +- Better Icons for DMs / Rooms (#2069, #2071) +- Show weekdays in timeline (#2080) +- Show Element version in about settings (#2098) +- Better asynchronous decryption (NV #3777) + +Bugfix πŸ›: + +Translations πŸ—£: + +SDK API changes ⚠️: + +Build 🧱: + + +Other changes: + +Changes in BWI project 1.10.0 (2021-07-14) +=================================================== + +Upstream merge ✨: + +- v1.4.1h + +Features ✨: +- Avatar can by restored to standard avatar (#1905) + +Improvements πŸ™Œ: +- Crypto: Do not decrypt synchronously. It asynchronously happens upstream now + +Bugfix πŸ›: +- SettingsViewController: Fix crash when changing the app language +- UserSessionsService: Fix room lists lost after a reset cache +- From MatrixSDK 0.19.1: App can fail to perform an incremental sync on reopening (#4417) suggested by Giom + +Translations πŸ—£: +SDK API changes ⚠️: +Build 🧱: + + +Other changes: + +Changes in BWI project 1.9.0 (2021-06-17) +=================================================== + +Upstream merge ✨: + +- v1.3.9h + +Features ✨: + +Improvements πŸ™Œ: + +Bugfix πŸ›: +Translations πŸ—£: +SDK API changes ⚠️: +Build 🧱: + +- Added automated UI testing (#1700) +- Disable Logging (#1787) + +Other changes: + + +Changes in BWI project 1.8.0 (2021-05-20) +=================================================== + +Upstream merge ✨: + +- v1.3.5h + +Features ✨: + +- Redesigned Composer (writing mode and action mode) +- Typing notifications + +Improvements πŸ™Œ: + +- Disable Integrations (#1832) +- Disable stickers for composer (#1832) + +Bugfix πŸ›: +Translations πŸ—£: +SDK API changes ⚠️: +Build 🧱: +Other changes: + +- Rest Time UI and Logic (apart from Notifications) (#1800, #1803, #1804) + + +Changes in BWI project 1.7.0 (2021-04-19) +=================================================== + +Upstream merge ✨: + +v1.3.1h + +Features ✨: + +- Rooms can be changed from privat to public and back by the room admin (#912) + +Improvements πŸ™Œ: + +- Deactivate Cross signing for web client release (#1742) +- Allow change display name depending on server config (#1668) +- Disable voice call settings (#1744) +- Disable NSFW setting (#1744) + +Bugfix πŸ›: + +- Clearify Face/Touch ID error message (#1659) + +Translations πŸ—£: + +- Change "Passphrase" to "VerschlΓΌsselungskennwort" +- Add translations for New Room details (#1744) + + +SDK API changes ⚠️: +Build 🧱: + +Create Beta build config (#1732) + +Other changes: + +Allow test server login in Beta build (#1732) +Enable Rageshake in Beta build (#1623) + +Changes in BWI project 1.6.0 (2021-03-23) +=================================================== + +Upstream merge ✨: + - v1.2.6h + +Features ✨: + - Rooms can be changed from privat to public and back by the room admin (#912) + +Improvements πŸ™Œ: +- Rooms may not be left by the only admin (#1629) +- Accepting invitations takes only one click (#749) + +Bugfix πŸ›: + + +Translations πŸ—£: + - German translation for integrity check (#1689) + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - + +Changes in BWI project 1.5.1 (2021-02-15) +=================================================== + +Upstream merge ✨: + - v1.1.2h + - v1.1.5h + +Features ✨: + - + +Improvements πŸ™Œ: + - Added timestamp to all chat messages. + - Added Face ID support + - Changed room details (members) icon backround in olive green + - Hide Pin change description text and localized some strings + - Removed room encryption alert view entry + - Javascipt support disabled + - Removed invite friends to BwMessenger + - Removed "Direktnachricht" from support.html + +Bugfix πŸ›: + - Login screen icon centered. + +Translations πŸ—£: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - + + +Changes in BWI project 1.0.21 (2020-11-25) +=================================================== + +Upstream merge ✨: + - + +Features ✨: + - + +Improvements πŸ™Œ: + - Removed files and messages from search tab + - No popup for selecting the file size appears anymore + - Increased pin storage security (MESSENGER-834) + +Bugfix πŸ›: + - Pin bypass (MESSENGER-936) + +Translations πŸ—£: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - + + Changes in BWI project 1.0.18 (2020-11-09) + =================================================== + + Upstream merge ✨: + - + + Features ✨: + - + + Improvements πŸ™Œ: + - + + Bugfix πŸ›: + - Hotfix to disable matomo tracking + + Translations πŸ—£: + - + + SDK API changes ⚠️: + - + + Build 🧱: + - + + Other changes: + - removed other element specific urls in project and buildsettings as they are opened on startup + +Changes in BWI project 1.0.16 (2020-10-30) + =================================================== + + Upstream merge ✨: + - + + Features ✨: + - + + Improvements πŸ™Œ: + - Improved login screen + - Pin gracetime 3min + - Logout and delete all files if jailbreak detected + + Bugfix πŸ›: + - Fix pincode bypass on fast switching (#59). + + Translations πŸ—£: + - Push message notification text in german + + SDK API changes ⚠️: + - + + Build 🧱: + - + + Other changes: + - + +Changes in BWI project 1.0.15 (2020-10-20) +=================================================== + +Upstream merge ✨: +- + +Features ✨: +- + +Improvements πŸ™Œ: +- + +Bugfix πŸ›: +- + +Translations πŸ—£: +- + +SDK API changes ⚠️: +- + +Build 🧱: +- + +Other changes: +- Removed text below mobile icon to be conform for Apple Review. + + +Changes in BWI project 1.0.13.7 (2020-10-15) +=================================================== + +Upstream merge ✨: + - + +Features ✨: + - + +Improvements πŸ™Œ: + - + +Bugfix πŸ›: + - Removed audio background mode from info.plist (app store review issue) + +Translations πŸ—£: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - + + + Changes in BWI project 1.0.13.6 (2020-10-12) +=================================================== + +Upstream merge ✨: + - + +Features ✨: + - + +Improvements πŸ™Œ: + - New splash screen with camou background and BWI logo + +Bugfix πŸ›: + - Face ID now completely deactivated + +Translations πŸ—£: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - + + + +Changes in BWI project 1.0.13.2 (2020-10-09) +=================================================== + +Upstream merge ✨: + - + +Features ✨: + - + +Improvements πŸ™Œ: + - Hide security info + - Support setting + - Removed Terms and Conditions setting + - Added Support setting + - Reset passphrase workflow fixed + +Bugfix πŸ›: + - + +Translations πŸ—£: + - Secure recovery + - DM leave + - Reset passphrase + + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - + + + + +Changes in BWI project 1.0.13.0 (2020-10-01) +=================================================== + +Upstream merge ✨: + - Hardened 1.0.13 + +Features ✨: + - New appicon design + - New Splash screen design + - Removed email field under settings + +Improvements πŸ™Œ: + - Themes contains olive green tint color for button and text elements. + +Bugfix πŸ›: + - + +Translations πŸ—£: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - + + + + +Changes in BWI project 1.0.11.0 (2020-09-18) +=================================================== + +Features ✨: + - Update to Element 1.0.11 + +Improvements πŸ™Œ: + - Changed hint text in search field for people + - Hide trusted text in attachments like in images + - Security header in bold + - Removed Advanced section under Security + +Bugfix πŸ›: + - GIFs are animated again after uploading them + +Translations πŸ—£: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - Disable Xcode Warning from Third Party Vendors + - Upgrade minor Xcode project changes to v11.x + + +Changes in BWI project 1.0.4.11 (2020-09-13) +=================================================== + +Features ✨: + - + +Improvements πŸ™Œ: + - Settings header in bold + - No context menu when file is infected + - Hide presence status of user + - Hide email text from settings + - Compress every image to max. 1600x1200 px + - Hide "Direct" and "Low Priority" Button from swipe menu on room, "Leave room" is red now + - Hide AV status text if it is "Trusted" + +Bugfix πŸ›: + - Fixed problem with removing Face ID after first time login. + +Translations πŸ—£: + - German Translations for Jailbreak info + - AV texts + - Improved password change texts + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - Login text username only (no email) + + + + + +Changes in BWI project 1.0.4.10 (2020-09-04) +=================================================== + +Features ✨: + - + +Improvements πŸ™Œ: + - string change "Personen" to "Direktnachrichten + - Moved sign out button from top to bottom of settings. + - Removed choose language feature from settings. + - Hide Home and Communnities in Bottom Bar, DM is default + - Disabled iCloud link + - Disabled Siri + - Password change dialog with rules text + - BwMessenger splash screen inspired from Android version + - Display name readonly + + +Bugfix πŸ›: + - + +Translations πŸ—£: + - Pin Code dialog + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - Disabled Face ID + + + + + +======================================================= ++ TEMPLATE WHEN PREPARING A NEW RELEASE + +======================================================= + + +Changes in BWI project 1.X.X (2020-XX-XX) +=================================================== + +Upstream merge ✨: + - + +Features ✨: + - + +Improvements πŸ™Œ: + - + +Bugfix πŸ›: + - + +Translations πŸ—£: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - diff --git a/Config/AppConfiguration.swift b/Config/AppConfiguration.swift index 52b041656..9ae6356e1 100644 --- a/Config/AppConfiguration.swift +++ b/Config/AppConfiguration.swift @@ -1,5 +1,6 @@ // // Copyright 2020 Vector Creations Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -56,6 +57,10 @@ class AppConfiguration: CommonConfiguration { override func setupSettings(for matrixSession: MXSession) { super.setupSettings(for: matrixSession) setupWidgetReadReceipts(for: matrixSession) + + if BuildSettings.enableAntivirusScan, let homeServerStringURL = matrixSession.credentials?.homeServer { + matrixSession.antivirusServerURL = homeServerStringURL + } } private func setupWidgetReadReceipts(for matrixSession: MXSession) { diff --git a/Config/AppIdentifiers.xcconfig b/Config/AppIdentifiers.xcconfig index e3cb5d48b..b556cd7a8 100644 --- a/Config/AppIdentifiers.xcconfig +++ b/Config/AppIdentifiers.xcconfig @@ -1,5 +1,6 @@ // // Copyright 2021 Vector Creations Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,24 +17,28 @@ // App identity -BUNDLE_DISPLAY_NAME = Element -BASE_BUNDLE_IDENTIFIER = im.vector.app -APPLICATION_GROUP_IDENTIFIER = group.im.vector +BUNDLE_DISPLAY_NAME = BundesMessenger +BASE_BUNDLE_IDENTIFIER = de.bwi.messenger +APPLICATION_GROUP_IDENTIFIER = group.de.messenger APPLICATION_SCHEME = element +// Version +MARKETING_VERSION = 1.18.0 +CURRENT_PROJECT_VERSION = 1.18.0 + // Team -DEVELOPMENT_TEAM = 7J4U792NQT +DEVELOPMENT_TEAM = Q111Q11QQ1 // Provisioning profiles RIOT_PROVISIONING_PROFILE_SPECIFIER = Vector App Store -RIOT_PROVISIONING_PROFILE = 4b43c1ca-3246-4984-828f-165838f5715a +RIOT_PROVISIONING_PROFILE = f65e7447-b8a3-46cc-8fba-fa60e55e2511 NSE_PROVISIONING_PROFILE_SPECIFIER = "Vector NSE: App Store" -NSE_PROVISIONING_PROFILE = de44ca91-4318-4c23-8611-b531793505c2 +NSE_PROVISIONING_PROFILE = 31dc9316-e029-47fd-81f5-778db07d76a2 SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER = "Vector Share Extension: App Store" -SHARE_EXTENSION_PROVISIONING_PROFILE = 546090a2-77ca-4bc2-b904-da5bd97a2f37 +SHARE_EXTENSION_PROVISIONING_PROFILE = 1a3be143-50c7-4ae2-834e-00596a053141 SIRI_INTENTS_PROVISIONING_PROFILE_SPECIFIER = "Vector Siri Intents: App Store" -SIRI_INTENTS_PROVISIONING_PROFILE = 6951ad31-4850-445a-89c8-b64bca0a1c44 +SIRI_INTENTS_PROVISIONING_PROFILE = 18a66f93-ffe1-4008-b343-58350cc65023 diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 3fe634b3f..858593730 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -1,5 +1,6 @@ // // Copyright 2021 Vector Creations Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,5 +16,5 @@ // // Version -MARKETING_VERSION = 1.8.1 -CURRENT_PROJECT_VERSION = 1.8.1 +MARKETING_VERSION = 1.18.0 +CURRENT_PROJECT_VERSION = 20220309075454 diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 305f4dddc..9148fd4aa 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -1,5 +1,6 @@ // // Copyright 2020 Vector Creations Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +17,10 @@ import Foundation +#if LOCATION +import Keys +#endif + /// BuildSettings provides settings computed at build time. /// In future, it may be automatically generated from xcconfig files @objcMembers @@ -87,48 +92,30 @@ final class BuildSettings: NSObject { } // Element-Web instance for the app - static let applicationWebAppUrlString = "https://app.element.io" - + static let applicationWebAppUrlString = "" // MARK: - Server configuration // Default servers proposed on the authentication screen - static let serverConfigDefaultHomeserverUrlString = "https://matrix.org" - static let serverConfigDefaultIdentityServerUrlString = "https://vector.im" + static let serverConfigDefaultHomeserverUrlString = "" + static let serverConfigDefaultIdentityServerUrlString = "" - static let serverConfigSygnalAPIUrlString = "https://matrix.org/_matrix/push/v1/notify" + static let serverConfigPreSelections = ["":""] + static let serverConfigSygnalAPIUrlString = "" // MARK: - Legal URLs // Note: Set empty strings to hide the related entry in application settings static let applicationCopyrightUrlString = "https://element.io/copyright" - static let applicationPrivacyPolicyUrlString = "https://element.io/privacy" + static let applicationPrivacyPolicyUrlString = "" static let applicationTermsConditionsUrlString = "https://element.io/terms-of-service" - static let applicationHelpUrlString = - "https://element.io/help" - - - // MARK: - Permalinks - // Hosts/Paths for URLs that will considered as valid permalinks. Those permalinks are opened within the app. - static let permalinkSupportedHosts: [String: [String]] = [ - "app.element.io": [], - "staging.element.io": [], - "develop.element.io": [], - "mobile.element.io": [""], - // Historical ones - "riot.im": ["/app", "/staging", "/develop"], - "www.riot.im": ["/app", "/staging", "/develop"], - "vector.im": ["/app", "/staging", "/develop"], - "www.vector.im": ["/app", "/staging", "/develop"], - // Official Matrix ones - "matrix.to": ["/"], - "www.matrix.to": ["/"], - // Client Permalinks (for use with `BuildSettings.clientPermalinkBaseUrl`) -// "example.com": ["/"], -// "www.example.com": ["/"], - ] - + static let applicationHelpUrlString = "https://element.io/help" + + // MARk: - Matrix permalinks + // Paths for URLs that will considered as Matrix permalinks. Those permalinks are opened within the app + static let permalinkSupportedHosts: [String: [String]] = [:] + // For use in clients that use a custom base url for permalinks rather than matrix.to. // This baseURL is used to generate permalinks within the app (E.g. timeline message permalinks). // Optional String that when set is used as permalink base, when nil matrix.to format is used. @@ -138,12 +125,13 @@ final class BuildSettings: NSObject { // MARK: - VoIP static var allowVoIPUsage: Bool { #if canImport(JitsiMeetSDK) - return true + return false #else return false #endif } - static let stunServerFallbackUrlString: String? = "stun:turn.matrix.org" + + static let stunServerFallbackUrlString: String? = "" // MARK: - Public rooms Directory #warning("Unused build setting: should this be implemented in ShowDirectory?") @@ -158,24 +146,20 @@ final class BuildSettings: NSObject { static let roomsAllowToJoinPublicRooms: Bool = true // MARK: - Analytics - #if DEBUG - /// Host to use for PostHog analytics during development. Set to nil to disable analytics in debug builds. - static let analyticsHost: String? = "https://posthog-poc.lab.element.dev" - /// Public key for submitting analytics during development. Set to nil to disable analytics in debug builds. - static let analyticsKey: String? = "rs-pJjsYJTuAkXJfhaMmPUNBhWliDyTKLOOxike6ck8" - #else - /// Host to use for PostHog analytics. Set to nil to disable analytics. - static let analyticsHost: String? = "https://posthog.hss.element.io" - /// Public key for submitting analytics. Set to nil to disable analytics. - static let analyticsKey: String? = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO" - #endif + static let analyticsServerUrl = URL(string: "") + static let analyticsAppId = "0" - /// The URL to open with more information about analytics terms. + /// BWI: set host and key to nil to disable PostHog tracking + static let analyticsHost: String? = nil + static let analyticsKey: String? = nil static let analyticsTermsURL = URL(string: "https://element.io/cookie-policy")! + static let bwiPresentAnalyticsPrompt = false + static let bwiAnalyticsServerUrlString = "" + static let bwiAnalyticsAppId = "1" // MARK: - Bug report - static let bugReportEndpointUrlString = "https://riot.im/bugreports" + static let bugReportEndpointUrlString = "" // Use the name allocated by the bug report server static let bugReportApplicationId = "riot-ios" @@ -184,36 +168,23 @@ final class BuildSettings: NSObject { static let integrationsUiUrlString = "https://scalar.vector.im/" static let integrationsRestApiUrlString = "https://scalar.vector.im/api" // Widgets in those paths require a scalar token - static let integrationsScalarWidgetsPaths = [ - "https://scalar.vector.im/_matrix/integrations/v1", - "https://scalar.vector.im/api", - "https://scalar-staging.vector.im/_matrix/integrations/v1", - "https://scalar-staging.vector.im/api", - "https://scalar-staging.riot.im/scalar/api", - ] + static let integrationsScalarWidgetsPaths = [""] // Jitsi server used outside integrations to create conference calls from the call button in the timeline - static let jitsiServerUrl: URL = URL(string: "https://jitsi.riot.im")! - + static let jitsiServerUrl: URL = URL(string: "https://enter.jitsi.url")! // MARK: - Features - /// Setting to force protection by pin code - static let forcePinProtection: Bool = false - /// Max allowed time to continue using the app without prompting PIN - static let pinCodeGraceTimeInSeconds: TimeInterval = 0 + static let pinCodeGraceTimeInSeconds: TimeInterval = 180 - /// Force non-jailbroken app usage - static let forceNonJailbrokenUsage: Bool = true - static let allowSendingStickers: Bool = true - - static let allowLocalContactsAccess: Bool = true + static let allowSendingStickers: Bool = false + static let allowLocalContactsAccess: Bool = false + static let allowInviteExernalUsers: Bool = true - // MARK: - Side Menu - static let enableSideMenu: Bool = true + static let enableSideMenu: Bool = false static let sideMenuShowInviteFriends: Bool = true /// Whether to read the `io.element.functional_members` state event and exclude any service members when computing a room's name and avatar. @@ -222,7 +193,32 @@ final class BuildSettings: NSObject { // MARK: - Feature Specifics /// Not allowed pin codes. User won't be able to select one of the pin in the list. - static let notAllowedPINs: [String] = [] + static let notAllowedPINs: [String] = [ + "1234", + "1111", + "0000", + "1212", + "7777", + "1004", + "2000", + "4444", + "2222", + "6969", + "9999", + "3333", + "5555", + "6666", + "1122", + "1313", + "8888", + "4321", + "2001", + "1010", + "2580", + "4711", + "0815", + "0852" + ] /// Maximum number of allowed pin failures when unlocking, before force logging out the user. Defaults to `3` static let maxAllowedNumberOfPinFailures: Int = 3 @@ -230,55 +226,86 @@ final class BuildSettings: NSObject { /// Maximum number of allowed biometrics failures when unlocking, before fallbacking the user to the pin if set or logging out the user. Defaults to `5` static let maxAllowedNumberOfBiometricsFailures: Int = 5 + static let allowLocalContactPresence: Bool = false + /// Indicates should the app log out the user when number of PIN failures reaches `maxAllowedNumberOfPinFailures`. Defaults to `false` - static let logOutUserWhenPINFailuresExceeded: Bool = false + static let logOutUserWhenPINFailuresExceeded: Bool = true /// Indicates should the app log out the user when number of biometrics failures reaches `maxAllowedNumberOfBiometricsFailures`. Defaults to `false` - static let logOutUserWhenBiometricsFailuresExceeded: Bool = false + static let logOutUserWhenBiometricsFailuresExceeded: Bool = true + + /// If force is enabled the dialog for asking if reseting the key backup is not shown.. already asked before in mandatory verfication + static let forceResetBackupIfLost: Bool = true + + /// if skip is enabled the key backup dialogs are passed through + static let skipKeyBackupStep: Bool = true + static let showNotificationsV2: Bool = true // MARK: - Main Tabs + static let homeScreenShowHomeTab: Bool = false static let homeScreenShowFavouritesTab: Bool = true static let homeScreenShowPeopleTab: Bool = true static let homeScreenShowRoomsTab: Bool = true - static let homeScreenShowCommunitiesTab: Bool = true + static let homeScreenShowCommunitiesTab: Bool = false // MARK: - General Settings Screen static let settingsScreenShowUserFirstName: Bool = false static let settingsScreenShowUserSurname: Bool = false - static let settingsScreenAllowAddingEmailThreepids: Bool = true - static let settingsScreenAllowAddingPhoneThreepids: Bool = true - static let settingsScreenShowThreepidExplanatory: Bool = true - static let settingsScreenShowDiscoverySettings: Bool = true - static let settingsScreenAllowIdentityServerConfig: Bool = true - static let settingsScreenShowConfirmMediaSize: Bool = true - static let settingsScreenShowAdvancedSettings: Bool = true - static let settingsScreenShowLabSettings: Bool = true - static let settingsScreenAllowChangingRageshakeSettings: Bool = true - static let settingsScreenAllowChangingCrashUsageDataSettings: Bool = true - static let settingsScreenAllowBugReportingManually: Bool = true - static let settingsScreenAllowDeactivatingAccount: Bool = true + static let settingsScreenAllowAddingLinkedEmails: Bool = false + static let settingsScreenAllowAddingPhoneNumbers: Bool = false + static let settingsScreenAllowAddingEmailThreepids: Bool = false + static let settingsScreenAllowAddingPhoneThreepids: Bool = false + static let settingsScreenShowThreepidExplanatory: Bool = false + static let settingsScreenShowDiscoverySettings: Bool = false + static let settingsScreenAllowIdentityServerConfig: Bool = false + static let settingsScreenShowConfirmMediaSize: Bool = false + static let settingsScreenShowAdvancedSettings: Bool = false + static let settingsScreenShowLabSettings: Bool = false + static let settingsScreenAllowChangingRageshakeSettings: Bool = false + static let settingsScreenAllowChangingCrashUsageDataSettings: Bool = false + static let settingsScreenAllowBugReportingManually: Bool = false + static let settingsScreenAllowDeactivatingAccount: Bool = false + static let settingsScreenEnableDecodedContentAndGlobalSettings: Bool = false static let settingsScreenShowChangePassword:Bool = true + + static let settingsScreenShowLinkPreviews:Bool = false + static let settingsScreenShowInviteFriends:Bool = false + static let settingsScreenShowSettings:Bool = true + static let settingsScreenShowFeedback:Bool = false + static let settingsScreenShowHelp:Bool = false + static let settingsScreenShowEnableStunServerFallback: Bool = true - static let settingsScreenShowNotificationDecodedContentOption: Bool = true - static let settingsScreenShowNsfwRoomsOption: Bool = true + static let settingsScreenShowNotificationDecodedContentOption: Bool = false + static let settingsScreenShowSystemSettingsOption: Bool = false + static let settingsScreenShowNsfwRoomsOption: Bool = false + static let settingsScreenShowTACSetting: Bool = false + static let settingsScreenShowSupportSetting: Bool = true static let settingsSecurityScreenShowSessions:Bool = true static let settingsSecurityScreenShowSetupBackup:Bool = true static let settingsSecurityScreenShowRestoreBackup:Bool = true static let settingsSecurityScreenShowDeleteBackup:Bool = true - static let settingsSecurityScreenShowCryptographyInfo:Bool = true - static let settingsSecurityScreenShowCryptographyExport:Bool = true - static let settingsSecurityScreenShowAdvancedUnverifiedDevices:Bool = true + static let settingsSecurityScreenShowCryptographyInfo:Bool = false + static let settingsSecurityScreenShowCryptographyExport:Bool = false + + static let settingsSecurityScreenShowAdvancedUnverifiedDevices:Bool = false + + // MARK: - Notification Settings + static let settingsNotificationsBWIDefaultSet:Bool = true + static let settingsNotificationsShowDefault:Bool = true + static let settingsNotificationsShowMentions:Bool = false + static let settingsNotificationsShowAdvanced:Bool = false + // MARK: - Timeline settings - static let roomInputToolbarCompressionMode = MXKRoomInputToolbarCompressionModePrompt + static let roomInputToolbarCompressionMode = MXKRoomInputToolbarCompressionModeNone // MARK: - Room Creation Screen - static let roomCreationScreenAllowEncryptionConfiguration: Bool = true + static let roomCreationScreenAllowEncryptionConfiguration: Bool = false static let roomCreationScreenRoomIsEncrypted: Bool = true static let roomCreationScreenAllowRoomTypeConfiguration: Bool = true static let roomCreationScreenRoomIsPublic: Bool = false @@ -289,7 +316,7 @@ final class BuildSettings: NSObject { static let roomScreenAllowVoIPForNonDirectRoom: Bool = true static let roomScreenAllowCameraAction: Bool = true static let roomScreenAllowMediaLibraryAction: Bool = true - static let roomScreenAllowStickerAction: Bool = true + static let roomScreenAllowStickerAction: Bool = false static let roomScreenAllowFilesAction: Bool = true // Timeline style @@ -307,59 +334,171 @@ final class BuildSettings: NSObject { static let roomContextualMenuShowMoreOptionForMessages: Bool = true static let roomContextualMenuShowMoreOptionForStates: Bool = true static let roomContextualMenuShowReportContentOption: Bool = true - + static let roomContextualMenuShowEncryptionOption: Bool = false + // MARK: - Room Info Screen - static let roomInfoScreenShowIntegrations: Bool = true + static let roomInfoScreenShowIntegrations: Bool = false // MARK: - Room Settings Screen - static let roomSettingsScreenShowLowPriorityOption: Bool = true - static let roomSettingsScreenShowDirectChatOption: Bool = true - static let roomSettingsScreenAllowChangingAccessSettings: Bool = true - static let roomSettingsScreenAllowChangingHistorySettings: Bool = true - static let roomSettingsScreenShowAddressSettings: Bool = true - static let roomSettingsScreenShowFlairSettings: Bool = true - static let roomSettingsScreenShowAdvancedSettings: Bool = true + static let roomSettingsScreenShowLowPriorityOption: Bool = false + static let roomSettingsScreenShowDirectChatOption: Bool = false + static let roomSettingsScreenAllowChangingAccessSettings: Bool = false + static let roomSettingsScreenAllowChangingHistorySettings: Bool = false + static let roomSettingsScreenShowAddressSettings: Bool = false + static let roomSettingsScreenShowFlairSettings: Bool = false + static let roomSettingsScreenShowAdvancedSettings: Bool = false + + static let roomSettingsScreenShowAccessSettingsBW: Bool = true + static let roomSettingsScreenRemoveLeave: Bool = true + static let roomSettingsScreenShowIntegrations: Bool = false static let roomSettingsScreenAdvancedShowEncryptToVerifiedOption: Bool = true - // MARK: - Room Member Screen + static let roomSettingsScreenShowNotificationsV2: Bool = false + // MARK: - Room Member Screen static let roomMemberScreenShowIgnore: Bool = true // MARK: - Message - static let messageDetailsAllowShare: Bool = true - static let messageDetailsAllowPermalink: Bool = true - static let messageDetailsAllowViewSource: Bool = true - static let messageDetailsAllowSave: Bool = true - static let messageDetailsAllowCopyMedia: Bool = true - static let messageDetailsAllowPasteMedia: Bool = true + static let messageDetailsAllowShare: Bool = false + static let messageDetailsAllowPermalink: Bool = false + static let messageDetailsAllowViewSource: Bool = false + static let messageDetailsAllowSave: Bool = false + static let messageDetailsAllowCopyMedia: Bool = false + static let messageDetailsAllowPasteMedia: Bool = false // MARK: - Notifications static let decryptNotificationsByDefault: Bool = true - // MARK: - HTTP - /// Additional HTTP headers will be sent by all requests. Not recommended to use request-specific headers, like `Authorization`. - /// Empty dictionary by default. - static let httpAdditionalHeaders: [String: String] = [:] - - // MARK: - Authentication Screen - static let authScreenShowRegister = true - static let authScreenShowPhoneNumber = true - static let authScreenShowForgotPassword = true - static let authScreenShowCustomServerOptions = true - static let authScreenShowSocialLoginSection = true - + static let authScreenShowRegister = false + static let authScreenShowPhoneNumber = false + static let authScreenShowForgotPassword = false + static let authScreenShowCustomServerOptions = false + static let authScreenShowTestServerOptions = false + // MARK: - Authentication Options static let authEnableRefreshTokens = false - // MARK: - Unified Search - static let unifiedSearchScreenShowPublicDirectory = true + // MARK: - Cross-signing (bwi=true) + static let disableSelfUserVerification = true + + // MARK: - Antivirus scan (bwi=true) + static let enableAntivirusScan = true + + // MARK: - Room Screen + static let enableRoomSearchItem = false + + // MARK: - Matomo Analytics (bwi=false) + static let enableMatomoAnalytics = false + + // MARK: Verification screen (bwi=false) + static let showDetailedVerificationElements : Bool = false + static let showRecoverWithKey : Bool = false + + // MARK: Unified search screen (bwi=false) + static let showUnifiedSearchViewMessagesTab : Bool = false + static let showUnifiedSearchViewFilesTab : Bool = false // MARK: - Secrets Recovery static let secretsRecoveryAllowReset = true + // MARK: VoIP support (bwi=false) + static let enableVoIPSupport : Bool = false + + // MARK: Last message timestamp support (bwi=false) + static let enableLastMessageTimestamp : Bool = false + + // MARK: Room chat alert screen (bwi=false) + static let enableViewEncryptionAction : Bool = false + + // MARK: JavaScript support in WKWebView (bwi=false) + static let enableJSInWebView : Bool = false + + // MARK: Secure pin code description (bwi=false) + static let settingsSettingsSecureShowPinCodeDescription : Bool = false + + // MARK: Invite friends in Direct Chat (bwi=false) + static let directChatShowInviteFriends : Bool = false + + // MARK: Last admin is not allowed to leave the room (bwi=true) + static let lastAdminIsNotAllowedToLeaveRoom : Bool = true + + // MARK: Room Member Details Screen (bwi=true) + static let roomMemberDetailsHideLeaveButton : Bool = true + + // MARK: Rest time feature (bwi=false) + static let featureWorkTime : Bool = false + + // MARK: Room create options (bwi=false) + static let enableShowInRoomDirectory : Bool = false + + // MARK: sticker in composer (bwi=false) + static let enableRoomComposerSticker : Bool = false + + // Mark: Unified Search (bwi=true) + static let unifiedSearchScreenShowPublicDirectory = true + + // MARK: Allows removal of uploaded avatar photos (bwi=true) + static let enableRemoveAvatarImage = true + + // MARK: Add a toggle button to the login screen to make the password visible + static let passwordIndicatorOnLogin : Bool = true + + // MARK: Displays the element base version on the settings screen + static let elementBaseVersion : String = "1.8.1" + + static let showElementBaseVersion : Bool = true + + // MARK: Bypasses the normal forgot password process by presenting the user an information alert + // (requires authScreenShowForgotPassword set to true) + static let forgotPasswordInformationAlert : Bool = true + + // MARK: Promote new feature within a banner below the navigation view + static let showTopBanner : Bool = true + + static let showCustomServerDisplayName : Bool = true + static let customServerDisplayName : String = "" + + // MARK BWI show/hide developer menu + static let bwiShowDeveloperSettings : Bool = false + + // MARK BWI personal notes room + static let bwiPersonalNotesRoom : Bool = true + static let bwiPersonalNotesRoomLeavable : Bool = false + static let bwiResetPersonalNotesAccountData : Bool = false + + // MARK BWI personal notes room + static let bwiRollsAndRights : Bool = true + + // MARK: Timeline + static let settingsScreenShowSimpleTimeLineOptions : Bool = false + static let settingsScreenShowTimeStampOption : Bool = true + static let settingsScreenShowDeletedMessagesOption : Bool = false + static let settingsScreenShowNameChangeOption : Bool = false + static let settingsScreenShowChatEffectsOption : Bool = false + static let settingsScreenShowRoomAvatarChangeOption : Bool = false + static let settingsScreenShowUserAvatarChangeOption : Bool = true + static let settingsScreenShowEnterRoomOption : Bool = true + + static let bwiEnableVoiceMessages : Bool = true + + static let bwiLastAdminCanDowngradeHimself : Bool = false + + static let bwiEnableErrorTracking : Bool = false + + static let bwiEnableRegisterInfo : Bool = false + + static let bwiShowHappyBirthdayCampaign: Bool = false + static let bwiHappyBirthdayCampaignIdentifier: String = "one_year_anniversary" + static let bwiDisableSecuritySettingsUntrustedDevices: Bool = true + static let bwiShowMatomoInfoScreen = false + static let bwiShowMatomoSettings = false + static let bwiMatomoTrackingDefaultState = true + static let bwiShowNewFeatures = true + static let bwiSendMessageThreshold = 10.0 + // MARK: - Polls static var pollsEnabled: Bool { @@ -367,18 +506,13 @@ final class BuildSettings: NSObject { return false } - return true + return false } // MARK: - Location Sharing - - static let tileServerMapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx")! - - static var locationSharingEnabled: Bool { - guard #available(iOS 14, *) else { - return false - } - - return true - } + + static let tileServerMapURL = "" + static let tileServerMapStyleURL = URL(string: "")! + + static let locationSharingEnabled = false } diff --git a/Config/BwiSettings.swift b/Config/BwiSettings.swift new file mode 100644 index 000000000..f488e0037 --- /dev/null +++ b/Config/BwiSettings.swift @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation +import MatrixSDK +import KeychainAccess + +@objcMembers +final class BwiSettings : NSObject { + static let shared = BwiSettings() + + private enum UserDefaultsKeys { + static let additionalBwiHeaderKey = "bwiHeader" + } + + private struct BwiSettingsConstants { + static let bwiSettingsKeychainService: String = BuildSettings.baseBundleIdentifier + ".bwi-settings-service" + } + + private let vault: KeyValueVault + + override init() { + vault = KeychainVault(Keychain(service: BwiSettingsConstants.bwiSettingsKeychainService, + accessGroup: BuildSettings.keychainAccessGroup)) + super.init() + } + + func reset() { + additionalBwiHeader = UserAgentService().bwiUserAgentDict + let sdkOptions = MXSDKOptions.sharedInstance() + sdkOptions.httpAdditionalHeaders = UserAgentService().bwiUserAgentDict + } + + // MARK: Servers + + var additionalBwiHeader: Dictionary { + get { + do { + guard let data = try vault.data(forKey: UserDefaultsKeys.additionalBwiHeaderKey) else { + return UserAgentService().bwiUserAgentDict + } + var dict = try JSONDecoder().decode(Dictionary.self, from: data) + dict["User-Agent"] = UserAgentService().bwiUserAgent + return dict + } catch { + return UserAgentService().bwiUserAgentDict + } + } set { + do { + let data = try JSONEncoder().encode(newValue) + try vault.set(data, forKey: UserDefaultsKeys.additionalBwiHeaderKey) + } catch let error { + NSLog("[PinCodePreferences] Error when storing addional header to the vault: \(error)") + } + } + } +} diff --git a/Config/CommonConfiguration.swift b/Config/CommonConfiguration.swift index 9567a696d..67275c353 100644 --- a/Config/CommonConfiguration.swift +++ b/Config/CommonConfiguration.swift @@ -1,5 +1,6 @@ // // Copyright 2020 Vector Creations Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -69,8 +70,11 @@ class CommonConfiguration: NSObject, Configurable { // Disable identicon use sdkOptions.disableIdenticonUseForUserAvatar = true + // Use UIKit BackgroundTask for handling background tasks in the SDK + sdkOptions.backgroundModeHandler = MXUIKitBackgroundModeHandler() + // Pass httpAdditionalHeaders to the SDK - sdkOptions.httpAdditionalHeaders = BuildSettings.httpAdditionalHeaders + sdkOptions.httpAdditionalHeaders = UserAgentService().bwiUserAgentDict // Disable key backup on common sdkOptions.enableKeyBackupWhenStartingMXCrypto = false @@ -81,8 +85,10 @@ class CommonConfiguration: NSObject, Configurable { sdkOptions.clientPermalinkBaseUrl = BuildSettings.clientPermalinkBaseUrl sdkOptions.authEnableRefreshTokens = BuildSettings.authEnableRefreshTokens + // Configure key provider delegate MXKeyProvider.sharedInstance().delegate = EncryptionKeyManager.shared + EncryptionKeyManager.shared.initKeys() } diff --git a/Config/GlobalLogging.swift b/Config/GlobalLogging.swift new file mode 100644 index 000000000..de65f88cc --- /dev/null +++ b/Config/GlobalLogging.swift @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +#if !DEBUG +public func NSLog(_ format: String, _ args: CVarArg...) { +} + +public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") { +} +#endif diff --git a/Config/PrefixHeader.pch b/Config/PrefixHeader.pch new file mode 100644 index 000000000..5b8c025ee --- /dev/null +++ b/Config/PrefixHeader.pch @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PrefixHeader_pch +#define PrefixHeader_pch + +// Include any system framework and library headers here that should be included in all compilation units. +// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. + +// Disable logs +#ifndef DEBUG + #define NSLog(...) +#endif + +#endif /* PrefixHeader_pch */ diff --git a/Gemfile b/Gemfile index 53efbaf92..ea061a17e 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source "https://rubygems.org" gem "xcode-install" gem "fastlane" gem "cocoapods", '~>1.11.2' +gem "cocoapods-keys" plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index 610cbbadd..b7a3e484d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,9 @@ GEM specs: CFPropertyList (3.0.5) rexml + RubyInline (3.12.5) + ZenTest (~> 4.3) + ZenTest (4.12.1) activesupport (6.1.4.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) @@ -64,6 +67,9 @@ GEM typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) cocoapods-downloader (1.5.1) + cocoapods-keys (2.2.1) + dotenv + osx_keychain cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -225,6 +231,8 @@ GEM netrc (0.11.0) optparse (0.1.1) os (1.1.4) + osx_keychain (1.0.2) + RubyInline (~> 3) plist (3.6.0) public_suffix (4.0.6) rake (13.0.6) @@ -292,6 +300,7 @@ PLATFORMS DEPENDENCIES cocoapods (~> 1.11.2) + cocoapods-keys fastlane fastlane-plugin-diawi fastlane-plugin-versioning diff --git a/IDETemplateMacros.plist b/IDETemplateMacros.plist index 5a3026b83..a151853b0 100644 --- a/IDETemplateMacros.plist +++ b/IDETemplateMacros.plist @@ -3,20 +3,21 @@ FILEHEADER - -// Copyright 2021 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// + +/* + * Copyright (c) 2022 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ - \ No newline at end of file + diff --git a/Podfile b/Podfile index 523f61fc5..0fd3f25eb 100644 --- a/Podfile +++ b/Podfile @@ -40,6 +40,9 @@ when String # specific MatrixSDK released version $matrixSDKVersionSpec = $matrixSDKVersion end + +$matrixSDKVersionSpec = { :git => 'https://dl-gitlab.example.com/bwmessenger/ios-matrix-sdk', :tag => 'v0.22.1.1_bwi' } + # Method to import the MatrixSDK def import_MatrixSDK pod 'MatrixSDK', $matrixSDKVersionSpec @@ -66,10 +69,18 @@ abstract_target 'RiotPods' do pod 'Reusable', '~> 4.1' pod 'KeychainAccess', '~> 4.2.2' pod 'WeakDictionary', '~> 2.0' + + # Piwik for analytics + pod 'MatomoTracker', '~> 7.4.1' # PostHog for analytics pod 'PostHog', '~> 1.4.4' - pod 'AnalyticsEvents', :git => 'https://github.com/matrix-org/matrix-analytics-events.git', :branch => 'release/swift' + pod 'AnalyticsEvents', :git => 'https://github.com/matrix-org/matrix-analytics-events.git', :commit => '264e549b27ee4e1c53c0d69fafd8f401c36fffd4' + # pod 'AnalyticsEvents', :path => '../matrix-analytics-events/AnalyticsEvents.podspec' + + # PostHog for analytics + pod 'PostHog', '~> 1.4.4' + pod 'AnalyticsEvents', :git => 'https://github.com/matrix-org/matrix-analytics-events.git', :commit => '264e549b27ee4e1c53c0d69fafd8f401c36fffd4' # pod 'AnalyticsEvents', :path => '../matrix-analytics-events/AnalyticsEvents.podspec' # Remove warnings from "bad" pods @@ -91,6 +102,7 @@ abstract_target 'RiotPods' do pod 'KTCenterFlowLayout', '~> 1.3.1' pod 'ZXingObjC', '~> 3.6.5' pod 'FlowCommoniOS', '~> 1.12.0' + pod 'DTTJailbreakDetection', '~> 0.4.0' pod 'ReadMoreTextView', '~> 3.0.1' pod 'SwiftBase32', '~> 0.9.0' pod 'SwiftJWT', '~> 3.6.200' @@ -103,6 +115,32 @@ abstract_target 'RiotPods' do target 'RiotTests' do inherit! :search_paths end + +# target 'RiotUITests' do +# inherit! :search_paths +# end + end + + target "BundesMessenger" do + import_MatrixSDK + import_MatrixKit_pods + + import_SwiftUI_pods + + pod 'DGCollectionViewLeftAlignFlowLayout', '~> 1.0.4' + pod 'UICollectionViewRightAlignedLayout', '~> 0.0.3' + pod 'KTCenterFlowLayout', '~> 1.3.1' + pod 'ZXingObjC', '~> 3.6.5' + pod 'FlowCommoniOS', '~> 1.12.0' + pod 'DTTJailbreakDetection', '~> 0.4.0' + pod 'ReadMoreTextView', '~> 3.0.1' + pod 'SwiftBase32', '~> 0.9.0' + pod 'SwiftJWT', '~> 3.6.200' + pod 'SideMenu', '~> 6.5' + pod 'DSWaveformImage', '~> 6.1.1' + pod 'ffmpeg-kit-ios-audio', '4.5.1' + + pod 'FLEX', '~> 4.5.0', :configurations => ['Debug'] end target "RiotShareExtension" do @@ -118,11 +156,6 @@ abstract_target 'RiotPods' do import_SwiftUI_pods end - target "SiriIntents" do - import_MatrixSDK - import_MatrixKit_pods - end - target "RiotNSE" do import_MatrixSDK import_MatrixKit_pods @@ -151,4 +184,16 @@ post_install do |installer| config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' end end -end \ No newline at end of file + + Dir.glob("#{installer.sandbox.target_support_files_root}/**/*.pch") do |item| + open(item, "a") do |file| + print "[post_install] Updating #{item}\n" + file.puts < 1.1) - Logging (1.4.0) + - MatomoTracker (7.4.1): + - MatomoTracker/Core (= 7.4.1) + - MatomoTracker/Core (7.4.1) - MatrixSDK (0.22.1): - MatrixSDK/Core (= 0.22.1) - MatrixSDK/Core (0.22.1): @@ -103,11 +107,12 @@ PODS: - ZXingObjC/All (3.6.5) DEPENDENCIES: - - AnalyticsEvents (from `https://github.com/matrix-org/matrix-analytics-events.git`, branch `release/swift`) + - AnalyticsEvents (from `https://github.com/matrix-org/matrix-analytics-events.git`, commit `264e549b27ee4e1c53c0d69fafd8f401c36fffd4`) - DGCollectionViewLeftAlignFlowLayout (~> 1.0.4) - Down (~> 0.11.0) - DSWaveformImage (~> 6.1.1) - DTCoreText (~> 1.6.25) + - DTTJailbreakDetection (~> 0.4.0) - ffmpeg-kit-ios-audio (= 4.5.1) - FLEX (~> 4.5.0) - FlowCommoniOS (~> 1.12.0) @@ -117,8 +122,9 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.22.1) - - MatrixSDK/JingleCallStack (= 0.22.1) + - MatomoTracker (~> 7.4.1) + - MatrixSDK (from `https://dl-gitlab.example.com/bwmessenger/ios-matrix-sdk`, tag `v0.22.1.1_bwi`) + - MatrixSDK/JingleCallStack (from `https://dl-gitlab.example.com/bwmessenger/ios-matrix-sdk`, tag `v0.22.1.1_bwi`) - OLMKit - PostHog (~> 1.4.4) - ReadMoreTextView (~> 3.0.1) @@ -144,6 +150,7 @@ SPEC REPOS: - DSWaveformImage - DTCoreText - DTFoundation + - DTTJailbreakDetection - ffmpeg-kit-ios-audio - FLEX - FlowCommoniOS @@ -159,7 +166,7 @@ SPEC REPOS: - libPhoneNumber-iOS - LoggerAPI - Logging - - MatrixSDK + - MatomoTracker - OLMKit - PostHog - ReadMoreTextView @@ -178,13 +185,19 @@ SPEC REPOS: EXTERNAL SOURCES: AnalyticsEvents: - :branch: release/swift + :commit: 264e549b27ee4e1c53c0d69fafd8f401c36fffd4 :git: https://github.com/matrix-org/matrix-analytics-events.git + MatrixSDK: + :git: https://dl-gitlab.example.com/bwmessenger/ios-matrix-sdk + :tag: v0.22.1.1_bwi CHECKOUT OPTIONS: AnalyticsEvents: - :commit: 8058dc6ec07ce0acfe5fdb19eb7e309b0c13845c + :commit: 264e549b27ee4e1c53c0d69fafd8f401c36fffd4 :git: https://github.com/matrix-org/matrix-analytics-events.git + MatrixSDK: + :git: https://dl-gitlab.example.com/bwmessenger/ios-matrix-sdk + :tag: v0.22.1.1_bwi SPEC CHECKSUMS: AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce @@ -197,13 +210,14 @@ SPEC CHECKSUMS: DSWaveformImage: 3c718a0cf99291887ee70d1d0c18d80101d3d9ce DTCoreText: ec749e013f2e1f76de5e7c7634642e600a7467ce DTFoundation: a53f8cda2489208cbc71c648be177f902ee17536 + DTTJailbreakDetection: 5e356c5badc17995f65a83ed9483f787a0057b71 ffmpeg-kit-ios-audio: 662ce2064e56733ca7d8216705efbc38d9e1c3fe FLEX: e51461dd6f0bfb00643c262acdfea5d5d12c596b FlowCommoniOS: ca92071ab526dc89905495a37844fd7e78d1a7f2 GBDeviceInfo: ed0db16230d2fa280e1cbb39a5a7f60f6946aaec GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3 HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19 - Introspect: 2be020f30f084ada52bb4387fff83fa52c5c400e + Introspect: b62c4dd2063072327c21d618ef2bedc3c87bc366 JitsiMeetSDK: 2f118fa770f23e518f3560fc224fae3ac7062223 KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51 KituraContracts: e845e60dc8627ad0a76fa55ef20a45451d8f830b @@ -212,6 +226,7 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b + MatomoTracker: 24a846c9d3aa76933183fe9d47fd62c9efa863fb MatrixSDK: 12c1a56e037f629e493cbcd615fd13cfc58cee3a OLMKit: 9fb4799c4a044dd2c06bda31ec31a12191ad30b5 PostHog: 4b6321b521569092d4ef3a02238d9435dbaeb99f @@ -229,6 +244,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: ae70a46e98aae87f130ad3d246711fc6b6ae7286 +PODFILE CHECKSUM: f244882794bea4100efbe62c723a95c31e6d4b12 COCOAPODS: 1.11.2 diff --git a/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme b/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme index a9bea1d96..84ecb908a 100644 --- a/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme +++ b/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme @@ -4,8 +4,7 @@ version = "1.3"> + buildImplicitDependencies = "YES"> - + - + + @@ -18,21 +19,7 @@ - - - - - - - - - - - - - - - + @@ -41,6 +28,8 @@ - + + + diff --git a/Riot/Assets/Base.lproj/Main.storyboard.orig b/Riot/Assets/Base.lproj/Main.storyboard.orig new file mode 100644 index 000000000..82b1dfd26 --- /dev/null +++ b/Riot/Assets/Base.lproj/Main.storyboard.orig @@ -0,0 +1,795 @@ + + + + + + +<<<<<<< HEAD + +======= +>>>>>>> 9792605e19cb683f1991f354dbc575b2b1e910bb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<<<<<<< HEAD + + +======= +>>>>>>> 9792605e19cb683f1991f354dbc575b2b1e910bb + + + + + + + + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/BWI/Contents.json b/Riot/Assets/Images.xcassets/BWI/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/birthday_cake.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/birthday_cake.imageset/Contents.json new file mode 100644 index 000000000..b56f90c52 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/birthday_cake.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "undraw_Birthday_cake_BwM.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/birthday_cake.imageset/undraw_Birthday_cake_BwM.svg b/Riot/Assets/Images.xcassets/BWI/birthday_cake.imageset/undraw_Birthday_cake_BwM.svg new file mode 100644 index 000000000..6e7859edb --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/birthday_cake.imageset/undraw_Birthday_cake_BwM.svg @@ -0,0 +1,758 @@ + + + + + + image/svg+xml + + Birthday cake + + + + + + Birthday cake + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/Contents.json new file mode 100644 index 000000000..876a62d3c --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "file_attachment_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "file_attachment_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "file_attachment_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/file_attachment_icon.png b/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/file_attachment_icon.png new file mode 100644 index 000000000..19adef549 Binary files /dev/null and b/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/file_attachment_icon.png differ diff --git a/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/file_attachment_icon@2x.png b/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/file_attachment_icon@2x.png new file mode 100644 index 000000000..2f00fc500 Binary files /dev/null and b/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/file_attachment_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/file_attachment_icon@3x.png b/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/file_attachment_icon@3x.png new file mode 100644 index 000000000..fa4b5986f Binary files /dev/null and b/Riot/Assets/Images.xcassets/BWI/file_attachment_icon.imageset/file_attachment_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/Contents.json new file mode 100644 index 000000000..a33bdd4a0 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "file_scan_infected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "file_scan_infected@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "file_scan_infected@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/file_scan_infected.png b/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/file_scan_infected.png new file mode 100644 index 000000000..b636b5f75 Binary files /dev/null and b/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/file_scan_infected.png differ diff --git a/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/file_scan_infected@2x.png b/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/file_scan_infected@2x.png new file mode 100644 index 000000000..d4e46cab8 Binary files /dev/null and b/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/file_scan_infected@2x.png differ diff --git a/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/file_scan_infected@3x.png b/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/file_scan_infected@3x.png new file mode 100644 index 000000000..605f10aa5 Binary files /dev/null and b/Riot/Assets/Images.xcassets/BWI/file_scan_infected.imageset/file_scan_infected@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Common/attachment_gallery.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/attachment_gallery.imageset/Contents.json new file mode 100644 index 000000000..7e84c8dfd --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/attachment_gallery.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "attachment_gallery.pdf" + } + ], + "info": { + "version": 1, + "author": "xcode", + "generatedBy": "Respresso" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Common/attachment_gallery.imageset/attachment_gallery.pdf b/Riot/Assets/Images.xcassets/Common/attachment_gallery.imageset/attachment_gallery.pdf new file mode 100644 index 000000000..c6d7b89e3 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/attachment_gallery.imageset/attachment_gallery.pdf @@ -0,0 +1,52 @@ +%PDF-1.4 +%Σλια +1 0 obj +<> +endobj +3 0 obj +<> +endobj +4 0 obj +<> stream +xœUNIΒ0 Όϋ>#aβ,₯yAΟpα”C‹DωΏΔ8©h±'γq*>δμGΩQΉΜτ&Τ΅³άιvΰn£h‡8W߁IΩς:pk–‘NCΰρC4σl˜x2’…²wjzΰB u’M –;–0S‡1΄Z·MΆ₯t ²ίΎ΅…TθI½$Φ€‚ΏΖe'Im‰‡I£i΅Λ¬=05ψy&,xΠωΈ>y +endstream +endobj +2 0 obj +<>>> +/MediaBox [0 0 24 24] +/Contents 4 0 R +/StructParents 0 +/Parent 5 0 R>> +endobj +5 0 obj +<> +endobj +6 0 obj +<> +endobj +xref +0 7 +0000000000 65535 f +0000000015 00000 n +0000000432 00000 n +0000000154 00000 n +0000000191 00000 n +0000000618 00000 n +0000000673 00000 n +trailer +<> +startxref +720 +%%EOF \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/Contents.json new file mode 100644 index 000000000..e774b0425 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "hide_password_button.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "hide_password_button@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "hide_password_button@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/hide_password_button.png b/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/hide_password_button.png new file mode 100644 index 000000000..b3a522dea Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/hide_password_button.png differ diff --git a/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/hide_password_button@2x.png b/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/hide_password_button@2x.png new file mode 100644 index 000000000..8fd600965 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/hide_password_button@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/hide_password_button@3x.png b/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/hide_password_button@3x.png new file mode 100644 index 000000000..e12636649 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/hide_password_button.imageset/hide_password_button@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Common/lock.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/lock.imageset/Contents.json new file mode 100755 index 000000000..a3ea0c801 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/lock.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "lock.pdf" + } + ], + "info": { + "version": 1, + "author": "xcode", + "generatedBy": "Respresso" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Common/lock.imageset/lock.pdf b/Riot/Assets/Images.xcassets/Common/lock.imageset/lock.pdf new file mode 100755 index 000000000..1b92d2a03 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/lock.imageset/lock.pdf @@ -0,0 +1,65 @@ +%PDF-1.4 +%Σλια +1 0 obj +<> +endobj +3 0 obj +<> +endobj +4 0 obj +<> +endobj +5 0 obj +<> stream +xœmPAnΓ0 »λ>¨fΙNΏ ηm‡= ΨΊC:`݁QvR«ΐlah’VȚj[!bŸΨ}jλ•~Θ$φηφAοOαlf™±Jσ=|ΐ$Αφλ9tp»Πσ9…Λ/%ŽX–/Κ‹α¨ακiaΙ pAhΚIMPοΩ„D·’,»ί±Iεyš=§rθά5ŽT—ιθΚ₯w²9([ϊq‹κ‘Σ n₯1ω`78ΨQΖq‡«§ŽΔ»άθ‹>ΡvΆΆ ϊšPoA—Zc5Α‚€Žg"ν… +ShmbΕykΖή+=κb 69Pβ²;œξΤίpHGρΰίcΧ}rϋ™7zΑώAγƒΗ +endstream +endobj +2 0 obj +<>>> +/MediaBox [0 0 24 24] +/Contents 5 0 R +/StructParents 0 +/Parent 6 0 R>> +endobj +6 0 obj +<> +endobj +7 0 obj +<> +endobj +xref +0 8 +0000000000 65535 f +0000000015 00000 n +0000000599 00000 n +0000000154 00000 n +0000000191 00000 n +0000000267 00000 n +0000000795 00000 n +0000000850 00000 n +trailer +<> +startxref +897 +%%EOF \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Common/message_circle.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/message_circle.imageset/Contents.json new file mode 100644 index 000000000..b40b2b54a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/message_circle.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "message_circle.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Common/message_circle.imageset/message_circle.pdf b/Riot/Assets/Images.xcassets/Common/message_circle.imageset/message_circle.pdf new file mode 100644 index 000000000..8df1bef48 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/message_circle.imageset/message_circle.pdf differ diff --git a/Riot/Assets/Images.xcassets/Common/powered_by_BWI_blk.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/powered_by_BWI_blk.imageset/Contents.json new file mode 100644 index 000000000..35095ea25 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/powered_by_BWI_blk.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "powered_by_BWI_blk.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Common/powered_by_BWI_blk.imageset/powered_by_BWI_blk.png b/Riot/Assets/Images.xcassets/Common/powered_by_BWI_blk.imageset/powered_by_BWI_blk.png new file mode 100644 index 000000000..7918e82ff Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/powered_by_BWI_blk.imageset/powered_by_BWI_blk.png differ diff --git a/Riot/Assets/Images.xcassets/Common/powered_by_BWI_gry.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/powered_by_BWI_gry.imageset/Contents.json new file mode 100644 index 000000000..d4ba93874 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/powered_by_BWI_gry.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "powered_by_BWI_gry.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Common/powered_by_BWI_gry.imageset/powered_by_BWI_gry.png b/Riot/Assets/Images.xcassets/Common/powered_by_BWI_gry.imageset/powered_by_BWI_gry.png new file mode 100644 index 000000000..6b54b99c9 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/powered_by_BWI_gry.imageset/powered_by_BWI_gry.png differ diff --git a/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button.png b/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button.png index b1c176d8e..f0e328d3d 100644 Binary files a/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button.png and b/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button.png differ diff --git a/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button@2x.png b/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button@2x.png index 25c0f9e44..406eaa875 100644 Binary files a/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button@2x.png and b/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button@3x.png b/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button@3x.png index 1bb741161..a2c368b42 100644 Binary files a/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button@3x.png and b/Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Common/room_checkbox.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/room_checkbox.imageset/Contents.json new file mode 100644 index 000000000..712cf8bc2 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/room_checkbox.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "room_checkbox.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Common/room_checkbox.imageset/room_checkbox.pdf b/Riot/Assets/Images.xcassets/Common/room_checkbox.imageset/room_checkbox.pdf new file mode 100644 index 000000000..60d3bb3c1 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/room_checkbox.imageset/room_checkbox.pdf @@ -0,0 +1,1607 @@ +%PDF-1.5 %βγΟΣ +1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + room_checkbox + + + + + 2020-11-26T01:06:18-08:00 + 2020-11-26T01:06:18-08:00 + 2020-11-26T01:06:18-08:00 + Adobe Illustrator CS6 (Macintosh) + + + + 256 + 248 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgA+AEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FUHrGsaXo2l3Oq6rcx2enWcZlubmU0R EHc/qAG5OwxV8afmv/zlj5v1+9nsPJ0r6DoSMVS6joL6cdObSb+iD1Cp8Q7semKvDNR1XU9SuDc6 jeT3tw27TXEjyuSfFnLHFV2m6vq2l3AudMvZ7G4FCJraV4XBHT4kKnFXtv5Zf85a+d/L9zDZ+a2b zFotQskr8RfRL3ZJdvV8aSbn+YYq+xvK/mjQvNGh2uuaFdreabdryimSoNRsyspoVZTsyncYqmuK uxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV8W/8AOV/5xSeY vMLeS9HuP9wWjSU1B4z8NzerswJHVIPsgfz1O9FxV8+4q7FXYq7FXuv/ADid+Z1x5c88x+V7yY/o TzEwhSNj8MV8RSF1/wCMlPSNOpK+GKvt/FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXk3/OR35tr5A8lPDp8wXzLrIe30wA/FCtKS3P/ADzBAX/KI6gHFXwKzMzF mJZmNSTuSTirWKuxV2KuxVFaZqFxpupWmo2zcbizmjuIWGxDxMHU1+YxV+pWKuxV2KuxV2KuxV2K uxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVB6xq+naNpV3q2pTrbWFjE891O3RY4xyY+ /sO+Kvzn/Nf8xdR/MDzre+YLrlHbufR061JqILWMn00+ZqWbxYnFWH4q7FXYq7FXYqmXlrRZtc8x aXosAJl1K7gtE49azSBK/RyxV+oWKuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kux V2KuxV2KuxV8mf8AOYH5ufWblPy80iesFuyT6/Ih2aUUaG2qDuE2dx/Nx7qcVfL2KuxV2KuxV2Ku xV9E/wDOHv5aTat5ql87X0R/Rmh8orAsPhkvZE4kiuxEMbkn/KZfDFX2XirsVdirsVdirsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirAvzr/M+0/LvyPdauSr6rcVttHtm353LqaMy/yR j42+7qRir87729u768nvbyVri7uZGmuJ5Dyd5HJZmYnqSTXFVDFXYq7FXYq7FU/8i+TNX85+atP8 uaUlbm+kCtLSqxRDeSZ/8lFqffp1xV+jnk/yno/lLy1YeXtIi9Kx0+IRpWnJ26vI5HV5HJZvc4qn OKuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVZPPDbwSTzusUMSl5ZHIVV VRVmYnoAMVfnv+fX5qzfmJ54mvIHYaFp3K10WE7fugfimI/mmYcj7cR2xV5tirsVdirsVdirsVfc v/OLn5QDyd5UHmDVoOHmTXY1dlcUe2tD8UcO+6s+zv8AQD9nFXt+KuxV2KuxV2KuxV2KuxV2KuxV 2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kvm3/AJy8/Nz9F6SvkHSJqahqaCTWpEO8VofswbdGmIq3 +R7Pir49xV2KuxV2KuxV2KvcP+cXPygHnLzX/iDVoOflvQnVyriqXN4KNHDvsyp9t/oB2bFX3Jir sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirGPzJ8+6V5E8n3/mPUaMts vG1tqgNPcPtFEv8ArHr4LU9sVfnJ5i8war5i1y+1zVpjcajqErT3Ep/mbso7Ko+FR2ApiqW4q7FX Yq7FXYqnPk7ynq/m3zLp/l7SI/UvtQlEaH9lF6vI/gsaAs3sMVfo75F8maP5M8rWHlzSU42tlHxa QijyyneSV/8AKdqk/d0xVPsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi rsVfCX/OTn5uf4384fonTJuflvQWeG1ZDVLi46TXHuNuCewqPtYq8YxV2KuxV2KuxV2Kvtr/AJxT /KAeVvLP+K9Xg4+YNcjBt0cUe3sTRkX2aagdvbiNiDir3rFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXh/wDzlN+bn+D/ACn/AId0ubh5i1+NkDKaPb2Rqss23Rn3jT/Z EfZxV8N4q7FXYq7FXYq7FXsv/OM35Qnzz5vGqapBz8s6Gyy3YcfBcXH2orff7Q/ak/ydj9oYq+7w ABQbAdBirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiqVeavM2k+V/Lt /r+ry+lp+nRNNMwpyNNlRAaVd2IVR3JxV+cPn7zrq3nXzZqHmPVG/f3slY4QSUhhXaOFK/sou3v1 6nFWPYq7FXYq7FXYqmnlfy1q3mbzBY6DpEJn1DUJVhgTsK7s7HsqLVmPYDFX6Ofl35F0nyN5RsPL mmKDHapW4npRp523lmf3dvuFB0GKskxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kux V2KuxV2KuxV8Z/8AOW/5ufp3Xx5I0mauk6LJy1ORDtNfCoMZp1WAbf69f5Rir53xV2KuxV2KuxV2 Kvs//nEz8oP8P6AfOusW/HWdZjpp0bj4oLFqENv0af7X+pTxIxV9C4q7FXYq7FXYq7FULqeq6XpV lJfapeQ2NlEKy3NzIsUSj/KdyqjFXm+qf85Ofknp05gfzEtzIPtfVbe4nQf89EjKH6GxVrTf+cnv ySv5hCvmEW0hNF+s21zCp/2bR8B9LYq9J0zVdL1WyjvtLvIb6ylFYrm2kWWJh/kuhZTiqKxV2Kux V2KuxV2KuxV2KuxV2KuxV2KuxV5d/wA5C/myn5e+SJHs5APMWq8rbSE6lDT95cEHtErbf5RXtXFX 5/O7yO0kjF3clmZjUknckk4qtxV2KuxV2KuxV65/zjh+UTefvOa3OowF/LOjMs+pFh8Mz9Yrb35k Vf8AyQehIxV97qqqoVQFVRRVGwAHYYq3irsVdirsVdirAfzk/N3Rvy08tfpG6QXWqXZMelaby4ma Rac2ZgG4xxhgWPyHU4q+DvPX5jecPPGqtqPmPUJLp6kwWwJW3gU/swxD4VFO/U9yTirGsVdirK/y 9/M3zf5B1hNS8v3rRKWBu7FyWtrhR+zLHWh26MPiHYjFX3r+U/5paF+Y3lePWNO/cXcVItT05m5S W01Psk7ckalUbuPAggKs0xV2KuxV2KuxV2KuxV2KuxV2KuxVDanqVjpenXOpX8y29jZxPPczuaKk calmY/IDFX51fnB+ZV9+Yfne812blHYr/o+lWrf7ptUJ4A0/aapd/wDKPhTFWE4q7FXYq7FXYqmH l/QdV8wa3ZaLpMBudR1CVYLaFe7MepPZVG7E7Ab4q/Rr8sfy+0ryF5OsfLtgAzQr6l9dAUM9y4Hq yn5kUXwUAdsVZVirsVdirsVdirsVfnp/zkJ56uPN/wCaWr3Hqc9P0yVtN01AfhENsxUuP+MknJ/p xV5tirsVdirsVZj+VX5m63+XfmuDXNOJltzSLUbAsVS4tyfiQ9aMOqNTY+1QVX6G+VPNOi+avL1l r+izi406/jEkT7BlPRkcAni6MCrDscVTbFXYq7FXYq7FXYq7FXYq7FXYq+Vf+cwfzcqU/LvSJtvg uPMEiH5PDamn0SP/ALH3xV8q4q7FXYq7FXYq7FX2L/ziN+UH6I0g+fNZt6anqcZTRo5BvDaN9qYA 9Gn7H+T2Y4q+j8VdirsVdirsVdirsVflbJI8kjSOeTuSzMepJNScVW4q7FXYq7FXYq9e/wCcePzt uPy88w/UNSdpPKmqSKL+Pc/V5D8K3Ma+2wcDqvuBir7ytrm3uraK5tpVmtp0WSGaMhkdHHJWVhsQ Qag4qqYq7FXYq7FXYq7FXYq7FWEfnD+Zdj+Xnki71yXjJfv/AKPpNox/vrpweAIH7KAF39h4kYq/ OvUtRvtT1C51G/ma5vryV57q4c1Z5ZGLOx9yTiqGxV2KuxV2KuxV6l/zj3+Usv5hedY1u4ifLmlF bjWJNwHFf3dsCP2pipr4KGPWmKv0AiiiiiSKJFjijULHGoCqqqKAADYADFV2KuxV2KuxV2KuxV2K vzR/MryrceVPPmuaBNGYhZXci24Ipyt3POBx7NEynFWM4q7FXYq7FXYq7FX0z/zit+e36NuIPIHm W4/3H3D8dBvZD/cyuf8AeVyf91yE/uz+y23Qjiq+u8VdirsVdirsVdirsVWySRxRtJIwSNAWd2IC qoFSST0AxV+f3/OQf5sSfmF53lktJCfLulcrbR49wHWv7y4INN5mWv8AqhRiry7FXYq7FXYq7FUb o2j6lrOq2mk6ZA1zqF9KkFtAnVnc0A9vcnpir9Ffyl/LjTvy+8lWWgWvGS6A9bU7tRQz3Tgeo/8A qigVf8kDFWZYq7FXYq7FXYq7FXYq7FXz7/zlV+Stx5q0tPOGgwGXXdJhKX1qg+K5s1q1VHeSGpIH VlqOoUYq+LcVdirsVdirsVdirYJBqNiMVfa3/OMX56jzbpqeUvMNwP8AE2nx/wCh3Eh+K9t4x136 zRqPi7svxfzYq99xV2KuxV2KuxV2Kvnb/nLj83P0HoS+RtJmpqusx8tUkQ/FDZE09Oo6NOQQf8iv 8wxV8aYq7FXYq7FXYq7FX11/ziH+UBsbI/mDrNvS7vFMWgRSDeOA1ElzQ9DL9lP8mp6Nir6axV2K uxV2KuxV2KuxV2KuxV2Kvj3/AJyk/IY6LdT+evLNt/uHuX5a1ZRLtbTOf79AOkUhPxD9lvY/Cq+b cVdirsVdirsVdiqL0rVdR0jUrXU9NuHtb+zkWa1uIzRkkQ1Vhir9AfyN/OHTvzJ8rLcMUg8w2AWP WLFdqORRZowd/TkpUeBqvapVekYq7FXYq7FWO/mB530nyR5S1DzJqZrBZx/uoQaNNM20UK+7tt7D foMVfnF5p8y6t5n8w3+v6tL62oajM00zb8RX7KICTREWiqOwGKpVirsVdirsVdir0n8hvyouPzE8 7Q2kyMNA08rc61OKgekD8MIYUo8xHEdwKt2xV+g9vbwW1vFbW8aw28KLHDCgCoiIKKqqNgABQDFV TFXYq7FXYq7FXYq7FXYq7FXYqpXVrbXdtLa3UST206NHPBIodHRxRlZTUEEGhBxV8G/85C/khc/l 3r317TUaTynqch+oS7t9XkNWNrIxruACUJ+0vuDiryLFXYq7FXYq7FXYqyP8v/Pmu+RvNNp5h0aS k9ueM0DE+nPCx/eQyAdVYfcaEbgYq/Q/yB570Hzz5XtPMOiy8re4Wk0DEepBMv8AeQygdGU/eKEb EYqyLFXYq7FXw3/zlL+bn+MfNv8Ah/S5ufl7QJGjVlPwz3gqssvuqbon0kbNirw/FXYq7FXYq7FU Tpum32p6hbadp8DXN9eSpBbW8YqzySMFVR8ycVfol+Tf5ZWP5d+SbTRYwkmpS0uNXu1H97cuByoT vwQfAnsK9ScVZzirsVdirsVdirsVdirsVdirsVdirsVSrzV5X0XzT5fvdB1qAXGnX0ZjmTow7q6H 9l0ajKexxV+en5s/lfrf5dea5tG1AGW0krLpeoAUS4t60Dezr0dex9iCVWF4q7FXYq7FXYq7FXpf 5F/nHqP5beaBM5efy7flY9Xshv8ACD8M8Y/35HX6RUeBCr7/ANK1XTtW0221PTbhLqwvI1mtriM1 V43FVYYqisVeL/8AOT35uf4K8nHR9Mm4eZNeR4bdkPx29t9mafbdWIPCM/zVI+zir4TxV2KuxV2K uxV2Kvq//nEH8oDFGfzE1qD45A0Pl6KQbhTVZbqh/m3jT25HuDir6lxV2KuxV2KuxV2KuxV2KuxV 2KuxV2KuxV2KsN/NX8sdD/MTyrNomogRXK1l03UAoZ7acCgcdKqejrXce9CFX56+bPKmueVPMF5o Gt25t9Rsn4SL1Vh1WRG25I67qfDFUnxV2KuxV2KuxV2KvoD/AJxh/PY+VNSTyh5juKeWr+T/AEK5 kPw2Vy57k/ZhlJ+LsrfFsCxxV9heY/MOleXdBvtd1WYQ6dp8LT3Em32VGwWpFWY0VR3Jpir84/zH 896p5684ah5j1ElWun421vWqwW6bRQr/AKq9T3NT3xVjOKuxV2KuxV2KvQfyR/K28/MXzvb6YVZd HtONzrVytQEt1P8Adhuzyn4F+luinFX6G2VlaWNnBZWcK29pbRrDbwRjiiRoAqqoHQACmKq2KuxV 2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KvJv+cgvyStPzF8vfWrBEh816ahOnXB+ETJ1a1lP8rHdC fst7FsVfBV5Z3Vldz2d5C9vd20jRXEEgKukiHiysp3BBFDiqjirsVdirsVdirsVZ35h/OXznr/5e 6V5H1C49TTtLkLmep9WeNABbxSnusPxcfHau6g4qwTFXYq7FXYq7FVeysru+vILKzia4u7mRYbeC McneRyFVVA6kk0xV+hn5H/lZa/l15It9MZVfWbulzrNytDynYf3at3SIfCv0t+0cVeg4q7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq+c/+covyG/xBaTeePLVvXXbSPlq1nGN7uCNf71AOs0a jp+0u3UAFV8b4q7FXYq7FXYq7FXYq7FXYq7FXYq7FX1J/wA4g/lAZZj+Ymswfuo+cPl6KQbM+6y3 W/8ALvGnvyPYHFX1hirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir47/5yj/Ib9B3 U3nnyzb00a6flrFlEu1rM5/vkA6RSMdx+y3sfhVfN+KuxV2KuxV2KuxV2KuxV2KuxVnH5O/llqH5 iedbXRYeUenx0n1a8Uf3NspHKhO3N/sp7mvQHFX6J6bpthpmn22nafAttY2cSQW1vGKKkcahVUfI DFUTirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiqleWdre2k1ndxLPa3CNFPBIAyO jjiysD1BBpir4L/5yD/JK6/LrzD9b09Gl8p6m5OnTkljA5+JraQnuvVCftL7hsVeSYq7FXYq7FXY q7FXYq7FVS3t57m4itreNpriZ1jhiQFnd3NFVVG5JJoBir9BvyE/KiH8u/JMVpcIp1/UeNzrM4oa SEfBAGFarCp49aFuR74q9KxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVKf NnlXRPNfl+90DWoBcadfRmOVduSnqsiEg8XRviU9jir89PzX/LDW/wAuvNU2i6iDLavWXTNQC0S4 grQMOtGXo69j7EEqsMxV2KuxV2KuxV2KuxV9Nf8AOIf5QG/v/wDlYOswVsrJmi0GKQbSXA+F7ih6 rF9lP8up6rir66xV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVhn5r/ AJX6J+YvlWbRtQAiu0rLpmoBavbz0oGHSqN0de49wCFX56+a/Kut+VfMF7oGtQG31GxkMcqb8WHV ZEJA5I60ZT3GKpRirsVdirsVdirMfyn/AC41P8wvOdnoFpyjtifW1K8UVEFqhHqSb7cjUKg7sRir 9F9G0fTdF0q00nTIFttPsYkgtoE6KiCgHv7k9cVRmKuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2 KuxV2KuxV2KuxV2KuxV2KuxV5N/zkD+SFr+Y2gi609Uh816ah/R1wx4rNHWrW0reDdUJ+y3sWxV8 HanpmoaXqFxp2o28lpfWkjRXNtKpV0dTQqwOKoXFXYq7FUVpmmahqmoW+nadbyXd9dyLFbW0Slnd 2NAqgYq++/yB/KCH8uPKPpXYSTzFqfGfV51oQpA/d26MOqxAnfuxJ6UxV6firsVdirsVdirsVdir sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVedfmp+RXkj8xovW1KJrLWo14Qaxa8 VmAH2VlBHGVB4NuOxGKvmXzR/wA4efmnpkzfoVrTX7ap9NopVtZqeLx3BRF/2MjYqxX/AKFu/O3/ AKlaf/kdbf8AVXFWU+Wv+cPfzW1KZf0x9T0G3r+8aeZbiWn+QlsZEY/ORcVfS/5UfkJ5J/LlPrNi jahrrrxl1i6CmQAijLCg+GJT7VY92OKvScVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVf /9k= + + + + + + uuid:ca813f84-1fc2-1c49-aa8c-5923c3cbe084 + xmp.did:F87F11740720681183D1C67287E77B9D + uuid:5D20892493BFDB11914A8590D31508C8 + proof:pdf + + xmp.iid:F77F11740720681183D1C67287E77B9D + xmp.did:F77F11740720681183D1C67287E77B9D + uuid:5D20892493BFDB11914A8590D31508C8 + proof:pdf + + + + + saved + xmp.iid:F77F11740720681183D1C67287E77B9D + 2020-11-26T01:04:48-08:00 + Adobe Illustrator CS6 (Macintosh) + / + + + saved + xmp.iid:F87F11740720681183D1C67287E77B9D + 2020-11-26T01:06:16-08:00 + Adobe Illustrator CS6 (Macintosh) + / + + + + + + Print + + + False + False + 1 + + 1024.000000 + 1024.000000 + Points + + + + Black + + + + + + Default Swatch Group + 0 + + + + White + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 0.000000 + + + Black + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 100.000000 + + + CMYK Red + CMYK + PROCESS + 0.000000 + 100.000000 + 100.000000 + 0.000000 + + + CMYK Yellow + CMYK + PROCESS + 0.000000 + 0.000000 + 100.000000 + 0.000000 + + + CMYK Green + CMYK + PROCESS + 100.000000 + 0.000000 + 100.000000 + 0.000000 + + + CMYK Cyan + CMYK + PROCESS + 100.000000 + 0.000000 + 0.000000 + 0.000000 + + + CMYK Blue + CMYK + PROCESS + 100.000000 + 100.000000 + 0.000000 + 0.000000 + + + CMYK Magenta + CMYK + PROCESS + 0.000000 + 100.000000 + 0.000000 + 0.000000 + + + C=15 M=100 Y=90 K=10 + CMYK + PROCESS + 14.999998 + 100.000000 + 90.000000 + 10.000002 + + + C=0 M=90 Y=85 K=0 + CMYK + PROCESS + 0.000000 + 90.000000 + 85.000000 + 0.000000 + + + C=0 M=80 Y=95 K=0 + CMYK + PROCESS + 0.000000 + 80.000000 + 95.000000 + 0.000000 + + + C=0 M=50 Y=100 K=0 + CMYK + PROCESS + 0.000000 + 50.000000 + 100.000000 + 0.000000 + + + C=0 M=35 Y=85 K=0 + CMYK + PROCESS + 0.000000 + 35.000004 + 85.000000 + 0.000000 + + + C=5 M=0 Y=90 K=0 + CMYK + PROCESS + 5.000001 + 0.000000 + 90.000000 + 0.000000 + + + C=20 M=0 Y=100 K=0 + CMYK + PROCESS + 19.999998 + 0.000000 + 100.000000 + 0.000000 + + + C=50 M=0 Y=100 K=0 + CMYK + PROCESS + 50.000000 + 0.000000 + 100.000000 + 0.000000 + + + C=75 M=0 Y=100 K=0 + CMYK + PROCESS + 75.000000 + 0.000000 + 100.000000 + 0.000000 + + + C=85 M=10 Y=100 K=10 + CMYK + PROCESS + 85.000000 + 10.000002 + 100.000000 + 10.000002 + + + C=90 M=30 Y=95 K=30 + CMYK + PROCESS + 90.000000 + 30.000002 + 95.000000 + 30.000002 + + + C=75 M=0 Y=75 K=0 + CMYK + PROCESS + 75.000000 + 0.000000 + 75.000000 + 0.000000 + + + C=80 M=10 Y=45 K=0 + CMYK + PROCESS + 80.000000 + 10.000002 + 45.000000 + 0.000000 + + + C=70 M=15 Y=0 K=0 + CMYK + PROCESS + 70.000000 + 14.999998 + 0.000000 + 0.000000 + + + C=85 M=50 Y=0 K=0 + CMYK + PROCESS + 85.000000 + 50.000000 + 0.000000 + 0.000000 + + + C=100 M=95 Y=5 K=0 + CMYK + PROCESS + 100.000000 + 95.000000 + 5.000001 + 0.000000 + + + C=100 M=100 Y=25 K=25 + CMYK + PROCESS + 100.000000 + 100.000000 + 25.000000 + 25.000000 + + + C=75 M=100 Y=0 K=0 + CMYK + PROCESS + 75.000000 + 100.000000 + 0.000000 + 0.000000 + + + C=50 M=100 Y=0 K=0 + CMYK + PROCESS + 50.000000 + 100.000000 + 0.000000 + 0.000000 + + + C=35 M=100 Y=35 K=10 + CMYK + PROCESS + 35.000004 + 100.000000 + 35.000004 + 10.000002 + + + C=10 M=100 Y=50 K=0 + CMYK + PROCESS + 10.000002 + 100.000000 + 50.000000 + 0.000000 + + + C=0 M=95 Y=20 K=0 + CMYK + PROCESS + 0.000000 + 95.000000 + 19.999998 + 0.000000 + + + C=25 M=25 Y=40 K=0 + CMYK + PROCESS + 25.000000 + 25.000000 + 39.999996 + 0.000000 + + + C=40 M=45 Y=50 K=5 + CMYK + PROCESS + 39.999996 + 45.000000 + 50.000000 + 5.000001 + + + C=50 M=50 Y=60 K=25 + CMYK + PROCESS + 50.000000 + 50.000000 + 60.000004 + 25.000000 + + + C=55 M=60 Y=65 K=40 + CMYK + PROCESS + 55.000000 + 60.000004 + 65.000000 + 39.999996 + + + C=25 M=40 Y=65 K=0 + CMYK + PROCESS + 25.000000 + 39.999996 + 65.000000 + 0.000000 + + + C=30 M=50 Y=75 K=10 + CMYK + PROCESS + 30.000002 + 50.000000 + 75.000000 + 10.000002 + + + C=35 M=60 Y=80 K=25 + CMYK + PROCESS + 35.000004 + 60.000004 + 80.000000 + 25.000000 + + + C=40 M=65 Y=90 K=35 + CMYK + PROCESS + 39.999996 + 65.000000 + 90.000000 + 35.000004 + + + C=40 M=70 Y=100 K=50 + CMYK + PROCESS + 39.999996 + 70.000000 + 100.000000 + 50.000000 + + + C=50 M=70 Y=80 K=70 + CMYK + PROCESS + 50.000000 + 70.000000 + 80.000000 + 70.000000 + + + + + + Grays + 1 + + + + C=0 M=0 Y=0 K=100 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 100.000000 + + + C=0 M=0 Y=0 K=90 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 89.999405 + + + C=0 M=0 Y=0 K=80 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 79.998795 + + + C=0 M=0 Y=0 K=70 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 69.999702 + + + C=0 M=0 Y=0 K=60 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 59.999104 + + + C=0 M=0 Y=0 K=50 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 50.000000 + + + C=0 M=0 Y=0 K=40 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 39.999401 + + + C=0 M=0 Y=0 K=30 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 29.998802 + + + C=0 M=0 Y=0 K=20 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 19.999701 + + + C=0 M=0 Y=0 K=10 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 9.999103 + + + C=0 M=0 Y=0 K=5 + CMYK + PROCESS + 0.000000 + 0.000000 + 0.000000 + 4.998803 + + + + + + Brights + 1 + + + + C=0 M=100 Y=100 K=0 + CMYK + PROCESS + 0.000000 + 100.000000 + 100.000000 + 0.000000 + + + C=0 M=75 Y=100 K=0 + CMYK + PROCESS + 0.000000 + 75.000000 + 100.000000 + 0.000000 + + + C=0 M=10 Y=95 K=0 + CMYK + PROCESS + 0.000000 + 10.000002 + 95.000000 + 0.000000 + + + C=85 M=10 Y=100 K=0 + CMYK + PROCESS + 85.000000 + 10.000002 + 100.000000 + 0.000000 + + + C=100 M=90 Y=0 K=0 + CMYK + PROCESS + 100.000000 + 90.000000 + 0.000000 + 0.000000 + + + C=60 M=90 Y=0 K=0 + CMYK + PROCESS + 60.000004 + 90.000000 + 0.003099 + 0.003099 + + + + + + + + + Adobe PDF library 10.01 + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 7 0 obj <>/Resources<>/Properties<>>>/Thumb 11 0 R/TrimBox[0.0 0.0 1024.0 1024.0]/Type/Page>> endobj 8 0 obj <>stream +H‰LRΛN1 Όη+όuγGbϋJAœBψ€ +8@‘€—˜΄hA+ewΖφŒνμώώ@ϋ»C§«λ΅ΟΦIΊϊε؝ϯηφD¬Gθ­νo;½~·O  ηκ6Kc›²1―„vŒδ +`tCT`¨ΆH š@mξ$’ Y%wΡ3UhΉΠ€ηZ£Β9j­i²[°ΰΫ+$[Ž­"xY ωΐ*ΧςP΄Qζ¬#‘ΣΧώ•lYΧ€SuŒΰ€Ul!«h&{‘)#y΅λΕ57Ž9`£ck—λ>Ά—φΠnξπk<΄ξϋkŽ endstream endobj 11 0 obj <>stream +8;Z]";%CJt#X]-@@UmBiqJr>eGp;@[BVg''cO:&mn,Y`iPP-^3_(#;ZIS`[]-o@l`_NXTuR$5C0=)^Z/Oq!*Fod6?=h\Z>^ +Z=H5:5U-sSe endstream endobj 12 0 obj [/Indexed/DeviceRGB 255 13 0 R] endobj 13 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 14 0 obj [/View/Design] endobj 15 0 obj <>>> endobj 10 0 obj <> endobj 9 0 obj <> endobj 16 0 obj <> endobj 17 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 16.0 %%AI8_CreatorVersion: 16.0.0 %%For: (Kurt Krueger) () %%Title: (room_checkbox.svg) %%CreationDate: 11/26/20 1:06 AM %%Canvassize: 16383 %%BoundingBox: 111 -897 913 -127 %%HiResBoundingBox: 111.8359 -896.8711 912.166 -127.127 %%DocumentProcessColors: Black %AI5_FileFormat 12.0 %AI12_BuildNumber: 682 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%CMYKProcessColor: 1 1 1 1 ([Registration]) %AI3_Cropmarks: 0 -1024 1024 0 %AI3_TemplateBox: 512.5 -512.5 512.5 -512.5 %AI3_TileBox: 206 -908 818 -116 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 2 %AI9_ColorModel: 2 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: -523 33 0.82 1701 904 18 0 0 46 134 0 0 0 1 1 0 1 1 0 1 %AI5_OpenViewLayers: 7 %%PageOrigin:206 -908 %AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9 %AI9_Flatten: 1 %AI12_CMSettings: 00.6 %%EndComments endstream endobj 18 0 obj <>stream +%%BoundingBox: 111 -897 913 -127 %%HiResBoundingBox: 111.8359 -896.8711 912.166 -127.127 %AI7_Thumbnail: 128 124 8 %%BeginData: 4268 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FD6FFF7D52FD05F82752A8FD74FFA8FD0BF8277DFD71FF7DFD0EF8 %27FD6FFF52FD11F8FD6DFF7DFD12F827FD6BFFA8FD14F87DFD6AFF27FD14 %F827FD69FF52FD16F87DFD67FFA827FD16F827FD67FF27FD18F8FD66FFA8 %FD19F8FD66FFFD1AF8FD65FF52FD1AF8FD64FF7DFD1AF827FD64FF27FD1A %F827FD63FF52FD1BF87DFD62FFA8FD1CF8A8FD62FF27FD1BF827FD62FF7D %FD1CF8A8FD61FFA8FD1CF852FD62FF52FD1BF827FD62FFA8FD1CF87DFD62 %FFFD1CF852FD62FF52FD1CF8FD62FFA8FD1CF8A8FD62FF27FD1BF852FD62 %FF7DFD1BF827A8FD61FFA8FD1CF87DFD62FF27FD1BF852FD62FF7DFD1CF8 %FD62FFA8FD1CF87DFD62FF52FD1BF852FD62FFA8FD1BF827A8FD62FFFD1C %F87DFD62FF52FD1BF827FD62FF7DFD1CF8FD63FF27FD1BF87DFD62FF7DFD %1BF827FD63FFFD1CF8A8FD62FF27FD1BF852FD62FF7DFD1BF827FD62FFA8 %FD1CF8A8FD62FF52FD1BF87DFD62FFA8FD1BF827FD63FFFD1CF8A8FD62FF %52FD1BF852FD62FFA8FD1BF827FD63FF27FD1BF8A8FD62FF7DFD1BF87DFD %63FFFD1BF827FD63FF27FD1BF87DFD62FF7DFD1BF852FD63FF27FD1AF827 %FD63FF52FD1BF8A8FD23FFA8FFA8FD3CFFA8FD1BF852FD1FFFA87D2727FD %05F82752FD39FFFD1BF827A8FD1EFF7D27FD0BF87DFD36FF52FD1BF87DFD %1EFF27FD0EF852FD34FFA8FD1BF827FD1EFFFD11F827FD33FF27FD1BF8FD %1EFF27FD12F852FD31FF7DFD1BF87DFD1DFF52FD14F87DFD30FFFD1BF852 %FD1DFFA8FD16F8A8FD2EFF27FD1BF8A8FD1DFF7DFD17F8FD2DFF7DFD1BF8 %7DFD1EFF27FD18F8FD2BFFA827FD1AF827FD1FFF27FD18F827FD2AFF7DFD %1BF8FD20FFFD1AF852FD28FFA8FD1BF87DFD20FFFD1BF87DFD27FF27FD1A %F852FD21FFFD1CF8A8FD25FF52FD1BF8A8FD21FF27FD1BF827A8FD23FFA8 %FD1BF87DFD22FF27FD1CF827FD23FF27FD1AF827FD23FF7DFD1DF852FD21 %FF7DFD1BF8FD24FFA8FD1EF87DFD20FFFD1BF87DFD25FF52FD1EF8A8FD1E %FF27FD1AF827FD27FFFD1FF8FD1DFF7DFD1BF8A8FD27FFA8FD1EF827FD1C %FF27FD1AF852FD29FF7DFD1EF827FD1AFF52FD1AF827FD2BFF7DFD1EF852 %FD18FFA8FD1BF8A8FD2CFF52FD1EF87DFD17FF27FD1AF87DFD2EFF27FD1E %F8A8FD15FF52FD1AF827FD2FFFA8FD1EF827A8FD13FFA8FD1BF87DFD30FF %A8FD1EF852FD13FF52FD1AF852FD32FF7DFD1EF852FD11FF7DFD1BF8FD34 %FF52FD1EF87DFD10FFFD1BF8A8FD35FF27FD1EF8A8FD0EFF27FD1AF852FD %37FF27FD1EF8FD0DFF7DFD1AF827FD39FFFD1EF827FD0CFF27FD1AF87DFD %39FFA8FD1EF852FD0AFF7DFD1AF852FD3BFF7DFD1EF852FD08FFA8FD1BF8 %FD3DFF7DFD1EF87DFD07FF27FD1AF8A8FD3EFF27FD1EF8A8FD05FF52FD1A %F852FD40FF27FD1DF827FD04FFA8FD1AF827FD41FFA8FD1EF852FFFFFF52 %FD1AF87DFD42FFA8FD1EF87DFFA8FD1AF827FD44FF7DFD1EF87DFD1BF8FD %46FF52FD38F87DFD47FF27FD36F852FD49FF27FD34F827A8FD4AFFFD34F8 %7DFD4BFFA8FD32F827FD4DFF7DFD31F8A8FD4EFF7DFD2FF87DFD50FF27FD %2DF827FD52FF27FD2CF8A8FD52FFA8FD2BF852FD54FF7DFD29F827FD56FF %7DFD28F8A8FD57FF52FD26F87DFD59FF27FD24F827FD5BFF27FD23F8A8FD %5CFFFD22F852FD5DFFA8FD20F827FD5FFF7DFD1FF8A8FD60FF52FD1DF87D %FD62FF27FD1BF827FD64FF27FD1AF8A8FD64FFA8FD19F852FD66FF7DFD18 %F8FD68FF7DFD16F8A8FD69FF52FD14F87DFD6BFF52FD12F852FD6DFF7DFD %10F87DFD6FFFA852FD0CF827A8FD72FFA85227FD05F827277DA8FD78FFFD %05A8FD4AFFFF %%EndData endstream endobj 19 0 obj <>stream +H‰μWkOYύώύe₯DŠ›ϋ~μ|"dƒfE’ѐ­V£ΖnBoŒΝΆΫ™Ιόϊ=UΧ€cΈDΒ&B€]}uͺNͺή{κθυdXον½h]3Wν|ϋ©xςΗωhŒGύͺλΪζdΦΥΣ§Ε³ήή~ΫVK+gΝhΨΦczнŸΗέυ3ϊΣ}Ή¨ιΩω΄ΨϋΧΈΐvŒCΗΏ^χΉΝΒιE=˜ͺφ`2žvΥΈ»}ίΈ:ηmpiuγ³ ,{ί +Γ7λΞ²a]­_;¨Qσρμb^Γj†ΩˆhiiΏα³ΌΕηbοv/Oλ —ΝΡέΨ=~φ0ΟK™}ΑΚ|wΆ₯œ›;ͺσνδ’n«nf“δzΓΪy<™΅ƒztqVe³¬ςιOkΧκ²oΌ™ε—tΎφ6›έο€ΤΦΣΩ(ΏΩ^.ߘZLΞ/&Σ¦«wO²G‘Ow¬\Θ-];Ν²G–O&ˆΩj :ˆήj ωͺόi’\΅Mwv^wΝΰ1φΡasz:›ή!]»ΤCΣ`pΨVgωΫφ.:jΊ_ͺζ7Π-t-4_η~΄ΠmjΆΌ…ζωΡB7ΨBΏλ­mΫΫθ};Ώϊx:θέΆ₯ωNNO§uχ|4kΫzΈ?Ί8«Œ―Κλ«ΊύXSDwo2zp ٝ”<œ»G‹ΣΆtΥθυ€ΙνL›3ΙqyӚq•ΒζΒ9©¦υΛΆώί¬ΎdγZΪ΅ώq4ϋ a<;ƒ¦K3Α-nY;²ρδΈkΊΑY.Ύ)―~یξπ«=kGx<™΅ƒϊa;δš!½΅'³j![Evm¦ΛEΈ*^α¨?“ΎΛBΜnϊteΙ…ΜŸςεγrω”ΩΆ Η!ΆNΥθΡκΖΑd4i_eΰ6 +Η#μT&Ύ¨?79tΑbqΣΪΑ=ΝΪΆή+a»VP‡Υl:mͺ1aέ½ŠκgΏ— σ_F†›xιgW0[ωxιNTΝΆΛά›ΣΣiέύτ !έ=%Έ‹AΧ'u[u“l΅[Ψ°CΌkΖκ%₯vaVέv‘8˜œ_Pͺθ΅β±ΚaœL›.ϋ•v{β{³ν”“eφ΄‹ψbΚέ-©έžΐ₯ŠΏ?­Ϋ—M›ι)·-yξͺ“ό§]™ΡΈΌbΝ€T!²αφwτušjqΟΖ΄εεl<ψuχDεΡ‘­τ…(ώt;όA·ΝΣMώUΔνy>Ϋξ}h\»·m5žblΨ=φί5]Ý\ΤmΥM²1.lX{ Ofν ΖtzqΦ ςgΣ;ή2ΎNՎL§»0q§tmΙΜύpJ1mΊμζ±= +ρͺn?Φϋ£Ρ€f[Bς}œ½w%nˆ«œpŠζ†»+ϋ±YšQ—ίΤχ^Τ§ΕO½Ών,ΕoίΎδΘdΙςΫλΙψΡα”~?™ŸΧ›ρβƒήλ >Γ€GΗ_ΞO&£ή“ΓΊ=A?}ΪΕ>~ίή›αF=ώy_ώ‰…ιχΒ―ŠGCZωk――Tƒ4…₯ˆ2η=ϊ\Ό²]Yϊψ M00]ν»Ιv½sΜΌι‰RΘθ +Qϊ€?NZzοωΤλK[z©q²+­ˆήγϊΎ₯²XΧχ₯S6βƒ’₯2B₯U2Ϊβ―rΪω’oK½ΐKη>˜‹ $,‘΄V²ˆΰC,p£+₯²E_—ΡΩ€3₯.΅0ž,N{:G–ΖJE΅΅Ό Θ€£ƒ΄Φδ’4₯Ρπ„^«Hz§Χ“₯τΞ;Š(‘ΈIx«ƒ.€/½₯―>ΪXΌλΑAΟ¨‚Ι—I:%Y΄ήΒ"Jι 2 ,.ZΛΰ‘››Kΰ\ ƒ’Ŏβƒ{ψΏ’T€-ex…€λΚ”JGχg.Vά'L”Jgq₯ˆΰŸD`)™ΎF"K0Q°%ΐΒd @’ΐψ>wG%ƒΧΚΐ’7NβΟ`A8",G°φ»€χžŽ "X:Φ*«qS(eŒŠ6ιλcΓGΈu0Θ‡/­3†7ήYG§h‘=­°Βq«Žζ€oΰ.卖τP/U\π-ΒS2i©ρAQΰˆS Ό@β"žˆDΏ(9Š|;!3ΙΔΤJ0΅”UQ'T + +oΣ;`¬ζΛ£WŽ ©hT¬1΄1JΖ΄F0nŠka@¬ψOΙ %\A¨Αb#ΌœCB Ÿ€Τ1ΑeZI*T Tΐ5‘ΙxBΐ .#³œ’gˆ9‚Λ`P +l*@e~μQΔ5ώ³;8UλΘΔΰ°2›JΤ €ωDZ-›΄D°¨œΑ‚A@? + .³ˆ ž³­ζ‚Dq•+– pˆΘ©C ­GΑ’Ψ–œGfŒεsŒΡPˆN„xaŽU†΅Ζ%χ" π_‘S”u(O¨`Ε—J„›]ƒοQ¦άωB±|˜9!g¨Ό±,xΤΫP/Ε ΰ>|›ίΰšςΪφDFC\ ›ε‹ ι3›9o΄MΚ*ηΉ‡’%Υ€ΝΈΐ2 PRdXk4ў,1 +₯“ΕikRrζΆΚ.,ΰtHΑmΩ)€ϊΑuZXŸ[rύ–ŒγœΘœΒήHΗΒ)Ωd­β#Υ3e έFHR’9Α#§…βΕ=š…žZ"uBR1Π"$Y§κ9A½¦8‘·r~ΊvA%—™ 4!χ!λ)τ*PVz7Ζg5†K‘Ζ>ΫQqWŒτT+A(*{h³TX…Θά‘B> ΠΩΛΠy‡"?b³Ÿc‡h³ "ν‚K!Ώ„Έ+1ΤQkκ °yΕ5„VξŸL‘n…ΒΙ”:_ˆ4D >Ω]’¨?tXH‚#©•›R}5\ΨB˜Λ‘!²bŸW"€b½³ΤΕ!Dœ(€+<€΄δ…L φGXΐ>ΙƒΕ +Ύ[˜Š[ƒgpHI2bA‘§£` ˜ c&υœtοΜ•Θeg9«ˆ…#Šb•ADr‡ nφ‘…™½s€ωΧ B@“΅ ΄ί˜–VFͺ•‘ky2°“|4ι{€νΣΆ”Uκ§Iν0EṕŒs.sJΣΡsΡEθBH··[ǘ>·Θ4t‹εR›…-MWDB +% fPcΌJΦb`Aσ>ΔQ₯’ΉσΤACGRK# + †΅μΓ-ƒΘ―zΔClP‘‘γ +%Ρd NAMΣά«›"Ίυ₯ΜP:υe•—–η‹Ύ’Α&ς.Ι΄ΐ&m΅ε™•ήR>1β2Hτ:™ϊ +βŠΖ^"Cθτ©Τ΄υ<5Ϊ9u΅MAŠΥ˜PΞy& ’ wšΎΞη&$¦ΔHžp%Ω‹vΡ } aώ:ΐ]3Όά# 1½ Έ4…0ο#οQ4ΎbθτΤ˜άΞ@Œΐ4ΐ(rgQCΑ`:ίHŒν@qβζNƒ…ίhβh…24²~ъPϊytάo`°όŸΣ&β$9 ‡|ՎsΊ€ƒ[ι«Γ“ -9°δαbŠLι”<ΎL’.H Š€-€_ `!‰oΪΝcΓ€Μ[!V4€Q[₯—„σ]ζΘqτJ>ο  H}±ιώ#dϋθςό/Ώ, EMhIΔt7€ͺ¬\’8£ŠIψŽ\±Ή!CΧΫz՟Uη›1“ε!_}κΨ»'£n)0Εf¬ΨX³-­@ίMaε°£šK –d[ΚBυμΥ0zφΩΥL¬ +xT­O;vΪθΉs)ϊόη +@N~e‡uθ9ωΐ}—»H%PŽ/ͺύμ όΨr₯X0@νΚΗ:x©N”βαJ>|¬Δο#D^ͺ04λΐ\E[Ž.ΎnospεΑζηη'Έœςr‡»ή NΗ:Vί‘ ¨ίύ;­†Ξμ$‹’‹x§μ‘¦†±“ζ³4ΥWΟΞNβΠω‘β§:lχΊQ ηΠ +.}f tλΥu¨C-UDbυΗΧΒ̖ʏK₯Ύƒ’π\ύ™νͺ5`Έ”u§ ‘ &ΧίeM3zάD0η6¬=οξςX Χ’—U‡΅_h²9xψκF|[³Ψ„%+’ΊS ύžΜC;Ϊ±»/Ρ &½lE{‹ΫT6₯¬Z +ώ“_ +βΧ­Ή@F@B₯Lφχ;Ώ>©ΓT4έͺΦβ‘ΦνTyβ冡ψq6S„ύΤΟΆw°…λ‚ν€vΡ4^_*›‘†²›ώNE·€·’σ ­@m$ζqζLb-MΧΣΔ€¬x1ΐΒvΖMΛ@fA²ΨT}ΊΩgΌr Ϋ­,%L=aΏΐίfε‡[ώ›€7—:­ϊ―zPPΟΡ@Ÿ‚ώ§ΰΟιgΗ0²[άΗ£¨ΏΉό™‘.£ψχ\7Η₯ΆΨ QŒ°©ν.έGΘ©DMμ)ΒMd4FyΈBy5ŸΙHΕΠwκrz¦S/‡ΊΗ{|γˆΨœΠΕΗΆ>¦+ŸΦΑ9 ~©TWΫ€Q0΄ΚWRq +Εa ˜{iλFΡVχxBώŒ4oΚqυYŠ―ځΗγ)Ύu*δήNK Φβ] Z‰Ž'Dbœ.ΆgfKœ`†›ΦtοtΣ;Ήf?²…“πΡ!ίdD?9eN؍˜ψσwV½0γ{`“dfΑ―+)Θ8μ½Oh~‹6Α‹ΗeξΉΈύω3yo¬h”•Ί2)U+Ά9œ€ξήΤ–Δ΅ΆΟμv +…Z{ξ1°ΤœQ…OԊ²ΪJ mf%Ώaj+Ζ#z™’Ζ5d߁žκ;³˜ΜŠFΝ χ’V~axeoνWΝy‚S> eπ0 σŸ<ω¦Ϋi:L>ͺJ~i5ΐ°_Š V†½{©΅μ₯6Gθ SΘ‚³ήορr†hμ»υ1UV…-|rtΨR;ͺ=:―P€*s ²°/5P ,₯bl•΄l »p"R‹h; ΙLΊ]$>ϊΰ₯ε‚%—tΞλ]o/—z\*v©κ Άπit‡*£/b1€be―ͺrΠ?G_sϋtw!|–XsR‰„ϋ9‘ΞΟPrMK)ΉΖ©ν&$SήΠέ΄CKΕ–Δ²«Ώ—†1ρw'οsί’v"•§©Μ½TίΌχδRw€ΒZ E\K-TO»f%bΙaH jR,$Ι&0_[qβ'6%œ¬hΘvPKŽΑΖύρI{]ΠσϊrΑΨΫsώuΓžq]₯|HΖχγΨ|}QΠ Š,$hU8©˜#½…½Ε#κrB€AΏδS+MŽθκ"˜ΫΙΌUWΗFš…)‰O0«gΈ€~„Χ¬VX$KπUu–Oυαš—^}ΛθκΘtUGfκωyg/ί|Σ€€4Ty9«–Θoΐίq2²hι±Ή=$ΨΜξš€€Qϋ@”O­±\+Ε¨βτ0ς°v(žΊ…¬)t.Μ`ξαξ53·θŠn‡Y“λv0•Χkζ˜7L–γφΟύΧΗ^4^ŒτγR{‘¨δU©ΐϊύ%*ΰφ$G6}%&΅0w9¬φpτ7̏s'‰*!τλΠρΐ^μ?šS…(fHwƒTΐ‹ΆƒPž tΑσ θƒΫ‰ςŽΠ<άα[΄¨§~ΫόΚ•εhΠξl›E,Ω‰NOQΜΩo>)f ΰξ’υ¦₯iƒ% ,„, ¨±ΒΜΫ.αΨ΄‘D©)1ch)dUPδ,ŒIΆ©ΦgF%·OΑύHAΖΝΑ LxCΤ-/ηΘsί’ΒΡθ²>rΓ  vπ­&7,7οW>»Ψžk σF:TμŒ[¨ρNώΐπŽLLΠΉΊV–Œ„†Db0Sυή₯`Ρ5Kƒ΄q”¨Fμ¬2– ]ΫυφΛ ςε/\8kQΆf“y²#t†Έ§3f4ςζΤΘ›κΞS΅‘Œ` Ε KS€Πq-;ƒιZi2;) η8μ6SόJ\τqoOqωXŽ—/e+ =%^YcΊ««%‹w‹ ψSώ,‹š#Υ­97#ʁ,ιgD΅PGÚ9Ν,XsšAŽΞ€ρ·λ2@œ?ξΡvζݟγήFΠQΞwœcrvΦφ°U°—+š—}φl¬S †°”Ιt·θ³uY}ήδύyͺ"²ρ₯Ώ±pϋŽτxΟ•’υ ~#‡ξΖα†άοqοwΫ-[Ώ±βχΑυΕ«Υ›Ήτ)R2ϊ$7ΓgZ޳λ0[=Τ}Ψ³ž³6Ρ7oγπ²Η0ꎊ[w&>‹­`Ν +0YB›΄°π[jJŒΜεσPj>H[’uΝ Ψ&)δΫΪί1SŽŠY€gaMΛ"g87‘v΄ρ +JkΗ +πxό$½΄Νt»εœd"pRъy’•ˆV—†ΐλόΪpψŠ Ώ{­X5ΐBh|NŠL^Ț=b‰ΌβIΪ]}Ϊ―πΓϊO ??…x%¨T+{'G$Φ’2|4ΗΎ³»ΔΪΨριzηΫΛ“Κ\«w©ρΜύYBvπ³ο˜~D/`Θ°΄]^ ί4λ»@ώξ±=!Ϋ^Ιg+I1mΉ›<ύ&pmIΠίσΠ•o½8TMΒcΘ•xΦ„Αΰ?}b…c\½F-RνϋϋΕkΉΫ}‡λεn©ϊ­l—ρu%5|Λ닇3-ΏVοδŒΝ Ψ•vΛ±Œ-JίOΠ›ηC!Χ›ί^žΤηZΓΗBλ5Ρw+§Ο’ΉfB_Γ―Α|λΜ厡Z:­›Ίš˜Φθb"­,Ϋp'xœD·LK;›Xφ=ΐŽ»Ύν_Τ¨m `}λnhΚDœ€ ύ₯K?Ώ΅`|¦†|h«ρ•ν½μό°Ιͺ["eš΄$%9cŽ4μŸaηS0aƒ‹„šHΊCΰŸ£α f.°ϊ{―OzΉ΅Ιv·U‚Χμ•ˆFc†oλ!ζ2ιΌ'."ΎyW†“ε5М0NQΪΰ‚ƒ*‰Μ:o­{URψ w(Ώ’mΧ‹cSƒδgσ”-q—φ›ΨlL|Eΐ!—έ< AαςΛkΉ$ς+9›ζ¦έrŠ%9’¨―ο.eEπ!ΓR AAΜ±X'Πι¦,‘εφ†T€ŠπPŒvͺRHbX¨ΥΓΏ-2ρ + °Ϊ$—…δΨ‘­c΄VΦNΎ‚τΚ ΕίYξςsΑΆ@Ν\ώ‰2+Δώs„βT_v.χ׊άqηrEЍšcμaw|αΙ ώ+\²Η–IR(Nn©0₯·TeΛβ—…x™0χYΙ₯ΊωCζdm°ϋΪ+ž"‚h†¬±’}!—μψ£ψΈΦΚθΓO5R₯9C8£%JμχΒΟώ³Θžτ‡F\{uιθ-ΤΙg-ΨkΏ―•ΞI `d“ε##f_’qΣOGDΥ©ΰ φ«*eφιƒ ΔFEK}ΔXβω‰gρyΝ£π…ηŠζΎhQ γ_4²@%++ώηiQ9»^Ϋ€qδ© +“AΣX-¦ΏT>sΡ„‡œm{Mαgϋ ε’½l†xΔΛχ*Β– RL+b\"_§2U&’½ΕqαΨΤσ].;ŽGύώ—Ά6κύX­e^IΆΧν₯πΖϊ~Η‰,rΤ—@‹³y/«2#γρ/C’ρΏυΡΗι6 +’>Κΐvͺ°²m'₯ˆTj\ +–^°+/6έ«Πt*Ψ¬ΗCHσρCήx±h-Γ‘N«Pϊ™Ώδ:"θΐG³5λΧΰ–―­Ξu€d[ΑI|§ζ5 €>χψΌΛš‘πκ₯|Κ{`Iy”JxUα T!‘d]^σŒ‰ΕΌœP'‹kˆG玣 ]Ψ~aχΒεϋσ‡ΎNϊΘE«Zΰη‹μ g’Ν5Χ.έT΅­ΒCH βšέͺΫDΗ³RΣ^‘N›έM…kφ+‚ސyΥWΰ½8/· +‹Άτe]i”ψΒF^™QŸ‘·–+²gŽl@ƒ™ΐyBνgβσΨ7FTˆλ°|P‚£‚ˆh΄mlάρΎσs 7DHFʘǚnͺ»~.WgΠ­S·MΑ‘Χφ©3¨ δe0η§Ρ{ΚŒιorg›ŸFΡ ·€Φ™ˆ-dUS lH—θڏΨΘuΛd ;"?eO.$LŽ’Dˆ½BγΗYD§Μ“SΒ§α;ψ_N‘νι#ϊ·τ³Κά7l_οΆ?ΣΧUυγZš>‚ŒUJ˜ΚuΠΗMμ9TH›Ž›ͺ·E.ʝ½/ΗF€:c11š‰α#νldgί6ΌΫp5{³qMs¬΄•J/ΟΦI€ ’όZξεuN—a]§y™wΐΥ·ΨΗΠΨ;T»& +jUh˜7ΤIށVφ €φ¨]Gψcݐ Μ,τ<μπR³΅Ÿ!ΤXEΌδδ«ύ^—ΏΞαePΧaέΉlγoHμκB8ΩΑΚθpλ„4³ §eΖ¬Rl40?4$ΥሁΘΰ9ϋ£Ο±šQΔΜD,žΟΡΔ“ΘFγ1›Gx ΐa2ΡOΝΦβ[o{Œl Ρ©n†ΩΗ@Π^#GώΘ ρŸ[ζ°JΔd*€°Χ™˜βλaL}+₯Px•L΄*…Ÿ!‹ω³¬ +”ύυΟΫλ _ξp½§±=2φž*΅Ή_ —U°ϋ)œ€­¨˜PkK”=ΜΜπ:gρmΛ†=c=γΉσδTϋkxR&}x”hm1”'I—_žΌ’jΦρdzύš: ’Ώˆc;TZί-RuϊΤ; ΒΛ‰>oΏwκλ½Ύ^ύS'‹L˜‰{‹“I$€Fjzκ5|'V2h˜ρχ›XUKκŠΕ_lΤ!ŸPΐ΅Έ%Ϋ0L½«ρwg,~’A_šσC9Φέ7Ξ…έ$Έ|»ν³Ρ˜ŸεΕ°}Β+ΗeΓY’No’h‡W΅Ίζa ³χΑρkΝvΣ$·c‚jZ)’›˜Ά‘;€AŽfgqb Ν“Θ;©½ί-…<’gΛfψJ$ +E GC‘$Ί†ΊΎ­snΡ„„t­yτP±ΰnW Ω\=[*ώύ5[Trβ’μ1βUη"‘j κ¦tΧ§w偒πcΐΈΡϋͺίTvΜΈεΉησ8Χγ}ήΎœώr³λΥΥ,}HάJ)¦}Ÿ·"‘ε£5Gˆ +$΅Z³-#ΆΒ@­ΟG„ϊ•yϋέΤΥ@Ζ$n;]5ٽΏ4ιΡΞΝ²LHXvxΞ‡X3~"έ·+I€£d/qμγ„εmWΈvΙwPΙDΖuάΓcο°lΆwύ46·cζ>΅j«β#)Ρ—nߜχ²¨ŠLYΆ]_˜«―g Υ―‰NφΥΜAδΪ&‚`܁/†²n[Lϋή‡ϊΎ±W8εΟWo9U˜ tgnΐbti +π—έςύ§[Ίσ_V;§^υΫ V Μj§JUɈ4έΞΣ›δΔ±– ¨@ŠLΘyΖ!•7œ‹½F^Ψr<Ι`»(–wMৎ“)ΟπΨ'b,Γ‘•+ΑΘ^L썞ΐsŸ7Jθ„K°‚ +ΣmWa΄a Ν<β3Ω› k1¬ͺΞ…\ΐ§Bρ‹΅~ Βt,἟Ιά(“JρβjRiτ4™N^Εdΰ{Aηe0<3ζ$4ͺd£‚‘πvVͺ< CYx&Α“—ν)ύŽε-φ°ΖTl°Σ° ΅νih^ΓΙΦ3™ šŸ3ό>ρžV‘ψ–HSl8^ξ[h†±J¨eϋH•²Nυ,ΉpθCSˆkϊ£·`„š½ηlρΣU‰fܟφ”1έS +$±_žτŠ; ’•GΞά5uLΙƒKΏ=ΈΥω!UGͺZΞο&vÞ?Α€Ÿ$²΄™rUkρko˜‘ ŒK%Cy­—Σ –$ΣGAΠγβiωNυΩΟ‘7…ηζ&N°’Β“\$7™΅³δʁS™¨/Ϋ%OΪ#Γ&ηV°ƒpz=J³QxC”Qλtœ„¬ήaXΘ ;α`Yi©ψ*βg\\;α₯ΝΩλζ +wWe;·Ήάd>’ν‡Ÿk₯„³cΜχΒJ]΄ΐSns‘)?=‘DŸ%τs―x3IQΫ»gότhΪΘrδU)νZFq7œbjx}}Ξ6ΗžΞΟθ"“―L λο87Α-ά›‚¦9sό˜ΧΦCD:/ΦpLΜŠψ8ž„§χCΑύ,–˜}γΊϊqvζάΘL›φYͺθ ±Ϊ ΪpΪSΑ'‘CΖs“ہ§zα€iΊ#βΞοΫ‘Ό]σ\ΒΫ΅ηn(Α™b=, Β2œTΘθœlqJ{ΕCa½δ*βΚ„¨°cIωA!uyΑ.Fl£μœ•fc‘‚Ύ‘yΗφ}Θ·¦²GΒ57}ΙΌ‘[Ωƒ †•we“βBq)ί-ƒK'%Xιλ~SψΙu_ζ5^R΄pTͺΩ²Ϋ|Αk Οξδ5υ B_sΡ57]3FεkjΈ„Š‹wύ΄™ξ‘`Ί•šaΒ‰Ρ‹#剫g½zΪ―†Ξa/―C/¦o)υζ³G₯uΘVΎ“e=ΡΜ₯uˆZ\2j”†Γ⣕ρ-SY2ΘΦyςx-¦)άΗ²Z˜ Ϋ,ΧβΕR­ΔκN& m―fA)G„:e3ιp―ΥGZZΓ~ψ%Ώm.ίll.SΙy¨IXΥkχθ$¨5ή#²xm±"ž!8Ζ“γŒΐ€ψϋvLΣ ης5hsڍRRΔh%λ’Β„F₯o: +ψγ¨°ŸώωοΫΈιΟχώC.嚧]cr.«έ^ ο-_#Π}{‡³_­½mGτπ?ά/δ —ήχ£ύdΚiαšA'… K¬­F€YFκίsfοξ½Όd$Ωθ‡ Ϋδpη±³gfΞπυ%GόŒIͺ‘€QΦΡvΠ’3ΥJ ~d¬ '†ΒF‹«Β‹Ηχ¨"vγOΥ€«Ω.ΚbΚ›kŸ¬L/Οκϋ±ΥaK@Ψ‘―`Β%ngnhbcTU"c ?ϊρjͺZi‚1UƒΆΑb…h@υ™iͺˆ¬ΤD5βfhv'IτΎ 56šQe NzλEθσz<†8bι ΐ…™Œ’0π΄‰b;Υ쯏ψ$ŽQN’7tΝν‚tM½ΪΊα]9h+Ν%~˜P£V=S-―|­/8”δ95w@φ]–@~'-©#MΚZšΙ―› +όD‘ώ\Ν=Ρ=w7ξ\Z²gηΤ–Ίχ…6κI4₯t›fXE§2a³zͺ<žk^ΦG}KL¦–š> +F„L'A0Ι%iΗzrυ‚3₯_΅‰Θh3lhU΅«ΦGΌJ,˜R*p)ƒ {†Β%WΪ‚Ζλ됆aVŽ‘³pΝ)##©΅=™ŽΉΐ·ιΆsƒ‹υ‘Χ!Λ₯ˆ “kH(‘γ"”Lv +“ΊŽhT–±›Θιœ(ΦSfΜάγ&›¦ΞVζjβžjF΄ΤSΊΌl)ψ©Θ |vO±œͺζΧ‡% „+7ΐω< .7p…š—J₯7ϊ€G(Wχ Sa_€½ΫΣ«‡ͺυυ‘C‰‚K))"‡ΡC퀐 +gcχ+(§κ6*Ψws™’‰&‘5Νv¬:Xϊ,mΔC +’)Ÿ η7VŽ#>r)ΔαΧ 4”{w,M}"‹ΉPΫ¦[5λ#n η¦–K n‘Zω})u;Cc8•ĐόT«ž±­ΊηΎŠC!οƒ=Œ+#ˆŠ@qιZΗΰQΨ#ν뒝©ΘΧnVΫ©f}ΔgaIy GoOΞ=Ώ½{quqwu³;ΏύΤύ’Ώΰo;σδωK­ΞΎή]~χiϋφζZΎ»³ΥζέΥnœΌΌΎώΈ½Ϊίm.»ܞοήmžrήβο?cνh‹ΈX‡W‘??|Β—ΏγΓ ϊΉΣͺϋ¦ϋηΏTwI­Χ »ͺ ‘«B©ΆM‚˜@ωδ²π"‡©αΓ…Pο„u―JϊΉjhŸ$¦ +«ϊψ‘ώ€Ζ²RJMt`ΰ˜›Υ[δγδΝnwΎEΎήݞ_^mvw3O%1™9*Yͺή-Š>o'Fl’ ΅z<°-ξPά4\χΓvͺ£iσRfζκb’ͺΡΕ˜ψ£œΆ€αK*†~qΏJB xŒΡIh~€J¨A:U>χnu€Γ»‘VΏCœiυ›Β_Μ1ΜΨΑ59Εύ*>ψΜ8²βΨω~¨KtύC¨λ+μϊc)ν)ΦΕυπϊϊ<χ+cαY»Αϋ¨Nšα,}œG¨˜σe&ί«ι"P•2θ‹P Τάx½Ό^ΒN=θ1&πz;뎏LL+¬†Ϋ‹―>r”sz—??Ξgx―yΰ’κ «υ(ͺχς ω4Ĝ.ͺΜK?p]΅"rΟͺ©ŸNύόΗ~έsΡΔ:ΉΙιβ―ρ‹Ε€Ά‹Γj9RQ‡qΊΨ+Oρ½W?¨0Υ>.φΚΎ•¨|ΜΓA₯;ΉΙιτZ€ƒ_½δΐΣgΕ—‹0pΐΏ1#―Ιΰ΅›„jΐ+‹Ι(LdΥ½ΪG‡!³λΎω-χ»H^%°σ΄=9;vρQϊπ՟œ}Ωε‘χΉΧrφe xrΆcΗΎ»Ωέ·S¨ξΩκζζ_jϋνωέέζvχυξόνυζo―.7žvΒ)Σ={‰i±wθΏ?έάή}ι§M9ςΉΛΙ··7›ΊΧ›‹;¬&ΧσέD}φn s‹΅ +ύΒiξq”`“³U"ί}(Z·'0™M‘ +ϊ"YοK,†KΧ¬φ³βΈ +N"αH$k‘`―m1λύhv*Xͺ F2ω.ΑW‹£AqΩb˜ηcύ‹Τσ‘Μώmηό2jλ€lcTΊ€“ +»Bς3ήi‘‡ΐξΧtn½s3ήi– 3χqNλ~crαOηγ —6χΠΥ ²αfΔσQwKy)₯Νο? ϊ…P5Ώ6¬ͺςγ!ϊ(…˜Ώh4½ΏΰψIόx^εΤ«χΗΫe~:Ψ‡ 2Mκ°tΙ•΅ +¬ήιœΙζcζ‹ωφ΄Η7Π?ήηKgXxτ&+­‘?RHΑc-tΏ…4[c‡Ό_7j‹WχaXc3†‹}H§ξ|΅„‡u!>T~\°­1ΝVKτ·ΆyΜVΛ),π¦ΫΕδΡΪκ›ν ϊ ¦6ΦΓb7{7€ΏizW‘r¨~Ο©βΦ$³/i!’i@Uΰ θr³:­7πnR +5 +9_ε»XPKk“ο΅δϋτϊM 1W{}38ΝΓι<1“υ°bύp9|3gΞσˆ› ΐ ό½7δ2tπŠQ?Κ^rnk=έD:ύ―ry}υφ-₯£+ΘλEνφ―Ž/#Έ§Η>βfλίΦZdΛb>ηΘfξ@gUΠ"ΚΑ§Ξ/σΡ΄ΤΙ„.!w&³ψΓ2:§±Ι‘ €Θ6 }YσŒcE¦z©³‚ ’«rΜΰΊ+Χ?‹κuZΔΔ˜$@Μ’wΫ₯ΦΪZΈΐω£¬2ΡD&…Ξ.‹Fœ +±CΪ!ψ θ𒝣"Α*ˆ ρδλ(΄s·ΔνL—_ŽpΠΰΕ"€Ÿ'Τ\)λLΗ+yβ²α +k|°.u’5 RμΈ•2يȱφa%Δ#΄X72\]¦#Π#₯Α}K’Œ2mI%pHF=Σfx=f-*ΩτΦΗς.)[π/€άK…B€δ0P”wΕžMsͺΨe€SDCΦrΠ"w*_ΌY]ž‘šΤ3­cey'“Υ,­1ό€h φ\M«² Βeτoθ!ΦψŠδαu*`³’Ηϊ.Žέ |+&iχμ»»Ϋ«έ»ξd΅z~qρqϋϊζξœGKQ<_―'gΎ{ώΣfu½Ω]~s~wρώϋ›Υζί7·CžΌ:zεϊ~¬“Υ/zωbRΘzi³ηδΒ]œ6ΆΐQΐV*Icva™­b€ ―mφπ^’#ΐUΑ·A–Œ‘δ–JBΪL’ζ―Κrτ₯ό,ˆ ~‘JЍ?’Ήˆb „n ΄€…ˆ’I\uxiR(…d7J”ƒFΌ, 8ͺΰ•ΆΔ― ŽΧΖγΉ ‹ °1Ϊ[bSΰνA;Ό"@\fUΰΗ€°sh4m‚m$$&φTΨƒ£ΐ9Ή™+€—ISδŽπ―ο‘mΙ"4ϊ >›‘B䰳ÐbΦ€δe),Πάσ€θΗш¨Cπj(#1‹¦F S€ϊβ‡₯ l|₯ЬTˆΓ«θΜ±L6KY fθ EXΌHD6QDξžSϊaπM…”–™ΡgρxΪ™‘Š’7₯« ψE„,ͺWΪA…—ōζΐcα₯Π?“ΤxF @czl₯μr„ZOOΈKŽνυhEΰ‰> y’TzιΘ +g­ 8Ά[ΝG+·†'`D7"‡Cs³Aϊh‘]ΰcΘD AΕX'~€IF΄Zγ +gσD pΠ3) .LPΔε-§ŠΑpςΒ‰4ͺDΪdFχΐΚ8―Άέ:Ž#ψηφΕDςτά'y’ΰ<8‘ΰ@TΆ!0m1&)’@θοSU={.’mAk9œνιͺf¬“ΤσΣ€ΆRƒ mΕ~g#«†Δc +Κ…’†‚τ΄‚kΔ]p― +Œ½AώFΙ`F. α5‘*“‘hΦό3N(y4%wZΔ#―b΄rL―r–Τ'2',€υ¬ή/ΫD£UšGr^ΔμWxHv•\|Ό£ihλŽ%ΧAγ“*ΖΌ±t[3Al‰œ‰ΤΥκ6Ή3AŽΓ`Eq­ιr^²ADb’QUδ)§]ΆΚΔ‡σ'pςξFPϊΎi+ŒHNjκž£‡κuψίynZ:Nη`o£©‰‘L"ς€ .;jΞ³υ`nX%‘€ƒOΝΖ(O -Θ!ΛήBXHαHα IBΊc‘€šΨ›4Z˜…€a’?κ :Τ6EόΩiPVZυ VE*q¨ΈΠ [—)Ν₯κ&X‰s +›YZ–Η&‹—Ί0aόΧΥ6”1“‰ΝΘXFͺ¦Eδ¬JύHubѝˆρvUψ@‡8>w'ƒšJκ°Ήωd-Μ{M΄ZŒ&Ebσ»ušuΡ|ϋ„""Y‡ήIΤQΝ\œΟ`D~ "A†%ΚSά Ή€6\6 +ΕCΘθR^FV_†Φjv—št"Ξ1€‘IU{pψAΊΏ@‘HUΌI0PnΘq-π»‘…3Ν§=Eiš]FχτΒ$²[‘ϋ  +䦱’žψ ZˆΓ9 +ρ1Ή‘.―˜4 ‘§St‘"Οচ¨πθΙ*χ€*’‹τMβ¨ˆΧDNK.ΓίyvPΨjjpΞBJΈ;l}4 bγx8AΆΓ0’Ϊ¨Αλ‰δkuJ-“IFb% vmΈe©V™³ljZ64μFcVa’KqΜβœΦϊτ†ƒƒ΅ AΘ+NOωqξ+³S CΕέ< Ո Aχ—…ΛξΘ]E$Ε£υi+%Ξμ™t`‘œΏσΡ„X\<ΉύšΨ‰YH‡Κ™ƒyΰζ7kή!2:Θ2XΤAˆZ}6BŽ―kΜ>ήP˜N£ΣwbΥ`@°SŠ“z>ΉŠϋšΉ}ƒΒ•δƒ`³$*MAξM.ckΚ4…"hΌmni(†œYP·β₯_‘΄NvPο ψDΙ-4 ~Ί© 'e)I~.λά%‘Τ8e™#ρ 3M{H+5=_ΰl;XIφͺv 2†a8Ϋεe‘vϊ0A©ΰeψ‘bžŸ‘h`‡ΓqQψƒͺךΆo0>H’‡=okIΏαΠ°–m•ζ‚Α 9‹ζmΑS8£aB―“ε(Υ =Ν%ͺ4Λ œ cΆΊ$σι2ϊΪ/’Hb―ɜΡ,k€5l’σεABi³…JΞ.Ν50λΕ§=Ž\#εη°#Η;χΗ₯£¬7T?ϋD•ΑΨΣάαAZ|PΫ¬IourU¦{θΤdFξ'€’D)!h”Š +"(V -ŽqKŒ6¨%L0…¦0Ψδ/XgV‹UΆ)t!.t`”Φ: ;h₯Ή Χι^ρ­F²*ˆA!ΖF!QF™ )¨ ²ΓΌUΒ΄¬UJœ5ΰFMMcR]‡[r[Δ”Ρ"cΈO₯mΛ•dHJ˜Β¬χ•tnΆ«¨:,|•x€—ͺϋωA9yΚJ¦‰Εf‚p¬Τ§«€,ThΌŒHΰ «k§€Φά€γ2Y€Έ\‰kμξ8υyqfœο%Ÿ‰ΈΫ"Ά­fŽψQK’wϋ˜γ/M{ceL|’όΆ°;8,R‘`ξqx¨JλMμA•cΡ€€ΤI/$G#ψ yνXδ&ι@ιƒΝε†Ÿ|Z‰°Έ«s š*Oy`Zƒ'pΠΦaX˜@„rUߎτ"H +’+ “ρ;(ρ“Ι78 7¨±νΔD»ΎpΘ©ΚJηδo +™„)ΜQ,Ok˜Eκt•Rbzχ8 η^ +ΩH»›gŠΌ<Œ†FΞSΌ$Ζ(±1Ρ›88‘ϊi€₯]dιϋ3§₯ Ής%†Dτ7CZœΓnγl@±ζ+ύœζΪs[Ν@Ψiͺ +‹ tˆΘZό΅¨1ό3|:RΟ‘Π‘?­ΡxbTsΨ’c5(Rυ‰©¬‰/¦0\žG>δίς^Γ΄βJq9βP—RΙμNτFk£VυΩ8ϊ<Φy?4x©.ξ?²ό(n–„ώDnR ›*Χ^FRJuυοeδuξ€θπ€5Sψ‘γ‘"³γχoγ©οΉHήΡVqV—ςYφ g ₯8l¦†α[ˆώd^#Η”βμ^V7ΫΠ²F†€£O<σΨPž fGŠX&Dv)„ΩμFΈ”₯΅¨Θ „…³Œί‘ bΛi‘X³ΛMC‚ιέhδ™9IWδ4zΐ6Œ‘Cl―H³”ΤhΜ„u·ΥW=8Ί@)!y‘&Εe_Χ$σ‘r~ˆζšέIk; dΡΕπhgΆH§Κλ π;²¦z{ΒgσΖ)₯ΰ…xeΘX_ xόκυΧ'wχί\]ά_½»=Ώϋ΄όKGœ1uφγελ³ϋ»«ΫŸ—£gΟN..>ήΌxwΞ­Η˟ΉρδτtoΟΩΫσχ—Ο/oίUν±αtσΣο„}j +,ωυΐλ{‡Ρ΄aορρΐQΙ–3πϊήa4mΨ{|ΫΉ-8ΙϊΉΪAμ`+Ηϋ™eξ71+ˆμ’89fmα½—ΘΊύ²"ώ@’EΞγh‚Vq[iΉVΣGΩ©φ썓m’Ÿ$¦xAd<9Tl°«£3ξ7(Žύσ]Σ­ΐŸ€ Θ@4gάΕΥm£^"₯ΛΆ³£!₯ΓͺR έ‘VύΡVΠ‰β–ίΦh‡<8 _‹”?f\Εύκ%{AepBYωή₯άΆ{Ά[Ν™έ6ΓΨέβκ~©\23H ͺρfΟπΪx³Gf΄`n妍ToΤξGkΞ4I‚ΗCχΚ ŽψΩΛ"·…μΧlιξeρi*np)υλΏ–6OΊ,»¬‘ +«•ύέˆΜ”ιήKmw«²μ(’ΆVΤοβιiφΤZ’{ PgR+.k>iνŠG;šw§Ή}G{I™%!οΦΏΣΑμ™Φ?Ÿ‰ΛLώ(ηά– ’*Yω²RΖw6K-3·΅Žωφ!ΡΏ…εσ\ „©AεAΪϊS-ψϋϊ)ό^+όϊίi…œ1ΙύύηŽΠΤb— τŠƒΙ<«(ΉMt€Σ‚’’B΄Ο¬±Q€`G‹ΟV†CƒδΘΌ lήΆ¦τ6ήΓ½Άx··˜INLfΚJSΪ²XͺΙ:dά―ΉDέΗA`k‹*1zΡ0"’δx@δψΠ1hΈ 'Έή8¦νv@ψn³QFΤ27B₯τΊΑcT‘‘ZΧΞV(ωH@RΖ°ξ_UYδJt‚ς?|΅ι–‰\ΩΈVd΄—mΠp°­Š,Δ]χi΄‘ΈφPͺ½­ηθRͺώήΏφŒ'E€Ή2ぱB3 έΦέΎ;"΄vΞ#ŠLΚp―©υσIKτڝΌi²|·!υdή™}jHθΔΌŒ7lΠ«Q7cJkώπθ κQ"¦Z1~-™ +VΧγ2IJ• ΪΓ]ƒβh{>ίQ}ξ"wAίlω&υύ˜F<ᇴ’vΝQΒσηŽΘ˜λ]χΖύΆΖŸl¨Vα9λ)N£ΉΚEΗ#Kΐ)©ŸŽ«\θ…θ“Qvz[αOq꣡>5 Ώj]\ ών•Ξ{<Ν€Tή ³jΟαl“|Ah˜ŸPΉή›βcΈ!δφ>1Ž€o“όΧLˆψ]nŸf‚<ω€/ˆλn&-ΊπΌΉŸm½3X@Ϋ–pΥ`4LuzŸŸΡ―Ζ‹ŠpΡΊ™$C~θζ†’•n0ΘK/ ZΓι£€|©ΆΡΗ‡1UΤΡ‡V5PαΑαδμœ›“l³>‡GιϋCϋΪ_N>ΙξcTDχςΦΪ„υ§ϋ_υς?ˆ4—iih¨PKϊS„ΣυS>ψ½HϋυΏiͺΗ$\€0iB£Ή¦θP]Λ‘ +Λ[ΫF‘‹px?zˆ‰YιQ!ώ\κ)Α²–\Vlίϋ…0 +Œ~­υŒ  +np—ά/tqΧΐ 2ˆ+FΊL…(ΣEq’MMψT +γΈηΖΙj9 ΏŸή·]ψ'Ψt|~nl…Β–œ΄ˆΒΌSΔ#ξΛ–ΖΨα,¦ς 0ƒGΜό„Xήͺ?RΡ*OΒ\j|’ΪkΉςœΓ––I%/EK0dδBIδ‡Ψ _pωyZΕpΩ—yτ'd|Ξ*4Χ'$7tλτsm‘Γ:q›ρˆBJ)ΤΧ7±—HΏJπڞ‚Βvϋ©!$^ύ¨σζ{a­>Οϊkυtφˆ^ 6ΔΪs•Σ ]19`Ζ—η›w"6€«οgDˆ›}짚.ίοj*­ϋδ!‰xΑ3Ηb ϊFή‡ΎΕ\°kπ¨ώμ-)υϊ)όžΎύΐοθ[8b*υ-d!;Β?ͺJS¬Υ υΝ‘ŽeA*ΎRR ‘j~T²qΩ¬\¨+ °P«νύΐ6(νΎ{=f`j,ε/3— ΘLΣ +46ΦΆ‰ΊΝ£ϋΘ“Υ‘?ϋ,ŽΠζ# yHΙΛ­=ϋԁφ Ρ-¦ώΔtj²ν–E͐²“2\³ +(X|7Zρ¨•£cήPζeοη;ŽΰΊN5d…&pr“'‹z‹‘/Bˍ#Ϋ5œΝΒ^EW§?!ν΅@…ΜΆ7x•Ί5a»ƒ‘‚ ;ΖFΘ½,„cddG‡―žοš8žXδPοQΠ­•Š£’πhWΏΝίΊ %ŒΔ=΄nΐm†τ½π¦…rIma€ͺmšK‰)p¦.αi½ μ“ϋϋ™n’Γί„Ίrmvˆ.±Τάκ ςiςΑΜhΘ©ι3…vί_Ω’>4₯ o…L yΉ­€ΖQ<¦w3‹ δΎ C'›<”…ΟλωΞ—5½mηώΖΆ6S FšΥΩ±¬uHk?23D ͺΉω!Ίƒ6ή ΛˆΦ5v|W wΔQ‡κ™n&6AŠnγoI):[άV:ZwΥεΞβ«eš\ +¬ΫSΉΆO©”Eι{ ΄ΜZυωNΤ~ά6έύ!€Ώλγ됆ž»΅’|W‹Ί»¬Zmeε¨3©—±w½ΛJtb:μ΄·s΄8d­ΊSwλΟt0({†λη;Lu(”ƒ{MΠΨ,Yϊ«lΒ›θšŽΫV§/‘ώνΊA8Ω₯ E‰Zύγ `XΒ•PΥƒ-` μ&Sο§ϊ*sΖϋ—H+£‡ί½ΗΦ[¨γύw―zAv”dλE`ό 1?‡A‰‹$α,q]΁<Υ8 ց\Όυ‘O½jbΤWΌ8[PΔ:ԘΰΞμizΡ(β+ 8ι[&@ΈnΫ¨•|γGΫ?@Y!ϋ ΓxΔΛ.¨>c\ΐ3ͺξoKσYY™_$3}ΕD–C;FΓD²„Wά^‘ΰ3vτw[(Ο*'θByεμΩ-«΅E]γ¬@³μΟ!FεZ ¨β;S…­Έm·υ@υ*τrΤ9w‘H]α'ΚΝ bΚ"3Ωo'ΝΓ•PΔmοQŸδk{… •‰–[–±¦)‚J¨V–›3ζˆ₯§Ήώ¨slP© F#‚э9Ύ«+,tξ3Θ…˜P|AX‘0ΜΟw£DŸΫQm-_…ˆ9ƍ‘.pΈ άJΗΥ8Ή Ίξ ¬I?Γ7'ψςS}dBBŸH~KM“Ήh•˜η-Ζ³U +vΝ]•ΰ©<’Ÿϊ”DΤuΜ‹Ύ %.dgWˆ:ΎΊ'ˆ₯"Θ§₯\‘HξW«ΔΡΰ§·{(υ‹»Š“{Μ/rάΏ0H;)MϊŒ¦9/Ÿ•ˆa Œ?ΑΎCŽz‘Ι$mΊο λEDEΑ.μλϋM2igrπ ½”―i’ΞŸόΎΚŠΪcΦ‰2»†Βώηθ +±I©ΎΪφθ’11‡qήΉfΕHyrΗ;€΄Η‰π<²b'΅"e‹ωΞ+mx²’RL&Σj﨓€ηe 1*7;tBbΧ¬YrlήZΗ²"/Ρ½Œ;κεŽRζη½ΦK5±ΞN 8σ`: +sΎ΄1χΝγPˆΈ(|`L">stream +H‰œ—[Šf·€WΠ{θΗΔΰ© ›@ δ! In'qπφSRUI:jπƒšο?’κ~ziΑϊώ-κKπύΫ +τ)°Ρ—· …7όX\‡±±:²β h=οC¦Έ―ΦΌ³e=Ρ-Χ―75ξΣG»WΑ‘λ…Nl(aυᝬP&Gοt2ƒΪϊ#«†^½]>ΤV·ΖΡ°ι~­σΥ³D€Ο°αQΈύέͺτθCνW·0ΘΡ-’§,£οΖs’Υ Nl\ *۝!ͺνj‹Aλ£}*€&kZι¬g‡^Ωsvhƒ₯·λ0©Fη%βŒ;Ξ½MX%DΖd«ςπ‘ες R…+™Ρ:Ε‘ΰO&Κ-Y°‘ύњ4†J°αD΅FLzλ˜#ΧfπG»Œρ]―ίILΓΌΖ«֐΄a“|‚γΥ=ΠVlzeΈA(Ο 7ϊ•α†ΈΙ•αΫ(;Αρ%δ3„Φš‘gΛ:²£E?²ή Λ3λ1ΊϋTΆυ-£œpsŽαabC-τί0½bl—"ζ‡cqR­„Ίb­Lyv6)”μ'+f9‘{ΧΓ>R<Δ³)Œ>©Qχ·Ά U/›ltΨnΓ•³17 O΄Ό ³ξc]τςμRaϋΏVΈ[Ζ0ŠΦ+xΐ¬v~PϋsΡ3B«Se­TΘΨv2«%΅PπΟ€wxΔΣ‰–MNΈ +y^–>ήά~8dKjW€@Ξp:΄?C‘ω\&lΈαYΛέ-:_}/ογOφ ϋΉΞΎ7+’?1ώ΄‘μ#>8ώDοŸεύ»Ζύα/?όύŸϊϊΛφΛΏώσοΏώΡnώσoεΕοΏώήΎŽξ\ήΏΝ7ΎΪ―Ώž‹Žuz±΄ς&np ΫƒΝZ;Ι,‰‘ΖΚ%ΘρΥ°dξMP«C‰“άσ2­Q_yΨ‰β$cΕϋ»’²e§Nu‹'©q!υyp M½Ιl2\G―“Υ ¦ž€Ε Œ’₯œ‹“£ζζ6r’ΎM΄α΄©?‘A…ΌlfΕ\τ2Η‡=Ζj8ΒΩeάjΕ­–‚!=ΡςKοΧwΖυΚ9I 1ζ0';Έ³€0Z«gσ@RB-!/Œy6Žš)ύ €tŒJηj1P›[άπΩΤG›M4Εw=Mξa1¦ΣΒ| \eσΥύšγ‰[!Q4bY«9€‡jLϋά«ίζoΘ„d‘u4αΜ†ΌmZ6MS’ΕΦ±‰π₯AάaΌG 4&΅Π΄(Œ4‡ˆγ3#Δώf*@±K4Κkš] >Υ6†γ)¦Oa†ƒ‘N+M½qJ&₯Ȍ-CΠCŸZ³o†oσλpJ4 ƒλ΄ΤVΰK»Ύ3G†-η λ‰fC]Σ]Έy’GenΉd>IΡe’‘εHΛΊ³Ÿϋ›!@; +Rο¦±¦ ŽlcΝ<΅EΡ(€™mζ-σΧiqŒΫ +eκΆΪ\ωr€ Ίΐ₯€t]y 1nΦJ[x£L™6ϊίγ;ΌMsΛ•Ϊλ₯«ν―’a»Φ"|½Ο_Ϊ…ΆωGM’χΈL<λ-|j>ͺYϊ’—$R\*„pΠ’κIGΊͺΓD+g€>Ύ3„ύcξ¦ΩΖ΄¬ΠχΕh6<Ν¬‘Ό Σ­Ο†ϊΕηλΙ{¦˜4λώ“gXϋ–/₯39χٞωΉ +@.Ή#„bΙ34Η€ΉΧΘMΦnpΐ>:.Λυσxτ\S—p{?{<φ5€Ϊh ψz|sVƒ‚ΧαΖί뉦QQNQ|Ι=DΆΆ(v z,Gζ0Ώ.KcŽGρ²ω­ΚΓ7˜qzP©ΒFk«ΒΝ>žl†ΝΗ±Ξ‘$ηΦaœΚŸ‘fλŒIΨ„―ΡΧr^υ%Z؞«ΗŽ‹ρ]€ό°z0η~’5̟°I>±ΖÚŲb‹μƒΆR¨BΙω 㠟efVA-άd£/O£μUΖΛFςzοαccβŽqxνUΆΝBˆ·ƒ‚9κTΜ£$=”mύFΗΦ·α +(+?ς0±‘ϊŸ«¦δ‡Λ σΓ±˜—g$Τψ’Θβ&ΕΉžq’ϋ͜ϋn¨]―‡c9Ε$ϊ€†5ΕK[˜₯·M6:l·a §vz’εm¨c#žσ(ΔΏ=»TΨώŸCτPE΅^Α“;γ5ΖέΌ0·γΩά^§xe•Y-©…Ζ’»f猧-›œpςΌ, |ΌΉύpΘ–ώ2Τ:»ωξp:΄?CΒ–*oϋs²½αYΛέν;ο|Ύ„–χŸφΎš m¬γO]K¬ž›νΈ£Όπ›»μoεΕs›ύםΉΌ›/|΅_υΑChn-θΝύη€8ηYτΐt4Gyτ5Υ‰9θV\KΓ¨Kζ¦Β2{ ‘˜ α0u’u˜))ύ’ΆΔήsBξβ·M;η -~™Λ@=-ξ«S± ’αFσ¨δ¬s~‡s›Oxιο)C,5OF]Β΅jς) cς>•ΞMε€3β6ž‘χŠgΌιί f6Ρͺ/qfKˆ2||F E‰6 +uuλ―ο yΆ.8κί@£°¦ucΡ5Ÿ–π©2υΓ§ZόG‹{ZŽοC‰³_­­yGτθ?Ό_64ςήf/ν§΄MChBΥ@)AQΤ "ΩAV±ύο3»sΩΩ}ΪΔ’Ÿβcxή£½ΝΜ™™3Nr2§@§Ε #Žσ uΩ+ >ΔCkγ­€μJNR΅pΏΑΆNκ>2 $Ϋ8eΡΑEwRvβC +σ-9j\η1WΤG +b[+Ε»šάΘο²σ#PμΚ‘o†Q㷟¨*aξ>5ΗLouψόΒqΙD‘Ψ;Sνχ‘ͺπ:Վ­nŽͺ$‰ξŽ&(ƒΗlΛζ<΄θΈB&Ü₯κ\Τ,κ€η§8;Τv¨;@· +Θe―ί―EέΛ₯ΫF­jDά¬RΡ±5p₯OΉ°Η•&$φ3v+³p&IH +us¦T' Š£—β’)μ2eρΔ)}KΉιά₯ˆνt +G?Œ‘’αrŽžα κt’½μ$!Φ!R|½4΄Ÿ Γ ›u·Bcnb²!f‡^ψȁL;VY—© Τ<ŸFZBސ}‚#θ!n"R›@ΐΕ,Έΰu§TZ‰&έ `’ +«»1‹–θ7NE@’ψ$dξv£P Θ½cn γ\βΣΤC7ΗXy―ό@΄"0DJ aΧjβ­ZΠΝΊΒaœ§₯Ζ bΤ[UΔΜΧ‘Ψ2Ϋ‰mΘ*VΣyΡe½RS*›ϋ9ϋbΕT|h{»@P6wuϋl=eAnerkΓRwkcμ/ρά-“Ί…†ΤΩfΚ>Α€eνΡ:W—ΝΘ²βΉ—³ θΔcdΨ^π΅ͺ*h€°#7ˆ›ω»˜ωΒ*0erηaGΧ… ]l‡΅w©-τPfυ$允]€3c˜¦Qϋ θΨs˜γΝ\κpτ­ϊ8»† LgΙΘ>ΔΈes€cΦtŽΘH·–„H Ν+Κ\γηS¬–'#Τ€8°a² +a‚"$τ°©6ΌΘ#J|₯’βΕ΅fdA f6w™ΠLγ#ϋ$ΤΚ‰ΫG9£gκ. 5Ί¨C_”A°g}d-™Bβy‘KΈχαΟ7ύ–« n™UId‰ΦO}­’6…Ίη8‚>9Ž6Τbr— |ΐ–γΖ+3ΗΓy+eηί–ΡΟ5‹8zσlh3ΑΤΦΜο!b•Hh!υŠ5ω*°^#$…ZCXqΞ!C"˜hΤ’a…δTΪ8•‡ϊhEQ-ΠJSpν–PΆ{Α•Ί½Ž{ΐj$iLj,—ει’ ΧMPλ$ΰ²hι· +Η’Χz>μUfψ“γZ= …^)ξH(!@WΓv t4½ΤωΖDŒŽΩδ1dbI₯ŽiD& ©G,Ȟ›‡Mχκ6 Β Φ”RCέ¨d,·|Pɘϋ΅ƒΆ’-͍Ÿ;υώτ=.‰ξτq]―/,c~₯n—££qΙφΧυ£άιΕ™?ΡΏ'xqύέΝλ‡ϋΛ‡›W/ω/ψκβ̝ΓιΝtΛλή¦έι/?yΡkόλžF%sΓ^lvwά‡ΆWTGž.c%,v μ†ŽL¬ŸGFΊΩ;atμΥΑΡξKο=nhv9N±ΠTΗMU»n0Žλͺ]žβΘTν}=ο₯vζΊ¨v2 +ht^h‘ρΔ„v€ΑDBΥN`EE’&ΆξΗΈ_ƒΒƒ0ο^‡Σ:ͺ’%„,+ ½±ε\dυƒ€ΖarΪtkRlwήοq*ά`γ‰ž³˜ΈaL ;2™χ@ί³ΟUφhΘΨΐšΒι‘‘ωεXΕ­iHμ>ΠBž.̴ٍΏό˜g7dΤ[λ@ΔΈ?ͺωάΓσ›·Χ·Ο―οu}υpρξξ›W·ŸΎΌόζφϊϋλwOOΏΑΥΎ•s$ œBΔ{w)΅ϊΜαΏg//oΏΌ|ΈΏy‹KΗ‰ρωεΓΓυύΛvq{su}quy{σς»ΟξoΎύ³œ§gŸΏ|X['όυέΧ|΅y¨¬€Χ}φο›o―_λͺύΈOίώπκώaτ;όΦNOžžΎϊϋΩGΈΠ}ύ#Ωε²’[RDα'¨wψ‡*ό›ΌDή†Z:8§[A脦ρνύ֊]Uη28œΪρgfDF¬X±ς?}ŸΧυwόγwDπΣmωΥ_IΖώϋ κψ5υϊ-Ύϋεεη—ίόl°–ΗΏψW~ωδcϊε??ώφχςψž"χ ί«…ΨσZ{NOr­ΓθΩηŠe‘:θ1m©eΝ½λŒυ±λU³γuOΫNT-j`Lo,½Iο―½½Ύό“@~οέ)ΒηΎΪΌγ`ϊ¬M “"―Ξh<ΧήmΧ΄‡F¬…§:ΆJ¨³¨Œ²π6q»­+’FυͺΆ»–_uly‹«ΟεmKΗIΫιo„{ ±°”kφXDχœ,―{³±\ε΄΅ΊύŘ`‘“t[‚l5φ#ˆZb«Σ΄―=Ž"+ +d±dΥ₯5«βŠ!ΐ½Ω+Î’MηE ^γ*j§ ‹­Ι΅X3ΡOъ“ωϊΘζ˜*&qΖ<ε΄Ή–ύ*³Jo°yTY‚ +ϊ±ƒmD—ΩJΛT1IΚΝ–FΜJΟ)Ηε•₯·$–ΆEΥcΣyRξtϊ"Θ'ι$ψ ΤεΜ-§ξͺY©ΕPWSΕίΧKεψΜίy·1 f?†X¬ž9ˆ^¬Ρ”8~Z;aψjhߏΤν’J°ŠΛ1Ε>ήΧ,Sξ2GξξksYzλ+AΎ•Ji&ϊΚyp‹ut-ζΤ#ΖΰόŽaMJZοοk€„!šP± WƒλU›vu6 &QΌΫΙ μCΝ1΄ΡΏΈœ—ΉmjsM­™½¦‘ΜεSΎ O1 z­v·M©«³LG-Φπy¬vΗ,ÌφψΨϊi§ΧSͺrm ‘M:t»Aψƒ~|θ‡Υ† c’ςO/ΈM&ν&ϊΝ{΅ΘΜvŽ–YŸSn&…ί’-VΜΚ]fYEn9γ<ύη―}jeΗΠε{¦^taŸz§‘ω>MH§Ktν]ή²(o<Τ€ƒ³JΣχ-%=xKB;!Ϋ4„ϊ•{Κι6΄%œιR'Zn:§B5#£^Σ&Ψ΅nΓh +•KFdhC\9DΪΤŠο©’ؘt<-¦χ˜.Σ‘‘u΅ΚΓ™λZ!'^‡rTΩΐλΚ”‘©#’ˆ°³tj©ƒΛ₯,Ÿη,\ΐ¬Λ1Eƒ±Bπͺ]wΈ%ˆφjo~εEΝ5ԏΆT’dAΙP-†έRIΥθ†Ρvεu€mΚ©…!φNΡ»zστΘM3bΨ΄OρΑτ|ΪώΟw¨3žtζj*4Šf–κqEJΖ"”'΅4Q‰.tζ²eΉΔ*¬“ήaέ–ƒSiπ˜δ¬:t“_ZŽφ}γΟύΙDPαΔΔcχηž΄™H‘MŸΔAʁ ΗΌ φ\Sˆrτ"θ€έΖb;„rrR6kΎqδtΪD~“σggjμ€iiTπ]ΦtWpφwK£†Δ03›'\·Φ‘I€zv Gbͺ1L€fά\Α–€λ5©•”MEd0r•7;jΘ±i.“Π3:ΘΝsšš[—K¬[U’ Θ…lχ'V΅Yθy P#;4W²³Dβ4ΨcDυ$nΘ‚dʏ‚ +J3O†s›ΓŠ8Wΐu‰Ψ„ΰiξΙΠ<δ4ΣΫ“ΖۍmWΊ΄i' e’+SYŸ¦±Ί5£€‰4"Ί|!XΊ θšΖΪξ02ƒοΎ-ˆΊή˜‹Νhw‹I†AΣ'‚蕇GΟnR©―GΙ-δvL„2χέ¦Β!ώQ3έ†>ΉΌPμπ¦’£gYQ%·™(€‰ά·"$—& κ{'‘0=‡L3ω‡οYΥΆzεA§ΣA†¦ΚEQ±έ¦Ty‘8 δ ί†’wDν‘9:y‘ΖΨΙ£Ρ¬Oq[.^zY%Ά‡†²)Tƒ£ŠΎΦ@tŒΟ=a" ‹ΊΦΜ€<štξά˟ΞΨg~„\ΙYLk +>‰OΫέ{}ιε&ˆ^‡$™†ΚSͺV4§Mm©F•ψV\X44iΊτΏΈω(ΞΎσ¨%v]ύnξvτ λ9jήΏίͺσΎ‡9—…3F₯u5Jά$Ιψ ΑρF;"곍6[ό'iͺKψƒ+Υ¨ >3RυH±Δ€G‡W―Ψƒ¬‚άΉjΊΡL—k/:…ιTtαΆo‘Ρs2<ΫΤτ?^±Ζ½§’€΅DΣ}»Τλ§}άζDE¬f@Ιω‘š(ΕbU!qAΪ¦²iP:ΑΗΜǐDAP©ΔΝϋL# A)‡ά ~όΪƒιό=x°s |ήτNIΣͺ‚k«ω4,τ„«dtΪ²y6ˆ›³E,C}Ρ΄ŠΑΐθθoΩ»Π}jΌα η ΣΎTΔ”σ–KP.©w[Ο1?y; eš7Ϊͺ•ΊΥΙS±[ά2Mω>ω!ZΑ]€―χƒίΊW$ΟΤK:’]΅κHΡGΎ/E5{ΨΤ’.―g94xvΫ°$³ΝSΥύΖ±€Ϊkͺ%~$'λ:ͺΤΧ 0 ‡Nr₯Ϊ­ΎB ΆVϊ=€ς»‘iυ}4†τΝ I@(βυ₯#/&ΰ"T€ ν+ςΩΔ2Ό46Kζ3₯©Φh} κY€Μ·Υk·₯ΜΪΩρ›σ~YςΎλκ©v֍»J;~νC~–―E"–)‚ϋρ3SΝ‰§Έ‰ž”6:UοΦΜ +΄Ύα‹ΖΙ—'•“Z¦QπΌ„ΖpϋMM―H1’FαXž +ez7v ,jτιE¦©§NW±ϊ­&DJŒh]lnΟ ΕPόπ•jj–XzК4Csgw GtζφΝRM€j%G»΄gUΚθf΄€Έλ‘Z³”}:Ϊ΅ΜzMΉΜ€a:DΕ+{_q^dΒƒ$…•[sξπ}1kŠa+Η~eVΨb›u‹%μf[(-ΘψGeιPίχ–‘@4}ϊΖR₯EeEΙ=’ΊΫ’₯πoh©π$^ξ²…ΓI5!;ΦΈoΏq+;aΡ`aεXVε“·²TgŠRPM+αcBBβ,#Es]d†•& ΄²|²’&Ξ­huλητ$Ρtφ΄{VZ~θυxί‰]U„«Y’)―¬£IΉψΙ–πs•δ•έ>ZK»šΈ=’HΫͺYvVS^=+ύ·KŠrImγ―)Ξ’*„‹οοΛλ₯+oκΛrSa\~[HΎ,ΡΆ,£νE†%@Ηy%}ΙΦ-hθ„’ N‘f$^hο}ο™t»»OJI’_#¨n΅₯’D|„(ή‡ +΅Oθ\ΩΰΪΊμά5Η6ξͺ-\qΉHΥ§LˆGΤ6Ο&vY)!<τΥ2‚‰°Όo…¬Vξ-#Ωw«4Ώ1<oœHτ’‚32’\gζ΅yNΎ5ƒί…°³o¦ζ6•qp-ύ!M?y ‹r‚―%ιͺ‡Ί­Jtή»ΞύtΔmgŸύŸοrΫ‘κΈΒπΜ;μK₯EφΨΘ²"ΫΘΨJξP3ΣƝ =£fΰνσkνξi%B ͺΊvΦα?˜Ν’. ‡Ξ>k»w6—L+πD@d:>–}§‚{Ί‰0aMF‘ΈˆΫ’:ŒkK† +5κΈZˆ,'Εψ§γ˜›΄ —"iΙΥ}"’3˜½Uί¨ϊišsρy”Y.έ»ΝΌζ8ζ‹γTβ#“S Υ’ͺ¦#g;‘€kήΊŠK’‚Sβ³ξ’Μ•\£y³–TΒΘΥݍlT6Tœ!Ί'Š.ρ,­V˘’dίMΞMf₯τπi3«ώcnšeΚ°"•"΅εν¦έ‹ΕY^lF§Ε’(Βαs‘ώτυ8 ΔΜΐ•qœμ§Υ +³ΚUϊΩ³% ­#aSμ3vtg‰ζ±§(S•Οq#mϊS¦…©S…TΨEτΉΚΠε»•!W}}–SœΙͺΡαeJΛaΟΓdpl°P+Gώ°A©GΔ'΄₯Χ+5h`p4.Z%‹ΩΦΰΛ8΄EΑtνCσ’΅ +UχW±Έ8\#Ÿ”WΥΏ€qA‹³κbτAJ +φB4$P”œ²4P΅ΨA+F½AŠ'3ќg‚K Ehkψ¬ΞΡ¦΄a{Α‘nFΤ±κYM–†γ1ΈhoΈ,ΎPTwŒ˜©Ϋž}ΤΆŠŠΉZ9iΫΰ°Ηˆ¨Δf +%ω'ΌT ΰ—Ha"Vp΅ΪΎf™μŽΪ(ϊΒzS) §σΠ—Nύl^JΧΎˆΎOΑΎ±ύα™‘£kσζγΊκnxΦ•‹P·-¦|\ΜB¨+ψΨε7"ν.δ[3Zο†\ φ@—Εμ ”Σ›Υώ’Ϋ ε DT6ΦhŠ Β2­§LΧ013ΆL»4vm>τώ~j)Ν‡©œˆHεψFu"_’«w!˜‚%0ζBb,ΩΒjD?ΟŠ`²€iXΧΌ BWW‹θ΄ε”[‡…xšΰͺΛΌτΦ6”e—t-Q­„*(•·›Π}ϋp]†Su₯΅DC_Υ}dlς”J(Š„ΖjρΖο]±γsΩΒ ΪΑ—4gqΝ¨šAL«ΟΎ*uPXsρšΆρΘα]ΙxόιU$5FUF–υŠ΄Ξ^0ueShv₯DΰΧυϋΛλ@ΜΨϊδΪG;fI +‘ϋ²p§υjg(³¨ϊxΓθ*'pΑi½TΕ¬υˆdΑYVΚΐεBΉμœPBΛ­Τ*ΊŽœ &,„ͺpRU q•ν«Š6lt‚Y]!ͺfi¨iύ=E=[γŒΤv2}\'9”SΝΐϊάΌcΛΎτΞ=7ͺPjΤi­+…ƒέbc­―”BF7ΫΞΈIλ©Nύ’ ΦΥ§κ“ρ,ΰ΄ψ΄φ‹Ξ~α‹Qλ«ΗHˆ€Δ+fΝ±q'ι'˜'A’5ν¬/́‘’ξ±μb]$R²ͺΛm9%K"μ‚`‘»ͺM—ΖWPQ―bƒλ+³ΉΕ€ΣΙEEZ[¨~}?ΧtzΒDˆ(}›v*žqNtΑlz˜θw>¨ψΐ΅ δS™:F>4Š +Oͺ5Ο°€Ž rŒΙ‡†Y’/c# Ζď]Š’πw*̈ΤK–9Η*Ϋ΅ƒžΪ›·Π₯"/Μ3X₯«F5ξՏΘ~fυAMkχΈΨ\/εI„Q«νΠ°’Œ1Όyεˆτ +ΕσΩμ‹κ‚žp ΐΔ₯ӈS'M3>ώ|„$-60ΐξJ°ισ’έ^mΞ]D9ͺ™&Ίd, +YXŽτΦ/ΠFŒΑ Ϊtύ¨’UΤε‰’ sσ4TNΤĜـM–εbRπέΞΝ Ώ ”r0Δ( +0[!‰FiΒdT™D5P(F±_ ? {μ*exΒrαhεE ›ξ€ςώ«3½m$e +Αx%)}±™ΦÚΒj±Ί†Δτ’ΈLLίL醆‰bΊK­`'Σε°EEO5;?e+$υ‘4Ό Γκ#SΕ­Jκ +qϊΨ<y ΄‘4‰yKEth֍]&˜"›b=V¨ct¨Œ(]’ ΌR3™¬‰Ϋω  㠜ρZΙ‚’+x&\ΩτaξΨNZԌ̣-ΣmδŒ\g6τGAχΫp hσulP+Μm,-ΌΗϊ“‘ΩΉ’o(q„΅½'t¬’27Lωχ.ΰu O]₯vŸk%%¦lb{V]Ώ*θ]Φ֊Ύ‹ι#iΠGε€κ…ΪRƒ‹μWΟ<ΐ<‚b*A)δ,6Q:·ΏEέζ:ŠƒM„θIX„ς3L%—8ΩkeCΗ&Τ‘ŠxmM4q¦MPΥ›LYE¬ιYeζΊRΚ΄b³&ϋ― šΫJΥ؁[=Κώ™±›πρ|tωΑElkΨ»Ξ<\Έ5 \xW‘‰ c!JαΝ0ψ'໨‡v.J·OΨΞ#ο'΄›Λέ! ρ%5Ž_ΙΛ}7x)Kͺ*˜±»PZ`QG]τOΈ7dŒz]”½hΜ»ˆ^Λ²=K„μσ3b‰v’Ν©Δ!€FZ‘ ‚Uΰd 1-ϊA: δ―Λά½LsΓ0ύ]©ϋ:C±¬Ψp°jaρWr—‘‘Yώͺi‹ίyT$fρͺH­χ +υωγ0ΉχIάκα]ΐΎΝjο!dzχ+Δ¨Gw(Ψ-ɞvwqΫχ|°…β!-½q_νpΛδ/ Δέ3/φ€υP‹†‰;Ί~‚Ǎ½‘€@ŒS/©³υS un”TH&QxMκk‘³ώI$6‚D™‚Γδ”ŒB HcϊΓ„AμiKUTM5€“Sζ χ–%[Wa QΡa«H…σ{ψς_ψπ/Ρ,£ο?T4†ΥσΣEαp2AŸP ΅₯ƒ(wό„˜^H6ύχƒ8η<~Y„'N&“˜$¦’ύbÁ琉4‡‘βΞ€ΕuyvΓ&"ς―]ŠŽ Ρή„’~@σ’ηΔ’}Δ8hPΰb (^ρΜwοωΧΞ’p‰p, ¦ΨOŽ–Ν|™iW»9Ο;>)ͺ~ΰVζ(VΨ¬*½ +;©•jΩΜΒΎBΒύ7š†4 Φε *«pΕΙ’ W8ƒ!ζΞOHΛvց@=CžΝUΪκ%>{w`―γHήΜu ΞΜgΙb²wK‚“ρψΥsD§Lj£9ςώ"¬©ŒτΏX„߁[L}σny₯:YΣ?OΊ­ξή/2J©μΫ[EՐfα?ύιœε$MžCZψΘΫ[Ζ\4ΘUU‰κ¦²'QυMS—¬Δ4Δemm­Χ0—&I€$Ιζ!gZ/ύŽ”³EUπ{ΝΌ‰Fž95T·*υ¦»n¦λ[a'Ι»βΊ‚ΫΥήDRΈ@ψj§οjnWγ>Ε―4‘ꨏή9«j¦ECQuRuΝΐŠͺͺέΒΦK•V>ΰμ§χθ؍―Χ5ν7™R§ηΓΟJƒγΊ§Δ NΑ9iό\‹'Έ¨Ώ._”DΦ󇙹γΖχn:ΌJ@Κ΄ ’ύne€p'§bSL&» ϊ‹tΣ?­±~ŽΒNέqxΧtΦχΦY_ε~?ΧλgΊΌκ–2xU‘¦@ŽS\―Ί:—oC½n%ŠρO?`&¨Š…σz‡--τ’ ze>Χ<Ιv…‡³6ž˜’ +ΞΌ`zA"lekAz³Ξ<—RΖαχ@DYεE<Ή>9»άwG0C,>ω[~’ͺsΟ$‘όl~ {tδωι|G&nB.ι|εεG7α-••Μ©K&>Tφ‹θ™-ΙώL"Wυ¬RUqΔy“MBtΚ¦C6ρ5 +βζj +d–b⦬lŽqkΆ`ά–Ε·¦±‚’υm£)(MAωˆΕj +Κ ŠYb(†₯¬ Ψ|±‚²Ήό) +Φ7š?/}ώΊ~rόvΘ^)z›QZέ½_d”RΨΆ·Υ= Β»@œ a }Ο½"—_†Π•D§κξ€β·$Γ¬‘r,J&ξθϊω΅g˜Dα5©Ώ_gό“Hl|,<φδψ0ˆœ…’œš5b7 Ζ©—Τ1κ•ng–3Q.3ίΠΧwZˆc£ΝlK7uƒνΎ‘ΰ6 ΩΛ_λ^ +χJSΏ6^ΏŽ P‘SNγΠySÚVγŽ>ΧλΏ“o{Δ4*FL­1λŒ˜Ϊ›1_±H tΊγ0šωΞKτοΗθ%‰jžw•TU‹πΎFNͺ=›“nξκkΉϊ―C•¦ό3‘k#Ψυ£φ§ΥξSzέΡn$Š‘ŽfΙ&+x%=*©°š"ρ ΎŸΖΐh€S(*Ν ­G—τœ ΕHγeϋέχ!ήbhΦΦΏaΪΗNZYχX[εYΛρ!+ΟwXuS?ω!œ‘w3σσ‚3g¦-Ο*U"iσŸƒώε^0ή‰v^LW0]ΉόƒΠ^p΅΅Ε—υeSLOL‹ΑO&™ΎΪίOα)+H`πšσΈ5ΏΪŒ$cΪ›z ‘5ΊθψPFΔ"oκ°Έ{|qˆNΙ8ΛλΔχΓ;φH›{τ™ ’JOvοέμVzΰψ)ɏ—»W0ψΈόνΞπXιΣFΝ Ϋ°rΌM¦‹νΎŠαc¦-CΩXΘΪ†ΰPΔΕ6,n(›"K(› +£pn ƒ` +‘§Κ/Kε•ρ ͺGQυsε3.9t[W€tž‚e ΖX%…+A6£=Μ―Β§6χiŽΜ|Κό˜‹Έ~fΚΆavΦ£¬E˜U†a +«P7[Dΰ2ΒX’½Y†ιK΄7Κ0m‰φz¦.Ρ^›ƒ™Υ‡β,^Nδ]M“"b-Ϊ.κμ<°ζˆ‚…+@Y’…ΝQ©+Wχd©•-E΄±ω–%J…C+>°=½Š-'§€σ,Έ¨LR[_gœΞ²'‘=ς)³μVϋsΞΠp +χͺEϋ”ްh7ŒΕHG³Ό[dsβΰτη°ΰ[β³kL€1Τ«μ>^~v€ΖΎΞ³j¦Δ£ΔΊΒMύ?χUΫ›Ά…οΨΘ—MTΊ-„·„νSWΖ-RΧυ6Ϊ‡IΘ#¬Ω¦₯ϋυ{’ΰ„d[«ϋ‚pόψψΌω9ηlΆ’»ΕDr+ζά₯dL΅†‚±χayUαΏTθvΩΒ.‡œ―LƒagCψx…Mτ°κZ{_k·ΜΜ½ήaΔYLΏXG°ΙD’!ύGΥ„*rήέΫ‹­Hί—ρj6ΉO yυz\Ο֘dμΚ™Ι°n5ƒnΰΥ‡Ψ] K˜ίÌιψ¦ό¦}ΗPτzάΨ‡KzφΦνtPώφ@ν‘ QΘκ—”πά§%‰™ΉΫ`ΡLμΓHL“P” Οθμ8,ΤcΔθ-όΫ‡η‰ˆS|θƒQΒvΫߜΊ>?<™dθJ›£φ —ŠI¦X‘Γ? –Κλ«žΈεΓΪPQŽ΅Nq‘νN΄ .p)•“ϋ2(Αn3£Z!3ά½Μhv›~‘²Φ0GΧNΨ ύ=ΠϋIΌXAQ »§g‰I³™¦Y–fο z¬f +lΚ΄ΐˆ}K­i«y*ϋ&Ε)΅ι’’ ΏΊ[n󽔐‚κz3NP@ίy¬¬³tίβN g‚zΪ(yMυC‘»ΐ†³τLεyƒ Ξv―p΅ΉσΞl­ΘH‘~s-₯9‘\ͺršrΨi4ʞδ%Ε%X$Ατχ9Ο:l£€†-ψ’L’υς'‚ΪΉOn/Ή&'FL³ ³Z^qΉ(θ•sΡVΞ•\ΊͺW%³­€χΉpetΘ°‚ͺΘΨεΐ Χ©ζ@ϋͺ~Ξ+NΥ'toL Ε”#K1ύΉ3¦tΙ5Ij‡©θXnΞο7σ‹±ΝΟ?mψΥBkEμŠl,ΥT‘Λ“7T-mSΒ1gK/F—ΗιΪSht€HOόΨΣԏ¦«δο;ŒŽ”Ÿƒ&θL’²ž“ΥΙ~zv7Α’jΨw‘ξφ™pjoέlΥόφϊθ―e™¨v}<¬8ΟZ―œ±›ήϊεγΩ9HhgK†‹Φ .°}Ϋ›¬LF‘ŽΆO-βδ;¨xΞψTee8+0Ωύ1yq©½z|C”~η8ԅސΌL'ίuN ΦyμRΰΏ/ή;B +ZΑ1\ΖΧΆI)χL†|€ΔόU»&LL‘¨_Α6$GDΝybDΉ}.ϊ‘Β›žπχz’’ύΜ2P•Δ/κKzθoo*?u }挢ζΕ+‚ϊΌLφtyψV[ϋΔ<±ͺιψδο"²ƒΤ£ΚίπJυμφΖΟό 4gρKηβ°Ρ8j΅i›_lλ-›šy•§ΐη₯ΰ Ϋ; +ςω§Ψͺ9΅ƒi³2δ“Ωυ]€ΥΔJ endstream endobj 21 0 obj <>stream +H‰ΔWιVΫΊ~ήA]Lι21„©m& αH …ήSh+ΫJ"bK$§Π§Ώ[žβΔ‰“»Ίξ:?Θ"Ρ'νoΟ{³·h{eύ{ZL‘ά‹c3n‘ύ‘^]œξδ+₯*άlOΒFΨφB\ξυ-*t• ¬?‰aΨ !'Hƒτ…/Wνk8CΗϊK“šŠr†Ε«ύd|+xΠπ‘α½š8…2ΤΆaΛ¨S*•ς‡»ϋG‹΄yYF›—]T¨sž‘†Α•βN›τԍ }Κ–Ρ*}gϋΟυ(eκ1›URiS“tΉ'LRη³δšl_ υ~ύΫ’°…ώSGΘcΥβ¦η¦–‰X²%β…Ν lίΧ³%€OpZŒNλ6!Φ-νΤύXπδιwηžι`ž<τnξΦΑ5ΘΨIσt*W*GΘπίξpΚ”6PξΦ³‰H&K$ I₯kγΧ+,†‘­jB •ΐXŸ5Α>‘Νc;ΕΕΆS» )–χЎ9~Kμ;~φ‰tΈ€š±ZŽΈ—ΎRš*+-ρm’φluŽ©αΫ-΄α”αv"}\lY±‰–Γ)±εj +…mV›έbd]Λ₯ω8Šό_Ln‹8j-TσG·X*"θo2υd­M-ŠF4dάrO‘Ύΰž› ₯’ΫXdΨ„YΰΉψΉΨ%Iκx€ŒΓ9JxΐQ g.&‹½>W3ιbHRσ8Q ^‹TΨ/ο•ηΎ…ΚΐΜ[Ϊ$ͺ ±‚„Ξ;Ψίίݟ,%,„&,ΔΖJ»‡»1²z¦ΩΛnl§„:‚H"Fέ‘…Ξ,ͺ°Amͺ^gUŒΊί5#χυŠXΤsΠ-χ{Ϊ©OιφEtP—(ΟEηŠ0ˆŸB‰<ߏ₯j}―ˆΔακΗMBZ"ϊΗ7n<εBΜfί)Ζ†J“kcΦχpŸ w£°―Œ}P*’^L_ψRvFΔTΰ ¨Φ˜™q5R&±Ήš8ŽΣ!BΊΔ/Ώγί» Oη^ +2ΰΏ>Rkͺ”O£‚’ήŠΣiί½Ώ¨Yά Ϊ `&™ΜM8>ηLu=\₯βμ.%―CUω›–~XGΦ λ`5˜dGΝ»f%‘mRϊ]ά}Λγƒ3‘ΪtΥ«MΖW& 9U)ΟͺοΠτ­NΏFΫψΔIeͺ”…„ΫܜQηΪπ~ΤZΝΜΠΨSΥlŠeΪ]Ρ{ ›Ί¨ΑΑδDχA¬œ…œ²)#(ρa4γη‘΄ΛΣiγCσξƒ|©'σ₯4½α‚”\ΦJ%tΦι.o& ^`§ΰΕε `Zj6ΫTf9[…οM«”μΌŒ##Κ u8q’ρ.»0M\o9…gnδ](’Π’ϋS€R0-Ϋv6J©k€ŠΓΙμŸ† šH’ι‰l$4Θqšη}iuΦA£θŠL X~WΥ†ΎδF‹υ8Φ]ΚΠΉ―αD9£‰&hňΠΔtœb*•·‚}Β‘ΑΨB_ ργ-e™;εΐ±Ν–&εZK?nzfψΖ,Ÿ»aσ₯kL(=― Φ„³Γ4ξ΅’žU9š‹½MKΣ κ@¦δmΨΈ–*ξ.d ΕXxΗkψγ[hqˆ4(?2^QSΐT ²] _aΙ2šdNΩl>h‘Ι‚§x―Υ{αcS°”Λ}”Ώ9d— ‹HΪg‰Ž]ΞJ~ƒ{Μ’KT ή£6Ι–TiPεD³ΟεjΒi­m‘Χ٘09Χ+½[fXG#γώ%Ϊ Kt₯<-¬<Ζiœ·§=θ¬.~O¬Σ(ΏY%ߚ ΎΤ~>3ψBΜ(t[q.JšΨ&Ρ[ q£l₯ιΪζλόP 0&“YA ‹Y4£ΜΧόdcw±B\w·ο σ„aΓ&YUW£€žΆ—Š\Tιqb,sB^Q§RΎΌ?³Ϊi(x=[MΦ’ΩΔ ½Χ.ΐ˜‚» z’ΠλΐŒŽJQv^BΉΝ……ΜπγX¨ »ΛX%TZ„Mτ˜ƒΉh‹‘œ€±xάθc4feQ©Όeg—ΌγŠgYυNΓ$μΊjrα› &AΡ±3Ό(σΥ“^&„‘>DΓ(ƒ3€τΈ›ŒΜŽƒvΓ²ί²Kϊ5©6»΄N0 Ψ$ΓJDTFlάbΗΜ|mU™Uη_άΌ‘άp|j³PBΔλ_(t²?œƒ » ŠW±Yρ5ΖI³ΛFB[Λ‰aνΔ[η w͌ͺβd†Η}€ε…eφ뱨ηΐ +ΩeΑ¦Χ³Η Ψ–δβα£z33’$ΐ@œ(2Ž”ΑΏƒγ‘ίfoͺ>jΡb`:‰q)χ9ίΝ£b ‡ BΉξΓMηρ-•¬fLs‰‘oΦ ½\Α_&ςDŒˆιΌ3ƒ1«A΄αΝ›4ƒsFZ,κΙέa‰Z„Ο\Γ&χ$ir³Ώ†~?AΉZ«TB=q’Zt5©9ξquΞm·ΰΊηΐΔ‰:τ…Ψ"zd°‹Gδ +6?κΪ€&`„Η"XΠ/ƒA ^£cTΈζκ–˜\Xΰϋyφ€lhKEΑ‹±n±!jC£OЍŒPkΙv‰ς\δbυ‘\ΝβAζ9hNUzκα&<γΰ!etΙ t‘λ8G+•ΈΈ¦P]ϊ;Dκ4΄Φm΄Z‡ϋM’#Zξ}ΈΩψΊuϊπnσΩΎάέΈΩ©WΕ…38ξ³7—ηoΆs› ŠσrνΰσΗ³ƒΥγκη‹χW{ŽΫ›WUα™•σ³ςΥαzioo΅X”Νηf»ΈV=ω–«zΊνΚͺό»\XY―ž΄ίˆt©κύŸΪΥΣ=mΠwοΝf>ΏΩO‰j[y•ζωϊqεŸ Υ|~ͺού³³]sx[‚sΥ`λύΑͺwήά[{¨?Ϋ›+λΝ^ρ˜ωΨZε¨WΉτυ±vΧΘίΟšΔ?UO‡ηOΥc™wΆšΫλήyξΒκ­¬ϋΖ:ρύΖkφž*u»j9ξΥͺ1¨όSš0Ǐ¦Yj¬ž~Ψ|ήΚ²ρ­Γ?›-«υ¦ΎsψΌVλ€Γly+λGΟΉ-σΜά”k φΎŸœΦΦw7ΆκΧΫ?ΆͺΝΟη βύυώώrupbšx¨£[g½φ \*pEΠ΅ΗτΫ₯U·Χ?lG―ΦξnόΤόίVO.»+λ'χOΥ37­wW'…ŠσψŽVώΛ}u'‹mα+ΘEΕ†Œ‚(*έ[ΤD{ΧϋŸ]И3ߜ9ΏΞ`―½Κ»:>oβqœ§ΙU‚69Ž•Β©`c=SΆύFM 9ό όK—Sή``*­Ωκ[Π)‚œηm5ΐΕNΐ/ωγL³ςOς­I/9ΏΫτ–₯ΏΊ€Η΄™Ϊβ”–V>&x ΝΦ€DΡ_Ξ²ώζݚƒμ‘τDςBP£Χ|½“iΏΎρm΅ˆΙε€:ΔΜ"H„ξE¦Σj ’df©+ŸvŠON–ο}δISaΐ―"ΕR‘T0ψ² ½)M£θ_Μ!)΅2=e’L—YŽΕ†²$#\<¨ίί—ΔŒš5J|4Ŋ,U£Ύ8‡―Δ셐’ΖƒΩe&]u@“-)ϊ!V4eΩΘ‹%Nb¦Ι†Ÿ¦‡,«Ov–Όxύ°)Š•’Ώ¨)ΎIcsΗ>πώyͺ‘™Φχ”fβίB²q~…σε§iΘΪpΈ9βγθΈ±ͺƒΜ©ΘηBνNˆYdΘgJϋήPπC^S@tψ€΄γϊΎΊGΦξΨ›#pδdζ§ B ΨbΗ©Pʊƒ"†'χΙ‘ζ5Š!‘β݈ΟΥη±"χp0›j^χή'Ο}₯’₯fE*H€ΞV˜Ί:*€€ΪΈiNŸ<›«'69nκ"ۍ΄}PΝ­[wΌ_³j00➹„[M‹ς !6ΚT˜4§H₯Ξκ™ΆˆF—ν°H5BE²ΡςΟΘ9W5_uΤ”X7B¦³Γΰ aΩ¬šͺΔX‰ VZ¨ΰ„θ\+€„*ααή/-Ο“΄ήΆvbcώα,Š€Ι`―vοΝd{’nΜ=ΰΪ—[bΘWKŸbxr Πv7ΗUPm}δn%(~5ή4a–ϊ€~¨Œ’σΆΨ$Ζ§{:·¨7zλψrδQE³bο +/­Όϋ…Ζgi―­Άwu·ΠKlڦFΥ›:΄­Nσp +"Ω{fg@½iύΗy|H&σ'Tμ±3&Ρ—uO¬ ςυ”‡Ÿ“C€dΨU δ–θξe Φ/¨uŠˆA?6ΒqœpcoΩσ3άα3yΛκΖΜKϊMρΝνp©0¦e:+—ψ–¦ +Y:EMG‰ πΚσ_ΪΊρBX”θ/ό”`)”ΡEτΙΤK‘­w·mQN‘ερrςό’w•bρ Ω ’SQί*‘ή?Žΐ»6τ½ν‰wj=ΰƒCmχx~υυΐΪΪ«γ@#,1HΠΆ7h¦LύjΚ­ΰ€TΈp0Χ)μ3^±­nφ!¬¦p·εΦΪΞΐγΩQp-Έ —υOκ&%0­ΗΊf9ωƒΥNJr9ωƒΥδ:υ™„¦πόλb9ƒmΥzŸbCξ―¬ %ΎsW4‰Υu>ϊ(HhŒ΅p²Xύmμqαήφ©ά§R”ΰI5‘η—jΔ׍ðT°Op²ο"(μΣL1€δ—μ·Ψ, pWψΕmŽm­K ±,‚pΩ‰ƒΣRδ·₯γ™fΏ™YiΝΜzhϊ!¬ω§’ӊ’]S^50` λ1x œ³ϋΧΗ‘Ιθϋ=Ϋ/K +οΑ&+ΦΧ nΟτXΟT"BΣΑ]7Υ† +ΐZEaακύžW‘ξΚ)Ο?  π`pσώW˜Ή/­Κ$XDͺFΤL\ ϊ*’rXf°S‰ΙfMA7lϋY₯ΔH―~†L.¬Φς;5u{Y!ΈMGVΐξΕΩq© ΥͺθΝϋUΡK‡kου–¦ψ&·ƒφςνŠρΞM±«Λ¨J•φ‘uφT™$_³ςAφZς a†€&σf4Qng€$•Iαv'S'ηmΌ+{)'E/Eš'ǝz}‘KƒζA#z-D΄‹©αψZΘi~_ +ΚUμ7Ιt']Σ>ΗZϊ&π£ώVhΧHwbNΦ ωqKηΆκ\)ω‹Π΄"ΞΤ4“ *…£ˆcμgΔ8Τ4¦4σχŽΩŸeo„έ¨‚ϋΫό}S ͺ0ϊƒμ΅)³¬‚;C^œ Θ}#ΨΣ©‚z8r«iQΆ%ZΆΓΐf5BE²Ρς#ۚtξ[φΎρ₯ΈF$ :ξ3ΏζλTΟb±CO°SΕχ‰2Ωp[πy_Γ“‘9ηΔΝ(Έs€C!:Χ + •‹%¦CχτŠS–Φ!Lλ[4vΌXβ{3ώ₯ϊD(?s©ο5;Φ܌;Puΰς°ηΜΤQΈ»ΈŸaPΕξ1]έ}νΧ₯˜”Ψvy_Ώοݘ’είΤ©Ϊ―Œ`6IΒ‚cψΤ1I}ŠQ/Wε―Η³ΐUό›Sΰ3M·’ί,zR¬υkώκLΤ$Ψ,²Dφ‹•VτδhUpCI…•|―δ‡Γ†ŽH@uζλΟ”7άΊ6pΊ;Šπ’ΦqW@)x}^4ܘΉ&‰±1Α½& W}ώDh4Qύ€’~Ζ°·7θ«ύ@κ -ΧωlJ.ΟΜΉ€ι ’cPπξΘγ ς½,eaφW¨r’Ό4ŒΔΒS+ƒ‘. ώΕx•ξ—Ξoα+θ=€ΤL3 IPUEPZCd"!ΓΩgΏΞ΅Ÿ•˜’[ϋξ/ϋΧ-λΏζυ¬gΡδΈΎΪ•\:rη+L΅ΟŸ„†αΜ+Wθ|)³Π¦&­(#*d*ͺM€ŒιΡOΣΣϊΦ lΉυ aώθ"―»€½:™ +φ^―½Φ†•/½yσψ ^Φο?€6ο{Pd|οb‡GUθ#€w΄»σε%υώωF a>[V’χ"©Ό€/$ψXΔ™χžϋ ­&{ΠΨOB-7΅[=dΧΕnΊŽΖνrm1ž :yͺ…ͺ;7σ>zV βz,g٘Œ™)JŠΉEœš¦K>sk=ΐΐޟ mΥ CΖ›εL—œ~^œ§*MW—”KXκVƒδ6Ε™\Ο‘sRμ°‘PqτΆα₯kΔT’kΈΤk<”M"ί·Eˆπ2M–.’¦Λ@b―Ϗ Οξτymυ4/όiΊ°S+θ’·ΥίFz yΧZŠu₯½”3υμ—ύ€0>*@ID§VνBw¬ύψPd,—ξ2φl‘=>†η_Χ9‡ΜC«5β₯οί»ΖuOΨΧku Ϋο*ύ%Ύf‹Sͺ…'w:‹lΜ ƒŒ.iͺzπ  ΗϋκΆSNΩ/]¬―ΝΔfJ…DpξΙT‰“"£Γ±‡Š[Χ6ι’ξ£ο[Έξ™€¨ι76[S +՚Eο;]+©Λψο›d`²>‹~x_Λh2½Y‘υ²OŠ‘Γ° ΖO(n81Ξj t°ΙμγςΣƈwR(ΞμSΑŠ\@GψΞ‡¦ΒΑP6­FΐŠκk’“¨¦fXΩΰΕςΞλžΕ ‚ΏψΈ+‹¨`εΑE€c­ ‰?­8BŽ£3:ΙάάIϊE₯Έ΄ +–c‘Ι +™~›†Œίƒ·­@ζ[ΩοT=‘RΎIΑ&eόχ5©Wm¨Λwφ’­)“’qΤ„!fTMVθ.4Υ₯Έ±ž&ρΖnŽj6‘ÍTγ +‘rν‘]DJΟ“gWv`λL«JpN ΘSbΑ:C(lΩxΡΛΗΔ’~ŽhR,T9܈νF­*Κna“Όν­s­ν%΅D‹7ƒ?jΔ…ˆžΈςΒ΅θE‰žφb’ΡΞ?1₯uφΪ½_+~Šσ`SΈdξušΚφ_ΙΖΎΡ{ϋ-πP·~?[kμΒ…n0€6 6υθ¬όΐ) +#* D ³ΨOrε6Œaλ,ӏ·,ίωΞΆGxQ‘CnώUYϋΉ³Gx_{₯r g‘DΚΖ$wG@K;€R°ΫΚk\φζ‹GB{’ηJŒΔ<σ.QDΆn˜ΥXv£ƒΟ±a-1Ψ!οίBΜ½ΫryΏuX€ΙNΈFK‹Θ.w’k%8€;ι² ΰzZ-A +½Z˜Œ»^¨ν©iξ|Ψ>‘λίξΣ4.A“χ-†χώŜ£ΧΙƒ}΄‘}FIΰ#Ε6яβP)¬Ί­8ΝIq–gNdc›b_Ϊ Φ‘*TŸ6< Σa6S©5j«ήc_XΌΨMΝΘV«(?f淞£Y?…¦ ™nMFχ όd;Wά=%=Ίaφ7Bο²υœ‚ΆχVw·υ€ eμm’aζ–zηΙZ‰”˜§΅>e¬ΌSp…χt‘ζŽέxΖ§340ιˆcΖρ¨xσGJxΖCΒ‘–6ΐ‘i-Ση΅"°ρέΓ,vξo*Οσ“O²α·ͺK0υχΙ» +u1«η,E^gK‹^L­Ωδ›η6Š©QζΙ)„»Ψ¨ΙB,N5Δxl… u)gl»8Kn˜—gΟ|UρUΧj‡@^g$pμXλZ—―‡Μ Γ\r—Ϋΰλˆ|;r‘C–#XQ;Τ Πβλξ2š‡dUE† Λq·ͺD6Ϊ΄υωeˆΕ™Υ3!ϊ.σ§βdͺʝVƒ… (ν<$―\κz`9v€­αzSkMε4™š½―8±ρ ρ6θ|¬σB~«›™β°PΊΪ…δŒΪμΒeQ}šΔܜ$aο·ι΄σΓ„Š³Rυi”ŠΉͺ/Ο’ΑFΫ·ΨJι“‚p\ΧφΞZΒ%˜—e~S"§ςΨλj΄³žΜPya`ηlΏS‘κΦV8#›Jmτeε£Ιξ§—ΆͺY¨ΛΫ$ͺΉβwόimΩΘ ΫχgšZ2Tϊ‚ŒW–'6qhπ=1 ]zšOφo9|ԍψο|™Q +εέx?*ε)v±Žjεώ^§I{>Ιη{½Έw=ŸšFΛΏ–‹@)"΄Ϊ-žQ ͺ4/>cX±ΌwІSάa‘R»Οl?Kr&7Δ‘sΝDd“OψιάrHΏσg'ag³βLΫΗ•ο|σ$š‰$ς¬-‘ΞΞ”5Υ@HΦdCHΎΖI™³cυίH‰ΏjfOΰ4xη3ŠΔ›ͺ‰γ¬iκς2#ΪPDTΰAtD³LEVΔώkt&ΣΨνg‰7’žΧ4EΣYϊς?ΒαAπ£©(–aꬩι)ˆ¬₯˜Σ¨2§ρB”¨΅:NSΑqύ•έυ$ίψ{1LjΏTƒέξ‘'šbΩ +ŽxΪΦT[ιΪVΈΌoΙ*γΛ€ύςMΧDˆ΅©r ΨzΣ™ϋύγ»[Ι…άhΑ8ΌK‘fO`•―OsHΗ2w–‰τXΓtωΦω)vϋυ!Ԏ(‚IC¬„nώτMU3Mmϋο9ͺλμέι‚aΪ΅XE6‘ώTF—·mVί.ϊΖJIQ;K~FUžΡv+κŽς'z‚δrφ§|œriό['z²΄: +™•`«Γ`­„?Μ₯1a δΧJPƒύ=¬ŠΈηΗξ·“^¦λ©*KΎοATœΔΩ8ΔY5qH²4N¨ΰΪgέύ©E:ΊφώΓ£έoWuΥΧΥVFB― K~ΞZa~λ’_μw,'­/’2%<Ο'ϋΓο­Β ‚Œa—dQΠκtΜ†³nΞ@~+ΐŽC9XΏUζΡ ΦΊ‘Ρnjυ‹›le^ΝFiΏX£ε58ΠΕΝ§O£8ΡVΗ―tόF²ξΉ-΅όYeS/č ηόO{Z…jycη―φŸλΫͺlTsΓm―ϊτ›X£|} Š6ρη" Υzh²?KOT‰ΏP{kφ5Ω•|]Πz2Γ-qy"E%›7ΚΒRΥ ΜΈš"±?ΥΠD캈ŠŠ~Γq©6g Ιμυν{†κeΕίμIΚWΤΊ(13ί€Όm’μΎ--1iΗ&ρΕμτjμ@ sώ7ϋo4ύ(Œ·ψ_Xˆόœ‘›ΜφŸΡ›6ύδ4Π˟½D‡ήɟεβƒ;ώ.:§2ό7ϋZβ9”<ΧXΒώnVVΌω _|mλ‚G^Ο*²¨M»₯©9˜ΙIsΐτ-rΏ·ΰΨοN‡+ΝΨQIUΐ$lhΝΜ{–+­―t,W7ή¬-‹ώ8”¦ψL£‚(Mρ9shZήؚ+ΧUΞ2%0§Κw#ξd΄Ύ7Ž…fgΚχGψw…tΫ‰’%"§₯±Cώ±D'++’]θΨΔO*΄‚60υ1τ΄W[~uΒ02Ϋ_οwsŽΩ|έΠΩj…g;' ΠτqW" :9%p™‚-Όεξ…ό9Ώz…<ΐ{ϋ9ΪQž“»(ƒn»˜ΥbδJ£ ³+ͺζ5άYΑ’jHιxΡ»Pε^[hXΊά€δΒή.591ΘΙ•ι0¨‰ξU¨Κ >\^/UηŒΆVž{RG]gvAR֚uk•χ‚΅Ιp{Π'λ3/μ…C„Bm^ξ² ^\Bγ­Ό² §Σ +·<Œ/32ƒ^†ΓΣ"”2‡Θ›E‰ιkΪ*τJF2Vf‘4 Y}ΉΨ,αΛΏΞCTΘΧ +ψςk9‚~ΡΡL'9:Η_Šα.’οΛΕς*UΫΦνέ,S^QƒάΡJϋ²έψšπDκY"ru-$αΛm3„§=LώυL³UπνΣ=ΒίϊU&T‹¦Bف[ΑLj’§•h©‹+o£ω§Y.RLF’ρa|7ΜfU>ρ‘–Ί•ͺOΩ’…Ε\v^π ν‡ϋΗ””KΦ3oΑ*?ŒΎVΔѐ*«ƒ~5[Θ.ΒήB‚Σ(GΗW +x»ΐkΖqξͺ#Ν”"vρQSΕ;©ƒ +E{ͺϊΚ€dU~₯'όΦI-a…ό€O+Ž))s'χΎ\p*Ž+ϋRήCR²M~ί}ΒίΪŒ”Ο‘#qάuΜ‘΅Δ$–nL‚¦Π‰£έ΄¬ok€94CΈE)LŠζΠηπ(j£Σ^j!u¬δς·hlνf\ƒŠ94κω2τ‡Ή§Άκ$j!ν/ΒWΣΜWͺZ|ΌΗ@γv'z²θ`BUWνž_N°5"γ ?Lϋ¦ΠZξcΓKsύ™…\œWΞχτ 2y΄Ο5ΦsΉ«ξAδύ+μh”ΏH₯&UΤ Ο.—‹ν¦‚Υ3YΕNΕρšλc χL°ΠήΊ3›+P yιλ{z}2ζΠf„|©ήg²ζPαΑfMFΨΧiΝ&cόώD½εΛζΠV2tΖcSθψiΫP θ|Ήτυs[V1ΠQ’K_shϋSΨ=₯’( +vΪpΨ‹dΟG` ƒͺ²Ω=˜B“퀍Έ@Η›ώ6Ν~š˜jΠyΔc(Ο°Ιx(νΚΉληž6©™7]@PΏ +ΒΒ΄ŸίΡϊF¨Δ;³TΞϋ ž#ζέ£B‹ƒpγ\ύ’τ’#€Ψΰ₯*5Š―-ΙRX·ΕUh>ό0‘_ˆΆΥ“&βH—(’hX‡(2sA)T ,ΘίΒ•bŸiν€Ι§;ACxm<;λ[΄ώe¦ρσEΥ_œ>΅:αέ+ΞΊ¦κ3r―[/”€·μȆ=pϊJ‡u}² ƒ5ž OϋšUΪ&/ͺ2Ξ­mΏΜμͺ(>7#¬5I{»3ΌuΝL\Lj]Ϊ ΄sΈΐZ›>.Oγ­σ§Υn5D,n²-ζuΜθdέYΟΌHͺuεϊNΖΎψ6‡ζtΆ₯/"φ2θqfvUεΚΎ­ˆ΅Ύ‘σ·ŽΚιμ!b&φ9I8°ΦOωY¨c­ΫWΊΨΡ­Ϋm«Ή n4,©sΓZθhξ>bδ‚›χšΈΡNΒΩy±ΦJ‘5g±Φ:o γ#V h‡'±ΖκT%λ=ψœvί¬Ύ~GΚkΦR0c¬ΚzVσt{TŠϊ»η7°΅t½—4ύ™‘_Θ*hΧΡκ4¦JO‘—Ϊκ―3£eΊa–ˆΐCj7ΜΟ>zΡ»*α/wKθρ¦άߎ·7U#4Σ-”O΄Ρμs@ΣsΈηœυc9{ΔvJm ›ΞIE„ZIΞχΨ_{V›x«Μ‘gm²s¨“­"ξgΑΐ™άŠ6 ©c•›·£{Ξ»94:b‘pˆ|† ύΨ©―ΚM EGή]žB£=‡…<Α&ΫΕή tιtΪu¨ύ‘CxQχτ΄φuυ ”Ϋλ)φ,ΐY UΊ ξƒΠύOt(ψrζλ …KQ<uX¨…DύΪάΧ ό Ϊta‘JO‘CQνŸaQOΡ?ΫU6uΐ+Ώ΄π”†Λέ-ίMφ;ξ΄φ1_Ζνί“Κ―§«ίΕΦZήijQŸGM-/Jwζή‘θtΩ^έ‹šΈ8{χ†ŠmμiΏώΘ ξ%ώ5Β©j +ͺ…Μ žŒpœΡ­L‘KΥŸΚ*r©Ϊ{„‹© rD—& Ÿ«ΪΓίβ5€ΗήΦΞeυ£ΓτQφŠ‘5[&ξiΗ σœ.|\F”Ϊ©»³‡Ό’όΚ’_žα…σ¬WiWKύσ#X†Ω“‰bΨ—·€ΰ†ψΔπδ)Βΐέ³v7]==ΰ—9VWέͺΊχ6΄›Λ·ΫtLTp£¬Ν=s/ͺj~π@^ΣW/©y±Ν$E¦4ήhht{)]+ηΪΟΈεΒrΔ–χ›JΧtό±“?\υi!°>Eυ*4?`ώ]…ύϋqλ;°gŒW!6―}Xςύ[˜σi]’` ‚ε*― 0”"3ξXίӁ&Λ›+J‘­Ιjή-ƒ#ΟΕ™›{’χF^―ά;bά,šzΫΤσP§©§Rβ7Ίυ»φŠΠM™0fQ»‡:φ06ΐ@>©zΚϊΨΨείω؍!Ά‘­<Χ4 ΄Q8xi€Š™ΕΚƒ?=?¨›έΞϋH€–dͺ"UŒ¨jzž‰Ωέί’Ρ:ze°TaΠμ snH`λ!9珉JnXΪ”ϊΘO’ZΛΜΥ²;09X₯g‹δ±39Δ$?P°Όl0^(wμ“ν2Άχnέ;ρΦ!­œ„‡a/#ωT?ŸΒŸ[λ|zŽjœ±@Μ―wyνDˆ‘ ΕΤPόTΨdTŸΛ‚Ϋ³qvα¦DδeΦ²nr3;i>P‘§ΉιΊŸέraΫ-ρ.Ύ^r»%ΟM?&K=σ#CQAa’πŒή#7s»eIQƒτ·‰1iwOΌέn<Ν«·π„WΊά΅ςμΞGΖ‘”`Cη΄NQε:XŒŠ…Ÿη§:ψ +E€ώ5ΟίΌPΦ$ομκ]"/΄•RΑhA š—Εόn°‘ψˆ R$ικφFlΌϊDΔhF ˜—©/U<~[mλcGwμεŠ^Aω΄xΗ dhΉ­W†³›Ώ•λrψΣΆ²2\FύνΎj9 ƒŸOuN'[ΙrΎϋΛΓFQwAgŽΙ·dΡ91\Yi¦§ΰΓC +ά}ρoΣ‰!>ρ˜DμIήNΕ‡€aΌεn˜ψ¬=‘Fμπ* HΤίVξ₯šΉ•\$<η₯%Qš5>(V1τ?ϊ½ρ”ΑYόvσiγ™―xΰpΑΕ9ΑΐΓpιXΆΨv(j€";Ν–xεξΧͺX–e  νΟΓ]“€\‘ς +β©ε9y{ΖP―νάԘ_ΖPΥΦωϊ½Ρτ˜Έ’€Γη\ΣΈ”yoΜ +²σiΦ"K~ΕΑ³ΗΌ’XWœΛ]qσ? +Ρβͺ8±&°+χΏiΜD·X !μ2Nx·})]$Δ₯ωΟyΧ?W_€IΝωG—Υ=¨ μaΐIG³C‹ή.Pξ«_ŸžΛC έ6, +#u—Eθ0Bη§C€—΅j!€.7Œ΅ά³v[%ͺBΏ“pλˆI’|2 ξMΔπЧvžSςΣ;62|]ίρŠ©φΎ€ΖΓ`ωψχZE-”zϋΜͺΩ5±δ)qθC2X6ζV:qφέ@+Š{MzQN'Έξa§«ά;;ЍMφh¦»HΕiΗ‰—οXL‘|ͺŸOαΕθ?F5ΰPΥ΅’ϊ―‘„άΰ`zFΞΑβΨDδ¦―Σ)( ϋYZR*Ζ2š1Ol1š1Og4ϋzeκGΐœ.χe΄ΎΗhΞυΔΑ³ΑόM!8MΘhύ}ΝέΚζ`ρ Œ†ΊΖc΄ΐ»βg4»ϋlœύ GqΝΚ]γЌ¦“QR•Έ=>Η½¬’ςά%Bƒk,Z²Σψ³ —kR|r˜Ί΄Ά^|σ°†ήόέ³6©Λσζ(τεE[Β4$ιΜͺhΟΟΦΌeFˆ¦Yl +D²Š +, +6 +οK3μƒ{¦SΪΗ  !ΙλK0Œ9kFΑ΅ΰ8¨ϋΓύ«zΙ‡GΌ(H+ƒΕ)Εo€ΝXrΧ]0Αΰ5τσχ¬’ϊ’Ψ]Πjˆ~›ήΗίC: _{_|ΓkžΏηi‘’ϊ«αu ΟΣBο²ΐΑφχχwKžU1ά5±Ώ—S1gODq΅pw3γpό=/Š{½qdΥP¬…&Γά-χVCJ Η+F ΝWΠoχUC»ϊR<ΗΫh[+³³EςΕ©§¬Ÿ―-!‘΄ͺΗ“ΜΤO.δPv»Ε»mr2 +ΆοvyqvΫΫ}_₯“²»N'ρ+ΠE„z™‘Ϊs3ΰkžΒυm±<5ό#•I œ\‹z±ΒTΝ€·d©šΙJέ•Β€W†ΟΘ=ζ¦λ~6ϋo€”2?^υ"3υW3Ί”Λυd*τ— –ƒιχ›Jκ‹7,ψc;\€Α +Ροέ_ςt$Dμ[0‹1 ―›@'ΗΑ$³Α€e™'Žx"՞ ©€$'bkΉlEΆVΧΣ³“/½wξeaχΰ—±ZιΖίPϊύbJ κ‘τΟΒC(έ)ΦCˆ`«ψ§Q(5{ύƒ?1όΫF(U»¬βΟu¦π«ucRΆ―Βh…ΟΣΙγΛ―³•‘WW€ŠE +ϊ·Ξ[&k΅Γz©ˆ»ο œν½kΉμρp³ZύΗΠ{/?άχ"Τ:vΒυΥζ1“&η…z4ωC{Α“^E‰G~WˆGŸc±ή}ηY#2›z6‘ΐ|²ŒΎΉ•6£MŒΐGΌΗ:εϊπ³d­ΕάoΰΡ―‘θε›ώ<:ΊgUT +`γ~΄™z€jΣψ·Ρ!4Ι0ΐαΖ/ |teτOB࣑Σϋτ] g§E'σ'»‰κΝσΥ}ΰο6oοβΏC΅ΰ·—υα™oΔ/3{ξl3YA5ίύ4ckqγožΑt/‹Ζf“”+€6C1ΟmwaoSά pRlE_=?v[£¬Ύ-e½…Ώ^Χ’'ηZƒηΆΝWΈΎξ ϊۚw—Θ¦dοΛVRO…pXΎυ@œ£5 Ηυ9©θξ+H-¦΅ˆ{OX'WΜ”χΛ¦„R$ύοmVϋ`­³.>'©Eο3pϊ~@+ξ>#τ8ŸΈλžΰΘM_§1NJΦύ" +fΜ―χOQq…i¦Β]λCχξHΈ/ς gƒ§diεvRΥΜνŽ8Ρ\“0£ΰZΆA!κ4Χpl³ ΧΤρΛM/MΤ}ώ ™ˆ™ΣΑ7Λ£ΤZlIjΕ†"W’υ»Ar¦&=¨ξη*νΰηSWf˜ν›6hi鈸4η›žgbΠŠPnžΏωW³ͺβΜΉ9Ι¨ηΜςνΤ«Zΐ+©Ω–|«M5ψ>υΞ,[b`Ζ+©ΣΤο ©›7›Κβ±@Α>Θ"σž‰°±{—LΝ;rς€θθ5·:{Ÿ +a™FΰΝ5ηšpς$άΛΛ]rvfθοJK /}©WnЦH׏₯*§γ^ΚώΨp$IΔl―ηœ!σ£Dz·U/%τΏ}j|3&9¨h‚Ά 5μμ’“kœpJtŸ›ΎΆ‹Aκξόhƒ^ρΡ$‘o₯ΐz +ρΐ2‘RLΒC/λ›T1 ¦$ΆΤ-f'5‹@IΊ­“κŸΛφξVniΖtμ_‘<ψΣχOQ%;ΈrνηΓ@ψd;˜ί!=Y«όAε΅a†r8yŸ`ώ/X~ΥZŸ…Ν΅{ι1ΪNˆYrκ š^Žxt»CάΒ«R§όΨnΕ~ή€ΛN―ϊ%ͺ…bσ0ΣŠΜ ρ¨?δρηQΈέ-‰cΟ½ψ|޽Ώ-ιc + ¨Ύα"7ŒϋβAQνNσm%–«χ=;4Ξωλ‹ciψk±>1|wΏ·PT8 #nkμa0Ζ+E•„Cδ\PA΄sa RΆΞ+,¬,Ηkk ΘΥ%.h’_ΫΘirN%Λ)΅±lɟ«€…Το5WΝwπcmٍχφτc―m鍝”(₯EwœfYρ(Μ){I‰Ό_pRΊ,ψ€D/³ΏŠ p:”MΙΊψDIU3·;βd³Ž«b3ΞI±}uBΌuhgƹ騫ίΝœλ`IoώΟz•₯ŽΡ'ΘCΘφ%ΐx0DΕΌΰrQFGTφχŸξNHΊ›ήBψΣίύΌ‘–SuͺN=ζ¦N4~BGΟa@ GΔG!ηZTαl(F*Yη΄Λ‘££ξsΓ’ΟJ)‘δ\ ΅8+5΄χ‰‹wΨ†q³·@ΩjΛ³ν¬ά–‚J k*PsŠ|›$tΆ$§x‘T°¨wΠc΅tξ*/FΚΣ’Έζ,Ψ΄$ιΟδΐ„έ)β` 'οdΘ;ΦF&Εr±v·Θ“bΉε‰εΥ£¨‰εb­“ ++4ƒ₯ځHx" ΖP:4`]*Χ1•ZΒGrBZ7εldOlΒίΔ@Ϋ€γδ惣βPΌ₯ωg–₯iψ¦7AaͺEΓ θ1εέΕ…ςs˜CιΦ>rΥZL―Ηξe>J‰Χ}‹%yŒο ρΎ‘΄/‹k΄ΰΆ£$lSΟΛzK-1Kc±bdΏu”ϋσQ1¨ h€άOjΡ­ΨIΛͺ―h'$[!;Ω­d•\IΉqκpΥEς‰B°€>];Ϋυπ"ηq_DC ½’4 Αί$… Hˆέ/ οƒΠpΥμ½ rYœ†ΐIC…9Ζ₯ao’JC‡xϋ»™f>ΣΓ“πβÎ?ϊ0―Wdg τVrj[Lj'―¦ ν‘1ς|:ωΉVˆ9ΏyI€ΡGΥςIδΣeψ‘΅c +₯t½LœΞ„ΙΏ!ζςΰk¨»”\―|‘³n¬ς%s€Άδ `lzΊό†δqρm2©ž¦PtvΔV45;σΡο Ρΐ\ 0\ΊΑ³=ΤSΈΕδv*ρN4WΧnϋxηa +ΣΙk%«Zƒ9^ψΤEΑ-όQ~Έθδr…+i. Ifœκ“4즑CΒJΧμ§Yώœτ‡Ω¬zυa}έΏφΓoK³YŽύiήUΫζQn“ΏyWϋΥ;~hφξŽΜΩΡί'»wgzφŸF£žk₯€±ΫΙjEΏΘ/νUΓΕψξμ;››ο„;“―G/άΕήO ^I‡έεlv°σ˜-žΏΈ(‡a  £Bζ|οΊΖΫ«? ΙΠΞ€ov?Rϋ\]`ύ‚;ύλ#=ΨΎn§₯^—ο4Τ|Λqζ›wυkΚ)ͺ r»Ž^ύwρΚsϊ"pjeʘΣ7]{NgσJjno±+{d’—Ίηϋ+§Φξτ ǝ–n#žΣΩςΕΘRNΡF^|°}ΓqΊOw—Ol§₯ή#Χ)€χ»@\dωζψξŽλT?~Όρœ)§φ„Y΅εu—οΤΌ|΄ΈπF~Šϋ}žS `Χͺ +λdώετyEρ»}Φw/Τ—₯§s%‹₯DΫώΞYη» !zΉΊ’ͺΝε½PίτΓwHyσŽwR‘ +6υcηrƒΟΥΆ>_ͺjϋϊρΨHΦ?›˜Ί(°%|Ώ&μQίΦ§ +!AΎΘƒ"DπzH@8a»Π|5©]h_―Η΄ώ=y9«)θ_VH…ηe½E…λΒΗ©4ζΰΫρU:‡ϋ¬ Hι»NJ`ΝkQήΙUwv<&0ω-0Tx5Ή!iΎϊiμ5§―ό°ΙοfΨ›l'?Z½?SISΑ©P(Ÿm…/Φp‘ˆΣ§e1εό"ΌTΛ'‘ΟU§ώ1Q[ΨsŒsY)ΑfŠΞ ηnΤΰšΠΡ%ΗF¬—^Ύ π9τž{‘x=7vjΙΏ&3΅šΡ:ηκd'9ij™°Β)‘Ά³Ί'iΎ˜ŠΒ_·³bO'PsRψΔΥ’Χjb˜ΟsKΠ9θ±Z:κJ¨`Χo,P¦γœ`Φaq8 g²τό^j&ΊΔ—Œ#e‚1Y ƒΒTΦηjω΅ι~Βc―™ΆǟρC•Σέ•ƒbH₯πqΘ׎Ř5} ]”)P}χsγΤαzd•)»ΡXδ%’LΧγŽR†q©–7X‘«£½ % Α―;’‘ο Cηz%₯‘Χ@¦Ι7W7^΄bqφ~Ωz³HŽ16ˆ¬ΎΞΗ£½%Kά8ΣRuΈΐμψXΪƏ=ΗA•C\ΕεE£G˜―”€=^?jή“β"¦,Oί,©dΧϝ–xgho₯fτήdL,5ŸΖ6λx6bƒE=.Tμm!&†RΔl™·j΄B½ΫΛΐΦ½t›ψRΕ^XIΝw³z™8“YυξΫΏςτ €™\ο~¨Ξ ¦ ``^^ + ψΌΩ}’ͺΕΝ–[ϊΐΗαZΒγ6μ1Œέž#ϋ­s}Οΐc<Χ«ΤD~ψΪοˆ+νΤ™PO«+Ιi–ΫŸ€κηεϋˆΔ"6-ΥΉ”ϋl0<5ΰx7B=p`°@ϋc,8žr ω;‘Š„ +–Tπ0γ€bŸ§h(ΗΘΉ’NιβN^Iς"υXxžO%d'ΐ/9κπ<ΜUώΟσ"ε« _Χ䩊ΊPΓi‘ŠωΒΙCΙ½’lœŽRa‘ΨΦύΨ ΙžcΚAQ!©ŽG‹‚zšnˆ“η"‚"―­8‘¦tΒp*τ7Ω–N›iϋΌΌιΖ‡†ΐ˜o…ΗΡό(Z•a†ο..”w,(™uΑIˆH$Ρ'ΥςIδΣυσΐ€φzΜΛΔ…ΩΛΔٌŸ$“ˆ’ƒΨΔξ ¦ΈψJbέΤωRE[βrΞ0h¬½œϋOΑϋ—«+ Ž‘ΰrΒΆ2ύV_ΡΞ{lΪυ Ό+Yv€2^)š έ]ωrV[ΩΉΊv(-Ÿh5¦. +nαςΓEΤΕY²*Z>‘bΠπžECŠ„ZT…† Y! `€τv endstream endobj 22 0 obj <>stream +H‰¬WλZΪJ}‚<„@H   ­iA +(Ό”Λ'+mA.οf&·Ι03IΔ?£ŸΰΪχ΅ΧΥΦ™(ͺ‹n6Y^§Ύ•ΚΛpvRK]ΏφτΌ *ΥΥΆ¦Χ»ςZ~ΊU*ΓyNΝo +θsψi5χ²¨Η΅Μ@Λΐη^)Ÿ™Τ§δΈN€9PΓJύ`’ϋτ˜–ώ>7`γΧ/„’DP ’Wάζ·€]_–/ΜΡP©θ½2U¬\OΛΐω›oΞ§ƒΪœ&Eπ€’js6ͺΫ…DΟ6ͺtzJΩ―ΥΕl2VŽΗΩ.ςQ„P8ΟΫθo`,G‹κY—ο0”ζ΄UA(‚ˆγLφi₯vš+ϊ¬–½§1‘ν{-γSΤ'ιΎfƒΝϊUψ·SηΏQΈ)ψ)šάο\ΞKυφ]5—ko/PΛρ©ydή\SφΌπΟΛ¨UΖΘXν·φ φΈ―Άέy9„`ύpΐŠ+&X!ρ₯}Είk'νΌ#,” ΨΙvΎΟψωζ3•Ε³X'CCq¬DΐΙFχΖͺ>“ϋ¨TΕΗ0Qq4Ÿ7υ₯‡’{Y)%§‡:+0•ˆWΛσBŠ(™7 ωκέE c]¬π™*˜R'Ÿu’a3€NΖΖP—3c¨Λ… !1„ˆaF#ή^χά†m1±IK%ˆκמ½ΕΎ©‹έ —ϋΧ3θ J₯.烂t άΊ˜'IX1LΛΑΗ†κJk*u9ΜTΒό ΜΨ?1Μޏ»΅TXϋTμώ ήϋ”ωqβšlx›οˆ.‘]ωπΖrΚΧτd­ρJNSYτ•ςE3ρκZ#+'g—ΐ₯ηΧ.νψT¦wI-«1 +ˆ>2π +s#Ο·Ϊ,!5ι;§,•l=ΔΖςβ’€δ“™PЊl΄·/|ΐφ…`)Ά_­}EƒŠaζΫBexΫ Ϊš^%y@Ά€Χ2η:ΖXωκhb-0ϋ”ž ΦC£—ψ!ˆ!ΦCq[ΜτB\}’Ϋs€_6·=k–nλK­VΝ΅~Φγ‹Φ Φ+ΚΝmi5UΞΑofχ[n'Υ£‡ΖS^*ΗκΦβiNγζžσ[Nj +1cϋωG₯lbΐυξ©Rꟿβ6>w±Œ–γ `·ώ§>7Λzν~μ΅RσωŸ€ζJ£ύfSL²…«η―4£›ύTΧόœμ3«6Τt‡aτ<žψΤO=2ŒŽ˜FA,±ΖΊΐŒU5γw¦QιržύΟ1jώΑ~=IyFA, 1x‚»gyΜθS2χŒn₯Oɍk΄ΰ3zφ’’+χhF&ψΥo4֘e™FΥFϋk—aτ<.Τ§Θ¨­Ηό žpŒš‰sΆQ³Ώ£DΤJβυ>ΰβΈΟ6ϊωG{Θ6jƒ2κld`6?ΟΈΏYίάμΣ₯œΏϊŒοj*σ{hφΫΣ΄ώ=’’―v†·&AΜγ"):ί$”ΘΙψς¬yΤ!!9‹Žy>ωOFΤ³δΝ˜†©“»™ΌJΓνf€]Zγ:Bιδ©J =}KIΛ…Φλ€ζεΊ;™νTmC8εw)ξ£ςOkϊήλ&·‚ΘΌA­ΛWΊΐ₯«=Σ%A€ζ)ύ}nPσDlφ₯³5ŒΟ©›Ψ‡δiε­t§ϊδRώ`b’έζΝ?KV–„pύdWΗΔ™ί‰P3·&ύ|ŒŸ_wSζ%tύΰ’ ςœšo?¦[>ξ«mΊSΆ?2ς›MWΖC9θΚυΑ ΘΚS}QιΚxΈάίbIΤ\LΝΛΰ°³•y;c_Ÿž CΏYΡc‡Φ•$Fhγ34Œa¬ΰ˜‘…Rϊf;iWΝηT}–+u%LT‹n6ΙωΖΑτ:ŒQͺΕ H}–ήF'U*‰(Μ+¬>V¨Μšs"&ΖM‹J€Μώϋ¦1sL@ " ΜΞΌFAψTΘ¬‘>I3ƒΧηᦑ”——œLφ’}aΩs%3Κ9k Γ(|μμWτα›ηߘs Υ8όά³ΟσLazΙ{FΧ=-3Π2ξsοdlΫ™.βΪw*ί ½>ξςςσ&Ί=Fi ՈIϊίhή£γ:₯Ί&mφeΑR$!λϋdϊnj\jBr]^œ%ΛYF„K0c>§ςΥ»‹c©ͺqŸKqΟ%”1ά)―x!%Θη2Α_Ϊ¨~ ΗBT°’χ:ΗΤώ%K]Μ&ƒiuρΊϊφώQ`ΫεΟ~Α‹/ŒΨ*ΗeμY‹?(cΟΊ|χa#νȌ]`Χ}‚iiκΠΞXΎ:ŸϊυaσcŽY€ό΄wve ¦ξxρrΐR},ˆeΠU6Νΰ3εŽujΩhsc‰ψΨχ[ŠQΏš…Δ—φ‘TLŸΆδ,PL.N₯ι²²ΙΏ0-\δ‰v΅ΠΜΨΌ }R²ςΙη 8ϋa1ΩDΈύτ7" `ΕrΔY“‡rΡ/}ό.-v„K`#»ΪΔςη0*Ώz·€zͺΘυΏj! XΓπzΊt(τχΉαEŠΕr %y3Ο© ΰδ1Ϋ©s¦SvŠiΫεf―άΏ-tεZζΞe]Ή-tq\04H₯’hv¨'BEk'|AΊiγxτŒz6‰Σ‚;†)ˆξΣηN` ϊuE„ζ)XB“ω‘YΟ’ ½v˜]²lvG•΄’’w%¨ΉzAΖ(Υ + ¨΅χ/‚ΓΞAΩN’„wεα΄oΕθ‰9L‹½Θ!ΓpwgVag‡˜YF'ΓΡ₯ιW²£)Tnwͺ_°·IΑNpHΑQγ,Ι^¨ ”ψ‘Ν°nc<Ζ"#c=vΈΪ?zGθ=‰YI{ΏxŸ„AhΊ=4ΨcΎΰό’; CŽˆ•»_Βw(¬ωp) kφΙj©ΟۈߜAƒ«χN΄YBj",₯τΞĜ‡\΄:[²U/ΜΞ|œΖτ3}ŸΆ„Iκ_τ7ΈκŽ]t5΄Ε²ΡjyXΙэR^^\2+i1Œ—zƍZfƒΊ•ή§.­Η ΨαΎ‹Φc0Ϋήθ½»Ηΰ½›r]aΠc\-]}mCE#-=Ά7ξP„Π8Aάν4ΆM·σBΚƒςρσ°ΡΐόRSM“‘ΡήΓn1H₯ wrΒ^jͺΆσ 0]αA°|6™ Ύέε‹Oελ"*•π‘H·“©„z-ΨλΎ†[Ω.• ±Œ_Ni‚<<;„ο?\ΡzΦ%²’@`ϋj;l‘‚-˜`…Δ—φΥq3»]ρX‘Zψ%ΝGΒβdŽρΔβΰœ~HTΤρ±o±(89φ•ϋ²RJNα$DΏ,Π€ψΗ0>S•ννZκδB(y Π^ir Αί +ΑCw‡hm₯τώ1―CΘyA 7†,ΚCθήbΨρc²M^‹οPγ5 ξκδI ΐXθ80>%λρΨQ8Ήp[,GeŽ!!ν™Βήa¦ΆΖ[`šΚ ―”/š‰W{*αμ€4O ΞδLφiœ4Ζ§2½½΄5Qylν£ΛFΝb“Xπ΅Fζ¨y\=]~Β²Γη)!˜©ή θΓwΗΞvωσHή(€\F4ΤcQp΄Ψ8Ί7[B]ΎΓ‡xΪͺ88Χ=·ΌσPa¬lΞ·¨μ˜•Ό[ψoκb7Θε~ΗυLΘ1<%ΖZyCθpς{ΗP—U&y2€ΨbΎέ§r²α\[Έ  ¦ͺ <™ ΜεΕ%—k$?˜ΜͺΛH©ΘF››1«Yΰš‚₯8`Zϋ*x*a ‚3V¨ o;qW‚Zf]ηΑ„ +ς9$‰·Β”†¬φΚ†μˆœ@½ΠΘϋμΉ–==†΅lΐ2φ„‘u½2₯α˜wŸαTπ?ιΥΉžΈDŸ aΐΨΖΰJIθΨ`z ½†ždC7πώW’+$dYξ>«0œ93sd5”_:² u?±[=ΰ’€ŸΏA)3βίτΨ7cYΜ}ΛXΰ1₯iέX n ήaμ. #H=‡ϋο]ΣωχJ "™­mLΔn»‘Ÿ³γo-οηζ`S?\jxΈ‹mZίghύί4Z΅…W;ΐvMΏ·n+ρμη^~Υ+Αμ‚˜fn€³f–ΌΖSKaύˆH)tΕ&ƒc„Š~ΨήΦ h3"/:$³³€αΨκ2hβYΉx‰–ŽOΈͺ +ΗO ΄•F*.ŽνBΪ¦X=ψ‰ΉΚρ<Γ‘[Œφ~Qύ,(ρυθε΅ε!}А—ϋ¨€œ‡³Δ†Σ…Ύ&I<πΙΉΛΥC=pXυOx(ΐψηΟe₯Λ ”²ώ‰T_΅‹F9Ψωl.+C›\Ύ»:)ʟώr4λΕUxνξ– r{‡Έšά+X§’ \"™ βοƒ|ž::ώέpΦsŸa\ΌΥl΄1†~KbΒΗ‘MGE"Ι)RQβ”χyΛέuUΌκρΉ“?:ί‚³c`6¨ql¬{VU?Υ€c¬―È₯‚€ΛdΎXυ8άEΰœ©Ύjˆ‘Π"ύψ%5݃́Ζ^ί2^ΚκyΒγžT²Tψ ,pχδ†q]ΨM_α)Ÿδ”X֜ν~3ξΪͺ¬βόgηz-βayPBά‘ηagοή_™€Ώ•·ΰoA'–RšMœs-Π—@mw)ΩχWι\«Ι&Ρ΄Oλ•6Χ@Yϋ ‡x—ΟOxΊ―,»iηβœ‘GΩz7X‘Ž${Š’μqQ^œ«Z¦“ίlΫxvέξSΉ 6vΪΓ,ΎFj†§W!Δͺχ–Œ”0y•Ϊμ “SΕNgLgΪέόΧΦ ωOœ -g‹n°³§’—ǞH Η'­³†0Ύxδ•—ˆ„Ά`ς@(8ω=ΖΌ¨Β8 ΠΕ*˜㏠+«d„ς30›γθ΄?+d.Ÿη½Δη&3_^86’ΠEαεFΌGζ#ςŸ)Λƒ…m[˜ΊδΨn,Ι>kIϊ­„±7Ο+ΪΟσΉςŸ0 +”ŒέŠΟ–SsŽΆ6?α|φ™›K>›μ>ΜΑ9VΧG +Φά>, p(hΆgoΈiϋ•ΦΆΜφD¨.θ²K βdΞ2Ϊ¦yP†v^9 BΣ*?ΟUΡP7;οx=V=…fˍ•V›^΄*PΑπΒ8΄Ι§p”λ™Φ <γ}‘;ΈΆ Ρ ½EΥ['ƒ%ύΠœΣj2Lk`–Υiτ Ώf9›Yžu£t+’γ-%-H˜.Pύ¦Σ¦7L +LDW4:QΖΤ (PηΕΚ‘γ΅B'€s1Θ•ξηyΨ]Bz^ θ_λPϊχyπ†€Š‘¦cύKψ@ΝΔΈŸ„ϋΰg&μ„ρΊΔiŸλΊν Υ5yΧμ&‘Ε—Ήδ7σsfσeΑdΨs"b’yͺKkζΓ“ͺΝίKιbkX%₯&DπP·ωƒkΎΤΝ&‰ψ9Ώ4±Θ Ρ-΄όθΆ’;2θ_ιιsŸ¨οkoRΧ·©ΚΝΡ€ΧŽyͺή™™/فm;*ŒŽŸ¬^Nϋ –•—"B#ˆQAέΤ,pFEζ[ ½ͺ αQuGf:RΉ Qk΅ji0'e8¦ω½‚š%Z― rδΎ'υLΐbF)^ΫιntW΄U©ϋvδΨ•ƒa8jnΡυ²9qΙuj‚Ί ΆτΐγΓν߁‘ŸFŸ&α0 TΊHE―ψ!ψO—ƒ0{€ušrΓrΤ‡ μ₯Υ£‹JλχθΧQ+’tό?π^n;1ZτhΥΔ+γ3sΫϡxΘι"ΰF¨“'«Τβ’Ύ–8.0+Β0yDg}­2J(§>ͺ° +‚z;Τd λΓ”Έ²²W›ΣϊF6―m`}IkΗVαs>…‡x[ΝΝ%κ"ςεΥs₯V‚vυ0ι%\(<Π―/»9’8%ΦΘ,ΈΎM«jlξΆ ¨RΤh±cꌜ 7g~Ky’9η!x.&ψ΅σ¬vzL,=Ÿ5ιξ ›˜FsX¬·”¬Ÿ mΝοQK™)Φ«Ω vΚ΅1?Ψ Χ«f’‘™Π +.ΔEZkεʘ¨‚r˜Dϋΰ Ο)}nzπθs+ 2z0ιΊ@Έ―$hQσϊ< Γ6HŠy’ΟŸVΐγCΩ„(21X_¨vPΒΰειhdS7EηιMΈY·*π’ΈΤ»(}3.]lΗf4™§J½‘π€0œΉκC@>κMζκm`!vΖౘ¦8pCœ0xhi]λ6φwθIΡΕ&~VΧ–ζ}V«yuΗ† ΒΠέtδ<¨št/!πZ<α7A“―$©ƒ0MT/@uτC1{HΎC|Ή!ΑŽ:ϋ%ͺh‚1Ž―Φ<€­…ΣIͺθ-vζν Μ«ε[< P­Υο-|Ό^Qι¦Ϋ땍{n²ρο\„Β8Kt”ϋwΉa'Γ?ΈaΣ–Zϊ?n€΄b―’aΕΒΜΚ_ά νΡx„Σ$θ•ΆhΨMάƒ2aAω““αΥΦ0αΑ―LXHόr‡ίͺ"ΆŽ—K7f†‰ήφr'…RΪ>₯)™ο‚cν{˜cφͺϋ(Η@S`4‘qμ‡ŒGŽΩ‘xcv$2aΟψΥnβMmιϊ”$Š…ΨC&¨»p`τo˜x°ώΪ ˜ω|y(”›oέφ—ο·ψ?υW3` 'p"φ―nXΙu‡θOW…‹ΊUΈξΎΓ]#ΓWέ„p|»4αy0?mΩcρς&ΏίαfΊ¬–Ώ α±χ‘Ηͺ₯η^ώΦ‡<Ώ³ςžjι€Ÿ· G§pΑа\„O™[~-Yν‰j{|G•˜―πΗ|ΟΎ>FΟ™ζ­R^(%ξ]fΪ“R{ρΕz~ϋΑ-Ππiκβ=2џ”ΚxlυΚψΜ\πΪZΌόEΖΎτ3Ξ‹—l牉φ^Ι†‘5™$ΓΖ\’67Ώρ™·pΤXP|OΈ΅xϊφ*[NV’_ΐ?ŒΖ³Μf°  Œ a’ΐΦͺϋtΏύΆ$k2Y˜JέJΆL«Υ}ϊτιΌmΘ‘Λ8 ΰ3ZT<‰lt£Θ6”ζψΆM΄Λ<ϊD°'ΓvMtΧcΨNg»ύX0,΄;DR +όΜΠ£EΜi¬I’œ_βμόυkΪειμχ[>%VJΕRσ!χ«ό”¨Ά@zgKx,>©η‘9ήL4\Ϋ€Ώ(“!+βJ3Εjžd"™ώδΞq5ί΅vΟ‹ηυΑsα‘‹5Eιg―Υέ|~;ΟAΟΧ‡™rρ¦\«₯βϊ|ϊ:›ΓηθΦ£δ Ζ’M6‹ΔNΧ§ήΈ@©€JKΣΧɍυ_‘ΎF1-›Ϋ-#<½˜ΠU‘—²Œπ80B΄zΓ„Μ½€QO3pΓ «ξcώk|ΌMΝ£ΛΌƒτ°OŸοJΗ +ίlόΤλ5ζΖΩΥ—’Ό›ιΡ­‘σ°•Ÿ₯y§ οYΩ,ήΥυ»jο¦ "―ΪζΪ a™shΟZ&‰Λ₯ΧύYΡ’ΰ`Έμ•Κˆr°~.uΈ‹_OɌΐ +θν%Γ_{―ΠύΠFž‹±#4-“ o}PγmΚfωκquLΐyΉΩόŸe  …―d‚KWZ’s7θh9žΪ ˆK+Σ½ϋ¦\­&Σ•u±(:-Ενφ6ΑΉ0%¨“2ι/p +ή’Ξ6jβ*P…Φ=OJξar„Ž‚Ϋxl\ۏπυήUq?»D‰ψ ς‚j9$Φ‚ΤΆ[2 %kλ‡ΗBiQνALnή,c¨9Bψ}Y&R»φβιΠ‘sŠMλφΌ6ƒš‹ϋC·™4xΈ.ήΆˆn?Ϋb4¨yuΗ"w™ΊN§Œj!8ΏΥk³γˆKqšθm3,tUΔ±Qnυθ³αΥ«”$ ΰ5i `«΄IWΆa?±"Κ€Qγ`5Pμ5F΅t- +ΉšŒcΦYΟTlΒNν²#™0 d€k—–£—ƒˆ³*”$ζš# f1jcΘΔα.XωŒš[ΦΎΛςwβ 1fΧώIŠΉ?ΘZ…,‰ͺ—© –Γ0’J!«±β¬f”™Ϊ½δπ’ώεMόΠ«†9ν"Ω½Νκ¨ΙΰacΗ}2riπq€vρ@q$Α†ιq΄0(΄ΦBDζ£ζ`Σψ‘ +ΩG ΐb„ή°€ψΣbΈ«»Ν›ΗLQ*ΜX\ώ–αžW#―ο—¦Q_πŸΜ°#vΒr}Bu'…φ8’(ΣώhΌE<ΧG2BEΑ +b‰?pšϋƒΆElΙΫl‰Δ2Ε–ύW¬QƒΡ`yΐiGlY–<¦b=*?[LΗωYΘπHžήU.ίHm²‘ο]ΥίdmtmΉδP+α±»ήπͺ·ΆΑ”Š< ‘αdIĈ0LΞOcˆˆdΟ,–ΕΩ›‘Λ#p—Y ₯8έjQ‹’‘ΖBE»’lEβ/8‚¦-Bžc»#׈_Ϋ”έάQnψNSgWnχΕ@Gn +^τ}G™ΊmƒΚ_Ζ#²Ϊ9@oή_J~!}Hwμ½oSGςy`šͺ£SZ” ζڟL΄± Ϊ€6$Υ,ΒδΔυέ.χJD`~{bœ—w»¬ΨĐΔκ#JχcΦ}ΣQ$ Γ–όΊcb u 6P’)JΥ2u― +λΔ•= >jεg­[ΑβMΓ8wήυΆΞ»ώN~n­ω˜?›aΐbΘΉq·ο¬rχ–Θ.>{ρ»ƒ$ƒΆ,΄α#«ξ;BaθBΗQ.Šrώ)\ψC-ςUΨP$μ&ήπ°\(?XΉΑγΙA,ΩZΒύ¬#δΰ¨ C½φ‘ΣV3"·ζ^δMδ497bσ*Wΐž·βa!˜ύŽ>64ρwξ‘TTƒψC+Jes +Atq} +Απ‡²O‚°Lmœ bTΨ,3r‚ Κn7K7 ϊβ‡!$γcσ™εΪ B˜ύ3β)φ‘(NJΞAξ€ϊ>A‘qƒΐ¨ϋAΰδ3νƒ μW?…ΐ|Ό/]'™§μ ΌΑAdΎ_rN3ΚP’ď O‡±Θώ™hόΛ„βεΟυκε]™π½k”%Ρ-)±ΎF7#Ÿ‘ΟͺRHΒίΑ΄"”Yώ;bηFBΊŠψΥ5ΖΩ+Χ@l°Rˆ?™Ψ”#ο^γΜP*’Ώ¦`{―a– Ÿ‡ v±-υ₯QΪrφ—ΥΕύ’U―τσρcπ›ϊΖͺδψcŸš"ίΕXπ)τ=ŒEΦά;ΫDƒΎm_βOŒ “w²‘‹œόžΐΨΉ&Œ†χλr}ίΞ>2Ρ Ν}οϋ0”ή%ςΑޓ ˜_Vόg>ΐ)Ζ&σ½l Uϋy(qωμ¦Xύ·ΪψΛ‡DCAjό΅aζ;Ρ/|Ψi²BudS=Hέ,eb2dΏΧ& žw:ς§^|βΓFωTo|°k­SRό"¦bNrCI±εˆ6‘(Ώ[βϊπ7[Nσ΅<ύhΆ +τγu·ˆ‡…ˆWBXQFƒšWw†Έ™™©ͺηωGK?ŽFωα2νCSΘ@ψOA΄GJ+βΓξ90#‡zRψe2ˆ†‘3R6Χ§!τ‘―τ,­FΌΦ^IhHƒ‘Ρ¨πHΖΣ­|‘Nϋxˆ\ΖYΠ›q)θ¬h!dΈρdΟycΌJΫΣƁπ/πΠn‘Ε„$K–œƒ”3$!δ€%„Γ₯$α(8ΫζΛώφΙΖ8mwŸ…!Ώ3͌f^Pt·{9σQ}ZΒc―—Υ υk*0zH –j.ύRΟιŸ–ήΪΡ;)π‚~,Θ&μ0δ“‚{ιΗ‚ΒΫ]•q,’ όΨμt+‰]ekεΣ\R0ζ£X€Ν• +x hˆΎϊΥ”f]ψ§P6{sΣΨϊ±•΄ ¦3ΫΚ…*PρΫΡΥ$ל›Λ£.b]w-h•σχS]MΚTr+2 dΜΒπnχΞwn₯Λ΄ρΩy‹σ2‘fοάΡ»μ8ϋ—ΑEω·JηΓ/κΙΡm0pΌq²9ΪlyΎͺhcι·Γ…©@γ#³λ»ΓβομYb±ΥΨ‹-–EpžςΊ5.΅_R“ZgtNtTtSΏ¬ϋr±2ι()άέ;λςΊδ&p+†^έ—q±άδMY΅UΡ4tSXώ Ψtυύτr\¬ιlί0νχ\ϜxΖC΅© sK@ohΎSX―ξx–¨6φΐξ'ko:NRP}JΥ†Oί>stream +H‰ΜWΫnΫ:ύύƒζ‘@L|HŠΧ`P@ΧNΡ[P· %VMeɐνφtΎ~·dGIΫΰ?BΗΗzΣTθκ»nyvqU]|=οώœ­Ώ]νζ…’¬ά@†σί„ώM°Ÿ0Ζo½@Ω~+ΧλϊΏΎ[G6B[mΫEέ^&ݟ~ ­3‘γQxΜ…ΐ?λΥϊΆΤΜFΚyQ=³ƒ35™ γ²ξb»¬ΪΝiί]TλuΪ5]Ώ> “¦Όψ`ϋꬨ› +;]–›Γ™oδβ,ΩΦΝβέvy^ΑΪ +ߝΡπOλςK§gίlΞ^-Ρ2―6¬ Κ½ν·ΏΏžΞ‰υŽxώLJ겦£€•ώ}4jξ»Υ²μΏϊαX?2€Ί?VΛUƒΞΦ©Βγαoϊ2ΚbC$'`ρcΗlhΉ…N‡ώk“TίκκϋIψk«ΑqΏ™η"%cΓ}θω°mͺώS[o°@2†ŒρΆ[TΝΨD㋦$ψυ}ψXφ—ΥGΩ5Ϋ y›έΝcΏ)Tώtψ0ΑϋUΥ~μ>ӏ•ˆΒ( +Ω̊†Γf0‘₯ €y$χsςλϋ Ϊ+ςjvϊ½cœβΘήχυeݞμμ4ζΛΎ^\Ÿ₯‘n΄‹™όάξ7,;ίlͺv\>|(};ρ 6ƒωŸεν"ν–ήϊkουp„^t—Cίώ™z0z»v@οg8¨ΣΎn½ΚΰυΨ³Σf‹—}·]½jΏtΑσ!ά?Wˆiœε"|ώΌ tΙMÏ}yxίΛΜΚzut―:μ­―Β‘#ιuχπθ¬ϊ‚»>΄ζν·ͺιV΅ϋ–²]„*ϋΥΓͺO›²-ϋΪχšίΤίΠSΒRΧΊ―Ϋ‘^²‚qh‰άšΰIΧΓΝ,Ο»¦^/χš§-§eΏ©/šjώc½©–XΆχΏΎ}ίβα*Lϊνϊ*όΨuΝυ²±λϊ8ϊ ΅xΩ_eHί=:eνσ ²ΐ!έ7»φϊΗζ_lŽ΄lšϊ²/WWυΕ‘‰τοgϊžζV8¦jλύΓλΝΪν¦aΎ¨‘δξγ{eζίΛΝΕΥ›ϊΌ/ϋΊΊwJΏψ/u»€ΥηΫzS]›¨[ˆΡηƒέÈτ ²ƒΘ!`ž›7ω#B5†w›Αgωπ©C"!|K2ͺξ9ψ¨Ώ†oŠ4C±%wΏ9Π'jxΞ’ͺ SpήΥΙΩšΎΞ’¦tj,Ύ(λFF1γ‡L*¨˜91MՏ’τ"6•&If΄Έ%ΙΡoΜΨ/ƒǍ# +‚ϊΊΘ^$»ΙnA’M99ω`Ψ…αCζδ#U―ͺ›€H€™ρ"Β[Z EIμκχΎ·Ήξ#gγΊLŸ]εΜΞσڟΉI€;η},ͺΣ¨v1Jή+Nԝˆ †δωΉƒšΩAυzœΈ€ω•Ž»a³[¨<=n―ρDΗ{΅― g6:s–ƒ›Υ™―ύοτ;Ε>ζ5–ο+Q$a$q€4 ί$”$–4˜»Ÿuύ£¦}ρ‘ν―:ρΦ§έ•φφzφ“Φ­ς#IUistξ…? e)7ώŒΛΧοΉ{ΦόΩRAω[’‰ΊώRzhΣΏπmhΧϋΔ{πlΞ^oτ8±αΧσ·ikώ«γgώ…ν-¬ŸρΔ{τ3|έπ‰‘©;~p‘kε‘νϊ«7±‘m>δ‰7ρ§Œ‰~ώΪώ4Ώ~ύΛ?ώϊλ·Χλ γ·_~}Ν/ύρς‡ΏΏΎώόo_/ΏΨK—ΊϋB§―ςr—ρ+/"Ζ²αμxΓ™°ααλxπΉ™Η­αIλ0e#OΨ‚ις7šΧM0έν ™ιUf|j}jQΥT/TE“±¦|I‚=ͺAŽΕ&§b–Y[‹YVΑDWΥRM덚ƒάAώ†Zε';u)œ‰Ξ_ή)>#zξτΟΉ`ŒθΉΣϋ.Θ†Δ½23/wΦΏ/}‚Žcvvς€(|ͺ,ΊGΎNfΤ«ρ‡ξR»‹/δ»1Ÿυ‚Ύ΅Ϊ·yωcθOΡ1~α―ν7θXοϊΜU›±.sήfΎΝH™‘«>³m4ۚΆvšάj¬Χ”fcέ†J½ΡΞ–[:Ž΅ν9ΒͺΪv4” [_¨‰»ž€NΙI Ν I +Ι +I ΑΪ;!7²ƒΣωΑ 5#ΔI"Y"i"Q:€J@²D€K„2ͺμ¬Ω7 +Γ1-‡EΕA΄W}­œΆ²Š΅uSYM‡U΅Pk¬ͺL5œ„P_6&5 ³O©σјœΙ›ZS§’ΪξΧζ›χ¦‘h4MEk(Vv$CΘͺ¨ήh{ΈόNν*Ϋ½ΒAρTύ™¨<ξi]ΓνQΏ©ζŽΩά–@ν*Ϊώγ ξ ι ΚOΤa3\‹ξŸ²›ϋ»’GO¬ϊσ5όΰρ9܌ έ™ͺ§υ?uA^Cƒ„?Ρ ώgψΟθ―ΰ―Τ ρ'£}%}ΚgΒ'ώ΄ŸAυ5hήƒγ#Θ}―Ο€τXξβΨ=€―…¦…œ™•πo T¦˜υ§”œ6lξW³›='^Η™Ή„ ά¦9aRΝk0―±œCY#lqŒ0Ά$ξK +k «©τ°4±qΣΕκCφ}캎ё=ΈΊΩ³šΕ]gyΞΖnzίCζ©?ο»ξ‘οi\Ϋ-8§ΟΥω§χ‹>ςζ^p|DτΨi»·Θ€±«΅pΆ¬PMλ1˜Ζ’Ι”ΨLlŠͺE•‘³ΐίJCΞδM­©3’ Ρ”ΏB>FΣdJ¦Ω΄¨F#^aN2ψlLΞδM­©3eŒ¦όΫσg4κ1™’)ίηE5΅•vuogBʜ’Ι|*8žA\Yόsοon–χ/Ή~ή2Νζ/γ?ΓΏλ·/ψΟ―_~ϊνΏςƒιλoΏk?ϊΫΫ_υνΫ―|ύςφ›ώθΝΏ˜ΣŸβ5ψΎΰa§ƒΚ͏dΈΜξkΉ§{ΖΟ φŽϊ;r§ςMfϋ/'‚3ΕS₯‘»ΜŠΩ|܏θέk:κ,˜~τxύάAώ tαρ(#r―½έ;δmοǝ¦M¦+«ζκͺ₯iχΩΫΥοΔsΥ¬\τ+!£r²)―ΪJ‘“©ΙΨ²jVΥU‹h·…νsΆΛ©ΌX,rCbΘ*ΡYΙωΡ•Ή¨@¨t¨¨Χ!}ZΗΕ]Ή7 +wΨHεa0–1»1  ’γšpn9Ÿ,γ‰θ»§ ιŠω 1†Ο;ΡΕXD &LΠ'd@#DT”ΨΨn—™Ψœ x'¦ D°v@’ _€(‚z°F?½žBk< Φγ\:˜„ +ά‘i˜ΏL€a6ΛRωΑT@³ž#v!¦PυΔΧ[άaλ+HΑIΨ€ŒΏ&β΄=j Gyt(œŠr*¨΅‘£Ε-|"="Ž€ΫaθΈνϋD°y΄ι‘§vG³4½γ›όJ—σp],Νf‹?Τe·°ζ0Σ/¬ύΤ(ονΩ(ΗOρΧFyEαS"‹EΦ¬β°*ƒ‹žvt(FΙU 2ΉdΆ©²e~SιQQ‘^ΜΜAφœ₯φΜI²‘§ΘEΎY<΅ˆ 5\ΨΒv¦ Ί +—OaΓͺ-J'-[άΆxoqβr‰M—)€bCoΤί‹Ϋoξ_²€$ƒ6xp΅(‘Α’<πΓ/j—τx^•T-ά΄°γW΅8΄$³f&»–« Ίj^UvΪӁΏΖμO»Žθ9ςιH°[Ξ}ΐαGnžφœΗ+°o~Ÿω<Ÿ‰-Εu]p0ζ’Ν9jz€[υ‚/ά&{ƒz’γN(\χΧzδςv\ΠπΜ;r}&EΟug©Ό― +W*ςq72uJ₯ΒD"&eΕ‘WYF‘ΐ¨˜‹’²Θ)‹ˆDΒ"κυi‡’#Œv82aV"ML$a’P)0•„K–l ³i£ΣΔ½6p&M RΖ{攐ʒυ‘HK#0kβφ$nΉˆ]‘ωE#†9†>Ή‘Ξ0Κf•6σΔ’±22Ϊn™Gˆ‹ŒΉΐ«yθ™x$€Λ0ϊΖ_UΞκQdJɜn‹ΣL˜ f‹Τ&‰krLMQ»Τr­6l3f›YΫ,άΞΧν ί‘…JMeΨKœ§?‘»£[k{”5'nψ‘ž^·fϋ…«yu«ά³ΑNj‘Ι0uΕ »_q»^mRK¦U\j%Gj،Šύτ\OΥEU6 ζ¨φ +Χb₯ΪΔ[-šγς \Μ‰‹{ΰBŸ\1¨{ͺJέ€§Β‘ +uά/Z“›(σό§Ζ’+άlΤt‹_p†τΡτ5x‡aΧΠ©‘;–,υ/‘Ί™@O½M.­ΞUAΥK BHoδ›#]“"σΔ¬Ρiް !;ΓΣeαb£fΰ~γΩγΨσWπL’+rΦΚ?!ΆΈ²#μoRUΡ|[΄ͺ₯γ€₯ŠGL†]zΘQΕΟΟνjΰm6‘φ M+ΣΜiμ=OΆ§φ6Έš'Iυεxjξ%ԝΚ­Ζμ ΞΥg2Ο—ά½u—ί>³ΐΗΦ²πh΄h0‡ϊ¨?*—ŒοαH L3ΩηƒΤ +ύAekβY€ˆ¨ΆŒ]ΑζFΧqΑ\νήˆ¨ΒŒAφ‡[§σΧ‚+μ(ϊğv%ά iœδ+‹9ά ]DϋPBΙΦ樁ΕbUοή1p2­^{i5?7!ώ€^Ζτi B}/°{θσ₯ŸŠL.œE&όξ%2u845(n5'U}ΙȞ$ͺ!/">D<ˆψρ^MΑώšΐιRv UŠο|ΗΊ –Π†βΧώψ[μ8€?μpgΕ§ΆΟŸνžοθ[ή\Ω·‘oγήF=a^#žπNi§¨σ‚9³Rα”oNΩ–”k#­*Ξe1–a‚/ Λ0ΉˆZ¬Δ¨R¨…ΡΤ3”Γ(3…&¦OeζXfgΒ$Γ\™%Db‡m> SΟ!VΕ©'πΌ;w(ς.‰²ϊ„χlδ}›xοH³Qλ@b§ŽF^Ν„γΝυj*ΔXDήκΔ۝Υd`Ϋ {Kρ™EΗΜη@b_ͺ.Kž.—S‰© jH’œVRg’UpΕΫ^μ―Gν‘έSShξxΕ3_ι[~hn<λQιςΉΜξ0WοΕ'―οyΰC‹όͺ£~ͺΣdάMΕ@Lψψ† +8a#Qβ½Γ0 :7l+ΌΆώsσўHϋ£&δΙ|μ³Ή2·\²%―σ'­…:κ΄Ρ„’fK)[Nα―2ϊa[X©:0μ–WφΫίΫvא§³QΏΤμFδ6&§5½²e»kί]Ι3ιη*f-P₯zΫS‰Λ…šςΒΎΆόsuݟΦuOΆΙ«mzΗ¦„0`Λ‹JEe"ŸΕΗ9¨$Κ₯£νnuΑΛqΖ“Βv/tΊώ‡ιες·Α½gΈψ7οακ{tD7.πω3όώί?ΐ'δ€0ϊΡΉέ° r̈BΒԀ앐Οœ3­:€ΐŠpXGd³Œh6=ςg€wΘ¨;[ΰ:Ζ$TY–₯BσR iΑ,§Ι΄D(,pͺ‹[zΖzGKk₯δRKEθ¨#§ \ρ\„!ƒ$C…LCέΝx2VΣU{&€Ÿlβƒ ³s°νΠ{Z”d‡‡„ω…d²‡yίvC†5φ›ϋ~ι&ξΨ톐m$Ύd˜ν.μoˆύϋ€)zφ–η7Ι[Ί”zK7€\Ο7`vΖΓ *₯έ[^ΈIί½£ΟJ=Ζq|³δΣΠͺdέ³uO׍―-hYSˆζ³F #-Žp ρ 'Ι$’J(—H2‘lBιDς %d£1Ε²οvV$P`‘ΘB‘…b Š.Eζ-L₯iΐL•H@–έδJ4”iπΘC<ρ Ο<Μθ#GQQΝͺΊj1˜P¬ΎΨήκyV:KK;7°³« 0›ΠΔtΜL-5΅άΤ’SΛN·ι) PiχεC R«¦CšΪΥ–ͺΦdek Yϋ¨u \ϋΨερ‹eΦΆ)}P>Υp&³ώw|¦Χ3ή Ε.ΰNΖ»–φNRŸy_ΊRσ0^ŽŠ·‘ρІk2Wώ‰²ιΗλ§gΣΧnξηοͺ‡_τ HΔΟπ‘#όdi«yΑ[΄Άƒι @E‚ `F'˜NpΑ4΅hlZ`$Ωΐǎπ³ΎΆŽ ­sΑΐΆπž.4Z Φt€I`W%Ω=½Œd²β˜a¬$V +W!°a{†oRπ +t«ΆgΞflfΊNΜΥΚ<%’:¨¦Γ€ΤΔΰ»xˆWzφκ°x:Y^cwgί₯α)ήOν‘Ÿ9Έφ-Θωκκόεϋe>sσg8]‘ΉΆμp ¦ZΰΙΦ”VeΥvͺiUQΝͺj0E‹¨YΞΥόmnΘ©Ό*¨’ŠM΄α²ͺ}B»&UQΝͺͺZD“:^ςœFΝg―r*― +ͺ¨j6>«Ϊίήή=™I’šUmŸQa«-nWz»9€ζSš3/«oF\ x³έΝn§Υ]³§6» VΥ37μV5³eu¬’ΫŒˆ]zλs»'Όh1n r[”Ϋœ„rΘs†wΈE:Κ1Τύύ*ۍ䢒_ΐ¨ΐ.s_œΌΤ8pΓف Œ”IΗγV (0όχ9Ό¬…΅t«[š— S3]b‘,yξ9ηκUQ¦’‹Ί¬¦Sl§€ ƒδŒε\7O*δδhίβΎJςΖΙΤ::„ω:Ϊz1Τ<šΆάΡv§¦‘}N{,Ω΄ΑisΓFuŸΧέ…ς²=ιΝ”χjνeϋβK΅κΛΤχ27”›‘™ς`ΌH!ΆPͺŒPΓίxόφΙΝ©ΗυΏ/±‘ΰ₯ΐ>α‡cOγWaθnxalι΄°Σt׌‘wΰ35z―©£TfZxͺDαM‰c”σ;3Όoγ₯36.Mk'πc½Τ«Aζό%—Β[κ–}R}enΊγέooοχΗ§ΤPίΏ;Η¦/‹Ot<ήώtWΌš0ΡΆΫŠΒάα͈nφ’»(ϊu0Κ¨eˆEΜ—ΚBa¦°)ςΡ „7’^"Ύj ΏD‰cΎv‰'"LΉ+UΊ4Λ1¦ςš-¦ΔΞ‰1§F=Q#Θ‘!Ρ‰)ΩGr쇔Ÿ©Ρ –ΫeΔ8b?₯?‘βLˆ‰fΎτΰ9$Ψ%ΆfβP’ —‡[nό&Β*ͺ,κ,š)ƒlΔb\-ρ΄Δ‘τ’ Δ2ΝψY£g‰9aGΐ13pΨrΪ]δΜΈ1#nH!’FŒΐιX&ͺςrͺrΊ\N'-•΄bkμ€£έ%ΧΑ†΅ ½“Ε>–Θ° 5[¬d™²G :@ΆΐE†‰ κ•›κŽJδP`K$l€°…ΑYg₯k6b`½ς g¨#gΆ’Ž~tNτš'|yU»P Ά–)ΤΩΠ'Β02ˆΫ°;α6α7™ύ9ͺUΤ‹hΡ.’KΑ†›~ŽΕνηή~šQr±U†Ή…s+¦’ΕΡ +λΧt£΄ cε½±jφT λrε8ςH q†zλρΧΨΆΙX93§\ζ―ς:€wύ‘>οςC[#ΔΞϊ ΅ ΚΥΛν Ά°gύΟ’άα.φz[2[©˜ίQ±ώΌŠ Υ ±xlr@y)δ—₯Πn1t’b±Z—CΧ{€#ΪΪ!ΆrCϋNhεƒΈ[!‹=λμJνž1@,“ΌSθ$vφ Γ6Ξy)~§¬σΎώ>l’ΐšΠΣ(Α%~Β ‚‚–b±ίδ’ ]ο“6hkˆΨnh‹œ5X2˜°}tΚeΐ˜a1ƒ€`&žυ@ΟX ͺ‘Ψ™J¨η($Λ(djιƒΆ ek(Ώ9qό/2DgŒ;εy.°9;ΖfmmΞ›™•‰Ωχ/μ„uΩX–ά­μ:•©28aQ2Μογύ„ιgΊώ‹©-Έp`Γ₯_²XtΡξψ ΅O7^D‹""τΓ ΏtεΈΛό/₯ΤΧω₯iΘ«ό’ί³K>Ί%3Ί₯—•§―υ„//1v2πƒΔΗ ?NψqΒ+&…U¦EΡς$A5iKΤAbaH< AC|ίΓKβtC<ξ‰ΊAٌœJOet'†|‰ŸI3ΪR2"Ι‘Ξ•Lς’£iYfDΕ)#šΥ1c%ΘX͌υ 6˜Ρ•$kšΌIt'©Δ=JK>₯Kfuπ+|Ι€_Š ξUS ”b˜Χ—" Q QOΡLΡ2ΤM)Ί,ϊ9–Β/ Ή +l<σjz'ΜΙ°Ϋ`Ωξβ8s±sWWΈ$Ψeέ.ί Gž`‰Θ\hvΰ ΔEVθΐ’Ψΐ κH IΙο(ρJψ˜ξ:¦9£FLμ˜1SΗτν)iΗκΑSfΞ5ΓP/P‚…T(0J—žAΖ-A.ψ +QϋΖ·Ύσ=\Ά€ΥT(s eΌΓ†P…Ρ„6t‘=ό·`dEU₯#―€bΎϊ +=«ͺͺMΥ"Ίͺ―zςθ‚Lk,Έ"u"‘˜Ωxwœ»FΕ«A΄ˆŽ’―{2φΙUGΗKυZ,Ύ’²†˜h O(*Φ` Ν׌WKΡ Ρ§˜j†©Bmεj +ΝZ=…ΙΒfαVαWς`ΓM΅υ™hφƒmš^y1όλ>d°;έΠ„ΰ >Φjπ§nbΗ‘ς: >2ΚsoΌCν” +%”Qv§„»b Š+4!l!TaDΙ‘δγL;OΖώ₯5x`κ8! +₯Q6½·ίc9ήaVΈ’BςRΙΰvΖ―{LγKΑ—ƒζζΧ•j·nTo°r,Αtœ§ΐ}’β Γ ά€ΐ*μ”aP§…Šΐ}Jm}“Nƒ‘Ξ›mPPγθα&[P\ εΐψ' ·©α:%Ό'Η`1#ϋ3œgƒ>Ϋμ‚p3ΞuΒ"C‡ω±³%wΚΞsœξrΝ凧ϋϊϊύνΫ±Ή‹?1χ΄«}ίw} *!‘χ0qήTχͺ—½ΐυ]‡j U:Γgα_5l²μφ―G†΅ »ΌZΟΐΌΜ¬AΫtΞAσ谁TΠ­°Π %‘Нιΐ« τ§‚y“…HiΘ•„tqλ@Κ ”­‚Ζy¨έi$„ΣX€άU]8:6Πa=8φϊάBΔj(i€toWsPτχRWCκ$ίAψ €‚o–3˜ωjΤΐβWπ >ΫΒ5hx‰Β€£Hθ \α,<­ƒΡ0° +¦Co=,H m­aG‚ς ΞΔŸh)ε +—=J—\CωΌ³CΙA#QοpΡ£φi!Σ5pΆHg@}&A’‘ ΐ9Ων=‹ΐ.ζ'KžΨyrΖhΈωζαψνγαψt8Ύϋόσ έωφΝΏβ•ž|{ϋτtxμϋ‡χ‡Ϋwχ@όxWm‘SΌ*<˜Ω&¨R§ +ε| 3gh™ύύΟψω݊aείBώ·DγΟψζβwΕγΕ]ς]€Γρ%γœΕOhέΌ©ψ:oτu>ΑnγbόΎ¨ŸΪΓΫ§ΓΓρφρ—βKbδ/ꇇχΕ§ΥWΓ~άtw‡§‡Η›I=»ωγαύύΝwχoŸήŸaΐ―πoϊΪίχΈ‘Ώdρc\©4₯ + 5α] P¦RΌ 5ΈY› …Qΰ'Tc…pO€ ―‚/„…IU44Ξ₯qθε‘…ΡΠNεDšΙΔ™΄ΑLγΌ/‘‘Γ8_:)M©½W4=ό;–εnΈ+ώLύT3 ύπR@ΌΨ,―θφχΨUˆτ@bnίL&dTM‰VYJh8dΨ•Ž+ˆt%²-RCΉB‹.1°D)JΛƒΖHQb€.γ\NΔ-6-uF©„6ΓHSβ€c?5Ύύ°M`šKπΈOq9˜ ο΄₯tγ;γ1pμmΦκ-Bό*Lͺγ>©RΕcΨiπŒΚργ±jΛ±4JݧΟTιh|Œ‰Ž>ΑΛβm {‘6HhψŒ€ν°X‘’X,7?‹wZμhά24Ύ«¦w.δΨxcc Ά…γ€%wiλ"ΐxhΑα8«\z%²kΆ ΨLPHΐ€ pΜ›Σl²sWpm‡cζ˜Ιy Β@°΅Œ¬€ώV…]ΡΤ’c~Z WXƒ‰πD†@a +°O>¦rΔ¦Τ-8`-ؚ0,Ζp‰€–κψ8ac?œ‘ˆh΅ŸαZŽ/βάςRcΤA °„Β¬ ‡)­βζB¬7 +υ&΅Pj₯‘xlpt˜g,ByJΘE Ή€Έ +ώ_Λ₯Χ²›ˆΒε Δ½νχc˜“ `HtFQ’N¨₯VΑΏη[e—χ>$ &I_Ÿm»\΅j­U+AU…ί˜,΄£τΡVK‹\@Ÿ%D••ψ +Hκ£(jݍΛδ«ΓQΜΧQ°Μ₯EΓΤJ,‚bή23ge]©΄PΛ¨ζ@ +AOλf³'ΥV¬YαX$”‰£Πύ@ιΤ‚b£Χ?~†m|ϋQζΟ]gŸ?^ ¦B‘“ΨPžΓ ί μΌ…α—=S“_ΣH9ύδσσΫΏrφKΚΤyαΑί@GPΎ}©Ζ*‰_"›ΖψρY‚χί £/Ή& šς%b"„7½ςΏa…Χ%ΐ͝rΏΐΚ6φpτ ½ώΒfέ?_ε‚tΏL&@ϋ?έΟΐ¬υbG€ΎΑCνώMVήΎύe±zsϋρζ>‘K>Ύ§Ώ=£Π―¦ψΝψφοΏφΣΗΏύ‹Ÿ}ωΕWn#ώό㇏X?m•Cίψξ‡oΎψj~C ϊτoTπΊώI %~οή;<―aaη‘=>ϋόρξ/όλ7΄GώΞ~Σ-Ρ(FkHΛ?k8TΠ…%+SΔcŠ`hάΑΙΒ°ς’ώ%7υ _‘P>bj"±2ΫR/εΡhΫ:θ_ΌHi)Γω +T΄6'ΫhΛωπ–Ϋυ&ˆ”zŽ­DΪ–3g΅ό’Gΰ­€ΑΕz•‘K7d.›;H>G@"-LΑCQΟέτD~ΩΥϋλHΙnη»bL°Δ[Hέm€ΰ&r‹€Š<@SΆ; ΩcΙOyi Ώζ#΅Špk0#Ί˜_[qΘκΊ«ξ‘(·Σ>ΒΦΠ J“#΄–τ—νiίFlvψ¬*G,y% +"–²¬yiο‚Ιr°Κφ¦“ΨF€οsiœ“PqΓ\κcΫhΙ*5»oƒΚKνΚuo6  °­Οs›€†xI™ψS£λ―܍5θz)(΄+"Ίž΄Ω6IE0œ΄ Σ–x“NœI“gΝgΑHΫΟΟWc=U8Ι±] =,‘_Ϋ|q™7ͺƒcϋω›»²ή5_{ω€CοΫΏ4`@υ™άRΛΧB“ΪŽ%μg‘꧁x+δPŠ)8ΗθςΜ΄(,ˆh'1―ΜVΜήb³l«Œlz-jΞ"ΩGΝCΤ»ΠωUq(’>ήr UVKΛuΥψeΖΌ“’§”ΊWΚθΐ"«BχHχλΝ"Rό–3·ϋωΥ%U‚X^PDg Nv4uA’©ξ΄FŒ1#Ή4’Ί…Œ'΅/•Ι³Ža΅”… Τzγaί!*M¦»5upάΈ"/š©ΌΛ±7kvΨ4P5;jŒΉQEE°†f!ηyWgΪSmOΌT{Ο₯φͺ`+ δ‡ί4wΒ³ύ‰υUBۜ)«Τ8Š₯7•κ]fCξ„[₯‘œ–« 'ξڐgι—³ϋΚb_’Ά% β+f—Ϊ:ρ“]h@ΚΥD•U°Ϊz³nονI”TΗϊ\Ή*?ΆN]]ΪαЌZ.¬΄J.„η2YebΔ_.€gα.·gΡEw‡/#m.Ϋj³cwmWY|Ύ €ςΡ~αΟ›‡π57B'ύ‘ŽΡ;SˆιΙ°θ’>κν+zO^Žρ©rΫκ–'„Šγ Ϋe£*Ι² +ΉΛˆdi½{1eΦέ›1σx:-$㘻υSμUσ¬Δ*ΓkέAj4\Βwσ™h1ϊq£ϊ*Dr½νͺ`eΪρdjλΆn}«Ϊ €žνk–iά» ν6»†ΌžκFΌΐqˆb}²λ…w‡DIέΣγΑ»ΈλΤ]άGƒυ9ΏξωA=3ξΛŒΕq½?!΄ „ί|X©|5pϊ8#i3—§‘§βt-K>i[œτ€O5Ηe?ξ#ΦωΚ11τ2Ο¨¦§‰Μξσœ^&Rς‘Υ]ωJωΐ•‚Ζ<Ν±>“!θHΧ観Zξΰφ„§οbP§ϊΈς%7ΰ³’% +Z“ιž(£œf–ͺϋΠ Μ·¦Ζϊ"K»'ΨΨP‡Ττ{ȍx~©ΦJЍ#!ΐx,ΈM+ΚΟJ¬vڊ”~؝μC%4bΆ΄)ŠσJ™Κ7ϊ,4TPd;υ(uœ’·GωΚ,|Ώ_ΩφΖ6ŸΙΔ¬’Α‰ζ¬z z81I'»<\―Σ<–2z_έ|’Π)―zΎkˆ•Έu­άοΌVOlόϊ-~aΌ*ύΟο‘W2¨6-«ΐ<_:Zόχ”ΦF‹€[ς!?ϋΫΛ£c–BέΛh»0ž·rΣΒ±ΗyA’aΌF +Ÿ‘Γ€³˜s!¬©ν3a3™°+o`mœ’eΘ¨Tjl|€‡¬ΰoν[8mΔO―₯Y/οVcZͺύΤ’}y΅.ώsy οnnRψD­ω&‹F‘ +ήοβ“ΦΗ"ϋ;ν4²i„~Ψ©ΙE©V‡Α˜AΦςςΛ₯9£ +Γε[Ϊ’§ο=°²œ*Κ (Κ0+—"+Ζ…eQBPδίσΌ§»§g$ΕqX°!©ΨQ«§ϋτΉΌ—!uY]όDΔ\”NΏaf6©¬ χΠͺ]ΞIt Ξ’Μb$` ­'šβ8@yΙT•q@~Ϊ¦“BV—E'wΨ}F₯bΠΛd&ΥΙD²γρe “±²8FRjšE Ξ>Ÿj f­“$³‡+\¬“HΥβΑ:nOΈΩΛΩΘJ bΞS†ƒΌηʞδηκ’‹©L½);―u…YD›ώPzJΗ?±²“2»Υx>T¦(Κ(ρΖ{ρ”τΌ¬q·)…Αή„˜F²Ήͺ½`£ΚηSΤιτV·‘ϋ2Ά%z―5ZΑr1t€’“ΈpjΝΜ8Ι°"UΩj4νί…k––²7q+Hˆ%ζ£Ξ”¬a‡VΊΉJ=­kδ΄Χέy6CŸgsΨd σ>sόΊ†|šηxšPzή7VZyF\ΫΎ-v!*{zτΔ™‡¦ΖΨWΝ ­moŽ«±―©r‘ΚόY’Κv˜^~΄ Ts]hυsΆ•~ίΥPΆ*_—R;5‰F*k€"ηΩ<-ϋŽυφΡ\Σά7bŸg=|γΥ1 ΛΎδRCΕƒnIΔΫ_Rΰ#Ρς©Ι‰φAυ’l9±WɝšfqιfΤ…?d·­7Κ«#TD\j+[+(·xΦ(δ*‘QBΧ[V6[‚>6±Ίu³dΟjί΅ώZΡ™Ξ€¨w –Sf£’Dk‰ Ψ%sSοψyΜŒΓeۍ˜Ά!aQάΏmμHΑꑦ9όϋlNYŸ@2*3η ·6`•žΰ5ΊaF±ο𠆣ΉγεCTU—^|gTΣώ½ψ+?ΈΣ³o?ΎΌΉ~ϊpwωώγυηϋS Οϋ޳ΏΟά)¬C›ΣΪ!2₯1Κʞ.Δ[β‘Anΐ™½ΠX%ρ±žqq³ήΕύβ[ώxρςξώ›Wχo?_ήύpϊ•–φ?Ÿ^όξφώΝυΥνέ{βό₯…ύΕ°^ΌΉΎότΫΛϋ»fσιΩ«—―Σ_χΗοoοnΪ―žΫQΏ>={ωώφ»λw/_―οδχ?|Ί~7―ο»ψΟ’v§·οŸΜ€ͺLŽ?-m=ωτμωινŸΥΌAU\U0Ή‹K@ό°fζί Υρ)‹Β)»JUi +œd^θ9/_γf.ρ€`ΗgZtρΑVhΧ4@̜„Uqλ†- YδΝμ¬ΉΑgXͺdΥ5‚7Ϋθt:’³vˆ(R= +ƒm>ˆ$€³ΊΪυ|ŒjΨ•x:'ψˆ½B¬κK8INkΰ• λν„œN#λαΝEούKˆ©”…VаΧCC˜K₯'7η!½ˆΏν_ϊώx"©Έ¨4Œ‹h›¬ο°UKp=4Y± ‡ξ 'y“½ΛΫ–ρΖν(ΧΔβƒ+—.s{`hœΈšV‘ӐΘ1w|!ͺΗΡLΫJ΅ΨG²ΐcΧ·O*ν4—3ω ―‘i/°w-υhZw› ~Ž=ΝƒΉΟΆ eFΣξ[`&2B:Νΰ₯4’Λ»7"V’Ψ¨}9r!!Β@ŸfΆΨg¬5³Κι¦[V-ϋH’ΊJ‘o’—vFWZ-1κ1 +>χ¦˜gζ9ή9ZlΖ6ΪpΖ?ΊυπΞ­«·|lΏεl›}nη$μf­ΧιαDΎκ`φ#Ϊȝ^\άή~d^ώςώώϊξσ»ΧxχζϊοΧ—χΧο8€Y7 ψ§rX0ƒf–˜W›ͺQ§KL ‘Q0L +@lžΊ’²KξjR«Υ4'VΔ„¬ΈΞ~ν~ΡG10Ί€­dφτŠ©+Ÿ²ŠHΡβ'Οωš€d:΄ΤΊ^+ΛyLz}$yU…Θ6$1:’I££˜d‚ j©-XaΪwΈ gSfΒC- ΅ƒ\±֞™/₯Ψ?LΉι­b₯8_ρT9Œ„8υ…€!@\ŠŠ}Η±&”Ρ(›‘οrF9Ή(οΊ(^_Ρ3ΫJ»‘ +u39 ‘ΆΫ>D±λ¬Πc₯puΖΪΫΪ!Gbԝrζ†ΘBˆΊ>ͺWιΪ“πΰΗT(…~ aΏV‚~Ν°7Κ)Σg C‚K ―e‘Z(QM°[I,~ν$Q’^)κυ£·Μ=q„817δς)F‰W”—©ΖΩ―qBd2Ÿ €tΥ +Ω)δλβηPτnT°AιΖ qςγ ςUεΌ€Άιά›HρΰlC˜(ΠΙ†R ε κέ +\ L€vΘ\ !F”-ρΰνjψ™Ι>‹ΠG β$v$ΰ†ξΨIΘ~V wΌ+ E7Pϊp‘α0<«˜]h²Ω#,ετ'ΫΈΩ€Rœ\Y}_zhώμ6šΊΩ^†1“ 6ρβώ$Gaφ +*σH4ׁI΄w Ξ(’Y΅±³‚Τ,Hg 7 O( žw΄•ΙΌS”]‚‡ͺ[g90ˆηz+ ,λιΛΈSuPwϋκy‘φ'`†Μ†Š&ξG§υ·; PΈPπΨZa–¦ό=)ˏΪμ–ΪΣ£˜έJ•π. ΅Γ ΠjΒa“¦JΖβš–rRƒZ*iX*V`dά˜/sδʊ(ˆnE6tΤ<ΨΝ&°1Xϋηdήͺ§―nDCžTΥt+oΎZ8Xhbj]Ž:CάΨB₯%Γ'T ˆO—?κφέS^αΦnρTχϊzδ ‡Lœ€jYsβ²’‘ŸΑ},Ω-.‰”ϊ₯ƒB‰nμ%§•2p–ς2²»Pێ)HDήΗ|daMUT(“x‰2T›FϊΧvΛhυl Ž}> SΙDX\v“έΆ^²΄†&f­*­ι£Ϊ/L@^Z ‰ύv +‘’=H §Am·?‚4&‘ͺ-?ΦΪQ dσKQhψz•A‹o₯Υi—α–^•q'“-j•.%ΕPSΰΔ’z’9DΘΠ f£UQ64R<švδ³ ΛC‘{ži₯΅NΣIJ4οξ½;©„7vt Β‡“»Z3€ύHΉΆn–5ˆ‹­’Υ x{ΰ6 u›2ΪΫ|¬ψ*ής~ά}[ΆmUοΎ,ΠΏ¦ˆΑp]yJξPΜϋ˜!v<ÜWμT‘1ε΅(Ξΐυλσ?'OΚPήιŒρϊe]?θΩσάΌ΅IuΘCΆςTΡ₯…ςΖV­6}!jζnβ2μŊ§‚΄&gAΆ¨Χˆ`tϊςΚ=)3ΠJžΜ½f‘‘SS• *›9­m‚@Eΰΐπ@9v Pˆ(«ž’ηΒ#.xεM¦aις,m푨Tl†›•Ca™œk}7ΰ²M₯dhΙ19«Κ“Ό^g_ŒcΨ²€ΥδΰhD Σα*Α·ς ΄ψˆ>₯§ΏtE`Y1U΄Dή $΄—!‡7ΒΨϊΎυŠ}πδ*z–­–HgΘ‚h“ όΊίύjβ7σ1εxΜ)Χhj?Υ)'Δ„­ξΛnmΥοΒCUe/οpJΌβήΑΨ_Χ,ςπΉ©°<p˜t6i‘4Οοΰ³ ξόϋο‡=ξΰsΔd6½GUσ€\ΎΉΟάskΑ5­—ΊT™|νϊ^;€(½υ%zΰ>cωπJΜj<ζϋ[κWšοylɘόΙYνΑ ·:ΐBς‹ε}[!tφ’ζΜύ@X!,‘4―8‡ω]κΫ K]ΦδΆKζ1,‘šJ»«¦·σΠΘ-*Ώ­iu‚Φ^Ϋwh²§ΗaΡZ +0,!ϊ–0\&ή2M—hf2™½’ΰv1.c)>•ψί”fβf“Wήγ›!ηrΈQz£π+$μ?œ―xXš; 2φU +0#rνe endstream endobj 24 0 obj <>stream +H‰lW;’(· <ο0‰Σ)ΰŒ₯K(r°UV€Tηw7@rfžHo—Λ!F£Ρτ2ϋυΫΏώύΉMj»jΏ»φKJ½ΝŠ\uή¦M―Ώ±iϊ-nύjrχ9μšύV©~5¬Χήψ»qu½k«’ηΉ₯Οy5,J»Dϊ]l +ο³Ϊ%Š?hσ« ώPuEcχ(ψ¨ήΣ₯"šr—QμjσVΗΏg‘Ο»ˆχλ'ξZ‹^ξY:Ο.·Nχkψ=[,4d'rωΌΗpΛΛAούγ6$E$ŠΆϊΟίqͺšηU{±ϋ]M€—θ= Ψτ~6 νn½΄³Ÿ©©ϊ,Š΅ΫΊέʐq`›H°%Άa½΅au*B˜<<ͺγ8§λˆν+Ξ1δΎP$ζE/wΰ’ ι~́Ο*£žwσΑλμ¬s}ΧΏ Τ>i™μ,ωΩDlc“χ*#ΥΜcΤ.ριΠάZŸέsπFό Œ8ΎfεΕω>=ΞΗίΫ5p?²tΉλTeΡλDDpXž[qžV=S4TΨτ­7ώt>·`§B.“*;$7:Ž:kΏ[¬pWζΆά³ƒ2'i~Uϊ ^f-©»ρ3Π]ΐΖW*ε³Ϋ)ŒΕύY$a•GCφN%‘#ήQͺ§ΰQš$/0έMˆ;?lvœ,YΎMAiιR94–:Πp¨Εβ1”eϋY0<Š‡Ϊ¦@°ω 8μ†|=¨Ολ+6Z­‹“wΣ "³RΗnLP₯nͺDλJγΑμΆΥΪ‚ΐ9dvϋ :—%„[#’[Œ"΄DD8τΨy[iSΡzK,ΆqΡ9N·^ 5”Ϊš&T»Έlλ³8iλ’€$ήV$:ζ~k,kίjŸΧ`M ψ~t•έ5[g‹y5κΆεžΧU ŠΧXXΣ(‡ΠAαόpΝ’ݝŒίcˆ!X7;σ$¦•ΔΙθΟ@DΘ‘>cϐ +ˆοφp$³¦N€Ώη§p£g̞•Ÿ5ωbΏφ­ 3C#φ^maxŸΑ.>―m^ ΛDίs΄ώΐC―ABΩηγQ¬pΜ„Χ + +2ΜκΧΆ8μ[φ ›ΒδΆI"G‡ψ²ΫK±EJΔΉ-˜!ΨeΙΨU]0`Ω6αœ)˜;ΫΧΑH₯lχ‡«p±ƒMxD™”%Ζ9’εΌnVHΞ±ς†!舁›pήτΏ£Ίιέ ™ΰ’JŒ΅^I(PΈwŸι)‘’ZV}ΆςΠ‘AΙoΞ>ξ2ΧοR!x¨~ρΏ—±Ό%2T (LBAισPQ„€& #Κ«PQΚθo[½Ή§¦MκDΌULο™“MˆˆΆΪ2+ΐ›‹#šŸΓI)›Š>±2ΆΠ Αΰ‰­Σ¬AB%1’x|kdfb¨„―Β“Ί-£β7cŽŸV)₯‰ΉΙρœζΊ\)x¬d Ε’-3u8αi―b%\ΗκkόΒ3ΈΧΖ$N­GiΛf=+AύƒΌ­&[i3€ <ωep2{\ .RƒT‘Ο]·΅Ϋ‹gΜΦNκA΄βK¦Δ–bμ1›#εB³rDJΈ@{λΑεά~VP@Ώ‚=«;ζνMΈ‚Α6§†–6xΗ L‚‚/GˆKφ…Œρ:λ¬|ξ<«'6hM §'ώxQ―ΏδIΝϊΐβXιoΔ0Ϋ’…ΎΘ’/βΓ§θž\9U‚RζϊT“_VΊ‚Su$ŽΦ²3θœB>B +-rρ Ϋ‘,>Csތε/£ωΓlv&P³‡όΜn…j6lkޟN’˜ΑδθΣlΜ£σΉτιIJΥ«w£°ΩέύΌ-Λ³Ί­ ,αǎύAΪ9@>6©σ1BόΟ>hΓ0f{,μr>Š>Φ 9{£Μl‡ ²rΗγ‘„‰τΗ zΙ7Ιγ Πμνε+aš[x°·ύœγ1©JΫi#;ט}Ϋ]εΣ3(»]1½9βΗ7“·9₯ήφZΩ6œg•H§Qgo¦|Ϋy…}Ξ –ιWŒȜ<ΟΨtdσz=@…  τšϋ‘‘F{ό^ο<;wm_ΟRκbηQC΅_=)ο§%2Ωy>©ΰρΩύΒ’κ”νϋSΨξDl?Χψe‹}λAGYœ•G½Ÿ}ΟΆύ8 qΧσ‘fYή―Lf؍¦x?FCcFμχ*;Α&'ΡϋYΛΙ:”S ―ο”1s§J²1Δ6@ π8\¨[XK¦τ‰ι’ ‚β”cΰNtΓ°­1Η 7τ”܏1Ό[ŠkΜ FΫJχ£ή“πCN~₯χ"ηŒZŒœ(‰ŒC;5ž·ŽˆCσ…Sƒsшœ«l{g<†ΑυœSζAϋ:”―qŠόΌƒΒμ›NNμ(‚BΓωŸшtnXΒ{°p[₯= ²Wώu€Ÿ-Τ‡–ΊΌϊl•qςψ0»BMaΐ‡μTLΊΈπΏψ―\ΏΗ\lΧΆ±ϊ+r.kΦα•Σ VΑ‹θΙ~N&¬Ρι{%β‚ ΄F―vφUΎœΨΖ|15>B΄΄~ΛσlΓx–~2β+΄β‡ηΚ½’Ϊ‘}'|ό€σκ―Y>PόΞΡ°4œ‡}ΡΓi··Σ…όŽωόNρ1Xώ± Ύc,΅Ξ9+뺟ε^sUπJkραΞ½Ϋ’ϋ<[iΣ–Μs _Y}+τη¨_όωδ?ˆτ¬`4ΑξΚƒ!G½ΆώΐάωnεxW£hNš½ΨθsˆΓ+r@ƒϋ§Ο¦Ε‘sΜ¦ΡΉμΝΆ^πξ8Cͺ’O;90ΞβJuεX°ωlΧ:Β«--NτPg`"Ζ, _Α#›xαtxδQ§χdl#-==]όρ’jΡ…Ρͺ+μCάj―K/£μζ₯x³M£ŒaaΦ[ckR€ €Δμι<†±]‡Ξ€“#όίΩΚPHλ‡ZΚV|n-qAΥ ½6MΤ―cVsœέΆšhE5ϊlΕ=»ρμu«iWVc―D-8Kθ"Š•ψE&«4“sΞ*Έ¨iUzΡΧBΓ +β 5‹Ί>cΜOhU(½ώΑt¦{ύwtΊψξΓ‡›χw·§77·oο><žjx2ΆpΎύΙhžΨRͺω”BΤ8Ÿί (RυΤ?šΉΔή`p#&Ή${X:]Ώ_jw}ΝΎϊŽ?ž>{xόζνλΗ·χn~<ύVKΗΏŸžώρώρΕέλϋ‡[βό…ύ³a=}qwσξ7oΓζΣΕΧϞ~ΌξΟ»xίυĎϊέιβΩνύw―ž=o―δΫΗίέ½Ϊ―»ψΟ’v§—·_Μ€U™œZΪFςιβΙιε_ϋW*[ώaUfκ+%œK£ήkMη„-)C­έknE˜Hx€h,ΉυNθ(…hP}γ»Άm][JλT—-0€‘δΕΔ²΄Ζ'=˜sSΡQr“-\ΉΙ:iΚP™BΣζ˜7ό“,BΞ&ΔΧtί’((iΞ²ςΤΠ·…ωΒuTdά υΣ+ΓΠ£|ΡέϊŒ\Γ^°P”Ν—uΩσ―qE:€Š‰3φδSΚ8P|Ξ‡ΤψΤΨ(ΜB97ω‡M¨˜Σ―ƒhr¦ΜzΈPΆ1MΒX„NΈ=xΑD›Tηο:2Ωs!ˆMVΆœ€‘(VV9έ¨pf΅g“[ΆaaVhӐvT”»ΑRΛφ‚οϋ&(φ³&xΞο[‘-πVϞ9!½²±PΏ2ΆΊγ,³«‹Vφ>›Uϊ΄§s s§§Χχχοΰ—ηΊy|Ό{ψπκωΏ^½ΈϋηέΝγέ­ΈζŒ[Ϊ’?Ϋ@BN }Qσ'` ρEΣ«)ΛΛkΒ,Θšΐ +NhΣPΫ6žL’mœa ˆ¨/d(ύTΑΙ†$ι¨VΡμΤ„.2γMG{@­\IB|ΖΡ€΄TΪUΨ \ΟΥIBIK^ωμ8<·γš€ϋŠΜ~ˆͺ l ΡαΉ_©άΦq%ϋF‘Υυ₯λNCΩ°M¦€Žπ©JΉΧ―7Ύ”ήdΰ€•β€§ΒU6œvρ !WΖ—~>>φΑ!΅ΛΊ—š—Ή½6Œz.ίόŒ– [P΄/qURΛl[ν/Wν>―ζΧSDΎ€ΐ!=9]B4>δƒ&aΘ-5jr9%„†σͺ>†ηΫά'ω‚Oανx?]κωžΖoX)±χDΑ_αΰκ—οΗΗ>n€*m‰yΆ‘έŸώ>Ÿ.cΙ€cƒ‰OΧ―·Σ†ΥάΗvy#Kδ^ο—λ{¬VΫΧ~χΑ\|‘'/.ƒˆϊ}rzϊ-.ΰΓ›ΣΕυυ³Χ―?Ύqx£½½+ΑόBώOδK¦γηΐσ«LˆfΫtεΝ†$č 7‹aνT„δf‡[Ηͺ™ˆJ‹76BμφΊΠΨ-Œ4H«H"²ΜA.sΆΐin’=ΣŠ―΅›ιήχ€yΛΩ‘σ:•δ,%.©©θ ΥθΦ^ζ3ƒ@υxM:Ρ.p­υ\›z‰&!S©)ƒ(s³]Us„Ψ!0λυγ51X!“Κ Uΰ2μ‘q€‘Υ―5)ΒΝ£aΗΎGsθ¬Hj6‘.hˆU‘^,ΰABŽ_e]SYeΩο,φ’ΣΣͺο4ϋ 5ΜlΞʞhY Hωb 6*3d±ηS”bΕ½%sVmO(Ή›nQΌ9<kΥAψ"‰ΡΎRα7D±GΉVg©I7€kόδtϊ5ΨΤdίοt]Θc*‰Ϋςρ¬΅rΌs_±%pυΖ=~ZvυΣwNh|ΘECΞH‡3`€σΌByγΓ™ώD ”ι¨.RΗ2Ϊ—›ΧQn=ά›Ή€FψgΠΡ fœ8!Ζ$;Ÿ9aˆZUŠ~ΧΡΐ*n‡΅yΞσ-θΛgΉ˜>iYL“’ΥH΄#‘•ΥkIEΔ]Ÿ·€’|―Φ•³OŒΥή:|3z:@"/^½<ΩwOβCήI§ΞωζI’ίRΪΞPP4ΜμςΞb:Ό Κηl—θ’bάYQcΐ†ŸΨ™SsΣ;ΞVϋΜυ."NrΞΤ`΅žj-瀞$7ŽHω§ιr–@Θ4G™π₯#=΅₯[σ₯7 ›ΣŠy΄‘KΆ"Mύ_ΒAMch²Rυ(J>—BΥΈ: ώ”L]2ήΚ.«Β;dΟε7i„‰-/•NrVΖ‹SΘχ•ς]-½yέFτ―|K§hl>†C²]Y PdΡ.fΡ•αȊk ²Ašίs†’WΓqDρ’ΓyœΗIψ{Υ…E7ZuρΐrρΤ $“6–a‚τχU³]ό°‘1Qi $Ύ:…˜BŠΦVΞ}ν΅Dd^Α<œβ j-‘‡Θ£‘•sˆΒΞLŸj‘± ΒV•LOCΈ[yš#R]*ί€δφ-dιυμαu‘zΠΡ½_51X]ΣκΦΞά"σοϊš7˜κΤαe +Wλu¨Biε‚Ύ$–”Jύώ%…yΎ„ςxΆ{šΛΥj†4€š·#}ΐMΜ…»’ˆ}pzG©ΌΡ-‘ Χ·MPYλΥNahp.˜ΫBΫVšΊ1#ψhΟr΅oΪ|dlΊ<~;΅’Α‚XL] γή7l%‘'&͍'_˜Lώ”/ŒύncΑυ³Ψnu9Ω: νYσ>δ4“½*Ι£ΞH +Ό+Ι—ˆΫcBC@ [N… +ƒxΐΕ$˜D‡›uŠrC &_ΖVE΅x€ϋξeε@ƒ~~Ι&7x μb+9ι‚α–k·/-Χ%°Κ˜{&QζW»“ΡDμ^θ›j‘ —eΌŒτ RwBίΤjΔΆφ56)ΚΌΟΒσZirά‰‡H¬³Ε<4Š‘ΦΐSΪΡΥ1φς§v + ΄ΩNG'lΔ3mΌ’œ1ζι₯"Ό­Xi ΏWŠ+Β>ΈχžbE…Z΅bWΥ!°~t’ΝT +ή£‹dΆΗΟFCζπ/)tΆ#ΜΞ.uΚΊ}±1%2ϋšͺν/~ƒΊKϋ e°6†:'Iˆ*ιœ8σYͺύK›KΙdK] ½ε#.Œ9ΉΖs(Θ 8«‚»[7%2Q…A %g^}`ž"όάB(Π9§wᘠ½Eϊ|€ΓW;IΦQ₯±Θ)†Έv‘#,Ÿ„ΈπΘ‰ΑLbȘι ΥθOƒι'€³/ͺœΓΎDͺttη’!=P z,bυ&‚/’ˆQπv:Τ„Ϋ $'.6Ί}ιδΖτ7ΥƒԈ3ύ$KA‘c5š„*Θ8ŽΡƒx‘αšόN'h{T4jŸ$.Το]dΏVξ'ρš(Ψϋ¦n`6ŠqύZPδRτ’.„rPsΉ-™²W\Ξ`TW”.{ΨθCΊ4ˆΧ΄P?An[IHπ, m†Ϊ)q%ο}( ΫqΦ^9οά«+6άΠ ŒŽψ;Α™Ι?_‰Ϊ™@­a^ΧL6τ;Sͺt$^ΙδGixΚ τ³„φΤΟQκNϋn₯|IlΑkΫ`ξnΞφRšWΫη-¨υΚvηΡͺ +ΓΛni>ΤNrσΆW*ΘΓ‘Δ¨ήΣ"Νt½6ΗcΞ 9ϊΉžΓhA`"χΘBN δcΝ£!Ÿσuό΅zS:L($"WίH’±jδŠ8 +;ΰά‘IΑœ]Q—…^ͺ€ψή―(§˜ €#m4„{mΘ䀩&–yε†UnŠ‘pπU +j€?š rιr%Η4ΦΡ_]ά8'(4^ͺCη» -F₯Ξ1θd§΄ΝEΏΪ^]œ₯δΚ©ΙjB₯<ɏΕbυM’šPεΘr;‘²Ι³΅άIΈJ©€GnbVΚ)ΓB'ο½r’ό^u1 ΰ9θΕ­π.ԏAœΊ‚c +ΧLOΝδψ―WN)s¬NΕΓπΑ£y‹"… τqŠ'EŒheΦ>θ΄\bœΙa¬NΑx}¨:?κζΚOQˆ‘ιS!rΊ²%Γ•$³SpγV› +‰Lz₯,\sSΌ*Υ”½Ϊ.QAͺθU+z«XΟΊ^†vΔR’»¦fΫ9]΄·.iγ]Ρφ8OΆŒWNžiΔSξsrΖ n ΠxxήΞA’Ζ §ΏPš5Μ―»XIHήΊm +wφ°3έn˜βΆG+Š‰Ωi@Άv΅PΙAan΅ΰπ€TDӍv0{ρjΪ„4θάΝ?LΆΟ  HΐΑύjχΎι'ύ@\·œDΔδΉZS’j,ΫΑ2ω`+έ&—ΐΠ¨Xα3tv]aZi­@ΐA„Q™‰tKΌ™dυ:3KQQΛh•Φ %΄·ΧΈn­˜΄ŽlΨ–βDP0}M θ +mšuΊt’VœΑh•­9ή‰aνψ₯%\ωkœnοDz(v’NHγΪbτi­Z]WFFπ–‘Ρ€™΅ φ>SiDϊuICz\‰VAΌΡ<4%Ε‡~<‘R['5S°ŸŠκ•Μ±ΑΜsζvΪp'pry₯W99Κ/g r‘ ώ^*κζΘ͊ζΒ ( VΥ+pGw ؊rΪ•?γoΈ}gύh'Aέtde%νΡVλ >&­EN +‰Ωΐ& ρ7œz_°Έ0ψΘuά»‹0¨rRΎ–hoBΔ΅ΗΫ>Š­+Ϋ|eTΘƒZϋvΰ}δωλξ4|4]†Œ7³I¨LE>^W1Ν&Έ]μ"ΡVδcEm‹ιζίΩ†ίΖ©ΉΖIka^xο"ΦVQΏͺ±σ»‰5@A0Σ»ΗγZ¨ΊDΎ˜yΪϋ<ψ}ΦΛGή_“€iΌ4νjnZwarΡ`šviWKŒυZ“˜o θZήcXZΩυE?ΏΠfךϋF³μ£f7νžΫωϋlήΛσ^–ζ=‘ )/‹JλΘ|κ:ΠμXιΆεRύcΫLό>Ιζ}χg·%4~„γΉ­’B¬ vίgγU`6Ι±o=hυβ‰^{*Ζή1ϋ© ΈVMΒΥΚBgψpT™bE6΄±9UUcίΠE€_“Sΰ"³bPL•τw₯|η$ΆŠΏC™…PΊ­XSΣU™!}0βό^+&@΅΄G¦₯C*―ϋuτΤόΞ3οT·³ 1VψΖPŒ™d4)¬˜43Θ³€Αsš€ΕkNΜί±9¨ηQ^^άΨt8 ο7ιΌ#‡p +#¦γ…\-%‰(,Ÿ?sΕ£ !γ5§όθΞ}!Ślυϊ”JΖ ύZΗB)[ε.˜x₯ψ_±b8/km7X)μ==ΖΆΐnE»ΡWlrΎή·Ξ’*lρxv-γπq₯Gf )»ƒ«dŽκzγΙΩ;J#RδΆσEΫΫ›yυ*ο_Ξ8T†¨ίR*^HΪUIςz¬iΖΊ*NK‹wέvO@θR·Ξ00ΘߍώΚΠεar[-˜i* αjUhhθ U¬₯}³ν³|<.W!šν9σΨ}/OάSλ©Ψ“νιΪpfu#…g£‰Wh£ΞE}-pšίψε=±ƒ8{gγ‘·Ψο`ζWΨΚv½ϋɈgόΉϋ/~ˆ·W?~ώόώραΓνγΣϋŸ>?ίZώfn‰ψΓΏ₯²NH)ϊν•–W`·πλ;²X„}¦έ>ςΎ…κDm: ή‘iΏ{\όww‡ώτ#ώσζνΣσwŸξŸ?}ωόώι·Ϋ_Έtώ|{σΟ/Ο?<άyϊ€@lqa\o~xxΛ?ή??}ϊ6ί^ύννχŸΟϋχΟ_žΗ―Ύ±£ώz{υφΓ—Ÿή½ύΎΏC zών—‡wϋϊs_&=vWώ+w‰·©αΤ΄L{Ε."R⬐e¬,%ζηyOΥ7άξΆ} RB‚΄Ύ[Γ©3ΌΓZΕΏuΌΌωώΕT¦»RΉύΧσΆrP/_½ΊΌω‹‹‰Θ`Uo|χ1.‚t½„‰Α€ƒ^£ aR5en”ΧΏδΑΛΘn7 S AΟIr>2°ΕΕΠθ8n*ΡτEHτ%Μ5―—Œκjyƒγ„^F™rƒ₯Ζ°‘6ήoϊΩ)£@•5ΈΦ$·lYΤ¨ΚTvu|›_h’΅1>Dˆτn—;ΖSLcUƒ‚3?x­²ν΄ά’ι£5ΰšͺΠΒB`4έΓ °њf^šž΄šhKϋΒπ/š\d²ΚρeήH8ΐe⭚ycΚFSrŠ,šI#Χ)π±ΏΨlCγΉR”ͺBΧωΣl>k€Χ[―½4©ΉΌΊ\α0•πi+Π‹ΣϊE°i€έΪν4&•\[β–Υ†Χ„5ΐGӌ·(Ψ +zCAT0k’#Ι%ΛεSŒxΎΌQo^sU—€QtO"?O·―Ζ“’N) ιA#ΤΛ£Ίσ“3y'θ†izιZiisAEtrχxΧ΅l₯**_[5Ρζ­'μω΅°σ±tώ‡Pη% ΉΏ>?όφΫhΡN4ρΑιϊFŸ‡φϋωЉα‘T‰ͺTSsrFνG·\©‡ƒœkK|1Ε—λ5¨›άL”žbτ}–ηF3Έφ†πFθξ)&­σeω\Γξ…θ‡7δΔΪΙΊZΤ’ςt,Λb€άε›κI€o¨έΕrωΌ,loάbr«•§χ…%•φΈPF6\γn±ƒ¨ΈψδBšκRuΛ…Π’—t€‹X’ηο&«œ’i8e_?g³£B΄ΐb³ =­΄θβΛqzMω|ghΉy‚št _Žπ +"žž‰Ό!QΫ3·tH»δ”.GΚP=QλŽΤ…Λύ=΅^ ­6βε(R…%χχZ2œ©…­{Ά’οΛΆ¦8NښηφƭɎȢF<’ίφζ•{cοΩΨ{ΟΨ1#§Μ³΄Uΰ4o«JΟΖςυ‚΅H©xωϊρηŸΰ|σΗ·>ΌηOί}σ―οΎ}χo?Ό^ΰs6c‡ƒ_ψ†f 5”θσŸcγ‘ΜIΧCV-Μ|FƒΦjΞ»ώ6Αχ€BΗ…"Ϊ1εSΌͺG’΅, Ή`WφΉΔ7ΦΜ•ˆ›˜ Βx$ο£χμbsδ< s ‰ζΈ²0œώ"FUŽ₯­(šŒ”jδ_Š0sέΙ:ήΔ ΙΦ#y:Β$m鄍Nofq=3Yΰ+eεΟC‘ΔT—δ'SE..&xΎυ)μ₯ζΦλρ1OeΜ†Ÿ55ΖχtΊ…Ÿι±Υp™Ÿι„4ΧU–Aˆ£‡β‘yνn +y—ͺHdF’~ˆ`ΉA[^7)₯:’ΟδΌΚκmΔ‚%βŸxQΊ’MΩϊ…/MzY·—ΈΰΔ%…–$cx°€`aΔ€}bΏ|†@€λˆN§ψύzŒRG>~ζvΤO–zόςΫ}?U‹•ΆΉC^|u‘,T―._ 5πΣί._=>ώαέ»_~όφηo΅vγg„Θgκπ;P"/uΟ=Δς67¨cI΅ΰΐu†€!’ο@Pά*šηud|lwΤ”Ρƒγ;όHϊ[Ψ˜c 1؁Γ[…P؁Ο:άπvœ Ι;0+bΆ`‡Σmήy†­ ΄ί;bΉbν;rbΞ°3ϊΪΉΓ_π#g؁ΡRT\gΨ±ΆΧ/Ψ14~ 'Τ1|JWΤ7¨cΦN u Τaν@ηΥόΥΠshοΘ36νύyΟ η₯Ω }Zή'Θγ›cuω#“vΙ΅μ―ΰω5G™UΌE_–ζ)τ|ϊφδω’Ϋηώς|Ί ΛΐƒV°€IH΄Κ¨Lΰ)0π(~bΐ3½mpMNδ…CqπϊR5& Jˆqη;5t‰όg—¨΄jHέ6ΔŌ2ΆΒžςό…f>W’―!‹³¨@$V’:OΜtkPΉŸU“ΎΘφM/`Ρ§I’τlBdZEˆζDtΥR \Bu,iuΫ†τ*½Ε™†²μ:ΥAz 39ΘϊammSN„'J…ƒ”]šYικΚ’!Κ}CxΖΧNy=¨Kώ30Ϊ9H'ιo 6R΄νΓάGF +ψ“š˜_mCΜ„š ƒα ΅σ*p›€΄‘ +Α4o§G¨εςΌώwAJœŒ'ΠΉ_„ΎΡη‹£―NDι$]²Ζf%o(˜“Νƒ:‘]AΡZpkΧΒΓƒLK"yΰκL’>tΘ*Iiφ%€&ΞΏpB4†j›@‘τXb)ςuΠ!WΨ{l/_E9Y·|ηŠΗ/ΊΦΛς^E'θBΣ uŸϊh‚`τρξΈ’˜Ω2=€Λ"yΩCΉ:…Α0Ή%Γ"aT¦˜‰τβ”)PzυSGρ‘NΫfhjξd£ni?Λ„ξ ₯QΥZΫΞΒ΄€<±¬KΠpC”ήρ‹]ώμλj]Εμ ŸΎα―«RολΰλξύλXκ₯J„ŒαOΘς@pΛ3ΑύΜ―ΒN²1ϊ&W`iθ˜:όπ‰uyE&( mnΜ>%eΈζλμ‘κ‘Ut’’n5ε©ιΆ±Ή—=-[~1W€,{yθΥΒ$₯δbnΫ«‘­E―!1Qlͺχ ―J$™^ΜžυnΩΕέM"Ξτ:6&^bMώ‹M·˜“θΒΚξPMΨ)Ϋ8ζ6ν’μe +:!ΆYD7―₯NNs>Hΰlptœ§t‘Dΐ<«βλS}³‰KΟ +^¨Ε©e1,8=EζΞ°ϊβgή0¬6@Šε`X?ΈΆƒ`1(‘gzMmύ +y£Χ»Εaω W?hˆNτ*†I`πN― Ϊ€¨ε Χ„…‹oθUQτ,βŸόšθ‘>R=ψU+fjΞόšˆΌxπ+­9­ΘΞ―ΙxZΠSΟόš +@“‰_΅sΦgΡiBAΤ’σ Ώ*~œ•όͺ}Uc²ρk’Π3Τ3Ώ¦Š kΉnόšq8›.~}^ύίΘ―;ϊc‘H¨KΡητzM’₯'Ώ2™—IOuΡSvMM[%%g£ΧeXŸΑΑΰ\ρiΏΈη'€Αά0€BB¦ΣDBši œ`Ε«ζC=^Ί$՟u'ΨΝάG€―cL…&ύv†ύLŠΦΐ?—ς7kΤ :ΈokΨY˜`•Y[‚5€;֐‚5ϊ1δ‚ε @m±[‚5΄―Ακ†j‘ή¬©ρ‚―[ϋό ΑJβΞ_Ξk`–γD°χγA°Χ4§Ε3˚ˆ€Š–6–5Œ •ƒd-Χέ™da9¬‡ ΕRwΔΤιχ[Šυ2 kΞλΔŝc•ަ2άp¬Ξ[_«2H4«‚;γ ΗZˆ+7;Ηβz»)ˆcΕž¬Žr‰’ ΗΠ\m«AΚκΜ±J>’9Λ¨Y>(φy7ŠUΧΜ:mΟχ +sœ=#θΤΠTŠEΈ-ŠδVλ|WέeήKΔPχ¦SΗΨ”‡"K Nƒ<Λ•Β½8χa·bhζ₯]―=UI˜"Έ$…z΄²Ίl“€Pέπ8 —ž4(rxm]Pͺεtσ+!ΰ¦rΞΛ *’ +‚Z[{ bT‡’v1)Κ/y/ΦάIKf΅›—―ψU$ΐ$uw―«Λ²c6VλράΙ°wΚΜ;Γԏ­" ΤB³?ΔζakΜpτaΦ:/j”- Τ4KχΌ˜w1¦P(ƒjδ£xeŒ|/W²[@–IApiώ ϊΛφ“³eΒC{4ιK>/ΊΤšΨ΄=χHS ύγ'hϊ¬& –Žψ7ωεŽDΛmΡ­(v ώΙΤφ&Ώΐ+°Φο> Ξ<Ιε*₯ΞnρΞ I ΡŸ­–χ “΅ώΧ4KOφχΏp€ύΝΖ”+ψˎŒΏšC5—Ώ.lί'‡&Mb£τ‘C“&±–_9Λ£}s¨='Λss¨}kΒ3žC“¬εθi|shRD˜ BχšζΦ O •t)!τ_ώxƒ?€Π$)yζšί*•¬VΏ)Tcό«ό{·šjήΎό“ByΙΈ,R¨}ƒ14™1Θ?ΕΠ$ωχ­ˆ‘IŠškΚ7†JC7–ί1τυΨ‰‘©¨@O~ΕΠ”’–Ύ1”³φIͺςšŠJ΄ψ–ΗP±ΆXQ}rh’–δOΕ•υIβυ šΰζ“"‡Jδ4šcήšŒgžθδΠ„ή`ƒ>94α]²δ(rhŸXLωC]#φoΦe\¬h”ζT–J[Άι“₯h:qŒI²+‡-»Ÿ΅+©°J§ ’Π¬ξΤƒf •€²€ 8ρξ%»ΎŽΊ_Π½7h2ωΪwC΅ŸS7ΔY̗΄T¬G}¬-ά ρmΜtήΜې2AŽl&Ύ!Βm λ˜ͺŒlfyϊ9k=~)XΣ“·Ή@ ³„«›§jxάlGPcςΆ)JŠSWQGe‘ρJ+ؐyΞΊ…Y}Mη’ύ9-EΟ5:ͺ ί3£ ;Ψ΅λχ€{eo)*- ™@+ΊP³E)’‹œ­Šˆ +ς»^YΦP(>2-’· 2’Ί Ό› Α8ΒtQS«ύO}ψ0e‘ηζό&£m{²Ϊ±„$ΫBMΖa&7 *€ΏY·ΡΟuοH~ΪΩ.λΤkΊYUέΣΣXΥ}Ή>ΖPΦΧ`†αΆ‘3ͺŽšκqhTˆ0ΙγR5ρb9΄ψ.C§·™φ…νrΛip<Υφ­©Ε“k½+™λγJœΙ9«'j°γθΖο±ςl›φ-UxuΔQΊ’hΜ|dεΆθS?ΥυZΰ]ώuo«ΨG΅‘𐍦£f³²F' χšG85«5 +eΘτ}x)+φ<Μd›!2(oˆίσ3LP†.h‰ ΄N‚‚AΞϊ…0J―Œβ‚*?€Αœf);ω‚ΧL²~³ητ7b&Ϋr’%‡θ¦sx•’”Σ­iZ©λl ™ix[%Œ)RQlό,Ώ–΄Κa'$±#ααaΎΕEΗ³4APRU6]}― 7»bž!Τͺ€O&G;hŠ€žΖΞAM„ΎzΪ;,ΫS§H(!ߚύ(‘ Ω xƒŒ³ ~9QΑ|l›QΤ.{ϊˆ¦df£@2²£έΙόΜi¨3$1H[g‡ͺ9O…Έs‡g—Φή΄•e-‰θΤ +έ6νˆl—χ‹V Hr\ό‘Ψ_yΣ°AVπ`Nψ±·”½Β¬u™#$)]Hύ”η,ϊ)Έu3¬h‚°ψΝ–Πœs +ƒ&dχ1ΐ09@|)V>ΖjœKdΪ'οprj?έ°;:£š‘” <^-}Ό>X­OQ[v\{ρλΙ·AϊΦΦτO#8=Ύu7\h[ζmΒ2Ηb§ΛeqΖ4βϋ–CΉΘVΣOh‹ZΥζ:7…δc΄fζPο ‘{Ψ"βƒ$ˆΘ3¬z‡­ΰx,φJ;‡UΫ‡—ψ•Ρ‹p>>€/δ6†σEžς  !pNΙ€ϊOά“Εξ­αŒœ£²X‡ Λ"1vως]Φ€Θƒ€Λ‹˜ΚQkΊάΙ)Ζ`Ο7Ηςœψb].ΞθΩp§kŒωΣA‹Υ‹˜Ζhq‹mNζΒ]Hviλρζ!8ς½=5tιwΫ@#χ”εβUP›rPgψίGε+‚tΈ.‹₯c<€«Ύι£»₯`ΰΑTΘ3ΰ…]ΑοΒ[θcΥύ@‘βuζ",ƒ»]βΧZθ ΝΈD΅1ώϋ³•—‰Υ°=…Υ(—[£bοY_%»¨‘¦ϊρœΠ; 3ξΕ4”ƒαϊZΆ*9 ¦\k§ ¬mΧώεΉ+ύ±‰LΨ( Βq“Τ§B¦NΛaPΤטͺq9χώ2°μdΥp“ 7΄΅ϊΧ νί€l˜fβY_tΐ5¨3•ϊπβ6'Œz‘,:Χ5σ$ά™rϋx~ζΖ6ˆh ‚ΪχŽψυγ)ŒΘ;fΘ’«㦑,ω™ΒφΔΒπŠάΦ7Ω0Ό©q'@θ€ί΄™’πS°e›Ε—€tS_d2˜§0Zοθ¦Dstέ#/Nΰ1°ςζƒ>qρ>η±’³ œύFOHqBŸˆΚAζ“Ǎ²Tί:q—ix O€΄O«²&–ΐUgέΧάϊ“ΕΘh―Qξδ\ZyJ9u•½˜km¬hVτCό+‹ΖH8ˆšŒ6ˆ΅mμ΄βΦήωž0ίΛ‡όy¬#ΎβJ½@ά­j3nvίUέήlς€m‹ΖΕξ™ΔžΏΫ² ΑcϊΧ3σŒZh΄7π € D;kf;Δs|™c$Ύ% +°€qχ”ΘMξΰΜΟ&m-έγwhΝψ͞σ»vβ ƒυθ@gΎλƞzγΤΝλΫ;‰χ`δ o―4' $όpdYO;ΐ΅Ρwm₯o¦>ZΫͺΈρα8]‡έ>°&e2­Ή˜Τ`HVˆζΖξ$ @!oπΉΌζ@ΣέϋΩσ>§­ϊθpΙ™©»›ώ=zEΉΐΘΤ_΄ εΤbςKŒ0©uα5c₯’½›wAΝi™Ώƒ5ΤΔ‚ωvα c8‘9 ‘Δ]lη> ε\Ίί• kΤαdΐ[*}Ϋ¨ο[ΩΟ‹QϊUΞJ΄GŸΪbώi#oβΒn»uηΩπŸŽ”¬ϊŽn07o:ΎζάΟ+Ζ;ΥωAjΑHλΕseΈFοσ΅V?ύk6*Yhœ3B +„ϊBmwΘͺό•>PΏΓhη(΅ή‘­Res@1Ψ΅”}ΫχόΧκΐ<4Q„τ0ƒΞ$UΆM>ω'ΖQ‡Ÿ;3UMGΧN—½ψΈf‘HN†M™ΠΰαΒͺF? EsΎ¬f–gϋςjΥώ›8ύVΌ@Γ©8ESΐnτ¦ςŠΚτ§γWw6W€„]±νŠΗ.«Κ˜†ΘT)‘¨°]1ϊέ6›nW²@€ΣΈ²fGψΚν5η2Yε +yμJ©Xτ1Π½%·ŠEGύJsΕL™F»|ΗΚGζοjΨ”N¬π² ΈZKΎk‘‰hEXκc9χOVήvζ΅zl0nψwF\άχ1PΪΡΗ5P‘Ύ\zμ:ͺ(όWξΠFt»ήUFnGBΐ HF‘Σv‚•΄-YΟ·v=Ξ9ΎΖq B`«_u뱟k―Υ¨%l'c Ν.¬N€ρηλVwr— ϊ–ΉΙ’χ=Τ'šˆΘ%―9θ$žo‚m0Nš‘³#1•ασ•Δ΅ΝαΑl΄ΖηΟ8Ρζ^^/’LΘHžΡύA¦Uq6œŽœ;MV³¨yπ’'vϊžjΝWΟΨKp9Η΄K†@Φ P>(‹Δs©l».@ˆR—Dvτ€„ŒΊ»8CΫ!x„5•’¨γcΛgρ„_}‚-‘•ΞΧ²λ0Nl‘υZβϊ@Ÿ²,ΐV#qCϊ%fWΰ·³DάχM))Τ1ΐ]rSnvv”₯ςsΛ>μς5ν>ΛΟ)qΥΥ3‹BΝ"vkΛΠ±\L.}·ΦFO•>­z³ŽX°² ΖοGNZΞ½Z¬QRU%8aGΩλMΙKΧ‹5ρώ$>»MΜ΄θΦqί™BS•gf•F” +ΉτO&Λ£F’νθΊd^ΆBGDΪ.·­υgΛ°£‹+°Βc@‘'“ΥFΦΧ>MzOξwIΒUΊΏŘì΄iΟUј.$ +H3ηkΫ6=₯Ί­vφhp°DΝυ4^¬ή ―Ψͺϋ‚ζԊΏΘ°!Κ]β˜„£ιf>ƒ&„1‚™sJšƒιP1'zσ;Ύάε3ϋώ½ΥΚFY䳇ΡΤ6φš·Τ)ъD Ψb€f–Ή ˆωZs;μ+UܐΤΝ ni―ο₯iί·u’±ίEOxxsτ` ΣΦΆέzϊ„y{ΉφrΕχF“‰{ρΛΤF?œWΉ°ΣξAy‘%ζ‚x˜vά}ήE75Βλ’}eޟcΰ+Acׁf{,Rδ䌷?€&Σ’yΝπ‡}Uμ§…=ΕΒןσrΨ7JfΏk–Υώζ±όvΫΦΎέώQΙΧ~ΎW 3 ΠγN‘N«EBa‰π₯t|[ δΜvkα°k€αpΡ\™ή‹Ο«θ…V+Ι^BF#]²yλ2c”ΜΎmχh^uεγ¬…$β¨ωΖ°Δ8γZ5F—«rviLEr΄…ΰ]C ³bηΝΑή¦B[τ*η)ΠΘ² ΔγDΡA'ή@ΐj* žUšΐ‘•^δΜΤΞQΈk VwC) pΆ“YŒ„ΑΑ˜—d3Ϊ—ͺΏ(?ϊIθI―ΛΪΥ+£WθͺsΠ{-0Q7Udq½¦²3VΡo”QIR«ͺε-ο©R…¦κjΫ,ΞΏοJ]€μ1ϊXί{Ϊ1Ν³†―KOΣ f‡YΊΞεC$ŒKΨ3Z Γr{/ͺY-θβ!ϊΜρjδ!lb‰D3HκπΆ¬|Αψ«D>;€89ΡμΰoΓΆRŒ [ƒ=σΜ%•[§ +Aˆ‚ΨJΏsXλ6lΙΒT…‰’#šΝ΄ŸSΥͺ,7³―‹}Ε©9ž ΥΘ*sΒ‰)œ$λEήsCW™τs„t+}¦HJš> Œƒ¦Ξ=ϊ x-JΫξΆ¦κ½u;z^νdtΫbTΘ7pήΞ5©. yV[KΊL=ΕΛ‹ŠρπΞ‹Α« fs·9ΣΩgB'·¦Qκ89©QͺX+ύ€οGΦΡuœLc…[+C55¬š½ΣZFI Ν›ΕΗw’Κμr]Ο&‚~hώΔόψrγYžr!±n€">‘Ϊ„ϋE³zasP ͺ6θŠAaCΠN±’”μ)Ζ(.IE3 ŒaPž7 +γζ¨ΑP@)pζN•κω@1"ŠœφΑΗ=]–Θ―'ΉϊtόΔΧy‹Ο`X:/ ζ΅»©±ΖdωεƒυgBϋ_¬š₯?_Ÿ2aέ9©gD +τIΡ@]ˆDM3GΥ‘΄βˆς‘‚ cίB$ ­RκH&—ςŽΓ0kχ‚£ψ-ΕGvΟ¦ή=ΒQ€1£`v82ν%₯°ΰHΚΘeq#Ɋ (‰85θΣΒ#mι±9βQs3΄yΗ#mi ;‰Ÿ6‚sΖ£ΕͺÏt²V[θέ’ή#Ι…(0Yx€“%§Έγ‘[·φˆGθΫ$–±π(Π—Δ4μxt]!Ν’;Ρk8Ί1<*GE€ΔΊτ†<ΥΈΑψ>αqiptΓe,Δ¦ͺ"4όκΆruAΨ²Θhς‰U4j‡„ƒ#ΊΑQϊΐΫI,Qόd€Oέύ‚·Ή@Šc+~’Q¬Ώ}<°χ`)cόι‰z°…Ϊ)uT‹9Ϊ"Ba)ΤaVaƒ#T.vEkιΥΤKS"e?30ρ«“j±„ιw0ΡĊθ w—1>rΪ₯„"Jυυ–ή- η›1‘@`«†ΦέΏc€έ€2aώzόζ_ωS>TŸ‚&Ϊ.Κ–TΈL9·‘³,₯u¦•I:†Υ­«\γ­N–ζ’ Y@4”·pnt}šUϋŒUDI /κ/-*m₯=I3 B¬γ$₯9oϋΦ¬·V’m‹ΦqΰCv²*Σξΐ~0φ Œ™$¦.·ΦΣ8€΄uΔhr’ξ€­6 α$mΫΚprήΔ–DτN/ˆ-Λ +N +σ–ρE¬ΫL=ϊ(#ψΩc!"RŒ-ΝxqΥ?©/we]n" +ΏΚ UήθΊLˆΝ@R'p$6ΟΟ·ZjΝΜ±qΛϋθΧHκΫΊ Οΰ+―0G²ώΜΏ8[~”H?Ή<RŽξ}ώγγ>K‘8w׍ϊλil3λΒΣx>θ)ΐx„ib©fdƒ}Vύ‚ΘϋZσGDfMLΤ;³*@½>mŽϊqΧ ξΝ±K)Δ…ZvDΕΟΆΣη¨Σ=―O——F<―? ϋŠς4φΙΖiώ“±{H‰Q:ιΏ§-Jτ³©ό~Cڟωο:Γό“@"…x‡|ͺ&Xώ` EŒW$θθ¦”n]Ad²Τυ…ΔoΆ:-KήΣ n™  + 6[ξ~Φ5)†VΊΫ +);νρ/™ηϊ%&°@Μ•Γ₯δvCΓ³"―‘ͺ«Šχ•Άδ…φa[³9ΫΎΰκˆέΜΔ,'#ε2ζΎ’}cnpΩ_ΞU e£ηĝTΈλn"ΰσUχL•Χ—‘;£HΗ~&I™ˆ©Σ—n©dQlδ±ΏΌ‰YΉwGΕ¨’` Ή,Jάδβd–R‰ΧNΡWͺkŸ`(_tV“QSδͺέΟ«ω?+…[뚐Ε(Ε>e4·ί84:aq,ίΫͺτθΝ„*oYνΙƒ8υk£oΪΏύ\ωωΆ]€”Πj9ŽsϋΦΏ~ωE’¬†ηψ/.χΟ-Q”WΊ>ψζ[ΪΝr»κo>~χ'Θόο?||σέwΏςε§Ώύρ?ώU{qΗήδ—Šπ¬#~ϋ“Gχνοξr‹μ1gvBΆm-΄ΖFλΥΥ; +ωΐQ’&BB¬‰χ|)Μ™TΌ`X^² ƒ€5κ 6˜ΰ™ωώΩΩ%1/ˆ)Ή-ξ%z§\oαqYPφKΗ!#‹vε{ξ *f­Π€Ωέ’pϊΊβΜ°Ά™N§‡`΄Tβ"Šζζ©=…Τ0ξj@ε‘ΐGMΜ4ŠΣːe­‰R“ώ)­ή­;Δ*–ΤΟ¬ζα5†–„’3c&Ι\Υ€ΕύΚ$³&yΒέy*‡.žσεΖ1Sy^;YΨ³TgΏ@ŠηpΤμCΉ€@+Ζ(_$E6‡π“ΐޟJ$iκF[’JπY䣚XΡ‚Θ‘™s=tsB`―μδϊ”[—AO±$Ος74œγΑη1eΕΰΧKˆœM€Zoβ!§Ω’45*™Νr‡)ι9MΡo-:‹tzk6ξ•/ήϋ) +ϋ˜†ΈΪ8χςXAΉδΔ |ΩS³W£sƒXS6χ¬@"Κ•vp6<Κκ=W|OΎ€y41$ύJσ^PG&[}w³€*-Ζi­Πί}ψ žΤτΦΝ{+Ϋ遅ΆU_ι$Μω¬ g|ΤΊή΄Ί‡&¨Έf΄›ΩGΛ–y*V-WΥ„™YΩAƒ\ΚΩsnΌΗτ/―‹vψΛΞ" b. +6ωΓ_b’π ―a„υY½cYρτψH(Do©ΣpV +f£π6¨Έ •i λKZ:ηŸ.Ώ“Φ—H¬,•ΛΚbσB‹d˜†ChΆΎδ%Ε§yFqC]’…Wh τ š«sxν{πCξo·Ϊύ‚–€Χ2UBaήΖΚ\c]₯eΏΖ[!-²lEl—ͺ>“γ­‡Sw*ρ—Xέυ0ŽwS¦‹€Ÿprι!φ–P, +₯«Ω7₯Ί„έ].p}%=t ­tֈ§Κ=xχ%‚LςͺM㝨cΟ―€—οα§S‘Μx@χκ>i~² + £Ξ238G>―aέ‘rœ;ΥρωρΔϊ—Ύ2\R–MRNΕB:Rξ~Λε€Μ"’• [_ϊΓΦσ-aρυΘ,(Fšƒ}φ+AΊiκŠΥEKχΚD­ΒŽλ±g5’ΐQγ©Ω…zRΣ‘~έi‘δcƒ\ΞΚλΞ³zή&t-c<ήoθ\ΎŽ3:τδŒœi>RΖαij\^™₯ˆλΓSˆy7{T‰‰[lυͺ¦Κ/ηzWέδιΚ£1ΐΜυϊw₯Og…ΣgΜψ2zπ(β―WΟ‚ &ύyZύ혽ωέ{ʍ>gΫηTrF) qά@ξiΛ™ΦΉRωj(υ%ϊΜ.τ|‰2Ξxst½Jƒ@W4ÁP|­4ι(˜ΪΏ¦ΗI~6ΉUΙ7x Q|{CœlΟU獄2~e/8XJ^4έυΔTvuΧR½β8™ΡƒΞS`Ύ1¨_x±‘>‡¨ΉΩβανf Ο#΄U}υ°K’ED"ϊ§Ξ‡_©βMVβ–μύ΅«β/_±1 ;Ž υτ`P:ΪJ›_1m–™½n>–ˆrό;”}V^Μ~VΠ±ΞŸG#0|KΎ΄οθžGrπB»_ZyΚ—³zdN–¬O)”%t%«^’©ΘaΩc5#JλTZό":sοπTi“Π΄zΤ¬ͺΕGAW¦_ΚPΣ”Sy(HO;ά*μjΒ’·ελύτ³1ŎQΟ~&‰@ͺc*LˆP³c Ή§u¦ρ2άΕEΡbόŒAφ7μ%±$³fρAΆ¬““Ÿμ£nΰξΌ–Jp<ΠέτΩJb †Κ%R½qŘ=tcωΖk…₯Ε : 3αž:*pq΅ΠΡι~£‘Βΰ=dlΓ₯R–*Sy£*ΠΗNb€―Š?4< ΊΝjΌ€p„KVωWθ½ͺ‚ΪΓΈKΤW™Ξ ]H-ž +nQ―/7(α³χOι žΏ`η3s!γ<΅9Ο’:Αy|S#p”Έ2ΤCΚβΤ`Yk9n6>+_65:k?φmfW6š³ρ½"Ÿkύ­LYθ΅}„˜x,lΝa(΄άCΕ†6QS/•ϊΕ\(₯ϊX‘ΟΑ—ΞQ[τ6ΔΪK1pIUa₯&½R­;±[€iJ +ψψqDšVZ1 )gUΣ\3„ήάP³ε#!‰]ΰ}t£™dPHœΠ—¦ap}:T_bS.»£WΔΛJmEKΛsΓ²U―h 1±`kKc-ϊτ†xΆf‹Cb[W'τ‘ ·ΖΙώΡκZ@‰•Pσϊ7ω[“³Ώq`d…%0%I†q ƒ9v,~Ψξ˜jRa?Œ?’­¨ά€XΧΌΩκΔ°2¦— ŽΫΑQ’ώpEΕuΤΆ['ΣcQ)a”Σ9‰0,˜Ž™e«ŸνΣ΄©L#η'‹»ίσs˜…-^·/R΅Β4…uo·^‡ι@.θ[sQ£Α˜…W΅*ެωφ΄†’©€aίΨ$=€φtκΛ΅"~HŒVOΒ!7³ν2hϊf=SόAθ°ύ!!5O1žbNˆ„JK―ΫO~,½ι `Ys―ά*ξτ§žΥx†’žM­ ₯Vτ M ZώΫO5I΄Ξ½Ο³Wœ³ΞΚλΞ³zήΖ$ζΩΪγύMπΛO―(Υ”'τnσ‘.Θiά+«΄ΩϊπdŸΙY+§B½o–~UR_šdΑ©8a·šλ£+€œ^ΝΣdf³Ι€θ|ίιΓρ^-»aέFτWξ.ͺ˜ΎΣ•εEν"mRge(’β‰,@Šζο{ΐCςJB“lΓ6Dρ’ΓyœδCmŠ9{Ώ6ΐRIuυ΅Π[&²˜΅ΎΠΌφψ·‡.Kμ›#Ɓ̑9i‚)K8M#’h>ϊmf[μ²Ζƚ‘Ω§_ΔzΣ@B₯Άίp‘ώ봁ξΐm˜$˜‹°`‹‡Γš„3Έ‰²YK …^ΆQPJŠj]Ξν‚RξŠ…0Ό2ZˆqA22κ„άž|‰_†ο~Jc_@εŽ’Ο˜’ηl―œ9₯√d1Οτ¨@iec(Ξ™sqr˜;3‹‹ξ ‹=€&P[6Ξd£kӝΈΕ€: S:ŝ₯mαDζΆ89ŸΔζsάtz&ΰ©~ ΕQ¦˜:Yt΅Υ—VN’e­š΄A #·»όΑ³;œdΊ²eζξ:”@95%ZΎϋ’“4Λ΄«œ©ΰpC―άTy¨eOυI V‚sς›jd†ZN›²,`ΤY2gYBU(œταSΜXϋ“δ€NΥ5eμΑΚL–vFω:K$ΆL%cRœg9§Ήjp6Τΰ.κ%λ7˜φ’΄ ~Ίhut˜Νζ!€DΘ2‘₯ Τ·ls"―8Š»Α‹EqΕ| @υ“9ŸŽ‘UΞ‰8ΩΙΜl”Ž3=­-BΛ’ΞnLx»™6~˜tŸω:” σΗ£vϋ·φ ¨%›‰δ0σμ5ωΖ¨‹Ν’2ω5Π7˜kε0„VγΩ܊gVι ΐĎFCcPςAUς"&ΒΡ¦•‘Wͺ‹z«Τ +ϋ)(0+h«x‡«$σWθ6ΆkF%ΡΝΰπ ε:μ:2mt@€B¬²£ΝlMƒpξη‡šn‘ψBƒπ•Θ­b"Ð œΧK“†!έCaΒzΛ1@@ Π….ϋΑ,ΆLοˆσσ€L½"eέ—Ω³Ρ:lDE’¦;YΑΊ”οχJ\φΘDΉ@֘‰ΐ—Βi[ ÍΙQή™ΨΜ™Ιόr$?€Ψq‰+YςΆQːxΚ«w!ΉΏυE!ΧήΓ?β―;Ύ?Žž.)΄•±;}Ήλ΄Η”UΟ +:>  °&ZZαΌΫ‚ΖD@HiνŠtP™A€…"#¨'ZΫ0x΄*λ€Κ†χ~»ΠVz,ΉoΕήzͺŸΏqeβ£jH’΅W!ƒτyMΑZΔ«θ6Α σ€UήV²nQ‚yaΈ¬ ΉΥOš γΎλ!dϋj‘”ΡSΥiN©|ާUWςϊEΣΪ7b_G=yαυ)…ΉnράψΧ―4ζ‘₯f¦3 ,ώ?$1Χ₯ΕDΕSΪ*-Ρ\ΟΥX»zŸΜcF#Ν»Άn›΅ρhΪgοzZχ‘r Pߝ―ά’.-w ΫVšn9~ΫΦ“Ύ?Ϋ·.Έ] ,»eΤΦ }Œgώ(ϊhŽmί|Ν<λΙϋ¬κ‰d£S½)MUΞUΚΆ + ¬―©wΛ”¬’L—F βϋΎ.„w]B}ΤƒASy gΎπ‘]₯«ΪŸb ˜)Ί[¨§ςƒ|‘@γK‹/I*:₯ΔF rVS¦ΒŽbή‘TœŒ/’™LSΜ βm |Z’3πθD¨Δ¦\4‘š«uQ)¦šeijFƒσά¦SΑxώ(ΐΑyΩχωα2ΦYνƒ8םΡu_c‘£T/―ΨUiTϋ©κ½“-Pb±Ÿoι‚uυNαuOkΰΤmΙχΥ©³@Bžtς€/0Vm•―Q𯎘!œ:Gp;ΰκ0θ›k·u‘Š^i+:8ΟχΝ³ ₯]υϋ³ Œ<½k±):Bπa='uΎrgλ• θψ°|Ρκ6ȏ•U:, +₯ώ…!PΏ*”)q}[•$‘ΓX¨VqϊXΌλX=™£R]­©‘ Χ[g΄XHŒy`–Άa€ρL….ΠΪςα GCcνͺm½φYλ―³ζˆœξœƒd‘Ω¨Νΰm"Ϗœ“;“1§{&l’ΐ)±-f&’Μπœ*9αΙ +>lφČαΤ;g=GΝ§ΰΚv½όAY§Ήό7:^}ϋωσΥένΝρρακζΣνηΗ£†Χc Φ/h‚θRΒσRˆ΄8pXψυεΗΡ“p‹‘d?\@ν@ςFOX[Ϊ6xy7Ιοςύι[όσζνΓγWŸ?έΎzψεψ’KϋΟΗ›Ώί?~s{}pƒ@¬qίΈή|s{υσί>ύ›Wοή~ύΧρΌώxpΧυZϊΛρκνΝύ·ή~έ> <ώςσν‡uύΨ…Ώ΅?ήίΌ˜JωM©΄5o#ωxυϊx/.puΣrό€ΒΒμyΐ~Α»‘•TΓHΦ@Bki:ασΤ;’Ύ@Γ +`]mjό ‘™ +μ`n«lkUž1‡€§M\‚ήMσCLΞ'βγycŠ˜Ξδj=ΎΣ}πͺϋΖ«Ο_πύθΪJύ φ‘{!Ίθ/έήβΊƊ°‘“]T)1Ύ#h0r‹Ω[i¬ΡWšMœŽθ7 QX =>I°ΌζϋψvθΎƒ­Τ“ίΚυ¨ +''ώ!ϋ…£ŸνFΦFiΫV;ΆΠi0ρ$$ƒb’UZφοΚx9Y©όΆ’?1αgOοκψζBkpSΊ)uδͺR„y @:/Μ;V“Δl4@Μΰ&… β«Ο'10)TxΦB§ΦhrΉ§\μΞ’˜¦SΝ-œE?Ν*…nBλ<―γ»­Β²Oγ•έ¨FG–‚Fdƒ–p8Rΐ2HΕFΓΰ&5ΌέΣ!ΓB^υ€DmyPΙplΙ€‘EήΈβϊM6ˆ―βS¬"•h 죫%ρIOΰYκκΘ’up‰@έ"Oœ!&t$ΠΕŠγž”Tε“atσΤ?«ΰ RRk"`κk=';FΠFbλ=ΎC‹Tev,FdHϋ1V@i χ½ωz·™ΞFγHgΝΜ%{FΆcc·γbK[ι’<Μ…aHd.~Ωχ)©q`r‘bη ±Ώ©TΫF5І§”]TŽη₯g¨ωη„τϊΈ@‡alρ§έWT†τ=τκ…!'Ττ]«dπŒCMAοQγέΠ%…lΩpα@„δ€J“ή/Jdυf €θΓρžm™PΔά¨/KΕ ΐσRkh’–#cr s‚Γυ: B •ΰK©/^LΥ §„λω ϋεοΉΎδ= QΕ~χFͺΏŸO%»ŸΎD‘ΏήΏ‰XΙ¨I7ύ€ + +$WH—'οI%‘²«γύ…:Tύ‚pτ|"΄pHKΓp;SˆZ»― ―rΰ°ŽG] +jNtΈΝΠIR_IΩκhΛZ—«Xh…!`bΑeI.PΙC ƒ·%ζξ’"utΑwj‰€Ο%o_“3a:μ―mΑ^h‘όη§χ…αV\ Θ€ήΝBl΅ ‘ξOΔΎ QΆTl•§g²"™ŠΩ;ε4 εESaΉ„©P·ϊzTYΝ[}₯³π:«DLŸ„ύN n”:Νn|‰‹~ ƒ'η3Α΄9¦υ̞jς@"ž)Qzξ[©Ejd-΅½τlšU€ ‚€‘΅D‡‚μJ+ωάfMa'ύO€άw† endstream endobj 25 0 obj <>stream +H‰”—λTΗ…Ÿ€wΨ,ΩQ΄ιϋΕω…Α‰‰‰‚ˆ[Š’ΡΙalO gΠα؈·Ο·ͺ{Ο 2GφΐΜ’ͺΊλΆͺΪ§ΆϊΦσkZc«uyxO}-₯†Υ§œŸύro qu1Έϊš…ψ΅HΖ‘«έ›X Υ#Φ^šΔ2ŠMˆCΎθΔ²ΆΪTΜίͺέ£•_ܚ½έ¬±λ%­΅ΦVWό8ς{>ηsΑνέ£Γυέαxsuϋvω\F—ϋ_/–O<~zuw·Ώ½Ω=~½{Ά΅ΏΊΫ?ί‘πΩς{δώΐ‡ϋςιgΛwί~Ψ^Zξ?ΎΉ»4χεσΓέρΦLή$Ίϋζν«ύ°ϊAiύuυοϋέΓγ«·»'ΗλέΧϋ·C'»εώ³ύΥ‹χh=:Όά=έί^οoξΞςiudγƒ:~Ύ;ώνΥΥυαζ‡έWσ„έκινώ—ΓώΝξΩρΝλ!JΒ©Τ>hώ›N|΅?όπγ δ{Ύ‰_ν7»?ήo>*ϋdύέoVXˆγaϊ"ŠσραρΕζc―«χTγτρΫΓσ»?vϋO·Η7wfώρή*όςή'Ηέ—7Ο§κψύΕώ‡ΓΝD>_>}z|ωκκωρηΫΟξ]|_‚§­{‘‹mZz_bΛ‰τŸŽrΛϋσ»7όυ³}υ‹³Ύ{k?ΜΧΎYΌ[ώ²όσ_ny>Tž)xΫ)›Νε%θ―NZž\’§=Ή΄π~τ 7ΏΉΙgŒΏΈΊώIρ΅>Ϋ_ΏΫνςχwΓιΏκΟ5ζfβΣψ„Όό€,ΊΊ†κΛ +ΡysP Qε +Hl€ΊΩ;“Θ-/Χ¦ΥΧΦ}YΌkξ½,ΙΣ_”žyν5DΏv‰FŒko> Ε¨₯@)ͺ”0/$υ ώIzXΛC(9U@‹m%‘²κλ”I)ΕΩ'‘7Vcδœδ1`䐹œΧP—'WΙ'o\œœ³ΐΛpΣ θΪOΜ‹δkΖϊ½τ +€7%'Sd0@ν Ο5yr ΘυyŸ‰eoΕMˎζF˜ΒΈ¬ΠΚΈhΐwHaˆ΅’’ΉΪ0jk-€eSΜ‘ Ε’Λ”ΛΉ’5δΖl‹Γ zy-ΎhRΡήμ“…±Ÿ*yYb!lNvΖoΣΚiTΔ&ƒŸ©α½Ζ!υή.DCžzg΄έ;ξΔDΝ©’EθΉζŒΓΊˆ9<q`G,\Hq+‹·ξ]{’ξR=`2ψY6†Κ-‘ή›? ΡββGY + Αβ2+vӌޏm‘iϊ IΙ₯0K Ιc iyζ‚»ΥVΌmT8^2λc.γΆΩΩύOωͺτΓΠάΠ’r’‡εxδ 4œΣΓQ.€tFFdqΟ…|!§ώrRdg!¨gΰŒl ϊrQeŒ›!š“b¬VρDέΖHHy"#Π*±ž†¦ίβΣ1œ5ΟTxΝ)>΄iζ‘8cί_§r[κ“ήjδ±WJθε“γςAδ^Ɍ€”+€θb”ρ»€0/ζ{30η6„`θ2€DaT½SL)¦A-‰š·Η†l 8™Υbzΰ“”ςhΝ 0-ζ!‘»b&φŒ:SΥWO_’― ‘©€X& +t’wfQ Π φ5rΠ$eh‘γΒNφkΪD­MΨθUœ& ΚήJ +ύvX•Υ΅ θUG©Ρβ”By'Δ²¨€UΐΝ}Β‰*¦˜…]½Ν"Σ½–NsΆ&Ls‘ΞmβRvMΆ=KB&…„“¦¨£Ω5M6`œF$<);Kq•Φ̎±κΐ^Ψ–¬‰QLŠ‹ω‘ώňޜμA44³žΤ]ΠLž΅Wε5)hh§`ΐ ˜='΅¬ +θ[ΖY²Ά•9νOΝ ~Μΰχkέ©*wt4Ήλζ‡κα pΙsΞ&Θp*-Ž;΅DιjΆωbKšoξ Œ]Μ0σI*@Ϊ «M ν 0%™Ό³η78:θ)ψΫێ +―A)hξ©RπƏ•‚Ϊ+gD4gT}§E‡Κf™ZββŒ#έ€© °CxA§Β…ΈA›cΘκtΝS΄5 )γ‡T¨bΘGqs…έ4hGiΩ:K0U~²£ ©β \V Υ5ϋB=i5N™~j€ϊ<«eβ4"mυ³g σΞWn»§{E*Mُ’«4˜θ–Ϋ&]fS*β¬ΘΎι !Ρ_Φ*2!ŽΆ³Z" «£ξƒuΝ€Ϊ Κά,<;¨NΝΆC_"ΜΆDγU[G‡Z™€h‹‡)‰³ƒ–aXΑΜj=‹ΊΨ§κ—m'UΩΠ]Ιw†rιhς\hΦ {9˜~Π°Ϊή1Ši…‘:Ι%§8{Fh1ΜΓψΧjΡ§,qΠ€`Ήg:rˆήͺC:«QprH£Φ~λ•"‡ς4Sxt-ΆCƒh8nf“Fwf‰ͺ’¨Pυ ¨ΞΆΝ1Ø|Ρ1z2ΣlE$φΝ΄xΒlˆ)zΊίΆΦ“œ#Η.1<υΜ’¬@ΰΤΒUX',κ ‰₯†9£i5&hs\‹_XΰžVψΥΫ>^Θ{½±YU^mgϋΝ)šβjΧ`ΘL VB­ͺtTWc‹7υ†›€©1OrΡ<Ω€pΛ%Κ(‰Ϊυv ›Ϊβf΅ΦΚzςόŒE₯9 mΐΜ’iz–‚΅%ΣΎΠ†(βVyKž@UZtΕTΡ[lY™:ς)uδZγG ΕΡ«θ*p–ΖΥ²ζSVwJ,zVO¬πHρ₯%›έΞj’€0ԘCtεYHdYm|€¨z=©~;l5 Š ’Η6enΜ€ΣS¬œ€© Pw!ΚxΊιε¦ώ:πWw›kXυΈ„ceΌkΥ¬s#£Η!0ξ³[mΩέ@-τ‰cd'ŸiP†>χ TŸYŠ0ba ”'Yΰl²Π©  7ΔFΊΥ ‰½zP$τ•cγ=j{šό5Ημ9–E‹0ΐpΐ– IhΣάΤZqVϊ³c(CΗ: `kνhΤΌ¦„s6ΙαdΩ}τ‹Š²ͺNΨΎΛY/“Uˎ+ˆώΚJΖ–²oμ‘š‘G6{( +lj DI ΏχŠΨ™ηΎΒS#TԍΚΜ“ΝήΡ,,κ’=WδΕΧ¨oH μ₯§εάΪPΩμ°=^*Λ$Z'υ΅‰“0δdbZ‹hŽ Υ—€Ή«τs?μoŒ.Β­Ψz€Te_šiΌΕnΜϋf‰] +Β{2 +N°K‘“ +{|ˆ‘._]»ΆωGηΚψzΚ»WΛaN¨ϋƒ<0ψψkšV%[¬o3Ρ΅aIΜΆδΑΘψ’Ό΄‹KνŸ6cWŽ£•Χ;›ξhTxΈ *€$Iϋˆ zχύ€dή½±Θ˜•'x[4ΒδVθθφΦ)΄c§f‹ŒΔ?>Τα4οϋ6ŝŠZ,h;‹‡)7©Ιte9jθGͺά¬˜*ιψύ› ^Š.ΉcΔψS;L‘‰ς‰XC¬«ϊa3Ή£mΑˆΨOΣΠ@GήiμqΌ ϊ­r%tzΎ4ߐ­Σ°_ͺl₯ΚΛ0kKύ«ΩβwΥmpψ΄ξ·d8ω7΅«-x ZŸύάt:ιvbyήG›U»1ω}ύ¦Φ·!“¨μ£"˜ύd=Ά:€«Ν˜ζΨ³0ͺΆCŒvέίΊyΥ.’I +€±TΕόC£T«rέάΡθߝάP& °”2d²έΟ­Ύ­ΎςπCΧaο€YŸ₯§|onm²KDVWz†rKΎΚΩfž'fͺΚ7χ“υO.δΊ£OΉ,ՎΗζ,΄Œζͺ½ς—Ϊ¨+#vλβξNΝx&ετΜμX³^φ0k`fΞ˜‘;°”-buΜΕ‚βΎΔΣ!—»gχ”$»q²ρΪ1tιƒk ³ο²ςVψα]dX½Τή' %hktμΙ1Ξ/€΅m,䜟3IΉ@©Š½xc3Ω΄§˜ΰζω·|°ŠΝΐI +ΛτμόwGځœρξϘ‘—QXΣΞOx5Θ9¦–Sx@¦Ysσφ!)ΥρBβ>h¬Υ?§{Σ£ΓΕ]ώ…ˆ]΅Δs“RΈŠL fΙΪΫΚδc(-ιFάΟαVF ›ι«Ξ@z™M·d„J~fšώΌή8_θ„E#ihχBd†³c•g¦½ξήΗ°Υ%ξΪύ˜žΑΉτQe‡ƒŸQ”'7=΅N‘ {€Έšcξ^¨4?Λ»`i{’…cnΪϊEBͺV$ΑΧ8mkλUΆˆκμHt‹:γύ’ν7BOΙ΄ι΅ΑI:»τώD(₯ΨχB‹Ί ©Z²26‘¨Œz ι„ οc3„.?˜ΠTb₯yΖω …dRͺ—νy˜’Sbv!ki&V³ΗL&!kίB#’xhΉAΗ₯Œ1£ Κ;dd`άnΊ(Κ<ڌV¨G^§ψ›˜XϊA>*-œσέ8^eΘ\η9Ω΅6ρBTb1οΑ†(’M€‘‰‚u +…*Qί¬κ»‹<Χp;―qŠlK}¬υ³):‡ΟΟq<5)et*Ȉ’Υs ݌8Fά™Ρ1σ8k!Γ6kJΩ{|snŸRβ?^\zfJˆ₯‰+έ νΟϋŠ™mΚoώB=ς¦Έ1,5‰_Ϋχ‡G]ψκFŠ‘Ε+―ŸF’_μ§œΨ•oΒO κœσ =λAˆΓM·Ρ7ΐ%ϊΛ2εJŸοi\Ώχ}Q’ZšK9¦cΛγ«%šzβz+ΧYζρ5ƒ˜Ό7ύeΐΙ/δH(ΔE©Z'›΄›9φθ–pυcάτMš +…ΐλςΥΨΧ†³ο&ڞaεN­cAΙ=9v!Aε˜Yž­{ζΆ TWAToΈLβ)ͺΙ"΅£½»ΣΫ +‘ΫΝAΛ―ί‘#Ϋυdw”ΞΓj5‰V/DΩΗ™xA±άΉ(kΙ`#Ϊ& /mH&ηA"ι Ό!=e΅p5-GŠΣ3υΌυΏωΠΏςGzϋž?3ΆΌ%χΟίΟγΰ„τd$™ΔI~<(ΎzEσJΗJ―6ΦJ‹]¬"δ‰τH– θ±Σξp‡­{“λβ¬ΊuS­n―Τ3ΆwκBGΦ°>'H1%Ϊl”ωΜ¬ζS‘*D!M™Ξk)~P;>Ήf>!Κ²UNzΖͺ‰ς±’&kMΉwω΄”YΤS_YVέŽΕYB…£φΌΰΐŠ©€ΌIΜDͺpJπίpΛ\§Υ΅ΩPr—ρ2tΙH΅ŸŠ#κΙΩ Έ,[ΔUK½Ρ΅@ίΰ˜ϊΜΣpƒTΈ‡…5Bw iϊ‹‘’ΓΊv »Y—RέOO₯G8ε–Τ«EUύοzΚ‹>¬{…œw±Iδ­€&‡G₯οy¨_Φz·:8€‡‘¨Γ φ!ϋLUι*$λ–Μc>ζ·Θ>xν£Έχt€ΊδŽΈI¬ξ9\‰B«„<φΊ‚„NΗγΚPF’¬"+³cζ8†Rh’D +ν{ΕK¦*οζŒ™iμ@κ8D¨ο]{souΉ[Μ5Ξ7ΉŸΰ―…έυžSA1λT-¬S§Ψ°3œ|Νψ'άMγ“τσM¬˜»ΌX9rΜτ |˜ΊZD™0g%SΈόΫΩνˆ@A]`ζ³Κ€ϋ@ήArτY΅G=f©λ:1rs“ ldm£·Dz%«Έ©ί}όω§~ωύνΟ@_ό‰ώ§«JωςνλΏϊεγOyϋβΫoΏωπαΧφσ§4φΛ·?2ς/όoκώp₯ΫΑΝ•ΚωρΠΩ$+½Ι*ψEΩ‘:œ\Υ=*ύώ&sqŒΏ ©@)L~XΐNt˜ΞΒVE@‚ΎŸifoΙ ¨Z»Πλx5«Ζo’έy0ά€κœK.#λyΠΙ*-QͺΏ‘ς>··ΣP£CΣΚ–<―οΌφΠ"‚§ΦYM5I?dΈΓ ?5/ΚΧ 4πΘPσPe] )1L˜/%Θ[M/Υ³šSΏw|lΤ5c–BΐD`Ξ¬υu±΅q„$$ȍNς σdίύ«BΏΎν‹66Θ!ΤaN(j™Γκj›Wh!m‰έ²άΩΌI`k +šu#C½RΔw^kœ™΅_η KΌΥ[x6’❴ͺtR–Γg|Σ΅+ ˜΄"`‘’–¬ΨP7¦ΐΆnYRψ"Ÿξ-t#]έš­½BeΒ…€-Ή‘·S…²R}Ν΄ώoqΗυβ·eFΏ+ϊΘ>·Σΐ€˜&v>gT€“ΏTψšPτžνΠΫΊg'Μq"ωG–R€ρΡς-Olιμ”ήξUNŽάΪmψ·•ΰdψɌαIά^1SYHJ2qΜ²~Mz17ήaΐταJnI7Ή)Ώ8F!ΟΊΌ·BΔΠMZR&“³Žΐ ,{‡[ρΊBˆγΨmVΘVZ―‰iζcaTΕF0'ώ’b₯©΅ƒ²^.­šUώ+g˜ˆš½λ^:ΚeθHt”F’@Σςο}ž΅jέΑiH9ηνΊνͺ΅ή‹τ”σ {Ώ”lξΞλZcενΫιήB•²ΪuΎ°λ/εnVΫΪDŒΞ Ÿ“ΏΒ^ΐ;”½)sδ—>σn5Φ 9ϋ\ρu†ΏΔθ/Q<Ϊ]ƒ Έw=݁@•j/°n¨h”DΔ΄>‚•β4‡Ÿ(+C­πΨ²Εsλ’eχΨ@κvρL…"?gB’ΥƒXC§kΥk†vΖ”vODΫ’ ƒs&ΎΟ­Œ7ͺ±g˜ ž²εξ χI Άο“OEq4FΚHή)iΘE¬rM¬―Yy‰ζ+5n+@w,QŸyύΈjšUq›DPΐv+½”Ǎ&z€'ΘίR”«e¦ώ”ν‚}-QsOε¨·΄DΏ…Fb½Ζ‹8’™¨ž5N1؊Δ₯cΓπΖά΅τ&Oi}%πΠ§ Ό–ΰ³xθ–λ4ΩΣ"v+λύP­Θ £CΫ»ρΦp‚R]υAR‡nO8¨r›0 A5Nΐ#₯χN”7=ͺ4ˆΣ+λΠ-oQμF–Œm£Q4tΔ½R<ΪIΟ΄)e·S:«η:όœ»­₯i+ιH“€q ’4ς―2EΛY‹Ί†6ΪH³•ι]… +*†VK?ΝΝαΉyΝΝ‘›nΚηN/§K³υϋlΓ${Ž«~˜ƒ#α†δΧckδšH:­βN'r·ΕγΙE·Ν>φC=^4ΟΜ9ω¨;Έ›ψΚeš,₯j7bΤ‡Ιρξ})¨">˜ϝf>1grΟ­―&§ϊXX@Κ6Λ,«―Φ¬Šv€‘νKA˜V§O€ήrΉS¦q•ω)’wνI©Ξ +ΎΊΦƒ¦ZΣ‰Κ°ŽAg(^Ηͺ Βcσz€m±:mΌPΓΖη$,h‡Fcζ΄πn0܌͒R”χœΥ3@—\–7΄|TΝΥŒί‡>D‰~Υ“Μj~ςsCΡ4K›‹˜~ςΩ<’,Šsω’9 *΄›wήώ<χHU›ψ2ήΡΘΏ„˜οΜI\{HŽ\'A ρ§„(₯°ιž±[ήΘΟ~ΊY=〬u(”šκY&Υk(σ87 +†½ΘxŸ,‰E…“ΙΨSuμA† Ωm“ংΗΘpμ¬­uΛ73‰yŸd'—Ž›·Σ―μcΤ¨¦ xκM½c%|―)₯ζ ›\yδ=0σ XrΠ(S "θάd€R―ώš†’mΑ(FΈΝ΅¬–)ΠiOΈ©ZΊ–W-γUΚβΆ―ν2Ž^}Ε*ΐμeα“H3Ϊ4Άΐ2ŠšΌΒxpOΛe΄J©φω²α‡ΘΪψΦ‘ŸH?>ΕQ£hx£šZGSEςˆ%`»(W ` Ž!~W@&{¦Q=ΐ;•BΑξΖ:¨ΗJŒ@ζgΣτPA›~ά +Oͺ}0ΊΤΌ‡41ςq gδίΒΞ²5f&ό‹y#]§Αqψ%wœY=c ·άχΩl^ψ*ž!ˆRυ½ΠΟΘqdΓΰ`ςίIύΠ³ΣΰθΝ†’Ο‘fΙΕϋηΝλ½Ϊω4ά΅γτιU½/κΊϋmϊ‹ΠIMI”?|Ή3Jκ…vξscΫ•ŒWW¨@;]£‡γiS*ξ$€ϋb?o\C.Α…Ky ιŚ‘D‘ΠόΎ°T–“Ξθ +}ΐAwRαΛn‡Z{t™ζ#:}[ΆwšDk¬9³ύΌ)ΦikΖΏBνθDΖƒXƒπ€‰Π―™‘Ϋ@Wπ0HρΝAuifŠο5sΨύDj$ΖΝΤR‘μŸ6O‚΅H Tίω$'1N Α}ˆKΤ:*œHΕΗζTΓ~―’"<σ‚q·‚'2°π9lν\)H1Ωγ*‚Ό{HCΌΝΠήmͺύα‡ΕwΗWΚV—θ¦ͺρƒ§ϋ‘ει£Fœ Η©ΝνϞŸβZ‘~¨JψaT eNΟ]h,‘‘Χ +ν§‘½#¦πιΠmv›γΠ%hPγ½ΒΜξφ[Z«3Πe’·VφMΜτvξC[9QΥm βΤbTX†@βW θΘ_ΉοœE/Ξn\Αq $lwΡ¦}άΡ![έ"ŒqβξA%gΏΛ 2w ‘"AΒ"TY δŽ—ΡM?3/Ι1ΠAyΗ%–λ¬₯7 $σŠ4ΟDΆGNc'#ŸF­Pi<@ϊξ¦.sTΥ•δ3Κςӊ¦©q¬iY =Ÿ)α5³vY:βBΦjη\Φ8ž$σ3ΓΧί~~½*J”Χc<ˆΥδΉg¦†IΌ΄Ηw|ƒ’Kdθ;˜O2˜ΗŠ)­yΧW…3Cώoκ`„GόΗ°BΰhΓ]ξˆνΈW>o™ϊ΅μ ‹Ό†HOώa»OύαA0ο"–Θπ}~5±Υ^3³K/ΏΫŸV{9ˆœ+Β7ΥDΦczΈ³j£+P‘ y¬‰7μJ‹ζZ·ž€œ BΏy§ƒ8EWhΚΘqΦΐ0gήxνλ>yΠZΉͺu]΄t€‘ψ{ΪJχb!n;Ύ8 Ψ2ά—.whϋNr ֝„ΏͺV_ΌΪ‹nPc’…€ΐB!-Ζ, °&bH)χόΔ8a‘kvšύΉδci­ΆΚΉ Λ†αεtΖ +ΦŒ[°CfΡTιAΐ{¦΅ύ S«D.‘»μDΞΑdI67άBKkν¨œN +~νt.(C>θ:Ύ!¦α|‚ς§qδ•θ+n|ΙIHdΧ-ŽΛsήιJbž +Q‚Ω—šg\O£ϋQΏΫ5ŒA!ζ½q<9ώΙ°y`έ:‘ς.„O`\ψ„Žϋ*γšαTVέ)6]fθ#IXA!€F_iuΛaΤΰϋpϊ§υEg΄h$œR­n9Žτ•‡νΧA4ή9s₯EEPέDn‘L[Ξ4Π‰\Χ<‡ +ˆσγ9fεγxΰ,ύΘŸ=!…:Δ΄r=dFWy ΅f Sq…P‚8Ή’½Ίνςšι†hξΞΟ Ό«ŸFυ?β©”#Œ΅_Β »|ΥΓ¦9Ξ3OΟͺΣ²T›v`Μ3Ρ;ι•©ρΧGκUgb HOσzΛۚΆ<+ή`‡m°€°λ"©%όMΰžΧΖ.―k=O‰Όt­άπ°8^tيžυηΣζc§±x‘T !p Χπ¬­?Пͺ9+έ©†ΟΜ{iDsΟ>s}μv"+7 #ψ”ΉΆF·ktƒα₯vΩΚ“λ³Cς’sž"ΗΓΧΨ²Mμ…Ί©3η5VμυΈŠ†τS¨€‘[nsγ5Fά‰VCK•¨sž ]oͺ±όφ@™WŽτ_εv`k―,ςΜŽ2qv3ϴݍεPz7?…32Qa΅ˆ–3Ι +VΰoYΚ ψππru(:Bΐ‘λΛ †Τ)XΫ-ι‰ ŽΜ šc14hˆθu «kz$ιπbΙΖS§ FOόσΈ juό–jΡΰΥΫ(0εTœu=Κw+M:ŒΝ-vFτqH‘θNΰφϋJ…iς@ΈU„HΟΩ‡‰Œ!ΡζŽΩ,ž(ς$n’Φ,‚¦§νΕ}jL.ύuΖρίW_ψψέϋwίτγχ~yϋΠΈA₯”/ίΎϊΫΗοόχΫί|συ»w?πן>~οΨ/ί~ΟΘ?σώξοΏΡB?»˜\oίşωΟβηϋόφ_’9π"-q{@.?4[ζAΘ­v•0cΜ3ρ“D{αVFg“V΅wψ;Λ0‰χaρ0Tuνϋt(`5ύXκ»νU…p‡šΕˆ#ώπk™ XΑ#έ42€;§ yN`Ÿœ„τ₯Ν38C·τ9³οd{«έΛΈ76ιθΰξξΆ|•j±I +»1Tλ-]©ψc½Lr%ɍ z•σ°£τϊόzfN2σK€ E/ͺPaEF2œξ6x[—Ζb5Ι›\Λ]μ–Oy†”°¨ΌΕO v)*ΠκyΆ¬)]”pw­ΡΌ+~L@eά€@f=½§Εσν\zΉFJΕiΕ‹κ™oΖC£ =Ύ©5ΰmLίρV90|r—’Ο@εσoΜ‡”ΰ'UΙ«Λ©L†ήŸgo‚$WOӁjα2$[r΄ZP`Bψγ_ +»wS™°X€=’Lߜ©v†ΰ7qmοo›MΆ(kΐ*ό—|°€"…pΪΠ6‚ΤΩ;$U‘’oŽΪηqο’dωΏχœΒΕΚΡaY7α‡EIHνΑqƒ9€zΜσ֐|°a›ΖΠ€M†ώί₯ =ͺnnΛ³CŸXˆ–Ko†WπόηάW– iΐάζ;…I$ΨΨών₯H€K§5 ؚ)ž«Κ’ roU˜4,ΟσκL1°U³«υΜθŸ’MΕ5’vεΒΊ‡θdKzYnίw0δ s_ŸEr²ˆ™S’ψŽ$΅‚ΜΖ΅bBΓέ«έs¬+½^œeώό7ώOA@<π)ϋερlϊV:sγ3ϊ f’θΦ$π|y+!±M­ž  𒐅qι[N1e[ΰΜΉΔΉ¬pή–v@5λΫFΧ ƒόF³ήΥ³#p-Ώ—=ωCTy υΜm‰e`W<ΕO• Σ=Z•Rš.΄;!Ϋ₯˜™πU 1£_eΊω^'N˜θ ›R?Ÿ&P0'Fά€˜C€,°« €e:ΫV8Zr!K©'”ήΚ”œ_˜žΆi(g²‹„ +γ™Ή*•mMγ6OS +Θ³©ΛΠΙyγƒPY0‘L\χ:χ™σ’ng²φgg8uΠ₯pς”D{ +HίρQχΝV%ϊ4―L‘ +œŸύΑ}:Œ¬σΞ_―ΒΦ#c¬uiΐκFξ[Ubω%”§–΄Ά+ς!Ί`ΝѐύDl=Θ_'}ΞH3ožG +₯w™ξ 4½οΞ_gγE»(\~Fε~Aκƒ—™έ±‡bXύζJPΠ5[[Ψ!{ϋνΏ+Λ”ϊvεWb§]ΈkUκ εs'άi/\’½'ςG₯ς αXεz₯‡Ve†?Ω•‘Έ7Z‡₯ΦΚjχ9zVn‚™ω¬JβV}vΥόο/BXJ5αT/*-ŠYqΈzΖρ`70&.΄’Kκ'!‘ς§,9ΕI4—ϋσ•‹Μf“«ξYrF&ΡΝωg‰ Nd&SfzzΩπ¨βeμ²uσE„Ή†ΟΝ8m†Έ.%Ε,—θheΧڏn;$˜»Α4Δφr «•@Ί΄jΘ ca½1ί°6δμj€²ΞΎ*λ+8JΆ'^7(vω ›‚o“ψυύέΦϊM”€ T΄ΐΈ¦ZT~κ#λΈ9*ŸK\<Ί+ &e¬·’μ03›ί&jb'θ2έT’­ΰ•:~u'u6§Δπ”)uKLdC2̝qΚπaλϋD―/ˆΏdͺΫθ’ρνήϋΡƒ*UΒϊq\Y…!²₯rœƒ–|ύΤt-΄­eWΕOs%›Α‡Pπ]ζ5Zε0λψ…$Ž‚‡‹š­™uε.rK:=o]ζ–”ωςϊ7Οΰ;,ό:eT6M(TcAΰ§›΅ΥίBVΎω[Π\{ό$j"βρNuςέ‰<I.ΊIf]Ina˜‰h‘Q>H|%Bh-t*št υΪΟ/DSΉnΛ=k΄T Ίg.Kh>LˆίΪΝ²}8,’œΪ―u)²¬~Α³υAάVοo>T ^|ΨuΉλ\‰€γθρAnŸν­#'μ&²* +5tΪ‘nη;ZFεόθ4€O}œH…smΣV—]$NKΕΫ7ͺΙΩ«·Γ¬Mt5ν°χέ‡φ ‘,0ϊGDl ΄aΔFΡΘ:•ΪΕ~›Œ€ζ_”•–άzMθφcΝΣ);­feΫb6€°S +Yf*άΧf€΄GsόTœl”ωGE‹ΎΊ qv>΄ΘυθMαϊV +λ©_28˜|¬Ο“Γ$P™Iΐ²Ož; •;qu½kοfΤQ̈eaξΘ"ρ‘Φ¦ώ.ŽΪ•’<ύ‘ήZαπ &$ϊ…HήNVω Ÿ/BτΩq㕬RΎžί}g]‘Ό %€ΞβΗϋΓκ$YΫνC9Xό9#Rd›Tj5Ÿ¨aΤ›R΄ŒΤαΌ;}†³#αμkd[%*v—υA~1ΑCy1ΦCλP8y)~{ ±ςle”B,ζΟɎ‘³fLλϋ^’Ζ™Ÿ,N“|mο2»πέhύψEeG ΄ΥS_ +1~Θίǐz.ΨS (·Έ3Ν"Τͺ™©λxΰyΥ¨R›H=³ΘΌ΅Ž"ρrvfΎΕHσsϋ<+X!z½}Υ’ΐ‡Κ +˜fz€Ύ0ζΙΥ)}ž£B§–kmvŒhG#ΚΛE\nW[—ω-τβςbΝ^ΗΝ³no#n2€Ύ>ϋblΫεηR%³ήΧKlΔΙ΅/鐦<cJΠ»TΆ(ofυ9_M+$GWˆΊδσ…σ\ΩΆBHδAΌPUΔϊ Iκ›ϊ OcΛέDQΝŒχμ[}³CΤQύ‹»(!:2π~;γ87>FΌ*JkxO1¦ΏμvHT†hΒΧ±3^•δ‡Ζ ’{½X+ΪΉ˜γx•ΌΨ^M ΐΥθυδ ΌΛ2  ŸΔ0…»› +š4R[G#¦|ϊ>Γ +HΨcάRτΌ¨ςΑzνm³wœCvKΫŠδμ ΉlRžύ’L ­Β·E S@Ωι₯&w˜m‘œ~λ<]ΟͺŒAl ²oΦ±,‹ σΐ†D†HρьNγa’ΊfΈ%¬}Ω3H;θλ€θ§"ήPΪ•Ο"Ϊ©Αšρ<Ϊ)H²½EͺΠl?R5sεyή9|η«’0Φ(‘5”Žƒr“a@τ“PΊqέιλ£²Lnρ’€¨|4˜SuΡ‚rŠ>ƒτ©φ~¦&~ν$5(Œ-‚7ήy49M$ΧMSIˆeθP?@\ρ%ή·LWY4ϊ)WγΪΐ "Y}―gθ°4y£‘ΜŽz^·λ1›₯q¨ ΚΨYbψ hΓύG"„ΧΎz-^nΝΏν0Ύ:)_αη"[fξεHΤ`Ϊ–ψ’οΘlύ&ΐΊ9ΔwΫ’·’φ€>τͺ™Ο*w Ο“^β1o—‰ Β‘΅z‘Ε ΠΩ± q€oοβ†T΄›l\7jΧ@+·\@₯šwΣΑŠ±ejη>:Ϋ/Γ1Eφ1˜'†\P’4εΆΕγβσχΌƒΜbΧkcΗMpΤ.£=ΘΣt£Ος€ΧY˜νΟ*†~'S*RLyτ…ςGτσe`›aζl‘g%ήψoΦΛ₯׎£ŠΒε mI½« FN‚P$(("Œ,ΛvρΜ5’=ίZ»ΊΟ΅€[9ΛUΥυΨ{=8ΔoEΜsΜk–¦€SANΐ”‹ ϋ+ «°\!ρ{₯Λώ(ʎ±"VΪλώ{>I?Q ž'χ‘CΤΘ‚ΌŸYJ·3NϊBΙx΅Ω5 +oŞΈςdB=Πο  ’Κ +7Ψ ³²θŠΦ—5°νi\©γμΜ䐔bZ J€™5‘AeAC]Ω™E:—``FωvšΜ|":u"€z«w€ν°βžv‚΄Ζ_j +G’O₯=΄Ϊ_[*ώ!·]T€σPJοŒ%)­/5€9|4h’_ΣΖπR½λ΅ŒXΐΖΘ«’Νž(fqJb1<]KκΡ1?ΗΐCcœ{C/4Δ’ΰδ&φδΟ—/><~σξυ㻇χ―>|ΊύθΩ―ΰ NΊΚσΫ—~όπξύίnΟΎϊκΕλΧϊξαρ•Ζ>Ώύ’‘ΏαΕχόυύi±DI•ώχΓ§@n?Όα?ΖνΩσΫωίΛ·/ΏzxψΗνΩ‹oτκρρν‡χ/ΏύηΛοήώόφΥγΫ7/™Ÿϊογώπκέϋ—_?όόιεÏ/ϋζέγο><|όω³Ν­kuξήέ7ώϋϊ›ggϋƒ?Gγοώ·ότ8Ψ|τ† ŠVςάν§ ΊGNP€“@˜"γ*@¦θ΅gQ!ΆΕˆA_4cΞΟe7j! ¬„$:EΚz⠰ӈ€šπP7#Δ#y]1¦En-kΟc΅y)ύκεMŽFΠ##3Ο=†φŒ‰΄§mZ†Vj][Ή=Š:HG†SΛTš•’Q4ξ’ښ2.ό+‡GS#Y‹¬;’nc'Μ™5έ―ž˜ε¨ΤM^‚ΜWΈ%ίΌϊ‚¬Ην+ΒŠl6PB'M¨©QΊcp&}Κ-‚Μ‘ζ5qwβΑ'ΗΧε>AΊtΦK΅Ÿά>―`B‡½gοα. €ϋjψ{^Ξ<ɍΕoΝΚNnιΒhŒ«Ψ(rOm¬Ÿσ.tFζΚ2ΪxVτ©C!Μ&>Œ;ΰ‰P|Χ½_£ΦζGν{*υJ•1Έκ\Ξy5•Πp‹BͺΝeŽͺΒÎ@ŽbBkΆξȎ©~@,3ω”!ΘhδθsΟdGΗZ#1bΆƒ•΅ΩžΌύ 9ΒΔΜͺ&:Zηl›"‘α»_$*ψŽx¦FerΌLRIiφŽ|φ$JGλ(Juэό\~ΣcU†ΔΐΘΧ~Jnm·h3γγΪΧ‘…°ΔHk»ύΛΝ:šˆζΓ4―/%/ΚυΰkŠθ(€Θix(!5A=ž™rΩ3ε"…Vε²R|‰SΜDΤP쬘λΗ=χBfRΤ ₯*x½4/h*L¦ΌΉ0…‘aφ£2œjJ+€ΟˆχΤΐl +―•ue5yΪ_Δ\Ι1έ/!ΧΡ1‘UΆZn\―¬ε0ΛαΧu«»£ŒulcŒ‚*ŒEA +Y~HΥwΞ$w‰…$Λ›’{Μ<Κ‘T;Νl#ŽReέ]ͺΓESU\Nα2]ΚX +!TO:vƒ*ΧΨAθ1“J^>|)aΙEυάσBσ7$[wό3H\4X䚍˜›χfψ=LΞu‡‘U67Λ/Kί„ΨδρEƒ°‘3σy?fμ"vVΔLešC’ΗΓ™~σ)ΏήOEŒVζfΦ€„~Ϊ`KlΎˆέ:5Jo%—XθcE9σξ~½gεuμύˆAPτΞ‡°e΅5©ξR–ΪΚŠίΛR‹u'B"ͭȐͺ3O Τ# YάG©7Ί¦c gF ͺ’L#•Tw˜]iˆ™Υ£βQΜ ’άτΕZKΏ{š{‹`ΞbA«1ΘB +°&·a V-Γζύ±"ϋΚKA_Τw΅οΞ"όh―iŠ* ς”§–{›;Γ;ό%ϋ„ j‰ψ°›ύ)»Ck;π„HM1£Ω₯''_Γ#χQ²6κΦ1©ήdΑY£˜dΡ|n•ϊΛmθ1zΔ\6u§#ΐ qlqi=ς@m+o  >Χ4;|@(?oUω΄o[PG™†ΫΣJά΅φ4΅G:š·[>‡καπϋ›mp¨ƒjμιh”.Ydπ.vi0όˆr„•ΉQo*«82_+Η pΡή½ΙδtŠY…ΉB½υ—μq +$eδθZΝ’ε ŠŠΈυΙ)Σ ΪDΠ8š§M6ŽiU12‘ ψͺάΰΰήΚj‘±ϊΊτΑΣV ŸΕ(σC‘X(BuY„΅&6'Šdσώ|p›œόΔ₯Δ¨‘ \*P3ώ]ΚΌf’N-%ƒj@}ήΥ2u’HΡΌ{ƒ²*ΧΖΫ.ϊϋ&R9τϊUtΥ‚‰>cΉSιΨ8₯ͺ/Q{ 2ύ-Id’Ζ—k©ˆ7©;γY¦JυeˆΥγgρ$w¨N­ΆΚ‹"¬6#ΥΌj?κiMŠφž$Ξ.rΓbs-Ϋj°hͺk )X—έΙΤ4MοĞνΓ+ŽlΠ&RE‡˜5Τ°K΄™EۚΕν­‹\κ»ΤT·6φΙΐC₯c πφ–>"@€ΫlχiœΜΈ₯VΏSΤ“}/3H]JΊ_’oJÞ1QSUW}3Ωmœ3vΘHz&Χa‹DnHb_ΜυΞ0'β‰VΘφt\C’β•*v‰Τ«ΎΜV( ?£ΓΪΑr΄Γ!s\uΛu›K•]&~λBόΕ‘KΜυΙ8Αρˆ1‰mw`JxΚqέ¦AΈfI*K¨ΣQ΄«ΞRδšN |…lοlχQ(ej”QO€πXΞi'HNu²(|<5I4¨€pηe©ι5 +rΙ΄οj\ͺΗ›‰„(…~>ž@K£ΒTΥZΠGΧ΄*ωχ vΊJ@M±ŽΉZ딝e΅§†zΉžlf \n8ΧΔhg“–Υ'ƒD–ΣςΡΨδF!άνY'Hw΄–βh¬M΅3ιΦ›Tp±G hz:jExSvS]b°u΄ N‘8V„(«9OGFš’MΦœom³{*Jˆ‡λ―ίI(Λڟ:Α–Ά]a_+°x>3­Ί. vΘ'ΪPέΠRΞ\’―YBή!δ”NώΑBgqAS"`ΘΔAl.4BNσœvŒδŽXIvΆm[뇁yq4ΌILp’q²)U©7lš%Σ‰E=` ¬ύϋ(Ϊo˜3”¨§ΏεάΪ.N¬›νε₯ς)“S%ν ™8ٌhŠ萬€Ήͺ7„0+ζžδoŒ&*)μ§'M*΅€πm^Φ’’ΒZΕ[˜ξƒηd4h“BZΓγ>`ΞzΥ rΥαύ‘θΜnΔΌz΅ζ„Έ_ΘuƒίΗA'UͺΕϊα%νρoΦΛdW―βŠΒ―r†%‘ϊ&Œ3J)"K‰<0H–AβνωΦΪuΝL#„ν»nUjφ^1Ϋ‘!Σ yi-–Ί?mΖ:¦ΌR«^y½qιNFΨΎΧ(2J’"Θx:§ήΐ}ƒCŠ(ΠožΰZ2>Ί•, ωΐκ½7UQG$ž½nibB§xί‹„ŠΈΙΰ”p³4FΉƒš?SλΫΐGTτQΜώD½γtW›1Ν=°g ` ;Τgž@„–bL«6EJ8ΰ ;@~QΘS5#Γͺyυω'6”IP «A~EΒ·[½Φ [ωtΣΠ5FΪ8`NrŸuϊήάΪD—H\τIpz.”aΎ?HMlیm<3©ΞΝύd‘€ +™ EŸrYͺ‹ + -£Ήj― (εDf]HλԌgRNΟ̎#=λe³f,…#¦FθŽ,E‹˜‡²΄€/rKβ)<8M £Ϋh‹φK «3uϊ ΑΓl»…¬lC‘_υR{Ÿ<&”œe4Q3gŽq~!­m_) ηόœ1HΊΰ²\ΘCμΕ{wπ vž"ΈYsΣ<ΕϊΔfΨGol‰γίύs„Τλ~c" ^” I‹³ώ9Y–SX@ε₯YsσφDε]Η ‰ϋ›οoΗωήτθpq—©!P[ΣΟMJα―’E4kŽh5Β’nΔρn•aΤ±P$9ι=f6έ’*ω™iϊσzγ|‘“€αSΦπΒΦέr/χL[]vfϋ£­.qΧξαqτ Ž₯7G<όŒ θgKΊΕ₯7WΣξ~ΈΡ|΄ –wΑφ=-§ά<΄υ Ο•#ΎΖξ:ΞΦΒ9`J‰@ǚ­υΚψNm€‰ Τ¬2i@G2οˆPJ»SίƒBrε<Ή3VΖΤ•Ρκ!πτ}l†ΠεšJŒ£4Ο8?‘LHβ²½g¦`a3»΅4Ξημ1Σ~IΘΪ§P·}ΔλΔρuƒNK&¦ ΚDdI*9­q£³!3Z‘"™vρ90±τƒΌ?TJ Τ7γ†σ(›•―Zόγ ΒWξy76Ȋ„ih’ΰ.‹7DˆxΐͺΎ»‘§ΰnη5n) ¨΅~6EΚΒS©qœ[ž¦B\t'! +BVρt3"ˆ5βž3±2l³¦”½Η7ηφ)%ώγΕ₯g&§J1BVΊΪ_ 9φ3o”‘—Ώ0ŽΌ)mΘ―,αPο 7;5¦+©ϋ2ͺΕ²ΆBbβ ξ~3ŽnαϊΌ~I~qrbWΎ ?%¨sΞƒώυ¬gWΣνζΐš’Ώ_=’y%‚#Ÿ12d¬…f{ w ¨τH– θ±Σξp‡­»sιΦM΅Ί=zθΜ*Ν+3*X YΓYψ˜ Ōh―Qζ3±šN…ΊAš"ΧΓ½γ‹kζ3ž,[Υ€W¬š(+Υh’"ͺ½ς+[dsE=εηRέŠ­ΌyK:.ϋχ@#81u‘„‰™θNuMι=ο͝t‹%b³aδ*ί5δ%€i©³ Ίeμ…N™, [€UK­0wί ˜ϊΜΣpƒΈ‡…4Bs iϊ‡‘’χ™H‘˜todιΙa§Μ²UE‚…ΫυT]{XώχQσΊTΫ²\Ώ=NΕΒwˆlΝ½U šKΓ$fυ΅7dž·ή HV­Ν!ς±uiΫΌΆQΤy>Q]ςF܍VΧžD‘U23sƒΠ‰ήxαs’€"#³cζ8vRh’@ +ν{ΕC¦*ηζŒ™iμ@κ8D¨―]{‹Ξ’'±ΑδVΞ7Ηa/ΉΦ}HU˜’1@['HΣΙΕϊUK)Œν‚Gz9€S£Η‹ržtC3}ΰ¦Ωjb¬YΙ4Ώ;Ή#NPXω¬Gη>ΰ­=VνQo‰\:Gtv‘›Jldm£΅Dy…ΐW 7υ«Η)άΚo‚:?©“ΜCFjFl.‰ρΣ¦νΟΫΊΉO6˜γ35ˆΣΝ!‹\_uΆ|WŽ'ͺjΌUš»W]ΏV°έrωx$Ωύ–kXχWΜδV΅ΊŒ_‘ά;Ώ1!ψG0z ηπΤt™πΧPωLυ-¨½Etb†–§–”Ιγ¬#pΛήaVΌΒΕ8G›r€•Φkbšω8•±Ό‰Ώ¨Pι…tjν@όσ m_ξ€`rιΊΦXqϋjuέBχΚ Y-.ΩK‘χT¨ΰ`ΈΪ=Ή’†½ΐQχʘ°i;Τ’yY«˜9ϋ\~:m/ލj§Ό=…w=ν?f›]u¨¨JΒ! U-ώžCGΤoΖa +z’xΔ'›Ÿy1½ϋβn-™°§`|ΟGWmΪ4j­šΆ’ЎRΊD[ΆͺόΞzΉτlraτ―|ΛqϋΦ6¬rY²!Α2 4BI€Ρ)žsͺάοLΔ6J&šχI»ν.W=—šŒœ{β πl1Λx£{†Ή τ΄-‡tΟQ–Ό@­'Šβh5c ? +„―AρρΫΆVe«Υy ¬–ϺqL΅ΝΈΜ!H`…~Γ;_ΧΚόCώ–ͺ¬Χ +ͺ'ΨύrDκη¨—Όt•Ι?unΔ'Ίa@±˜O3 vήε'ΘΜcpυ]~ γ%―ί Όψσ\ΥŸ—‡pωž.}D iZ‡Κ.d]ίk¦σ°›ΒHώQ­‘]΅·HΒsΏΈΘ~‡‚η j₯ΫΓ~’–v3QξΞ·³Ω‘MD²2έ/τDΣ!0Τΰ¬τ0že7w`΄ΐ4/ί+w[0€ϊ°{cΒ†¦βNΥPšbδHΆCG‡­ŽHhΪΌ{}‘^©v—gΛγqΟ,gυφΠ2ΌΧ_^Ν5ŽβPύ±Cu;F\μτΓ’/LkΨ!ωυψΉ&’†=]έ銈G3χκρδ"Xσ ½Όη~¨ΗB‡ΑBTΣΫƒjΤΘ#rωPλγ"f{˜Ό9Ν;.ΛΊ2ŒΠWΜεE…5‹–βx̚œš—…Ιαf™…ͺ˜΅Ξ%ΏΑ±Έ°ΣOl_+B`$»εͺΏ(ξ-ergκό-ΒΩύ © +Ύ˜Ϊ Γ „tk«u¨,›ρƒŽ©b―&έ/Π#mυΥe7Α+5l~NΒ‚N˜|ƒΛΒΌuT46‹NPήsΥΚiȘ0^νThy©VόŽίs\αΏΗ«ŸdVσ“ŸŠΖkΈ·ω†Hy6$ΛEo47γhN3`/δ hd©aεŽΧ >ΐ-ί=%Η[δ/˘ιΣ=γ°½‡~ύ™fτ Œσ0 PXveC ΕΩρΦQКFDnχeIl*”/r‹Ϊ`PLΫjΘΑœΎξε12ŠχΈ΅‘rE[ξ}’T:/N»²Ο"8;ψφ@R―x}[vπ΅l³šτσΖ£ξbζ‘Β^5u€Αΰ ΤVΖk‚Ά³U?σ‡@«+€₯φΈμ 7ΨF.+-α‘›>Π!Sšψΰp³€4Ό^φ}ΈΚx5Ι΅™\1Νχ₯¬ξ%Νlςϊ²β‡HΪψΦ©,PzŸ±lV oΡΝ·ΰŽ°χΉlδόw½„΅ˆ˜Σω)# T@"{–υFŸ κ*†Wξ{Τx’ΙΓ―e0£¬ΣΊσQJ&HDcΜͺg(gΪ6F”d9{¦3ΨΡt»$££[‘ePΰ8tkσδ*Ώ“v‘σΗ>›έ±c8“'/wŸ>§q!(Έΐεώado‡ΙΚ†ΣΥι•x’ZŽΥu„wEG]Ύ΄ΩŜ–—¦½™Τ“DχCχŸ£»Η_Z·Mξ-lz΄(μ“C£…γjS)ŽYΌ’„V?&ΑEQΣSΤ‚uξν|Ÿ WF£ςΐ#(γ›Μϊίc ±°Ό₯ΨΆ«ΣΡλŽ|^4kθqμπ+4:ω ½Δ „x­”.EWΠπν½8‘†Ώ $ΌιΦΝ­|DAxΦα +nυNdv³¨­o +N Δ@φ˜Š†ήΣHΉΥΐΛH!·‡Ά³Et@|w|₯œaw‰nΊ%9½8R•[αέ=™`foΆΪ hΦπ Ύ^Š6gaΣ=›y8 ŸΆΆΣΗ΅rόštε΄ω\/š`)nνtδϋ:ΨΌAνŒθrΎι5€›XhqΓZΉŽ~ˆ|ανŽO…a$~b?gώ€άg…rHΥg{ ›΄ΗΌb@Άͺ₯pΰα9ε­gΏζΌή;А +‘ΰ`š¬rΕŊθ₯Ÿ•En tέQCδ"ί₯3 €ά5χ<ΣšŽ”fλ]Δ€…Γm\ιΊ›2ΈLQMO’Λ$„Ϋ†φΣ4ξ6I…bδq35œfΆ.―ްΦ’‹©Ά8 Σ+ΗΐL2½p-ʁ6Π‘πΥ,‡²›YΜάI†]‚(9Γ¨ΑWcΠ@ΐ0vŸΔzχΣqΜDΦΊdOΈ2Τ"vLύwƒPη΄A h£]~$βp­Όήz=ύψΉ ‘•τΓv―q€#ΐ¨E"=‘χσωΒ1W­Μ!-~₯Υ/6d=ˆ”+—ƒ'²Žε±fΝ9W `"Ηο8+δκ„ζ».]f=Σ‘¬}sO‘qŠ‘ΞΠ ρœ=0‡5¬Ώλ€A{₯4ϋšNΟQΖ/G”C0V’?ˆ“ς4«S-Y†ωγnM?z`,"ΐ«5ω~Ω²½½huT½ΰJƒeƒueMO«^ω}α!¦wݟ' tΛIs>—4tNR›όJΫ¦οRΟ_AZ‚Y•~Ε„άΥΤsHNΨ³¬οη1₯Jd"ω‰\VSδΌ:ˆ,R U^νN6—ώ¦αIha\Ά#.tΫΛ(b0%›ϊF¬sEΕ—œ„B¨ZρœWš’X§@ΰBƒ ξΫΎbA-°@ώ^εPΧ»ζqδ0;ŠαπPΨvΓδrGρΉτ^Σ+2W«νΤL$ušιKΒ zψˆ ϊΊε4hπ}ψόgτAο;ΠΘ7"+dΛηφ<‡ε ϊξ\Ι‰ZO=uiνZΠ¦=WηDJΉΟaι2vΏ…Μρq.‹&ݏϊΜ‘3±οT‡X6ΪCfœΠc(5•ŠZΤF6sή+Ζk8.―•n(†ίn˜»;Θλκ§1jvͺυθβΗ–hε«.Νη@ζνY5ZΆjΧ Μϋ,€SΪ8¨L₯‘”hh™ž$νΐxΊδΌιJ5|V^K&šλΈφ;ίί€E‘•†|Ϊ\W£‡Ϋαfn%Β’’jηŠ“£t!yžΌ<}S±ΨrLœ…Άι3Χu}³eΪIμζŒ Χ’λ˜Δ{₯ƒ—ͺCΠΡΞά^>0”nΖ… 1w²υΔ ˜γ0ΘnP±-«€9ΔP­R ΩŸKT‹‘ψη1΄κ6φ­ Β0•ΦŒ—>έT]UεςT& Ζf£Dρω(¨' (£+¦JαU)Ή–³φ‡ˆ FΥnjΜfqC!ƒu₯™ΔWAHœ#qE՘ΙUΓfΌδΙΧş―Ύώπρ»χο>ΎιΗο?όςφ' /ώp‚j­_Ύ}υ·ήψο·/Ύωζλwο~ώα―?}όήgΏ|ϋ=Oώ™?όϋ»ΏF/ϊΩ—ωŸςφ]ό7ωOόύ:ΏώρKΜL]e%Ζ„[~8hNΜƒͺ+& dCΧCg₯~¦ΠΣQΈTΡ»ΛͺŽ=φΞ6Lήm,.NkcŸlf@뎧Z§‘³xβŽ'Κxθΐw™WA#Γ,2 UW.›œΐ~RRίιςιK)?·΄9χΠζΈ£ϊ|?dΎΣ>@$Γݝψ&ӎ•‘°Buή²•‚Λ–V‡Ε†si20UQl Λ³ΒΚάzήCϊckšυχέE2Β³jΝ«Ξ^\`ηωžžΏ_Γ΅k„΄χμwδώŒ73ι$s ―‘ ˆμ:Rφz*bρΗJžŸ +πφ Ό’“ώΗz™$W’Aτ*ϋσπIf2-zC2@&†š"Ϋ"Δ":Mή«nχ9Ύ₯υφτ0$V'tφ£EDψ£ΐH½“ΚisΦ3€` +¨¦`ΉoPmοoš-vΓu¨Žš¬φΖιK³χΦ~˜¦ux];qŠ:–Έ©γέ΅ΖUΫΧs +o³H •IΤ’PίlFκ΅sL"cŒ˜ZΞ!"ι’Κ‹Ζl`zΉ όqςAE/ݏ½κΫΫ–O~r`j|žŸλ²‹ ½(ςVjΈځ"Λ£Ξι\ :Ι1 +†sέΛΟ–4χ^*˜"XžηΥy5Κ›#+ΥΎg5ώΉέf§›% ŽΠ}uμd²ΉΉL›Ύ „O­ΊΔυ0Ϋ|«άͺ#l'λΊ‚ΚΖσaPV:ŒΗΏAό}³β,σηΏΩσʁλ χΛβΩδM4sSdιCαίsty -b€’’kΕ9Θ2ι¬τ%¦€½‰«Ξ‘67΄n‘oLK»τ™κ›¦ͺω†žσžΥΙ‚z‹ίΫw,s+,)lI;sΣΝKτ©Φ‘Πe€*₯q‘%ΘΆΰΉΘXβν°j”XǍ₯Q§ %L©ŒΘ6₯~ΆˆΏ˜Σl1 +β€’α€-~¨2LgΪ±³€’{ž3žœg(₯Ε{kΉ‚&4a3ϊφι‡ΒμΞυ?.νχ?X’Nׁ¦η\’ξ½s1Ϊ¨Nσε +YΖ$;/μyvΊΕ ”W±Ηz1`©5ΒNΓ›΅0υ՜ΈΜ¬¦ID4‡"nίt𐰧ς‚κΉ―qƒλ¦έdPΝΜD€<U&Ÿ8_v(aΐ@Υqέγ\g ζ%έΞdνΟΜ°ιBΰΨ‰@{œτ;Ύγ]CjψςŠλ4―LAH’*!Ί‚Τ§£ΘΘ¬_δzVα|‘eŒVc\ΧIΞ‘oULΚCˆ'—²ΏΠl'»€ErΑjz½―ύDf=ΘΏNτœeή8Ι2Ε» χ/—)Џ3ρ’ͺΪ6D"ή”λ₯—ΰeΘsμA«ί\)”φ]Γ™u†‹ΩΫ‹˜gω$Sκ›ΉJT_‰™φΰFFχ"Ξ@ΚχNςΉ–f(9–VδM₯ς’e•k•ZI #>ΩIPΊ7 ΄}«Έ+žγFD±šό5ΚμΛΆ«φ¦+ g3ΟιQ ΄“ον\ΞΌjηκZJΆ%^7jٜv؈½ρ/Τύ€ξkύI”€<Υ0mͺ E;-hX.n+̘lγŠl‹)#%Y|\p;nŽ™Γ̚†…JH +3[' +ηŠHΘ3:…ΣΎ3;γΉ Z TœŒm·jsk ϊœθΤ$ž£Ύ\xφ3™!Τ)h +P'sSŠ1Ε±ε‘ξD…¨ε”t4<u#&Ψ-^Χ½ΰ‰ΐϊ”^ΏQeF.eΕ6zpFΪY{χ~Am ’J23Z.Va 6:Ή‚*%δ$Ώ2κ‘kΠF-ΣZms%›Α‡hΝ»Μk΄ͺ +cι(pjp”ΆQ\lf]ΌΒEξ‘NχΕgœœ;‘/kΩϊ"nžΑw°Ξν(Pl¨4¨Ζ8 +D¬O7kSί λ6”>l„ζΪγ“rP+tΔ7β™Tς)y<’\¦ρδq‹šY"%2Κ‰‰R {lP™ŸFΕγσνφό ς+eέ’{¨τ{ω4ΤΛšͺ!v³lΔ3²ΥΎΗιSγ:χΦ©* zΏωP„¦ψ›*]άu.ΧDͺͺ%Πγƒά:(^Ϋ§œ°d₯](A'σm-£8?Uš€Ok#bZeКΞg›6Uv‘X­NΌ}£tΞ^½f5’ŽνPά•¨ήK!Τ£Ω8ρ^…d”£‹eϊΛn'ΜS| (;γQ ?4nΘ€ξy1#Ϊ½†js―ZΛƒa΄Rπιθύδ ή±L:θ€ΛSθ2ŠξN‹K…TΛidZΔ9]:ΐ£ZMώ±¨ψ`ήΣxΣμέΞ!»εi„"œ½B›TΒΐ΅_Υ™•Q"—°8«b§EN­ βΗΝzbΪ•“.Όνβ#h+c1aεΚ}’k<{–ΈEFχaP‡ͺfΈ$¬}QB3iG’; ̎“ˆ7H»‹&SŠΔ›φμηq*™RδΨή NhΆ‰žΉς>«ςμ;ί)‚i ‰¬ζΚBEz6 ήΐT(}–τ΅© "“[<$•`XΗ]yΞ…εz +βμύ¬3ρk§R“ <@•ΌΉ5΅$ΧMSJΈ‹aΪ§8 ~€ΨΣ%ή7L…Έ ­ŸRΈϊ‡ψ0ϊ=ŒJώ3u―WΣ©#UδUο3;ςΌNΥΌBΣ’.ˆ±³Δh/4¨Ίυ$Β,άG/D@ς•)Π²&Ιfμπ ?ΩΌαε@IΤ ’"ΦJXΞlύ$‘*=·nXϊΦ}†-uφ‡ψΠ£f>£\R”‰jIΟmΆρf™ͺ8Φζ± Œ9b–Δ@ΎΉΎ‹¨(I¨UŸ›[΅ΣΠu~u՚·θ&ο1veAεθl? ‡ω*ΨDcŽˆ!oΑ·->‡΅?Ο;ΘΜ³.X{»Šψ +Ύ>™N}ΰU–Μύg”š~'5&>£˜ςtΗx/6ΡΟΞτ°Ιƒ-²…ŸIϊέv +`Αoϊ]εκή,'LΌA`ββ°ΏΒΈ!‘Ε΄ώK‡‘SQ‚A€eελy~O€R D­61" κώ‚₯Ψο–“Ύ@œ‘ƒ6{£δλΪΞε‡Τe2ΡYϋTςkeΟ£œš%π#UΦ—5°νQ6V$Ε8;3iΰoΦΛmGβŠΒ―ς_ΪQ"ΧωW6D)„bF#ΫD#°ΗΖH~ϋ|kοͺξ”δΑΨΣΛUέuΨ{|ZwJ,|¬vDα_ψ«·Κ$ΊaΞΐ€Ύͺ—3>FɎ“ Υ*ΘX>V3 Ε5mƒtF“]κ +=¨'φͺΡΚώ΅‰4°ΔΛͺ©&š’Jξ΄IiΚG‘ θΝvVXά1­5{U­:E½F$`ΎΘ>† 0Kδkδ +b²[:θ§#2f·qΠP[γ9Ζfu \GOeJΙZO_cuβ=G!"½ΈΕ©­w1¬Η6o—&P!E Όω?Φ»¦u½9Ύ&ujž#O€₯ςΣ +€T§Θ sλ–>Q&v(rμαΠΠ$ΜκgΟJ,6—Μ³ϋΗXΤUYΌY(eKYι©Βe]R]‰‘γO+ίΗ6栈ȦCG0–C·žδ}g7ǁ"-ϊoΡΩΆN”ΫN’]ω0ΤΪΙ—Ÿ5JΪpͺδ)ΈM\ΐνT$€Λšσwb ΨέvρΪ²F{”" wJŽBΞζρ£ŒF^°AΡ‰8Γθ=Ή›Ω(ΆΔρW­Gi«y|/AI* {=γεΔb]r‘!¦άD^ϊβ/όΌxωπψυέ›Η»ϋ·Ÿ/_=ϋ›X6βwž_^όψψpχαί—g―^½|σζΣϋξo5φω富ό’ώΛOόρӟτ²@I%•ώ{ύΩ‘Λλ·ό.Ϟ_^λ,^^ΌΊΏνςμε·ίί>>Ύ{ψpσνο7?ΌϋψξφρέΫ&ψ§ώχΈήή}Έωκώγη›ϋ_nώώφξρ›‡ϋOŸ,n+ψ€}²Υ}m~§?Ήv–ίψΎ_νίβυv:έ ͺ‰—χŽΡ!'Ζ3mήυ\†ˆηJνκY~H—'2•ρι:ι›!sWέVβ)Q·αρQŽPΣ&ŠΘ/#}P¦±ΣU6’¬tκ–SoͺDQΜ@sLΩ’αc +"δώ΅ϋ[J)Ξ_…A%HΨP‘bΚ„Ϊ‡φ IȘ)¦vΚ›φuTvŽfYMP,^ˆΛ`\s$½„@•|fiqΝΔD­_V„f`Ρ +)O4ί»4σδΉΓ‹†φ'ε“Ι.^Ξ5;]ΤΨ…ΠΙy1―pf˜θY`ΚφΑέη žΫ@8΅ζ„šW&ʍεz%ABR) ±cα&¦ΟŒϋxšRŒ(•εœAtxΊ5χ)u_ν{–-Uwκ³C·οXB0J"#¨Pe)ιΙFi„]ΐ»k!€qBΣ€΅ͺΨF@‘―β+uϊ΄Όb„Ί{u‘6Υ7™3 p:‘ΓL›5γŒ/8τΚ荑b³*sΒ΅JδΊp…Μ‘ŽςΚ9`q’63"B Τ«ŽlD€εœ'LΑs έWFNJΒ¬Adη$|<ΟΞI˜½“UhDŸ#_Ι!sθu@5T!€νOΏο† +Ζ’ΈψDοS5Όωšκε!-i–Θ‚„|=£DKοs6ΖV―5cΞϋTτζSoKφ³-N‹$ƒ±”4š½Γσx†I“Ÿτ†δΔ»ΝθΊyŠ ΖδΰΉJΆ)p°X―x +™f;$ž³ΙeΪ³­ΤLΓΚ`UςG·χϊN2I[­Αn΅Œ›ΊΆΗ=·LγνgΨ ‘τ΅mŒSC6˜ƒiDΆt‘R2 ’LIF³¬!™C/e±#˜μU΅΅’¦΄“B©νLj*°ΰΘ™ςρ¬τ%o—R΅θښμHκ.“ϊ΄8ί¦šϋ3¨οMθ α°B“#3yͺ|¦έ{•%Z"€{τQΈ΄i€ŠP變ԏiC2¨wΙ- φωζΌΪΛ|aάκ`-šϋD\¦ιεΠ…gΡPq‚yΒ^[Ύv¦‰ŠH‹l¦­%r^Gπ^³|ˆFX1ρυ$ύ–?•Ϋ# +rP½˜Μ savqNrΩ0ŸθΣΑuζ lZrz,Mμͺ—μτςςwQ—ΝfΝgh4ώeθ¦Xuν0QBξ›r§a&ή§MugF²¦%RΙΑ –H­ι¨’‘Σ2ΌbJp¨z<ΪΪγ€ΉνQž€–sRFAj€"ƒ±·4μ’€Ξc΄όΔmš†hΙ8’$.JΝ¨{=˜‰Ψϋ2l¨<Ρ”XšΉ +?ˆ]#­δ‘b#V<#²‘|=‹ξΡίΐλ©+l}Γ*ΜθχhΑ¨2xΫΤwδβGX2DΧ’αAΉtUρFάΤɊ„q’:KRŒΗ%ΞΰΓΫrœ¦UκΓu"‘x4ΔVqPL[IΟΨηP Tkž£PΐPπ!ED0€ͺ—Φ΄€•Y£α˜&dhžμ™ΨΪΐ>Ν&©IΣ\_Ϊ Β4μ†x‡*Άlr |Ÿ<μΰ δdΛ¨v(‘8kN8›‹4A‡‘CΨμ#a¦…y~£OWεν§hΛ>4GžŠ•ύθ2”βEΛPΥ,ύ”v“λŠrΟp49Κ€Ίd=a―y—μ1θ`Š L§K–Uν0άIVοYƒ€p՝eϋΥε£Λ αWΩΨΑÝΨOjɎ-9sθΐoοϊͺωW«]-%` ιͺήγqέc-έkΚ”»”«NEΡ`Μ½.Ζ +*cuM5ΦxΏVε€Ύ4ξvΟ*Ϋκa!ˆΞŒ―XΗ΄·#<+ˆ|΄ˆnߝͺd©4-³¨!³―W1<ͺΖ}ω©œ5vϊΆml„Ν:ΌΖ%‘ρΩγ^t&Ιhκ5ά­€?Υf +΅c@τΊb’±R9­ε3SΤΏFoυΊzv΅¬’ϊFαN¦:ιΞxY?ƒφ$\¦δiΥ¨_ωψθ‘dSΜΫO–ξ=ϋKΌ»[\’Ζλpmur«ξdρΉqΖͺν­p‰mιΒΙιD:^Ž#‡ž[ O-₯οwekγΩϋ=ž†ΘQ&0Ά'W}«¨uV—˜ςYν‘οΊ:¦+ΣϋΉOΪs K~:V +U³Ρ6ZbžΝ5ΘQ:aJ½ΧZy―μ°_7Qdε€bqάܚΝνδιΕl™¬ΥU±·L·ώ_@ΟR9“Ό J™η""‘©'—α’=΄xπΗԘθU_Zldˆ΅λbΠ α ₯ƒŠΚΓοloά2©ασρΧ7$Υυ1ΟαγνE» ώ"ςΚ2sύ h J^Ϊ#ωπ'΄qγ2³ FEά+ΞYμ€$S—Φbe%XF”Πw₯™Ξϋυύ½e7’ΊoΩΓχ’cKŽι΄­ΥΙΒιθ¨Ά‚ι˜ΕkΈ=@\qΣ흕θΝ”Q ™φI6‘9.ξ’θϋΊΫS(Y―ή\’-—ΚAΒ^ΥθωήyβΚV(k]¨Lό§ό˜[7ό¬j2ͺ_lίΘ&u;•jYϊΗ} +s'nί7Ό¨H^kτSό“I#‰½e’ u[ +ΠιEΚA”ž{žί䑬J‹Τ=+7cP}θ%Θά’ψθς±ξd©^Δ:JquεΊ5Brg6Ρ€c&ϊκEΕཎ¨…‚dΨ'ΟV]7cδψkSjkΎ¨ΌFΗEγξΫΤ^@"«.Τi½K5l₯rΊ†—s'*·W(ΊƒάlΣ‘?ζ©ΔΜΒlώΈΊU’gρMκ©a]v΄@G†€A° ³=4“’ΥN»η§ZΖ6Π έnІ!΅ψζXΎ"Jί_:έ+Εε…e\ Ά­C}ύςΔΉβZΡvΎΦ8šŠrQvάΧ„Sϋ˜₯ό— γ¬Ε;’ ι°θ#¨ΤzΆm™Ό+[©“Sn¨s1Ι7ZT¬ΕΏ¦\u7ΐ*M +:Υ*εζd’-+BΧ†2ΏΓ<τ?°«H;E¨nIRS\NZί>€Ε Ε3\P•=πρ]™Ηh·'¬Λ™1‘‡ψVΗ•ΞΐΔL)Ε*³m•}»­" ƒΔm +φ1 °₯βfΪΐOy™²¦R’μ˜bψIg.}. žϋcS…ΑeΩw/hbί†φX)xω‚2`ψ/vM=ΩδEθ ΎΆŸλ‰Vε‚?c+1i”έͺ|]>1Cώ:–ΫΗP Θυγ& ͺζ ΄5ηΆ"ύβLW¬ͺH]δHπƒ|η‰–’5q‘$ΛE7D+rP₯€4δqΊT³n%Dθš2j& Q4ιλf’γιŒΓι +ζζ”„°ku»—"^¦s³:Ύ²`D-\φwj£†ΨηλOΟ›?JξΓ₯< δ\Υz…l-Τ''+N{‘hΣ±[g ΞzB3‡:£Q„ŸΦχΒΒ6R…,cyPM”AΣ™ΰϊAWv‹>stream +H‰¬—ΝξΌ7 …―€{˜ R+ΑΫ$ŽVύΈΈTUuΡVͺώTβξyŽ“ω΅ˆm£™xβΔρΗρρή―ν ίγΥη~ΜG?’–συέώψφτφκ>Ÿ–Ύ^}ΩΆ€ΠŸ;‘μ§5νλ6¦€³{I{χQϋ8f—€Yφ’Œ΅ΔΗϊΠ΄ΤΑ’FΜΪ7sΖ9+’ήΈ©ά«_Ε|Ζ@ΟΦ“Σ€;f=nΊιΖ†­ «œ³{Ύ–βžμλQ ™˜ώŒΩxnψ“#Χk΄ρτL9`=Ύ†•ζhρΨΨ²¨?8ϊĝ-k½2ΙzΆηqS4σ2UμνΌd΅}Άm³l%Xιηρ)χΎυV[~€›‡Hβϋž”{YIζŽ}$£ηΥ΄‡ΞγE±Ή²ω³Ϋ’SχJ ύΚ…lOšΏMe±Ω•ςatΞχΗή’Kf„ŒΟΗ‡σΨΦ³kκ<€­=sφωÞ‘£p~%h[ΞωxΗG±cΖ΄2cΈNγAίΔ°rσ,›ΘAσu δΝ*ακΎk׈»mν%!ζQŒΩ?—ΤsŸ0Ά‘G2Χ:šM^“Δ"Ζ‡f–«±lmWΨβq—SwΌοΜ'Ί*gγο΄χ#η³»Άρ’ε\iX΄τκ½τŠfΌΆŸdάΓο•ΔQήlhZ?šΌχx₯9ε!Ώ―”1ƒΏ­γΝ’ ²έwΠ-et―²R€,)ΕqΨ>9NΦ°f'©–ΤU[ + +ϊϊ₯φeέWIgΛQ†g›ϋHΜβ<₯u;š2’ +iδΪχ}ӏίώόŸΧ_}φηN½/ͺεσΧτσχ?ώλυΩW_}ωέwώαo?}ϊV{?ύ‰ε#^ί\¬²Ž©κύϊαβΨZL„|uκl¦½°,Ί)‚KnŠ—`=Ξƒ$#ul»Χ¦l`˜€/e ΐύ‘&XˆARW΄)΄¦_¬Zo›^‚Φoς +D•δΈxD_“m{Ύ|«­°ΘΧ.k&mθd 8|Η«|n~šΠ€EpΞΚHpΚ,ΑGΖΨΛG +Gb0yH`}ψP€P75‚£.‘wS‚<·…νuΤΈW‚E΄οm`Ψ$όQΆξ³Υ§{hŸ˜}ύ;¦}j₯\#H’™UB ŒEώIH—$ZŽsž]ŒD0·:0 ήμ6R ρO »ž"A”mQ0§sB₯‘^ΰ—,¬A«€ΥNJ½ΤΆ·jwPTυLεm@η•΄@ΞΨ4uΪrύΥF¬ƒ ‹ ½Σ$Ϊ»I(f΅p™>X5Ϊ¬€vž\ρZέμC«ϊ|ͺUŒJ(ΊήΩEC™X/ΰGo(9/”œ‡Α:ΒFνŠΕ ΛT‘m‘=6&ν1ΗͺDπΕvΤΧPo ŒNέ)^€·ί©Djq­€ŒέĜš2Qc_Ίs‚΅Γ;ͺ’J³«7©c‹ΰ€n ₯g1¦%ΰε―&7Γ~M^π3ΓOύQ]»lλ‡’La·ϊΈό_LfίF&ιΘ<ΩΝ VϋϊύrY+ΙπΓZvΫΏj6qŸβ)JΩ’δEεI€ά ‚’£7Đδl5g„Ηv±qΥ5Žπ:š-{ΆϋΘ.)€ζΈ,:G‰(ςD¬Τ£•Ί‘Πργz]ζ8ρs ρ°μxƒψ›΄H/"γvκΞβΞ‚]\2ΕP< +~ιA+VΑO\@(’œΰΆ©X§―³ΠyqE·¬—ΥdΑl#ΕSšZι½”uψf +Φυh0χ@+&ΐF=&cΊMΤ‘‹h‚αΊΜΫ„ΰͺ}9‘„YB Z ϊ΄.žμτ(Σ―qšˆο)χˆQi-Κ+V1Ε‰€δ—)cΰIα ’˜θ©RΫuχίοΩlgžΆ€νοͺPt*Δ°R3­ŸiήΑ'ό=EI¦ΨΫMYΙ&Ηh+,_{ά‰ρ,R9΅&}Οϊ‹SLiΩ=·šΞV΄NhJέ3φyφ„©)k4sΪ,λœδx»ψŸI°Sη›ή;[`½ωΉZMΟ‘·ΠLΪχKe|OΛ5it2¬²yhKˆ j­‰DkF¦ZG|¨0ην’)?Xo'Τ:6w]³aφ΅o°†Ϊ9Ncš«ξM‰ι4jNm:]£œ¦Δ5ΗŽ1Φ06WΟ… +Ϋ’A E²¨δ ~]—Swެs΅nΑΑP5΅b3t°SΎΕfšoZΒ%5%ˆ( ΅ΰτ5œQήsT#€f@^9~EΎΡD&έyΟκ…ΘΨJX˜{@hρEtˆ@ΨΕgPž\wFΣ_B’f^U`Eƒ4IwΝH’(V%±uˆέ˜qΞΪ$Ϊ‡f¨ͺH‹²^.«zW~•(™Υύb$y˜QB  +A‚0ΆAΘΏ}Φ·vuG „ „­#υ:UέuΩ{]<†‚hΏΣH«ώ¦*‘Έπώˆ^£lu°ΑŠβ2t‡”£σή܈D· ©{ββ@)ΠYjˆsUψ3‚oΠ9υζΌη™D7j2Ϊ΅vΌΘV»'F—&vιΣ—Ϋε .H[tΘ:6†3SΞ-V1Qw?ΩrpΑ­ˆ ί₯ύ¨ŸΞ ήfo;ψ³XνΆ™·<ŽPΘZΎTέΘΉՊ€kΈΞ£—–}xj+Κσ8φ*]΅*Y–°TfΕ'Ϋv©Ϋφ½˜ξWγκκM«Ίευ˜tPki¨Υr₯ύΑ»jΆ„mΌ g9ΆΈΛθMτ[^©Ύ‰ρ’NHT³Π8!₯ӐBf17Δυ tΨ5³ν33”JΘΘϋ ™ΣΉήn*‹™δν&ΉR‹ˆ Š—ΘΫ«d<*7ΊŽgσ2ƒσ°ψΈBΡΏk:Α¨•u―«SχςΓΆ#ž‡Bή*:˜ͺyΛIΪb“Κτ¦z8DΏQb­ηƒ Σ7Oά”Ϋ+©D^aœm‚^£;Jα<–RoμP;›n7Ω {>ocΏaη“ϊ£—χΛ  n;‘Ξ* ΛΒΕ8¨XlΫΑRΗΜΖ=*Aε B Υi‹™D29ufͺρšZv;j‹¦Jχ₯?ϊ›BŒdqΙ›I;Xb3΄¦/‹<)ΔΦ.€Δ’σsf3Ι”pΥY?Νύ"€±Ϋ₯žˆA9ΚΈΧρ%2°]―ρ…¬ŠVΡ)ΙΤc΄\¬ C0Žˆo šωΆŠ&Δ}.ƒofϋΦά{xΆΈIκDΖ()\^‡[δ$ +§λ<·-Xi]^fΐM Ά ‹ΓA—ζι \­Ft 74}³”QšΦΧ©{Xψ~έΎ^”Γ~OΜ .yΊυx“DnBn)mζ»Μ7–UΩšbξΆς¨KΗh[τXϊΙFμ[Μ ‘#€ΤΫ±%]Ύ’##ύΤͺm·5…6†Π§©4ΒΌ„ΰt!kωώPε FAΙε+gγ'tνeαqδΙB™PY―V/D&›#2T&3)‡_AΑC'\3E@Œ9›˜Θρι|Doκ’7+AζqοQΏ©iώL/ν™(’iψ¬D·πuΆ ϊΫFΞΆk€Φ]o?hΑι²΅–yWNk‘Πτ’κΡ₯6κΦ“3&+κh^Š–™Ψ]lmρχc5T―Θ·,Ψ{ΉΥ‡ S%#\T…iιlDM”(|ΖΖΫgD<«Τθ²j’0θόϋ +₯ +μX5Ϋ9/."t0Td«3Ρw|AlBc/IXΣ†έτ:‡¦~ΐ,Λz.νϊσζνη/?~ϊψεΣ―Ώ|ψόϋγ{A―ώ˜‰@ˆΧ7ωςωΣ/|ΌzχξνǏΏύόη_Ώ|`μλΗ4ςύΡίύυ½θ7^ƏτψΡ?㿟όο|žώφ»©FbW`$T^ψω Ρ+Š8.7δgzξ‡Κ<υyΣq±­Œ|1—2]~ΑΊ…eαμϋ3u©τj1Ϋ£jK2R#fHύpίE@$^9 θd*ν˜6πϋJIr,>[τ+7W19³Cς"ڌn“Xʜ‡Jx‡sNΛΝ^‘ΩΎ""(Ύ»ΫC΄˜F>‘§₯f€3ͺ!‘ΘιΠΣ2PmΥθ>@f‘ ³Ζ•γόξηΎ,ξ뀕Š‘[QΌ“™σzEŒ8Ϋ!3PƒΟδΜΥ 7TδΕλΦe+d4œό |uμ(ZU_DΗ΅Idtδ>vVΕΖσόώά• ½εΧlYnj·‘ύ—G Tύ±sŒΣ)aοεηŠP>SSƒΖ«E$i’ΦΓ³ΊώΜ’δvm^M(ΎΑŽΥ€I6‚[ρ{Ί€ £9tƒ¬π•RxVΗΘG›%Ϊ²ͺΝΗνΑόtoε§Rœ¨Θ‘ηTT‘ξ%ρlξΖ8†iϋ}Θ.˜ŸρύM[§ΡUB¨?”Μ!qtPz)‘{Γα|wχ¨ΞA °ΊyZZlMΤ’κ=Me“ ΒιzΞΣ†C]βD£+Yb:μ3KΕ΅2=€}σKY>ύc?*$Uēƍ–γ!p[δ+1‰˜!LΕmΛiΘ·D²P­ ΈVRsΆˆ«ιΞΆ-FA0ru²ωTΟͺrIgVΓΚsΪa©ΣςL~VΓPΣΐ“–:FaϋθΓτ`ς1¦ycοΏeQTUoη.Τ•Žs=Ί-M£Ν₯Ι8ί¬‰P5’”@φ<»©nΉetΊ „ΤΪϊAμšBfΐQ1S…(f4zξpΘ5α([HΆ}Βυ‰„ώκβκOm•f“)+o{<[΅ΟθσN =ά¨ϊ­{œ« Δ΄Rẃ¬ύœφίΒύ;p’Ξ'ρ€―xΧP콓Š«”ΌX!Μ*\½L‚Jk:‚(»΄ώz΅Ϋ&Wξ0Ο­σbgGv©ψ“ “\|ύ{0]ήψWύ—•`žˆͺωxηŒόrPΦ)N―2ΣϋΉΩrOύ8;<  +Ά δΕΫ‘T€AAΘ‰šι€ΖϊδH£ς₯Fe>jŒΓ»€¨"‹‘X{'·”zΟ\₯ͺ―ΔLϋn#£{ /g ελ6Ϊ9{–†˜–$U[œ {Jε+RΈ VˆOvς>βXΕY bΌ¨ΧJΐZ/Ζ)2-ά(M0Σ~ˆΛ&όι…v{&’ {€ΧžΩξΤΝwŸ΅ΌδJύΔ―.A‘/ΝH#η£fΠ?Φ‹#’ΩΫR< ν™hϊŒΣHΎΗθ6’OK˜kaΆˆπVΒθΉmžI\Cr]qθ“Y‘ͺwJW|qˆΠŒΦ0a@υ•Φ«3Ξ=WΆΕKs>ρ TQνΌΧjG R±­1&ΕΫ›šπΜ\"Y,ΏφΩ»\«Že»ΫF>Όυ5W~KyΦq §L“x§™ˆAd™lβ:ΗT–sHɈΧΩ¨εК@ο‘›¦aI6§„, °Ώ>ΣnZΖk1k*WDΫΓo ₯Tω¨΅B*’ξA}Τ}νZ³D}’k³˜ΕεœJŽhH½ ε•υwΒ|•‘³ϋκs½ΦςΗT™lΫ Υ0Ήgνί¬—ΫŽ^G…_εΏtΰ>ΰΚNŠ%Š0W#ΛvρXfΙoΟ·ͺΊ{‘ΰ%3Ι^Σ½wͺΦ‘ΐ,Lvdww8£σΕ\mTυYQ΄―MU‰€}j)eO•D£χuϊΩ"ͺJ[AιιŽώ  ˆΦ3:ΐ« ’Θ“:œΡόI-ήϊώϋ¬ξWŽ$ž•€ΤαθF’¬¨Ή&uT:œΫ -έ+mf^>T±œg}¦™£¬-sjΈΈι@³Ν”uΉ‚ξ³§k‰rMRΒ9oXq1q2Xt]x%>ι€Μβ€(ςn‚»Π 3IΆXq°Ί1΅uˆQ`λ»ό–P.fE\”ۍΕPΎ±ΪD˜Œ—@N3Ϋ «ΕΆD*FpŠq«Kxu0αWΰγυ-όPv@Ί`‡oΣT€kΪlΩk'B²£ΙWJZιQj«iϊμ$9sΚ1Ϊ~–QΧ»§Ϊν Θ4Ό’.²Ϊώ”υrς6.Γ2^ΖΨeΗςέ(Τ₯-΅gv9Ο"ΛΌΎ΅1,#nΊι͊y1-G©¦¦΅l`_t²mTφ~± ε7 §;D²W³8*ΛF)ΑΟ;,IΫΉΒIι(ΓTmΐΦ)!½•(G™‹δεˆ¨9ΟΥ5y l+ζxΔΞ²πφŒ_€·KŒu’€5G•%ίΕ>`©.Ύμ«έΊgΛΘbmoM‚*ξΤJκXWd@*›¨ςΠ6(SeœΜId„γ θ!>ν€κ)…7i#ΈMδ[­ΛΗa)ZΫ"1V,¬`˜¨²™Μ/”Τƞ6ζ˜nOd—“Δ)rk5ΪΊ4e4.εHΦ‚ #W.*†\τΈ³Δ)>«ˆ¨ιgΣ―Ά*Ξvϋψ“εPl₯«U»ΌάŒˆ‹+`wΧ΅w@ +Ί5n‘ίω/ +–͍›ο‡ΣΧ‹Γ,Χ5]ΫFQWΑQUf‘φ χν}‘IfO~‡¦ fs"ηΪti)JΙ²Ρϋ€†ά”₯ΉβYDŒ‹ΖeΤIέΙ“=ψ­šΫaσ­,(?e~"ͺhŸΙqόΌΜ’wΛF—ΑχݘΨΤς¦]EY½‘ΧΒ Ν•x’ΆΞ«‡ια }¦9­™Qz-ΔΟSά3›ο](gf½³S/iTÈŸ7Œ+‚Eͺ}­TνaώQ:ƒίŸ~Z±ETͺŠ9υ;ίZ«Ί9Ιε[€”…i“λTδ² +©γšθΝX¦2Μ=’%”6±κ@R_-δ™‹‚εt«’Κ$ΡW‰Wλύ`™2ik‡yέER¦L6ˆΗ5Ψ7+]Hΰ…„Φ“lΪδ ζΙ'ΕϋΎkuRθVψ?θΐ•1LOΤ]ώr±OΆ φ}·ͺρ#i–eΩy63b£BaxΫζ_ŒžΝι€²/A„Ž—ρWε·•-JZάΞ ζες΅ξZi'LlθΚ06œ bΡm¨ϋλάΤ:ΐa%«*J¨q:%€z9Σδ»D7Me€Y‘ͺρς}z―<₯¬ϊqO’’¦QT­—/*ί;Tΰ–3ƒ+…ό£–γ©rŠ$ΪRLΚ€V™ΛJ‡σl“Πδ„(œ14΅­Lΰ2»3΅3ΧQ*³‹²M1TœυΰξΒi;ΠVγcμΤΞ(PηͺΝωΕzTžΗsŒ2ΚΙͺqΧ"δΉ<†ΆΡU°”eΈΫZ˜ζΔiƒ΄μKΓ-œμ…ώžΦΡ—E|štN@΄1Ό?ϋsJφ!Š€/YwͺwΝ±Ύj<δ<“[1O΄ρ-ς=Úκ@έ‚σƒΨΤ]ό €λ£ŠΝ_O7ΘΨΟe³!&=Ž]ϊά0F§ΠΎZ!+ΙZ—”•έ’Ί}KyeXΥθdhρΖ¨,ξN-a‘až Pm ]qFͺ³κŽΘΉ)§LΤ™XΧc†LgΠγe5Γ3$d^¬—vf9d…έl£†Δ QαΐΜζΨθ„|n‰Z”gЧ5¬Φs?Ο›Πwι;”ŠfStΪΊHN~+JΆΰ7†~ ΓEswωi½kφyžΞgή ΜΥ­eΜ:aΤ,+'F‡ΟZε”6pΚͺpύgTU2’;ε3’Xί42εh»¨kg€S-+0ڎ²m΅ΜΰΟCυ @ΰκg–₯Δ$ί,U¬RE?‘$―+ ͺ쨧%ω4Ωcͺ8« ‡IΟsΏbƒΕ\`Τc–λ8p§*N§Ν8ΰgΘuŒq Άf΅E&Ìj“Ή.iφ₯ΊDϊΖςΖΗEx­™(Ξ̘Ι"ΙψΫ΄œYjQ‡LλGά5ΜΠt&΄Μΐ?9Π½xe"|F!ΟurOt +R–/€ε xΡ§ΖhH7ξU ¨έpyNφ―Α€,1qzλc‰φ‘f›Ε© JSΩ”θη@oΆ³’{=ӚNIΫ­œ£½‡γٜ‘}+ӟfŠ|‡h,hΣ</ŠμZσμφš @B­ν5nc˜DUžbS–Bό%ΰ.i8νn.Δ…Ρ7πqκS·ΨέΗ°ΫΌ_Zχt"PήόOλ]bUΡ›λC(Ε¬Κ‹ηYΗΠςε %¦μEΜ€tc¦€Ϊ Q}&rΐUΜ[8:š’ždμ²G$–šΛζΥϋ6~ƒTa5OOMQ¨–)*-ng@y—T—Χ A₯ͺ οbΗΈzB―©YΎΙω²b9TΑoa4Kΰ fζŽφ©)א¨hΑœόrc»JiEJ>°ƒ˜άxž-MwD RJ άΓισ±ύ#±gdίυE •rΐlX‰‹gc_ΤIΥBDΖ%οα’DΤ—ΜθέT…Μ—1–’TINώfΕTϊ؏ό<ριι›χožή?~xύιΛνw@Ο~ƒlΜfϊκφόϋ§Oο?όγφμεΛoή|ώι»Η§ΧϋΥν׌ό=?όϋ«ψυΓιeα¦ΰK™θŸW_Ή½z«°{{φΥνΥίώχΗβνωΛΗΗݞ½ψφ――Ÿžή}ϊππνΏΎ{χρέλ§wo˜ΰŸϊογώόϊύ‡‡―?~yxόραoί?ύρΣγ珿Xά<+ψ¬}ΆΥ}cΏ’ί\8Λoό ίΖ?νoρ~;EΤΥΔpΫν§…w¨KNB°\ӐŠ[5„ς* RXš§QΔQ,ΑΘΦ›θ#š˜%9A6ΠoΏΠΞ!¨Δ ˜€‹ΎR¦#ςpȐ4Χ<ήV}%Ϋλ‘CT$@B:jδH)NE ›i*Ι/σͺDQ§© Ω¦ε>ރς¦pΌΌƒ’FΥ–†ά°δ^%» S’ΐsW i9ͺSiMM„‘θlT_.ΣΞxNΙN?'ρʐαQ+γ,1 Σ²KB;NάQ—–|Φ:P±ς†τϊ™XΣπ‰Ν…]:R»!έρW•μοZΆυA†qΦ ΐLΪϋq•MήFn#…Aδ}?Ϋ,hΞJbQμ1e‘«4B’VΧΌ -΄ ΅ίΦ‚Έη„’T‘HUZhΰ€Κtξg«²σΦΊ»’nV˜U’Θ"Κ½& +΅m©<£ν$Ϋ~†—₯8Ί9VΚ­VCG+Ι½Bˆ΄@HεΆuέkƒϋuCΑsbnE€μ«­Α֐*/0Φ̍f•m­σΐ»`β!eΙQΐ„\ˆŸ,Χhύkœ‘Ί1=Β^ΐύl~ΦKΩ½¨¨’ +―ςͺ:@‚!Vα0~Z JHVšfBΕΣrϋΩV«ΕΪD€·&ί:¨ ”Ϋ’Ξ} Ug +υ[I‘-€τjΛn>3Δ΄*ζΟ†RCe/Σ§ψeP$ΡHΥymΉ*ir rL/¦|ΈTΜ”){"$6α΄΄ωIΏW +³™Ό-μZτ―Z~ΨτŒI’•;λΘRAί³Φγ9Ι’w—φ›ΰ1?Χ“s„Φ©Οά΄5NΝϊA’ΠπEΫ-fζ[<[„–TœίέgώUώ>WέfΗŒSE#ι£Ϊ»Jτΰn4N·%dΩkbΏ [΄r6ˆ‹΄χΓ κqΒ‰ŒΖΌz +~£IΧ$ΈCaͺλΉ PφRžQXπ;½ˆ'ΒrΤπ;NO₯³‚β\žY )Ξ3ΡW'ŒMJΈ ££vB|EBLΟΌ1ξz:ŒΡaZEQ W#›?©5–špd7[–F۝κ-"uYM^ κuQ΅½c4’Θa6'ξ[Œf‘ΝΡ“ΔƒP8Δ)ΟEe9–>uZ±"z—'hΦH˜?θ²CεοΈ‚›•·$ˆςΝ²>νZŽxQϊ(wΝ„L©©Ϋ`•¦8+΅δ@rΡ#Χа†AΧ°(».κΟ·d•FΝ\sΙ#Žι™#π&½‘ͺΤΨpG°ήπΈ1($tνMHY<ΤMγL,M2£¦B¦΅mƒMnwވ]°[R¨QLNŒI’'Δ] 2Σ + ”ωL\V_-'kΔμPτΎκA€³Εn6Ÿ†ΙΩΥuδΑg^ΌiΉ3’ψ‘ύdΚ+—Λ5DzEν™`1q(μΟ‹‡Δ²?\:EG΄B.,Υε ΤαBν-‘9—ƒκqw;e]!"˜nsSƒ‘mocζΈΤj'γy!m@'^[†Ϋtœκ>σP–-ζ³γ*a;6ΝυiχΠwΑͺσ€²τKyΔ9o|›~³T@Λr™©·zm…Ϋj9ŸΔL…:Π₯Ξ2R”x„l>eΔή„·w¦[’V—†Pς+ξg©A€3R[ΦΘ‡\²%L»‡›§9 Κ"uΪΛάCΨe3΅ή¨Ž]Ι$-’‚«.β™ΰφ—Π˜Τ8€zšžΡ·ΩM‘a|·z>Χ–ΦγΥ,ωο£Θπ9ιΚGsΝρ7«κ™λgΥpDΔΛΒΔc*-+λ)l PΤ–ΜtlR†6-ΛEqS'*²fβ™IΫuΗ‘Β©U5†ˆc}‘¬ ZξΜ‹*bέΗψ ₯©ybQ₯‘Ή•κΟ8ΘSΆ\o}σZA f“V΅χ{›BC19„ΝηnΑρ5#8Γb€τη6Q )šQWV¦±―tg$8_k©­c&·YΦi4Βγxω«…ΌnΏLχΚh/3%‘υ;Ll;CŽ»ύ"πPΞχ“εKh…Κ‘G‘ΠI¦4m’5 ρA<³HžΣwœ"£ΎΠτ?„™ξ1΄ŠΑΰi}aΛμΉα‘ΈOϋΊΘmξUͺhmE‹φΟO©pΩΗή.^OΗ2dΙ \![.$N VFcδyοΜ5R<’σΌ6Ε›ΕˆmvŒYΜ~?ΙӐ:ΙΡΖ)«dͺfΙ‘¦8PφΏό ρ’W$ΩΟΈ— q@©j‹Yd’RΫ£…SλQl\ΆW΅λ›“φ22δžbf‹ΧdΤ C,#sWMΆšίϋ‘ƒU^x†φΌI? ±sτΪ±«Ύ.ψ9‡/R}ΈiΖA₯φE™H‰«ϊ"œ;ΧΛx/Κτή-ΉΚ܏ι;—€ζ„…Έΐs#ƒΟ?£`‘*Τςφ,½ςŠ,΄lΚ)Α΄Ξs‘’ΓΕΆ±a€” z$…N/Ηβq«+Β_εA•£’ΕEώw¦ϊAΎLπ’UžAq%jα5Y-ΚΓ‚ΦE[HMx<ͺε4Wθα•8³ΈsŽ— Η>ƚχSn΄”ΧΛΆψπ–$ΏΙι€–ΰέe:oιŽͺΠγτf(­Œ*ΦT‚¬G³ŽŸέ‡N B/X$ΞΫ +€]„;Ί3Ω,_δzR₯BF\ !’¬8’;f[[ς ΪJ•IGpΒή“SZΥΕ‘iέ/Κυϊ‹ΌΩκXPD΄rͺOΦΪΝbΖ’ίΆjPIDΟa2δRΤdB¦|۝Φ +t΅3ύ’„μYΥΫ’F»Ω Ύh=ΘΩ~₯IgGΝn•”ψS#ύU‡Q½ 1Γο“jΚ”Σ¦UVƒ=›Tu7ΎΩ[xΫΒώU؈¨ςΐuΊŽ‘Ύπƒσa«λΘΓ¦Oσύ&έ²ε° 5fς.ϋVhΕΊΥ9žίsΕ’€ΦŽί…Έ–Ρ\΅[Ϊh8#ΞbN'y&]φΜμψα³^φ0«*κKrΰͺ΅€₯Όσ(Κ J‘ΥΈ}^΅Fo†\+»η²ρϋ1SεƒΦfλ/dε­L†²OΦR{ΖZ₯½²ϊ.£fΞγ|GZΫ>V@Ξω9£y½lܜ[ύ,΅Žεxh³iO1퀝EwΔN[Ύ“wή°IΨΧH§ˆπ…φ—ρ5t;ξ‹βύ­Δε1)°9Γo*ΑΝ*W·Δv4bλju½‹DQ”$ϊq*ž-Τέ•'>’€%žrΕW₯·ΦΗρ„ΕζИ"Έ©.~Οω–κ°*€&ϊΥHο1³ΉTBryg?j½qΎΠΙ―FΠφ…ΨzCΝ[^θΞ [ΝΦΒO±ΩeV%<“.c™™.νv‰ϊ'š{z­"{χ".Πρ/ΊŽlΔά»P‘†έ’CϋΏHhۊPϊŽ#d’ Ε§ͺμ…Ϊ'W²hγƒ˜ΠέP™κΘΌ2EŽM½θ₯΄“>_κ @Γρ*ΆΉ¨Ÿz Ή­Iδ~<ŠΠ₯‹3šJŒ£GΟ8_₯<Šχοyxƒ…MϊBΦLτ€γΗLΉ#kߎ?ρɊ +¨ˆΞi¨Qo”"κ$&ΈhΛΘČ7Q‘/^A“…m?Θί‡X ηόŒC‡\{•'[ΪΔƒΐl—ͺ^L1°ˆD‹&B.²ˆC\‚¬,ω OΧ5L;+jπ ½~6a*Π―qœ+YMΉ±:±8Χ”9„¬žιβG!VŒ;Σ/‡…K2δΞ„δΤγ›sϋάrγeΦ3;T%“Ψ&λHΕc₯4ΎWβ}ΕΜ‹’J0§ϊB ±«Š0ΓΒƒyΣσy¨m‹Ž{Η*^D9—ώ!―A/\&‘qηky”β&RˆhCάΎ HλW€|Œ€₯rΨΏͺ―τ;±Šb5m-©ƒμ„%}φ-‰Ζ~ν΄ξvφ •Œe%¬Ο/ubΰΟΜ‹ς¦’fΠ‘Θkζw}³mw +₯Ό¬žδw)ΑœbX”«τ m#rO:γYΊf:Ή•Dk‰’„ΨŒω˜½FuμΔb³θː‡MΕ›wεW¬υA”κ™xAUvjdWRw1ͺ•3ε ±Χώ/λ岫IqαW9K°lSχ‹½β²τΚ–%{‰FΒb1 ‘‰·w|‘Ω b‹ΠΜpβtuWeeΖε“η4-*Ÿί_VΑJΞl'νΚ•πU +uBz‘Θχ‰—‡ξJθμ +τΧ{‰(ϋ "ΓΘHΌ¬©QMB·FλΘΆ ]"Ρ=w¦ζz!nWόδΗηbοmΰDΕΙ‘‡FB$T]+Ν©&νaŽV‹OkωξY!ΔU + m¦ψŒ…Μ + Kίπ ΎΈŠ•§z]†4-τδ΄&Ζ5•+k¨ΰκV^LaS~Ϋ"ͺ·ςœ^ά₯ΘB5’Eγ-Ž‘~ΠΉ‹ZMqκXŽψ9‡φή“ZγESΎΰ,Μ6καqZ9‘όxΖόC|]ωwϊσ§σΧΟϊ«Ό}£Ώλ›Όμ[ρω5οHΈ;RΨ):ΠϋDeΆ·QI_‹ΈΩΓn[HŸ0Θ+hT*jΐ—KaΕ~+ό<.L¦ψf\Ѝ(+Ž₯u'ƒTq˜ THƒΓv΄ύZΩ‘U£τ#Θpτγ]kPgίψδΩ5Š/e€΅5n³³g‹zT$I]―!΄GΞθΩf"ςΪZ w‰j$‡U³Σ±!β )]i…iB€₯'±RϊΣc:Ρύ7Ωg•Σ’)ΡΉbζ‰[x +΄­τ™'Δξƒnω-?vj#"`ίy ˆjϊkT£ϋ1ϋΡ@4d Γ¬j8Ω‰Ύ›|zdςΥjα eT]ό¦Ρ%ςr·gΫMXΔ6`₯ͺΟAƒS-Ν€½Ž.UγO[Ψ›g«2v׏ΙJυ˜ΊoιϊϊβšbωZ:EM+Σ¨}θΨGσr€~0I*JKΓœdΡσΨλ –έ +‘~NqaBv©[sη•+}%h±R +χΔM–n ¦Oν+ΛΊˆ=Ϋk₯λΞή<[sb§©²Δ7υ?αH±―·?ηWi2„ΦpPS%–«αδ#~%“ξ +§Νό¦·A[9—η4[0ŸΘ@3 JύβηΘΌR P*FΙLdDφΜHΈ V² o­έt]~τB*q&3²πBΚ½YȎ©‘ύv‰+e—jBΚΨ^hΩ₯§ΜψρIJώΟΣ*νT‹J–ΥQŠ\h^R‘,·Ÿ—κ’ £ +£š1xΝPΡίrρCγ_|ωΣ‡oΎχαϋψφ§_ίώ&θ³ΏhώUΫΦ>ϋβ_~ϊώ‡½}φΥW_Ύ{χσϋώψα[žύόνΟzςοϊcκ&Ή2⨈¨v΅Ξϋδ³­ΜτΦπ ΎSu3ή‚Ϊ œηΤZ—l/ βAn©#*›aDΎΕH‹ΏVš$Ώƒ12ω€-Α0rzŠzyu²—t|S½Ϋͺά”ZΏ[GεαYh}ξΫ’¦δlύ`#ΰθ3”i zΧ΄¨ΘΎŠJβν―@\3Pσ‹n]\½t<­‘VqWφ kΗλfe`n~rυ³c! dΧ‘•EΪ•#}ιK(“ œ4HбϊΫο―ςλ?°]°ΈςσΧΎιVΡςϋDφ@%£5¬°wΚs2DZBpoϋI +7¬+hεDFd%Œ4ˆΠοZΉ²ΟΗR`™/HžN’8€;ΑŸ₯&΅¦ϊ³Y0Ή‘ƒΓ•&»d"Ϋ>3νε‹zΑ!(εΠjQΕ€ƒ”‹Ž +Qϋ_#Α! Λ[ϊΗ•6BϋΕ©σ…ψ9τG?Λυ@nVgΒ*%Xν9#d«ηδΧ― {‰=γΰΗyΞΈ2=Œžqq>¨`’—£υ§sΌ¦ρN5ς³cρxƒA'Θ=”)Γ¬xΨb₯N`‰aόρ„#ΰ„§&κ!―°$aWž]ΌΖγ κξΗ{‹pu\7‡ϋ9©|€νή°1~/H=ΤΧ~ˆΝ‚$pΚωΈ°μšή†.6"Χβ/;ύ"NΝ`¬X'FπφΡrՎru’ϊ ή+Δ΄wΚfC .…zб²‘”s(8ψ99g±BrφeϋIδaQPΡ[’ΟBΔxΧ€’„άΧ7UXy€ψXc‚κšΨ¦Γ]{OΫ·b– /Ά?žΧ[tCžυFtea‘J·_βΥ/ͺ„>™τ+Ω”0:Ό3!‹©0rLε'ε€dyVnXά(νrfΎK?Δ7ΟΩ;–”―.םtVŽp–'Qƒζκuˆ¬Τ=Ϊ‹ KSkΥΓ­2 ςš*εΥszΓΥφδο[Υ¦™„‘πfxy'ŠbƒΒ!υόpΚΓ­AΏι€ "'›³ΖχŽΓaS·«γO•hUίΣΊur’ψt€N¨VΣ{U¦ΥFfΰϋ†ˆλYύ‘ϋΒ€t_9PΩΉω½·―[u†ΕΑ%ΞΌŠΗβAΎ\’죘ο@¨ Ϊ±It,η–*ͺ]θ,Λ+ϋ†—t%Gυn~Υ”γ 6t©Ηtζ », vι˜“1Ν_wnΫͺ!’w‘˜(²Ί??Y‘“΅?’lνΊ₯YΉβŒΨθτO‰Τ I1Λ±2<Ÿxqϋ“n$#Έ‚X8#‚ήuh/UμψΊΉ†m`Ν~~ΎN&’ωμC½Jχ©‚IΰηΚ=¬+θŠ w+΅Es€ΠW²³²$!΅§»ΎmζΕ +žϊ«›;ώ@ƒ]UαΑμ €4?›ΜάˆΈμ!|Iн‡ξHΆ€" 3»₯‘π ― [B?‘@υΎ—±qΧI@#ViT›ξ!8©KΤ”]•J=ŽNς/ό(.• +ŠŸ™‘εU UωUUM±PχH «“ZF§Χƒ;ΉU;Σ-tμͺH6Άd΅m€Wμ₯jΗ-D7>_+₯‘Χ¨–½^ǐŽΑrˆœ^ω¨Ύ΅‘ε‡7»θ€ΒžJ\‚Žo“ά” +λΊ1"Ά«ρ~ΉCL='‡R, š)υŸ K]#εuΚΕ™άχΒδn©ΔX±r5άτ&*Œ ž<Ε^[―ŠW‘ώB$ +¬₯ν€ΨO Ÿ•κŽ(ΖdΚPΗ» +.ΗH‹g$9ϋ“•\¦X«Ν9uω"\«Ρ\ZΠ—ζx?jοΡ³ΧϋdZ-βYτBάdŸΔ1η @“Dβmi +”prαΜΐ€!™7?ΉKih3.:_ʊܻ”HΝθ +W:σžžσM^₯Ψ‘`Υώφf<Σ|)Ϊ_―gδ1Ε“BςβϊΡώΣΗT;[Š1A1&κ™OœX’8…'^ˆk―ί΄ΤΔΕ²2žρΒ5κΒC}Ÿ6Χσ[θπ$άsA9m»uœ-·.g αGχفk.€Cua7α‘σ/ιΩ΅SΥ]Ίsιd¨ΐΆ΄Σyƒ•#₯Ή«‹7μ/όeςY2Z"\ΎΗk₯“’Πc ξDjΘ ¨_+ΡΥ}εψΉς βƒŒN¦‘g‰p_§+™ράMκ]%’dΗM_.EŽ@χAζγ +j2(­ΊJ½|±žΈΗ %Ο:34ΰE4A–BC<φΦΛeU“"‹Β―ςUZŒϋ₯i9μ‘ C9ΠMΡ”BQ6ψφ½Ύ΅#σ?Υ‚8(΄ŠsVEdFΖή{]֎'™=/―bΆ'μμAŠΘh&Εΐ;$°h‰fԜz–ΎΫ_ —Π_ [-η]`qŸzv#ΐHΧς8έΉ™‡‡‰‹·Τ#"?-“QS£χGOv€E‹;φ’J!Žoή%¦°Γbž9­“’U!κlOκ"NjμTΠώHθbŒ4fbυ˜οD[Φa²Ψ¨†ΐuΚzUΈ*;βWέwμ’‚ΞN@Z8Y5_—λΪ}dOΘFαtβN΁Εχ%Fvn£Φΰf!Ήqτ„κ=Lϋ΅3™+A‡ΪΫ—XyΆΧˆο<­ΣWμO χΤ«xΎ‚=’u΄·oˆ₯δ@ΒΟDa„IGσi»„.βΟΌ¬P±{uοςh“θUU¦@wΚ‘π΄3²Γ€tεC5ŒBς2ety aW*n«ιΪΆΚκ]=Τ”Lϊ6ϋA―σX9΄7n{F[°3|‚Ξ°·«$O_; ”ρJ)E^Q`‘ž†»ͺꃁδž)0ΒΙlη6Œ΄@ϊhc«έ;ΓεLΎ’ϋŸ΄d9¬ ’oͺ¬ΛιΦ*£ŽˆŒΙΛΏθƒ_Η>ΟZ˜Τrj‚ήƒοŸ’#6j―£ ΡVНœπI·€κΞ–SΣθ§°:„K‡Ψ€š•mΫιWhΐ„> <Θ6i’YΕ +±λ>‚ΤiάVovνΕώQ%ψNL’KlαW†»¨Τ/Ο'υ„ O1sLκ‚qŠ©½25+>—ώΡ†r†d™ΐ}= ΐΤU¨0ΓvmkϋZ†l Y½@rفœG›Τ‚wt)(RΙ6¦«‹BΣ€χ•νh$§Άύ οΤuκς.S%ӘqύŽ’dŠQ%s ‘_aUΌΕ(6 +š­λΗΛi¨πΘ +€‡RTμ5ŽŸ—Y•„0JΊί:Eν]·Ι Œoθ•ZQ\MΩͺ;ħˎΟ>ΒͺΨ,rόˆό₯°κwJQΦ~¨tΖ¦HJ Λ±…uJsqڞ‚UλY‘jήΟA6BrΪ-v:! +IižΣͺίτg»(Sκ¨―Ϊ§eΗ₯H΅œ!-Γ;KΊN‡δ$θΟHDέ‡Κ%syΰ:tοτ;χkήεω&‚Pemω:ΝΒTšTρ1Δ²–γUp0X·uσ:qκδ°Έ―‰Γ"Œy6ͺ]j?(μ- ~ΙΏ~Π§ +ιαj•ιΒΘΕY₯ς+Qh+Yza²Α\HΗΧPqρΈg]Υ”δtμί"›ψΰ˞Ϋopuu)c‡ΩΈiΛh±εS gb`ĝ'$žΒβkg^6nBcŸ*?γωϊ+/΄9ΌZ«ƒ±ΫՁhp©E?,NŽϋF}ςt5ϊ”°Uc‹QaκήΝϋšžΨk8 ž,m:½*ΠΑ¦6ΣΎΏ4 YŸŽςqΣΘΐlΎ94 ζuƒžώ‡QY»mlν}ήγωZEHν$’Y²k=Λ!ωN ΐ-Ρbϊ­“Žš΅Ϋ‘sw‚4s3±OUDCtMQ޲&ΤΒ]ΖπξΗSηkΓ&~iΘZ΄©|ΆzαJZww‡Ktφz›ΔΠΐκΚH}ZP•¨-B/AΖ=Σ½–XΟGΣ€­€η6S΄Œ0‰ΞσρΣ1!κΫν„Έ‘&ΡQΘy”%nΌΩ˜.U1Ρ­ŒΩzK5™Ζr4 -d„¦―PŸ5ΐιμLxΣ–Ae ΠΒ1α·ξwΊpŽŸ»‡οTβΤ77ΔΘbΦ‘LH@ͺ˜φ<νΏτη«oήψξνΛ‡·ΏώςσϋίτΩ—Πχ,₯|ώψκ‡οίώςοΗgί~ϋΝΛΛoοΎυΓΟ¬ύόρ7­όZτόDϊ‡ρWz|ηΏγΏψη|~ϋρwOh\|i;Ή‡XηέA=K7Š€.fίf`žO/ IΆΜΞαΒς‚nΞΰδ„CDx΅ο3½B+i ΄μΆ½6ZVHΆήy͌5©_tΑσˆ•4qπŒξSΙb9v}•τρ„τ[4-Έμ‹fίΛ„œ­ρVςyQώ8nC|Σ9ΔP!dρeδΙN‚΅k‡ΦpήΩqΡ2fEο W"/—/ͺˆλΘ&οTΣ~D !” lDT‡:ΤΐlX€@:τqm\£ΕΖx%He"Q,³$ΟjΈ:Zc ω—φŠ#cδϊ/"Πδ2ςbΤηpρN Έσ\η<ΩΰΎ!έWˆΚ‘―$x£8Aσ˜ SoA—αΰEΐλγΌOƒΎzz‚¬J”1τ’–ˆͺ΄D4—yο“}«#?‚Ψb ©έL=Γ/€΄Ϊϋ½3l{ΖΈ¨ΣδWΔε ²£'Α°Sωμμ”oOΎW]ΣΦ‘ϋΌ2»hΔ fτ}lΌQZJiFE–Ίε 1I€šGϊ’ζƒΤcΗ7ξΰ8Bλf`τC#)<‘Χυx’"ωQU.·U†;έ–λa™ $Ό9uΜθ£σ°ο$aΨ-ΧKLAη6Ϋ€lF“Μ-9ζ\υDWσ<ΟΉds‰;y½qΖi0Υ¦btS§©UȎ“1m4]&Φ†ςFΒξ'•w½ZΖlŒε‡Θ LεΆ|8.›ιAtΙ?ƒ”^΅Ξ2€ΰ?Υ <Πώ«R­δΐH}ŠF}θς ωΦp|>ΡΡΦ/ς""+#‘ΛΊK,Λλ΅> •ιΫFη-I:<ˆ΅;Σ.Ft―υή©^κMY&=€μˆ*΄V<\ν{iŠf₯XŒsSq› fnhR‚­4 +Mž­:e*\Ž[Αί9Τ‰€τ…r5βψZ=ΦΊυ·‘ΘDΉDΩ)υσ™ 6RBsš-Φ™l vHdWμεŒsΪz ΄Q|NΕ­ΟΘ―)ν<Ό–λ~°ΰkƒ’„ΧΚdŒΒU1ί|Β†iƒ«S*>œ±t«ˆώχΞΕh£m½Ž‚ƒŒ,Γ’1dFφ<ΊΕ2 ¦XW±εD\Ν>ΈΔFkxΐa’"ΔSzΠ–¦))FhX5υB#ήH»l§†ρ‰’ΪM\χ¨rΔ&t’Gž*C%ŽΞ+³€βο@5Šέλάv ζ.h²φsgΔ‘‹6pΰEο½N~ U"ž^χθJ4mν#(„θ‘tZ• υι42ηΧΉ^*-ζp‘Ϊ…Pφ’›œ;Βζͺh퍋n­}’εPaΑΧP`qAxxΙqνB^N䝎PΟu’u4gY 4.k‘Q=oT4ʎrΏδ}h[΄:φ ϋ₯Χ$*”]CfC²·ΡΞρ ρ©Τ{η*=ΠάKμ΄Ι72Ί‘0‹R>5ρψŽˆ˜a>ε_ΗJ3>*•«\–λF©dΔ+»SΫFΠ9¬ξšρ½~žσVΝΜs•I™ΟNϊ6δχ2ΎhžΫy’Κ›eΰΉΙ₯Γ=Π³ά’,Šμ C]©Ÿτ‡άtf§žΛ‘ρΚξΟϋΊd©lœιž…§2‘JΚ,Qΰ$–Φd2VΣΛ†GUφΗξC˜/"7ްŒnFœΉƒΑ΄b48QΑMΧIΊΥm‡DŠΰΣ€ϋQΩΥJ Ί^#ϊ—σ…vy<ƒ`g_ΕΖ +₯Ϊ\―+€κΨ܍Ύ°e§a +}_ »ΓZε§Τνͺ«A›ςξ;£ε$σ€MŸ€$©"'>Ζ y8κ +όβt+ί±AaΗ©z ˆΔNύΪy³3ΜΗat=`)p@=βY—Λ T›7ŽŠ?§ϊ•ψΒΜ©m&鰊Uvιλl¬κ[*YΰΝ„Ž©.iσΠZβπυΜ»τ¬—]λ]GΕΏΚΉLEνžχ½jS‘‚’TŠυ*„$• MJό§Πoοo­gφ>'’w’ 9«3³ηεyΦKΕV IUaΪΪZΛ +Ής§Ρ•‚‹’Ά…΄“$ώζŠO&Ίmov9ρͺP‚νy2VΠζ‘$v!Δ§;*ZPš(R;t™ΚΖΠyψ4b_όv]™³Οϋ9q{eD,Ά‚J“k'e©Ž!\U"‰w’ΤτUžΉKQδG8oΎξˆ?΅‘ΚΕοΌ+χ,ˆR ¬ησŸ d‘Hœ0…lَA’ΓVΩͺœΣFv„¬εΨ¬-΄ΞiΕ§ƒΐ{Δ aΧ+Σu¨Β,ΔΕ87='ηΘͺΉjς•e8‘σ^‡ΫγeFX"τΎBΡΓ₯qˆ|vc•aŠ•η:!„δV Μ₯22Σ9±cM₯Ε'1R3Ι½Ώ‰gͺ Ο™¨δVζ¬ΗdΌςNΑ‹LzΤIΟw$N‰pΘ|Ε»ψ6Š"€>°Τ₯Ά%Ο³ξ.ŸΊ +hL))» B€/V•zŸHlVκU?‡Dž†Α-vG +¨œί<Ρ,½Ι~ͺΧ^{ž^’ΒF§ϋ9λ koχq€‡E»D§„ιB,ίιμ/£2€T +(Ηί·LβΣ8LςέKUv"±[nΌ>’j5[έ«+@ΫΓ}RPY› Φ½.ΙώIΏ‡ωX@Κ”΅‡Μ}³B›‰C«Ι©ρΕͺλ1–-6ΗΣς‘j²,.v¨Bρ V›²ς~6#Ή^lǟ~GEΝΆC₯©ίeλο7’Ϊ9σDλ’ωΡJaώκͺ/vωœΒτ~©OŽf¨ό$`Ϊ.ƒΨW±!‘Pη5qU:—±:€D$ρ!½©6/ΆΪΆ£βzκ!׍μ†ΠU:TYηŽ4XσŒ,w΄§ύ°< €Η‹"K~ψ}½‡6( TΩk λ‡γ‘ ΤB΅FΎxΎ[$Λ=΅]|’τQΘ.)D¬}"ώ’2$νaό^€,6³uG>a‚UWβ@4N4  {λb­ΦB ₯S»yΎ#€o=£οKa¬rΪ–±£0²C%œΨš‘ŸΆ/u#`£mΕαΦX`ΊY„z¨gΚάVxœ„š‚ζQ‹»·'Β]ΗL‘xή3g1Rύ±έΏ•/`έ―yΕ’ΐAεl 3=@[JςΊ!Ή½UθΤb­MNuλφΊβΫœ0κCΒεj»Ρq”δqœhμqkQ‘ hσ>Οm#°’oŸ E2λy-ΗDœGέ)Ά¨Δ‚,+Ψ΄UT–¨Ξ95jr(ΑЍsΪ'άώ]uR—²ˆc!G;?A8 ͺqe¨ΎlŽΞ(Δ­š`Q6ΈT9kKC%γd$0ž#‡ξd9Χ /“Eβ΅Ίf΄2…JΧM₯ iΐΪFq5wΘ ‰Μ!ΪχκSχ{Ε,οεδrΔϋΚ4BΧ@β’³¨~G ‘Ι>enύΰ%ΗΛd=$Ϋο“²ͺs·΅6³{”+?Ργΰšl€₯H™^%πlΧΜ°x £ΛΝ—„Μ™§‘π― + +—'sžΥ8tρˆΒ¦jδ΅sSν;κ֐• o.ν+’ν9nP¨Φ7Z(O©_μlχοHn\gο&J‘šΊKŚxGHpfσΰ΅EΪμE•;‰ D½-†ί1sθu_MΩWx Σe v‰$“^ΐ [z]©@v1άκ<΄a6ΏŠN2¦¬w¦΄Ž— ჳ +UBοΏβ;Όa3ΣcH©ΕΪƒŒe$ΣέBΖ~}.Z˜]…“b—M|–t£qdQξ@<ϋfθϋ0ŠteqDΫώ_ˆοε4ŒŠSH*ΐψ€š“ή₯ *KšI Μ³-d,ͺ6v’²$I½\‘oηH:œMςReCˆLΣξΉHζ{~ˆK΄Δ•7„JŠ’­θ…XY/ŸΩ`₯ΝEΔ~Μ"Xw&υΈ‘Ξq"U[ξ pkΏ&š5‹Θήΐα~εVx¦˜ˆ(Αp—λ%»εWͺίb΄tRλ+ς\7Η9oƒ γ—1,l‘¦]Nj€Sς‘ξHτo‰s‘’΄!·NeH/Y|‘`Lάh>rxΓ&ύV²ΔjΑ&2א`oκΊΉͺΗώ0ŠXM¬/”l²δνΉΈ8OΫ§δΗ’³ :€PΤΒ²L£‚AΌ&:¬ +Ν +…B†‘yi!Meg䊜³Ÿ3σ―Ε4bλZž–£/@―qSF₯B’ΙΦužιRk‚Σmυ…9ΏPGΙyΓ,²7 N +’Xΰ ‘δς +“Ω(εž(£ŒZ›+d9l§π¬ E7ςaX>y/匘Y7§&>IΕhX^β/ώ1zc"3$UήhΙΠχkœά>Ս…6–„mMhuΟΌPڨːUΕ*„9γΰ:νί\¨Œ6›SέUWΔpˆQy +!}‰?δΦΈ€@FcB/νšΩ{,ך.VK™ @lΐόΙ’~—Ο“ωЉ¬’Θ»oχwι›EύHwλύάμ…r³B›ΘnΠƒΊŽ%΅€γώ‰™yσχ}ΊcβL…χ]:fΪΆ‰}ω*όšB•‹Œ*όa―·’α¬Ά™]ΚBϋ;½^H’g€u·£F©θ%zkΕΞ#I άqΈ2…΄Wε(em')cR7²x#’Ξ‰&΅‘lΎLΙ4ΐ΄b“MβRh€ +Ηoaΐ·¦*€γQ+WpŒ₯q‹ζ₯"ͺάχaoQΆ€δΉΉ»6.9I@Žν0q πVΉύΝγ”ξšΖ‘EΌΓ%EπJόΏ©‰N$Bj9 ΩF‹²“^›D‡Z;0δlJάΖŠŠΔ.—γ,ΌΧκcαAš6₯άΗΎƒΚPX£ο +Δ#*.Έ²%XςV ±Δ8BŠωτΕοωσωžΎzϋκινϋw/?ό|ϋ Π³_A5œvεΟnŸειΓΫwΏ=ϋςΛ/^½ϊψΓ7οŸ^jμg·_2ς·όαΏψ–ΏΎύ?-vά”©n‡χέρܾ{Ν?ϊνΩg·οώϊΏ?–nŸωώύ?oΟΎψϊΟ/Ÿžή|xχβλ½ψζ͏o^>½yύ‚ ρ©>ξ/ίΎ{ρόύ?Ώxύ‹ί½~ϋτϋο?ώψΙζΦ΅ƒ:χGοξ+ύ'ύΝΓ³ύΟΗψ‡[z<1©Ι2n +ΙςφΓFέ3*Δ‘NHνΆ]g:bŒlΧ+O€JΒ‹>–Ί©εŠ΅ΫW>v«#­¨'v*όP¨Ž*αBH2’½cψOBςΪσX­Ε : xy3¨„lη±ΗΠ¬1Σ6Ά)…ο¨·–Yvι@:4=ƒ/U…=)WΈβT―j=ܘ+ΨΰP’‘ vΜ|fŽ—Έq=1Ι©Ή5h€6ƒ«}ϋκ bgK%“YhωulS&tΠHΪτJμyR³Λ΄σ2ϊ1‰Ρ ]6ΐH«ΓH³8k©šγ“Ϋ>Z’WΛΫΊr4€ΔζuΩ<θςhγό­YΙΉρΈ0Q6¦Όš ²ŒΓ*o°η]h€—dε±Γ HdƒFd“7ΉO”%½_£Φ&Jν{ [ ²*<~BŒR>η•HtBΓ{ +)>ΌͺΒΓφ@Ž„"‘3δqVŸrͺ„d62ΫΨ3y—‰ί+2MΥVs7k³νπφ/€…ˆ™'*Yι₯Ω +ςƒ΅l‹z·ΈTπρL’ +ΧΩΖY3%ZZ{DŸδBe²9₯ςct#ZR_»«2$Fžϋ)Ή΅έ’U¬Ÿ ‚5ΥCk΄Άή~ςΖ@S€N=’ω0ζΊΌ±-ɍ Ί`΄°g)cΣεϊ7ΐ««ίP™!šΧOVw€ςpY—F$T‚6f7’]˜™r93qžF‰}%ότ$ξ™ˆZ―6N»q/¨FΒϊPb\%{βY vL?.Lωa:φΞμ#Ζekπκ؁όΙΏW ³©ΌΪ}ΉΊT>˜jψΩOεΧΐ‡τžύ#γεyi3 +΅‡έ―mά6¦ˆ(Qz}vϋCe5rΗαΚκfœ­Κ`Ι;Z%ώ•gͺ#G£3QΊ<,.hθ¨Οί•IΤ,$ΞΗτΜ©#DO©€Iό¨XoxάVΙϊμM ,ž)Τ&q&Λ+!Œ‹L#…ΦΥνΚ±‹ΰνv²%‘²91n$D„*™iPΚ|—Υ—ε0ΉFΜΠ|­Ag‹νl> &›³«οQ?Δ +–κεΚHπ] "ϋ“)―\κͺgZ%‰x“XΜͺψΪΜCZά¨ͺΙΥΚΥ+δ‚€α 0Χ6·Ά@s.₯Ή« I;&‚©φ65ΪώΦ3AƒZmd<im`m΅FΠqͺϋ|΄ΕρXd¦ΟΗξΓάΛpŒΡνTHbβ Η#d;y\ΨΉΜTηΈM«[υήĐκ„.*ΛHΑlτ ΔήFˆ.ΈύfΊ$UwΦp2»ΈŸ₯†2‘’~ΧQλf8 ]©Σ^ζ2ΐN#jS;΄ S€Ε!ΥwŽIρΜΆt€φΧ€1‰'—+£€P@Π]_ρΆv Χλ >…i3ό€YgΒt•Ρ΄₯;ˆΏ90EΉ~ΖΝQ^VLL΄yˆ>Ύd‘ήΕZ==qU~UVY–εBήδDe8-$ά‹xhΜφ'N­RΗϊ"™€ZξΜ‹’'}FςέVPI%_„š!―‹ά›hυoœœΐΐ–Σλ»‘X»ΪΡδ%‡Ϊϋ}MΠ@؊Mq΅w-€mŠSˆHv½™ΥŠ/ νkRS¬œΟZ”uΜΤk–u +Ν™°«σW yέξLΧΚh?$Ξ©6U†ΑΆ3δ¨i·_D4©ly&^4γ₯’φ٘Zš"(­-°‰Ν"Οιq;ς#_hταa¦wΜ‹ͺ¬D²Ε_Ψ?Δ‹φu‘[6άΏqΌN[Q’ύσ'*\φωΰC%wvH,䛝‰ίdμwέ?$ΆͺΫn_”Ύi©ΦέŽ-ΗEˆΧSΊΌ…  ]qA [§V†1xή;sMν%d§YŒ„Νφ˜ΘΌΩ@άP'mœRrηšm$Gš ’“%Ώό`ΜΖχgœ˜ή›±Σ‹²xˆ‹<ΫS„ƒBG9°qXΥΠΰ Š4Aζ)N9£™Œšΰ„τ„ύ2ΙFZο§$+λϰž7UΞ 6Ž^;65ΓΦΕV[Ψ"G­q’^Ϋ’l³²ΎˆŽλ%Όͺϋοn¨>λ3Ι’gžEΝIβοA†>F‰€+2ΘςaY©¨Ζ›9$˜ΥώΛΛαλΰ‹΄‘ GP¨²γ±ψEόI\TρKήq2&ΙΪ’"λ3Υς‰*f\Q£”+A1wD#TMqx ŸSHƒΓu΄ύZΨaU£nG!Γɏw­e@}γ‹gΧ\(Ί”ΦΞΈΜΞBŒ-βQ±Ϋ4=ZVjRFΟ.ΧΦ½KLΫe½ŚΝΛ•ζ ‘χ- ϊ+9‰•[>2†Ωם«_§5Ω‚˜q`«Δ!Vιiž…^ά>θΖnρΨQ¦8Lˆ€}gTWLΣ_λό8ΰžΞ;3&dψ?@Z½ΉPbξU»ή…ΗW6°5€l 5šDBw{v]‡DμΦLQ—oΧβκ’ΨκtRbγ’dΜ{Άzu€c7xΑ•οXܞ,‰+) XΌ7U΄•λŽΆΑhτ‰ϊΑ"©6θ,ekBŽEΝceMŽΊ!ύœΒΒ +„δR7SΗΚ•΄ “ σžΈΘmΐp7;V–uwΆΧΚ(»φ“…5±ΟœύωζJ?:Uς›FD ¦¬μΆ¦ΊΪφΙGό4δγΐκΗΣfKκι9γ„?ΛΗύθΐeRcΥV]7½‘Πs™7’[³Η―P[h|–GkA΄}ω.₯ZΩηŠΫVvΝαyy€jήSƒHόϊΫ/~ŽΔ+½uœbηζ!#έ/nι1XΙ&Ό΅Υsω}Œ™Δ‰ΜVV@Ή7λΈ0E4Ÿ|­o”M\ŠA)c{5—–2έ{αDsΕΔΜͺ„sΨ:.KcCŒΰ8xcC)ςεφσBέQ v©¦SσΟ΅ϊ["~8όσ/~zυχίΎώΗΎωιΧ·Ώ +ϊδ/κ•Ά΅Oί>ηϋŸΎαΏoŸ|ωεί~ϋσ»όψώžύτνΟzςoϊcή:™2²¨8ΉvuΞ»d³­ΐ”>*η oΪάͺ»ZΣξ¨>YecΏVX&c[μψπ"»2J EώZiŠ@ωΘHηBφΒΈ 9=\a\Ό% ίTοΆjPX•zΐ²°Σάιδ†t)}<¨]€o#, TCο:ƒΥW1IΌύ5€kjzΡ­‹©·‘Ž‘5Κ5"κΚ~Αrνxέ¬Μ+ΐΝO~"{DZΓρΤ‘••‰Q·ά0£γ„.Ω½a3–βχWωΥΨ.ψ[™ωkΣt«Έϋ]’kΟcT"ZÏ‘Ο5~2}°n©΄F!ΠΚ‰Œ œ νΨ·κ]‹ŸΠ‹™~Β~ωbδŽ†SֈŽάkb¨ΙγR}Ό€Ι,—δσ`oΩ5%# Ωσ™hos/čJOΚ£'ξΙπƒΪ‘{–QQ!j›]DΛ/όnιVΪ,„₯­ψB‰ŒθψδgδΎΚCσΞ Ώ<$ρŽ9ΠsrλϊdnΪώ}œη7³ƒ$‚›ήιH­……nšŸ­?­γ…»ηVIJςoΊΤυ,’οˆ£φπ*žΆX‰šyΠD8BŒοuΎΣ¦MΤmSς]ϊjλ&Ύ½ žΥˆΦ-ndo-@ ξ†ψ£q˜Ÿ“ΚΨξ γχ‚ΤΣVΪ!6 ’ΐ)ηΓΒ²kZΪ؈LKΔ‡‘/βΤμʊuβ%o-'lLψ‰κ3ρTaΖ›•FΙ|'¬·]μ£μnFSυροp;ƒ*)δΦU‹―bθžϋψ6€OΥ‰sβ;›uΪό(ΤΫs<$Y’νa»myuK8Δ΅nn’­½8"δ΄’)š Z§O_χ*Αϊώ|ΞΛ#3jΦ―DŽ;);¨”w)Ύ•V&•iX Ξ ~ŽoŠAλ"Θ4uΣ£αS›6Ι7g9T@•;Oz]p©PΒ‡SδΎaΤA|SΧm¬rκjΉeœο_ηΊ™n›Ί+Ψ–Š―οjΎlͺΉ*oΰw ωGΚu«+€¨ή }ζί*ςœβΒz΅Ω +3Υ’ΊYOΊž~:λihЁ΄±ΐτ,dNέΏ<‡XŽΖ©°ϋNδE£σΥ}Ύ*Ζ»<*ΔyΥίTAr₯¬֘˜Ί¦¬Š8t"jΛ8nιΆζν'A¨E4u’¬7bυΘɚϊžEυ‹*‘O&J7Σ)txgB–§„όc„ι1²Φ‡•{Σεi'!g|αžψζQEiη‹-VY9BLP,O’:]#ΝΙΖ“!+ωtφbΓ"š«‘j}’—αΎzŽΫgϋZ°|kΪ^šyeΛǜa +SΗ@Ϊ‘pHžwΘγ°ΠoZ FΡ ΕξŒ+P ›ˆb4φ YUš[χ²Χ}Θ‰βχνκ€Υτ^i΅QE/ψžό±γzVθ^ͺ1ι4]9L!δΖP΅ρΩcβa±0…^ }<δ—(φμ©γ9²M:(΄ GI-z ₯NΰΨ$·ͺ›]7&ζŠψ€Σ^ωKsT/ΦM΅ΪΟd ’ +(―SΞ„ +ˆΓ€T-8|¬XΉΗ@Όzήqxˆ•3yCθtύ…ψ( k±2ƒŠ@†ΟΚΡP| Θt7π{iρL‡Φ_+E€Υ³T2Έ"FsiFXμKŠj\‘Γ°Z{d,aƒTΝ(Ηθΰi½-M?ΕB5α‘Ν4#σζ'w)‘^AΈ•M¬ ΜE61‘λϋkΠ^σνΩS±#ΑͺϋÚY•4$υ$ωθ}Ρq²ψ₯sύκ~½7μŒ•S"+ϊΑˆνš‘;]ΰ~¦΅šΈ΄Θi9]˜F]xˆΟH.Γ€ϋφIK²[VΨ{Fž=‰‡+3¬X±Ψθ ftj7’–];•Φ^ΛΜξαJ;7jXJo’«‹-χώΒoPΎ‘υ Μ‹έn ΧJxτ˜Γρ•\‰y ƒp’f|­\PD―49ϝκ ’Φ%u" =K‚ϋ*ΥxŒΑs7™Wύθ$©±•΅αRdtD> ζ< + Ϋ:)D/FΜζσxβ”δYg‚Όh&ΘRfˆΗ΍7™<«šΤΘwΡ…­„υC%€έ±^.»uAύ•;L"Pϊύ€QB†Œ@H0Œ,¬(‰d9ƒό={νκsΝ Δ–oέ>}ͺ«φι ˜Qsj/½·ί,‘Ώˆnό ¦Ο]EnΗ|H˜O{“<’;εa3a1vK¨"ώ^U†½‡½5³HnYTς@Ϋ3Λfή–)Δ2ΖΜmΌ oΈ–`ηΐdy€θbΒ‘i<D@| ‹ ϋΐ³|ΰStΈ&e]0zK’ς±žtKͺξlΙΓ,M·RBƒ₯CdκΚmικW‘€ρ2dΓ²qΦh˜'Σ\9|ΤMd^ΰΪ‹Υc?JLjζ71ΌΕ[†ΈΠΰ%”ιyBƒ§˜9&u8φ¨)^—ώi;•3ΡΩψE0Κ£ΓB`:Q +R`Ψ΄Ά―e°‘Q‰dΚJδlmL‹Dάo&˜.©cu:½/Κ· ©H`ίm?:Β™jˆΐ?ό"SΊIcFω;cκ’ΧH!»­Tœ aͺ(t˜“.Σ•JQq·ΊJπ\]’oΦ8j^H/a”Βu‚μcί€u– bέ—+-²κξ‘ΤT±F(kEŽb€BP­ž9π*ejχ~A’3¦0…O"²ΰ1―Ϋ㜢§A©G¦Ί·Άˆͺ=x‚"ErΪ-2ν'šnžΣͺ.šίνK™"GΆœ–!ι«A”Γ™½^'>α$ΠΟH=\ώL§ ΗΗ\ΈΞέ3ύLͺ•yΥύOΑ ŠŒ|ψ4ΑεpεθGͺHκv¬ZΉyyrXΔΧl¦ι…ΊŽDLL?Q υΝaM‹^U‘’VŽ.t\ά¦Κ·Q…Ά’’η΄EΑTΤ7(¬΄N} p[α€z¨΅aϋ άVτ±CkΌΐΡR Ξͺˆ%‚#ξφυω#k_ί~Πʟυ£oώψŸ6ϊΖfόJ·ώ>ωο|>ύω―4ίΖ« endstream endobj 27 0 obj <>stream +H‰¬WAd· °₯ ϊ)Τ6Žm ι3z!<›Hψι² · ΄?·γ("cx +YΩΛr¦νBφeϋΩcΐ78ŠmζΰqΌΟζ•žΖp™Θη6œϋA7ΔΟ‘F8NψΒΝΖΒΣΨΐΊΧ·?Γxœα2‰έeΉΰD\ΌΟη\}b]?ίΦy ΧΓ$.•ΗO€*TvX>η, £ΥGΉϊmΈf”α9 d΅yφЬvΒΈγ™9±]gψ΄lβΓΖ[υύτ†k#4ΟΆΈΥ…Π2ϊ8iq―³φά-MΧξΨπA4²ΌQlΆσ.E Žm>ωΐάάfΜ¨Ϋsφι˜#„_Fΰn\bζX.δmgΉ\ y΅lΜ%ΔG†>Χ>ΖmΩVοBGG’EC™ ;ˆυ!de9F$‡¦we9Αι€ΣΝg9»<ήeΪ—α2£αZΆ‰“:"3…|ρΉμ nηYκι……Ο'ςŽΟ…$mHŽςE8nΤό9‰ΫΩO‹₯p€^Π›€'ŒHςSެ†ΐπ–3±wόMλ€&‰(¨ΧΊ™KΘήK€£Z$ό„›`οC`Ο³_QΛϊζiς γD-]q@€=ΣŒ#ψ§ψ)Ο¨9RHDΥ°Ρ]/Π'όq–i xΆθ‘φUl8[6:Ϋ!MώL€œ ±¦εγLΛβο›oύόύ§Ÿ?ύςσΏώώψ ―ώŽΕxφm_?Ύωχη_?ύόγγ«οΎϋφγΗύτ―_>ΐ΅_?ώ†•ΔώυΗχψίΦD*=ΑΣ†HήVΏΚ‹ήM|“Yk•AΩιζBφndoΗ7=Κ'š“¬@€Αy"3X΄lΫ„Œl~["—f‘8‰@ΟY†£χ‚jσ±G=₯%rn]gΩ6AΟΑŽΤ:Š‘‰ xΥoV„m‰ςΖγ‹p]€E"Žͺ"Ώ ψ’} „έΪ8Χ$Š  ν-£Φ‰kHμδ "Bˆ·ΈΌEq’ʎ•τ:°P+D,ΞζnΧϋ$hΦΩαΪ[!Ι6+t«sΛ?σߘ0ΰ]<δ¨gY*U6+²›Π`ΪjNdvw!½΅²œ;Ο…ˆn° Qτ₯Zη>ή" .„wUν4„}ΦΩώνΐ;ͺ©%Φϋ!ΚΚ-Š1nD–1D1Ύ­CiEπCŒβsOn₯k²§,‰n ‘(Ε‘uJ;"β."Π,dν—ε `’,bQ·ΧΊΰD‰Ϊ υXŽJZ³(ω‚δ™τ/Υ9‚ʁώ;όχ«Iƒ9FRQϋ$Χ FεθΘ©ε>7‚δχ‹ΫίP+*„ƒ“ 0Έ O*ς’žκBκLRxΔϋ:|`Kγ^¦%@–d{@©ΓΕΩΣv]Jω‚Ύ%Ϊ&­Ξ=ρdθ!o$ +”5DtMχZG=Ddo9‘Η}Cα₯ωmΉlΪ‡•ε³2‡œh”ŠZΏbO蚀g’ –u©f_ d‰Kqέ(:Ι³Žxl₯ωΤ‹α­YΎΧχŠ˜ΧΣίVB(‘BH„ό€έZηΡ°7JΥ3iθ಩}ΰC τ$-§>]­ƒ†›zΧΩ>υ8ΑWΪοΟ),ΩΜμY”T²dϋO«7°΄Ξξ3΅lͺT‘ξtB|!C RŒJFιrhœΆΉŽ:3Α{}³χΦ/‚εp@Ό–ΟZ•+¬Αrk¬X*aφe·αX^ τ/cη’ΏZq\‰ΎNi΅Ž]Ί!dΥV8‡1οtwXλK’ό3ϋ6[ς,ΪD)8 wN8€%π€4{€ž‰zΖ’\λ*?2X4 ­δ d“ΈbβΠ²‘WΗ&"‘~YήμLU&Γyl!¦‰ €_ΝId*‰7Σ@0}8Ÿ“γ&M–b +ΆιΡI#/°&Pζ:­hMmzžzη¦$°:YΪ™DD™˜B’NtvBΔ²TΛΧτ)”b—¨r[ΘΈ(€­@gξ:²#㏑iήe’œΩِ»h³εκ/ΣΣ‹ εγ‘νq1©κ!QI~ΐyθU5υΥwY ”>%ώ΅fΪΡ6˜U1α5₯ŽŒ]Gh"o{4!4μ± c‰ »S¨ο’β]ϋ^‡¨i7³D`!j6«ζ +ΩœΝ ².K1°ιP+μiΦr&HCγ)Φ&Lih,]Φ€oοΎ-/Υu‘œe'GIzέHU¨_SΞ‰|).δzΧTyΌΦATrdKTΏ7PtŠφΩ\Wauδ{£–$’.Λ ?Ψ©Δ_d)Pž8Kη@DΜ»ϋŒ*&‚5?!€’Λ@JW‘ 6ΧmΈφŽR?δBΤ€h$©KΚ©Y2―˜yEœ`Y†h»jt &yτF@av,/i©@ε‘/DfE}ΪΫχ;tΌPή)XομΨ_ŠΗ8q#ζ―σα”ˆQ=ρ©™|€τΗ%έ―”a‡¨½/D'r†œ;ίΦmP;‹GBl½/˜ΰBY•P C%ΥΨΆήwςΉΦͺΙ±θΝ‡3BΦ«/4{cΆΡπ¬q‰†ϊ'2 χŸq”*Ε›ΖΠΦhy$«Ζ³ ωνθR½ ()HW…&E5“PkΖΧ‘Βν"Τ~hNK€Ϊ*$/o]–$q;–wϊκ8ί9_ +tΏν\-•²bzcχβ›ΖΧ!ζΎ\Νj!κ΅]ΣDžΎ½ορΑš0βmЧB³yΧ:ά(ΟΊ]YΙ$#0ΦΛNeC06yιqΆYΩ +C(8–μό ˜jθμ²pόΑτƒ +4N°$j;ΣΕh4ζIlQP«±μ윴ήμˆqΟPs«‡έ£P/‹ΒΑ³^n½vέTώ+ϋ1E Ϊ^ΎΒSΣ"T U„§(j)Š 9U8AΚΏg|cz­½β ΅‰Ξ±½|™s\VxΒN‘ϊ›ΗΦΊ,Ύ –K^¦@πšΉ—VΖΩθœ;(ΝcΫ"½Vp{:mQdhί«3¦žον\ΆE)2 H-G qΡBΪ±#GhΆOrΝi`Z<‡Τφϋ€¬ΚV mf_Π–ϋ‰ΆWFIHΛ+czu­USΏf†Εƒ†z1“Έ2g™FΒΏNOΖzgυμΒζŽρΪψ’ε™&+ςMΫ»½Ζ B]AMufFŒ΄Ά…Oύόέ³$ηςΐFΫ©šΊKŚxGz 6Δw’’6{QΜHWb RΝΨ+ρ{Νc}WΉQ\α5ŒΛΥFΑLzfΨλJ΅‹Ap;ςΑ†1πv(>ɘG˜ότpΌlH>x›LBHX@ωίCΚ~„ΜhŒΗ0σc„}…HΧJ1ΟE Σα8)vΩΰ3LΦdΖ±ΝˆΣm†Ύ+TΡGΨ;β{i罜¨Θ!S€ρbεπθ0©ΉΫ‚χ–ΩΜwԟΆ ‘τςΜ‘#ωa-/$₯ι*β»η‘ZΘ½<Δ₯ ΡVΣΊς(’FJš¨ϋ*;땝]9+­­Ψ9TQhmkׇǍ|Žs± n9Uύ¨ύšhΦx˜μσΦXEΤ%JFdΈλ%ϋizR€-Ν^ς1$1ΞΎΨ`^Y aΫ-j—MX·’ς‰ών‘bξγD·Κ@/,>$θ‰'ZR o(§€=σ‚ oX#1φFםΘUu•€pΛ€251_PΛA–:œ..ΞΣφ).uΆABŠ’H]) 1‚2ˆΧD‡UΠB(8B#xiFΩ9#';Ϋ3Εη²’–ν‘²'ΐk\q*PTH―–¦¨τH— Λ‡χ"gΔΜ±9U»ΨΊFσA±Ioš§›ι»ΘՈ!οΧ8)[Δ6υ•€ρx@΄«)Vέ3/T]ΤqO’ύR₯ΛE.²'ί\φZrέE'th+b)@ϊ‚>v’Œ§¬Ϊε5³χX5ξ•₯ΜOBμΏόΙƒvbο›Υύ;ρ‰{Γ<UώτΕJύFwDΦwΈy@u± \7Τ‚\Η²XφωHlΆmϊΎΣγ›7ΕμrX³nΧ€}ω*ό˜ Δ"£ΔίξυVτ›EΓ.³#,4αυBςβZϊqw£FE v#[ΩxdτΑ §³‘ςωΈ8hμcޞς`;R\mϋ¨Y΄²p’ͺΥ&&Ž–Ι0#τl’I\Š‘Šβ·.ˆCZ£ + U= C( IƒŠ(˜ο5ΒέδtlE‘ κVΟΙΑ’@Η6˜κ9ΡΦqϋ γ“NLT¨Aκ=u ʍ𻢽όƒ–8葉€ΩO?vŽ#:ρΪ e1Un`(ΝcίΖ ΒDA.-ž_bTI56ΟWe―8R"«ΝΠ›“‰ώΟ hJ+ΖFβγ‹?κΟ—_}xώζνχΟoŸήΏωπιφKA/~SgΩ«/n_ώιωΓΫχ»½xως«οΏψξOΟoϋΕνηω+ύΡ?ϋN}χZ,݈T·δ^} +δφκύΠo/ΎΈ½ϊσώXΎ}ωςιι·_}ϋ‡7ΟΟύπώυ·|ύ»7oίΏώϊι§O―Ÿ~|ύλή>ζΓΣǟ>ϋθΊVώΘy>ϊ«ίψοίσ·TΫκϊ3c{χΏεΗmΚ ΛΜΧ“θ#ίή¨NxρΛ2ΰQu]VJ@Λp›œƒΩYIS¦d6q?ͺ»vGJˆ$|32*–’iK%›*IόPWy¨w<Ω£]Γ΅²RsΪγ%UΒΕΜS₯J#X₯Φ$A(¬τ˜C’6okˆ‡Yœƒ“κp˜BΰJ%ΤβΨ΄ ΔΞ.πA`ΉUxήΈω˜TρZ·FΟ+Bͺq}Ρ‘έΒ$f³J§©0Ϊ ΟJfRΣ˜^P7”OΐθιT)υˆiέ. Υ Ό†°:{•ψΪv~ΥΞ„•J˜Ξ*wΧΡή,V‚ώDR8—ψ5Ί=ž}ChΕ¬Λ-\ϋ Α5Ή +OΊƒ5ξ»φj£1­―‡.π·΄σšΐ·yrŽoAΘψHΝφ4<"5&Πf@fΡ¦η‘ώ‘H;­ Τϋ›βξ€ά€)]Œ~τΘ2=Ϋ―SΈ 썡ϩΦ[l©­ϋο-t>T'°ƒrΑy7Υ"©Dφv’ +ΊMΥΓΊ.]©AAΡO0iΥα6}~αΙΩK‡ ΚE'ιΉ–UO~Ls³JVηξ%c0CΓdyobŒ μVξ>’qtqt΄^:Π ”σ NJiމ8)iUo'@›Ν1-επ<݁«[/ΨI΅ΎPˆ£ς˜…΄yY u) <’ #…S)%H+έI+…gTύ΅Ι@0JW΄Ι―/ŸSδ©BΩd›+―ΰRiτόa +͍orχ8€&»˜±Β+Η³v‘LndFυ_νgKͺ3kςu1ˆtΉCȚPCΕΛΞs¦,Κ€Š„NΉΦˆ€7fNT#zω3kΟ{¦oo¦ΫΧxUWšF”ι&γuμθΜ1{θΤ­δ;(Q[|*7›‡ͺRO'¨½Λχ3R +ΜKΑ°7i΅`rPO¬%ς0ΠRlS T P-Γ(«‘% p”Xgœmί±’ƒ-°ΉuSΡ) °Η5 ?&›–ΛFΒΆͺ˜E—OT¦G`Khόΐ«#ΘβEœRϋr³7ךŠΊ΅%mL\€·(Gš‰,'¨-•Ωc3κd”;nŽ©R†' ͺBYQΟ©€§dΥW›Ξd3  γmšpμQG]bΟΚ™u6d0ΟαΕλΡγwμ”~?°Jž%σ³Zά΅·4Ψ£D6£–œγθΪ–σΎ ΤŐ ȈUΖƒ”Ρ§Μ„Vžιp9Š +Ρΰ±_X<Ε@}M}z’[ klς6΅ΆΨa±•ιvвn ΕsX:OŽ^d0•6£|#j©Bυ]W$ρQgXŸΝτ¦5rvΠӍΞ²~( +\Χ;V¦ŒXο΅πL‘ΆNŒ3aȝΙ‡o/υ„πIM–λ«Η&’Α΅ΧŽΓ‘cœlή +„Jњ-RΖ5sZIYΗiΔM_μX7‚C·»Μ§F‹9%α~iι"­žr1$x¬?}Ξ€§²v‡ΛJ*Ϊ§ΜΘ;£UτνNαV +ΒqΜΰΌ\Ψ^― ’Ξ‚…m”φ-vzu#G±A)ΑΌφ1³άΣ–Δ3ΛζΩΪέ(δΊcσl:ΆeT9Ιχ°ή…Γ h< ϋo£—hŒ.(’AΜ\4t5―fψ™eœτγΫφΗΗ<Κv +I£Δ&'Εe€Δο«·ψέvEˆŽ[οσR +ξHh³ςˆ[·ν•ΊΜ˜‘’ξρL'±8±yQo1™•ŽΊǎq1@˜#I­ώMuΉ΄jzTQψ―œa"jκ~ΡQb†ŽA‡‘AΙ „N ήυ¬]υ~_hΊι³NU½uΩ{]JνΨXMνam.8EnyWΦΒ”ΦχqύΐLk}ω_{ΡϊŠΗuhX[,‡’ˆΝ$)tΎ ¨·pΩ§$œ£€€υBΉ Η5™R±ξΨoLΞθΉΨ@e’7BΒΫ«cͺΥΆό8Ξ/Δί”I χΫ8©kjXϋs¬w$Λ™ω €²IuM/‘sͺ+Σd&:a―‹ά«ρkœ$~Œ¬°2ͺ;Μ_2 ½_ž ‘ΥT,8ˆΛEˆΌ\3"FJžS}΅f 4ή@ύΧW €¦X+螡ΚI΅μ­•u*-ΣσKV !έ΄fKί/$ξVjΆJ7aά:€Vορ&Ÿo>¨n\Z­ -†»"XM›hΝ‚zΨμΒ"ν·q’Η<>ΠθΔ7Dœ™ξ1΄’Π‡Ύ°±±₯]§7‘?Άu‘[v#Ο0υZk0‘–κo?"ΎeŸο=¨”jωᴎ/š†άzν@"―ψBb§ΊμφŽšΦR;Ό»ωΫ7ˆΨSΊ¬₯ύeΪ0%Ζvm#>³EΓΎ…1xΩ;sυMβφΩiL#φΟ3f ³ίO*•"OΌ‹Y^w.Γ>Q\K x€ΰ­ΖΎΫϋ°/KUνeGp‘ ͺφθΑAEWυR·}ό:"Μ'§$ΗΘΐ0ΕΜ­d‚  ϊ„Μcš³€ύ’''^ΧgZFlγ”ήΦ '3g˜"ξGω%ZΕΘνYΦ;’ƒηzο…κzοζ\I2βgήά)‘3πΌΘP°Ό ²YQA–Σ"ƒ>¬³Ϊ”€i]FLWڌ¦¨ˆ‰4N}Π)vυ 4yO‡`Λω?¨,ΚΐžWέS_ΪΓT:;xΰkΊKΕΪ ε‚$ήθ\³ͺδ)9mϊmœz͜ΜβΤ«θnσ9I»Ν¬R˜=Π)'γq`²z€C‘ »ί„ JΧΤvΜ8Θΐ„ˆΐβ›sϋΜXρ"Χ33I—uGJ­H‰Ύ ”xGτήWΌγE3„ΐΉ«•‘ŽΩΨγΉ^€ο5ανήF©Ώ€Μ”‰\²τZ…’-έω8›Ε»²Τ;DrΚ―EΧ(¨{WUΔ +fiy•YͺΝΕ ;]d―OU>„²F{6ι–θΑ$<Υν€ωύ ΌP<ΓŠœ&)@œ“Uά!σφabueγΥ… H֘όƒfJ1K = `8Ψ“B}IœΕ` Έ›-S%½ €Ρς4-1f\R"aŠWFFζ΅η:XηDUσ|μb\£0θ¬ψμT@xΡΎ+=?ΰ”ΈΠ¨Z5 ©λΜ§V΄ΫΟ•#δ<ΰίc)‘i΄έn|]Ί,βV¦Θ’/@ϋ€Φ―Ο4¨ž―Π–2ŠΝEΥΕTWzFbtSr΅8γ_γ’·Υp–*–rΉ!βΛEU’’‘lnf]₯Τny–9‹Q±5MϋrεεŠΓ‘τT’FΞP}―J–Ν<^¦φΈ―,Α27\μ†5qγφŠŒξϋ|όΗγ֟u}Œƒπ{P%ΆPώ‘€$§ΏHιš―;Ž0΄sΆΝξΜu‰ZξγΜ|PψΖα)…@§:»άςτŸ§Ρ‹žy \4j€V2ΎΩϊ€Α1&bασβΥί?ό‹~Ρ?ιγ[Nώ!2ωHώσοίΞ#ΙνπHŠ-Iϊα Ί€bTjV"-Φ΅Œ i!Ζ5r=Voθͺ]T3mGXuœ•ˆλ“V_’"Υ™ j±ί-κDζa…qΰ  Ϊ +Qζ3³Β˜F]Bška '›p}RqφLΔtR|cλ½ιLζ‰$λΩο(z8Ά]‘q8@μ»ΦVϋΙωδ„;‘αζՏ_=Ž$‹υΪ’Φ₯‹vR;©XΎέ‰`&›pγξ~œ”Χƒ@ ŽXFΖθFήΗ¦mΞ€Ύφ{² ΡU§ΫKxEM”wΫ.'pΕΪpΔr£κ," Uσ°2Ηφœ¦Δf&*Ϋu=2ˆ@†σ„JM +=τσχ$|ωϋ«―ώόνχŸ>ӏίύόΫΗ_}ρ'y_έl)_~|υΟΟ?γ>Ύψζ›―?}ϊε‡ότω;Ζ~ωρGό«ώšσΏ=$ΩRΞVm½‚θEdsZ΄*·υΑύ€&#T +9§"„“›¬|L= Κχ€Έ]'Ϋρ½‘³^&Η‘9t…(j_ΞςiμŸL š_ΊκDμځχύf€ ίΏ#U]μHBωيƱ#pμb"BΫφΖ!§¦»fΧVυDτ0Πi9φ ‚ΉOξjΰI +dXΑθ3-ihšλ0AI‡zkF~+ Δ?ξΜ¨Ββ›#-"]f…Dλe‹Έ΅ϋŒθβ»ΐϊ0λ~vŽdυˆlrΰ­‰[Œ™|ΐ&Σ”έpΌbυŸΏείaΎ Ϋΐϊ dΊΈ\$ +Α΅Ε‚Ψg ζΐ]|ΥOΥώhΝGh{kPX5X=ŒΓυΟyΰστω(B:Ά~δΈAžύθη§ιπΨ,εΞ’ΓK·# s0·.‘Η=…8¬Κ£aΌbΊGωάƒίΠ›AQܟ7ΐΈšοΧ@•~Jΐ/(⊹ΛΙ―pša WΑΐύξƒŠίΆ@(tΜ.Νd©ζœ­M”ΣqεμΫλFΟŒkΘvq·+=‘g9ΉTΦΫ%Υ=»HfΟ ~Ύ»6jR¨-$κ*FΨυ°’W ρŒΔλ£YgPdX³%6όΙ_ύ.ΝH ΠNμ-;#CQ—­½@œσΞh»7‰†z|WˆΠγnΐΥI=εόY„’S6"·ε.Θ“œΙάpͺSŽk’“³)άÝIΫ{ΗυGys3§¦Uε²Κ—JψΠ=δDΰ%ϋM:š!φ6%ΘΦ·Ÿ ThDwΉQͺx¨hE+9ΕKΌsα4©g=ΰQpς ¨6&D΄j·š|ΤNŸUZOh¦=’« ίϊφΤφ˜[ΡΎOY\=Δ>†Ι“ξ μ”Kζ‘=C²γs²J!υ,‹§Μς;δ’fAœξj2EΣbΠt›Dh¨τ: .©Wι†$ύ;Τoίπ»^Άodϊ—f¬qžΈΥΏeaqcDOΖ ωš F +Έ!nzό «˜­*3cν ίιΒγ¦ΑΠΨΖ©ͺΓΡαΥ»C}‡RZep-|0Δ™šΌΜ΄58τͺmn”ΐœC–TΒH hοψδ1*¦½ΰ›<ΙyΠ—Ÿhl]\[ΘaΒΝΙnsM Ψ”veΤΚζ§΅sλŽM― ˜κ›Β―0ΉƒΙϋΞe λξ?ύXόlV‹₯<β}ξΨpCό½ΔΖΨΌΈνΦϊEOx2hΞ8Σηwξ{b±sVNψΓHοι5B΅aξΈN@ͺ 4΅ς"zΏj_ΉGϋ8A>3‘Ž)ΒΎ±ΐά+ΗΒq΄3ξz?―bP’•ΞΞ«"0άEIφΑߘ!š3Ω§«©ΑPΐΡ][ :4ΡΥ ξ£™AκOέ;š­½ξ§7ήΊΝ!gδˆ5D›MKOθ9*N΅žόFΉφ'βt +Č7€κέJY7βΝyΝh>2GšΖϊ͌πωdlε»ΩȐǒVκ +ve‚±ŽQcXηL`EΜI†ο{αλ[υ³“_{Κ¨Ž.Ypcˆ–ͺƒ6©²ŸΓ3Žœa‹ΧT΅Ό»ω5YŸ©φͺKίώΆφΟ¨εœ‘υδqdˆ [ΏUL£G!ŽˆO‘}icε\‡ΔϋΉ,ύN²qW~ΧΣUp„‘ΡcMΤεοg’Γ<&ΑSH«”½>}dη°!σκS;£ζΡέz:>ΫΊ‘T pO3{Ž Ρ5xu<#kΌ―ΞΝ‰B#b;φ!Fφ…όΕζΨuΑj?1”•lr€“Ή8vRΉθλDΤώ³φ™9e@[Γ†ΤvΒΌZ/9η²`c\ο‰ΎŠFΪψLag"[œF=pφ) Ζ‚€`’-½LΤ^έηΥߍH έn:–E[P+Τ=VWDBCΟΜΒ‰‘sg$+Gbw»Ώ({ο’ŒΔŸ«ίœzzα1ŒΪΨΣΞΑE*•£D…\M‹KpΟφ ΚΦ·ΰςξφˆhψ„‘T`"u„PωΛΜ€|ύ 37Έp°ΈΔVήLjDŠΎ°c‘7N³W³£ε«’ZΓUHτρͺ%»%/oxζκP 8”ΫΖXQρΰL ζŒΤuκθ2UI΄¦Τ’½—#η”3–J‹³Χn€φ¬Už`sf|ζ³Ω +HLΠΈ˜½C°ζΔ„2^―Ψ3ͺ¬{Ό€c“yΧ;ΣΒ‘iώAT29ΦΏ–Δ*Ϊνεšθ^υφy_€‹ΫΧΙΎή‘η "?ξρ§" r 5‚yXρB7ΧΏ\Ά§ŒϋΗZn{%ΧΘ8λdδ<βρΦϊ1·8±,ɏKNyκΪη›‹σPQοM4υΤο›v»˜ZΝνωY@7g^pιυ΅ˆf ώ΅"Σ³šuS«”MPX&0WΨχ ++_Ί"θe{π₯ΫvΩ?Π σ‘@\η³#λ:ςW^š} T”$ΟFΨ›tΆΥgrη*ζ―ςœl‰’ή¬R€ νΥDάBš©IςŒ[^ft™ΨDσrΆύ_™ΩΝD$C'ŒXlίΘqίΘ4-ίΎή©^kdΓϋ&T-G":RCG”YYΆΙΟ1‚a0ΥχΈσADRΝ~ +θ{‰ςK;ήT’`ΡFλwΠ8f?Ι(1RΗ4Cφ–Ÿ(U¨Σ"Yφ|L՟¦πΪg=DξZΦ2ά7H(xeχsNbΣπXΦ%Κ{ϊ}ξƒΔl‹OΌ*tϋِG§9€5ω…Δμσƒ·j‘F–μyŒ,Ύ»λνvάΒsδπ΅3J‚τ βj³EΒ;rdτxD ΦσFR‡Γ +ζQθ/Μωrv}–tΟ«4* #md} +Ηρά‰΄ŠM Ώ 'Z—Ζ°{Tά<΅ώk’ͺΑί•έϊπ Έγ9jΡΪsKΥ;XΕͺš8An¨Œ.jωFΦφΈυυŒ•hΑnF«’ηk'0geΝ@K›–—―I^.‹¬γxD7j#]΄(ΎσΕ;}ΚωPœ?¦tΧzίI*½ήV1”P/ΧνHƒΖ~™*{…8-FΊE{^“V{eEη“J’nΓ ηά"8ND΅ΫΜ­ΛZTι‹γSέ8„2Ζ%&ΊΧΜtφ€­$Τ †—ΛΤΧ2Η*―Fί±gPCFoϊ +‚ uζ%μι<₯(€ΰζΞ‘3λ|mlhδpY–Ή*^ΒΡΏκ¬A1ΉωOύϋζΫOŸΏψασǟϊαΣo? +ϊκPχ”z|ύψ毟?}όι_―ΎϋξΫ~ωρ/?ώ±_?~―‘?ύύέίΎΠƒ~αaόHού3ώόΫΏησιοΏ™ƒ$ς +ΗpC?ΤMt£ή/έ²Lά=8ΰ©ΰpqBι₯±²ύΕ4]ʌ’ Bž˜1 U]ίΗΖ ­ 'T ΪW[ +$#v1f#©ͺπσΘ”―9€;ΏQΥΗΜa§²―l%/λ`€EΤ ΛV›’Ω1E"γl}o$ƒ9M”γ4δ;k€*dŒ!p–€φ›-;Œ†3πL/γ²Δ$Ί¦ΓIψ΄X^r›o)!D”‰dΙ1BώŁ,g!ΦΈ&ΚνΖΔx%H₯+Σ#Ο2ίXΤNIλΚΤχ}ωΠ jώCjXZέόt5΅OlΦΎΧqάZ¦Φp&OΔ«>»ΌQ†ιKžQlΡBx;μΫΘvlχρFqOΗά£T&…ά&ϊξ8c†ˆ’²­‹βΣ=O·Ί ::ιΗαθJ›{V{Ώg†gϘΌ‘bΟΆ₯)\`+‘^˜¨lv&Κ³{žΎάόR―8 ₯.,δ X'Ϋu7JAazΔΌiYφЍ)ΘΥ±ΚAj° όώ;Lσ…91ξ’؈OδΝ]ά¨ˆ}Τ„i†§‘κ€ZlΈήUޟ; ?B/Ί°ί™KWΆ°KώΥγ„NμIjμγƌ ²χ2PΡ^η›λ3Η+PMΟΗΔ0ΡO3’Ό.’‹3Q‹μ”oΕΌRύόΠ‚Lκn±–ξk½p±ZOχR―qX ;bŽΐYl³Φ§9Ηνοόv8sε§U^%Τη-ώOAαt!_Θ¦`δ=Igς"ζψŒ[`˜I”:gόθ%‰[h- [ύΤd£f0:ΗξΧ“MωτΜ΄¦‘>S½gͺ˜r ¨CζGLμ$O΄V D&ͺξ_‹¦ετ΅ΝϋŠ€4gfG!η“r, +ΆΞiNχ(.Ρ«δ7k΅—³;! +Qjβk©ΧΩ&¨Ν“P=¦Ε83 Όc€μ•/Ξb4δœD*υ9Γb|–A3 ~8HΉη툦uνΎ}α2Α’°ΕΎΙχ_²ZΤΘΪΌLΎ:§”υϋ/mYΖqΫ R΄j$cΑŒμy6Ί₯> “φ©U-f@ˆΕΫŒ3uiˆlEϊ'%Ωp󲑕ArŸF}"!ιrwuŽ' +eΆͺ.¬ΜαŠΡ/SάWI‘σŽ*=¬¨±{œ‹ΔΤR·tΔΘΪΟ™‘ „d½κ!υΧ¨ΎβY£ξ+Η­(Ωj²Γogόng΅΄JŸo!U ¬WΒθΣ‰R΅Ρ:‘“t$$Άβy.€ tϋ,Aƒ8]Y=ˆˆ7Rˆ5οF>œ”;š.”ƒθXBό™d€¬‘Φ<;Ό@n«μ³J%οΓΧ’S‘ˆ«€'y•η5*Sc.DYŒΔΪ Q©Τ{ζ*-ΠάKΜ΄­72Ί‘0ˆRΎn£³gih/Žb¬TbO©ΌAxΛε³n4AFΌ²;‘‘NβXI·"Λ‰zu€[/γ&Ι‚'ΒΥ~ΜΨσΜ]Έ&DeQ¨€žI1Sš _UœιJύ„=}ΐιVi»ΟGΝ _ΦΛ‰ν—)ž…}χ́ϋˆΣHŽEσԁ2lDŸ–0ν,Βl9±„Ut-ڐ“λΘ‘逩ς–ξSvΉ•ΓBΥόFΣ€ψ+QΆΞ@tΖFτMŽ™ω@P…Ασ<Ό―Μ5HΕϋƘOoyήͺςŸ(>ϋl*|S·ŠΏΐx‡·ήrε—ΤmΧβTq!b9Ύΐz8>9­@¦8K7pBξJEZ¨γ= ΄`¦α™&κ°R؞Ÿi7-γƘ5•+ώm€Bj0PdvιZΚ!Iχ OΪ}νά™Ίω‘z™¬¬VιfιΑm«\Ν#ˆΡe"Σ6•©~ε=£Ο՘ΦςΛd[ΨΆAͺ`ζa 51K·νͺξ  ΈsπŠΉ{TYΪηe›*PwΕ¬Ή–’rΈœ~z$t˜Θ[Pφ ύ„¦DΫy 8ΆŽz‹ή=Χ vρ‘)Kη9ζ=bχc_†^¬€ϊΛ +ˆFΤΖi‘ΑΆj#aUδ"εŽΪcνHΆΝΠ –ΊLŽο π* –5[Op€“d§ή`xOνάρ`ύŠξˆ)t‘ΥRtε28±pΓΙ€ Λ,¦Τ+ƒ›’ίθŒwΉxξF“¦<ΆžΓ΅…’‚X3FΰΞ1NΧύ.±)P†‡˜wšfuίλt  PZ=(ŠΑΊά֞ρyfΡ£“υ_ (Εο’-ͺ |όžF­ži{Τ(‘,•α•ΝDaqb~—BŸOΐ')έΑB”aϋΟ³7]χ$νeEY=AςΫgΫΎΘ”&η€t³ž€Χ(Jυe”DfΡΧμv–ΏiυΌλ`…΄Sxίισ:6PΥZΙγ‹.,θF1ύ[uω-qΤ±ϊζΣ1βάTJ’iŒz9'&m’^Ž2mͺθΌN¬Σ+H[mn¨Η8(ΞΦΊOΫΤ£³1>ϊ¨­Li“ΕJ·™xΠdσ>: $nΥ6υ»z]B†ΡŸaw?Ύ€¨dΡχπ΅4S-ȁ­€cΆχtr§”p;"!1αξͺ½/ΡPx3«.]&jۍqβhmΣΜβΌ'~λͺ{vld#έΑ"’. ”„Z^€_gŒnΉ‡©ΛΔχΣLlQy«]‘ΗDoΤ³0£υB1zFhλtz(y!:θ˜ !—33KΆpž ΙƒΜ1‘΅ΎgVθΔcΤ4qγ緎 χœβ(JŸg₯ 1HG'³Ν~:z»Oz!?΅žΉ φb7†Fέ ͺ.Ξ7ζ*gζ“δE…τυœθfl»–Η€"”žΨK3"»ΠΞLQƒ’˜ξ[$‡•…oIf‹מ—$šh —γόUΎ3Š€‰l Nm…AzA΄ ΒpƞXœ;§΄h|Q₯Φ·©ˆ~¨Ώ©BΥ„'m¬wfΆ_ fœ[Ϋ(;&Rˆ&V™ΎΣ.ͺ—Τ@εI<.5M‰,³σp†΄λ6ώΓzΉυΨuTAψ―œGι{\Š%ŠO–ε8Θ‚x"3FΚΏη«Z½χ9π†[žšξήέλRUKΜ^[ίΗΙ₯ˆΫ5li₯aιu½ w΅Ζ6‘HΦ%6(\Π΄Η–!˜«О!©@{¬k‰Κσ +€>³ψέ΅Σζ p(mΜ*yr†S#a7eγ/K%}Z§±,ŠS'S\ΊκήSh +)πε΅bθ<ΧΡw­ρλl h`GBLœϋgojr`γΎ:‘Ζ²λΔΒwG”Ή|~' +―ΨHj0šSKW_k”όŒ]β¨WDπZ§XMς4zGŽπ•WL5Ύ*@E*Yΰ Ϋ„θ=S ΅•τπΘ΄ΊA:₯x™g^!ΙwΕ”(TZQv*ΪfGƒƒΚ7’c_©”Ÿ£|Ζυ½θβ1Ά[’ΨŠΠ +I:΄.wωˆοH<1XϊΎŒJ]E41f8ψ;βΨτ³;N΄iΈHηΤ/»@+δ:Δ₯ρσqφFΦ 2Ζ•Υ;:Τ¦'-ζ +˜2&>ڜK +ƒb.&8χΕJ\ρ5ηάšΦ54ΈŽΔεΕ` c<+ηB©[kK΅Ÿ;Aϋq4£υΊ™Οu%)1·ΠfΧΖΰΠjΖ’ά΅ˆy2£O#΄J½R9‚‹ΊXΚQtcgυw ΙĜέX^Y»U^=TZόμ(·–e"N$Hφ’tεZGx§ό8e!ΑxδΰΕƒΡό-©„€ΜΉ°9ΙJ4₯vα‰\%ΧTΧ:x‚Yeϊ ΅ˆ/qQ5Ζ4kfΙ͊ ίΔ½ŠHζŒyȨƁ?μσ ΦhΨiJ„Ή‘‡Τ°K„°ξ~T ά°Dm˜₯°2_Ιν¦‹>vj%Σ₯ΔϚ0ͺ.£"Ζ#΄¬πž ₯R»Έ/—Fx +ΤΞ΅CB4θ}kφΈ7• G$ΞγύLjŽ%¬7YoξnwƒD˜d―ΝΩ°$φ΅έ―Ν₯$l²λ―Z”5ήhF›!κ&5²ί€€J8R¬vΎž,ΧΆaΣ΄”T5’dξΠκΨαΑŠ{:₯Βvw\Mix!ŒuΝy­¦θϊ¦Ρ οA›ΣΊόrΑš>K‹‘•Ο‹b9}μώ|ϊΩϋη/ίΎ~~ϋτξΥϋŸoΏzρ+\=#β*Ÿά>ύζωύΫw»½ψόσΟ^ΏώπγΧOΟ―΄φ“Ϋ/Yω[ώπ/Ύε―oO‡₯›F¨[ςίύΘν»οωΗΈ½ψδφέ_ώχΗςνΣϟžώq{ρΩW~υόόζύ»—_ύσεΧo~zσκωΝχ/ΩŸϊολώψκν»—_<ύτσΛ§^ώξϋ·ΟΏτ᧏.·|Π»?ψv_ϊο?ιoRΞυŽxΖίύ»όψœ&аE“%`ŒΌύΈQzδβNžnι9Εj(κ,IP`υ&§· –% ύF!D·’±Θ*ή£Ρ­xΝΘ~«΄^’Δυ›°ν7#m‚$ύ +€¬½Σz,bDͺ>ΎŒEMͺ%d"a΄,θM”— ”ϊ}ΎŠŠzθA~4=ζς”7…εjΓ·!*Ε+V‹"έ4-Pΰu1±‘\ΰp Žώ4Ν›Έ^Φ­ύ%ށ!Άό$aZrΫ~ xώ@ΕdBF‰eψj O½dŽ4―½±qHςτ>t΄)Žj5ΞΪNBΈ±ξMΊž}>vu ?LRδεVετ“Ξ‰Ÿ½‹Α%Jb―ь#rj’ZΩ„ζΐŒlž9QΒΰΡbϋuoxj»'靉d܁ψ ό«Έ_«4Η Ÿb,1ΙτΠ,HήecTΟš³ίRύže)€=ρ ­Έy£G!ΰBZK-Ι‚NΆ9ϊΞwC„ΞL+΄·νΙχ?‘eΏpμͺr’­υπΚ5n]β-‰©ˆDBξHDVϊBλίΧ™ΤU&”(A½eδ)d=±‰^€VR…wK͈+œα‘μ₯$+#‚ΩFE)xΒΏ|+P…°©M DλidΤωΝRΎŒ”+ίBέ™ ΞͺόΓFΪμFjσhgΚeW +ΏF)ƒCeίU!}5Μ|εW‡S{"ήyH…Uy'ڈeΥ”Υ$Μ-Θ1έ©0εΝ… j0S¦‹Έ„η―ΘΗτ{Ja6“‹)VΤ–<& `zVͺœŽ₯“{φ?pΘ‘}Ÿ§Κ[„ƒαμljcΌ±Ž—άυ‰ aκ+»ϊ1EB¨Ώs爸 =κ<»©ΗΞ£ΈΦαΡΔ€βmμ ΠΡ20Γ¨†!Υ&%‘Ÿ +#έ›’˜Z‡j2ΝR 9Ή9ψΒ2?Qύ½½τ<…Π7L%ζΩZO―#ΈͺΑRύΙM27ΊŒ€ΘˆψΉξωHhz2mΑm$Ν3“€wζ3>X·ά₯iΆ’iΉ£‰^w₁#•_μT·Y£™ΕkRG?n΄₯Lˌ’λ³NΩ2“šYH1‘ΐ­μΛβ­νύˆePυΨH“.i$ˆ}u1κŒ,]«܁…dξ"ν=BcO“3UŸ^ˆ7b‡‰αγ:$ruN0kΟ,¨I¦°2βžs¬Q9v‘££vB‚ALσΌπςzڌΕ ΄Š’@,F–~%"‹O^D@ωΉΨj«?XFTΗ!Τ¨’Q‹"xF–έΝ",{%tδ›θh¬ΚJΕAyz—˜-vx.”Χϊ#ΪθJ­ΊcΚ'ˆ€5ί€+³2y_‡k9Ž8Λ΄ϋ€ΐΘε΄aHνΜΨ7U(Τgn*KMžΛ~`=R…Υ₯š pπ:;!θt qy›—9]Β+43Ύ€«θΨΏTY3ο䧝΅γ―»M'N’£’Ρ“κψαΆ9ŸΥs’όchhΡަϊΦ%ωPL]>ζΊμ¦ςs]QυrκcΤr< P"rΌ9βŽβ4rΧNœ.1uE4S€–Ε–©Ž\χP„θΝP,‹1 Χη2ΝCYzWψ™ήΩxBζ‹TM²£°ήπΊ1$tνKHYΌsδπhZgb±ΎjS ΣH‘uuΉςjά"Έ€ΫN©)(&'֍$s#ΔU<ΤVG₯Μkγ!υυqςΈFΜ’ωZ7"­v³y7&gUΕ‘¬―Ό–₯ϊpeLρ] "ϋ˜)OΉμX€ρ&XL +ϋΑσβ‘ΡΒώt‚ΎͺOΘE’&α +—w £‡*ΛH‘,Œ,>eDήFnχ.IκNn@#»Έο£#‘’ξaυ|¨$[Βt{Έωί\—Λͺ&Η„_ε,%c£Ί_μ•d-½²1ΨK1`£…$#ήήρEVυΛ 3œSΥ]•—iŽ+Cε‰₯½ΜύΘ Œ‰i:‘]˜ζ#->Rs)ι"ή™»>©½―K˜Τ˜7½`cvτ&†aξq·ŒλδΥ«ηrαΏUΑγ*Κ δ>DZkΎΏ³bŠr}[Χκ‰ˆΪ0• ί½|ΙB=…MQGdOWά_s΅κ#¦‡bΝΔ;%P} PΟ:©pj•6€8Φ;²ͺεξΌ(PΦMλτ†mM“}°(iδAne‡΅ϊ΅n©?WˆκΦ΄ +Ρ8šΌδP{Ώ· ([mSχι–MΎr­%A›ζRϊs›R ?¨++€tDtgĜοgΡΦ±S·)w¦π ušόΥB^·'Σ½2Ϊ ‰Sψ΅ Ά!GM§}GD“9ίW>(ώpΤπ٘Z†Β”Ζ!ZCˆ/βyNολ$3NΌ‘1‡/Dξs§ϋ™₯­D²ΕoΨ6»ωψA{ρ‚‹ΏΘmƒ0άuΌh+Z΄Ώ~΅ +—}μΝƒJξ–oNŠJΛ?ξ␠cΏι°‹ΔQUνφŽ27-υΓΊ[ΤΧλ>.BΌžŽeΤ#G€Έ -‰―V†5xή»sCrΖk«x³±ΝŽ5+Ωο+e¬Nr΄ρ•r•;Χl#9„E' Ώ|‘˜θIφmέ>—‹^΄Ε Q&*΅=Šh‡ŽrΘΖe{U‹0οœ2ΦFξ)vΆ3M f8!bˆedξj a«υϋ~€Δ 3Χ[xaο›κg;G?;NΥΒΧ?ηγ‹Tέ΄ΦI{ΓΩ­¬wDߝλeΌ­Ί€ή» W›ϋζ;GRsΒBΰΉ‘ΡΧk•XΊZυx{–^‰E5.Ν)Α΄^0`>Ζ +ca€•i%’B—‡_Δ­Ž*ΎΚƒ’£βB—υ™κςΞŠ₯ί‹8'λF4mͺω\‘Λΐ€iψΥ„ΗΓiͺœζšfHιˆδ)5šΔ3±C΅ΓX“»Κ–x½l‹/ήBςN'΅όΏΪ]¦3Kw•ΚY& Z±bT%Σαl‹ˆΓΟξK§€Π‹‹Δ™­@ΪEtGw§ΛUφŠJ‚τtW$'„δ€ΑŽ<¦g£2˜t NP½Ζ½P£8΅u߈gpvΑc:Τ φ©>£ΧR΅χΆj$ q؁ \*!­9|tΡΕ\ΟF[+ΠΥfμτ$μYΥΗrQνf7ρEλAΞφ+ 5»UR⿊Œ=aΤί3όυ€š25&΄ΚjbΟ†ͺξV½Αήφ ΡC)lDΤ‹6šl’μ«ΣιDψ’φ—ρΆjp;ξ‹βύ½ŽΰςΨ˜Α~“7kn>c*4’Ώ'³q‘( +Ψ·Žβqσ’ξNžxC²΄ŽW9ͺΙι­υJΗ—7‡ΖΑΝύZΣ~}ίβ…:¬‚Τδ~{μl.H.―ζG?oœ7τ•£ςip|¬7ΆρBw§m5G ?₯Γ.XΝΔhc₯ΛXf¦‹D»’~­kdΗ=ύ¬b{χ .Πρ/Λ°Ÿ7Έwuούtœ"‘m+Bιk(U +ZόUJϊω>ηΚ†—Φ5H'Ί“„Œ:j_™L£cSοoˆŽWΪIŸ/TΤ‘ >O?ϊIΖΑrK£I‘ΞdtFš‹M%Φ©GΟ:_%HΡΆξί»3xc0ƒ>ΘB8']φcΌ¬};~‘ΪQ1›T[Uސ1w°J˜mΖLΤΪΓ–Ηhl νωtˆ΅θ;ίΦΙ· \;±’/ρ ΅KUk›θλ“X4ycΗς…ΞΚ#/ςt]“Iz­ΣΈ΅•[i—ΓοΌνN)’λ[T2/`N±,Κ΅Λ‘m»'}γYΊf|Ή•„g™’„ΨŒω3{κΨ‰ΕaΥK!4vžSθψ2εωΝz6> *;«ύc/©»ΥΚ™ς!qΦv(ό΅N7ίθ"=?f1ΪI§r%|•Bτoηy"Δα‘ΫΊ-»ΒeϊΣe€SFⱦFEΥ]JX.‘¨‰†;s6:ΘΥ팟|­‰%Ξ¦IΥ7ΐΙyh$Dβ’κZiNv75sτ’\§3£B‰­!ЧψŒ…Μ +|¨8“™„‰ΐŠ•{Ζγ΅cy '§5ˆΖTΈ©kλήΉm +•ί¦ˆκγί¬“[…žΤx― ΘσφΊBη6jΥΕ©mΌ~ψΩC»«HSΫ”₯~W{CTαάΗΩω π’FΉΛXM˜˜ΚIυμΕ™Θ‚tω‚₯Δ Tκ‰αq΄)’kΜ?δΚηΚ£Ώψ'ό’Η·ϊ7Θτ$ωΧoηŽδŒΈ9]~ύpP™νiTΒU*Ξ°ΫR;WςdΪΠj ;OΫόͺα ? [΄M9Œ«"–Zb§ΡV’΄ξd¬1“αŘΆe>;«i”~iD??k κμ―\3ŸβΛ‚PMn³²g‹.ό6]˜₯|8£ž6‘ηRέCT« 9ΜΕξeKτΥ>2Ώ‰iB€₯'±³ΙHΖt’ϋ²Ο*§Eέ3w,ΨHρ#Υ~O/ΒξƒNω-/[ +΅‹0wςŠjκ³ΟΛΥθ^f?ˆ† €ω’ΓΙvτςEGχΙW«…7€ltυ‹ΩQοΤΣv± ¨ε¨ΊŒ{U¦±žUσ]οwΚ>h2>hΏpPCGšώς|ώK2>X ΉJ?οkzΚ;ρ<ΘΞηƒS§Ζͺ­Ϊϋ8’‘{›#ΒG=.?B}Αχ˜­ +ΥΦαΛ,uF—Zμ2NΫ,ΦΛ%e3Ÿ:€UκW―#σJ1@¨8Ή™ΘHεΑ| .Γ;9„Vv°^<™!•€8“3I{ŸBV|‘ΫoΖ•rŠM5!©Mo΄μSfόx%²+2fZ₯Νξq ŽhVΪΠΜ1!ΩΘ΄λz΄P"hU5CλJύ=_κλŸ?ϋύ§Οίτγw?φρgA_όI7©*εˏ―ώρωηοόοΗί|συ§OΏόπχŸ>ΗΪ/?ώ¨•Ρ_S·‡+#ŽŠ–sUλόpψl*3+uŽ―:άȕː“έ)Ÿ 6‰—ρΖ c½L’m»m%:•?Klk<ξώρ;Wϋ<9άuC‘wσlΦ@―©ΝŒτ3‘o1ΔβΏ‘& ΔοbŒŒL/ `Ή³E½z:ΩK*^§FνI>βAtTΟΒOϋ”™[’¦βlΩPŒΐτ ©P&έ΅ηΊΛ9eύΥE%9ϋ―ξΜ¨ωE―.>F&žΦΘhψΈ«κΧurΊέyf&˜7γ‡Θι«nV>FΥς›Κ”FON3pύ”Λ‹+?μ›^-«Π {B%£=­0;υwc_#ςΣι…{;_Rxe]…vNdDVΒΘ€=WΤΘΉ?Ke–:ΫΣΙA‡Τbw"ψκrΉ}μ€Ω,˜τ:#½Ό2‘mŸ™­‘Τ{©(νS—ˆU—>έΜ©Υ«2Šόfά6ŒΔ€ͺ"#WΘΏWVΌΔ@ʚȫλYβέΙ~uHAίΙ°λpΉι΄πλ~‡ŒŒHf<Νΰ€DiΩE«g|΄ύ•ŽV;%,M<ή’JIϊξρH +•ιVάm99£ΡT₯Η¦'ΐQπ:™'W>³Je½Δ·'§Eϋ™Χ{Λtw/Τί"Ηλ•τŽχΗx^~G”!b³ άv l§—Ή‘ŒΘΆd‚X5§fPVŽ/yϋˆ91`αΓoή>Ο-μœYb΅:αΖyφαΩR(Σ)Idν“eœ1τϊ€ίΈŽϊ’DΟζΧΕλα<:7ΞSZ¨†ημΨί¬φdΙΟrάΦW—9n½f^’­n‘SS,;”–\~ξq’υ½|ΞδΙ:Ϊ„u GJ'9+©”Ήf{€₯™!fιšJΐωΝΟ9R Ϊ_^‚\ΣτšΆ §:Ψ€ΦTeqΊΉϋXPy£ΞI<‘VΔ/.ϋoT^">†JυmΉdΰ›AΔΧ5n—ίf£ +Ά₯ΛΧΊκ―'}ω?žŠ ό7Cώ/εZ>ͺ€(*χ™ΟΛH$;)ၘΞ)έ<δ'= +ί,’?έ¨‚6Ί˜JώNΦY"v 9ωΐFAEo…~+€Š1Χ%Όίšγη_y;Ό1I5ΆC¬.Q5"D%‰Œγ– /ΆΏΎ 1―žτˆn΄ήˆ¬Χό κŸ4OL6£›Π’EΏΝ1ΆΡε ‰ζΓ Ή¦ς›zb$βΟΘ‹₯œ@ξΉτGyο9‰Œ’|ΉCI’Ί΄(–;q†Z ΈδγI‘“„zΦψ±a*­©ιΦ'yp]₯ΌΎΆO^ ΏšΆWn^\ζμbdžΤΊ«1‡Λe»Q‘ύ–’QμΞλ]§Γ)’PΕί$«Nqλ]NΌœΈ|W€fΠΑ€UZͺ<=<£ϊη‰ωΡ½Tc»$ž—γCn^―υ™8‡X,LS䬧(gYηεΕ˜ςž@“-KΊxζ•?gc "©U΅-ιΖ]Μ  7ΊœΤeΊ”:/ν2ΠμBSχiήƒ=·oνψωN¨°Α ©]Y‚½{Θ@ΎaΨY-8ύΫ― λΥWT†όcl¨:φœΉJγζf!½²ε'υȚ^€}ŽH8<0ϊ&›n°€ψQρZŽΞ›β}^™ΎΫS,¨ tχgRΛ¬τ~U‰œ­ςΛήε Ÿ\²©6ΗΘμ‘—±ά cΆύ)|ΉΛ^ŸσΧIwϋ'όά΅W]?Ό IuΨΐ]Ι)€8vΙσκiγe‹@]™ŸtΪwΨ’HJϋ9f£ϊ3ˆ₯R!Ϊο”Α™,ςāчΔWδΘυš+‰g!OΉΧQΙKθφύ m$ΑvΞH?m6όFͺ:v’έμ¬)†/ThΠ§‘‘ί¨ΚΞ?Fr b­Ή`ϋ Ήg5ΪΑ€gΨΦ=]Κ¬ZŸ›n΅`ΘY΅»₯O²Τ..hcj½-΅ƒ"N ά™˜h’ύjΙΣZΚ—am-’3C‘ΤŒΞϊ± :υω!°rΩφ ”Ώ½YΚ’σƒΊ€ίUΗμ‘ΏMέεΏ9&~ΖRŒ Κ6‘ύ‡KηŽΨΟ$*½Ι Vν…kΤƒ§ϊτς§8p mOΒT­ΡgΆέ;…ΎΛ˜ΈΆΣ‹5«fΨΈAδόεΩΗΡ /=α£’‘‚΄₯Tήκι)sͺβC{…@ιόDβCΰ9#Π›΅Ηo€‰Vθ5‰c,y:#zωDžDΕψP„τZΥβοNΚ3HXȚθ5K„ϋ»δX―ΖwQΤ«zt”\Oώx9‚Aζγ z1(₯z,”BfI‚ΨoάγΝO’oœπ!š ‘АŸέ—3™=?―bΆ'μΌ  +e +¨±“ΐοπΘ5ΌΞνSΒ%―‹ΕΠΕͺϋ²ͺn‰»‡œωq8ιQΥ™™‡l".vή)‹ότ΅† +}gΎνΞfGdy­ΞgxXŸ–.$3ΊηψNJFΦ#Ά6q²¨‘ΨπG —3²θ 7<χΔH΄e“ε@ξx%(θ―μ:ŒψOyάΘ?uί9ͺ‘}ρ†Τ†g!lFw‡<NρO;ήΣ=6Χ·žP5ώ3j-17ƒτΕΦ…t^Φ¦ύΩFΕή*o_βh5ΆΧˆkVιl ΘφΤ„L<ί b§·_Φ%Ή~&Ÿζ?T΄€BF8πXL ž—v―]ϋZ‚IΦͺΚeŒl§LxιμAΜIΔΗn‰ˆ­}^E3vgO·E₯5l· gSC˜‘ϋ-θυ”•C{σΆW–#Σ',5н½š?‚S(sIνΌί|ΰqvrΧ/ κ=@φΗ@ύOG +t8ΜΫ0² ^θ?ξΈλ72]Ξβ”άΏ‰Y!°.ˆΞ4ΉŸ?­M·ϊΒsΔvGΩΏpΧΙ―H€ηϊυ&θ}2x€ x±AJωŽ*’-ΟΪz…Oͺ₯MWΆΆ€¨'[6‘Ζ&FU‘ΫΦͺ^‘fϊ4š%ΘˆB#>Yη^±~71ςcΧ=μi |§6”R‰%Άpι”vι†7ύQOΊπ“=G§^Η)aNΊζΦq”έF5Ι5ϊzά,ΞΈΫΐ„)hΆoΨzίgΘHŒVH7Σ ©©MjΙ;δ/ΊkŠ&θέΙ*ΤΎ4ߎfc‚ύΆ£Œ„Gͺ ’IΈL=™ΪΜΧGI2Ε¨’Ή–ΖΫVΕ#QŒaΓ‘§W軝^Ž+Η#+₯θ—εηeV%!΄’XXRgš‘Ι‰ομΆVaΕW’iΚ‘:έΩ‘VΕf‘νgΰœ°΄–5ƒ΄²¦fίE Η](ΤI δZΘψNi.w»[!XυyΣΘ‚JaYAH’_{+G’AZ;΅[έ‹ώ{~”3uΌΝΆ]²γS$ύ”M"вbxδhΑIOΨ ϊΘ£Ε‡όΏτχ\7ά¦~#½¦HΥΦ|λύŽ„ηΗύ…%–s”W‘Χέ0ΨΆuσwjδΓfq_ΤμΑ"Δ©€˜]¨‡κ&εΕVψ²ΛΥή—F.χͺλ{Ά”•,=›H²‘Ή‰p½ΈHαΆϋ½&TA£?Ž ΫΓ]‡jΏN/ΝƏΆŒZτЁ`―B#ά―ς„ΤL;ςΩ―Fΐ_ιεOΞ/žΘΝmΏR·Υ‘ΚΌιŽ€ΛΉTε½Έμχmdην+tUΰœΖ.­B?(Œ.[šqΟrUΩU«6β+Ήw6ά"ΦUa |μ i@H`6.ηί¬—ΛͺΗ…_ε νΰΊ_’‘-3J$C#p0Α6Ωΰ·ΟϊΦξώ…!d l‰s–ͺΊ«kο½.)Φ zϊ―FeνΆ±΅Wτy$ +VR;‰h2ςDΖrHΎ°μ–ˆ\YwΨ•NΜZρΠΉ;AΗIΐ5Ο-ξn›γ5E9ʚP ©D ŸΊ75Κ“Ϊ«aK4š[½p%ϊΑξξ₯Χb½ή&ΡH0n΄TUuΣ½όO’EχZV”@&G«-乚“‰&Ρy>ώuLˆϊv;!.DΔήU‘Wpe‰›K¨ιXέͺx}Μ :KivΔrp{¦…ŠΠτκcωsό$ +βMΉy5-τηΈgχ;]8ΗOΝ“}g%X€r b­“hŽΖ«pw:ΚsΪουη«―?|όφ‡χψω§ο>όφφgA_όΙ™©”ςεΫWψα‡ŸώύφΕ7ί|ύώύ/?ώνηί±φΛ·?jε_τGαŸιAΏπ0ώJoίϊοψο?ώ9Ÿίώω›§FzXv„Υ=Δ:?4fιBΠε„αC€y6>"^’l™ ΒU9JύT…ΰδ„£@Š2Ίκ3½B+i ΄μΆ½N•]F²υΞkf¬Iύ’ žG¬€‰ƒg„t ˆΊ?vˆΠΘ>ιJϊxN MΛ.ϋ’Ωχ2!gkk΄Ψ―©L$Še–δYνWGk 4ƒΗ­˜Ήώ‹4ΉŒΌυn#ήΉΞ`<λœ'4žHχAττ•―$ψ ύπΨj‘z+Ί /\l7ΰ}’ΧΥΣr°Z&—¨nΔ³€ˆBu„¬#Μ{_™p ηό‚ΰ‚Τn¦žα@δϋ½ΣΆ΄gušόŠΈ£€ QBœ`ΌSωμμT’ο•y‘ϋ<™€Γ.Œδ΄πy±ρB-₯4£"Kέr˜$RΝ͏RίΈƒpΘζ«UΉ/΅)Iαy©ΗƒŠδGUI»Έ½**ͺεzX& οN3ϊθτ<θφ$ »ε_}°t‚©ΠΕΉΐ6 ›PΉ%ǜ«ή€θ*`žηωyυƒl.q'―7 Ξ8 &E‡©έΤij'c.Ϊhϊ<†ςFΒξ'•w½,ΓlΨΛ›:‘A™*υ +>—͍τ vΰŸAJ/‡Zg™oΏ§ΰ©)€Υ,£ψJAΆ`=Zτ§θaθς yη‹ΖηΣ¬ˆΆ~‘ηY‰\Φ]bYν―ϊ,T¦ocw―.#ΦJοL»Ρ½Φ{§ziJΰηˆ=;’ͺt‹e@%L±N.ξ".KΈΎ5<`Ž0Ρ0sO;†MŒΥV9^H»j¨xPT»5~#6‘=ςlαlΔ‡fAρw Εξun;sˆ +4YϋΩιx`cD8π’χ^Χxˆ*O‰ή9ΙMKZ5…=RœV7•ϊt™σΛΊζz©t=ΉΖΎ‘μ…κμ›«’΅’§ίZ{£8tSaΑΧ¨ΐM\^^r\»χ'ςNG¨g~@Σx–UΑ@λ6ϋS£z6ή¨ή=ʎ’_ΘqAΫ•ΖĎωJ’B=CBΧP§ΑμνC΄sόE|*υήΉJ4λ~½Ξ&ίΘθ>DΒ,HωΤΔγ;"b†ω$d¬4γ£RyEPξrY%•Œxewj:‡Υ]£†ΧοΡ³bή:λΛ*eΆ΅‘dƐί»-ΒSψίΕφ <7ΉtΈz–[’E‘α’q¨+υ“ώΆδΗ‹|ϊr·΄_―KfΨΖ™ξYx**ς?K8e’§ήΡ¬­GΥωŽ`¨C˜/"7ްŒnFœΉƒžƒ)FΣ‰Sg£†ϊ—¬n;$"‚gω€ϋ³‡ƒθzθ_FlΜWΪερ b€ œ}+ΞQ”―ZΔ±Λ_Ψ²£Y+τύ†ξk}J”ŸS·‘δm6Y«IήύΡ(ZΨτ‘Ύ&2NΙ°ΛΩ±~οt+ί±A«NŠ8ε"όŠ b§~νΌΩWζγ0Ί°g6υˆgUŠΝ[q"θTŸΓΟA@SΫΜβΘ&ƒUϊ:1TrΓ -”αΖ§­!Σ²’gήυP,Žͺ.Ν4(id2l ¨-5μς•?ξ\δή6/ +@ +όΞ―Μ5_‡έ‘xΥ('έ©ζΪLsεdΌπa Πl_16ϊάnS ?¨Ω₯UΑrρ»ϋ²hτΗzΦ”ήF’νψRΦ°;*¨Ž―¨o.ڸАxfbnŏd(ορ ζ ΥΎPΰΘ»!&–ή ¬={ +_₯18Π\{ΌRFj‚άƒDMΨIC_;₯’G™ ΕΤΘ;οL 7&=ϊd”‰―Τ΅`ΎN΄½ΐΣ-Dl§ΣXΕ–uυέ…j8σβ6Τ@sYIϋ!DΩέ¬ή‰ΓβιΪ'뀌—aˆ{5P½ήy£2ŸΕ₯Π^»υΛKͺ±₯ΣγA>(>Ϋ½Nιa7θT-Ί,L7‚|ηkΎŒr₯κ4)`;©¬/Λ―>&ϋnΣ^γAⴈς+κ»z;Ϋ`­yάΗ^"α}˜`ΟΠλ½μŸψ}ΒΗJF썬s³ βπΣπ 7‚Xu½&dk‰|η郔g·β# Άz@·΅ΪΤ]’l”v³ώŒ…šΓιVΨϊ ’6]Ή_;oΤζ‡'Ωόαqν@yγ°Ο‘]γVŸβa2θό$`Ω. ±―2bC.DΊξkοξCnuq$‰τ‘jΨΌ8jGΕυ4θ²k‘“`ŠGDVΘς‚(,/ +ΐμΦ¨xUd)/Ώίυΰΰ" +‰yoΓΟΗC6βAτŸσξŒHI+ΪΔ§€ΛΎ?ZF +ΟΎΏ‘ ©Œφ²nˆ¬,΄ίΔlέΘ'Lp£W j€²ΥuΏΦκ=…Xœζέ‰ΎυΦ%φ„±6$x%1Ξzeq¨'v¨pθϋGλΗ6!!ΡVOσXV4ωF~=ΎΤƒt]h+‡[ckš!ΤΑΜΤu¬π<„j”πf8Ξl2/Dw;!ρrvζΤm’ύmΣ>Ώ“/(έϋͺ%Aг˜fz}ηΰ›δΫRϊA*2λ}½ΔFΊvR&ΚO,PC θp„§Ο»Ι 9Αv>β|αx›LBHX@ω׈%y_ΒΨΜζΛHiˆ1¾Όλ`j₯χu1ΒLΈ”NΩΜgWKΑ€—c ύ0τ]Άθ +8’Τm/βΊΓxQ‘C’γ-„†ΐ&50ΟXθΎsε`υ«-Hh`#j͝#+ΌpδEIiFa-pΟE½pΟ7.]DG}ΦΝ H)iB/mEΦΓίDv‘°ΪŠέB…ΦΆvXΘHg»Hΐθ5\έ¨ύn ΦΠΕ@φΠXœi|6J”ŒΘp—{“ύ˜ž§FρHγQd€Ζςγμ;`›z@ Ϋn‘.ͺψ¦FMJ~‹Δό–H1En]#Ž^|,>$/ΊΒκvtfΨxΰ k$Ζή4Ή]§Kϋ¬›€2 1o(Ωd©ΓΒω{ΪώJK“mΠ!€¨’Η2!F"ƒx7:¬‚fB!Θΐ)φήRSΪΞȍœ³ŸβsΩάδΣJν1‡ΠmΌqΚ(*€[K \%%k‚£Z}εϊ"‘ŽΊ€9ΏλŸ–h”'([Ÿzxx M˜L,hOj£"¦3W`9l§0Ό‰kΚ‡Ά|οEΈ58΅θΤ²'^–φ_‡½i£&l>eldD—7M’υύ]'ιo +¨,icω"*€huο<¨ϊ½u ™x?W ³ΘV‘ƒDΐ;—T†ΓζTwΧ :J x +ΎμρΉ2z|¦θ₯ݝ½ΗγZ£°< +‚±σ+ σ.Δζ+6κωDΎα‹ZΊ²F_‹ˆΔz?‡=(•νŽ€HfPεX¨₯&ξ;sπχgnΏ―’$ΙΆ‰sΉΎMPr‘Qςΐο~ފ³jΨfͺ]…<‘^_€S–^^;jTμ° 7Β•GA NΓs^d}x#’ΞAΥ¬ma«4†Λ”L³b‹’(šQόN,U$Oͺκαž±XΗπf΄χ6Βή€§lI‘1wW ,,ςl‡©‘o•Ÿ^'5ιu’ˆο¨ +ŽˆŽ½όC(θA"€–cΘ6ZΘNOς‰€–λͺβΨ,Q BEΑ.9’_fTK5J§s p銡!8‡Š— ΰCrΕD endstream endobj 28 0 obj <>stream +H‰¬—έŽ]G…Ÿ€wΨ7H‚ξώ…«ΨA(ˆ((Βw#ΛqφDf)oΟ·ͺφήηŒ€;dŸΡœ5]έΥυ³jυΚϋLyn6ϊήW΅­±«u{ϋ«_?όΘηΛ―>=}ύώνΣϋǏo>ύ²ύθΕοlΟ5·UΎΨΎόλΣ§χΎ½xως«·o?ψξριΦ~±ύ–•ΰΓί|ϏοO›₯-ν₯mΙ½ώ%νυό·_l―φΏΛΫ—/Ή½ψκ›oί<=½ϋτρα›=|χξ§wožήύπ€AυίΧύωΝϋ―ϊεαρΗ‡?ώπώιOŸ?τΜΉuyπYχώμή}ν?’Ÿ»αωήωΜΈΖ?όoωώ:₯ς—^ΖVυ#ηνÁΪw¨Ί¬8R{YŽ΄œbM›Ν“XjΩηΚ}«΅οm­Ύ•VφάΫVΫΪΧ¨½ξ+Ωοc_3GφKo{J}‚Ξ=ΥΚώBκ +$6rŽέZ,ͺ©˜o?mΖ">Σ‘‘Η±¦RwnΨκ^κΘ }7γœRΩͺT­Σ…tiγœ‘δ|Ω+…³½’₯ΩήkşΑm·RlOV Ξ¨¬c«Δeϋ"6ƒΫτVΓ0γȘά|²C›e+ΉΡ_™pM’“pΏ%Έ +a*‡³ Γ†Z}NJΜΝ΄Ρ‹#£“—Σ°•†½υc]«Γ‘f₯ΔV΅Δ‘$(μζή³v›kΟΩχδ±―™ΙΛ–WΫ-΅q~—Užγ(‰cΝdΓΙνλ$Ψ™\ά!ΗΪiw’„aεTXΧφV;Y\ΆΦUΰN€κŠϋ΅ +―<ής{¬*„–W]͎ ε΄ΜͺAМύ: ζΧiQ•BHlD{ZZΞΝΡY¨!΅¦HžaΩL₯%dΆqXͺZΊΠΑβ’ΠŸ΅ΞΆδ5p;,/TΥDWλή†§φNxꞨΰβ–£ν„³έ­£ιRRI―Ύ/kνy–’ …¬uΛ1Žn€ve₯rP"bρ‘yε©$jG‹ \iΠ +Su’ΉΕΟξh°ͺQˆζ#ΐΥχg[JA@Ή.!β(i]Ύ¬€~ u4G,A=n™r9,Ι΄9J!ΜR<ˆc΅DT›,2θnˆχB^{5•ή‰R$„LΧFΫΦ`ΗtγBό{{°hR‡9gΆλ2ΔΌΪW Οωχœ…Ω©ά2iŠκΒΫˆŸ­θ\eciη–7ƒ‚ΈPδ_ϋYž01αhΩΞvŒ’w¬d± +Ίt€/ŠRȚV‘OΛEάUp ΣΖΪ„”άΒr–™±d#,k«X©Ρa NΜ‹Ζ²«VΚΉυ)€“κy4ιΑ³V0Hήœ°θDJ~Ot–σKΛCPΔƒ­Y !ϋ2‹ιH;xΐΰq‚ΆV.Cͺ.ͺ}iΘ )d94H«†e>γS`Ηζ„’™œ ΛBO ΎY‚‚3•―ŽT[κΡΞΦ‘χA}8Кδ}Ηr£uΚ–Qhg!EYp{ζ5mmΖ2ΈΊμusΓ°³βΥgYgΝ’Š1nEΖ„g–'ŠπΗ΅³Όλ‰aVΪ(Κ{ή!NΗ㠐J=NP%”ZΤ7‚4m.$ΞD)geςZ‡kή!ΪΛYχ I†H팰Š3υ™.†GΠΜw”:`G‘ώξλ†λ¬@ΒyΒ”Ί,›&―PfCœP‰` .ΰ@LΪΜ-‘k‡e9β/ί†œ₯σ‘ΒW²ώ Αۜπ\(Γ¬O ίf•ΊIj?„¨Tα‰Š….T~['Ρ'qiΜιGέ!Œqςαν -τ“Β•KP¬Κ`’€A4,ΉrΠzŽF—!:}Yˆζ+^ŸWȘX4 U εω5έ’m2ƒΜWh~υΊ―λ@–Uc>»š,n κ"MλœX +‘2„ΘΤ™Λ–<Ξ’@ς"8o_„"sb]Obk!ͺb!ƒ~t ”qNM_ίN"Χg‡₯ώ²ќ5—³ωh0dΞ2Ε‘¬/Xa!M―Œ!Ύ«AdΟ™ς—ΪΌι-ό„ΕœCι›κ<€giΔ°ΨwΘΕ»’Φ“Έ’ΞΪšs9P5·Ή Qζ…ˆ`Μ΅C»ΎuK‘A­.dάΞ@—ΎΦlΠήΥ:NΆŽqh‰ω\ρˆ‘4ψ<}ύχήj)DŒ?HΒ°«Η—Σo֐!α\7f2d\4-gΓ’ΗΐmCͺ,GŠΚBΘςξν!κ…ΰz³τ’d$ϋ  7wq;ΆκΌι)ιV κ&1ΒπnΞq₯ηqCͺ?rΖuΙQ:–4ΤΡqVρaκ’τ]<“NΔ-«tm½_Η/Œf3¬ˆPˆ 3hΌ"·@X‡Z7gS +άκ8τ€i2΄sΐΉ€;?“όΜ’νnш7b‘μμλΡ› u6Ei%Ÿ·Κτ±\τήԍŠ€Ω…„z‡ϊ¨wλ˜Β©šŽqΜ{dκ₯ZNΛΥ p¨ΟτςΥwˆlΘN,ͺχΘ…œ‘ν>«―u(.Yv\UMΥ X'/΄Dkg6…Ζ„gL<#Δ«Δ|Ξ6ΝY'$;/KΣDs42@σ΅9±{9ηϋ^*λ°$› Ε(4—" ƒYcΌ.οL―•^oHά“Ω¦Ύ-ێGΥTΞ7DΟ’|yC‰_Σ­j +@S₯αD­ϊΣ‰„³ΟιΩΊοGPΥ‡7š_ιΊζͺ¬ Ωβ',»ωΤƒUά‡_r–Aξk]ΡΫvF‰ΆΫWŸΒe^(s*E€iΘEn8ΏTa'νz:―!ρŽυImΉŠ€ΧS:y ³ΘC T.Y.$n­™αRFk€yOΛΩS4I΄ΘJRΧ !³}Ν dœo=h}:‘™CΦlΩ…dOj<ΨΆOιε ζΚr|έ­ƒι#ΉR†Σs!*@«ΧD”ΝvL—qνœΑ9ŠH€K<Ε-ΗΡLBΰ|xΝ#14ώͺT5ίΧ5ITΛ΅RΨνF6G\8ϊήαT YΦCέΌYB©?ΛΌGΈvΆ“π.Ξ™MΟ¨>›ZzVΚ]%)ˆΈU3η*Ζ¦ωdϋ,Θχξs§ό‘ΰ¬ŽΛΣݘ‘λδaZR2τ…? 9›Ÿˆ©\ρLžλxF%ΝYΙξgDp’5ξ).IU +q23λΨΩrΩPμˆ5βŒV ν"Ί£»S‡εΜͺ +ιι TΡH&MI“ζ:vŠ7”/T>NΰτΡΔz-€Ε!Ίž7vL―ލνκT0n*ΨQ}’ΦΦο…¨iή°U{€"4;I%£Ρd Ϋv7ΪY6c§' dΟΘ¨XΉZρξ‰nΈ”Φƒ›mW +2;jv«€DdΥθΥΉΈ¦1o΄ψGω"Mˆj=π#NV¨F dΛξΗN‘‹¬±ΜΞdε ΒχΣQDδM.I†Ηατ"{©βeΌ@(…[U—„&ž«eΝεAjΙδΨnΰfΝΝgLΚ§B4°“ΩΈHE£Άϊϋu.7Σ"NΌCԈ<β)§QΩκAxΣ‹CιžΓ™±Fn£.ηϋˆ±%«‚ˆIg ½ΗΞζRδςΪi~τσΖyCW|5’†?³†σ–“‰¬ςμ΄«ζhΆSvΑj­μ°L\Ζ23]$>συk΅=ύ¬‚»{!.P»£qQΓ~ήΰήtDN̘ηΏHΈΈ™τ΅"C₯ψ«d<${&ΗJΕZ«‡ΞΤBΟl$dͺ#ϋΚdšτ½”vΓηƒŠ:H9Ÿ„7 άmΉΥ‰άG]Ύ8ΠTbzτ¬σU‚δQŒΈοΞΰ ‘Ϋ€²NQCΦηΗN܍‘΅OΗnfzΐ'=*@#Ό1Ύ’«wΘζΖ)λEUQΙș¨’”Qσ,΄ύ ±ͺκ»uς-ΣήU¨ΎτΓ IΜτΩw°ΆIA—X4ycτ(YιΓ‹<]Χd’^λ4nmi ύόlΒΉ‹έΟη\ΙͺO tJΦΌ9@VρtψĊqwzrz:&dΰΞ@rκρΞΉύ•ΈƒρbΦ³sJ’‘Ι΅BGΊ RJγΒ•ψ\±σ’ RπΖ; ΞΏ |σz‡„LΞθΈΧ:Ω’Χ"-G?šMS(@=–―εi\±€°AΎC·©fύ…Έk]FmΤ €ŽΨ8L± ρB΄ΘNι³Ώ(Λϋ…ψ€eή†½λ”¬„L—ρBΈζήύΌP IA(fΰ“4ζχbu§#O&^ΐœb™]2ˆiۈέΒp«Q׌/·’π,S”›1f―Q;±Ψ©Η <„ΠD -β»nŽ/Sžί#’±y_ω€ͺμdŸΘ€ξbT+§bΕo‘Ψ8…ΏΦIT>??„•\§t*WΒW)Τ ιA~žg9gθvσI$έП*IφAΊΘd055*‚¨pœT,,HW±ͺGbβΞΤ\ru;γ'”θ‘8›&Uί@Έ½ΘC#€.ͺ•ζd“v3G+εv΄ΌΚΜF…4[),4BΆ™β32+’K•MήΏ›hυ Δ1½[%Λ=y§δ-¨Ο»Ρ νά6…ςΕSDυφ―λ_H‰„vτAγM>νu…ΞmΤJU3ΏώΑu^‡vΠFšΪ\ )―Ϋλ_D˜ϋˆ/^"˜ιΒ¬&UΙ«rR={q&~xΘ κ¦—ƒPI‡uUλνq΄)’kΜ?δΚηΚΏΧί?ύ‹~Ρ?ιν[ύ›ίΔΪoΙώύλΉ#9#nNa'ιƒ>Tf{•τ•ˆ›z‘Ϋ©rΉΩ jT2jΐ§Ma›Ϊ0ό<.Œn=—"j”ΞNPqδhd€ά)1Γί)€˜ν:Κ|6V³*¨ΫQH#ωωY€UŒb²ζλkζ³{Ίi*.³²c‹.좚Ύjβl‘Mυtv©”@χ ²¬ŠΙKν&ΝχU”Δ0‘Ώ’“Ψ)‚­1œΘΎ.UύΪ­™œ-bPΜΠΌχLL©Η< έΈ}Π‰έ2Η+Σ,&Dΐά}ΣΤgΛ ͺΟ½Μv4Y4~0Rς>Υ(p/2ΊίαHσΣ3|Ύά~[Ε|λ²r»ž«ˆ]@-!κ46C #¬N•₯ο2BtuυU +²!GΩU]-ί1t{Ί‡/¨$@BΌzΑ[Υh›ΜΓ1 +θ/ͺ2lbΥ²T—9¬ 9598t*Bzι„δ’gΪ±sW šΠIΠΎW\dͺ0!mΞΨ™Ζ€ŽcD@]vΞ“…5ΑgJ>ξ;GXΟς£ϋ €˜2cόŽKξόεηWM”οV&—τr¨§ΖŒw€ω`§?XιMƒΔ„lΔB­w]Ό~w.sG΄ΠΚΤg.]υ¨X:¦“bΙΚΉK©Φιs%Λ­J +ΥAΦfF ήλ2%΅¨Rσ:―τΤqŠI2©Πά(α1Ό“Cψh£ηΕσ`2 ˆ™‰?@ΪϋΤq`Šά|3n”ClŠAImzŸ5—–2έ{cΧ7511³*αT‡ͺ‘ˆ§-Žƒ%Zέ2νΊ!”Ύj—j: ΎΠΊCBKΔ—ΓΏόκηOίώπαΣ?ύψέΟΏΎύUΠgΡ…k¦JωόνΛ~ϊω‡ϋφΩΧ_υαΓ/ρΣ§οXϋω۟΅ςoϊkήφ0edQΉΰ\Υ9›M&wΟπ•’˜πM0|V9Ψ1Ί5+ί¬2‘ζc ²SiΛYΓHK$qψ³Σ’-\‘‘κHω₯FV=0Nή’†GπΚΘ¨6vή"*ΣΛBκ}ξ8V“._€iε}<YR­ύ¬ΥθPυC“Δӟ@¨™QΣ‹n]L=T ­‘biΦkς% ,׌ΗυΜΌμσΚ!•€5™Ϋ©¬LŒΊeϋ€.‘Kαςz1κΫο―ς›?°]π·2σΫ¦igqχΗƒŽ©A•ˆζπΑ­uEΏ‘τK’Φν(­QϋΦF:X-Φ‘œ eΩ·κYƒŒ#€φγ'μ—₯Ν6t +#d!ΨͺΘ½4Λw7ͺOz1™EϊRο.€%+& +ΘD+₯SVYq₯JΊ‚š»§‹ΞεWCνζ5 2HY©Ύvβ @Υ‘#ސβ ³Ÿ‘ύΤG,@Šƒ·ΟW’οκτ:Ωu½Rl}Δΐ·uΏrGxθ9δ¦ͺ8‘kρΠECγoλ·wΌqΦsT ½¨»8Φvη­Ϋ<ς@Λ5̊Η-vκ ¬1I"‚%,>«c¦.ς +Cy!μΚνλ"οΎD+‘§Θ‘³EΆ» ¦Ϋ"§'¬#}€eο°1~.H^e?ΔaA°zmLАαmθc#r-~#©Σβ«9œϋ€Z>>tΪQ5VTŸ‘§ +=ž¬ΡωΒ…ρΜΕOΫψGωέΘ¦]A§ΙxΘ0©ρB—ΏτξΛ¨/9tφΉ|ub<1lΟ"IνΈ’cήυΜ‡΄Ki·Ωo[_έ€Έ&I‡¬K‡S= 5fΞS±©;?-Φ»‚)hί/€ΠyxЎa!πNτΈrΩ‚KyVM›¬T#ΒΉˆ†ΘAΗ;eΈσžfΉ¦κwΪv4D¨θΌS1H\׍― ς:FIΰH;uβZnϋ»+ŠˆΣ§S]-R ΐͺρφΩ׏έζ ξ +Ž₯β«ͺb`»κΦKΰwωGκuΙγhΡ8©Οœ ”dAυBΝ0“Ζώ¬—ΙŠfΗ…_₯–’±Qfδl―4,½²1ΨK!iŒ$hZ½½Οw"ο_­½‘Ίι:•™7‡ˆ3€pͺγJΧ£0¦“0β{> ΪYΟφ\ό 2fgΑrΝ΅ΦE^<κ~Š‹>_Hc­.J +woΖΎ€F‡ΩόΨ6ΘΡc½ƒΠρ ΏN„·ίŸ‘QΧΥΜPήH„^έ6Uχ‡"ŠMIŸθ“—F%œ νή™IW!‘{ŒΜω>sι9u9 Ω―ΜΞΞonέh"ρpΎβŠj³3Ά˜ YξD|Τΐʈ;D6κκρbΓ"šΓWV ”VŽϋ€W>l_^pϊΥD΅ΧΝVΚξlγδžβАΆ(RγΓ)<ύ^9’؞ί«™%υέμ²­:²­yrβςνΙt½ Ε]Τ!ώnŠωα{υσ^ω<³=t―΄1vσσΙ‚^Ζ7Υ|}½ͺ3,¦P‹)’ΧδuŠ#Q…μ;yhΛ7MίιFΡ±ΌΫ-8ν"’MΟΤW΅-ιΖΦ}+YθIτ˜Έ‚eΊ”:φ'μ2Ρμφ€σΧΒŸΫΈΦƒ k-gD]²ͺ~²#'ΤΔƒ²΅ƒ"{ζRŠ32=σsβuG +Ν^φΜt}]ΪλOf!ΰ +rβΞD +z¦˜20U›ΰxπeΛΐ5ύxœL&σΩ-†›ωIΰΗΌ{˜2ΥB¦Šε<§Β ΩρrΟζOfj©κͺ·iΖ’B¨Yβƒγΐ ».x4δ(½?›ξ)ΨyqΐΨι=Υ'aSשּׁώ.°WΆΏ¨ ωnl¨Ί^K+ζ5©ο›ž;ε'uȚ=TaεqD:ΐβ{ΰH%νaŸ¨₯βΖGXyΦΙ©·“lΥS+(ιsυ0‰εv‘ΧM!Fξ΄mmn΄YΥjΛ¦Τ)Tλι’ždžμ*"Κό΄eε/5N/Z¬άΫf)“Τ1R^§,œΙ*€oΔhΧΜ™3pΣ*’ZςŽΣCΞΌΌ:|ψPŽ2'έVFR±Θπ™Ω ŠoTƒΧ’*]$rLƒΦ_3ƒl'j^R 9Ή"Fcj‚ZXμKͺώœj|C˜¦bν‘μ©i«ξ] 6―ct.hIΌ-έΏΚ‰D€G7:Ξύδ*Θ©n4 —b–Hgr$›˜Π‹Τ‘―α6_τ—mΫ@υ§5ӘΰQ¨ώVχ%ύΪΧ3J£ΦUύZ7νŒ•”]’ξ7b+¬]γ.¨‡!.ιME\P]Δν.L£<Ε§'—uιeρ¨lIXΚ7œ[ΗG(ςέCβαΚH+V,6²μfά&Τ nώο΅μ2ΝZN;0HoΘ5K‚ϋφͺ±^qσ2/ΆžψΥq ‡GC*.ωx‚zϋ ”R]θ$H“‘4b7|0;‡ $Ο<4ΰA3A¦2CΫ'W2y!>V₯‘šΘ:gb*ͺ%:ΤX `‡/b‚ΤΦΉ}JΈ„ϊ=ͺlΙΟ­ͺΪήδdΜ—³IY-‘‡h’R"n‰U€ίH2PαCΰAYτλ4•|°©m›•)² [ΩsŒλΧέρΞ'3ΐ©—ewΔvmέt˜\LjlODYΖ%²œ'm$ΡΚy”=‡(‡;σG]χ₯‹Δ#9‰ΎΦΤ§cV7ΘAίD-Ϊπ ε@bΟχ·ŽQK ˆ©€vvήψΗ‰`ٟ™ͺ4:!&ξPZ’kaz”ωΝ[9­!w>‚β–"fΓρΆAΖΡΞΎ‘•›ά6q39 β_΄μδ!P$‘σΉεe„ΒήΥ₯ˎ%iUUgΫαΦ²’e톬iop.jR@κ6cΝrι»)Β4G]’^Kd©]θS0Φ$ Ji@~v]ύ֜6™·έ³*˜™6Α>Lώ’Ι#ΜI‰ˆ¨‰“yLρ^έωΐ±'τ퉧zΤ{}5$ ξ`τm$™~‘Ο'ŽΉϋkfzœΞ)Ή!γΔE ]Ύi=}έ·ΦθtΤ@\D^ϋΒ]›^ιΡ\Kͺ+n χIΰ3υΐ»hΝ,9Ž*˜ƒ;”ν+υFOͺ₯4WΆ +Ϊ͌΄€Ϊ„K›2'¦­<υ*0_¦MΓ!DΑΐJ}τ,"ΧΈ€―Η:Μ|Θu„έc€£ΞΊυ CŒnqJ› ltΑ™Ύ˜'=ψʞ£S7„ΣkΏξ¦Χ<.υΣO‰ΫΡΥό˜ΧΣ«›`K +h0ΝφLλη†jL9€D*ΧκjΘ₯Νi9QμoA5vΊKξX•Nνλ64mΖ}ΫΈ>Β3WΏόΗsa212κ9fΒQ… .¨ϊH§β™Fp‹b‡΅Zšύ( 2γXδ&΅KJΡcοyέΌmΨ€•DΒriܛw™–šΞiB9ŠcW(χYμϊζΐΟΠ‘4Φ«ΨH^άkΑ«Δ;Ž{ό’(h”w‰Θ Π₯Λ€Š5$ΉΩ\ψzΝ‹΅ΣΡ8=AnAΪ€¨…Ψ-€ΘΧL›vΠQΗz™#λmAψ*: ϋλ(Žϊόξ― )»Κεΐ‘τχHp0ΣKΕ„KΩ{9HAσ„8Ώx§Ω‘ΉφδΊκΟG“N"ΰ°«Ά/ΠΒεΕΖ‹:Š,# +‡υJͺS˜«xΊ@j9eΕΌ˜SγΞ!Ž˜πAΎΧρ ’ψQ±±ΚΖ#Σ mω:¦+¨„πσ\c˜Ζ‘i°·ΛPΌ°UώΣλ„Ξ‘CmtΌΊ=½—Hhdœ{έ€Ωnς6ηωy΅X–75,5ά€γܚΩτ„ƒUOsc&Ϊ-Ξb7ω a‚užΎ^ΠNØ +lΣ#G΅+Ζp>OhD‘&‰C@¬³Μ$ΰU’j–KόBΝ„ΐν)v²$ΝwΌFHv“•”’ƒœ·¬Ίd2¬υC ½a…Ξ!V0’(„PJv¦]Œτ™κ³S½4Υ›ς±±“=Aτ€xxί=JY40JΈα¦sS3xςΗΐ«¨#4Ί„ΊNF)AΨsG’γυΉUλŠό₯§ŸT@Δ!a§ΤΟg‚b£@sš-Φ™k v8dWη'5‡ΜΩΩ™Γ=•rδ6 ·f€΄σπZn}πί$5ŽΆϋφ•„ΣZŒB©~™?Η†οͺtΊ 9]υΖ­’‰ί;£Άυ:.ddΉuŒ3²ηω Π-–•.Ε:yΈˆ·,ΰFψΦp€-’DCώ'6cλΙ…NLδc\$ό°lžZΰ³N€ΩD?:fΣ|ΘSΧΠΙ’σ ,%ά¨F±{ΫΔά’ ¬ύξt6]ΙCUBν½ΡΉ Ί‰xΊHτ InΪ^… I2§Σ2,x’sΰόI¬W€‘tB†@ƒΜΊJΞIsUxμE ΝKh:wP!–w*-‘Α‡\ͺωύδέιότc6Fσ³ Σ₯π‘Q=”;³"­°ο„8Σ6΄:φ ψΝ/‰ +e†@Χ¨5Φα‡@φφ!ζ9ώ"<•ϊμ\₯š{‰ΆψF˜Z"a €|ο$Η ψhΆžΛ–f|T*_α.Χq½(™dΔ+»3qΕ#^δχwάYV5Ÿ=ΙZϋ˜·η©Ξ‹ͺΚ`#‘tΈzξΔ#ι6€Υπ§+a°qΫοJί£8MEΎεR>΄m¦{–Κ; €Δ'ε#M&՜^6<ͺr?φξΒ|αBν݌φεΔ<ΗR|¦σ¦:|£†bν,ΓrΈ@ ’/0 œv"Χ"ΐ q2lؘOν@ϋ_€³―ΪώzζΔήzέψ) Jmτ…²U*>Ζ ΠχΊ;¬υ+QώŸΊ]Tͺm6½f’vMΓς|[Κ`°η+25.SΧ‰KάΓbl*)9(ΜPL7jοDˆ@¬Ψ 6κwηΓΞΈ2o‡Ρυ€m€ΐ}υŠg‘Ύ1(έ捣ͺΉ}?5ΡB$άdÞ$’₯S”<Τ·άδ†ΠkYΚ„O ZK~άyΧb,^)#& ”Ζ426D³‰cŒΨ¨‘Onϊ4ŠΩuo闐ΏsΗ+³:ώl,‘wΧa{.€Ξ΄™b/2>|(΄ΠTžZ(ΏκcS©q‚ΦΛΕoοκͺ0%ΌnHΆk€θtΊ­¦ρ.¨Ž-ΠPί€V_¬2t-S,ρΖ\ρ#M˜±Σρ ΅‘Ο\?λʌ΄ΛΑm‘T:M{‚*8ΙΦθΘkYΠ“G‡ ΟJ>ΘIeΤΓΪ –—κΥA`ΙρATχ]ζu]%ˊΈT ½!AUϊSqΟ™|± ΉuŒΗgX—Θ(Γz_DΡΣ΄§›λ(† TRTc}bοΝΐΪ$uηJ/H8Π\{ΌRFj‚܃ĝ°“†Ύ;₯’G™ Λ4ςδ£f/!­ˆ>εEb£” +σmTήΕΥ¨D^°=₯/"GPΦν»‹.²–«A¬€ύ’lΧnVοƒxg₯φ]'}Χ0xΔ>ˆ¨ήw>(™©ψόYMήϊυ’jlιτx‘ΫΕg{ΦιU»A§jΡ…0½ςο|-͝&ΤΩδ[,“]ŒάΔαmΪkΌHœφE½z;k$χ±—HxίK‘ΧρOόžπ±’±_4²ne…vˆΓOΓ€4ή.5![Kδ;OΤζ<«Έu>RΓ­t{P«²K\[ ₯=l§Έ2^jΆR ΡΌΧώAΔξ+χ»σAζ‡'Ωόαqν@yγΐηtΩό1υι&ƒδ'€e»,ľʈ Ήρβz6½›Qη2#¨ˆ#I|€UΓζ3―pT”§%ΖP±ΠΙNi0Ι:’ο(7²|ΠΛ«_„Τ7 ύ­|~?χΑΑε›š δωv<2‡κŠνC9_ό<#RŠRΣ|Pƒτ‘0Ρ’ePˆxφEόFδPν³Ž&²²`8¦ΜΦ‹όΒͺ©”a]M ΠtAΉ°$ +±8ΝΟ!]υ&:ά„±"~ο%1Ξz₯C.KœΨE… +uϊμvœj"BB,’­.MUξ«‘Ιςηρ₯€g™&ΠV·ΖΚ“jš!ΤΑΜΤu¬p:„j7i8Ξl2/’ZΗNHΌœYίb€ω·¨γό&_Q{φUK$Η²i¦'.lR <Ή:ƒšχ¨GB¬΅ΩibθO<εv'ŒόI§Dyy’f―ΣΝ³—KΊC’τυξ S%°νZ~ό‚TdΦϋz‰2tνμ[4btA―Πq!vvϊœ―–Β;Ακ/ε€ ΅G\šώ#·M†M'dHZDί"³ώd¨±mŽž(”ƒš|φMK”# IMZ$~gν‘; /ο7h$η–—©τ;Ό&Σ£\7άNPš'©TΖλΆ$2΄ΟΣ½¦έϋ€φλχZ^ζLJ Δ…ιυD >‘cŸΔMͺžL‹\Ύ6p‘:ώXj«ZbΡaN„Š/Ϊc]KΊAžw Υώ˜g΅4žαζ„Ξ!ζΔ%΅Κ2ώ• p=ί0†ΧιέΨb²j―ι}ZYΡG¨RύœφY§ΡkdΧ£­Œυ#©ΒJlγ·we άx0(Eέ4ά*ΦΔαξς1/*n±%ŽŽ‘ΛΜ6©AΠ·0N—KΠ’„Ο2Š5uJI’™τzΨ§€€•ΙηώιQμw ‡β/™„WρZIŸΟˆ䃋%B°ͺΜΫ5bI9—3‚q²ŽζgOe,#Χ!€J1žΧΕλTvRœ²›Οt=‹uϊf‘E}ψΊΛΠΟ25ι.pDma_Δuι·.ΝD”t_ΐpΚ:hjͺΑ€Φ‹²<μ’Ψ@ ŸΔ,k°OŽTΦYϋΘ‹ψJ64EdZvΟ“Ζΐ=?qιAtΤ΄ŸΌŠ€ιD}‘ΞzjηΘ.UMn₯έ‹ΨοTUΪF‡8ΤλfΎλάl谚 @f<ƒ54&{4DuΔΖΔX ‘αΟMδ·’‘ψ7ΥιΊύ:?ΘΔΗάΉΈ`_z@ε`%DφjΔΊ•”_$ζwDŠyΧ©ΐ·G/>τΖέα ε”tfΨxβ Ρςng.ςt.ν³N±܁])&Kρ…σχτσ•7¬HH1@Τi;² F*ƒψl\‘mXs΅ΘΔΑKƒtΪΞȍœœμμ” AΡHω΄:$A¨μθ6ž8(*€[Λ}π=#%§j]Ϊ‹DUuIk}Χu=Σ(ιPB]棂²ηd‚›Ϊ¨*ϋWΘi„š*—%Nί`ΛWρ^δŒΨ9ƒS«f³Ρ1ZVΈŒ-6=nx%zΟΘΐ•   +ŠbF–ν Ψc™q$Ώ=ίͺκήϋwQbkfΉ»wͺΦΎ„UΧ̍’κj“λ\ι2?4ΊίΏ9%~|&Ε²Š΄Υ‡PY +!mͺ~»μ„.@Ho~Jˆ­ž3[σεjΥ½j)ρ“CώΛ>™Υξ½ΊχςΝrJ|Ή·e;•?μb!A{£ Q]αζBu±Νv€©δ:¦‰%~σ)β›­Nί7γΈγbˆτFnƒ +4Χ€}ΩUΨc +U,2TqΰOk½ιύf’a.³Iσؐ…Χ ‘Iδk—5r˜b7<΅Y/£/X₯('œd(ddΫN•ε<‘yC. ₯V+6 ŽŽlYL;•­ˆ&~)4 Ώ ηl΅ͺξY"(’r‘K˜0TIζ{vw71δ₯(rΨ’ξUβ`ω—μSZΦ™ωwƒB«‡ί&L‘ϋx΄Z€uԜ*aM1ένB.TωJCH€” η™kf^Ω#Rχ±T³μά>§%Η†!Dُ°rD’uζ‰{kŠHgΝj9–λκTe…K9$A;zΖ9Ί­’QT+ŒhΫ9•Mq‚o³YΠJςI254`βΕδδλτέφM–S©±dύ θH Ϊcφ ±ϋΠ¦υκΑ)¦‹ Δ΄όP¦Ί|Heš» ί—>ν©ΑΑΪζαυš¨Tμ΄₯Ϊ^2qΆEœkT–Ψ [J£ω:Ζ¨@ŠL~ϋΖf»MκV#¬Κk z»Οͺ›™{-Ε@½iTΟkiΘί3@B_φ΄*™G·ΕKnώ»μTS+_7až|V]w­-uν‘ŽŠ¦:Gn·{ŒqέΗ‘’¦μ ,Yd<e;0ΌBα°r„ ++5}‚h|•ΕΛ“Μτι ·¨§oς!;ψτ»‰%Ω[(λ˜R< •’¬ϊφMΙ₯δ{d?›Ρ[`xT dY!.β\R‚d45Y™Τ]bmR › jΦIγŒ0p' 1Ϋν…$|(ί΄ϊΚΎ opφκ“ςλΡΗaσ¦#ͺT!¬YI©Ÿ3G˜k=9NC¬ι§ϊ'/$ +zΗ­Ρδ9$ά^}’H‹ΠT ]3]Œi–Δf¦Ε³8όiNwρlΘΛ2F_±šy—™mλΩνPYΒ”5…< GŸ9ΥΠΕx5Šί53MCύXΪ΅.ϊΚi9iΊ¨SθB‡ŠΛδΏO8Α~—]ΒqΛ5/ηŽ m6DyΔZ·•f̐‹΅x¦g6qζ‘ήdd– ΅ )‘˜EΩgt0Ι«‘V)p;f¬ˆapΖHίynوU–\€Œμ5K?πq™υ):‚iΓ‰,ž=υ’ΥΠrΤ&ώšq܁λ|υK燑ω΄…ΈsδARͺΛπΈ–(f‚Ϊ Β)ΞθΌΨΰ׈“ Ρ8A“siΔΤ7uQ#^ˆ}“.A ηΝ8j$°Š@Μ1n‘‘ИΦΜU*λj4εΟ!Ξαγ‘k¦H«‹½6²―Ά™ŸγΔΣm8+Μ‚ΜEh«ρ:΅nžκB‡1Ν²ΰB¬\@r΄Ϋ6톀z>'‡=9κoΐ%Χ‘ΙΑΧ2Ί·΅J΅Ϊ[ΑΘ{₯'2Λ.€S­ιΕRη…ψέ’f#Υ›q]ŒΫ]‡hεv‹Π£1o^(5WΉ@τšs6λ +g56QŠu#ΎΩ!‹4oΖρ:στ’NΌ g†}̍ͺ°bU‹ΝΖΖντ¨δ‘\Θ.sη0ΒBΑΑΪRυϊΥΔ7Νυ½₯_†=œΘΙ.S?yvEN•ΨF|§JD·¨‡o-ޝ?/μΎ‘ΙϋoΦΒ¨τDIρΘn ±3 qί’1ς²{ζ¨Σ›ΔΪKΘ ­bώΩΖ΄ξH―ϋ“‘mHžpͺvΘD~š•ODAΊ +'ΰΌU΄οr;¬—-|zP7ΘU•S%j!ξΨκa}²#9†4&ŸV+ Α ©Aν[΅D6 ˜c:Ζ)$auΉΡΌMμ0™3‰~JΫVq'η3ϋ2Eάω…qˆ―»"%@v{ƒpπ˜7γ(YcΤZcV‡g»;SΪd!pΎH«ωα˜ίfSA–wΣBpl¦³lΚ€ΡΊ9=%·m¦Nt¦4$ΟΫν‹oΔκUΩ,Ω[ξqμ9H[TeάSΎAžΠΐF‹ͺfXΞ#Vή‡tΉβ"΄Ξ’ˆš·‰+ΑSZAσDicFχzT[ Π.ͺ{i‚ '_™Qf/š§Ν$ψQV'¨—π³ΩΛΆzi²5tHΕ„vͺLΓzΉ¬LVdQψUr¨]ΖύŽΤφHiΠ‘ΨΤΐ*(ΚίΎΏ΅vœ“)NEsύqβ²χΊ¨gV˜Ω~Q©0¦°>œΖ2Π.€ιgά‘i’9.G@7ιθώdε2^‹iMkƒθ?Ύ• ~ίofσοW‚σ^ρ­%―Λ-Š»ΆΪ6Ϋ`Ǎσf”ͺ…N{ΙV‹ + +Π‰ƒOδΪ —ΕTU5SΜ\Χ4)«MOsγΨ³UNO£…Σ†2SλΑΓφ&RΤQ†Λƒ΅”›g“2bο#Ή^5±,)νάάK+9Lμͺξ`”l²ΊΘΧζvo€_–dH½4ϊ> η'έ0¦ž—ι½Ύρ£M4κrN=zΙηDνθ&(ΜPR₯Σγθ»ˆσΎS•žI-έ3{Ÿω¬—=L²Ω°9b|πύ°υ£(΄Χjκ!…]Β,γΨαΠσ‘(£;>ιΩJ O3A‰?ύ 5ΖΩί Y°/—½:kν}XAh•ΊΚΝϋ5sζbDΟγΕmU,ξ3šΌl%U,‰ΓΎp}α)nϊhΪ“γη’yε«Ψiρ>"α†Q@oΔίcB.ύΝΆ§»»L,~O–1k}]hkΗ[VYνμ­&Β(Ρ’.ϋwC‹­Ά_ΖωU°tWrxA`tόϋσVςν‘€†ωUkJ§ +ˆ:BšΗ­qŸr•lΤΉTHM΅{ΜlΎ1!ε=ΣTθυΖω/:Œ€αcŽpΩxVόν*χL;hm-œSQΝθΰ°pΣ8½Ι2]HσpςsΫΖ.^«¬‘__ΠeoTφ`ί_PΟ X '½/M湐0l-βηsܐ>ͺΓ9A“£˜$XΉΣ#)7ΰΊβ΅›.rHjϋ>‰Ϊz" όŽy£PˆΜ>uig]TO|ΖΪD·νψ‘Λ/'T@uzΖω-…δ>Έ†―™Α"yΛ«%‘$μeΞ3εdŒ¬}JVŒ)ΩlΣW°}‹‘Τp±«Œς‚HεΘ;§KΚ•’3š’JL`Oϊ «ΨιφxοΑ– ωί(eȞχ Ρ.φpI‚

Ϋw>'iW3«όz ³P΄F,„¬žι’H!»_ S¨Ί¦§cΖ… ™0!X|snQV`<ΙυΜ€Έρά©άpΧΈξ4^½†χοx‘K~O_ξ[ξTƒcφVΰ{Mςv/£Hn(³Κ„αΘ⡊₯‰ΚΗΩ,½«–b4F»ΛmTSΎ‚ά`ԜΗήa΄+4‹AΑ«ϊΖBd§+Ωλ“Κb Ρž€ΝFΊJτ€D0„§Ί˜ίŸ€Βeορ OήQ +@rS‘yΣΗΪve)E|kΘΖΐπ4SŠY0τ0`ƒΔΑξ*Π—€ΣJ1΄Ž©ΐfK'λκm2Zž†ŠŒ—””0α‹‘%σ²sCλάε>ΟΗnŒkŸ½J ρ―?ν»‘η ΔEΘ“iΥ4PΧ™O­°ΫΟ•#δάΰΏc)Θ4Ϊn7}‚ΈfdΡ θIΉ¬χΫg€η«h‹:±ΉθΠHu₯Γ›EžΘ₯ΕYρ9Žΰ΄­.ΝˍάάρεBω2’MΓΝ¬‹νμ–ηΊΟΕP슚¦}ά ΊΌ\ρ0œςQyS]oBžτ½ώ²Ϋρ2DØH 7#9u‹eίμφΨΜ„}?{άzSΛΠ8|H’cβHl‘\[ϊSΈ<$§?‘Hι2_Χ8…‘³h’£^D9χqfή¨ψ](H˜’:H*―&Ρύt”»½ΘvaδAQie‘tΛϊ0)‚›Ζ˜X ο•Ώϊώσ;I·:ω2y$σΣη‘p;z:Z!q ίΚ%£¨Y‰΄XΧ22l€AͺΧΘεω3½ΑU?:T3mGXu9+ˆMΧ“V_b©εΜj±9κXζa…qΠA…Ӟ­D™χΜjΖκ‚iJp^k =YΗω$qφL„εΈ§³jž,«DnΙIW}1ϋk†¨§Πh|?ΨeΆˆŒ‘r¦)X§€xύ­v’‘ρœ€±Ν))”¬λLΫΏηFyŠlUΗ&τ,Zγ1…nE%‘Κ!Άπθ="`ξή μt•Π4{ ΤΊΗΩdiHO %ο3έΟJ χΰι‹²¦ _ΟrΔδ’¨….7^ϋ΅UxDς^u‡΄}!ŽΤ™E!#μ ί qS]]Q―ήβdβ€^‘ )u‘CKqω4„€$yκ…χΜǚ€wγύΌβΤ‰θ™.[, Υ5ΫrΘm)ΖFΝ'αΠ£Κ8Γ¦ˆJ ΕΔq|’ΠΔߍφ½βя@ڜ13ιΦ„Τqό…Πν«ΦaΥXEŽCΞ±Σώη›;X¬σ?kΧλhMΦέ/Ή">Y%l>uΦ_˜Ccΰ΅ΈΘΎŸO*Y©Γ»Rbžfr^fŠ‘±;ΎΧ‘Sv9Γ|½c‰¬€lβ³ή›Ϋ¨d€ήΊΫJ$YwΝ~GθαΨv·ΐ΄°΅i?(.'άIG*‹>ώηqJ²X‘ΠbqνD;©²|»‡‰πLm»{^¬'f’!ΔΛΘέHΪϋΨ4‰€ŠŸδαχΤ& )Uτ›δ™ˆwΫ.'ΞΈC°]SI†#²ΘVΚ(%N{NSb“c…΄λΊePΥΌ¬|‘¨4¨‚a₯‡~ώ™„/ώώϊΫOŸίΎχωύΗΏ|ϊγρ/ /ώΙsΓ§₯|ωψϊΗϟήψογ‹οΎϋφέ»ίϋαγη_4φΛΗ?ω šσί’Œl‰³₯­W=D6'ΣδD”άOβ@¨ΧGΔΰ}S„‰ΗΤ €|(·+d'ˆΜΊH݁$ψϋž)vκ-9‘6 ΣΐRŠ1Bl;σ S•;υF“f½V- EνXν +ϊ\±«†&W0ΔεΓGΚ"TKΟk­¦U$Ϋ9»LώΊ3£"½9$]ŒTyV#4†HλT‹μΦ¬έhΗxœm½τ|ΖLΎndR盍ΜΫ›Οΐ&C”Βΰ%:^~β―oωύίX/CBΔΦe™6*ηBSιωΜφΌMζN£ +δ €›Eš›LΫQX6«€Y‡0< ιi‘ži +0ΗEΘ«δγΘΨckI©‰jwόcΣ₯+‘˜Α4KΙg₯b φ=‘nlσL­ΫCβίkΘGΊδC‘ΉέWxqΉˆ-g @Αot—•κ=K@ 8ΌvZ1 +©ijΆ$Ȍ\Β7f›¦θ8™bš"£°εϋ±ΖQAud9Bb‰x@֍ζθ)Υ •*–`u"ΦΉŠΔΕ”ΖΩ%ν G‡nΫΟΈ­€&Ϋίr GβΖςLLK΅– ρ„ΎΐC+­ΙΟ@Ιx&Œ^Β7< ˜ν!uΙ²‚uyo!„4±ΊΜ΅ξίg‰ZφKΓ +Υγς*ωΈνVHιαgVZΟ™IΘF5kd—Ψ…+Qήƒθ)ζΩ;i5ΙvΣΞd·ητυ»ΏνήΌ4JΤ9€Tx§ι墍"‚ξΐΩ“θ†φ¦θZdKΖ^ŒΒ<9ΕΛ Uω=pνΓY²Λ_RΌ]GΡ0ο b”·]uVzPA(«± “²ΦΓ,Z‹Ϊ8Œ€9νIό΅ι­%T"w―-Φή΅¬lοi΄jP‘ω’uj’λζ~©κH±M\½Ος¬KqΣβqͺΎ&SΡ:vRΧΗ¨&qqe·(pŒmP±BΏEϊmΎΗΙamΈΟ€Έ4°Βy|= +'ϊ-3,ΏΡΚُ‡fƒ.mˆ›neΞΤE3Είώ σύ2,ό?λe²kΝM…ΡW9Γ)·eΓ(͐ †Q$P„’HΡ€Ό=kνν:Ή€ Ι½ϋΪ.{7_ 3VΥQCs7Ή§Σ[’!xuΣB_ͺW8A_fΫμuΛ₯fΊι‘ζ2η€Ι}>v₯T=ΑsxR’ητ( ‘«βkueHαŒ­]ΫΖjZŒjόρ֞9 εŽKχǐRlU K7τυ˜~]ځφΙUΐΫζ\M‡ F¬{‘©΅Όwt/°W˜σ½ SΈ2hΟXΐTž½W|l­™‡ώŒm Αˆ4–dwξ*΅y ωg4MJΈ{}Net >‚0άΥΤ/0[!ο*“λϋ(ρ‚ΒLm΅€vͺ +ΐ‹+ΕN…;CότKL}$ ΰφ"Δ•JiΆ1½ +²βOΈΘ%P°Ό1λ­Ζyb Π欣Ϋ7(Ξ]Φ=FΤeΆβΖADΝQ@0£qMΏW’ζc§ϋT\£'iΜ#ΡΊhjωηŠ#Ί*μgΊwIΝΠP+ lΗ™ΡŠq€©LгEύ\£ΐoEͺσγŸQ#/*›Τ΅„eš₯3<¦=’0FηŒΜ 3 yDάzo Δ$ΊβˆX²ˆPνˆT.šο½s:ϊπ, λZ²ͺή{G€·ψ&₯Ρ |}H΄ΫΗΈ±R OϊsΤ#Φ€jž¦†Μ +”ƒvFν£wmΙψ »•ͺς42pίο}Ί·Œgdφ³jν<(1"Ϊ±Gb4틦šcc“±£]ΔΣ{¨M–j₯ƒ'kρμx€pΡ抨ώ³΄qzj%-—ΞΩ=μν>OsͺhU +Β†οAWm,›BΓPZ[ΆΰΟjΐ“UΥbT\eθμu©³ζΊ~!$ΐnn*t,‡ΆPΦKQct9]CΔΰΔΞqg$+w±w;QξλB5D$~mηΡΓ„Η6θ‘³©Β.‚ “ΚSbBΆ€EΌ³DΨz>hξ»G4xΒH °‘¨πt1*³ήήƒΜ N,’X―s’j5"ΧΌσ‹Oη8ŸΓ€0ΛWTjVιΦΕδ9pugYQrϊF€χ,r(lΕ>ΕΔ#gjhΞh]­JͺPΧΦμh«¬Ξh"-Μ^šfBΐšΧμβ΅Γ|z‘˜Κ™}` •¨žΎΆ‡Όκ§ιΰΥΜuΟΆpg’Ώ* 49Χ™sZj*¬ΫΛo2ϋΕ쏀.²/“=ή‘rιβϊ“ήιΟCdˆΏšŽˆτΜ¨ΠΗ}cώޘΒ₯ϋΚΘ?ήυD„\#`ΛΚΘ:ŠΗ¬΅e/vεΔt$.1Χ‰ iž5ΤήυΤDROόžIq‹!ΥΜ+Χ•H”9”Ύ‹h·ΐ_3:=§yjf\‚Α²I!>*”Ψυ΄λΔ†_²mSϋ_F ΚΈŽƒω0ρvηTZKPΥXwτYzSέmρΚΐ_—jσ<+-QΜ›S*“τ"½Ϊˆ·"-Z“΄―γŒN›h&‡H׈Θ]mD"-pˆΓφμπsg’Φ-Ψο)¦ΥΘsz ZξΌAω’z1^(ΐλΆωkK ©žβΦ#"RΥάΡ3S 0g‘! +-Zωaqœ>R£ΔN£–πp3¬+ͺN±ιω8ͺL‘Ϊk‰^―†wΐ0:qΏκUΣύΈ.dΗP¬˜KŒ b)ŸΉ5»8ς"T‘ΧOŸΘδ―ΚΏΙ_DNx#zΗ Οcd“ΉC–·νϋDαΉ3ͺQΦ/I΄Q@t±SgΥΊΞNζ°;ΑΙ:”Vφl}(‰唀QAI#σA8ΈΧ›H@σΔτ€© hš('n¬R~ΫίdB4ί|€˜ΒΘξ£Jυ°ε^G« oΗDQVeŒ(€β+ΊφV$ΜCμXŸΪ„m£EΘ[λ9»NΏYRΠ*@ΥqYM`g;™ΫΆ’ˆ^"Ρ†ͺΓGEOΕ·³)ΞΉΓIί>Ωcλ–ω‚Υ½”ς+ΥΖ·"*ιΌH{,K°ή·Ÿƒ†9ήΛ +έF4χQψ< e³2Ύ“H%ΧοVίΚ<•έVδΨ°3²Ώ΄ ^]ωDθՏςέ@Μˆΰš€Π‰`vlTŠ`ŽΦXΛ8νJτ +Ν―“ΌζŒ†θAz Xj\!@1’άόϊl̞ώKt0ΪN1ht핝ž–ΒuZΠͺΊ…}a=8_υT!˜4YEO₯εΰΖ$ΓU—S}€όlrr'•μ&θR2¬’΅½δ ήh _έ"]ύ£l£ΊΞS˜°|©Mΐώψbxς=ΑυΫ[*κΚ¨t)μ5s+M…t_«Ξͺ°ƒcςΰφfm³ώΊd’ ΦΞňόσ(z·‡7\Α%Œρ@ΞbδfšPΧD‹ρ@v^ηM ό%{5•ΘFkΊh/ξΪT|”~ +hhςΝXΙB’CO½ ςτ>Υ†ξ„ε*G(Ÿ‹²ω YRub_y-ΑV,'/έ9ΩiD$ύΎδΏψη‹/ώτΝχί}ϊώ§ΏύωΧן}φ‡πK°Ηη―/ώφιηοόχλ³―Ύϊς»ο~ωα―?}ϊΦ΅ŸΏ~ΟΚ?σέίOύβaώλz}Ξύ'~.η·ό€γͺ4[ΐψαDsˆž¨δΉbϊ”>άγ`ί|;2} Ω_¦k½³)o5X‹ Aν#γˆ[-’\hΗ:Jš‘"Ω嚻Fδ*β<=₯Νcdθ_ŒΠυΉsͺT"²o… c „‘‘€-DΡ=E,-ΑοExί&κQ—ΉV@BŒ±5ι%«ξUΙήD4•Aμ¬JθζL°λz&ΏιΜ–Γ?„¦ϊ A’u£^r2 ΰ:Yz#CΤx6’vsc~Hs%«€GΟo"ς΄4%cξ‡η­œ†λ¨W$ο{¨#{pίΗua&¬ο.Βνoρ©ψ!:ΎΠŒ ΄ΩδdΡWςCŸ@~QαμόΏWΡ&uΊ [€2vIε‘\‘p…ϋ½ͺ†uŒˆdk„’φˆ„V0ς?ΦΛδFwΒ©Lά—σ Ε98~ΧWMQ2 >ψ6 )QΝξZδώϊέiΟ*#€“*φl,΅‘‚:΅βτβΚfg#βΒρ%jŠ–*ώ‰ua!_ …Γ‹{|ΠACΙτhmYφЍ‰ΦκωRƒεχqgV΄ΏξΜγεΨ‰δ{±ͺ¦(bΔΪ +S;Π'ί…ͺW~Ÿ;΄ρ,z”"δF³³…]ςŸ^'Tύetcݘ‘Aφ^ͺ΅—|σά5`FVΡγ<_ΛD?ΝHZ5ΡΕΩ(ιμ*c+ΕΎΪŽ…1 m”\LΦ}­.V(ίπY‡Ε°#VΰŠ,Ά9«±Θc\7ΰ·Γ™+Η:βπκ,σηŸόϋ―‚Bu!«dΣ? °EsІΌˆ9>γψώ9B©sƏ>’θ°…ΦΘjjrf¬δ[@ηΨέλ:5±NzgZΣHŸ©ήj¦(κ Ο;Ι$-]·ώlTv]|‚„XήA;ωz›>Μ"ιjLoRδ ‹‚­#ΝΑ>β9ω͊―ΗΚȁΈ ‡ωZκu>σͺΗ΄Xg¦Χa ]cc•#;Ξ9@€’F„Εψ-ƒf@όpςηMDγ\»oίGΈ+ŒςŸ7ωϋμ–ͺΎξΊ\q‰ώpŸˆχ·xΩhsΟ²ŽΫ‘’U# fdΟσA [κ:Z5B`„XΌΐh±JAd‘ώ‰“m¨ 6ΒYI$χ aΤ I—ΣTGΏ(‚έ4»?]=$.ίyŠϋ:)tή¨Γځj»ΧΉι@L] 2Ά9΅ίΞ ‹.pΞEκ½NV ΎβYCzγ‘[Ά— +`&π»ΣκRŸΞ= +$Bτ!Ή>3(ސ@‰ƒ6cάaU’HH`ͺxž θΩ±_pξ ΑΌρ3Ίή&ν/AINhςΗIΉΣ‘ιA)¬££eAπo’‘ž¨Ρ<_ψ€jžQφωœ”Ϊ‚―E§"’κ|ΙΣ¨<―Qy˜λpA κΘb$ΞΎˆJ₯ޝ«΄@³*λuΆυFFχ!Ρ@ΚΟm΄¨½†φyΑ±R‰oJε‹ ΨερY/ΚGΌ²;‘ tάe…%‘εE’_+‘n}ΦΙύ,LBŽήΉ?€xm•Βυ^΄γv؈3—E‘zΦηjr$@ΤgΊR?aOτb§«^p}4 ϊc}*F ΄_¦yφέ;zʌj$bQξ²Sct/1§%L;‡0[DN,aέ‹6δδ:ηPό₯¦βœtŸΆγ4υα‘yŸˆ_ˆlΗ D56’δΨ™OU<ΟΓϋΑ\ƒTΌo¬Iρτ–Χ™­ΊΔ²˜}gSγk¦­ΠόΖ;Όυwό?u[εg“΅šD\ˆXŽ/Θ΄„γλΎ=ΡT–nΰ„˜€Z­ Ty-ΨVΝΫD½VjΫσ³ν2nŒ]γPΉβί(€υHf—₯R‘tς€kηΜRyύC-ΝΙ:YzpD£]eDΔƒxHωΞ,¨ YΩϜλ`-ΏLδ³ “Ό& 51 €]ΩoΠά9ψGΝά½ͺΗ νσ²M—ΤgμύδZ΅ΔΘh‡”lβΫ4u‰Όuρ‘?ƒ°v½J—Gσœΰš?(-~²Εt7οŠέΓΎθ6δ¬4j(baΚΥ$iaΐΤ–"Δe)ͺm?X’mE3τ"Žί πͺό₯υ‚#E’εΈrx;€αojηŽnAwΔΊΘj©NΊ‡8€δq€ u΅ΑΙ€ Λ,η€W7%;Ώ‹¨Ζ»<_ΐ•”Ίa‘ ΚƒπωΟ³7Sχκδ²’­.¨θ :dFίχ 4\g³ ΐg”Β¦ϊY₯³Ž#ϊΪέήίΈ¬σSŒ½<™LΨϊ±κΦ28Λ<]| Ετο ͺύ–8κƒ ΎωLL y»•DΣυΧε©›¨—R¦ν)=€Ο©‡΄/¨Ψ«νÍυ5]U¦ ¨Ggb|HKΈy–mΕ+¬SI½ζXUQq—X)Y"³D›σŒΫŒτ©pί6TL<+–ΡΪ Dδ+ + ΄‡―.Κ ΥLΝ%k~ή.—JΩΟΆ Š|τŽ^ 3j·¨w©~ξŠ&cEμ‘+n?2ρ +.ΘζwL@IΎ΅E‹ @B†ΡoaΕλρΰˆNV#_ΫA%@^δ+ΰ˜{‘οxΏ¨ΊHξ@λτ‘ΙnG*Ό‰IW―uΥτΎDCαΝHœš*²λ*c€ίzcqήγφAT4Ϊ±‘t_L΄qj:»τώ<ž1¦ε.Σ”‰ο§™Ψ’(ςΦΈBƒΩ¨η`FλƒbτŒΰ\Η™‘@惨бB.gg–l‘ž Ei#9f ΚGwg…ήΉχ«¦‰[€μή²OQŠη9©X2ΩF’“Ωf?Βο'½ŸZgώGώΨ‘½kfΏf―s•³σIς’Γ@ϊz7†ϋΨv-?C*BιέeϊΟ3βΊ3UjpΏ"9¬,R©dΦ:-oΖέ;ZbηŽσG5|gj™`Ηολo2W‘’oQv½gμ‰ΕΉ9₯Εΰ7υμ›Žθ‡ϊήAŠ΄±~™λqΨ~j>·>£B‘p˜H!†Έ")».T/©Κ“x]jΪYfΧΓ½€=·³ΛԜΗαRΰvΒH+5—€Χcόω‚ή-Ϋ€‹Τ:ͺp:ώΠh<Y"suκ#Tl›Ϊc]Kt¦žw ΥF–gιw§˜ΐ90PlΜ4 +ž\αΤHΨMlό΅TθΣπ:™±hj™:›βLί;…¦}ƒ +ΥΟaο: “άΊvg Χ¬–“8γ·75 ϋοšΚ`ΰΤ'Ύ‹™ΒƒTf#I0Ξ©™Ή& 6rΤΘ8-Eο:j5ΥM4ϊ"=|ε­©QdrΝʞκ˜C +&„ο™ξθ­φϊ|dΪέ &₯xYd^!Ιg•)‘T¬(η*Ϊ΀Co$Η*½₯PJΌNν3ξϋbŠk=nIν&΄*Κa]ξζ‹ώ"ρ‰‡₯ο²Aƒ&Μσ΄ΓE\›ώLΗƒΚdΊ0^@ΜH;BVό°Ηζχzf#c¦ΖxQ¬ήκ’6>i+Wdό…_Γ< +#6XΡL*ώWVZΕΕ?9ηEtΤ΄oh5whϋΟΤΤ'€ΐdh­ψ^ΤώμΪΧjF‡hΤλf~ΦΉ—ˆ· (•Œ»ρpΗύΒψ h>HΡ£O#•z―rXΚUd°λbώλ|)‹1Ÿι8–lγy[•κώ`Gώ•ΥΊ‰kT§%]'I›ψq9‚ρpππ` EwΈA΅Ή !OUŽͺ]䢜&κ³N<‘¬2ύ†ZΰK„²ͺYγƒϊωʍƒ’?ŒΤψξΆS‹Qv›w£'h!ށΜt*V0Ξ }ϋ»Χ9ΪΩ)mVYυΏ.£V₯!½€nγζ€@Q"²ΞΪΎ|Χ尜ͺ5vή/UΥ‡­υY¦θΓؘ-Έ‘ΡjΈ²η‘d*’i­«¦Π†DΔ¨~&*§‡ΪρUΜ‘Β;΅ΞΔZ₯τ2(^V°;u‹Lt?±^­½Q%Gτψ?ά/‘` ίδ“1$aγ%ˆ]΄DQ4rμœ03–ροsͺ«ͺο;Χ°HΡρψLwUu=O9,PάLΜ9ξLα ?žΓκ+ΒGe…ρθ§Š•ov;WLΰ Η΄bQRc¦‘ψY':(ΩκΰOQιPl(ΰ†ϊvn,”Ώ™Ѝ!9ρ+ΡΦbΏ™šΧθνžm²ZFf`M£§b§Τ&φΕiΤ}‹oμ‚zlnΦΧάDu,%5VQr,‘Ž:]’„7* Lgμ aRVΈywu Φ—θ¨dήΜΌ‰μjΰ`fή‡JλΐΉΘCc­βΪΠhL3Ργ /©HP„2ω(θ •ZΘSο§nMδ₯ΠΗ΄sό7mΎ‰qv€NšF»ΣQdj€ήg±πΤ֍K$~E7«Έ€“ŠB΅AYR ”υ¨vyx?Ά +ψ +”D½a{c7˜4μ&΅Φ³τ5 8έΘ₯%ΗγΦίι₯υ†aa‰πxZβκi*%βBΔHh›e&Xs”‘‘Žφ]hN(€ :Μ.θ`lν„Δ šπBt”Ψ¬AŠoΈκ­fΔ=¨K΄„ώHTZ^ι‚γaφEκr€μ~žœήξŸ]_ξ―wΫ‹Ϋ/Γ=xV±Ί‡Γ“Ÿχ·ΧΫχΓƒ§OO//ο6―wϋ :ϋpψ=Nώ?ψ‡7ψηΝI˜¨8˜φίΫ/Œ o―π! o½_™ž<έν>N_ΌΊΨοΧ·ΫΥ‹O«Χλ›υΕ~}΅ΒV΅|ξ§‹λνκlwσe΅{·z~u½σνξξζΐΈϊ- ΒπδΕv?L‚v·νΧΕΏ>YΑωξrυΧυ±Ζ O^―/>.άzv½Y½Zί^·ϋρ|ΌOΗ«Ϋυ―ןW―wŸ?‰hΚ}²Ή†5Y_ΏpΏWτθξfίόιv·ύζΩσυ»ύo>LοƒCןΎγyg»γσΈ\ΎϊΖ_――φΓψόδw§/όκωφJξΡί‘ώ^½άm_!YχΘΧG~Ί~wMΎ8yyCίώζινέ§*ηΑΛυηAώ}xb†SόΌύ|r‡_ΟπcΗ7Ïψπo@Ÿ‡0ό4όγŸfΈ’Σ―Oa€’kv@Σ#ͺ‚f»9„ρΙ[L‚s‚Ρ–½zΎ,ρόdΫ ϋΫ e‡ΠΑΆrΖvŸ°Ι²κH ¬XυHφ2άeΜ5R_Ώ™«x¦œ΄ΡωΉπ{ΰ.δέΙ›Νφ;:„uΠΣΞ„C­ΰflΐ¦»I=G»έ(r!ηΛ²εΥ„ +ΫΪ\ιžJΗŠ1ώ5xͺτXφα«Έ;±(82Ο‚A»©\Ά’2až«αƒ7_xϊO…`‡σ #G0MiŒηζIς"0q6OB@lξ²Ai°Ζ1l,x¦€ΕΚYg@Ξ © -₯—r?‡6 +αB‘ρΨm¨ˆψ¬^ξcσ±T4νθRĚεY˜“0δφ.Ϊ{ˆͺS\ƒiεΥeY…΄©Ρv«@#ω€·ύx"θƒ’GylrQ•βΒΔfƒ«V@Ά7†«5MYhkme0;ρ+’κ­Δν)€_ œZ|IƒυQ±QkS°ρ½¦΄dqP€ν>ˆl0e`«Ϊ^Β—Rvς_K3δΤΆ‚α\ZšSςψͺ.h€σbΔ`LμmlXW»[J5Λ ' Δ–&ΐ!SkλC;i9žšF[ΑlΣFυ·ηΧb{LβXxΫ²Y8i|ν>hΎΑΥ&ρ–‰Φ‹τ-ƒ!σ}ΦυϋV™_‹F—D"gD­Y½1…ΑθbθYΠ<ΧΰRGeE#ƒMDA—X*%IΥ ΠNΚ.H£΅0¦ΙnlL~ΰΒOβΓΏ§Βi„Pv\JΦkαϋ„ΊζbΔhަΙΕ*P°nH…Φ”4–K}Ο„,ι ώ-,ϋB–ΞqΤρ¨7i‡PŠ-½lιYυrδRADBΜκPξ½ιI-ΗήZŽ₯fβz¦Ά¬kaΞΙj» +Ξ9›‹Ά«6½X(`šh΅$Ι‰(N†C”€NVœ ob5•L§Ξ©aΞ5J‘…kNjM€΅όfΈ”!¦œͺD΄z’ΦθB6kΟL©HΗ*β.€mύβb΄pφA^Ζ=ΣGn",@Ί«ϊ›’ˆα. AΏTÈρκ2\ΨLN1pc•|ΤKΨy'“DŠΧMqš©UυΟ<@Αžχ† /ςέύ-,8`;œή| Ά³V™ΫΡOLwλ"’΅Τ¦rξ‘½905…¨»`0lf0>…θ'βΉzΎ,Q˜˜„HΟUMΰΑ-ΒWΰ©cΩͺΤhz*α©τ„™ό‘ | τHΆ2ίΐœt¦tO₯ΗήJe‹RκΘΒH§J'πt;‘΅‹πTι±lUJλU닇JGx*½‘ˆc₯ψ@ι‘l₯ΨΎΣΰ© <•ξΛ„K/ΒS₯Η²)φw6…7ΫνΕf}5€ƒ†@uψ=-ΎOγ ΏπXŸ¬K‚`Ή‚δο,w—0½»Πš4OG¨ηQ)MlFΑηΕ‚†Š–TSκγΠ^ΟD@ ©ΐgΘ«¨Υ€z°ΜαHϋ ˆ!j‚paχ…]L|Όew•šψt0Iα#!η,έšΦAΏ0A€ΓΥΰ‚ω90θ‚Ν έΐ +‹7"Ωtή3±#Σ¬b;ΐ]Ίy†°Υ%η 0¦U‡k*ͺ.»$’}ΐ£ν7Yβ-UœŸL’‡vή \ +?`ιӈΔ, q.ŠjBg"ΐDz_ƒ1^τl, +ψ0€0λ}‹¬QTUΩκͺbI―Ο0ΎμPξFWΌUΉFί5>ΦρΨ}f’šŽvΑsq»Ρr GΈ’AΛcη`ZΉ*g½χ‚Y˜Ζ˜³ΨΞDrCΡC ZZΤcXς°hΧY78l\HΖ«Α΄ξDm-j0`Κf†±TŠμƐ4Ν#Ήc8*΅”fIΕ@ό–BWJΡ:χ*Ϋ/ιC¨ϊΒμ₯ «U9γ€ύxΧ]d½+ϊŒž@3ΓF‹sWgJK#™ΰ΄±MMŸuΎQbZ$=‡Š+΅ Ιmό#VΠΩE Ξzϋ±Uk~Κ½ύԘš9χ6‘CΡΣΔZ.ΡI³Βa+ΒYŽπ8,jS3Β€ * +‘pL…Π:ΕΝΕζ `²’W†φηαΐg½Ά;ΐ5$Ιπ콊h{$W€ΟyX4l΄x¦P-6κΑ€…8]«$³€AE`1pχ=€ΧYTί›sO’Ϊ¬ζTfήN§l΄MίΡ™ ν:‘Λ€ϋ₯ΪF`OΌ€Α¦7,°ΖŠScφ#<“‘žo,}#0&°·ΨP±₯ΖjrAΐPŠŸΠ‰3eΣ Œr΄ϊ*FΝώŒΥζι­±ύ~uE (!‰όXm”πμβ 4(yςEΛ™ΐžx†δmb…σU@x΅~Ν4:«iηsΦg`Έh.fqΨ‘g=€Ζ͚}©‘{ΨkVκ­L K IՁ£jIΛ* šΪ£oνΨ~₯€ 7e«`τΓ’a£ΕP”€ύπ*HŽ /Θφ *rΞ5ΏΘ9Ώ•ΖΝΜnεΪΕhƒΠττΣ„o.€a_Άoƒ>QΠp­ΠE3@ΡΗχBΝZJMνvθ…ΧΡ€-ψ›™΅Τ3Ι¬ͺY +μβΚϊM)ΫƒΕκ™<,85λΒΤΘGFβδ™άO bΨΎΦu@β"ηΊMυŽt―JΑ2zκ)³…7έyΖΌm‘XYWίY³Q π)½©›fξ«jκfΗλΡu μβ’Η‘v·`'Ζ έϋ:oηΦ).«ΙγΡέ‚‡Μώύ#yκ²*N*)y`k$Bwη[λ^ύKQΨn-‘&?±”h²½”Ά4]τNοΖΎE,ηˆ.ύ’ώQ³lXΚΌJΎHiit/ «£h2s«'Ρε§βΆ6ψρ’˜MωL3%‚χ΄½n7 GnώύΖ8Zδΰ­8‚Ϊ„ο‘ +na-AXόωρ…ΤιaθTŸŽ uμVαKΩPH2XŠ΅|ΜΌ¨=­Δή:|†H>1£Ίd΄ˆͺΈ ΛOSΉΊ»π? Ρ/-Γ……heΔ–²¬>kΘ‘”†%§Λς-PσwΙaƒι³y?tώξΥϋπBΰ3Kξλ‹ΏtΆnzh_‡Š6UŠ^(]³w₯+@:%s—†ΤΈβ€ZΣ„ +0Οδ*2 endstream endobj 29 0 obj <>stream +H‰„WAre9 +ιΔ°ήΗπ…«ω»―„ι}Ο΅ή +dq*X'ΐαΤ–ρ/B³w]Ά™Β|λ1#΅S2BΈ‹—αϋ€=.`™x οΰΦέφ–U ΟAׁ,‘:lξŒ‡Θ³NaD₯ιμο7o)ΨΥΆ"Ί ά-@υνpJόά@|΅ƒYh„Εš•–Ιk\SΌ@Ϋ¦ΧΑ/oπΩ°ndVi·‹(0-ΠΗΝΜ/oۏnοrKdε_€™ζ‘Ά± J‡v—ΟKC»ΘΤϋ}³—z¦DύyΚSζ {DΤO‰L†ƒeTAC‘ήJ’Ϋ<ρ€Ÿ―οπ‰ΛiT^;Yy/ΐ&m―ρ΄ˆ˜hβΞjoeιΎo‘πDϊ.l³ΰs`ΫˆQΒ0>CEΪVΊ‰‹’[]yΒ–ςΉͺ€Ii2Fφ`Ϊ‰  ½=™’νtέ“hϋύž·5έΜέ—Ώl!Σ;€IΝΩ¨ν@o¬Γϊ::ƒΓœ;+~ γΙοχΫ―[WΕK~‡ηϋΊX]cΎYΏ›. ήΐΆ\rφαχΉt΅₯λγΐΌ«T‘gY'fί‡χ mς-/ν•Θδ>bwΩθl―Νω$Ζω¦›Ωmέ ΣιDŒυ ΏkpήΊϊ]ϊΣAσΪΚzρ(/ήυwΌA7ώεΝΏC›KNeΏlΨ¦)ŽCLΗ&_€ηTpK]-±έ¬6 ΑGŽy¦έσZσ2Ϋς·—η?-$2αbΏ-L— wεφL˜$ •Η‘@ΎΊ‚φuπά‚šοIρjc +nqkn½«΄Σar―f=cœ|ΛΝ"pœη£|ŽΉX›d88ή17‰ρΐFϊ0u}πυ»χ‚QkΑg˜›‡›_¦φ»λyΎ‚gZ‚EηέCΊφBpΩάί†χ…]a­©œR²"a蒝mƒθdο«­ώΔ£’–­n ™vUT龜|ΪηNπ‡š({α_œ`’Ρiuκ^άΈ{ΒΛα]£ŸΛMͺ ޘΒΞNώΕ Όοxά] .Q>‘΄ΎοU™•ΘDhΗOΐ"xx ΌΗlPJ¬ύτ‹dν•΅mψ{@'ΜΥ—―`+’9δEɏ©ƒ)σ^ϊŁ+t+Kσž ΄kΐ:²ͺOώlžg_)Qr%HΥA‰›$ԁqΈRŸΡ‡:nοDLΥU μήF +Ÿ—q!` ½Η +Ξ‘@pΒ΄ΐ\jλά:@ρfΓ`Τέ Ά(ρχογiεΘmΚG΄%(y‹—?kχ{VBXΒΧTΔZ₯BSΌΐs3‹–¨Χ”ΥΕ&ωtsLΙVŸ€ΔIΠ"\Έ±ΨόύΣ/ԐpΪ•ΒΪΌ­&™”8Ό6]ψά%°Θσ“ί^ΊΕySοΡ©–Ώσά“³δD€{hλ!•άΰθu$ψ•Ζ˜y˜Ϊ?ΕγϞΆ;νs~ˆ'yϊ<ΘεΚPη;ΗΛ„­¨wCΟξΩΎ>Ϊ―·ί ωΰjMUω3€ΞYτΑhjCП―}wVFJt6H>XιΕ#ΰ πφ – \BΊή΅ΈκχIχ#,ΣΦ¦›G,ΪΥE?―€»ω~: †lA+'ΜviTΟΣ%¨CΖξΧ $k€[ZΒΝIΟΧt·)4ίΟσ+°ΣQ̈Xs}& —γηXΡ§p,‡h½ ΔΰοB±KΓv·=ZφE£Vμyw‚•~ Εf6³ͺφ0²}Ύ½ϊψγ₯Λ­Α^³w½ h ξ„…ZVXA‚κ­‚xiƒΪ©¨2αΚ°]¨έÚXw2ιː… εή ζ“ί―†³μΈΑVͺ]“ ZΛΡ~΄ƒu˜_[”2εμ/1σ΅λ.λφ¨δεΦΆΐIΧψ©αCψ(‘–i»!pJi‡ƒ΅;„hΨV| +ιuφC.σל4»&γ°3ό +τΡμM]H)D:‚}ΔΦ9Μ]a»ΌΑasΚ-Ze=W8kΞΊ[Smtών2JQ'ˆ,ΔaΆ&@Ω‚h­+!Π© ύδŸΗ Ef£|kΕ–ke}jAv•“x-yίς*½V¦Γrϋΰ>d?"?ΰΥt’Ρ?eά2"ΐωP³tȎ%b’½Ξ¬([βΫ ½Ξ*dΔΙH˜v vΩ„κ-α±ο”’rΰšθƒ–y "ύ‘Τmzύ²Άν֚giK]Ρ~°GmΜ_‘ΗAT€`ΎHU†dKtΒ‡Υz+qu{’ x2ΰζήΫ`ιά\°Qο ŽΨξηS›ΈNHΪΓ[Έš¬dQh ‹QΑ|:α—$"ψ,,˜›ŠΰΐταQl&A\\TΓ-%vKp΅hΞΰ͜φξd7n½§E•WPMt*βJB¬’™ρέl‘gΉ˜ŽW ·AŒb[,tιΈVvdΕu1ϊέΥvDKŠΓRΩ¦ΐΨαΥDΤφ&Jή!tϋL„ ©iΧκbW”%Έρ‡IΈς끠Ρβ#ν:LKη.νfβ³ή4Φ&Kϋ¨Μ"ΐ +ΣH}εε+€ι΅ά ϊπ‘ϊVbΦρφJΰlwt[5³/ΐΨ‡ύQ)ΔH u8d©Ό? υ―­»ΖΟl4X-BJσ²ŸλΑ+M ΑΛ2œ1x—ςMΤ's6kΘ:­ͺίNQ‡uΌŸC_=κEŠ)lΗOω6Ή±ξςWfeώΏ§Dƒ©ζΖžn-T^TΞz=ΚL&FϋΏJτ£[1jψ¬C™^F1ηKΩy³“$ηυ3Ϊ”όωφΪοή%šs­—λδ s ¬Ÿ}N k[ΣΪHjζΪ!΅η‹E”³gMυa蠘ΊVφϊΒAm4JΪδΪY΄aO΄VJ8@qj2»ξνoψqρCœMΠ[—8σΚ 8Ά)3Wβίsƒͺ―fŽW² vεΝ„€˜#pJ17sΪ΅ρ„£­g|„:62nG1Ω‘€‘Q`³φαΤPK†xϋϋ«β·Υ¨AŽ£“;z|>πjΰ?\ΫΨKΧ³%‘xΰΗΘ‘‚\‡΅κЍ¬r-Δ"sxrEνbϊ“¦O§Ε)F―cTzXhU²ΐAy―8ΑΪ;α³ώ%ί‘ƒ Ο%8‡@ΊΒ, +X6·©l]flΦωmi΄φεϊΧ&…UΦv€‘­§…y˜―*ΐCΪ{%γj˜ͺfρΩTϋ”J^oΰœ{Ϋ”Χ¬†ώ•X­ΦΑuύ@€c4iœΖ‹εCWγΉ”`θ&5„4Χ›Ξ7SΊ²¦μjRŠΔ―C”hν${@g/uΤύY§φC²ξ―ΔJ0ωUΪ»₯β} θ]3!ρ €~]z±Ζ9>_¨~+j₯`d‹*@˜lΰm2ώl5κΤ|άδݚΨk}ΊI|Άα΅vI7ΉŠ6gϊnΜARLΨ‚Jωκΰφ³5μQνΈDΖc­-αΨ+ΐœ[ij#!δΌψτɊ +Π(•G0—F½Ξ8Ϋ ΚΩ₯±rπy£kΧͺ΅M..'λΓmΈΨjSϋ[ΊWξ_”v£ρVm½ΨΛ@dƒίύ@Ώ^ΆWρF¦2q2  =KF•―Μ•SΊϋ’Ρηά·ϋTηϋŽΪίcRˆ=Α²bk‚Φ™fkυdˆ<μZOYΨ§ZσTσƒΙΊ`+ψ˜6αζ―λ°9bΗ―­Λ».ω‘HƒZ)Xƒ=ω―"^ΪlΏ.ğ<‚ΰ8hΆ±;KkϋΡ¨;£nΠφ"ˆ…l" fΧΪ”Ÿ/j·7fΪS7oεν ž΅t ΖYλϋ)Ύ\‡%(χ°­IΠΣbκΑβ@ +zύ 2’‰mu:Φ\šΏIš + >G$ιόΡΝ2Ϊ…ΓΝ#„Δ~ΫήF+S=)·θ#ώ”πΚ ΌΫ R $˜…’:ͺ洬ΏWžV²ΰuŽο±QqΚLμΪ£Βƒχ₯’=Ψ ·)‡ž|”)B! i€\;°Ϋ£HΓiό €– +@ ¦tΠ©Δδ›c)%3<ž’ο_γXiό̟>ύ΅X\ωΛύv΄DΌύ8y;K©YΔ|5Ι»§Ό‡T›ΑΉςX=i€Ε|(“6ΚdP`K‚xlζΐ³^\h_ΟwΔ\A·=Bψελ>ΞΟw%¦r˜ϋΩ’φΘ—ΉMΚΊk1Rfέvπ¬ Φσ%ά»NpΨεHΧήμΗ1Y‰–moεœUŠ€ϋR€9΅6ΦSΙνŸ¬OK Κ+ Λ­œύΚk{SΩͺͺA²^;·΄hΏEΧCp-±„΅ͺd:•–^pόμί]kŸ6ϊ©d€γΝκΓ]ΰΆ/GΰΉ£~fΜ‘΄θ½ΑT—ΜΔΰΕlhœ^'˜³q-$₯N°©‚ϊ?%fA=½©›/έV€=ΫΕύη0(ΈuTλš -ͺoS@­N™CZ·₯ΠΘ2XΉMSb0f›Š#¬O>Xα˝Δ‚#Xό,ϋWžgΙΰ—Ώn€ΙΎƒο: @ԟNπMלŽͺπ»λ`ΊΎϊσYS―wΞ sSGΦ£Α΅Rώ·§΅€†Βu +”¨IM5υΓΘ‘Hέ54KΡBR΄XqOpσP“l$Χd=ݞ„ ‘’(‚^iπ@χΤsͺŽΔ>hεœ›H(±ξA₯άΖdΤ9ΊτχJ7ͺ«r1Δ +CΆ1u·‘mŽ•γρžBΟ +„ΧK€η’ Ωt1ΰ;k«μΗžRž :YΤάϊ‘ιIsχ՚ςl²¨ω―WΟiλ‡u™ΩΡTΟžη—™νwπή$|Ά³–¬Oš…ΤΊš-ˆ―ϋηΆ±Ÿ‡ω~ƒ'€¬αδY^/`_ΊY27Aœ\98&°έλόμ‡ J}₯‘5φ^XνT+ZΑ@ΡFLS}›Ό’»½νT€1Uφgs­ΌlϋL'ΐQ'(e›–^ΡΫλs€Q}ξρΰ::kΜν`o ‡–πGπQΡ4H%†8_ΝΚΘMs 5¦Ζsͺx¨θν>η61Ν°­›­ΤΜ£Τά΅b΅Έ½ƒχ΄9ΛΪl’φ-ωζYiSεjσ2¨=Ωόθεd²Š4‡Ό? τ~֟žqHŒd«οA°SΈτΜ,Λ;α3% cs©…Δ—·¨ύIύYYνGq2μh]k!‘9œp­IVεH=J >]•Ό%Œr·Τ*4η(·εί0Ύ^j0!cRB„8Aπ¨4έq*χZ8n„°ϋYz±eΧq#‘,Ÿe$LŸ’±uγΘς9μOe”„Νδ.κJOR¬ϊ +ζ<Ε~ΜδŽW_ΩcV#ήIžΪςK+χθΥ5&¬:ι|ΧΚQ N^· #{cͺΓ΅ύ οβύ<’ΕύAυ/{DΓq‘Ήuϊ!γ·$J Ό¦”ώπRi ύκͺ@₯Σ$F“½ [₯3Sώεώ7.lΣa}M θΊΐ2bλ8‰ RΦΣΈ}°γϋ! ‚qΤ{ +ΐςιύΙw sž₯)υΘ!ΎvΑ‰7«'rώ.• hΧ?άύ1t$°I7jFdW_4γUSLͺ(ώΛv΅[Y–Ϋΐ”Γ& ώΪ#s␷ω»*UΈ½Zy}ͺω@^¨Ο±œ‹MUg—oŒΌ¨Γξτ‚ρ₯μυ‘ L&9žl‡φΖ4™θ%ΜP‚’“`Bφ% ζΦ¨Νf§ +€`y'τΏ {ΟœYβ”|Ηl/bkk―cGηΌ:Ώk_£«ΩΤn/ΟεZίΊ[?ΌC—h―—d±€Υh[»UΦg +%RΨαUpœ±ρmžv­¦.BΈcY/CYι$ζ¨ΐ΅Γ‡‹q&sδi?bά€μeΩ3₯F†›Ω,p_κύ©Ύ;F]+[ΌΉ +œΗΡiΓΌ +”x‰σN†ή[NpŽTuœΟRXC;ΣLŸšDSδ;x§v)υ§εΌϋ( –fŒ–έ5<©/ΐζta ΔρίP{ͺg gχŽ‚έε;·y>ΩύΑ©wεœTΊώˆ>7»€οeΣJΣ€ΐem[† Φ†‡΅φΓ8Ώ[Lpžυ?χm½μ|HŽ2 ΰGΟ͐Ÿς»,J +0nꚑiζ.Cž½l°A₯υ^\pE(R­­ ’i8ηϊ)ΜYౡ€αΌ›±ΰUΐΡΣ€Ϋ ΎPstυ`+΅`Bψa01μ/p(™ +ko7’μ¬Dϊ ΡA-vΥ½-mΔoέ<ƒαdϊξ½ΔΪάι65xό~KEρ˜m{ϋDoη%τ  –.°‰–ίY\ΩFuMφ½χj―$eΩ6εXΐvtή1/ύ¬ΟPvBΏφΑλΤvN€» I¦8`½Λ£½7₯²C=:(#Mί’?Ί›~:_Χ!Ϋ%J0q¦š\ŒφQ!φ·Χοe&"n«―Ί&²]αL~s5„†}k―kbZ +εχΓyOMs½q³]ZήJt±Φ―βήήΙ”±²(iO)KΒ:–εΰΈ +D’ ΨΏͺ½}p1<<§άÐάj₯½ +Ύύ–jYωΰƒθΦvw))ΌaxNkη oΎrΘai³αι”ϊj₯nʌ~‹Φ"ΙεχFƒΛζLΟόpΓˆΎζ”–† Q‚ΩZ +I•Γˆτš nƒCώyϋΌΈleŠΠν₯!‘’Όn8\1^ƒδk·΄©Ό€;"Ν(?]$ΈŽθ`―:ΐ©Φf3ΝH¬½­D;# @σκΜό1Ϋ]4Ήϋ’5½σ Ψ;O„ηk"ixmُτ#ι •Ρm0v*€`ɝLͺtƒ:MQIτ~ŒΜ»"X^}υ–—bꦙΧ“‘ξ·t՜kΏ\Ρ-L¬t±Σ@`ˆ¨λΓ ±Ρ‚τ"δρžΕˆ ]¬pQ `9ψ ³k\λ5΄ηΞυΐπGWΣN―Ÿ_0^κ­]Sέ’γςΐ^#€ξg…/wi{§^{Ϋu5ouž―³;O ZΛӞ1jήΕ²OYMΓΩ&˞̟ίxδΪΑA²΄άχέ">ρΪγΒƒ­„ίΎό΄›’°Δβπς,πͺκΒFΟΆƒaœΛΉv·t―‘„ΙpΐBB +Ώί―δι•a2ΑΦuΨΗ:L9o9rΨi» +ŒT­άk_]8MW³zρtθS:74œu].3Μ)›F§μ ΰ=Ώlι¬€`Ι +ΫΖ$8WηŜ+ž`θΣFž t8&ΏαμτMq‚9τbα-U`…ΣΘγn†Πw²m±Φ„ΡΈπŸί^–w ’δJc™ΰμ&pœ―ΐhzHΓπςf0ηw³ξ\Ν‹T@kƒE/μ`€Υ4΄DΆSμΔU6; @ΧΧϊV‡b[#zPΫ†Ύ}d· ―“ό1«n<Œ jγχέλχέ/;τρΟo’°δ*ή¬η•‘ραέx.ήm(HέmH…ž@}Ϋ—ξ+λ²?^vMΠΡϋ,qš€ρ"u±ΐeΰ’=~6Ž wΊ™φςhΜΤ6aχL+CGTΡT»ρΙBς―:d ‘νLWA.ŸcdΔ<ΩΰοOφΩΥƒΨ|κΒ‰ΈΖΉΟί„ Vg Ύ)1(ς Ψ)Ί³xυ™Ϋ\ +Θ`šό°fτt.œCOSΘίcFFžκ9»K+ζα哏μTΏ·%Ε ƒ6k ˜(:ΆZ«οκDι: ό­₯βϋΗΠfύ™~“' £s―ςRœ€|π’Δ’o4>Λ‘σ` Ρδvυ[Έά=†ΰ!η»ΥTαη|ηQΡ/gη:yϊvγV»jN06Α^ ˆΔ  ŒΞ­»ŠπΚLΡRXDΡJ€’ξt1AίσΛOχθΆξJֈόδΊWΆΥ5e[Αͺ*”w·₯gqΕ/l –žβ1$ϋΗw΅x£<–Σb£jsϋ+9D.€ά―Η9_Z”βϋt/΄'Ξ™zΨ6ΧΌΪͺ8{(L@―/ΈΕ9tžλΊ‘τFCΖ,±2lε±Ξ¬ϊBl‚νŽ)ΠύH˜³υ›p[ƒ7”Ο Ήχ_Ϋ°γbœeAΛzΈΘ;8²£Ιύή[ϊu|–θ|FD±“6ί[)Fο ―Ίς'€ΓaεZLύψΘ0Ž+.^ @ώΔ~1w0pΆ0ΏžEŸMh7–ŠfRΐ§Έh3~ @σζ"¨|r¬„ϋ–¦†«ΐfΟa‚-Ϋ ΰ`* Yϊ2^ΑσYY$Ήa‰}°¦ͺ}½·ΑJά‹ύ0* 3·kςξjΠE™2―1FθAΞY,œk`Ι³όΟ`?ΉΧ²-2LΝ]ΠΑ/XGCζ%«αFL… VΑ}|—ξ΄ Zήe{nΎ%άIYR$M°νΖkxύβοϋΦDYOZ·ΰo9RL―ά.ΧυάoΨ%ΈI’@γ=žž“¨Ӵ«ͺΎ³›.ήbL4‘γ²hΟS?Œ6ήΰr­ό!>Υ]ίδτΞ;iFFΤΰ6uΦVMϋ’`€A»ΐ₯ #ΌUχm†ΉD§ςΊ₯)V†gN=μͺ“ζ(Φn=ŒοτΤηϋ'8~ρa ƒkΫρ2ΪI>φΡrΠ”Ξ±u©aΤZψ`ωΤΖψΑgϊ9όύΦN΅ρ‹|ϋΖωΛf;…'Α>Ap§¦lΟ!EG[Ί3 ώžυβISΦ1_bΤ«†X†~φΤ0€³ΎκEΗΗ|χ ο,φΌbΟaδΰYΡ·ν|¬sè邆_ΐ½΄βF.zs<‚*™,qh€v(κ{C¬τ΅4²2M@uܘ―uΉτiΜxΪWυαK¬*€OŒΎΧ>Vδ,™sPZωΊœΏŽΊ-j@Ψ3ί―ΐ=+ΕHώϊΗώΡψχ+ƒK?St0Ρ”ž_/‡P&x†CΖ υ£„Ίu9⚱η\¦a:–>ϋοˆΣα…Η’O{mώ'aοkKaύέΐ»—T ŸbΊή0π‡SKwGδbΡSfŸoωχπdγy©χ.F§Ypš[ΐαΑ¦–Iwž₯’§$ΌωϋψΔΔζΌ\9ΠΖόίύγ`-t„u2nŽοO‘ΜcΘ€;ώIΫ-Ÿ›ΰ֎³œdΐ7-> +Μ΄²#(mύ—ύͺmnΫ8ΒΏ€_W€ύSšκ!$+A+ ‰[j”hΧ~ ૃUVρΖΏ½ηxBAΊSρÚH°β.qd0Υ<±έΞ‡`Μ‹X8ΛC\x<ƒ‹yAξ΅ζ= <ΔΦͺΒV¬RX”g9δŽΰΦLͺA©oνΧ­S"α~­Œ¦VΕmΑ”…’Ρs^:o#άLyAРߜ»Ž-Χ./JYcp²ΙJ`€C +#ΒŠΕ›•Ρd·-gΣΈ±|?—3j~\AsBχFW§ΆH†H*`ƒΑr0©[₯!)΅;΄a|,ΛΐVv[0|Ω€ν’7—ύ"‡Μ㆔t[ΞΓΒ½ΐ2Ά‘‚%4N"HzU:½xB@ΗŽXc~[T…n€hBE`LƒfΩ―Y„‘ΡΪΠ §σ9Βvφ[Ψ΄‘ …Ϋp„Έ©,‘0©Δ!Ή˜F Β8Ž ֚Ϧ‚ξŠ ι’V} j©tΕŠΧ‡NWΐֈšv₯ϋΜ F$Θ2Œ%β…έ,&# £yλsΰ R»DΕb²uMQŸν EZ±Uι„ά²μ G’}08¨5hβˆZeӈ0M(Ν "ŠΑ»€MRάhNΘ@œβς‚0ώ‹[ΆLιͺX +Ά:w,ΰ) ₯HΤ©5A+#Lνˆw- |ͺ)‘ Μ[tΑΖiΛ9ls]Α¬αΝe‰BŒΝ¬…l—Ϊ=5ΦƒhM8u ρr.ρ&°i½“’ζϊΰF0q«Τ²_%Γμ°mΌVμ―mx4‰”}o΅;&ӈΞBWΡtV)XA`³Ά' +M1ΆύŽίΙDšlm(C)Έσ¨D΅΅`„Κ@# Sφ„ΗΑΌ1JάΩ ΐ‰¦p¨ΤνU­ιυβ4fP‡’^Ϊj»ΎŽ6μ`†ΟF(½άeŠΊΟ’cέq8p4Λ* ‹Š!έ©`Ο Lc£Θ `!ΕHΆ +ž•ˆš˜t§I ΗsιΆYΰ@!’i»¨pΰ’Τ s\UD\vŠ + ŒTΤQ!«¬ΤP'ΥOS•°Υ(b œ΄γ=M#zl;yΌ' Ώi’ς›"œꆉαlΩ‹‡7RZ‘`-£Œ8’Š$HœΈr2R9ŽFΈέ@³¬ e BŠ‘1ͺΤ‘– †1±ΰέ%Φ2n $’΅πδ»Tm${΅ΐΰ€₯Ά`™©)R’1e‚ZuO΅š’Ϋ΄Ž± +Sn²˜DΌ²ΚορB·l@’²ΒΝ6„S»Λ:QcŸ A˜šζ +΄€^0qJΆ΅ΫeάθE9yΕpš‡Ώ±uύ”l@-¦$oP ―1ξφ{8ςE¬Ό‹βqυ +νmP° []άέtͺ8QOŽ5Α‰p’Γvc‹a€ q*MDBuω:Jm ‰ƒ»— c»±!rΎhMk  ΠλH\‡¨lOWΫ`γ¨Σ έ +­ +ΐq,–nσΣ(]1ΰCD)RΈ\ΦΨ“bσF₯1»μ΄ψ²χ½4xό$ψπχΉΏ=:z•œRŽλιdψ.kšΌ.xœ,Κ9ψρϋ²ΜςA`Ρΰ@<₯νώαΊ7…?DΪ>|…ψώς  λ@„ΑλΰŸ +ƒύ½w`@ƒ0 +ΰOhKRΨΦmš’˜Α§σp aΒέγ΄kb%83PΏχ BΘT ΠKqA…„ +±/ιΰ7XwG?V8]*xΆΌq$νΰAΩ‘ ­ΔE’Ίπiο θCά€ƒt„ίπ’š9ΪEρό +Ά‰ό σZ&©³ ‡ρxdν†ΑΫ[-^Š5]³"EkPšΦ,‡ͺƒΞ…ξ…˜₯K u¦?ˆjΟ²QμIΘ ίO ΠΨ@ϝEPγΣ΅Ϊ;ΜΓδmΧj—Φ|ϊsU0΅!Η‰νθΜ‚„‘ˆΌŽ΅6―:—v ™{-8#17£άΉΉ€γ$Š—ΞJ₯Ε‚Υ8wn=YZ€ήn“°=θι6rS’²ΩΞ•§ y!ΙC7~, 35r°R03FTw1(t-F+ψ9C&~‹UhXe0#\zΐ½…ί’άΒ…«[Θ†FΪ?ζ ™aΧBPQzή„°uϋΒ5+Β¨υ +LF2sk‘JBΣ‘ys°[³ tλύϋ±ύάλΤ9†·4>θB«Ÿb|l”πί0Z”k€τ ¦N[Paμϊτ6± +cη]TFdW‹”jF ƒ1Lw6α’Φ.xξ²8UΑJoWU₯݌–(aΰc½@IƒtΤ©š§„³%8¨΅HΙ'‚₯Θ1E:' Ay9GjΩ_οZϊώŽ<S•οκ’lŠςγΑƒν`ξώΠ{3Ζ_Dθ~:ϋσε‹bfz‡ν_ƒŸ{‡^ŸΎ©9ώυyΡoŠͺΜκ―·ύπ,x|s5*α§p«..¦M>yόΤ;<ͺλlαDXŒu^βο28|U6³ίπ_ΝΧqŽΏ=aψθIpψΎ,ϊŸέςγόΡ/ΩhκΞ^ƒfxϋaT$x\‘Σ?ν2£a^|6ή”ψψΦ9UŸς~s\MΛψw\έψΌ΄ωG›‰7ΛΉoΆNυθΥωΡh<ΜΞ…/Ηb'ΧpΒ3[§βŸ‡_½ηλNσX“—7όηνξξJ›l¦υΕt”—ύά7ξSΟhπ=Ϋ~Ψ§aμΛη"›δ/κόίS‚Ί.|΅u†—^9½zΫo²/ΉŸμ~²ubeuΦMΝ8žρ›ΨΣ£ Ξ}³u†XrΎμκ|2ωΟq>ξӁΎγΊΈΕυΥΣ¨3”σ?<ΫΙΜη[ΒΏεw)JίW©Ζy5Uνύ.³v:ۊrM[ι +<»u6gΥ΄ξη/λl<,ϊώ΄6`u‡ΕsR]«IΡlP;?Β«’}]8|ž_Οφ+ΰξ1Ϊ―€+XξΘ +¨φ+ΰ +"ϋπNΪδeΪ½©ŠΙ[ΕΓή½»Θ~άο€ϋpΏξwΐύψΆͺ·nΐθΎμ€°η_ςΡΩ0TΧηήΊmgΧ£ωP€M˜<Ψɍœ£Ρx˜ύ°³«ΛΓ€<ΟΏ:΄Ίξ~΄ur£ιšιuευΛl:™YyΌ–γ. +lο΄ψ·ΕΑ]τE"ώ]qp³γ΅²λM­ΊΌœδΝϊΚΈΏΥΦ2Όu{›i}™υσ³~6ςށ>ΪzڍP6ƒύjTΥ?_Χξoέρϊu’ξτΎIό.“qή;]SCƒ±ν½0τN9ΐt”ΥΏάŒ«2/ύΉ-Έ}–›’<©ΚI“ύ$gή§MΦ5ψŸoΎyGεΫ]¬žb.ώzρ.δβAΌΕxs‡Rδ]U”Ν)Ž>9ςC9£Š=₯1|ΤΡΊ‘9c[σ:k*οΝ―σΑΦsώ¬šΦύόh4fώΪAn δ½ΊΣuι€ΊW“΅r|{‚χ΄ψΌFuޏn=Εό‰("j—΅ηηȟH΄ΣDό»ρη»hΖY]4Γ«Ό)ϊq~nςΝΣλbΠ ½S•No_5*FΓΌψ8τr||λœͺ‹OyΏ9¦εό;n| ΊD…£ΝΔ›εά7[§zτκό€ͺFΗuž[Sδ:xςφΛh7qˆ’ΏΨV--ΎvAέ'ŠTψΑqŠ%Η¦ΆΓ’ύϊM(]ε† +Η±ͺ™’S{ξ½συΘξKgΝuƒΛXΐ'T΄A‡AjnηΘ +s³PΔ2’κf7wΈυΚσ(sŠeαΑA€ AN^Ω|;τMF’DδΈumΛΫ‹ΘΪ,ζZκWXΜΓY[βU·h4m‹¦Α M»hΪ·ς7z* Ώ“6ς7>ϊ‘kΨ]4"“œ¬b-ό8M-‡ί7œqO 1GςY ™u3x2ΫoςΜu‹»ΛŒ,2ό-ηŠα’φ©Λ;`ΕSy6.xΙqέoρ¦τΔ Ηζ(Ÿ­ͺ§B6<θ Xƒw&xΘ‚w&<ŒvΜ™¦Ή™ε²T)X³Γq§GτPŠΑœ”βrΒ%gNψx޽_½ru*δq£wΘ{XΞI<]lͺ– ‘jJ4Όw±£οΐ·,x;‚IΊfGCa’ΟΈr#ZtΟ“2*Τ*ͺ]ιjϋΉ:zΌ;xD©«'<ŽoΛ³ΰ₯ο!ιƒIΊ&}-΅ς’μ5Έ`‘J?–Qƒ›uɐ/½Ώž­š>¦3κ@(Σ—ΞΦ‰=©RŽu±ΠQv43ώΪ@R6ΥήyπΫ‘}|ύsυώ α§.ο€J+ψ&cy–Kr πφZΌ³υα[™δFͺj½³^-ZπQ%‰pbΩΰ >Β5ΎΤθ WΒͺ؝ƒwIo’Ί’¨€¨΄aG¦‘m―λ½sI‘8ΥLϋŽ‹‘d£ς70Ψ‘₯Ν„”Θ(τsΌCΚSn­ υKβΚΞztύ6Žχ’ά˞Η`—}ΟΓIΞΈ +eθΗij9όΎαŒ{jˆ9’ΟZX8`­›Qί|“ο­μζŽ#3²όΙπ·œ+†kΪ§.ΡρεSy6.yΙk"Νά~K {ΩΔ Ηζ(Ÿ­ͺ§B6<θ Xƒχ¦^4ή“tΝ›¦Ή™ε²”©Ι8zD₯ΜI).'\rζ4œΌΏhτ~υ^PΘΥ©Ηή!α½Β–{$ž/6ΥΛ…P5₯ή»ΨΡwHξ£±#˜€kv4v!‰ρŒ+7’Eχ<)£ςO­’Ϊ–ΆŸΝ»=’ΤΥΗ·εYπχτΑ$]“Ύ–ΪŒωQφ\°ΠG₯Λ¨ΐΑΝΊdΘ—‚ή_Dϊ'ΐϋξΎ endstream endobj 30 0 obj <>stream +H‰μW]sΉύώΌάͺΈjχώHR—'6”«b;΅ά›Ίo.!υ€4W#a³Ώ~5x6λiωˆyˆƒ‘Ϋτ™Σ}ϊtηMnΨœδiη—“ξo>έ*tGœo½tΏ :oS!έGΏc4ŸZy™q₯5Ω‰ s.˜†ς/žuΊΧTŸ?Μ2ƒβ³7τς΄ΣύδΤ½9qUΞΆDΨU€†ά +σ|΄$iμ*Ω„ςςΒϋΟήι>_mcbσœ9Vc«ώΏί¦FK)—h:Ήl‘ΚΠ3(žΰΰ10Σ#ρAŽ\챎vkΰΒZη»#H:ƒ“ξδΛψc™‘|_]ߏΈ ξΟΡνΚNΏ|κ}Κp}Λc`ε3ΐ>•&F‘ –άrM”ΥƚdsNχ¦HA‘{UΖmŽ·‘λπφ$Vιl„š-ϊφƒυννQߎϊv ϊ†ξΥ£Ύυν7νήxέφ ŠGpT·Q·£{;ͺ›§Ί½jσvT·CR·£w;ͺZέΖΔζ9'r(¬ΎΏxΝ"‡Ÿ6‚'EAθαΩNjm„κt‡h/Cτξ5όqˆŽCTλ›Λ^粇νμίo]jΑ혚~j†ΚJζJͺG?˜.Τψ]η‡κ€π3ρYε}5λ †T-Π'“cMψP,ZΰΆΧΠ„š)ΜF±k7Ui¦rηŒξlΓ°4|Ψ`<<]θa9ωΦGσQ„ο3< <‹6Ό6Θ9ΘyΤ@πzό­ 9&š›y +Ώbo‡zή ±oΠ—lCΪ€θΩzΝ«΄B,[ψEΤzχkK”ή€žAρ0ρ”ξ―ŽvkΰΒz)tGt'έΙ—ρΗ21’†½μu.юkϋχβk‘ύZΛ.³jϊ¨*+™+q¨ύ`ΊPγ tκΥυύgώβ³ Λϋ†•^ΣvϊύF― Γ N,ύ¬!U‹qΨbZψP,ΪΈ‡š°B3…™Η*YGΗƒ‘ +ΦΣwί–Ε#ανd‡)•D·!‘Τ¦VΌYΪJ β{’YόνTΖ£!„βΉbŒή$1u¦žΒί»β,ΡTΉ“Ανub¬βƒγ\iθ« +%‚ή(Όk­%GΖ%ƒ„ΛΖ³©ΞZČ<Ζ¬–_i7g½ΞYoΰe=ΛWx;Ά oιV"’§jώ ω™(«)\‰lNφv½†4M³±γˆ‘XX Xήaδ†`ΑIQvRΤDΉέc…Χͺ*Γ[’ƒΖΔζ9'r؈1FUθ£έO›Γ%5x~ώ’Όω– „zΐβ|67ξσ_© +ΏywΣΒ―_΄~X8S+±‹šJ’L1˜—\š +ά•H_Սπrrβc!rVi¬I6ητΘΛρˆ‹ύˆλ_τώνΣΞΜέͺλθΰΛ―ŽG4>›γΧέ&<όBŸ~j†ΚJζκͺ/΅Ϋ¨.Τδh”[9Α‘^]ίOζ„©‡{τiΖΩιχΆ.œ¬,Ώφ/Ρ­ˆχωmΨ|$x£ˆ[f±hεjΉοΧr†τ $7l NŠ‚ΠΆ“’>ινπ›ηœΘa#Ζmς{,- /‹¬ ]ΔΑ«"{Œ|Vb5•$9˜b240/Ή>4Έ+‘ώ€ω?œ%όrr½ΫΆ%RΛσΆx’‡§λ+‡4Φ$›szδeu΄[Cy!bKθŽ ι NΊ“/γebLj2ύ +Τ ••Μ!ͺ†-½ϋ\¨ΙΡέ»•|+^]ίOŒ³βψΉd§ίo„ϊ@²Γ:αM¨!βVρΌa„ͺη°JFrΌω¦ΐΈzΌΔΒ™’>jψŸIρξz'+8ΐ~‹OΪτΞqΌό`ΦS‚#“jbΈ‘s,ΎΌŒώ7·r‚#€ύŸε² g^β‰DΒ’F²Η›(–­ !U τ>pk\ψμ|Ρ½"_{ϊώΫ²~$¬τS’©#”ΪΤ +b<ΰΥR‚c»lψ +³h?ΎŠ ŽEC&ΕSΕ7Όi*λD=%ΔμΎ­RΌx”Α-ΐy‡φΖ +οŒU Pˆx KτΉέeˆφΪu«ψΰΈ—@&ΎΒE‰ 7ξ4°*!82.$\ς&‰³–ΑŸμ—ΝrΪ0…Ÿ οΐ¦‹Ξ„ς“ι$+~ +Σ™L³ “.3Β–±ZaΉ– O_c ’VWΣ"ˊ–It':Φ½ηž‰‘†–* 1ε2:‘Ρh)ά-! -tyŽ;Dψ^f3Iσˆ q[2› +:YŠ3$Ό=Ζ»Κdΰ>Mcg&E”?‚¦Υ¬‘Ν• o„‡l‘2τ¨s0‘ƒΫΊ5ΒQγζ’5}˜Œ‹B‹¬ΣnΏφΘ’„"·Ιξ΄ρžΧQc2α©cάΈ&6ϋ†1`2 σϋ Ψ +z‹~ˏ +E+gίrq©ύϏ‡εψxφ―πνΛC[6°ŠHnΖ5ψ…ΦVλP4gIΗ +ΆHlρJ m%H+J‘˜Άϋ?Ζ™³έƒκ™!ŽΗώ!σoΧ“*γ +?Bε%rqτ„αfY.1.,aSAD ΨΙ}Ό8}O¨†ΐ£γ +7#§Q.)|™οWe=z΄l„<jέΓS §@ƒš^-‚σ©§@kt8KQ†ςΐOΏ0Βγΐχnc XžΗ@=z „ˆρθφœΓ@ E_PY9~e,œgΎi¬eΐfΗ +ΤQβ,nSΒ$CiL‚³mVΓ’fT*6ΩΙ ΅T1o`ήγ"α'‚6’r‘ΥU7"š Ι9AΙ@©ΡF&·]7ω° +—‡ {|Έ²|Vl7hE+fΉ>½U³™σ ‡}šΖΘUO»Ό›nΡ<7FYv=£(ψ~ΣΨώŠ₯( b}έ~§±gΧk,Ψβ΄ρž,΄Ί2`Zbκ6SγM#Φo€ΰ(ΛRœ!‘zΐ“ +jdε$ιjμάνSd{‚ΰ1 +Ωrθ’Λ½ž1d‹”q"ΐkΤΫλ€q‚D‘δΉΤ„ ”ΐηYρ>\ΖΚ—9Θ€d‹όοM€»”Tž–ΩΗ=™E(ΐΣι€Ύ£"γβΐMΊu=mG5Ζ₯Νr{te‡νFέ•ύ₯)ηŒKΛ–eρ‘ ύ˜ζ?lώ₯ +σώrΆOτ“,€ˆΑ²~Ÿ7.ͺXIŠ ΓXQΤ‘"FάnΔΑΣΦ™RψΓνnυΧ/ϋω`Tχ`δRzΰ)$EٝT φXΉ¦χ xoξ?Β§UΚ¬YΟ //~ 0‘ιΎž endstream endobj 6 0 obj [5 0 R] endobj 31 0 obj <> endobj xref 0 32 0000000000 65535 f +0000000016 00000 n +0000000144 00000 n +0000048300 00000 n +0000000000 00000 f +0000050202 00000 n +0000281080 00000 n +0000048351 00000 n +0000048691 00000 n +0000050501 00000 n +0000050388 00000 n +0000049078 00000 n +0000049641 00000 n +0000049689 00000 n +0000050272 00000 n +0000050303 00000 n +0000050574 00000 n +0000051012 00000 n +0000052009 00000 n +0000056481 00000 n +0000074080 00000 n +0000089178 00000 n +0000104967 00000 n +0000114567 00000 n +0000133441 00000 n +0000157353 00000 n +0000184825 00000 n +0000212166 00000 n +0000239092 00000 n +0000265406 00000 n +0000277302 00000 n +0000281103 00000 n +trailer <]>> startxref 281295 %%EOF \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/Contents.json b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/Contents.json new file mode 100644 index 000000000..9118d98aa --- /dev/null +++ b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/Contents.json @@ -0,0 +1,27 @@ +{ + "images": [ + { + "filename": "room_action_resttime.png", + "idiom": "universal", + "scale": "1x", + "size": "24x24" + }, + { + "filename": "room_action_resttime@2x.png", + "idiom": "universal", + "scale": "2x", + "size": "24x24" + }, + { + "filename": "room_action_resttime@3x.png", + "idiom": "universal", + "scale": "3x", + "size": "24x24" + } + ], + "info": { + "author": "xcode", + "template-rendering-intent": "template", + "version": 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/room_action_resttime.png b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/room_action_resttime.png new file mode 100644 index 000000000..d0cc871ee Binary files /dev/null and b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/room_action_resttime.png differ diff --git a/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/room_action_resttime@2x.png b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/room_action_resttime@2x.png new file mode 100644 index 000000000..57d2136d2 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/room_action_resttime@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/room_action_resttime@3x.png b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/room_action_resttime@3x.png new file mode 100644 index 000000000..b2bdee662 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Home/RoomContextualMenu/room_action_resttime.imageset/room_action_resttime@3x.png differ diff --git a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Contents.json b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Contents.json index 6276dd85e..953b84292 100644 --- a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "Group 3144.png", + "filename" : "dm_add.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "Group 3144@2x.png", + "filename" : "dm_add@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "Group 3144@3x.png", + "filename" : "dm_add@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Group 3144.png b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Group 3144.png deleted file mode 100644 index 01ffc3d5e..000000000 Binary files a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Group 3144.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Group 3144@2x.png b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Group 3144@2x.png deleted file mode 100644 index 5f750e6e9..000000000 Binary files a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Group 3144@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Group 3144@3x.png b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Group 3144@3x.png deleted file mode 100644 index 8563481b9..000000000 Binary files a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Group 3144@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/dm_add.png b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/dm_add.png new file mode 100644 index 000000000..2e7453946 Binary files /dev/null and b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/dm_add.png differ diff --git a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/dm_add@2x.png b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/dm_add@2x.png new file mode 100644 index 000000000..2faa7b884 Binary files /dev/null and b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/dm_add@2x.png differ diff --git a/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/dm_add@3x.png b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/dm_add@3x.png new file mode 100644 index 000000000..67de115f2 Binary files /dev/null and b/Riot/Assets/Images.xcassets/People/people_floating_action.imageset/dm_add@3x.png differ diff --git a/Riot/Assets/Images.xcassets/PersonalNotes/Contents.json b/Riot/Assets/Images.xcassets/PersonalNotes/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/PersonalNotes/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/PersonalNotes/personal_notes_avatar.imageset/Contents.json b/Riot/Assets/Images.xcassets/PersonalNotes/personal_notes_avatar.imageset/Contents.json new file mode 100644 index 000000000..87c46ad3b --- /dev/null +++ b/Riot/Assets/Images.xcassets/PersonalNotes/personal_notes_avatar.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "personal_notes-1.jpg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/PersonalNotes/personal_notes_avatar.imageset/personal_notes-1.jpg b/Riot/Assets/Images.xcassets/PersonalNotes/personal_notes_avatar.imageset/personal_notes-1.jpg new file mode 100644 index 000000000..77b011135 Binary files /dev/null and b/Riot/Assets/Images.xcassets/PersonalNotes/personal_notes_avatar.imageset/personal_notes-1.jpg differ diff --git a/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/Contents.json new file mode 100644 index 000000000..19ea7edd6 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "rolls_and_rights.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "rolls_and_rights@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "rolls_and_rights@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/rolls_and_rights.png b/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/rolls_and_rights.png new file mode 100644 index 000000000..644a14ba2 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/rolls_and_rights.png differ diff --git a/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/rolls_and_rights@2x.png b/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/rolls_and_rights@2x.png new file mode 100644 index 000000000..59b558e93 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/rolls_and_rights@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/rolls_and_rights@3x.png b/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/rolls_and_rights@3x.png new file mode 100644 index 000000000..1b1444b75 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/rolls_and_rights.imageset/rolls_and_rights@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/3144_bwi.png b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/3144_bwi.png new file mode 100644 index 000000000..12a71f219 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/3144_bwi.png differ diff --git a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/3144_bwi@2x.png b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/3144_bwi@2x.png new file mode 100644 index 000000000..0c7d53126 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/3144_bwi@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/3144_bwi@3x.png b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/3144_bwi@3x.png new file mode 100644 index 000000000..ec9699c70 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/3144_bwi@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Contents.json b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Contents.json index 6276dd85e..623e3d14a 100644 --- a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "Group 3144.png", + "filename" : "3144_bwi.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "Group 3144@2x.png", + "filename" : "3144_bwi@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "Group 3144@3x.png", + "filename" : "3144_bwi@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Group 3144.png b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Group 3144.png deleted file mode 100644 index 3e3c18811..000000000 Binary files a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Group 3144.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Group 3144@2x.png b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Group 3144@2x.png deleted file mode 100644 index fbfccecc6..000000000 Binary files a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Group 3144@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Group 3144@3x.png b/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Group 3144@3x.png deleted file mode 100644 index dbab11a19..000000000 Binary files a/Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Group 3144@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/Contents.json b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/Contents.json index da4842521..8ef06aec6 100644 --- a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/Contents.json @@ -1,23 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "secrets_recovery_key.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "secrets_recovery_key@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "secrets_recovery_key@3x.png", - "scale" : "3x" + "filename" : "password-2.png", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/password-2.png b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/password-2.png new file mode 100644 index 000000000..3123b91ca Binary files /dev/null and b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/password-2.png differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/secrets_recovery_key.png b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/secrets_recovery_key.png deleted file mode 100644 index a2b43eab4..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/secrets_recovery_key.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/secrets_recovery_key@2x.png b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/secrets_recovery_key@2x.png deleted file mode 100644 index b760e84a6..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/secrets_recovery_key@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/secrets_recovery_key@3x.png b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/secrets_recovery_key@3x.png deleted file mode 100644 index 8b5e3cc0d..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/secrets_recovery_key@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/Contents.json b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/Contents.json index 86e068eb7..51c03f62c 100644 --- a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/Contents.json @@ -1,23 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "secrets_recovery_passphrase.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "secrets_recovery_passphrase@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "secrets_recovery_passphrase@3x.png", - "scale" : "3x" + "filename" : "password-1.png", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/password-1.png b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/password-1.png new file mode 100644 index 000000000..3123b91ca Binary files /dev/null and b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/password-1.png differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/secrets_recovery_passphrase.png b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/secrets_recovery_passphrase.png deleted file mode 100644 index 0c34a9b23..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/secrets_recovery_passphrase.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/secrets_recovery_passphrase@2x.png b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/secrets_recovery_passphrase@2x.png deleted file mode 100644 index 923fcec81..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/secrets_recovery_passphrase@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/secrets_recovery_passphrase@3x.png b/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/secrets_recovery_passphrase@3x.png deleted file mode 100644 index 57707b318..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/secrets_recovery_passphrase@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/Contents.json b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/Contents.json index 58e5ed869..2073d4d9a 100644 --- a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/Contents.json @@ -1,23 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "secrets_setup_key.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "secrets_setup_key@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "secrets_setup_key@3x.png", - "scale" : "3x" + "filename" : "password.png", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/password.png b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/password.png new file mode 100644 index 000000000..3123b91ca Binary files /dev/null and b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/password.png differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/secrets_setup_key.png b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/secrets_setup_key.png deleted file mode 100644 index db27047e7..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/secrets_setup_key.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/secrets_setup_key@2x.png b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/secrets_setup_key@2x.png deleted file mode 100644 index 4e9c7e1bf..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/secrets_setup_key@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/secrets_setup_key@3x.png b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/secrets_setup_key@3x.png deleted file mode 100644 index 29b52d76a..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/secrets_setup_key@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/Contents.json b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/Contents.json index c99ab2f3d..2073d4d9a 100644 --- a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/Contents.json @@ -1,23 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "secrets_setup_passphrase.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "secrets_setup_passphrase@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "secrets_setup_passphrase@3x.png", - "scale" : "3x" + "filename" : "password.png", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/password.png b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/password.png new file mode 100644 index 000000000..3123b91ca Binary files /dev/null and b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/password.png differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/secrets_setup_passphrase.png b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/secrets_setup_passphrase.png deleted file mode 100644 index 1002f668f..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/secrets_setup_passphrase.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/secrets_setup_passphrase@2x.png b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/secrets_setup_passphrase@2x.png deleted file mode 100644 index 5d211e7b8..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/secrets_setup_passphrase@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/secrets_setup_passphrase@3x.png b/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/secrets_setup_passphrase@3x.png deleted file mode 100644 index 98f37844b..000000000 Binary files a/Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/secrets_setup_passphrase@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/Contents.json b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/Contents.json index 2f904b1d3..c73e79794 100644 --- a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "tab_people.png", + "filename" : "dm.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "tab_people@2x.png", + "filename" : "dm@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "tab_people@3x.png", + "filename" : "dm@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/dm.png b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/dm.png new file mode 100644 index 000000000..689adc93a Binary files /dev/null and b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/dm.png differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/dm@2x.png b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/dm@2x.png new file mode 100644 index 000000000..2ca049d4c Binary files /dev/null and b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/dm@2x.png differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/dm@3x.png b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/dm@3x.png new file mode 100644 index 000000000..b2ce11e6b Binary files /dev/null and b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/dm@3x.png differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/tab_people.png b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/tab_people.png deleted file mode 100644 index f76685a14..000000000 Binary files a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/tab_people.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/tab_people@2x.png b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/tab_people@2x.png deleted file mode 100644 index 3c8b956fc..000000000 Binary files a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/tab_people@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/tab_people@3x.png b/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/tab_people@3x.png deleted file mode 100644 index bf14b13db..000000000 Binary files a/Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/tab_people@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/Contents.json b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/Contents.json index 984caedcc..c72cb1a25 100644 --- a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "tab_rooms.png", + "filename" : "tab_rooms_bwi.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "tab_rooms@2x.png", + "filename" : "tab_rooms_bwi@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "tab_rooms@3x.png", + "filename" : "tab_rooms_bwi@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms.png b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms.png deleted file mode 100644 index 1b5e4bdd9..000000000 Binary files a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms@2x.png b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms@2x.png deleted file mode 100644 index 69b448cd9..000000000 Binary files a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms@3x.png b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms@3x.png deleted file mode 100644 index 55bba3ba0..000000000 Binary files a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms_bwi.png b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms_bwi.png new file mode 100644 index 000000000..621602e74 Binary files /dev/null and b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms_bwi.png differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms_bwi@2x.png b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms_bwi@2x.png new file mode 100644 index 000000000..513e7a68d Binary files /dev/null and b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms_bwi@2x.png differ diff --git a/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms_bwi@3x.png b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms_bwi@3x.png new file mode 100644 index 000000000..5fb7a26b5 Binary files /dev/null and b/Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/tab_rooms_bwi@3x.png differ diff --git a/Riot/Assets/Riot-Defaults.plist b/Riot/Assets/Riot-Defaults.plist index 7ef98b242..dadc2a4fb 100644 --- a/Riot/Assets/Riot-Defaults.plist +++ b/Riot/Assets/Riot-Defaults.plist @@ -7,7 +7,7 @@ pinRoomsWithUnread matrixApps - + showAllEventsInRoomHistory showRedactionsInRoomHistory @@ -21,7 +21,7 @@ syncLocalContacts enableRageShake - + maxAllowedMediaCacheSize 1073741824 presenceColorForOnlineUser @@ -32,6 +32,8 @@ 15020851 enableBotCreation + enableCrashReport + enableRingingForGroupCalls diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iOS-Marketing-1024@1x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iOS-Marketing-1024@1x.png deleted file mode 100644 index d0fda273a..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iOS-Marketing-1024@1x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-20@1x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-20@1x.png deleted file mode 100644 index 6c9b3a53b..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-20@1x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-20@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-20@2x.png deleted file mode 100644 index ca66da10a..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-20@2x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-29@1x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-29@1x.png deleted file mode 100644 index ddfe1e525..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-29@1x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-29@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-29@2x.png deleted file mode 100644 index 0a77ec724..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-29@2x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-40@1x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-40@1x.png deleted file mode 100644 index ca66da10a..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-40@1x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-40@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-40@2x.png deleted file mode 100644 index f4afdbcad..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-40@2x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-76@1x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-76@1x.png deleted file mode 100644 index 96a684566..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-76@1x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-76@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-76@2x.png deleted file mode 100644 index 58ed6ff6b..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-76@2x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-83.5@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-83.5@2x.png deleted file mode 100644 index 5c9c91801..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPad-83.5@2x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-20@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-20@2x.png deleted file mode 100644 index ca66da10a..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-20@2x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-20@3x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-20@3x.png deleted file mode 100644 index 20b6df7cf..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-20@3x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-29@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-29@2x.png deleted file mode 100644 index 0a77ec724..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-29@2x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-29@3x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-29@3x.png deleted file mode 100644 index 10519c275..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-29@3x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-40@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-40@2x.png deleted file mode 100644 index f4afdbcad..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-40@2x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-40@3x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-40@3x.png deleted file mode 100644 index b1aa2a401..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-40@3x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-60@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-60@2x.png deleted file mode 100644 index b1aa2a401..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-60@2x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-60@3x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-60@3x.png deleted file mode 100644 index 1b0df4b6a..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/AppIcon~iPhone-60@3x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Contents.json b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Contents.json index 82463f805..c194b5a87 100644 --- a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Contents.json +++ b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Contents.json @@ -1,109 +1,109 @@ { "images" : [ { - "filename" : "AppIcon~iPhone-20@2x.png", + "filename" : "Icon-20@2x-1.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { - "filename" : "AppIcon~iPhone-20@3x.png", + "filename" : "Icon-20@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { - "filename" : "AppIcon~iPhone-29@2x.png", + "filename" : "Icon-29@2x-1.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { - "filename" : "AppIcon~iPhone-29@3x.png", + "filename" : "Icon-29@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { - "filename" : "AppIcon~iPhone-40@2x.png", + "filename" : "Icon-40@2x-1.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { - "filename" : "AppIcon~iPhone-40@3x.png", + "filename" : "Icon-40@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { - "filename" : "AppIcon~iPhone-60@2x.png", + "filename" : "Icon-60@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { - "filename" : "AppIcon~iPhone-60@3x.png", + "filename" : "Icon-60@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { - "filename" : "AppIcon~iPad-20@1x.png", + "filename" : "Icon-20.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { - "filename" : "AppIcon~iPad-20@2x.png", + "filename" : "Icon-20@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { - "filename" : "AppIcon~iPad-29@1x.png", + "filename" : "Icon-29.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { - "filename" : "AppIcon~iPad-29@2x.png", + "filename" : "Icon-29@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { - "filename" : "AppIcon~iPad-40@1x.png", + "filename" : "Icon-40.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { - "filename" : "AppIcon~iPad-40@2x.png", + "filename" : "Icon-40@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { - "filename" : "AppIcon~iPad-76@1x.png", + "filename" : "Icon-76.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { - "filename" : "AppIcon~iPad-76@2x.png", + "filename" : "Icon-76@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { - "filename" : "AppIcon~iPad-83.5@2x.png", + "filename" : "Icon-83.5@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { - "filename" : "AppIcon~iOS-Marketing-1024@1x.png", + "filename" : "Icon-1024.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-1024.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-1024.png new file mode 100644 index 000000000..ec3778a9a Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-1024.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20.png new file mode 100644 index 000000000..141ac7a52 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20@2x-1.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20@2x-1.png new file mode 100644 index 000000000..dd6c689b6 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20@2x-1.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20@2x.png new file mode 100644 index 000000000..dd6c689b6 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20@2x.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20@3x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20@3x.png new file mode 100644 index 000000000..a6c599df7 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-20@3x.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29.png new file mode 100644 index 000000000..a7df2fd39 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29@2x-1.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29@2x-1.png new file mode 100644 index 000000000..ddd9fe442 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29@2x-1.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29@2x.png new file mode 100644 index 000000000..ddd9fe442 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29@2x.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29@3x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29@3x.png new file mode 100644 index 000000000..baf15c1b7 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-29@3x.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40.png new file mode 100644 index 000000000..dd6c689b6 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40@2x-1.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40@2x-1.png new file mode 100644 index 000000000..a33a3641f Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40@2x-1.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40@2x.png new file mode 100644 index 000000000..a33a3641f Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40@2x.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40@3x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40@3x.png new file mode 100644 index 000000000..979dc5615 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-40@3x.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-60@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 000000000..979dc5615 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-60@3x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 000000000..7580215e1 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-76.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-76.png new file mode 100644 index 000000000..cd7cbb959 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-76.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-76@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-76@2x.png new file mode 100644 index 000000000..bda6d14b1 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-76@2x.png differ diff --git a/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-83.5@2x.png b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-83.5@2x.png new file mode 100644 index 000000000..f2f46d742 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Icon-83.5@2x.png differ diff --git a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/Contents.json b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/Contents.json index 77a3e65b5..7335659f1 100644 --- a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/Contents.json +++ b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "horizontal_logo.png", + "filename" : "bundesmessenger-logo-1.svg", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "horizontal_logo@2x.png", + "filename" : "bundesmessenger-logo.svg", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "horizontal_logo@3x.png", + "filename" : "bundesmessenger-logo-2.svg", "idiom" : "universal", "scale" : "3x" } @@ -19,8 +19,5 @@ "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" } } diff --git a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/bundesmessenger-logo-1.svg b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/bundesmessenger-logo-1.svg new file mode 100644 index 000000000..2bd1791f6 --- /dev/null +++ b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/bundesmessenger-logo-1.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/bundesmessenger-logo-2.svg b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/bundesmessenger-logo-2.svg new file mode 100644 index 000000000..2bd1791f6 --- /dev/null +++ b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/bundesmessenger-logo-2.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/bundesmessenger-logo.svg b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/bundesmessenger-logo.svg new file mode 100644 index 000000000..2bd1791f6 --- /dev/null +++ b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/bundesmessenger-logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/horizontal_logo.png b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/horizontal_logo.png deleted file mode 100644 index 8da11cec0..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/horizontal_logo.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/horizontal_logo@2x.png b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/horizontal_logo@2x.png deleted file mode 100644 index 6427db5ea..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/horizontal_logo@2x.png and /dev/null differ diff --git a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/horizontal_logo@3x.png b/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/horizontal_logo@3x.png deleted file mode 100644 index e7baca3a8..000000000 Binary files a/Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/horizontal_logo@3x.png and /dev/null differ diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings new file mode 100644 index 000000000..475990c21 --- /dev/null +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// MARK: - Attachment +"content_scan_status_failed" = "Fehler beim Laden"; +"content_scan_status_in_progress" = "Scanne..."; +"content_scan_status_infected" = "Blockiert"; +"content_scan_status_trusted" = "βœ“ Sicher"; + +"settings_downtime_message" = "\nDer Messenger steht vom\n%@ bis %@\nnicht zur VerfΓΌgung.\n"; + +"bwi_auth_beta_selection_title" = "Serverauswahl"; +"bwi_auth_beta_selection_button_title" = "Vorauswahl"; + +"bwi_settings_developer_apply_app_config" = "MDM Config: MSG Demo"; +"bwi_settings_developer_apply_orig_app_config" = "MDM Config: BW"; +"bwi_settings_developer_reset_app_config" = "MDM Config: Reset"; + +"bwi_mdm_logout_message" = "Die Konfiguration hat sich geΓ€ndert. Bitte melde dich neu an."; diff --git a/Riot/Assets/de.lproj/InfoPlist.strings b/Riot/Assets/de.lproj/InfoPlist.strings index 594fb19a0..a4ae89803 100644 --- a/Riot/Assets/de.lproj/InfoPlist.strings +++ b/Riot/Assets/de.lproj/InfoPlist.strings @@ -1,8 +1,10 @@ // Permissions usage explanations "NSCameraUsageDescription" = "Die Kamera wird verwendet, um Fotos und Videos aufzunehmen sowie Videoanrufe durchzufΓΌhren."; "NSPhotoLibraryUsageDescription" = "Die Fotobibliothek wird verwendet, um Fotos und Videos zu versenden."; -"NSMicrophoneUsageDescription" = "Element benΓΆtigt Zugriff auf das Mikrofon um Anrufe zu tΓ€tigen und Videos oder Sprachnachrichten aufzunehmen."; -"NSContactsUsageDescription" = "Element zeigt deine Kontakte an, damit du sie zum chatten einladen kannst."; + +"NSMicrophoneUsageDescription" = "BundesMessenger benΓΆtigt Zugriff auf das Mikrofon um Videos oder Sprachnachrichten aufzunehmen."; +"NSContactsUsageDescription" = "BundesMessenger verwendet deine Kontakte im Gegensatz zu normalen Matrixklienten nicht"; + "NSCalendarsUsageDescription" = "Sieh dir deine geplanten Meetings in der App an."; "NSFaceIDUsageDescription" = "Face-ID wird zum Zugriff auf deine App verwendet."; -"NSLocationWhenInUseUsageDescription" = "Wenn du deinen Standort mit Personen teilst, braucht Element Zugriff um ihnen eine Karte anzuzeigen."; +"NSLocationWhenInUseUsageDescription" = "Wenn du deinen Standort mit Personen teilst, braucht BundesMessenger Zugriff um ihnen eine Karte anzuzeigen."; diff --git a/Riot/Assets/de.lproj/Localizable.strings b/Riot/Assets/de.lproj/Localizable.strings index a5edb4e62..44393808c 100644 --- a/Riot/Assets/de.lproj/Localizable.strings +++ b/Riot/Assets/de.lproj/Localizable.strings @@ -56,7 +56,8 @@ "STICKER_FROM_USER" = "%@ hat einen Sticker gesendet"; "KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ mΓΆchte verifizieren"; /* New message indicator on unknown room */ -"MESSAGE" = "Nachricht"; +"MESSAGE" = "Neue Nachricht"; +"MESSAGE_PROTECTED" = "Neue Nachricht"; /* New message indicator from a DM */ "MESSAGE_FROM_X" = "Nachricht von %@"; /* New message indicator on a room */ @@ -117,5 +118,13 @@ /* A user's membership has updated in an unknown way */ "USER_MEMBERSHIP_UPDATED" = "Profil von %@ geupdatet"; +// +/* BWI: Happy Birthday Campaign*/ +"HAPPY_BIRTHDAY" = "HAPPY BIRTHDAY"; +"HAPPY_BIRTHDAY_BODY" = "Wir feiern 1 Jahr Messenger!\nWillst du uns nicht auch\ngratulieren?\nSchenke dem Messenger\ndeine Sternebewertung im\nAppStore!"; +"HAPPY_BIRTHDAY_BUTTON_GIVE_STARS" = "Sterne schenken"; +"HAPPY_BIRTHDAY_BUTTON_DISMISS" = "X"; + /* New file message from a specific person, not referencing a room. */ "LOCATION_FROM_USER" = "%@ hat den eigenen Standort geteilt"; + diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 1c6bc5911..fa3f74200 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -1,8 +1,10 @@ +// Actions +"view" = "Ansehen"; +"next" = "NΓ€chster"; "back" = "ZurΓΌck"; -"continue" = "Fortsetzen"; -"retry" = "Wiederholen"; -"cancel" = "Abbruch"; -"save" = "Speichern"; +"continue" = "Weiter"; +"create" = "Erstelle"; +"start" = "Starte"; "leave" = "Verlassen"; // Actions "view" = "Ansehen"; @@ -14,11 +16,15 @@ "title_rooms" = "RΓ€ume"; "warning" = "Warnung"; "remove" = "Entferne"; +"invite" = "Einladen"; +"retry" = "Wiederholen"; "start" = "Starte"; "create" = "Erstellen"; "on" = "An"; "off" = "Aus"; -"join" = "Beitreten"; +"cancel" = "Abbruch"; +"save" = "Speichern"; +"join" = "Betreten"; "decline" = "Ablehnen"; "accept" = "Akzeptieren"; "preview" = "Vorschau"; @@ -29,6 +35,23 @@ "active_call_details" = "Aktives GesprΓ€ch (%@)"; "later" = "SpΓ€ter"; "rename" = "Umbenennen"; +"collapse" = "Einklappen"; +"send_to" = "Senden an %@"; +"sending" = "Senden"; +"close" = "Beenden"; +"skip" = "Überspringen"; +"joined" = "Beigetreten"; +"switch" = "Switch"; +"more" = "mehr"; + +// Room Details +"room_details_title" = "Raumdetails"; +"room_details_title_for_dm" = "Details"; +"title_favourites" = "Favoriten"; +"title_people" = "Personen"; +"title_rooms" = "RΓ€ume"; +"warning" = "Warnung"; + // Authentication "auth_login" = "Anmelden"; "auth_register" = "Registrieren"; @@ -36,7 +59,7 @@ "auth_skip" = "Überspringen"; "auth_send_reset_email" = "Sende RΓΌcksetz-E-Mail"; "auth_return_to_login" = "Gehe zurΓΌck zum Anmeldefenster"; -"auth_user_id_placeholder" = "E-Mail oder Nutzername"; +"auth_user_id_placeholder" = "Benutzername"; "auth_password_placeholder" = "Passwort"; "auth_new_password_placeholder" = "Neues Passwort"; "auth_user_name_placeholder" = "Nutzername"; @@ -57,6 +80,7 @@ "auth_password_dont_match" = "PasswΓΆrter stimmen nicht ΓΌberein"; "auth_username_in_use" = "Benutzername bereits verwendet"; "auth_forgot_password" = "Passwort vergessen?"; +"bwi_auth_forgot_password_alert_text" = "Falls du dein Passwort vergessen hast, wende dich an deinen Poolverwalter oder gehe auf das SelfServicePortal im IntranetBw und setze dein Passwort zurΓΌck."; "auth_msisdn_validation_title" = "Verifizierung ausstehend"; "auth_msisdn_validation_message" = "Bitte gib unten den Aktivierungs-Code ein, den wir per SMS verschickt haben."; "auth_msisdn_validation_error" = "Telefonnummer kann nicht verifiziert werden."; @@ -68,7 +92,7 @@ "room_creation_account" = "Konto"; "room_creation_appearance" = "Aussehen"; "room_creation_appearance_name" = "Name"; -"room_creation_appearance_picture" = "Avatar (optional)"; +"room_creation_appearance_picture" = "Raumbild (optional)"; "room_creation_privacy" = "Datenschutz"; "room_creation_private_room" = "Dieser Chat ist privat"; "room_creation_public_room" = "Dieser Chat ist ΓΆffentlich"; @@ -101,7 +125,7 @@ "search_people" = "Personen"; "search_files" = "Dateien"; "search_default_placeholder" = "Suche"; -"search_people_placeholder" = "Suche nach Nutzer-ID, Name oder E-Mail"; +"search_people_placeholder" = "Name von Kontakt eingeben"; "search_no_result" = "Keine Ergebnisse"; // Directory "directory_cell_title" = "Durchsuche Verzeichnis"; @@ -120,10 +144,15 @@ "room_participants_multi_participants" = "%d Teilnehmende"; "room_participants_leave_prompt_title" = "Raum verlassen"; "room_participants_leave_prompt_msg" = "Bist du sicher, dass du den Raum verlassen willst?"; +"room_participants_leave_prompt_title_for_dm" = "Verlassen"; +"room_participants_leave_prompt_msg_for_dm" = "Bist du sicher, dass du die Konversation verlassen mΓΆchtest?"; +"room_participants_cant_leave_prompt_message" = "Du kannst den Raum nicht verlassen, da es keine weiteren Admins im Raum gibt."; "room_participants_remove_prompt_title" = "BestΓ€tigung"; "room_participants_remove_prompt_msg" = "Bist du sicher, dass du %@ aus dem Chat entfernen mΓΆchtest?"; "room_participants_invite_prompt_title" = "BestΓ€tigung"; "room_participants_invite_prompt_msg" = "Bist du sicher, dass du %@ in diesen Chat einladen mΓΆchtest?"; +"room_participants_filter_room_members" = "Filtere Raum-Mitglieder"; +"room_participants_filter_room_members_for_dm" = "Mitglieder filtern"; "room_participants_filter_room_members" = "Mitglieder filtern"; "room_participants_invite_malformed_id_title" = "Fehler beim Einladen"; "room_participants_invited_section" = "EINGELADEN"; @@ -138,10 +167,11 @@ "room_participants_action_leave" = "Diesen Raum verlassen"; "room_participants_action_remove" = "Aus diesem Raum entfernen"; "room_participants_action_ban" = "Aus diesem Raum bannen"; -"room_participants_action_ignore" = "Alle Nachrichten dieser Person verbergen"; -"room_participants_action_unignore" = "Alle Nachrichten dieser Person zeigen"; -"room_participants_action_set_moderator" = "Moderationsrechte vergeben"; -"room_participants_action_set_admin" = "Administrationsrechte vergeben"; +"room_participants_action_ignore" = "Alle Nachrichten von diesem Nutzer verbergen"; +"room_participants_action_unignore" = "Zeige alle Nachrichten von diesem Nutzer"; +"room_participants_action_set_moderator" = "Gib Moderationsrechte"; +"room_participants_action_set_admin" = "Mache zum Administrator"; + "room_participants_action_start_new_chat" = "Starte neuen Chat"; "room_participants_action_start_video_call" = "Starte Video-Anruf"; "room_participants_action_mention" = "ErwΓ€hnen"; @@ -208,7 +238,28 @@ "settings_ignored_users" = "IGNORIERTE NUTZER"; "settings_contacts" = "GERΓ„TEKONTAKTE"; "settings_advanced" = "ERWEITERT"; + +"settings_default" = "Benachrichtigungen"; +"settings_mentions_and_keywords" = "Mentions and Keywords"; +"settings_other" = "Erweitert"; +"settings_notify_me_for" = "Benachrichtige mich fΓΌr"; +"settings_direct_messages" = "Direktnachrichten"; +"settings_encrypted_direct_messages" = "Direktnachrichten"; +"settings_group_messages" = "RΓ€ume"; +"settings_encrypted_group_messages" = "RΓ€ume"; +"settings_messages_containing_display_name" = "ErwΓ€hnungen"; +"settings_messages_containing_user_name" = "Mein Nutzername"; +"settings_messages_containing_at_room" = "@room"; +"settings_messages_containing_keywords" = "Keywords"; +"settings_room_invitations" = "Einladungen"; +"settings_call_invitations" = "Call invitations"; +"settings_messages_by_a_bot" = "Messages by a bot"; +"settings_room_upgrades" = "Room upgrades"; +"settings_your_keywords" = "Your Keywords"; +"settings_new_keyword" = "Add new Keyword"; +"settings_mentions_and_keywords_encryption_notice" = "You won’t get notifications for mentions & keywords in encrypted rooms on mobile."; "settings_other" = "Weiteres"; + "settings_devices" = "SITZUNGEN"; "settings_cryptography" = "VERSCHLÜSSELUNG"; "settings_sign_out" = "Abmelden"; @@ -230,18 +281,22 @@ "settings_labs_e2e_encryption" = "Ende-zu-Ende-VerschlΓΌsselung"; "settings_version" = "Version %@"; "settings_olm_version" = "Olm-Version %@"; +"settings_element_version" = "Element-Version %@"; "settings_copyright" = "Urheberrecht"; "settings_term_conditions" = "GeschΓ€ftsbedingungen"; "settings_privacy_policy" = "DatenschutzerklΓ€rung"; "settings_send_crash_report" = "Sende anonyme Absturz- und Nutzungsdaten"; "settings_change_password" = "Passwort Γ€ndern"; -"settings_old_password" = "altes Passwort"; -"settings_new_password" = "neues Passwort"; -"settings_confirm_password" = "bestΓ€tige Passwort"; +"settings_old_password" = "Aktuelles Passwort"; +"settings_new_password" = "Neues Passwort"; +"settings_confirm_password" = "Neues Passwort bestΓ€tigen"; "settings_password_updated" = "Dein Passwort wurde aktualisiert"; +"settings_password_condition" = "Regeln: MindestlΓ€nge 8 Zeichen, mind. 1 Sonderzeichen, 1 Groß- und 1 Kleinbuchstaben und 1 Ziffer"; "settings_crypto_device_name" = "Γ–ffentlicher Sitzungs-Name: "; "settings_crypto_device_key" = "\nSitzungs-SchlΓΌssel:\n"; "settings_crypto_export" = "SchlΓΌssel exportieren"; +"settings_about" = "Erweitert"; + "room_details_settings" = "Einstellungen"; "room_details_photo" = "Raumbild"; "room_details_room_name" = "Raumname"; @@ -249,9 +304,10 @@ "room_details_favourite_tag" = "Favorit"; "room_details_low_priority_tag" = "Niedrige PrioritΓ€t"; "room_details_access_section" = "Wer kann auf diesen Raum zugreifen?"; +"room_details_access_section_for_dm" = "Wer hat Zugriff hierauf?"; "room_details_access_section_invited_only" = "Nur Personen, die eingeladen wurden"; -"room_details_access_section_anyone_apart_from_guest" = "Alle, die den Raumlink kennen - außer GΓ€ste"; -"room_details_access_section_anyone" = "Alle, die den Raumlink kennen - auch GΓ€ste"; +"room_details_access_section_access_toggle_bw" = "Γ–ffentlicher Raum"; +"room_details_access_section_anyone_apart_from_guest" = "Jeder, der den Raumlink kennt - außer GΓ€ste"; "room_details_history_section" = "Wer kann die Historie lesen?"; "room_details_history_section_anyone" = "Alle"; "room_details_history_section_members_only" = "Nur Mitglieder (ab Auswahl dieser Option)"; @@ -260,14 +316,18 @@ "room_details_history_section_prompt_title" = "Datenschutzwarnung"; "room_details_addresses_section" = "Adressen"; "room_details_no_local_addresses" = "Dieser Raum hat keine lokalen Adressen"; +"room_details_no_local_addresses_for_dm" = "Keine lokalen Adressen vorhanden"; "room_details_new_address" = "FΓΌge neue Adresse hinzu"; "room_details_new_address_placeholder" = "FΓΌge neue Adresse hinzu (z.B. #foo%@)"; "room_details_banned_users_section" = "Gebannte Nutzer"; "room_details_advanced_section" = "Erweitert"; "room_details_advanced_room_id" = "Raum-ID:"; +"room_details_advanced_room_id_for_dm" = "ID:"; "room_details_advanced_enable_e2e_encryption" = "Aktivere VerschlΓΌsselung (Warnung: Nicht deaktivierbar!)"; "room_details_advanced_e2e_encryption_enabled" = "VerschlΓΌsselung ist in diesem Raum aktiviert"; +"room_details_advanced_e2e_encryption_enabled_for_dm" = "VerschlΓΌsselung ist hier aktiviert"; "room_details_advanced_e2e_encryption_disabled" = "VerschlΓΌsselung ist in diesem Raum nicht aktiviert."; +"room_details_advanced_e2e_encryption_disabled_for_dm" = "VerschlΓΌsselung ist hier nicht aktiviert."; // Titles "title_home" = "Start"; "next" = "Weiter"; @@ -286,14 +346,14 @@ "auth_reset_password_success_message" = "Dein Passwort wurde zurΓΌckgesetzt.\n\nDu wurdest aus allen Sitzungen abgemeldet, diese bekommen jetzt keine Push-Benachrichtigungen mehr. Um diese wieder einzuschalten, melde dich auf den GerΓ€ten erneut an."; "auth_add_email_and_phone_warning" = "Registrierung mit E-Mail und Telefonnummer zugleich ist noch nicht unterstΓΌtzt. Nur die Telefonnummer wird berΓΌcksichtigt. Du kannst deine E-Mail-Adresse in deinem Profil ergΓ€nzen."; "room_creation_make_public_prompt_msg" = "Sicher, dass du diesen Raum ΓΆffentlich machen willst? Jeder kann deine Nachrichten lesen und dem Raum beitreten."; -"room_creation_invite_another_user" = "Benutzer-ID, Name oder E-Mail"; +"room_creation_invite_another_user" = "Name von Kontakt eingeben"; "room_recents_join_room_prompt" = "Gib eine Raum-ID oder einem Raum-Alias an"; "directory_cell_description" = "%tu RΓ€ume"; "directory_search_results" = "%tu Treffer gefunden fΓΌr %@"; "directory_search_results_more_than" = ">%tu Treffer gefunden fΓΌr %@"; "contacts_address_book_permission_denied" = "%@ wurde nicht erlaubt, auf lokale Kontakte zuzugreifen"; "room_participants_remove_third_party_invite_msg" = "Entfernen der Einladung von Dritten ist noch nicht unterstΓΌtzt"; -"room_participants_invite_another_user" = "Suchen/Einladen mittels Benutzer-ID, Name oder E-Mail-Adresse"; +"room_participants_invite_another_user" = "Name von Kontakt eingeben"; "room_participants_invite_malformed_id" = "UngΓΌltige ID. Sollte eine E-Mail-Adresse oder Matrix-ID wie \"@thomas:matrix.org' sein"; "room_participants_idle" = "UntΓ€tig"; "room_participants_ago" = "her"; @@ -313,21 +373,25 @@ "settings_phone_number" = "Telefon"; "settings_add_phone_number" = "Telefonnummer hinzufΓΌgen"; "settings_fail_to_update_profile" = "Profilaktualisierung fehlgeschlagen"; +"settings_fail_to_update_profile_message" = "Das Γ„ndern des Anzeigenamens ist nicht erlaubt"; "settings_global_settings_info" = "Globale Benachrichtigungseinstellungen sind auf deinem %@ web-Client verfΓΌgbar"; "settings_pin_rooms_with_missed_notif" = "Pinnen von RΓ€umen mit verpassten Benachrichtigungen"; "settings_pin_rooms_with_unread" = "Pinnen von RΓ€umen mit ungelesenen Nachrichten"; "settings_contacts_discover_matrix_users" = "Entdecke andere Benutzer mittels E-Mail-Adressen oder Telefonnummern"; "settings_labs_e2e_encryption_prompt_message" = "Zum Fertigstellen der VerschlΓΌsselung bitte neu anmelden."; "settings_third_party_notices" = "Anmerkungen von Dritten"; +"settings_support" = "Support"; "settings_clear_cache" = "Cache lΓΆschen"; "settings_fail_to_update_password" = "PasswortΓ€nderung fehlgeschlagen"; "settings_crypto_device_id" = "\nSitzungs-ID: "; "settings_crypto_blacklist_unverified_devices" = "VerschlΓΌssele nur zu verifizierten Sitzungen"; "room_details_people" = "Mitglieder"; -"room_details_files" = "Hochgeladenes"; +"room_details_search" = "Suche"; +"room_details_files" = "Dateien"; "room_details_mute_notifs" = "Benachrichtigungen stummschalten"; "room_details_access_section_no_address_warning" = "Um einen Raum zu betreten, muss er eine Adresse haben"; -"room_details_access_section_directory_toggle" = "Diesen Raum im Raumverzeichnis anzeigen"; +"room_details_access_section_directory_toggle" = "Zeige alle RΓ€ume in diesem Verzeichnis"; +"room_details_access_section_directory_toggle_for_dm" = "Im Raum-Verzeichnis auflisten"; "room_details_history_section_prompt_msg" = "Γ„nderungen an der Leseberechtigung des Verlaufs wird nur auf zukΓΌnftige Nachrichten dieses Raums angewandt. Die Sichtbarkeit des existierenden Verlaufs wird nicht verΓ€ndert."; "room_details_addresses_invalid_address_prompt_title" = "UngΓΌltiges Aliasformat"; "room_details_addresses_invalid_address_prompt_msg" = "%@ ist kein gΓΌltiges Format fΓΌr einen Alias"; @@ -531,7 +595,7 @@ "event_formatter_rerequest_keys_part2" = " von deinen anderen Sitzungen anfragen."; // Re-request confirmation dialog "rerequest_keys_alert_title" = "Anfrage gesendet"; -"rerequest_keys_alert_message" = "Bitte %@ auf einem anderen GerΓ€t ΓΆffenen, das die Nachricht entschlΓΌsseln kann, damit es die SchlΓΌssel an diese Sitzung senden kann."; +"rerequest_keys_alert_message" = "Bitte %@ auf einem anderen GerΓ€t ΓΆffnen, das die Nachricht entschlΓΌsseln kann, damit es die SchlΓΌssel an diese Sitzung senden kann."; "room_message_reply_to_placeholder" = "Antwort senden (unverschlΓΌsselt)…"; "encrypted_room_message_reply_to_placeholder" = "Sende eine verschlΓΌsselte Antwort…"; "room_message_reply_to_short_placeholder" = "Sende eine Antwort…"; @@ -584,46 +648,46 @@ "key_backup_setup_intro_info" = "Nachrichten in verschlΓΌsselten RΓ€umen werden durch Ende-zu-Ende-VerschlΓΌsselung gesichert. Nur Du und der/die EmpfΓ€nger haben die SchlΓΌssel zum Lesen dieser Nachrichten.\n\nSichere deine SchlΓΌssel auf einer sicheren Art, um sie nicht zu verlieren."; "key_backup_setup_intro_setup_action_without_existing_backup" = "Beginne SchlΓΌsselsicherung zu nutzen"; "key_backup_setup_intro_setup_action_with_existing_backup" = "Benutze SchlΓΌsselsicherung"; -"key_backup_setup_passphrase_title" = "Sichere dein Backup mit einer Sicherungsphrase"; -"key_backup_setup_passphrase_info" = "Wir speichern eine verschlΓΌsselte Kopie deiner SchlΓΌssel auf unserem Server. SchΓΌtze sie mit einer Passphrase, um sie sicher zu halten.\n\nFΓΌr maximale Sicherheit sollte sich dies von deinem Kontopasswort unterscheiden."; -"key_backup_setup_passphrase_passphrase_title" = "Eingeben"; -"key_backup_setup_passphrase_passphrase_placeholder" = "Sicherungsphrase eingeben"; +"key_backup_setup_passphrase_title" = "Sichere dein Backup mit einem WiederherstellungsschlΓΌssel"; +"key_backup_setup_passphrase_info" = "Wir speichern eine verschlΓΌsselte Kopie deiner SchlΓΌssel auf unserem Server. SchΓΌtze deine Sicherung mit einem WiederherstellungsschlΓΌssel, um sie sicher zu halten.\n\nFΓΌr maximale Sicherheit sollte sich dieser von deinem Passwort unterscheiden."; +"key_backup_setup_passphrase_passphrase_title" = ""; +"key_backup_setup_passphrase_passphrase_placeholder" = "WiederherstellungsschlΓΌssel"; "key_backup_setup_passphrase_passphrase_valid" = "Gut!"; -"key_backup_setup_passphrase_passphrase_invalid" = "Versuche ein Wort hinzuzufΓΌgen"; -"key_backup_setup_passphrase_confirm_passphrase_title" = "BestΓ€tigen"; -"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Sicherungsphrase bestΓ€tigen"; +"key_backup_setup_passphrase_passphrase_invalid" = "Versuche ein weiteres Zeichen hinzuzufΓΌgen"; +"key_backup_setup_passphrase_confirm_passphrase_title" = ""; +"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "WiederherstellungsschlΓΌssel"; "key_backup_setup_passphrase_confirm_passphrase_valid" = "Gut!"; -"key_backup_setup_passphrase_confirm_passphrase_invalid" = "Sicherungsphrase inkorrekt"; -"key_backup_setup_passphrase_set_passphrase_action" = "Sicherungsphrase setzen"; -"key_backup_setup_passphrase_setup_recovery_key_info" = "Oder sichere deine Sicherung mit einem SicherungsschlΓΌssel und speichere sie an einem sicheren Ort ab."; -"key_backup_setup_passphrase_setup_recovery_key_action" = "(Erweitert) Mit SicherungsschlΓΌssel einrichten"; +"key_backup_setup_passphrase_confirm_passphrase_invalid" = "Keine Übereinstimmung"; +"key_backup_setup_passphrase_set_passphrase_action" = "Setze WiederherstellungsschlΓΌssel"; +"key_backup_setup_passphrase_setup_recovery_key_info" = "Oder sichere deine Sicherung mit einem WiederherstellungsschlΓΌssel, und sichere sie an einem sicheren Ort."; +"key_backup_setup_passphrase_setup_recovery_key_action" = "(Erweitert) Mit WiederherstellungsschlΓΌssel einrichten"; "key_backup_setup_success_title" = "Erfolgreich!"; // Success from passphrase -"key_backup_setup_success_from_passphrase_info" = "Deine SchlΓΌssel werden gesichert.\n\nDein SicherungsschlΓΌssel ist wie ein Schutznetz - Du kannst damit den Zugriff auf deine verschlΓΌsselten Nachrichten wiederherstellen, wenn du deine Passphrase vergisst.\n\nBewahre ihn an einem sicheren Ort wie einem Passwortmanager oder Tresor auf."; -"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "SicherungsschlΓΌssel speichern"; +"key_backup_setup_success_from_passphrase_info" = "Deine SchlΓΌssel werden gesichert.\n\nDein WiederherstellungsschlΓΌssel ist ein Sicherheitsnetzwerk - Du kannst damit den Zugriff auf deine verschlΓΌsselten Nachrichten wiederherstellen, wenn du deine VerschlΓΌsselungskennwort vergisst.\n\nBewahre den WiederherstellungsschlΓΌssel an einem sehr sicheren Ort auf, beispielsweise einem Kennwortmanager (oder einem Tresor)."; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "WiederherstellungsschlΓΌssel speichern"; "key_backup_setup_success_from_passphrase_done_action" = "Erledigt"; // Success from recovery key -"key_backup_setup_success_from_recovery_key_info" = "Deine SchlΓΌssel werden gesichert.\n\nErstelle eine Kopie des SicherungsschlΓΌssels und bewahre ihn sicher auf."; -"key_backup_setup_success_from_recovery_key_recovery_key_title" = "SicherungsschlΓΌssel"; +"key_backup_setup_success_from_recovery_key_info" = "Deine SchlΓΌssel werden gesichert.\n\nErstelle eine Kopie dieses WiederherstellungsschlΓΌssels und bewahre ihn auf."; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "VerschlΓΌsselungskennwort"; "key_backup_setup_success_from_recovery_key_make_copy_action" = "Mache eine Kopie"; "key_backup_setup_success_from_recovery_key_made_copy_action" = "Ich fertigte eine Kopie an"; -"key_backup_recover_title" = "Nachrichten sichern"; -"key_backup_recover_invalid_passphrase_title" = "Falsche Sicherungsphrase"; -"key_backup_recover_invalid_passphrase" = "Sicherung konnte mit dieser Phrase nicht entschlΓΌsselt werden: Bitte stelle sicher, dass du die richtige Sicherungsphrase eingegeben hast."; -"key_backup_recover_invalid_recovery_key_title" = "UngΓΌltiger SicherungsschlΓΌssel"; -"key_backup_recover_invalid_recovery_key" = "Sicherung konnte mit diesem SchlΓΌssel nicht entschlΓΌsselt werden: Bitte stelle sicher, dass du den richtigen SicherungsschlΓΌssel eingegeben hast."; -"key_backup_recover_from_passphrase_info" = "Nutze deine Sicherungsphrase um deinen sicheren Chatverlauf zu entschlΓΌsseln"; -"key_backup_recover_from_passphrase_passphrase_title" = "Eingeben"; -"key_backup_recover_from_passphrase_passphrase_placeholder" = "Phrase eingeben"; +"key_backup_recover_title" = "Sichere Nachrichten"; +"key_backup_recover_invalid_passphrase_title" = "Falscher WiederherstellungsschlΓΌssel"; +"key_backup_recover_invalid_passphrase" = "Sicherung konnte nicht mit diesem WiederherstellungsschlΓΌssel entschlΓΌsselt werden: Bitte stelle sicher, dass du den richtigen WiederherstellungsschlΓΌssel eingegeben hast."; +"key_backup_recover_invalid_recovery_key_title" = "VerschlΓΌsselungskennwort passt nicht"; +"key_backup_recover_invalid_recovery_key" = "Sicherung konnte mit diesem SchlΓΌssel nicht entschlΓΌsselt werden: Bitte stelle sicher, dass du das richtige VerschlΓΌsselungskennwort eingegeben hast."; +"key_backup_recover_from_passphrase_info" = "Nutze dein VerschlΓΌsselungskennwort um deinen sicheren Chatverlauf zu entschlΓΌsseln"; +"key_backup_recover_from_passphrase_passphrase_title" = "Gebe"; +"key_backup_recover_from_passphrase_passphrase_placeholder" = "WiederherstellungsschlΓΌssel eingeben"; "key_backup_recover_from_passphrase_recover_action" = "Historie entschlΓΌsseln"; -"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Kennst deine Sicherungsphrase nicht? Du kannst "; -"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "deinen SicherungsschlΓΌssel nutzen"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "WiederherstellungsschlΓΌssel vergessen? Du kannst "; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "nutze dein WiederherstellungsschlΓΌssel"; "key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; -"key_backup_recover_from_recovery_key_info" = "Nutze deinen SicherungsschlΓΌssel, um deine ChatverlΓ€ufe zu entschlΓΌsseln"; +"key_backup_recover_from_recovery_key_info" = "Nutze dein VerschlΓΌsselungskennwort um deinen sicheren Chatverlauf zu entschlΓΌsseln"; "key_backup_recover_from_recovery_key_recovery_key_title" = "Eingeben"; -"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "SicherungsschlΓΌssel eingeben"; +"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Gebe VerschlΓΌsselungskennwort ein"; "key_backup_recover_from_recovery_key_recover_action" = "Verlauf entschlΓΌsseln"; -"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Hast du deinen SicherungsschlΓΌssel verloren? Du kannst in den Einstellungen einen neuen einrichten."; +"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Hast du dein VerschlΓΌsselungskennwort verloren? Du kannst in den Einstellungen ein neues einrichten."; "key_backup_recover_success_info" = "Sicherung wiederhergestellt!"; "key_backup_recover_done_action" = "Erledigt"; "key_backup_setup_banner_title" = "Verliere niemals verschlΓΌsselte Nachrichten"; @@ -644,7 +708,7 @@ "sign_out_key_backup_in_progress_alert_cancel_action" = "Ich werde warten"; // Key backup wrong version "e2e_key_backup_wrong_version_title" = "Neue SchlΓΌsselsicherung"; -"e2e_key_backup_wrong_version" = "Eine neues SchlΓΌsselbackup wurde erkannt.\n\nWenn dies nicht der Fall war, lege in den Einstellungen eine neue Sicherungsphrase fest."; +"e2e_key_backup_wrong_version" = "Eine neues SchlΓΌsselbackup wurde erkannt.\n\nWenn dies nicht der Fall war, lege in den Einstellungen ein neues VerschlΓΌsselungskennwort fest."; "e2e_key_backup_wrong_version_button_settings" = "Einstellungen"; "e2e_key_backup_wrong_version_button_wasme" = "Das war ich"; "key_backup_setup_intro_manual_export_info" = "(Erweitert)"; @@ -675,7 +739,7 @@ // Mark: Incoming "device_verification_incoming_title" = "Eingehende Verifikationsanfrage"; "device_verification_start_verify_button" = "Beginne Verifizierung"; -"device_verification_verified_got_it_button" = "Verstanden"; +"device_verification_verified_got_it_button" = "Fertig"; // MARK: Emoji "device_verification_emoji_dog" = "Hund"; "device_verification_emoji_cat" = "Katze"; @@ -764,7 +828,7 @@ "device_verification_verify_title_number" = "ÜberprΓΌfe diese Sitzung, indem du bestΓ€tigst, dass die folgenden Zahlen auf dem Bildschirm des GegenΓΌber angezeigt werden"; "device_verification_verify_wait_partner" = "Warten auf BestΓ€tigung des GegenΓΌber…"; // MARK: Verified -"device_verification_verified_title" = "Verifiziert!"; +"device_verification_verified_title" = "Geschafft!"; "device_verification_verified_description_1" = "Du hast diese Sitzung erfolgreich verifiziert."; "device_verification_verified_description_2" = "VerschlΓΌsselte Nachrichten mit diesem/r Benutzer!n werden durchgehend verschlΓΌsselt und kΓΆnnen von Dritten nicht gelesen werden."; "device_verification_emoji_rooster" = "Hahn"; @@ -799,6 +863,7 @@ // Image picker "image_picker_action_camera" = "Foto aufnehmen"; "image_picker_action_library" = "Aus der Mediathek auswΓ€hlen"; +"image_picker_action_remove_photo" = "Foto entfernen"; "camera_unavailable" = "Die Kamera ist auf deinem GerΓ€t nicht verfΓΌgbar"; "photo_library_access_not_granted" = "%@ hat keine Berechtigung zum Zugriff auf die Fotobibliothek. Bitte Γ€ndere die Datenschutzeinstellungen"; "auth_forgot_password_error_no_configured_identity_server" = "Es ist kein IdentitΓ€tsserver konfiguriert: FΓΌge einen hinzu, um dein Kennwort zurΓΌckzusetzen."; @@ -909,6 +974,7 @@ "room_participants_action_security_status_warning" = "Warnung"; "room_participants_security_loading" = "Lade…"; "room_participants_security_information_room_not_encrypted" = "Nachrichten in diesem Raum sind nicht Ende-zu-Ende verschlΓΌsselt."; +"room_participants_security_information_room_not_encrypted_for_dm" = "Die Nachrichten hier sind nicht Ende-zu-Ende verschlΓΌsselt."; "settings_security" = "SICHERHEIT"; "settings_integrations_allow_description" = "Benutze einen Integrationsmanager (%@), um Bots, Bridges, Widgets und Aufkleberpakete zu verwalten.\n\nIntegrationsmanager erhalten Konfigurationsdaten und kΓΆnnen Widgets verΓ€ndern, Raum-Einladungen versenden sowie Berechtigungen in deinem Namen einstellen."; "settings_labs_enable_cross_signing" = "Aktiviere Cross-Signing, um deinen GesprΓ€chspartner anstatt dessen GerΓ€t zu verifizieren (in Entwicklung)"; @@ -916,8 +982,9 @@ "security_settings_title" = "Sicherheit"; "security_settings_crypto_sessions" = "MEINE SITZUNGEN"; "security_settings_crypto_sessions_loading" = "Lade Sitzungen…"; -"room_participants_security_information_room_encrypted" = "Nachrichten in diesem Raum sind Ende-zu-Ende verschlΓΌsselt.\n\nDeine Nachrichten sind mit digitalen SchlΓΌsseln gesichert, nur du und der EmpfΓ€nger haben die einzigen SchlΓΌssel, um jene zu entsperren."; -"security_settings_crypto_sessions_description" = "Vertraue Sitzungen, um Zugriff auf Ende-zu-Ende verschlΓΌsselte Nachrichten zu gewΓ€hren. Wenn du eine Sitzung nicht kennst, Γ€ndere dein Anmeldepasswort und setze dein Nachrichtenpasswort fΓΌr die Nachrichtensicherung zurΓΌck."; +"room_participants_security_information_room_encrypted" = "Nachrichten in diesem Raum sind Ende-zu-Ende verschlΓΌsselt.\n\nDeine Nachrichten sind mit digitalen SchlΓΌsseln gesichert, nur du und der/die EmpfΓ€nger!n haben die einzigen SchlΓΌssel, um jene zu entschlΓΌsseln."; +"security_settings_crypto_sessions_description" = "Vertraue Sitzungen, um Zugriff auf Ende-zu-Ende verschlΓΌsselte Nachrichten zu gewΓ€hren. Wenn du eine Sitzung nicht kennst, Γ€ndere dein Passwort und setze dein Nachrichtenpasswort fΓΌr die Nachrichtensicherung zurΓΌck."; +"room_participants_security_information_room_encrypted_for_dm" = "Nachrichten hier sind Ende-zu-Ende verschlΓΌsselt.\n\nDeine Nachrichten sind mit digitalen SchlΓΌsseln gesichert, nur du und der/die EmpfΓ€nger!n haben die einzigen SchlΓΌssel, um jene zu entsperren."; "security_settings_backup" = "NACHRICHTENSICHERUNG"; "security_settings_advanced" = "ERWEITERT"; "security_settings_blacklist_unverified_devices" = "Sende niemals Nachrichten an nicht-vertrauenswΓΌrdige Sitzungen"; @@ -937,7 +1004,7 @@ "room_widget_permission_webview_information_title" = "Bei Benutzung werden vermutlich Cookies gesetzt und Daten geteilt mit %@:\n"; "room_widget_permission_information_title" = "Bei Benutzung werden vermutlich Daten geteilt mit %@:\n"; "room_widget_permission_display_name_permission" = "Dein Anzeigename"; -"room_widget_permission_avatar_url_permission" = "Deine Avatar-URL"; +"room_widget_permission_avatar_url_permission" = "Deine Profilbild-URL"; "room_widget_permission_user_id_permission" = "Deine Benutzer-ID"; "room_widget_permission_theme_permission" = "Dein Thema"; "room_widget_permission_widget_id_permission" = "Widget-ID"; @@ -995,27 +1062,37 @@ "device_verification_self_verify_start_verify_action" = "ÜberprΓΌfung starten"; "device_verification_self_verify_start_information" = "Benutze diese Sitzung um deine Neue zu verifizieren. Erlaube Zugriff auf die verschlΓΌsselten Nachrichten."; "device_verification_self_verify_start_waiting" = "Warte…"; -"device_verification_self_verify_wait_title" = "vervollstΓ€ndige Sicherheit"; -"device_verification_self_verify_wait_information" = "ÜberprΓΌfe diese Sitzung von einer anderen aus, um Zugriff auf die verschlΓΌsselten Nachrichten zu erhalten.\n\nBenutze die neuest %@-Sitzung auf deinem anderen GerΓ€t:"; +"device_verification_self_verify_wait_title" = "Verifiziere dieses GerΓ€t"; +"device_verification_self_verify_wait_information" = "Verfiziere dieses GerΓ€t von einer Deiner anderen Sitzungen, um Zugriff auf die verschlΓΌsselten Nachrichten zu erhalten."; +"device_verification_self_verify_wait_information_more" = "Benutze die neuste Messenger-Sitzung auf deinem anderen GerΓ€t:"; "device_verification_self_verify_wait_waiting" = "warte…"; -"skip" = "Überspringen"; + +// MARK: Clients +"client_desktop_name" = "BundesMessenger Desktop"; "security_settings_crosssigning_info_not_bootstrapped" = "Quersignierung ist bisher nicht konfiguriert."; +"client_web_name" = "BundesMessenger Web"; +"client_ios_name" = "BundesMessenger iOS"; +"client_android_name" = "BundesMessenger Android"; + +"skip" = "Überspringen"; "room_member_power_level_admin_in" = "Admin in %@"; "room_member_power_level_moderator_in" = "Mod in %@"; "room_member_power_level_custom_in" = "Selbstdefiniert (%@) in %@"; "room_member_power_level_short_admin" = "Admin"; "room_member_power_level_short_moderator" = "Mod"; "room_member_power_level_short_custom" = "Selbstdefiniert"; + "security_settings_secure_backup" = "SICHERE SICHERHEITSKOPIE"; "security_settings_secure_backup_synchronise" = "Synchronisiere"; -"security_settings_secure_backup_delete" = "Backup lΓΆschen"; +"security_settings_secure_backup_delete" = "LΓΆsche"; +"security_settings_crosssigning_info_ok" = "Cross-signing ist angeschaltet."; +"security_settings_crosssigning_reset" = "Cross-signing zurΓΌcksetzen"; +"security_settings_user_password_description" = "BestΓ€tige deine IdentitΓ€t durch Eingabe des Passwort"; "security_settings_crosssigning_info_ok" = "Quersignierung ist bereit zur Anwendung."; -"security_settings_crosssigning_reset" = "ZurΓΌcksetzen"; "security_settings_coming_soon" = "Entschuldigung, diese Funktion ist noch nicht fΓΌr %@ iOS verfΓΌgbar. Bitte nutze einen anderen Matrix-Client, um es einzurichten. %@ iOS wird es benutzen."; -"security_settings_user_password_description" = "BestΓ€tige deine IdentitΓ€t durch Eingabe des Kontopassworts"; // AuthenticatedSessionViewControllerFactory "authenticated_session_flow_not_supported" = "Diese App unterstΓΌtzt nicht diese Authentifizierungsmethode fΓΌr deinen Heimserver."; -"secure_key_backup_setup_intro_title" = "Sichere Datensicherung"; +"secure_key_backup_setup_intro_title" = "WiederherstellungsschlΓΌssel"; "store_promotional_text" = "PrivatsphΓ€re-wahrende Kollaborations-App in einem offenen Netzwerk. Dezentral, um dir die Kontrolle zu geben. Keine Datenerfassung, keine HintertΓΌren und kein Zugriff durch Dritte."; "room_participants_action_security_status_complete_security" = "VollstΓ€ndige Sicherheit"; "external_link_confirmation_title" = "ÜberprΓΌfe diesen Link genau"; @@ -1032,11 +1109,11 @@ "event_formatter_widget_removed_by_you" = "Du hast das Widget entfernt: %@"; "event_formatter_jitsi_widget_added_by_you" = "Du hast eine VoIP-Konferenz hinzugefΓΌgt"; "event_formatter_jitsi_widget_removed_by_you" = "Du hast eine VoIP-Konferenz entfernt"; -"secure_key_backup_setup_intro_info" = "Absicherung um den Zugriffsverlust auf verschlΓΌsselte Nachrichten und Daten zu verhindern, indem die SchlΓΌssel fΓΌr die EntschlΓΌsselung auf dem Server gesichert werden."; +"secure_key_backup_setup_intro_info" = "Damit du deine Nachrichten bei einem erneuten Login auf diesem GerΓ€t entschlΓΌsseln kannst, richte einen WiederherstellungsschlΓΌssel ein."; "secure_key_backup_setup_intro_use_security_key_title" = "Benutze einen SicherheitsschlΓΌssel"; "secure_key_backup_setup_intro_use_security_key_info" = "Generiere einen SicherheitsschlΓΌssel, welcher z.B. in einer Passwortverwaltung oder in einem Tresor sicher aufbewahrt werden sollte."; -"secure_key_backup_setup_intro_use_security_passphrase_title" = "Benutze Sicherungsphrase"; -"secure_key_backup_setup_intro_use_security_passphrase_info" = "Gib eine geheime Phrase ein, die nur du kennst, um einen SchlΓΌssel fΓΌr die Sicherung zu generieren."; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "WiederherstellungsschlΓΌssel"; +"secure_key_backup_setup_intro_use_security_passphrase_info" = "Nutze deinen WiederherstellungsschlΓΌssel, um alle deine Nachrichten zu entschlΓΌsseln."; "secure_key_backup_setup_existing_backup_error_title" = "Eine Sicherheitskopie fΓΌr Nachrichten existiert bereits"; "secure_key_backup_setup_existing_backup_error_info" = "Entsperre es, um es in der sicheren Datensicherung wiederzuverwenden, oder lΓΆsche es, um eine neue Nachrichtensicherung zu erstellen."; "secure_key_backup_setup_existing_backup_error_unlock_it" = "EntschlΓΌsseln"; @@ -1050,7 +1127,7 @@ // MARK: - Device Verification "key_verification_other_session_title" = "Sitzung verifizieren"; "key_verification_new_session_title" = "Neue Sitzung verifizieren"; -"key_verification_this_session_title" = "Verifiziere diese Sitzung"; +"key_verification_this_session_title" = "WiederherstellungsschlΓΌssel"; "device_verification_security_advice_emoji" = "Vergleiche die Emojis und kontrolliere, dass sie in derselben Reihenfolge angezeigt werden."; "device_verification_security_advice_number" = "Vergleiche die Zahlen und stell sicher, dass sie in derselben Reihenfolge angezeigt werden."; "key_verification_self_verify_current_session_alert_title" = "Verifiziere diese Sitzung"; @@ -1059,10 +1136,10 @@ "key_verification_self_verify_unverified_sessions_alert_message" = "Verifiziere alle deine Sitzungen, um sicher zu stellen, dass dein Konto und deine Nachrichten sicher sind."; "key_verification_self_verify_unverified_sessions_alert_validate_action" = "ÜberprΓΌfung"; "device_verification_self_verify_wait_new_sign_in_title" = "Diese Anmeldung verifizieren"; +"device_verification_self_verify_wait_recover_secrets_without_passphrase" = "VerschlΓΌsselungskennwort verwenden"; +"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Alternative Wiederherstellung"; +"device_verification_self_verify_wait_recover_secrets_additional_information" = "Nutze Dein WiederherstellungsschlΓΌssel um alle Deine Nachrichten zu entschlΓΌsseln"; "device_verification_self_verify_wait_additional_information" = "Dies funktioniert mit %@ oder einem anderen Matrix-Client, der Quersignierung unterstΓΌtzt."; -"device_verification_self_verify_wait_recover_secrets_without_passphrase" = "SicherungsschlΓΌssel verwenden"; -"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Sicherungsphrase oder -schlΓΌssel verwenden"; -"device_verification_self_verify_wait_recover_secrets_additional_information" = "Falls du keinen Zugang zu einer existierenden Sitzung hast"; "key_verification_verify_sas_title_emoji" = "Vergleiche Emojis"; "key_verification_verify_sas_title_number" = "Vergleiche Zahlen"; "key_verification_verify_sas_cancel_action" = "Sie passen nicht"; @@ -1077,7 +1154,7 @@ "key_verification_verified_new_session_title" = "Neue Sitzung verifiziert!"; "key_verification_verified_other_session_information" = "Du kannst nun sichere Nachrichten in deiner anderen Sitzung lesen. Andere Benutzer wissen, dass sie ihr vertrauen kΓΆnnen."; "key_verification_verified_new_session_information" = "Du kannst nun sichere Nachrichten auf deinem neuen GerΓ€t lesen. Andere Benutzer wissen, dass sie es vertrauen kΓΆnnen."; -"key_verification_verified_this_session_information" = "Du kannst nun sichere Nachrichten auf diesem GerΓ€t lesen. Andere Benutzer wissen, dass sie es vertrauen kΓΆnnen."; +"key_verification_verified_this_session_information" = "Deine Chats wurden erfolgreich wiederhergestellt."; "key_verification_verified_user_information" = "Nachrichten mit diesem GegenΓΌber sind Ende-zu-Ende verschlΓΌsselt und kΓΆnnen nicht von Dritten gelesen werden."; "key_verification_bootstrap_not_setup_title" = "Fehler"; "key_verification_bootstrap_not_setup_message" = "Du musst erst die Quersignatur einrichten."; @@ -1099,28 +1176,36 @@ "key_verification_scan_confirmation_scanned_user_information" = "Zeigt %@ dasselbe Schild an?"; "key_verification_scan_confirmation_scanned_device_information" = "Zeigt das andere GerΓ€t das gleiche Schild an?"; "user_verification_session_details_verify_action_current_user_manually" = "ÜberprΓΌfe manuell mit einem Text"; -"secrets_recovery_with_passphrase_title" = "Sicherungsphrase"; -"secrets_recovery_with_passphrase_information_default" = "FΓΌr Zugriff auf die Nachrichtenhistorie und Quersignatur gib deine Sicherungsphrase ein."; -"secrets_recovery_with_passphrase_information_verify_device" = "Nutze deine Sicherungsphrase um dieses GerΓ€t zu verifizieren."; -"secrets_recovery_with_passphrase_passphrase_placeholder" = "Gib die Sicherungsphrase ein"; -"secrets_recovery_with_passphrase_recover_action" = "Sicherungsphrase verwenden"; -"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Wenn du die Sicherungsphrase vergessen hast, kannst du "; -"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "den SicherungsschlΓΌssel nutzen"; +"secrets_recovery_reset_action_part_1" = "Keine anderen GerΓ€te zur Verfikation verfΓΌgbar oder Kennwort vergessen?"; +"secrets_recovery_reset_action_part_2" = "VerschlΓΌsselungskennwort zurΓΌcksetzen"; +"secrets_recovery_with_key_recovery_key_title" = "Eingabe"; + +"secrets_recovery_with_passphrase_title" = "VerschlΓΌsselungskennwort"; +"secrets_recovery_with_passphrase_information_default" = "Gib dein WiederherstellungsschlΓΌssel ein, um auf auf deine Nachrichtenhistorie und deine Cross-Signing-IdentitΓ€t zuzugreifen. Die Cross-Signing-IdentitΓ€t ermΓΆglicht dir andere Sitzungen zu verifizieren."; +"secrets_recovery_with_passphrase_information_verify_device" = "Benutze dein WiederherstellungsschlΓΌssel um Deine Nachrichten wiederherzustellen."; +"secrets_recovery_with_passphrase_passphrase_placeholder" = "WiederherstellungsschlΓΌssel eingeben"; +"secrets_recovery_with_passphrase_recover_action" = "Weiter"; +"secrets_recovery_with_passphrase_title" = "WiederherstellungsschlΓΌssel"; +"secrets_recovery_with_passphrase_information_default" = "Gib dein WiederherstellungsschlΓΌssel ein, um auf deine verschlΓΌsselten Nachrichten und deine Cross-Signing-IdentitΓ€t zuzugreifen. Mit der Cross-Signing-IdentitΓ€t kannst du andere Sitzungen verifizieren."; +"secrets_recovery_with_passphrase_information_verify_device" = "Nutze deinen WiederherstellungsschlΓΌssel, um alle deine Nachrichten zu entschlΓΌsseln."; +"secrets_recovery_with_passphrase_passphrase_placeholder" = "WiederherstellungsschlΓΌssel"; +"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Wenn du deinen WiederherstellungsschlΓΌssel nicht weißt, kannst du "; +"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "nutze deinen WiederherstellungsschlΓΌssel"; "secrets_recovery_with_passphrase_lost_passphrase_action_part3" = "."; "secrets_recovery_with_passphrase_invalid_passphrase_title" = "Auf sicheren Speicher kann nicht zugegriffen werden"; -"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Bitte stell sicher, dass du die korrekte Sicherungsphrase eingegeben hast."; -"secrets_recovery_with_key_title" = "SicherungsschlΓΌssel"; -"secrets_recovery_with_key_information_default" = "Gib den SicherungsschlΓΌssel ein, um auf die verschlΓΌsselten Nachrichten und Quersignatur zuzugreifen. Mit der Quersignatur kannst du andere Sitzungen verifizieren."; -"secrets_recovery_with_key_information_verify_device" = "Nutze deinen SicherungsschlΓΌssel, um dieses GerΓ€t zu verifizieren."; -"secrets_recovery_with_key_recovery_key_placeholder" = "SicherungsschlΓΌssel eingeben"; +"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Bitte stell sicher, dass du den korrekten WiederherstellungsschlΓΌssel eingegeben hast."; +"secrets_recovery_with_key_title" = "VerschlΓΌsselungskennwort"; +"secrets_recovery_with_key_information_default" = "Gib dein VerschlΓΌsselungskennwort ein, um auf deine verschlΓΌsselten Nachrichten und deine Cross-Signing-IdentitΓ€t zuzugreifen. Mit der Cross-Signing-IdentitΓ€t kannst du andere Sitzungen verifizieren."; +"secrets_recovery_with_key_information_verify_device" = "Nutze dein VerschlΓΌsselungskennwort um dieses GerΓ€t zu verifizieren."; +"secrets_recovery_with_key_recovery_key_placeholder" = "VerschlΓΌsselungskennwort eingeben"; "secrets_recovery_with_key_recover_action" = "SchlΓΌssel benutzen"; "secrets_recovery_with_key_invalid_recovery_key_title" = "Auf sicheren Speicher kann nicht zugegriffen werden"; -"secrets_recovery_with_key_invalid_recovery_key_message" = "ÜberprΓΌfe, dass du den richtigen SicherungsschlΓΌssel verwendest."; -"secrets_setup_recovery_key_title" = "Speichere deinen SicherheitsschlΓΌssel"; -"secrets_setup_recovery_key_information" = "Bewahre deinen SicherungsschlΓΌssel an einem sicheren Ort auf. Mit ihm kannst du deine verschlΓΌsselten Nachrichten wiederherstellen."; +"secrets_recovery_with_key_invalid_recovery_key_message" = "Bitte stell sicher, dass du das korrekte VerschlΓΌsselungskennwort benutzt hast."; +"secrets_setup_recovery_key_title" = "WiederherstellungsschlΓΌssel"; +"secrets_setup_recovery_key_information" = "Geschafft!\n\nDeine Chats wurden erfolgreich wiederhergestellt."; "secrets_setup_recovery_key_loading" = "LΓ€dt…"; "secrets_setup_recovery_key_export_action" = "Speichern"; -"secrets_setup_recovery_key_done_action" = "Erledigt"; +"secrets_setup_recovery_key_done_action" = "Fertig"; "secrets_setup_recovery_key_storage_alert_title" = "Bewahre ihn sicher auf"; "key_verification_verify_qr_code_scan_other_code_success_message" = "Der QR-Code wurde erfolgreich validiert."; "security_settings_secure_backup_setup" = "Einrichten"; @@ -1129,16 +1214,22 @@ "device_verification_self_verify_alert_validate_action" = "ÜberprΓΌfen"; "key_verification_self_verify_current_session_alert_validate_action" = "ÜberprΓΌfen"; "key_verification_manually_verify_device_validate_action" = "ÜberprΓΌfen"; -"secrets_recovery_with_passphrase_passphrase_title" = "Eingeben"; -"secrets_recovery_with_key_recovery_key_title" = "Eingeben"; +"secrets_recovery_with_passphrase_passphrase_title" = ""; +"secrets_recovery_with_key_recovery_key_title" = ""; "secrets_setup_recovery_key_storage_alert_message" = "βœ“ Drucke ihn aus und bewahre ihn an einem sicheren Ort auf\nβœ“ Speichere ihn auf einem USB-Stick oder einem Sicherungslaufwerk\nβœ“ Kopiere ihn zu deinem persΓΆnlichen Speicher im Netz"; -"secrets_setup_recovery_passphrase_title" = "Setze Sicherheitsphrase"; -"secrets_setup_recovery_passphrase_information" = "Gib eine Sicherheitsphrase ein, welche nur du kennst und deine Daten auf dem Server geheim halten soll."; -"secrets_setup_recovery_passphrase_additional_information" = "Benutze dein Kontopasswort nicht mehrfach."; +"secrets_setup_recovery_passphrase_title" = "WiederherstellungsschlΓΌssel"; +"secrets_setup_recovery_passphrase_information" = "Ein WiederherstellungsschlΓΌssel kann ein Satz sein, den du dir gut merken kannst.\n\nWenn du diesen vergisst, kΓΆnnen deine Nachrichten nicht mehr entschlΓΌsselt werden."; +"secrets_setup_recovery_passphrase_additional_information" = "⚠️ Benutze hier nicht dein Account Passwort."; "secrets_setup_recovery_passphrase_validate_action" = "Fertig"; -"secrets_setup_recovery_passphrase_confirm_information" = "Gib deine Sicherheitsphrase zur BestΓ€tigung erneut ein."; -"secrets_setup_recovery_passphrase_confirm_passphrase_title" = "BestΓ€tigen"; -"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Phrase bestΓ€tigen"; +"secrets_setup_recovery_passphrase_confirm_information" = "Gib deinen WiederherstellungsschlΓΌssel zur BestΓ€tigung erneut ein."; +"secrets_setup_recovery_passphrase_confirm_passphrase_title" = ""; +"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "WiederherstellungsschlΓΌssel"; +"secrets_reset_title" = "VerschlΓΌsselung zurΓΌcksetzen"; +"secrets_reset_information" = "Bitte fahre nur fort, wenn Du kein anderes GerΓ€t hast um diesen Login zu verifizieren."; +"secrets_reset_warning_title" = "Wenn Du die VerschlΓΌsselung zurΓΌcksetzt:"; +"secrets_reset_warning_message" = "Du verlierst dadurch deinen gesamten bisherigen Nachrichtenverlauf, sowie verfizierte GerΓ€te oder Nutzer!"; +"secrets_reset_reset_action" = "ZurΓΌcksetzen"; +"secrets_reset_authentication_message" = "Bitte gebe zur BestΓ€tigung Dein Accountpassword ein!"; "cross_signing_setup_banner_title" = "VerschlΓΌsselung einrichten"; "cross_signing_setup_banner_subtitle" = "Verifiziere deine weiteren GerΓ€te bequemer"; "major_update_title" = "Riot ist jetzt %@"; @@ -1146,8 +1237,9 @@ "major_update_learn_more_action" = "Mehr erfahren"; "major_update_done_action" = "Verstanden"; "pin_protection_choose_pin" = "Erstelle eine PIN fΓΌr mehr Sicherheit"; -"pin_protection_confirm_pin" = "BestΓ€tige deinen PIN"; +"pin_protection_confirm_pin" = "BestΓ€tige deine PIN"; "pin_protection_confirm_pin_to_disable" = "BestΓ€tige PIN um die PIN zu deaktivieren"; +"pin_protection_confirm_pin_to_change" = "Aktuelle PIN eingeben"; "pin_protection_enter_pin" = "Gib deine PIN ein"; "pin_protection_forgot_pin" = "PIN vergessen"; "pin_protection_reset_alert_title" = "PIN zurΓΌcksetzen"; @@ -1156,6 +1248,7 @@ "pin_protection_mismatch_error_title" = "Die PINs stimmen nicht ΓΌberein"; "pin_protection_mismatch_error_message" = "Bitte versuche es erneut"; "pin_protection_mismatch_too_many_times_error_message" = "Wenn du dich nicht an deine PIN erinnern kannst, drΓΌcke \"PIN vergessen\"."; +"pin_protection_settings_section_header_x" = "PIN"; "pin_protection_settings_section_header" = "PIN"; "pin_protection_settings_section_header_with_biometrics" = "PIN und %@"; "pin_protection_settings_section_footer" = "Um deine PIN zurΓΌckzusetzen, musst du dich erneut anmelden und eine neue erstellen."; @@ -1174,17 +1267,24 @@ "biometrics_cant_unlocked_alert_message_x" = "Zum Entsperren nutze %@ oder melde dich erneut an und reaktiviere %@"; "biometrics_cant_unlocked_alert_message_login" = "Erneut anmelden"; "biometrics_cant_unlocked_alert_message_retry" = "Erneut probieren"; +"biometrics_mode_cant_unlock_button_title" = "PIN eingeben"; + +// MARK: - Searchable Directory View Controller +"searchable_directory_create_new_room" = "Erstelle einen neuen Raum"; +"searchable_directory_x_network" = "%@ Netzwerk"; +"searchable_directory_search_placeholder" = "Raumname suchen"; +"searchable_directory_room_title" = "Raumverzeichnis"; + "device_verification_self_verify_wait_recover_secrets_checking_availability" = "Nach anderen ÜberprΓΌfungsfunktionen suchen ..."; "joined" = "Beigetreten"; "switch" = "Γ„ndern"; "more" = "Mehr"; -"pin_protection_choose_pin_welcome_after_login" = "Willkommen zurΓΌck."; -"pin_protection_choose_pin_welcome_after_register" = "Willkommen."; +"pin_protection_choose_pin_welcome_after_login" = "Willkommen!"; +"pin_protection_choose_pin_welcome_after_register" = "Willkommen!"; "pin_protection_not_allowed_pin" = "Aus SicherheitsgrΓΌnden ist diese PIN nicht verfΓΌgbar. Bitte versuche es mit einer anderen PIN"; "pin_protection_explanatory" = "Das Einrichten einer PIN schΓΌtzt Daten wie Nachrichten und Kontakten so, dass nur du Zugriff auf sie hast, indem du die PIN beim Starten der App eingibst."; -"searchable_directory_create_new_room" = "Erstelle einen neuen Raum"; -"searchable_directory_x_network" = "%@ Netzwerk"; -"searchable_directory_search_placeholder" = "Name oder ID"; + +// MARK: - Create Room "create_room_title" = "Neuer Raum"; "create_room_section_header_name" = "Raumname"; "create_room_placeholder_name" = "Name"; @@ -1200,6 +1300,13 @@ "create_room_show_in_directory" = "Zeige den Raum im Raumverzeichnis"; "create_room_section_header_address" = "Raum-Adresse"; "create_room_placeholder_address" = "#testraum:matrix.org"; + +// MARK: - Integrity + +"integrity_alert_title" = "Fehler beim Appstart"; +"integrity_alert_message" = "Deine Zugangsdaten sind leider nicht mehr in der App gespeichert. Bitte beende den Messenger und logge dich neu ein. Dabei musst du deinen Pin Code neu setzen und dich durch dein VerschlΓΌsselungskennwort verifizieren."; + +// MARK: - Room Info "room_info_list_room_encrypted" = "Nachrichten in diesem Raum sind Ende-zu-Ende verschlΓΌsselt"; "room_info_list_one_member" = "1 Mitglied"; "room_info_list_several_members" = "%@ Mitglieder"; @@ -1229,7 +1336,7 @@ // MARK: - Secrets recovery -"secrets_recovery_reset_action_part_1" = "Wiederherstellungsoptionen vergessen oder verloren? "; +"secrets_recovery_reset_action_part_1" = "WiederherstellungsschlΓΌssel vergessen?"; "less" = "Weniger"; "secrets_reset_authentication_message" = "Gib dein Kontopasswort ein, um zu bestΓ€tigen"; "secrets_reset_reset_action" = "ZurΓΌcksetzen"; @@ -1237,11 +1344,22 @@ "secrets_reset_warning_title" = "Falls du alles zurΓΌcksetzt"; "secrets_reset_information" = "Tu dies nur, wenn du kein anderes GerΓ€t hast, mit dem du dieses GerΓ€t ΓΌberprΓΌfen kannst."; +// MARK: - Jailbreak +"jailbreak_alert_title" = "Systemanforderungen"; +"jailbreak_alert_message" = "%@ unterstΓΌtzt nur nicht modifizierte iOS-Versionen."; + +// MARK: - Splash Screen +"splash_screen_title" = "Dein Messenger fΓΌr unser Land"; +"splash_screen_first_info" = "Chats mit Kameraden und Kollegen"; +"splash_screen_second_info" = "Sicher Medien und Dateien ΓΌbertragen"; +"splash_screen_third_info" = "Immer verschlΓΌsselt"; +"splash_screen_start" = "Loslegen"; + // MARK: - Home "home_empty_view_title" = "Willkommen bei %@,\n%@"; -"secrets_setup_recovery_passphrase_summary_information" = "Erinnere dich an deine Sicherheitsphrase. Sie kann zum entschlΓΌsseln der Nachrichten und Daten verwendet werden."; -"secrets_setup_recovery_passphrase_summary_title" = "Sichere deine Sicherheitsphrase"; +"secrets_setup_recovery_passphrase_summary_information" = "Erinnere dich an deinen WiederherstellungsschlΓΌssel. Dieser dient zur EntschlΓΌsselung deiner bereits empfangenen Nachrichten und Daten."; +"secrets_setup_recovery_passphrase_summary_title" = "Sichere deinen WiederherstellungsschlΓΌssel"; "rooms_empty_view_title" = "RΓ€ume"; "people_empty_view_information" = "Sicher kommunizieren mit allen. DrΓΌcke + um Leute hinzuzufΓΌgen."; "people_empty_view_title" = "Leute"; @@ -1272,7 +1390,7 @@ "room_intro_cell_information_multiple_dm_sentence2" = "Nur ihr seid in diesem GesprΓ€ch, außer ihr ladet jemanden ein."; "room_intro_cell_information_dm_sentence2" = "Nur zwei von euch sind in diesem GesprΓ€ch. Keine anderer kann beitreten."; "room_intro_cell_information_dm_sentence1_part3" = ". "; -"room_intro_cell_information_dm_sentence1_part1" = "Dies ist der Beginn deiner Direktnachricht mit "; +"room_intro_cell_information_dm_sentence1_part1" = "Das ist der Anfang deiner Direktnachricht mit "; "room_intro_cell_information_room_without_topic_sentence2_part2" = " damit Leute wissen worum es in diesem geht."; "room_intro_cell_information_room_without_topic_sentence2_part1" = "Thema hinzufΓΌgen"; "room_intro_cell_information_room_with_topic_sentence2" = "Thema: %@"; @@ -1282,11 +1400,11 @@ // Mark: - Room creation introduction cell "room_intro_cell_add_participants_action" = "Leute hinzufΓΌgen"; -"room_avatar_view_accessibility_hint" = "Raumavatar Γ€ndern"; +"room_avatar_view_accessibility_hint" = "Raumbild Γ€ndern"; // Mark: - Room avatar view -"room_avatar_view_accessibility_label" = "Avatar"; +"room_avatar_view_accessibility_label" = "Profilbild"; "call_transfer_error_message" = "Rufumleitung fehlgeschlagen"; "call_transfer_error_title" = "Fehler"; "call_transfer_contacts_all" = "Alle"; @@ -1321,12 +1439,24 @@ "callbar_only_single_active" = "ZurΓΌck zum Anruf (%@)"; "room_event_action_delete_confirmation_message" = "MΓΆchtest Du die nicht gesendete Nachricht wirklich lΓΆschen?"; "room_event_action_delete_confirmation_title" = "Nicht gesendete Nachricht lΓΆschen"; + "room_details_integrations" = "Integrationen"; "room_details_search" = "Raum durchsuchen"; "room_multiple_typing_notification" = "%@ und andere"; "room_accessibility_video_call" = "Videoanruf"; "room_message_replying_to" = "%@ anworten"; "room_message_editing" = "Bearbeitung"; + +// MARK - Rest Time bwi +"settings_enable_rest_time" = "Ruhezeitregelung"; +"room_details_main_section_rest_time" = "Ruhezeitregelung"; +"room_details_main_section_rest_time_global_disabled" = "Aktiviere die Ruhezeitregelung in den Profileinstellungen"; +"room_details_main_section_rest_time_global" = ""; +"room_details_main_section_rest_time_locally_enabled" = ""; +"room_details_main_section_rest_time_locally_disabled" = "Nicht aktiv"; +"room_details_main_section_rest_time_restday" = "heute Ruhetag"; +"room_details_main_section_rest_time_time" = "%@ - %@"; + "space_beta_announce_information" = "Spaces bieten neue MΓΆglichkeiten um RΓ€ume und Personen zu gruppieren. Sie sind noch nicht auf iOS verfΓΌgbar, aber du kannst sie jetzt schon mit Web und Desktop nutzen."; "space_feature_unavailable_information" = "Spaces bieten neue MΓΆglichkeiten um RΓ€ume und Personen zu gruppieren.\n\nBald werden sie auch hier verfΓΌgbar sein. FΓΌr den Moment kannst du ihnen auf einer der anderen Plattformen beitreten und hier auf alle RΓ€ume zugreifen, denen du dort beitrittst."; "space_beta_announce_subtitle" = "Die verbesserte Version von Communities"; @@ -1349,6 +1479,7 @@ "event_formatter_call_ringing" = "LΓ€uten…"; "event_formatter_call_connecting" = "Verbinden…"; "settings_labs_enable_ringing_for_group_calls" = "Bei Gruppenanrufen klingeln"; + "room_no_privileges_to_create_group_call" = "Du musst Administrations- oder Moderationsrechte besitzen, um einen Anruf zu starten."; "room_join_group_call" = "Beitreten"; @@ -1365,13 +1496,13 @@ // Mark: - Side menu "side_menu_reveal_action_accessibility_label" = "Linkes Panel"; -"user_avatar_view_accessibility_hint" = "Nutzeravatar Γ€ndern"; +"user_avatar_view_accessibility_hint" = "Profilbild Γ€ndern"; // Mark: - User avatar view -"user_avatar_view_accessibility_label" = "Avatar"; +"user_avatar_view_accessibility_label" = "Profilbild"; "secrets_recovery_with_key_information_unlock_secure_backup_with_key" = "Zum Fortfahren gib deinen SicherheitsschlΓΌssel ein."; -"secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Zum Forfahren gib deine Passphrase ein."; +"secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Zum Forfahren gib deinen WiederherstellungsschlΓΌssel ein."; // Success from secure backup "key_backup_setup_success_from_secure_backup_info" = "Deine SchlΓΌssel werden gesichert."; @@ -1401,6 +1532,62 @@ "settings_labs_voice_messages" = "Sprachnachrichten"; "settings_notifications_disabled_alert_message" = "Γ–ffne die Systemeinstellungen um Benachrichtigungen zu aktivieren."; "settings_notifications_disabled_alert_title" = "Benachrichtigungen deaktiviert"; + +// Mark: - Voice Messages + +"voice_message_release_to_send" = "Zur Aufnahme halten, zum Senden loslassen"; +"voice_message_remaining_recording_time" = "noch %@s"; +"voice_message_stop_locked_mode_recording" = "Klick die Nachricht um zu stoppen oder zu hΓΆren"; +"voice_message_lock_screen_placeholder" = "Sprachnachricht"; +"voice_message_slide_to_cancel" = "Sliden zum Abbrechen"; + +// developer menu +"bwi_settings_developer" = "Entwickler"; +"bwi_settings_show_developer_settings" = "Einstellungen"; +"bwi_settings_developer_create_new_personal_notes_room" = "Neuen Notizen-Raum Raum erstellen"; +"bwi_settings_developer_reset_matomo_info" = "Matomo-Info-Screen zurΓΌcksetzen"; +"bwi_settings_developer_settings_reset_birthday_banner" = "Geburtstagsbanner zurΓΌcksetzen"; +"bwi_settings_developer_new_personal_notes_room_created" = "Der alte Notizen-Raum Raum wurde durch einen neuen ersetzt."; +"bwi_settings_developer_show_matomo_privacy_notes_resetted" = "Der Matomo-Info-Screen wird wieder angezeigt."; + +// personal notes room +"bwi_personal_notes_beginning_notes_room" = "Das ist dein privater Notizen-Raum. Du bist der einzige, der auf die Inhalte in diesem Raum zugreifen kann."; +"bwi_personal_notes_room_profile_encrypted_subtitle" = "Nachrichten in diesem Raum sind Ende-zu-Ende-verschlΓΌsselt."; +"bwi_personal_notes_hint_message" = "Dir steht jetzt ein privater Raum fΓΌr deine Notizen und Dateien zur VerfΓΌgung. Klicke, um den Raum zu ΓΆffnen."; +"bwi_settings_enable_notes_room_title" = "Zeige meine Notizen"; +"bwi_settings_enable_notes_room_summary" = "Einen Raum fΓΌr private Notizen anzeigen"; +"bwi_notes_room_title" = "Meine Notizen"; + +// rolls and rights +"bwi_rolls_and_rights" = "Rollen und Berechtigungen"; +"bwi_rolls_and_rights_main_title" = "Berechtigungen"; +"bwi_rolls_and_rights_main_header_admin" = "Rollen, die zum Γ„ndern verschiedener Teile des Raums erforderlich sind, auswΓ€hlen"; +"bwi_rolls_and_rights_main_header_no_admin" = "Rollen, die zum Γ„ndern verschiedener Teile des Raums erforderlich sind, auswΓ€hlen"; +"bwi_rolls_and_rights_general" = "Raumeigenschaften (Name, Thema, Raumbild) anpassen"; +"bwi_rolls_and_rights_send" = "Nachrichten senden"; +"bwi_rolls_and_rights_invite" = "Nutzer einladen"; +"bwi_rolls_and_rights_redact" = "Nachrichten lΓΆschen"; +"bwi_rolls_and_rights_ban" = "Nutzer entfernen"; +"bwi_rolls_and_rights_admin" = "Admin"; +"bwi_rolls_and_rights_moderator" = "Moderator"; +"bwi_rolls_and_rights_user" = "Standard"; +"bwi_rolls_and_rights_reset_button" = "Werte zurΓΌcksetzen"; +"bwi_rolls_and_rights_reset_title" = "Bist du sicher, dass die Rechteeinstellungen dieses Raumes zurΓΌck gesetzt werden sollen?"; +"bwi_rolls_and_rights_reset_cancel" = "Abbruch"; +"bwi_rolls_and_rights_reset_ok" = "ZurΓΌcksetzen"; + +// Time Line settings +"settings_enable_simple_time_line" = "Zeige Zeitlinienereignisse"; +"settings_enable_time_stamp" = "Zeige Zeitstempel"; +"settings_enable_deleted_messages" = "Zeige gelΓΆschte Nachrichten"; +"settings_enable_name_change" = "Zeige Kontoereignisse"; +"settings_enable_chat_effects" = "Zeige Chateffekte"; +"settings_enable_room_avatar" = "Zeige Raumereignisse"; +"settings_enable_user_avatar" = "Zeige Kontoereignisse"; +"settings_enable_enter_room" = "Zeige Betreten und Verlassen"; + +// Last Admin Downgrade +"room_member_details_downgrade_last_admin" = "Du kannst dich nur zurΓΌckstufen, wenn du vorher einen weiteren Admin benennst."; "event_formatter_call_incoming_video" = "Eingehender Videoanruf"; "event_formatter_call_has_ended_with_time" = "Anruf beendet β€’ %@"; "voice_message_stop_locked_mode_recording" = "Klicke, um die Aufnahme zu starten oder stoppen"; @@ -1458,8 +1645,9 @@ // Mark: Avatar -"space_avatar_view_accessibility_label" = "Avatar"; +"space_avatar_view_accessibility_label" = "Raumbild"; "spaces_coming_soon_detail" = "Diese Funktion wurde hier noch nicht eingebaut, kommt aber bald. So lange kannst du dafΓΌr %@ am Computer nutzen."; + "spaces_invites_coming_soon_title" = "Einladungen sind bald verfΓΌgbar"; "spaces_add_rooms_coming_soon_title" = "RΓ€ume hinzufΓΌgen ist bald verfΓΌgbar"; "spaces_no_result_found_title" = "Keine Ergebnisse gefunden"; @@ -1467,7 +1655,7 @@ "leave_space_message_admin_warning" = "Du besitzt in diesem Space Administrationsrechte, stelle vor dem Verlassen sicher, dass du die Administrationsrechte auf ein anderes Mitglied ΓΌbertragen hast."; "leave_space_message" = "Bist du dir sicher, dass du %@ verlassen mΓΆchtest? MΓΆchtest du außerdem alle RΓ€ume und Spaces in diesem Space verlassen?"; "leave_space_title" = "%@ verlassen"; -"space_avatar_view_accessibility_hint" = "Space-Avatar Γ€ndern"; +"space_avatar_view_accessibility_hint" = "Raumbild Γ€ndern"; "space_public_join_rule" = "Γ–ffentlicher Space"; "space_private_join_rule" = "Privater Space"; "space_participants_action_ban" = "Aus diesem Space verbannen"; @@ -1483,6 +1671,28 @@ "space_tag" = "Space"; "open" = "Γ–ffnen"; "settings_links" = "LINKS"; + +// Mark: bwiRegistration + +"bwi_auth_register_button_title" = "Konto erstellen"; +"bwi_auth_register_alert_title" = "Konto erstellen"; + +// Mark: bwi error codes + +"bwi_error_invite_already_in_room" = "%@ ist bereits im Raum."; +"bwi_error_invite_banned_in_room" = "%@ ist vom Raum gebannt."; +"bwi_error_invite_general" = "%@ konnte nicht eingeladen werden."; + +/* BWI: Matomo */ +"MATOMO_USER_INFO" = "Wir brauchen Deine Hilfe, um einige Fehler im Messenger besser analysieren zu kΓΆnnen.\nDazu erfassen wir Diagnosedaten.\nDetails dazu findest Du in der DatenschutzerklΓ€rung."; +"MATOMO_USER_INFO_BUTTON_OK" = "OK"; +"MATOMO_USER_INFO_BUTTON_MORE_INFO" = "DatenschutzerklΓ€rung"; +"MATOMO_SETTINGS_SEND_DIAGNOSTIC_DATA" = "Diagnosedaten senden"; + +/* BWI: New Features */ +"bwi_settings_new_features_header" = "Neue Funktionen"; +"bwi_settings_new_features_show_features" = "Neue Funktionen anzeigen"; + "find_your_contacts_footer" = "Dies kann jederzeit in den Einstellungen deaktiviert werden."; "settings_contacts_enable_sync" = "Finde deine Kontakte"; "space_home_show_all_rooms" = "Alle RΓ€ume anzeigen"; @@ -1567,6 +1777,7 @@ "analytics_prompt_point_1" = "Wir erfassen und analysieren keine Accountdaten"; /* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ "analytics_prompt_terms_upgrade" = "Alle unsere Bedingungen lesen %@. Bist du damit einverstanden?"; + "ok" = "OK"; "location_sharing_settings_toggle_title" = "Standortfreigabe aktivieren"; "location_sharing_settings_header" = "Standortfreigabe"; @@ -1603,3 +1814,4 @@ "poll_edit_form_poll_type_open_description" = "Ergebnisse werden direkt nach Stimmabgabe angezeigt"; "poll_edit_form_poll_type_open" = "Offene Umfrage"; "poll_edit_form_update_failure_title" = "Aktualisierung der Umfrage fehlgeschlagen"; + diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings new file mode 100644 index 000000000..264b333fe --- /dev/null +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// MARK: - Attachment +"content_scan_status_failed" = "Fail to load"; +"content_scan_status_in_progress" = "Scanning"; +"content_scan_status_infected" = "Blocked"; +"content_scan_status_trusted" = "βœ“ Trusted"; + +"settings_downtime_message" = "\nThe Messenger is not available from\n%@ till %@\n\n"; + +"bwi_auth_beta_selection_title" = "Server selection"; +"bwi_auth_beta_selection_button_title" = "Preselection"; + +"bwi_settings_developer_apply_app_config" = "MDM Config: MSG Demo"; +"bwi_settings_developer_apply_orig_app_config" = "MDM Config: BW"; +"bwi_settings_developer_reset_app_config" = "MDM Config: Reset"; + +"bwi_mdm_logout_message" = "The configuration has changed. Please reconnect."; diff --git a/Riot/Assets/en.lproj/Localizable.strings b/Riot/Assets/en.lproj/Localizable.strings index f3547d667..3743c5123 100644 --- a/Riot/Assets/en.lproj/Localizable.strings +++ b/Riot/Assets/en.lproj/Localizable.strings @@ -180,5 +180,10 @@ "GROUP_CALL_FROM_USER" = "%@ (Group call)"; /** Key verification **/ - "KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ wants to verify"; + +/* BWI: Happy Birthday Campaign*/ +"HAPPY_BIRTHDAY" = "HAPPY BIRTHDAY"; +"HAPPY_BIRTHDAY_BODY" = "Messenger is celebrating its\nfirst year anniversary!\nWould you like to celebrate with us?\nThen jump right into the AppStore\nand send us some stars."; +"HAPPY_BIRTHDAY_BUTTON_GIVE_STARS" = "Send Gift"; +"HAPPY_BIRTHDAY_BUTTON_DISMISS" = "X"; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index e05acff9b..2ea773dae 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -23,7 +23,7 @@ // Titles "title_home" = "Home"; "title_favourites" = "Favourites"; -"title_people" = "People"; +"title_people" = "Direct Messages"; "title_rooms" = "Rooms"; "title_groups" = "Communities"; "warning" = "Warning"; @@ -101,7 +101,7 @@ "auth_login_single_sign_on" = "Sign In"; "auth_send_reset_email" = "Send Reset Email"; "auth_return_to_login" = "Return to login screen"; -"auth_user_id_placeholder" = "Email or user name"; +"auth_user_id_placeholder" = "User name"; "auth_password_placeholder" = "Password"; "auth_new_password_placeholder" = "New password"; "auth_user_name_placeholder" = "User name"; @@ -132,8 +132,10 @@ "auth_untrusted_id_server" = "The identity server is not trusted"; "auth_password_dont_match" = "Passwords don't match"; "auth_username_in_use" = "Username in use"; -"auth_forgot_password" = "Forgot Matrix account password?"; -"auth_forgot_password_error_no_configured_identity_server" = "No identity server is configured: add one to reset your Matrix account password."; + +"auth_forgot_password" = "Forgot password?"; +"bwi_auth_forgot_password_alert_text" = "If you have forgotton your password, contact your pool manager or go to the SelfServicePortal at IntranetBw and reset your password."; +"auth_forgot_password_error_no_configured_identity_server" = "No identity server is configured: add one to reset your password."; "auth_email_not_found" = "Failed to send email: This email address was not found"; "auth_use_server_options" = "Use custom server options (advanced)"; "auth_email_validation_message" = "Please check your email to continue registration"; @@ -278,6 +280,7 @@ Tap the + to start adding people."; "room_participants_leave_prompt_title_for_dm" = "Leave"; "room_participants_leave_prompt_msg" = "Are you sure you want to leave the room?"; "room_participants_leave_prompt_msg_for_dm" = "Are you sure you want to leave?"; +"room_participants_cant_leave_prompt_message" = "You cannot leave the room because there are no other admins in the room."; "room_participants_remove_prompt_title" = "Confirmation"; "room_participants_remove_prompt_msg" = "Are you sure you want to remove %@ from this chat?"; "room_participants_remove_third_party_invite_prompt_msg" = "Are you sure you want to revoke this invite?"; @@ -534,6 +537,7 @@ Tap the + to start adding people."; "settings_change_password" = "Change Matrix account password"; "settings_night_mode" = "Night Mode"; "settings_fail_to_update_profile" = "Fail to update profile"; +"settings_fail_to_update_profile_message" = "Changing display name is disabled on this server"; "settings_three_pids_management_information_part1" = "Manage which email addresses or phone numbers you can use to log in or recover your account here. Control who can find you in "; "settings_three_pids_management_information_part2" = "Discovery"; "settings_three_pids_management_information_part3" = "."; @@ -604,14 +608,18 @@ Tap the + to start adding people."; "settings_labs_message_reaction" = "React to messages with emoji"; "settings_labs_enable_ringing_for_group_calls" = "Ring for group calls"; "settings_labs_enabled_polls" = "Polls"; + "settings_labs_enable_threads" = "Threaded messaging"; "settings_version" = "Version %@"; "settings_olm_version" = "Olm Version %@"; +"settings_element_version" = "Element Version %@"; "settings_copyright" = "Copyright"; "settings_term_conditions" = "Terms & Conditions"; "settings_privacy_policy" = "Privacy Policy"; "settings_third_party_notices" = "Third-party Notices"; +"settings_support" = "Support"; +"settings_send_crash_report" = "Send anon crash & usage data"; "settings_analytics_and_crash_data" = "Send crash and analytics data"; "settings_enable_rageshake" = "Rage shake to report bug"; "settings_clear_cache" = "Clear cache"; @@ -620,8 +628,9 @@ Tap the + to start adding people."; "settings_old_password" = "old password"; "settings_new_password" = "new password"; "settings_confirm_password" = "confirm password"; -"settings_fail_to_update_password" = "Fail to update Matrix account password"; -"settings_password_updated" = "Your Matrix account password has been updated"; +"settings_fail_to_update_password" = "Fail to update password"; +"settings_password_updated" = "Your password has been updated"; +"settings_password_condition" = "Rules: Minimum length 8 characters, at least 1 special character, 1 uppercase and 1 lowercase letter and 1 number"; "settings_add_3pid_password_title_email" = "Add email address"; "settings_add_3pid_password_title_msidsn" = "Add phone number"; @@ -794,6 +803,7 @@ Tap the + to start adding people."; "room_details_access_section_anyone_apart_from_guest_for_dm"="Anyone who knows the link, apart from guests"; "room_details_access_section_anyone"="Anyone who knows the room's link, including guests"; "room_details_access_section_anyone_for_dm"="Anyone who knows the link, including guests"; +"room_details_access_section_access_toggle_bw" = "Public room"; "room_details_access_section_no_address_warning" = "To link to a room it must have an address"; "room_details_access_section_directory_toggle"="List this room in room directory"; "room_details_access_section_directory_toggle_for_dm"="List in room directory"; @@ -900,6 +910,7 @@ Tap the + to start adding people."; // Image picker "image_picker_action_camera" = "Take photo"; "image_picker_action_library" = "Choose from library"; +"image_picker_action_remove_photo" = "Remove photo"; // Directory "directory_title" = "Directory"; @@ -1008,7 +1019,7 @@ Tap the + to start adding people."; // Key backup wrong version "e2e_key_backup_wrong_version_title" = "New Key Backup"; -"e2e_key_backup_wrong_version" = "A new secure message key backup has been detected.\n\nIf this wasn’t you, set a new Security Phrase in Settings."; +"e2e_key_backup_wrong_version" = "A new secure message key backup has been detected.\n\nIf this wasn’t you, set a new recovery key in settings."; "e2e_key_backup_wrong_version_button_settings" = "Settings"; "e2e_key_backup_wrong_version_button_wasme" = "It was me"; @@ -1134,14 +1145,14 @@ Tap the + to start adding people."; // Intro -"secure_key_backup_setup_intro_title" = "Secure Backup"; -"secure_key_backup_setup_intro_info" = "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server."; +"secure_key_backup_setup_intro_title" = "Recovery Key"; +"secure_key_backup_setup_intro_info" = "To decrypt your messages when you login on this or any other device, assign a recovery key."; "secure_key_backup_setup_intro_use_security_key_title" = "Use a Security Key"; "secure_key_backup_setup_intro_use_security_key_info" = "Generate a security key to store somewhere safe like a password manager or a safe."; -"secure_key_backup_setup_intro_use_security_passphrase_title" = "Use a Security Phrase"; -"secure_key_backup_setup_intro_use_security_passphrase_info" = "Enter a secret phrase only you know, and generate a key for backup."; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "Recovery Key"; +"secure_key_backup_setup_intro_use_security_passphrase_info" = "Set up recovery key."; "secure_key_backup_setup_existing_backup_error_title" = "A backup for messages already exists"; "secure_key_backup_setup_existing_backup_error_info" = "Unlock it to reuse it in the secure backup or delete it to create a new messages backup in the secure backup."; @@ -1185,10 +1196,10 @@ Tap the + to start adding people."; "key_backup_setup_passphrase_passphrase_placeholder" = "Enter phrase"; "key_backup_setup_passphrase_passphrase_valid" = "Great!"; "key_backup_setup_passphrase_passphrase_invalid" = "Try adding a word"; -"key_backup_setup_passphrase_confirm_passphrase_title" = "Confirm"; -"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Confirm phrase"; +"key_backup_setup_passphrase_confirm_passphrase_title" = ""; +"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Recovery Key"; "key_backup_setup_passphrase_confirm_passphrase_valid" = "Great!"; -"key_backup_setup_passphrase_confirm_passphrase_invalid" = "phrase doesn’t match"; +"key_backup_setup_passphrase_confirm_passphrase_invalid" = "doesn’t match"; "key_backup_setup_passphrase_set_passphrase_action" = "Set Phrase"; "key_backup_setup_passphrase_setup_recovery_key_info" = "Or, secure your backup with a Security Key, saving it somewhere safe."; "key_backup_setup_passphrase_setup_recovery_key_action" = "(Advanced) Set up with Security Key"; @@ -1215,8 +1226,8 @@ Tap the + to start adding people."; "key_backup_recover_title" = "Secure Messages"; -"key_backup_recover_invalid_passphrase_title" = "Incorrect Security Phrase"; -"key_backup_recover_invalid_passphrase" = "Backup could not be decrypted with this phrase: please verify that you entered the correct Security Phrase."; +"key_backup_recover_invalid_passphrase_title" = "Incorrect Recovery Key"; +"key_backup_recover_invalid_passphrase" = "Backup could not be decrypted with this phrase: please verify that you entered the correct recovery key."; "key_backup_recover_invalid_recovery_key_title" = "Security Key Mismatch"; "key_backup_recover_invalid_recovery_key" = "Backup could not be decrypted with this key: please verify that you entered the correct Security Key."; @@ -1225,12 +1236,12 @@ Tap the + to start adding people."; // Recover from passphrase -"key_backup_recover_from_passphrase_info" = "Use your Security Phrase to unlock your secure message history"; +"key_backup_recover_from_passphrase_info" = "Use your recovery key to unlock your secure message history"; "key_backup_recover_from_passphrase_passphrase_title" = "Enter"; "key_backup_recover_from_passphrase_passphrase_placeholder" = "Enter Phrase"; "key_backup_recover_from_passphrase_recover_action" = "Unlock History"; -"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Don’t know your Security Phrase? You can "; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Don’t know your recovery key? You can "; "key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "use your Security Key"; "key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; @@ -1269,7 +1280,7 @@ Tap the + to start adding people."; // MARK: - Device Verification "key_verification_other_session_title" = "Verify session"; "key_verification_new_session_title" = "Verify your new session"; -"key_verification_this_session_title" = "Verify this session"; +"key_verification_this_session_title" = "Recovery Key"; "key_verification_user_title" = "Verify them"; "device_verification_security_advice_emoji" = "Compare the unique emoji, ensuring they appear in the same order."; "device_verification_security_advice_number" = "Compare the numbers, ensuring they appear in the same order."; @@ -1319,7 +1330,7 @@ Tap the + to start adding people."; "device_verification_self_verify_wait_information" = "Verify this session from one of your other sessions, granting it access to encrypted messages.\n\nUse the latest %@ on your other devices:"; "device_verification_self_verify_wait_additional_information" = "This works with %@ and other cross-signing capable Matrix clients."; "device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Use Security Key"; -"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Use Security Phrase or Key"; +"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Use recovery key or key"; "device_verification_self_verify_wait_recover_secrets_additional_information" = "If you can't access an existing session"; "device_verification_self_verify_wait_recover_secrets_checking_availability" = "Checking for other verification capabilities ..."; @@ -1351,13 +1362,13 @@ Tap the + to start adding people."; // Device -"device_verification_verified_title" = "Verified!"; -"device_verification_verified_got_it_button" = "Got it"; +"device_verification_verified_title" = "Completed!"; +"device_verification_verified_got_it_button" = "Finish"; "key_verification_verified_new_session_title" = "New session verified!"; "key_verification_verified_other_session_information" = "You can now read secure messages on your other session, and other users will know they can trust it."; "key_verification_verified_new_session_information" = "You can now read secure messages on your new device, and other users will know they can trust it."; -"key_verification_verified_this_session_information" = "You can now read secure messages on this device, and other users will know they can trust it."; +"key_verification_verified_this_session_information" = "Your chats have been successfully decrypted."; // User @@ -1549,31 +1560,31 @@ Tap the + to start adding people."; // MARK: - Secrets recovery -"secrets_recovery_reset_action_part_1" = "Forgot or lost all recovery options? "; +"secrets_recovery_reset_action_part_1" = "Forgot recovery key?"; "secrets_recovery_reset_action_part_2" = "Reset everything"; // Recover with passphrase -"secrets_recovery_with_passphrase_title" = "Security Phrase"; -"secrets_recovery_with_passphrase_information_default" = "Access your secure message history and your cross-signing identity for verifying other sessions by entering your Security Phrase."; -"secrets_recovery_with_passphrase_information_verify_device" = "Use your Security Phrase to verify this device."; -"secrets_recovery_with_passphrase_passphrase_title" = "Enter"; -"secrets_recovery_with_passphrase_passphrase_placeholder" = "Enter Security Phrase"; -"secrets_recovery_with_passphrase_recover_action" = "Use Phrase"; +"secrets_recovery_with_passphrase_title" = "Recovery Key"; +"secrets_recovery_with_passphrase_information_default" = "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key."; +"secrets_recovery_with_passphrase_information_verify_device" = "Enter your recovery key to decrypt all your messages."; +"secrets_recovery_with_passphrase_passphrase_title" = ""; +"secrets_recovery_with_passphrase_passphrase_placeholder" = "Recovery Key"; +"secrets_recovery_with_passphrase_recover_action" = "Next"; -"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Don’t know your Security Phrase? You can "; -"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "use your Security Key"; +"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Don’t know your recovery key? You can "; +"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "use your security key"; "secrets_recovery_with_passphrase_lost_passphrase_action_part3" = "."; "secrets_recovery_with_passphrase_invalid_passphrase_title" = "Unable to access secret storage"; -"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Please verify that you entered the correct Security Phrase."; +"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Please verify that you entered the correct recovery Key."; // Recover with key "secrets_recovery_with_key_title" = "Security Key"; "secrets_recovery_with_key_information_default" = "Access your secure message history and your cross-signing identity for verifying other sessions by entering your Security Key."; "secrets_recovery_with_key_information_verify_device" = "Use your Security Key to verify this device."; -"secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Enter your Security Phrase to continue."; +"secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Enter your recovery key to continue."; "secrets_recovery_with_key_information_unlock_secure_backup_with_key" = "Enter your Security Key to continue."; "secrets_recovery_with_key_recovery_key_title" = "Enter"; "secrets_recovery_with_key_recovery_key_placeholder" = "Enter Security Key"; @@ -1586,31 +1597,31 @@ Tap the + to start adding people."; // Recovery Key -"secrets_setup_recovery_key_title" = "Save your Security Key"; -"secrets_setup_recovery_key_information" = "Store your Security Key somewhere safe. It can be used to unlock your encrypted messages & data."; +"secrets_setup_recovery_key_title" = "Recovery Key"; +"secrets_setup_recovery_key_information" = "Completed!\n\nYour chats have een successfully decrypted."; "secrets_setup_recovery_key_loading" = "Loading…"; "secrets_setup_recovery_key_export_action" = "Save"; -"secrets_setup_recovery_key_done_action" = "Done"; +"secrets_setup_recovery_key_done_action" = "Finish"; "secrets_setup_recovery_key_storage_alert_title" = "Keep it safe"; "secrets_setup_recovery_key_storage_alert_message" = "βœ“ Print it and store it somewhere safe\nβœ“ Save it on a USB key or backup drive\nβœ“ Copy it to your personal cloud storage"; // Recovery passphrase +"secrets_setup_recovery_passphrase_additional_information" = "⚠️ Do not use your account password."; "secrets_setup_recovery_passphrase_title" = "Set a Security Phrase"; "secrets_setup_recovery_passphrase_information" = "Enter a security phrase only you know, used to secure secrets on your server."; -"secrets_setup_recovery_passphrase_additional_information" = "Don't use your Matrix account password."; "secrets_setup_recovery_passphrase_validate_action" = "Done"; -"secrets_setup_recovery_passphrase_confirm_information" = "Enter your Security Phrase again to confirm it."; -"secrets_setup_recovery_passphrase_confirm_passphrase_title" = "Confirm"; -"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Confirm phrase"; +"secrets_setup_recovery_passphrase_confirm_information" = "Enter your recovery key again to confirm it."; +"secrets_setup_recovery_passphrase_confirm_passphrase_title" = ""; +"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Recovery Key"; "key_backup_setup_passphrase_confirm_passphrase_valid" = "Great!"; "key_backup_setup_passphrase_confirm_passphrase_invalid" = "Phrase doesn’t match"; -"secrets_setup_recovery_passphrase_summary_title" = "Save your Security Phrase"; -"secrets_setup_recovery_passphrase_summary_information" = "Remember your Security Phrase. It can be used to unlock your encrypted messages & data."; +"secrets_setup_recovery_passphrase_summary_title" = "Save your recovery key"; +"secrets_setup_recovery_passphrase_summary_information" = "Remember your recovery key. It can be used to unlock your encrypted messages & data."; // MARK: - Secrets reset @@ -1619,7 +1630,7 @@ Tap the + to start adding people."; "secrets_reset_warning_title" = "If you reset everything"; "secrets_reset_warning_message" = "You will restart with no history, no messages, trusted devices or trusted users."; "secrets_reset_reset_action" = "Reset"; -"secrets_reset_authentication_message" = "Enter your Matrix account password to confirm"; +"secrets_reset_authentication_message" = "Enter your account password to confirm"; // MARK: - Cross-signing @@ -1651,6 +1662,8 @@ Tap the + to start adding people."; "pin_protection_mismatch_error_title" = "PINs don't match"; "pin_protection_mismatch_error_message" = "Please try again"; "pin_protection_mismatch_too_many_times_error_message" = "If you can't remember your PIN, tap the forgot PIN button."; + +"pin_protection_settings_section_header_x" = "PIN"; "pin_protection_settings_section_header" = "PIN"; "pin_protection_settings_section_header_with_biometrics" = "PIN & %@"; "pin_protection_settings_section_footer" = "To reset your PIN, you'll need to re-login and create a new one."; @@ -1678,12 +1691,15 @@ Tap the + to start adding people."; "biometrics_cant_unlocked_alert_message_x" = "To unlock, use %@ or log back in and enable %@ again"; "biometrics_cant_unlocked_alert_message_login" = "Log back in"; "biometrics_cant_unlocked_alert_message_retry" = "Retry"; +"biometrics_mode_cant_unlock_button_title" = "PIN eingeben"; + // MARK: - Searchable Directory View Controller "searchable_directory_create_new_room" = "Create a new room"; "searchable_directory_x_network" = "%@ Network"; -"searchable_directory_search_placeholder" = "Name or ID"; +"searchable_directory_search_placeholder" = "Roomname"; +"searchable_directory_room_title" = "Room directory"; // MARK: - Create Room @@ -1703,6 +1719,16 @@ Tap the + to start adding people."; "create_room_section_header_address" = "Room address"; "create_room_placeholder_address" = "#testroom:matrix.org"; +// MARK: - Jailbreak + +"jailbreak_alert_title" = "System Requirements"; +"jailbreak_alert_message" = "%@ is only supported on unmodified versions of iOS."; + +// MARK: - Integrity + +"integrity_alert_title" = "Integrity Breach"; +"integrity_alert_message" = "Integrity check failed. You need to sign out or close and reinstall the application."; + // MARK: - Room Info "room_info_list_one_member" = "1 member"; @@ -1741,6 +1767,20 @@ Tap the + to start adding people."; "favourites_empty_view_title" = "Favourite rooms and people"; "favourites_empty_view_information" = "You can favourite a few ways - the quickest is just to press and hold. Tap the star and they’ll automatically appear here for safe keeping."; +// MARK: - Splash Screen + +"splash_screen_title" = "Your Messenger for our country"; +"splash_screen_first_info" = "Chats with comrades and colleagues"; +"splash_screen_second_info" = "Secure transfer of media and files"; +"splash_screen_third_info" = "Always encrypted"; +"splash_screen_start" = "Let go"; + +// MARK: Clients +"client_desktop_name" = "BundesMessenger Desktop"; +"client_web_name" = "BundesMessenger Web"; +"client_ios_name" = "BundesMessenger iOS"; +"client_android_name" = "BundesMessenger Android"; + // MARK: - Invite friends "invite_friends_action" = "Invite friends to %@"; @@ -1762,7 +1802,7 @@ Tap the + to start adding people."; "room_intro_cell_information_room_without_topic_sentence2_part1" = "Add a topic"; "room_intro_cell_information_room_without_topic_sentence2_part2" = " to let people know what this room is about."; -"room_intro_cell_information_dm_sentence1_part1" = "This is the beginning of your direct message with "; +"room_intro_cell_information_dm_sentence1_part1" = "This is the beginning of your conversation with "; "room_intro_cell_information_dm_sentence1_part3" = ". "; "room_intro_cell_information_dm_sentence2" = "Only the two of you are in this conversation, no one else can join."; @@ -1779,6 +1819,16 @@ Tap the + to start adding people."; "space_beta_announce_subtitle" = "The new version of communities"; "space_beta_announce_information" = "Spaces are a new way to group rooms and people. They’re not on iOS yet, but you can use them now on Web and Desktop."; +// MARK - Rest Time bwi +"settings_enable_rest_time" = "Off-time"; +"room_details_main_section_rest_time" = "Off-time"; +"room_details_main_section_rest_time_global_disabled" = "Activate the off-time setting in the preferences"; +"room_details_main_section_rest_time_global" = ""; +"room_details_main_section_rest_time_locally_enabled" = ""; +"room_details_main_section_rest_time_locally_disabled" = ""; +"room_details_main_section_rest_time_restday" = "Day off today"; +"room_details_main_section_rest_time_time" = "%@ - %@"; + "spaces_home_space_title" = "Home"; "spaces_left_panel_title" = "Spaces"; "leave_space_title" = "Leave %@"; @@ -1830,7 +1880,57 @@ Tap the + to start adding people."; "voice_message_remaining_recording_time" = "%@s left"; "voice_message_stop_locked_mode_recording" = "Tap on your recording to stop or listen"; "voice_message_lock_screen_placeholder" = "Voice message"; +"voice_message_slide_to_cancel" = "Slide to cancel"; +// developer menu +"bwi_settings_developer" = "Developer"; +"bwi_settings_show_developer_settings" = "Settings"; +"bwi_settings_developer_create_new_personal_notes_room" = "Create new note-taking room"; +"bwi_settings_developer_reset_matomo_info" = "Reset Matomo Info Screen"; +"bwi_settings_developer_settings_reset_birthday_banner" = "Reset birthday banner"; +"bwi_settings_developer_new_personal_notes_room_created" = "The existing note-taking room was replaced by a new one."; +"bwi_settings_developer_show_matomo_privacy_notes_resetted" = "The matomo info screen will be shown again."; + +// personal notes room +"bwi_personal_notes_beginning_notes_room" = "This is your private note-taking room. You are the only one who can access the contents in this room."; +"bwi_personal_notes_room_profile_encrypted_subtitle" = "Messages here are end-to-end encrypted."; +"bwi_personal_notes_hint_message" = "You now have a private room for your notes and files. Click to open the room."; +"bwi_settings_enable_notes_room_title" = "My notes"; +"bwi_settings_enable_notes_room_summary" = "how personal notes room."; +"bwi_notes_room_title" = "My notes"; + +// rolls and rights +"bwi_rolls_and_rights" = "Roles and Permissions"; +"bwi_rolls_and_rights_main_title" = "Permissions"; +"bwi_rolls_and_rights_main_header_admin" = "Permissions the user must have to interact with the room"; +"bwi_rolls_and_rights_main_header_no_admin" = "You don't have permission to update the roles required to change various parts of the room"; +"bwi_rolls_and_rights_general" = "Change room profile (title, topic, avatar)"; +"bwi_rolls_and_rights_send" = "Send messages"; +"bwi_rolls_and_rights_invite" = "Invite users"; +"bwi_rolls_and_rights_redact" = "Delete messages"; +"bwi_rolls_and_rights_ban" = "Remove members (kick, ban)"; +"bwi_rolls_and_rights_admin" = "Admin"; +"bwi_rolls_and_rights_moderator" = "Moderator"; +"bwi_rolls_and_rights_user" = "Default"; +"bwi_rolls_and_rights_reset_button" = "Reset to default"; +"bwi_rolls_and_rights_reset_title" = "Are you sure that you want to reset the permission settings?"; +"bwi_rolls_and_rights_reset_cancel" = "Cancel"; +"bwi_rolls_and_rights_reset_ok" = "Reset"; + +// rolls and rights +"bwi_rolls_and_rights" = "Rolls and Rights"; + +// Time Line settings +"settings_enable_simple_time_line" = "Show timeline events"; +"settings_enable_time_stamp" = "Show time stamps"; +"settings_enable_deleted_messages" = "Show deleted messages"; +"settings_enable_chat_effects" = "Show chat effects"; +"settings_enable_room_avatar" = "Show room events"; +"settings_enable_user_avatar" = "Show account events"; +"settings_enable_enter_room" = "Show user events"; + +// Last Admin Downgrade +"room_member_details_downgrade_last_admin" = "You must first assign another admin before you are allowed to downgrade yourself."; // Mark: - Version check "version_check_banner_title_supported" = "We’re ending support for iOS %@"; @@ -1847,10 +1947,30 @@ Tap the + to start adding people."; "version_check_modal_subtitle_deprecated" = "We've been working on enhancing %@ for a faster and more polished experience. Unfortunately your current version of iOS is not compatible with some of those fixes and is no longer supported.\nWe're advising you to upgrade your operating system to use %@ to its full potential."; "version_check_modal_action_title_deprecated" = "Find out how"; +// Mark: bwiRegistration + +"bwi_auth_register_button_title" = "Create account"; +"bwi_auth_register_alert_title" = "Create account"; + +// Mark: bwi error codes + +"bwi_error_invite_already_in_room" = "%@ is already in the room."; +"bwi_error_invite_banned_in_room" = "%@ is banned from the room."; +"bwi_error_invite_general" = "%@ could not be invited."; + +/* BWI: Matomo */ +"MATOMO_USER_INFO" = "To detect problems much easier and improve the app we need your support.\nThis app collects analytics data.\nDetails about this can be found in our privacy policy."; +"MATOMO_USER_INFO_BUTTON_OK" = "OK"; +"MATOMO_USER_INFO_BUTTON_MORE_INFO" = "Privacy Police"; +"MATOMO_SETTINGS_SEND_DIAGNOSTIC_DATA" = "Send analyze data"; + +/* BWI: New Features */ +"bwi_settings_new_features_header" = "New Features"; +"bwi_settings_new_features_show_features" = "Show new features"; + // Mark: - Polls "poll_edit_form_create_poll" = "Create poll"; - "poll_edit_form_poll_type" = "Poll type"; "poll_edit_form_poll_question_or_topic" = "Poll question or topic"; diff --git a/Riot/Assets/new_features.html b/Riot/Assets/new_features.html new file mode 100644 index 000000000..353c36768 --- /dev/null +++ b/Riot/Assets/new_features.html @@ -0,0 +1,35 @@ + + + + + + +

+
+ + + + diff --git a/Riot/Assets/support.html b/Riot/Assets/support.html new file mode 100755 index 000000000..93c9e7680 --- /dev/null +++ b/Riot/Assets/support.html @@ -0,0 +1,38 @@ + + + + + +
+

BundesMessenger

+

Hilfe & Support

+
+ + +
+ + + diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index 98fde6a11..b5faae361 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -1,6 +1,7 @@ /* Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -84,7 +85,7 @@ NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = [self addTimestampLabelForComponentIndex:componentIndex isFirstDisplayedComponent:isFirstDisplayedComponent viewTag:componentIndex - displayOnLeft:displayLabelOnLeft]; + displayOnLeft:/*displayLabelOnLeft*/false]; } } @@ -152,7 +153,8 @@ NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = timeLabelTextAlignment = NSTextAlignmentRight; } - timeLabelPosY = MAX(0.0, timeLabelPosY); + // Set Y-position zero to get the timestanp on all messagezero to the upper position + timeLabelPosY = BuildSettings.enableLastMessageTimestamp ? MAX(0.0, timeLabelPosY) : 0; UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(timeLabelPosX, timeLabelPosY, timeLabelWidth, timeLabelHeight)]; @@ -168,6 +170,8 @@ NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = timeLabel.accessibilityIdentifier = @"timestampLabel"; [self.bubbleInfoContainer addSubview:timeLabel]; + timeLabel.hidden = !RiotSettings.shared.settingTimeStampEnabled; + // Define timeLabel constraints (to handle auto-layout in case of screen rotation) NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:timeLabel attribute:NSLayoutAttributeTrailing diff --git a/Riot/Categories/MXSession+Riot.m b/Riot/Categories/MXSession+Riot.m index 97a74e5b2..c74035cfd 100644 --- a/Riot/Categories/MXSession+Riot.m +++ b/Riot/Categories/MXSession+Riot.m @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -73,6 +74,12 @@ - (BOOL)vc_canSetupSecureBackup { + if (!self.crypto) + { + // Crypto module not yet ready + return NO; + } + MXRecoveryService *recoveryService = self.crypto.recoveryService; if (recoveryService.hasRecovery) diff --git a/Riot/Categories/UIView.swift b/Riot/Categories/UIView.swift index 47b99e93d..2f3c43b04 100644 --- a/Riot/Categories/UIView.swift +++ b/Riot/Categories/UIView.swift @@ -1,5 +1,6 @@ /* Copyright 2019 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -65,6 +66,15 @@ extension UIView { layer.add(shake, forKey: "position") } + /// set visibility of specific view + func visiblity(gone: Bool, dimension: CGFloat = 0.0, attribute: NSLayoutConstraint.Attribute = .height) -> Void { + if let constraint = (self.constraints.filter{$0.firstAttribute == attribute}.first) { + constraint.constant = gone ? 0.0 : dimension + self.layoutIfNeeded() + self.isHidden = gone + } + } + @objc func vc_setupAccessibilityTraitsButton(withTitle title: String, hint: String, isEnabled: Bool) { self.isAccessibilityElement = true self.accessibilityLabel = title diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index eca6c861e..6b5ef33f4 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -28,6 +28,9 @@ internal enum Asset { internal static let socialLoginButtonGitlab = ImageAsset(name: "social_login_button_gitlab") internal static let socialLoginButtonGoogle = ImageAsset(name: "social_login_button_google") internal static let socialLoginButtonTwitter = ImageAsset(name: "social_login_button_twitter") + internal static let birthdayCake = ImageAsset(name: "birthday_cake") + internal static let fileAttachmentIcon = ImageAsset(name: "file_attachment_icon") + internal static let fileScanInfected = ImageAsset(name: "file_scan_infected") internal static let callAudioMuteOffIcon = ImageAsset(name: "call_audio_mute_off_icon") internal static let callAudioMuteOnIcon = ImageAsset(name: "call_audio_mute_on_icon") internal static let callAudioRouteBuiltin = ImageAsset(name: "call_audio_route_builtin") @@ -53,6 +56,7 @@ internal enum Asset { internal static let callkitIcon = ImageAsset(name: "callkit_icon") internal static let cameraSwitch = ImageAsset(name: "camera_switch") internal static let appSymbol = ImageAsset(name: "app_symbol") + internal static let attachmentGallery = ImageAsset(name: "attachment_gallery") internal static let backIcon = ImageAsset(name: "back_icon") internal static let camera = ImageAsset(name: "camera") internal static let checkmark = ImageAsset(name: "checkmark") @@ -62,12 +66,18 @@ internal enum Asset { internal static let errorIcon = ImageAsset(name: "error_icon") internal static let faceidIcon = ImageAsset(name: "faceid_icon") internal static let group = ImageAsset(name: "group") + internal static let hidePasswordButton = ImageAsset(name: "hide_password_button") internal static let informationButton = ImageAsset(name: "information_button") + internal static let lock = ImageAsset(name: "lock") + internal static let messageCircle = ImageAsset(name: "message_circle") internal static let monitor = ImageAsset(name: "monitor") internal static let placeholder = ImageAsset(name: "placeholder") internal static let plusIcon = ImageAsset(name: "plus_icon") + internal static let poweredByBWIBlk = ImageAsset(name: "powered_by_BWI_blk") + internal static let poweredByBWIGry = ImageAsset(name: "powered_by_BWI_gry") internal static let removeIcon = ImageAsset(name: "remove_icon") internal static let revealPasswordButton = ImageAsset(name: "reveal_password_button") + internal static let roomCheckbox = ImageAsset(name: "room_checkbox") internal static let selectionTick = ImageAsset(name: "selection_tick") internal static let selectionUntick = ImageAsset(name: "selection_untick") internal static let shareActionButton = ImageAsset(name: "share_action_button") @@ -94,6 +104,7 @@ internal enum Asset { internal static let roomActionNotificationMuted = ImageAsset(name: "room_action_notification_muted") internal static let roomActionPriorityHigh = ImageAsset(name: "room_action_priority_high") internal static let roomActionPriorityLow = ImageAsset(name: "room_action_priority_low") + internal static let roomActionResttime = ImageAsset(name: "room_action_resttime") internal static let homeEmptyScreenArtwork = ImageAsset(name: "home_empty_screen_artwork") internal static let homeEmptyScreenArtworkDark = ImageAsset(name: "home_empty_screen_artwork_dark") internal static let plusFloatingAction = ImageAsset(name: "plus_floating_action") @@ -121,6 +132,7 @@ internal enum Asset { internal static let peopleEmptyScreenArtwork = ImageAsset(name: "people_empty_screen_artwork") internal static let peopleEmptyScreenArtworkDark = ImageAsset(name: "people_empty_screen_artwork_dark") internal static let peopleFloatingAction = ImageAsset(name: "people_floating_action") + internal static let personalNotesAvatar = ImageAsset(name: "personal_notes_avatar") internal static let actionCamera = ImageAsset(name: "action_camera") internal static let actionFile = ImageAsset(name: "action_file") internal static let actionLocation = ImageAsset(name: "action_location") @@ -194,6 +206,7 @@ internal enum Asset { internal static let modIcon = ImageAsset(name: "mod_icon") internal static let moreReactions = ImageAsset(name: "more_reactions") internal static let notifications = ImageAsset(name: "notifications") + internal static let rollsAndRights = ImageAsset(name: "rolls_and_rights") internal static let scrollup = ImageAsset(name: "scrollup") internal static let roomsEmptyScreenArtwork = ImageAsset(name: "rooms_empty_screen_artwork") internal static let roomsEmptyScreenArtworkDark = ImageAsset(name: "rooms_empty_screen_artwork_dark") diff --git a/Riot/Generated/RiotDefaults.swift b/Riot/Generated/RiotDefaults.swift index 793d63662..8d4c2d275 100644 --- a/Riot/Generated/RiotDefaults.swift +++ b/Riot/Generated/RiotDefaults.swift @@ -13,6 +13,7 @@ internal enum RiotDefaults { private static let _document = PlistDocument(path: "Riot-Defaults.plist") internal static let enableBotCreation: Bool = _document["enableBotCreation"] + internal static let enableCrashReport: Bool = _document["enableCrashReport"] internal static let enableRageShake: Bool = _document["enableRageShake"] internal static let enableRingingForGroupCalls: Bool = _document["enableRingingForGroupCalls"] internal static let matrixApps: Bool = _document["matrixApps"] diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 168c6e544..68edcd1be 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -131,11 +131,11 @@ public class VectorL10n: NSObject { public static var authEmailValidationMessage: String { return VectorL10n.tr("Vector", "auth_email_validation_message") } - /// Forgot Matrix account password? + /// Forgot password? public static var authForgotPassword: String { return VectorL10n.tr("Vector", "auth_forgot_password") } - /// No identity server is configured: add one to reset your Matrix account password. + /// No identity server is configured: add one to reset your password. public static var authForgotPasswordErrorNoConfiguredIdentityServer: String { return VectorL10n.tr("Vector", "auth_forgot_password_error_no_configured_identity_server") } @@ -355,7 +355,7 @@ public class VectorL10n: NSObject { public static var authUseServerOptions: String { return VectorL10n.tr("Vector", "auth_use_server_options") } - /// Email or user name + /// User name public static var authUserIdPlaceholder: String { return VectorL10n.tr("Vector", "auth_user_id_placeholder") } @@ -399,6 +399,10 @@ public class VectorL10n: NSObject { public static func biometricsDesetupTitleX(_ p1: String) -> String { return VectorL10n.tr("Vector", "biometrics_desetup_title_x", p1) } + /// PIN eingeben + public static var biometricsModeCantUnlockButtonTitle: String { + return VectorL10n.tr("Vector", "biometrics_mode_cant_unlock_button_title") + } /// Face ID public static var biometricsModeFaceId: String { return VectorL10n.tr("Vector", "biometrics_mode_face_id") @@ -475,6 +479,154 @@ public class VectorL10n: NSObject { public static var bugReportTitle: String { return VectorL10n.tr("Vector", "bug_report_title") } + /// If you have forgotton your password, contact your pool manager or go to the SelfServicePortal at IntranetBw and reset your password. + public static var bwiAuthForgotPasswordAlertText: String { + return VectorL10n.tr("Vector", "bwi_auth_forgot_password_alert_text") + } + /// Create account + public static var bwiAuthRegisterAlertTitle: String { + return VectorL10n.tr("Vector", "bwi_auth_register_alert_title") + } + /// Create account + public static var bwiAuthRegisterButtonTitle: String { + return VectorL10n.tr("Vector", "bwi_auth_register_button_title") + } + /// %@ is already in the room. + public static func bwiErrorInviteAlreadyInRoom(_ p1: String) -> String { + return VectorL10n.tr("Vector", "bwi_error_invite_already_in_room", p1) + } + /// %@ is banned from the room. + public static func bwiErrorInviteBannedInRoom(_ p1: String) -> String { + return VectorL10n.tr("Vector", "bwi_error_invite_banned_in_room", p1) + } + /// %@ could not be invited. + public static func bwiErrorInviteGeneral(_ p1: String) -> String { + return VectorL10n.tr("Vector", "bwi_error_invite_general", p1) + } + /// My notes + public static var bwiNotesRoomTitle: String { + return VectorL10n.tr("Vector", "bwi_notes_room_title") + } + /// This is your private note-taking room. You are the only one who can access the contents in this room. + public static var bwiPersonalNotesBeginningNotesRoom: String { + return VectorL10n.tr("Vector", "bwi_personal_notes_beginning_notes_room") + } + /// You now have a private room for your notes and files. Click to open the room. + public static var bwiPersonalNotesHintMessage: String { + return VectorL10n.tr("Vector", "bwi_personal_notes_hint_message") + } + /// Messages here are end-to-end encrypted. + public static var bwiPersonalNotesRoomProfileEncryptedSubtitle: String { + return VectorL10n.tr("Vector", "bwi_personal_notes_room_profile_encrypted_subtitle") + } + /// Rolls and Rights + public static var bwiRollsAndRights: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights") + } + /// Admin + public static var bwiRollsAndRightsAdmin: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_admin") + } + /// Remove members (kick, ban) + public static var bwiRollsAndRightsBan: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_ban") + } + /// Change room profile (title, topic, avatar) + public static var bwiRollsAndRightsGeneral: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_general") + } + /// Invite users + public static var bwiRollsAndRightsInvite: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_invite") + } + /// Permissions the user must have to interact with the room + public static var bwiRollsAndRightsMainHeaderAdmin: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_main_header_admin") + } + /// You don't have permission to update the roles required to change various parts of the room + public static var bwiRollsAndRightsMainHeaderNoAdmin: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_main_header_no_admin") + } + /// Permissions + public static var bwiRollsAndRightsMainTitle: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_main_title") + } + /// Moderator + public static var bwiRollsAndRightsModerator: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_moderator") + } + /// Delete messages + public static var bwiRollsAndRightsRedact: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_redact") + } + /// Reset to default + public static var bwiRollsAndRightsResetButton: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_reset_button") + } + /// Cancel + public static var bwiRollsAndRightsResetCancel: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_reset_cancel") + } + /// Reset + public static var bwiRollsAndRightsResetOk: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_reset_ok") + } + /// Are you sure that you want to reset the permission settings? + public static var bwiRollsAndRightsResetTitle: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_reset_title") + } + /// Send messages + public static var bwiRollsAndRightsSend: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_send") + } + /// Default + public static var bwiRollsAndRightsUser: String { + return VectorL10n.tr("Vector", "bwi_rolls_and_rights_user") + } + /// Developer + public static var bwiSettingsDeveloper: String { + return VectorL10n.tr("Vector", "bwi_settings_developer") + } + /// Create new note-taking room + public static var bwiSettingsDeveloperCreateNewPersonalNotesRoom: String { + return VectorL10n.tr("Vector", "bwi_settings_developer_create_new_personal_notes_room") + } + /// The existing note-taking room was replaced by a new one. + public static var bwiSettingsDeveloperNewPersonalNotesRoomCreated: String { + return VectorL10n.tr("Vector", "bwi_settings_developer_new_personal_notes_room_created") + } + /// Reset Matomo Info Screen + public static var bwiSettingsDeveloperResetMatomoInfo: String { + return VectorL10n.tr("Vector", "bwi_settings_developer_reset_matomo_info") + } + /// Reset birthday banner + public static var bwiSettingsDeveloperSettingsResetBirthdayBanner: String { + return VectorL10n.tr("Vector", "bwi_settings_developer_settings_reset_birthday_banner") + } + /// The matomo info screen will be shown again. + public static var bwiSettingsDeveloperShowMatomoPrivacyNotesResetted: String { + return VectorL10n.tr("Vector", "bwi_settings_developer_show_matomo_privacy_notes_resetted") + } + /// how personal notes room. + public static var bwiSettingsEnableNotesRoomSummary: String { + return VectorL10n.tr("Vector", "bwi_settings_enable_notes_room_summary") + } + /// My notes + public static var bwiSettingsEnableNotesRoomTitle: String { + return VectorL10n.tr("Vector", "bwi_settings_enable_notes_room_title") + } + /// New Features + public static var bwiSettingsNewFeaturesHeader: String { + return VectorL10n.tr("Vector", "bwi_settings_new_features_header") + } + /// Show new features + public static var bwiSettingsNewFeaturesShowFeatures: String { + return VectorL10n.tr("Vector", "bwi_settings_new_features_show_features") + } + /// Settings + public static var bwiSettingsShowDeveloperSettings: String { + return VectorL10n.tr("Vector", "bwi_settings_show_developer_settings") + } /// Resume public static var callActionsUnhold: String { return VectorL10n.tr("Vector", "call_actions_unhold") @@ -591,6 +743,22 @@ public class VectorL10n: NSObject { public static var cancel: String { return VectorL10n.tr("Vector", "cancel") } + /// BundesMessenger Android + public static var clientAndroidName: String { + return VectorL10n.tr("Vector", "client_android_name") + } + /// BundesMessenger Desktop + public static var clientDesktopName: String { + return VectorL10n.tr("Vector", "client_desktop_name") + } + /// BundesMessenger iOS + public static var clientIosName: String { + return VectorL10n.tr("Vector", "client_ios_name") + } + /// BundesMessenger Web + public static var clientWebName: String { + return VectorL10n.tr("Vector", "client_web_name") + } /// Close public static var close: String { return VectorL10n.tr("Vector", "close") @@ -1099,7 +1267,7 @@ public class VectorL10n: NSObject { public static var deviceVerificationSelfVerifyWaitRecoverSecretsCheckingAvailability: String { return VectorL10n.tr("Vector", "device_verification_self_verify_wait_recover_secrets_checking_availability") } - /// Use Security Phrase or Key + /// Use recovery key or key public static var deviceVerificationSelfVerifyWaitRecoverSecretsWithPassphrase: String { return VectorL10n.tr("Vector", "device_verification_self_verify_wait_recover_secrets_with_passphrase") } @@ -1131,11 +1299,11 @@ public class VectorL10n: NSObject { public static var deviceVerificationStartWaitPartner: String { return VectorL10n.tr("Vector", "device_verification_start_wait_partner") } - /// Got it + /// Finish public static var deviceVerificationVerifiedGotItButton: String { return VectorL10n.tr("Vector", "device_verification_verified_got_it_button") } - /// Verified! + /// Completed! public static var deviceVerificationVerifiedTitle: String { return VectorL10n.tr("Vector", "device_verification_verified_title") } @@ -1211,7 +1379,7 @@ public class VectorL10n: NSObject { public static func e2eEnablingOnAppUpdate(_ p1: String) -> String { return VectorL10n.tr("Vector", "e2e_enabling_on_app_update", p1) } - /// A new secure message key backup has been detected.\n\nIf this wasn’t you, set a new Security Phrase in Settings. + /// A new secure message key backup has been detected.\n\nIf this wasn’t you, set a new recovery key in settings. public static var e2eKeyBackupWrongVersion: String { return VectorL10n.tr("Vector", "e2e_key_backup_wrong_version") } @@ -1735,6 +1903,18 @@ public class VectorL10n: NSObject { public static var imagePickerActionLibrary: String { return VectorL10n.tr("Vector", "image_picker_action_library") } + /// Remove photo + public static var imagePickerActionRemovePhoto: String { + return VectorL10n.tr("Vector", "image_picker_action_remove_photo") + } + /// Integrity check failed. You need to sign out or close and reinstall the application. + public static var integrityAlertMessage: String { + return VectorL10n.tr("Vector", "integrity_alert_message") + } + /// Integrity Breach + public static var integrityAlertTitle: String { + return VectorL10n.tr("Vector", "integrity_alert_title") + } /// Invite public static var invite: String { return VectorL10n.tr("Vector", "invite") @@ -1747,6 +1927,14 @@ public class VectorL10n: NSObject { public static func inviteFriendsShareText(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "invite_friends_share_text", p1, p2) } + /// %@ is only supported on unmodified versions of iOS. + public static func jailbreakAlertMessage(_ p1: String) -> String { + return VectorL10n.tr("Vector", "jailbreak_alert_message", p1) + } + /// System Requirements + public static var jailbreakAlertTitle: String { + return VectorL10n.tr("Vector", "jailbreak_alert_title") + } /// Join public static var join: String { return VectorL10n.tr("Vector", "join") @@ -1759,11 +1947,11 @@ public class VectorL10n: NSObject { public static var keyBackupRecoverDoneAction: String { return VectorL10n.tr("Vector", "key_backup_recover_done_action") } - /// Use your Security Phrase to unlock your secure message history + /// Use your recovery key to unlock your secure message history public static var keyBackupRecoverFromPassphraseInfo: String { return VectorL10n.tr("Vector", "key_backup_recover_from_passphrase_info") } - /// Don’t know your Security Phrase? You can + /// Don’t know your recovery key? You can public static var keyBackupRecoverFromPassphraseLostPassphraseActionPart1: String { return VectorL10n.tr("Vector", "key_backup_recover_from_passphrase_lost_passphrase_action_part1") } @@ -1811,11 +1999,11 @@ public class VectorL10n: NSObject { public static var keyBackupRecoverFromRecoveryKeyRecoveryKeyTitle: String { return VectorL10n.tr("Vector", "key_backup_recover_from_recovery_key_recovery_key_title") } - /// Backup could not be decrypted with this phrase: please verify that you entered the correct Security Phrase. + /// Backup could not be decrypted with this phrase: please verify that you entered the correct recovery key. public static var keyBackupRecoverInvalidPassphrase: String { return VectorL10n.tr("Vector", "key_backup_recover_invalid_passphrase") } - /// Incorrect Security Phrase + /// Incorrect Recovery Key public static var keyBackupRecoverInvalidPassphraseTitle: String { return VectorL10n.tr("Vector", "key_backup_recover_invalid_passphrase_title") } @@ -1863,11 +2051,11 @@ public class VectorL10n: NSObject { public static var keyBackupSetupPassphraseConfirmPassphraseInvalid: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_confirm_passphrase_invalid") } - /// Confirm phrase + /// Recovery Key public static var keyBackupSetupPassphraseConfirmPassphrasePlaceholder: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_confirm_passphrase_placeholder") } - /// Confirm + /// public static var keyBackupSetupPassphraseConfirmPassphraseTitle: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_confirm_passphrase_title") } @@ -2059,7 +2247,7 @@ public class VectorL10n: NSObject { public static var keyVerificationSelfVerifyUnverifiedSessionsAlertValidateAction: String { return VectorL10n.tr("Vector", "key_verification_self_verify_unverified_sessions_alert_validate_action") } - /// Verify this session + /// Recovery Key public static var keyVerificationThisSessionTitle: String { return VectorL10n.tr("Vector", "key_verification_this_session_title") } @@ -2127,7 +2315,7 @@ public class VectorL10n: NSObject { public static var keyVerificationVerifiedOtherSessionInformation: String { return VectorL10n.tr("Vector", "key_verification_verified_other_session_information") } - /// You can now read secure messages on this device, and other users will know they can trust it. + /// Your chats have been successfully decrypted. public static var keyVerificationVerifiedThisSessionInformation: String { return VectorL10n.tr("Vector", "key_verification_verified_this_session_information") } @@ -2331,6 +2519,22 @@ public class VectorL10n: NSObject { public static var manageSessionTrusted: String { return VectorL10n.tr("Vector", "manage_session_trusted") } + /// Send analyze data + public static var matomoSettingsSendDiagnosticData: String { + return VectorL10n.tr("Vector", "MATOMO_SETTINGS_SEND_DIAGNOSTIC_DATA") + } + /// To detect problems much easier and improve the app we need your support.\nThis app collects analytics data.\nDetails about this can be found in our privacy policy. + public static var matomoUserInfo: String { + return VectorL10n.tr("Vector", "MATOMO_USER_INFO") + } + /// Privacy Police + public static var matomoUserInfoButtonMoreInfo: String { + return VectorL10n.tr("Vector", "MATOMO_USER_INFO_BUTTON_MORE_INFO") + } + /// OK + public static var matomoUserInfoButtonOk: String { + return VectorL10n.tr("Vector", "MATOMO_USER_INFO_BUTTON_OK") + } /// Library public static var mediaPickerLibrary: String { return VectorL10n.tr("Vector", "media_picker_library") @@ -2567,6 +2771,10 @@ public class VectorL10n: NSObject { public static func pinProtectionSettingsSectionHeaderWithBiometrics(_ p1: String) -> String { return VectorL10n.tr("Vector", "pin_protection_settings_section_header_with_biometrics", p1) } + /// PIN + public static var pinProtectionSettingsSectionHeaderX: String { + return VectorL10n.tr("Vector", "pin_protection_settings_section_header_x") + } /// Add option public static var pollEditFormAddOption: String { return VectorL10n.tr("Vector", "poll_edit_form_add_option") @@ -2871,6 +3079,10 @@ public class VectorL10n: NSObject { public static var roomDetailsAccessSection: String { return VectorL10n.tr("Vector", "room_details_access_section") } + /// Public room + public static var roomDetailsAccessSectionAccessToggleBw: String { + return VectorL10n.tr("Vector", "room_details_access_section_access_toggle_bw") + } /// Anyone who knows the room's link, including guests public static var roomDetailsAccessSectionAnyone: String { return VectorL10n.tr("Vector", "room_details_access_section_anyone") @@ -3091,6 +3303,34 @@ public class VectorL10n: NSObject { public static var roomDetailsLowPriorityTag: String { return VectorL10n.tr("Vector", "room_details_low_priority_tag") } + /// Off-time + public static var roomDetailsMainSectionRestTime: String { + return VectorL10n.tr("Vector", "room_details_main_section_rest_time") + } + /// + public static var roomDetailsMainSectionRestTimeGlobal: String { + return VectorL10n.tr("Vector", "room_details_main_section_rest_time_global") + } + /// Activate the off-time setting in the preferences + public static var roomDetailsMainSectionRestTimeGlobalDisabled: String { + return VectorL10n.tr("Vector", "room_details_main_section_rest_time_global_disabled") + } + /// + public static var roomDetailsMainSectionRestTimeLocallyDisabled: String { + return VectorL10n.tr("Vector", "room_details_main_section_rest_time_locally_disabled") + } + /// + public static var roomDetailsMainSectionRestTimeLocallyEnabled: String { + return VectorL10n.tr("Vector", "room_details_main_section_rest_time_locally_enabled") + } + /// Day off today + public static var roomDetailsMainSectionRestTimeRestday: String { + return VectorL10n.tr("Vector", "room_details_main_section_rest_time_restday") + } + /// %@ - %@ + public static func roomDetailsMainSectionRestTimeTime(_ p1: String, _ p2: String) -> String { + return VectorL10n.tr("Vector", "room_details_main_section_rest_time_time", p1, p2) + } /// Mute notifications public static var roomDetailsMuteNotifs: String { return VectorL10n.tr("Vector", "room_details_mute_notifs") @@ -3331,7 +3571,7 @@ public class VectorL10n: NSObject { public static var roomIntroCellAddParticipantsAction: String { return VectorL10n.tr("Vector", "room_intro_cell_add_participants_action") } - /// This is the beginning of your direct message with + /// This is the beginning of your conversation with public static var roomIntroCellInformationDmSentence1Part1: String { return VectorL10n.tr("Vector", "room_intro_cell_information_dm_sentence1_part1") } @@ -3379,6 +3619,10 @@ public class VectorL10n: NSObject { public static func roomManyUsersAreTyping(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "room_many_users_are_typing", p1, p2) } + /// You must first assign another admin before you are allowed to downgrade yourself. + public static var roomMemberDetailsDowngradeLastAdmin: String { + return VectorL10n.tr("Vector", "room_member_details_downgrade_last_admin") + } /// Admin in %@ public static func roomMemberPowerLevelAdminIn(_ p1: String) -> String { return VectorL10n.tr("Vector", "room_member_power_level_admin_in", p1) @@ -3615,6 +3859,10 @@ public class VectorL10n: NSObject { public static var roomParticipantsAgo: String { return VectorL10n.tr("Vector", "room_participants_ago") } + /// You cannot leave the room because there are no other admins in the room. + public static var roomParticipantsCantLeavePromptMessage: String { + return VectorL10n.tr("Vector", "room_participants_cant_leave_prompt_message") + } /// Filter room members public static var roomParticipantsFilterRoomMembers: String { return VectorL10n.tr("Vector", "room_participants_filter_room_members") @@ -4015,7 +4263,11 @@ public class VectorL10n: NSObject { public static var searchableDirectoryCreateNewRoom: String { return VectorL10n.tr("Vector", "searchable_directory_create_new_room") } - /// Name or ID + /// Room directory + public static var searchableDirectoryRoomTitle: String { + return VectorL10n.tr("Vector", "searchable_directory_room_title") + } + /// Roomname public static var searchableDirectorySearchPlaceholder: String { return VectorL10n.tr("Vector", "searchable_directory_search_placeholder") } @@ -4023,7 +4275,7 @@ public class VectorL10n: NSObject { public static func searchableDirectoryXNetwork(_ p1: String) -> String { return VectorL10n.tr("Vector", "searchable_directory_x_network", p1) } - /// Forgot or lost all recovery options? + /// Forgot recovery key? public static var secretsRecoveryResetActionPart1: String { return VectorL10n.tr("Vector", "secrets_recovery_reset_action_part_1") } @@ -4039,7 +4291,7 @@ public class VectorL10n: NSObject { public static var secretsRecoveryWithKeyInformationUnlockSecureBackupWithKey: String { return VectorL10n.tr("Vector", "secrets_recovery_with_key_information_unlock_secure_backup_with_key") } - /// Enter your Security Phrase to continue. + /// Enter your recovery key to continue. public static var secretsRecoveryWithKeyInformationUnlockSecureBackupWithPhrase: String { return VectorL10n.tr("Vector", "secrets_recovery_with_key_information_unlock_secure_backup_with_phrase") } @@ -4071,15 +4323,15 @@ public class VectorL10n: NSObject { public static var secretsRecoveryWithKeyTitle: String { return VectorL10n.tr("Vector", "secrets_recovery_with_key_title") } - /// Access your secure message history and your cross-signing identity for verifying other sessions by entering your Security Phrase. + /// Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key. public static var secretsRecoveryWithPassphraseInformationDefault: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_information_default") } - /// Use your Security Phrase to verify this device. + /// Enter your recovery key to decrypt all your messages. public static var secretsRecoveryWithPassphraseInformationVerifyDevice: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_information_verify_device") } - /// Please verify that you entered the correct Security Phrase. + /// Please verify that you entered the correct recovery Key. public static var secretsRecoveryWithPassphraseInvalidPassphraseMessage: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_invalid_passphrase_message") } @@ -4087,11 +4339,11 @@ public class VectorL10n: NSObject { public static var secretsRecoveryWithPassphraseInvalidPassphraseTitle: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_invalid_passphrase_title") } - /// Don’t know your Security Phrase? You can + /// Don’t know your recovery key? You can public static var secretsRecoveryWithPassphraseLostPassphraseActionPart1: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_lost_passphrase_action_part1") } - /// use your Security Key + /// use your security key public static var secretsRecoveryWithPassphraseLostPassphraseActionPart2: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_lost_passphrase_action_part2") } @@ -4099,23 +4351,23 @@ public class VectorL10n: NSObject { public static var secretsRecoveryWithPassphraseLostPassphraseActionPart3: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_lost_passphrase_action_part3") } - /// Enter Security Phrase + /// Recovery Key public static var secretsRecoveryWithPassphrasePassphrasePlaceholder: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_passphrase_placeholder") } - /// Enter + /// public static var secretsRecoveryWithPassphrasePassphraseTitle: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_passphrase_title") } - /// Use Phrase + /// Next public static var secretsRecoveryWithPassphraseRecoverAction: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_recover_action") } - /// Security Phrase + /// Recovery Key public static var secretsRecoveryWithPassphraseTitle: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_title") } - /// Enter your Matrix account password to confirm + /// Enter your account password to confirm public static var secretsResetAuthenticationMessage: String { return VectorL10n.tr("Vector", "secrets_reset_authentication_message") } @@ -4139,7 +4391,7 @@ public class VectorL10n: NSObject { public static var secretsResetWarningTitle: String { return VectorL10n.tr("Vector", "secrets_reset_warning_title") } - /// Done + /// Finish public static var secretsSetupRecoveryKeyDoneAction: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_key_done_action") } @@ -4147,7 +4399,7 @@ public class VectorL10n: NSObject { public static var secretsSetupRecoveryKeyExportAction: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_key_export_action") } - /// Store your Security Key somewhere safe. It can be used to unlock your encrypted messages & data. + /// Completed!\n\nYour chats have een successfully decrypted. public static var secretsSetupRecoveryKeyInformation: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_key_information") } @@ -4163,23 +4415,23 @@ public class VectorL10n: NSObject { public static var secretsSetupRecoveryKeyStorageAlertTitle: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_key_storage_alert_title") } - /// Save your Security Key + /// Recovery Key public static var secretsSetupRecoveryKeyTitle: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_key_title") } - /// Don't use your Matrix account password. + /// ⚠️ Do not use your account password. public static var secretsSetupRecoveryPassphraseAdditionalInformation: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_passphrase_additional_information") } - /// Enter your Security Phrase again to confirm it. + /// Enter your recovery key again to confirm it. public static var secretsSetupRecoveryPassphraseConfirmInformation: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_passphrase_confirm_information") } - /// Confirm phrase + /// Recovery Key public static var secretsSetupRecoveryPassphraseConfirmPassphrasePlaceholder: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_passphrase_confirm_passphrase_placeholder") } - /// Confirm + /// public static var secretsSetupRecoveryPassphraseConfirmPassphraseTitle: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_passphrase_confirm_passphrase_title") } @@ -4187,11 +4439,11 @@ public class VectorL10n: NSObject { public static var secretsSetupRecoveryPassphraseInformation: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_passphrase_information") } - /// Remember your Security Phrase. It can be used to unlock your encrypted messages & data. + /// Remember your recovery key. It can be used to unlock your encrypted messages & data. public static var secretsSetupRecoveryPassphraseSummaryInformation: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_passphrase_summary_information") } - /// Save your Security Phrase + /// Save your recovery key public static var secretsSetupRecoveryPassphraseSummaryTitle: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_passphrase_summary_title") } @@ -4235,11 +4487,11 @@ public class VectorL10n: NSObject { public static var secureKeyBackupSetupExistingBackupErrorUnlockIt: String { return VectorL10n.tr("Vector", "secure_key_backup_setup_existing_backup_error_unlock_it") } - /// Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server. + /// To decrypt your messages when you login on this or any other device, assign a recovery key. public static var secureKeyBackupSetupIntroInfo: String { return VectorL10n.tr("Vector", "secure_key_backup_setup_intro_info") } - /// Secure Backup + /// Recovery Key public static var secureKeyBackupSetupIntroTitle: String { return VectorL10n.tr("Vector", "secure_key_backup_setup_intro_title") } @@ -4251,11 +4503,11 @@ public class VectorL10n: NSObject { public static var secureKeyBackupSetupIntroUseSecurityKeyTitle: String { return VectorL10n.tr("Vector", "secure_key_backup_setup_intro_use_security_key_title") } - /// Enter a secret phrase only you know, and generate a key for backup. + /// Set up recovery key. public static var secureKeyBackupSetupIntroUseSecurityPassphraseInfo: String { return VectorL10n.tr("Vector", "secure_key_backup_setup_intro_use_security_passphrase_info") } - /// Use a Security Phrase + /// Recovery Key public static var secureKeyBackupSetupIntroUseSecurityPassphraseTitle: String { return VectorL10n.tr("Vector", "secure_key_backup_setup_intro_use_security_passphrase_title") } @@ -4667,6 +4919,10 @@ public class VectorL10n: NSObject { public static var settingsDisplayName: String { return VectorL10n.tr("Vector", "settings_display_name") } + /// Element Version %@ + public static func settingsElementVersion(_ p1: String) -> String { + return VectorL10n.tr("Vector", "settings_element_version", p1) + } /// Email public static var settingsEmailAddress: String { return VectorL10n.tr("Vector", "settings_email_address") @@ -4679,6 +4935,18 @@ public class VectorL10n: NSObject { public static var settingsEnableCallkit: String { return VectorL10n.tr("Vector", "settings_enable_callkit") } + /// Show chat effects + public static var settingsEnableChatEffects: String { + return VectorL10n.tr("Vector", "settings_enable_chat_effects") + } + /// Show deleted messages + public static var settingsEnableDeletedMessages: String { + return VectorL10n.tr("Vector", "settings_enable_deleted_messages") + } + /// Show user events + public static var settingsEnableEnterRoom: String { + return VectorL10n.tr("Vector", "settings_enable_enter_room") + } /// Notifications on this device public static var settingsEnablePushNotif: String { return VectorL10n.tr("Vector", "settings_enable_push_notif") @@ -4687,10 +4955,30 @@ public class VectorL10n: NSObject { public static var settingsEnableRageshake: String { return VectorL10n.tr("Vector", "settings_enable_rageshake") } + /// Off-time + public static var settingsEnableRestTime: String { + return VectorL10n.tr("Vector", "settings_enable_rest_time") + } + /// Show room events + public static var settingsEnableRoomAvatar: String { + return VectorL10n.tr("Vector", "settings_enable_room_avatar") + } /// Message bubbles public static var settingsEnableRoomMessageBubbles: String { return VectorL10n.tr("Vector", "settings_enable_room_message_bubbles") } + /// Show timeline events + public static var settingsEnableSimpleTimeLine: String { + return VectorL10n.tr("Vector", "settings_enable_simple_time_line") + } + /// Show time stamps + public static var settingsEnableTimeStamp: String { + return VectorL10n.tr("Vector", "settings_enable_time_stamp") + } + /// Show account events + public static var settingsEnableUserAvatar: String { + return VectorL10n.tr("Vector", "settings_enable_user_avatar") + } /// Encrypted direct messages public static var settingsEncryptedDirectMessages: String { return VectorL10n.tr("Vector", "settings_encrypted_direct_messages") @@ -4699,7 +4987,7 @@ public class VectorL10n: NSObject { public static var settingsEncryptedGroupMessages: String { return VectorL10n.tr("Vector", "settings_encrypted_group_messages") } - /// Fail to update Matrix account password + /// Fail to update password public static var settingsFailToUpdatePassword: String { return VectorL10n.tr("Vector", "settings_fail_to_update_password") } @@ -4707,6 +4995,10 @@ public class VectorL10n: NSObject { public static var settingsFailToUpdateProfile: String { return VectorL10n.tr("Vector", "settings_fail_to_update_profile") } + /// Changing display name is disabled on this server + public static var settingsFailToUpdateProfileMessage: String { + return VectorL10n.tr("Vector", "settings_fail_to_update_profile_message") + } /// First Name public static var settingsFirstName: String { return VectorL10n.tr("Vector", "settings_first_name") @@ -4955,7 +5247,11 @@ public class VectorL10n: NSObject { public static var settingsOther: String { return VectorL10n.tr("Vector", "settings_other") } - /// Your Matrix account password has been updated + /// Rules: Minimum length 8 characters, at least 1 special character, 1 uppercase and 1 lowercase letter and 1 number + public static var settingsPasswordCondition: String { + return VectorL10n.tr("Vector", "settings_password_condition") + } + /// Your password has been updated public static var settingsPasswordUpdated: String { return VectorL10n.tr("Vector", "settings_password_updated") } @@ -5011,6 +5307,10 @@ public class VectorL10n: NSObject { public static var settingsSecurity: String { return VectorL10n.tr("Vector", "settings_security") } + /// Send anon crash & usage data + public static var settingsSendCrashReport: String { + return VectorL10n.tr("Vector", "settings_send_crash_report") + } /// SENDING IMAGES AND VIDEOS public static var settingsSendingMedia: String { return VectorL10n.tr("Vector", "settings_sending_media") @@ -5043,6 +5343,10 @@ public class VectorL10n: NSObject { public static var settingsSignOutE2eWarn: String { return VectorL10n.tr("Vector", "settings_sign_out_e2e_warn") } + /// Support + public static var settingsSupport: String { + return VectorL10n.tr("Vector", "settings_support") + } /// Surname public static var settingsSurname: String { return VectorL10n.tr("Vector", "settings_surname") @@ -5359,6 +5663,26 @@ public class VectorL10n: NSObject { public static var spacesSuggestedRoom: String { return VectorL10n.tr("Vector", "spaces_suggested_room") } + /// Chats with comrades and colleagues + public static var splashScreenFirstInfo: String { + return VectorL10n.tr("Vector", "splash_screen_first_info") + } + /// Secure transfer of media and files + public static var splashScreenSecondInfo: String { + return VectorL10n.tr("Vector", "splash_screen_second_info") + } + /// Let go + public static var splashScreenStart: String { + return VectorL10n.tr("Vector", "splash_screen_start") + } + /// Always encrypted + public static var splashScreenThirdInfo: String { + return VectorL10n.tr("Vector", "splash_screen_third_info") + } + /// Your Messenger for our country + public static var splashScreenTitle: String { + return VectorL10n.tr("Vector", "splash_screen_title") + } /// Start public static var start: String { return VectorL10n.tr("Vector", "start") @@ -5427,7 +5751,7 @@ public class VectorL10n: NSObject { public static var titleHome: String { return VectorL10n.tr("Vector", "title_home") } - /// People + /// Direct Messages public static var titlePeople: String { return VectorL10n.tr("Vector", "title_people") } @@ -5635,6 +5959,10 @@ public class VectorL10n: NSObject { public static func voiceMessageRemainingRecordingTime(_ p1: String) -> String { return VectorL10n.tr("Vector", "voice_message_remaining_recording_time", p1) } + /// Slide to cancel + public static var voiceMessageSlideToCancel: String { + return VectorL10n.tr("Vector", "voice_message_slide_to_cancel") + } /// Tap on your recording to stop or listen public static var voiceMessageStopLockedModeRecording: String { return VectorL10n.tr("Vector", "voice_message_stop_locked_mode_recording") diff --git a/Riot/Managers/Analytics/Analytics.m b/Riot/Managers/Analytics/Analytics.m new file mode 100644 index 000000000..3b9832acc --- /dev/null +++ b/Riot/Managers/Analytics/Analytics.m @@ -0,0 +1,162 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +//#import "Analytics.h" +// +//#import "GeneratedInterface-Swift.h" +// +// +//NSString *const AnalyticsNoficationsCategory = @"notifications"; +//NSString *const AnalyticsNoficationsTimeToDisplayContent = @"timelineDisplay"; +//NSString *const AnalyticsContactsIdentityServerAccepted = @"identityServerAccepted"; +// +// +//// Duration data will be visible under the Piwik category called "Performance". +//// Other values will be visible in "Metrics". +//// Some Matomo screenshots are available at https://github.com/vector-im/element-ios/pull/3789. +//NSString *const kAnalyticsPerformanceCategory = @"Performance"; +//NSString *const kAnalyticsMetricsCategory = @"Metrics"; +// +// +//@import MatomoTracker; +// +//@interface Analytics () +//{ +// MatomoTracker *matomoTracker; +//} +// +//@end +// +//@implementation Analytics +// +//+ (instancetype)sharedInstance +//{ +// static Analytics *sharedInstance = nil; +// static dispatch_once_t onceToken; +// +// dispatch_once(&onceToken, ^{ +// sharedInstance = [[Analytics alloc] init]; +// }); +// +// return sharedInstance; +//} +// +//- (instancetype)init +//{ +// self = [super init]; +// if (self) +// { +// matomoTracker = [[MatomoTracker alloc] initWithSiteId:BuildSettings.analyticsAppId +// baseURL:BuildSettings.analyticsServerUrl +// userAgent:@"iOSMatomoTracker"]; +// [self migrateFromFourPointFourSharedInstance]; +// } +// return self; +//} +// +//- (void)migrateFromFourPointFourSharedInstance +//{ +// if ([[NSUserDefaults standardUserDefaults] boolForKey:@"migratedFromFourPointFourSharedInstance"]) return; +// [matomoTracker copyFromOldSharedInstance]; +// [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"migratedFromFourPointFourSharedInstance"]; +//} +// +//- (void)start +//{ +// // Check whether the user has enabled the sending of crash reports. +// if (RiotSettings.shared.enableCrashReport) +// { +// matomoTracker.isOptedOut = NO; +// +// [matomoTracker setCustomVariableWithIndex:1 name:@"App Platform" value:@"iOS Platform"]; +// [matomoTracker setCustomVariableWithIndex:2 name:@"App Version" value:[AppDelegate theDelegate].appVersion]; +// +// // The language is either the one selected by the user within the app +// // or, else, the one configured by the OS +// NSString *language = [NSBundle mxk_language] ? [NSBundle mxk_language] : [[NSBundle mainBundle] preferredLocalizations][0]; +// [matomoTracker setCustomVariableWithIndex:4 name:@"Chosen Language" value:language]; +// +// MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; +// if (account) +// { +// [matomoTracker setCustomVariableWithIndex:7 name:@"Homeserver URL" value:account.mxCredentials.homeServer]; +// [matomoTracker setCustomVariableWithIndex:8 name:@"Identity Server URL" value:account.identityServerURL]; +// } +// +// // TODO: We should also track device and os version +// // But that needs to be decided for all platforms +// +// // Catch and log crashes +// [MXLogger logCrashes:YES]; +// [MXLogger setBuildVersion:[AppDelegate theDelegate].build]; +// +//#ifdef DEBUG +// // Disable analytics in debug as it pollutes stats +// matomoTracker.isOptedOut = YES; +//#endif +// } +// else +// { +// MXLogDebug(@"[AppDelegate] The user decided to not send analytics"); +// matomoTracker.isOptedOut = YES; +// [MXLogger logCrashes:NO]; +// } +//} +// +//- (void)stop +//{ +// matomoTracker.isOptedOut = YES; +// [MXLogger logCrashes:NO]; +//} +// +//- (void)trackScreen:(NSString *)screenName +//{ +// // Use the same pattern as Android +// NSString *appName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; +// NSString *appVersion = [AppDelegate theDelegate].appVersion; +// +// [matomoTracker trackWithView:@[@"ios", appName, appVersion, screenName] +// url:nil]; +//} +// +//- (void)dispatch +//{ +// [matomoTracker dispatch]; +//} +// +//#pragma mark - MXAnalyticsDelegate +// +//- (void)trackDuration:(NSTimeInterval)seconds category:(NSString*)category name:(NSString*)name +//{ +// // Report time in ms to make figures look better in Matomo +// NSNumber *value = @(seconds * 1000); +// [matomoTracker trackWithEventWithCategory:kAnalyticsPerformanceCategory +// action:category +// name:name +// number:value +// url:nil]; +//} +// +//- (void)trackValue:(NSNumber*)value category:(NSString*)category name:(NSString*)name +//{ +// [matomoTracker trackWithEventWithCategory:kAnalyticsMetricsCategory +// action:category +// name:name +// number:value +// url:nil]; +//} +// +//@end diff --git a/Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift b/Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift index 5085e9efb..7859b965a 100644 --- a/Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift +++ b/Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift @@ -1,5 +1,6 @@ -// +// // Copyright 2020 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,85 +23,95 @@ import MatrixSDK @objcMembers class EncryptionKeyManager: NSObject, MXKeyProviderDelegate { static let shared = EncryptionKeyManager() - - private static let keychainService: String = BuildSettings.baseBundleIdentifier + ".encryption-manager-service" - private static let contactsIv: KeyValueStoreKey = "contactsIv" - private static let contactsAesKey: KeyValueStoreKey = "contactsAesKey" - private static let accountIv: KeyValueStoreKey = "accountIv" - private static let accountAesKey: KeyValueStoreKey = "accountAesKey" - private static let cryptoOlmPickleKey: KeyValueStoreKey = "cryptoOlmPickleKey" - private static let roomLastMessageIv: KeyValueStoreKey = "roomLastMessageIv" - private static let roomLastMessageAesKey: KeyValueStoreKey = "roomLastMessageAesKey" - private let keychainStore: KeyValueStore = KeychainStore(withKeychain: Keychain(service: keychainService, accessGroup: BuildSettings.keychainAccessGroup)) + private static let keychainService: String = BuildSettings.baseBundleIdentifier + ".encryption-manager-service" + private static let contactsIv = "iv" + private static let contactsAesKey = "key" + private static let accountIv = "acountIv" + private static let accountAesKey = "acountKey" + private static let cryptoOlmPickleKey = "cryptoOlmPickleKey" + private static let roomLastMessageIv = "roomLastMessageIv" + private static let roomLastMessageAesKey = "roomLastMessageAesKey" + + private let keychainVault = KeychainVault(Keychain(service: keychainService, accessGroup: BuildSettings.keychainAccessGroup)) + private let securedFileStore: SecureFileStorage = SecureFileStorage.shared + private let vault: KeyValueVault private override init() { - super.init() - initKeys() + vault = BwiBuildSettings.forcedPinProtection ? securedFileStore : keychainVault } - - private func initKeys() { - generateIvIfNotExists(forKey: EncryptionKeyManager.accountIv) - generateAesKeyIfNotExists(forKey: EncryptionKeyManager.accountAesKey) - generateIvIfNotExists(forKey: EncryptionKeyManager.contactsIv) - generateAesKeyIfNotExists(forKey: EncryptionKeyManager.contactsAesKey) - generateKeyIfNotExists(forKey: EncryptionKeyManager.cryptoOlmPickleKey, size: 32) - generateIvIfNotExists(forKey: EncryptionKeyManager.roomLastMessageIv) - generateAesKeyIfNotExists(forKey: EncryptionKeyManager.roomLastMessageAesKey) - assert(keychainStore.containsObject(forKey: EncryptionKeyManager.contactsIv), "[EncryptionKeyManager] initKeys: Failed to generate IV for acount") - assert(keychainStore.containsObject(forKey: EncryptionKeyManager.contactsAesKey), "[EncryptionKeyManager] initKeys: Failed to generate AES Key for acount") - assert(keychainStore.containsObject(forKey: EncryptionKeyManager.contactsIv), "[EncryptionKeyManager] initKeys: Failed to generate IV for contacts") - assert(keychainStore.containsObject(forKey: EncryptionKeyManager.contactsAesKey), "[EncryptionKeyManager] initKeys: Failed to generate AES Key for contacts") - assert(keychainStore.containsObject(forKey: EncryptionKeyManager.cryptoOlmPickleKey), "[EncryptionKeyManager] initKeys: Failed to generate Key for olm pickle key") - assert(keychainStore.containsObject(forKey: EncryptionKeyManager.roomLastMessageIv), "[EncryptionKeyManager] initKeys: Failed to generate IV for room last message") - assert(keychainStore.containsObject(forKey: EncryptionKeyManager.roomLastMessageAesKey), "[EncryptionKeyManager] initKeys: Failed to generate AES Key for room last message encryption") + func initKeys() { + generateIvIfNotExists(forKey: EncryptionKeyManager.accountIv, inStore: keychainVault) + generateAesKeyIfNotExists(forKey: EncryptionKeyManager.accountAesKey, inStore: keychainVault) + generateKeyIfNotExists(forKey: EncryptionKeyManager.cryptoOlmPickleKey, inStore: keychainVault, size: 32) + + assert(keychainVault.objectExists(withKey: EncryptionKeyManager.accountIv), "[EncryptionKeyManager] initKeys: Failed to generate IV for acount") + assert(keychainVault.objectExists(withKey: EncryptionKeyManager.accountAesKey), "[EncryptionKeyManager] initKeys: Failed to generate AES Key for acount") + assert(keychainVault.objectExists(withKey: EncryptionKeyManager.cryptoOlmPickleKey), "[EncryptionKeyManager] initKeys: Failed to generate Key for olm pickle key") + + generateIvIfNotExists(forKey: EncryptionKeyManager.roomLastMessageIv, inStore: keychainVault) + generateAesKeyIfNotExists(forKey: EncryptionKeyManager.roomLastMessageAesKey, inStore: keychainVault) + + assert(keychainVault.objectExists(withKey: EncryptionKeyManager.roomLastMessageIv), "[EncryptionKeyManager] initKeys: Failed to generate IV for room last message") + assert(keychainVault.objectExists(withKey: EncryptionKeyManager.roomLastMessageAesKey), "[EncryptionKeyManager] initKeys: Failed to generate AES Key for room last message encryption") + + guard !BwiBuildSettings.forcedPinProtection || !SecureFileStorage.shared.locked else { + MXLog.debug("[EncryptionKeyManager] initKeys: cannot init keys as store is not ready") + return + } + + generateIvIfNotExists(forKey: EncryptionKeyManager.contactsIv, inStore: vault) + generateAesKeyIfNotExists(forKey: EncryptionKeyManager.contactsAesKey, inStore: vault) + + assert(vault.objectExists(withKey: EncryptionKeyManager.contactsIv), "[EncryptionKeyManager] initKeys: Failed to generate IV for contacts") + assert(vault.objectExists(withKey: EncryptionKeyManager.contactsAesKey), "[EncryptionKeyManager] initKeys: Failed to generate AES Key for contacts") } - + // MARK: - MXKeyProviderDelegate - + func isEncryptionAvailableForData(ofType dataType: String) -> Bool { return dataType == MXKContactManagerDataType || dataType == MXKAccountManagerDataType || dataType == MXCryptoOlmPickleKeyDataType || dataType == MXRoomLastMessageDataType } - + func hasKeyForData(ofType dataType: String) -> Bool { switch dataType { case MXKContactManagerDataType: - return keychainStore.containsObject(forKey: EncryptionKeyManager.contactsIv) && keychainStore.containsObject(forKey: EncryptionKeyManager.contactsAesKey) + return vault.objectExists(withKey: EncryptionKeyManager.contactsIv) && vault.objectExists(withKey: EncryptionKeyManager.contactsAesKey) case MXKAccountManagerDataType: - return keychainStore.containsObject(forKey: EncryptionKeyManager.accountIv) && keychainStore.containsObject(forKey: EncryptionKeyManager.accountAesKey) + return keychainVault.objectExists(withKey: EncryptionKeyManager.accountIv) && keychainVault.objectExists(withKey: EncryptionKeyManager.accountAesKey) case MXCryptoOlmPickleKeyDataType: - return keychainStore.containsObject(forKey: EncryptionKeyManager.cryptoOlmPickleKey) + return keychainVault.objectExists(withKey: EncryptionKeyManager.cryptoOlmPickleKey) case MXRoomLastMessageDataType: - return keychainStore.containsObject(forKey: EncryptionKeyManager.roomLastMessageIv) && - keychainStore.containsObject(forKey: EncryptionKeyManager.roomLastMessageAesKey) + return keychainVault.objectExists(withKey: EncryptionKeyManager.roomLastMessageIv) && + keychainVault.objectExists(withKey: EncryptionKeyManager.roomLastMessageAesKey) default: return false } } - + func keyDataForData(ofType dataType: String) -> MXKeyData? { switch dataType { case MXKContactManagerDataType: - if let ivKey = try? keychainStore.data(forKey: EncryptionKeyManager.contactsIv), - let aesKey = try? keychainStore.data(forKey: EncryptionKeyManager.contactsAesKey) { + if let ivKey = try? vault.data(forKey: EncryptionKeyManager.contactsIv), + let aesKey = try? vault.data(forKey: EncryptionKeyManager.contactsAesKey) { return MXAesKeyData(iv: ivKey, key: aesKey) } case MXKAccountManagerDataType: - if let ivKey = try? keychainStore.data(forKey: EncryptionKeyManager.accountIv), - let aesKey = try? keychainStore.data(forKey: EncryptionKeyManager.accountAesKey) { + if let ivKey = try? keychainVault.data(forKey: EncryptionKeyManager.accountIv), + let aesKey = try? keychainVault.data(forKey: EncryptionKeyManager.accountAesKey) { return MXAesKeyData(iv: ivKey, key: aesKey) } case MXCryptoOlmPickleKeyDataType: - if let key = try? keychainStore.data(forKey: EncryptionKeyManager.cryptoOlmPickleKey) { + if let key = try? keychainVault.data(forKey: EncryptionKeyManager.cryptoOlmPickleKey) { return MXRawDataKey(key: key) } case MXRoomLastMessageDataType: - if let ivKey = try? keychainStore.data(forKey: EncryptionKeyManager.roomLastMessageIv), - let aesKey = try? keychainStore.data(forKey: EncryptionKeyManager.roomLastMessageAesKey) { + if let ivKey = try? keychainVault.data(forKey: EncryptionKeyManager.roomLastMessageIv), + let aesKey = try? keychainVault.data(forKey: EncryptionKeyManager.roomLastMessageAesKey) { return MXAesKeyData(iv: ivKey, key: aesKey) } default: @@ -108,34 +119,34 @@ class EncryptionKeyManager: NSObject, MXKeyProviderDelegate { } return nil } - + // MARK: - Private methods - - private func generateIvIfNotExists(forKey key: String) { - guard !keychainStore.containsObject(forKey: key) else { + + private func generateIvIfNotExists(forKey key: String, inStore store: KeyValueVault) { + guard !store.objectExists(withKey: key) else { return } - + do { - try keychainStore.set(MXAes.iv(), forKey: key) + try store.set(MXAes.iv(), forKey: key) } catch { MXLog.debug("[EncryptionKeyManager] initKeys: Failed to generate IV: \(error.localizedDescription)") } } - - private func generateAesKeyIfNotExists(forKey key: String) { - generateKeyIfNotExists(forKey: key, size: kCCKeySizeAES256) + + private func generateAesKeyIfNotExists(forKey key: String, inStore store: KeyValueVault) { + generateKeyIfNotExists(forKey: key, inStore: store, size: kCCKeySizeAES256) } - - private func generateKeyIfNotExists(forKey key: String, size: Int) { - guard !keychainStore.containsObject(forKey: key) else { + + private func generateKeyIfNotExists(forKey key: String, inStore store: KeyValueVault, size: Int) { + guard !store.objectExists(withKey: key) else { return } - + do { - var keyBytes = [UInt8](repeating: 0, count: size) - _ = SecRandomCopyBytes(kSecRandomDefault, size, &keyBytes) - try keychainStore.set(Data(bytes: keyBytes, count: size), forKey: key) + var aesKeyBytes = [UInt8](repeating: 0, count: size) + _ = SecRandomCopyBytes(kSecRandomDefault, size, &aesKeyBytes) + try store.set(Data(bytes: aesKeyBytes, count: size), forKey: key) } catch { MXLog.debug("[EncryptionKeyManager] initKeys: Failed to generate Key[\(key)]: \(error.localizedDescription)") } diff --git a/Riot/Managers/KeyValueStorage/Extensions/Keychain.swift b/Riot/Managers/KeyValueStorage/Extensions/Keychain.swift deleted file mode 100644 index 36281edb4..000000000 --- a/Riot/Managers/KeyValueStorage/Extensions/Keychain.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright 2020 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import KeychainAccess - -/// Extension on Keychain to get/set booleans -extension Keychain { - - public func set(_ value: Bool, key: String, ignoringAttributeSynchronizable: Bool = true) throws { - try set(value.description, key: key, ignoringAttributeSynchronizable: ignoringAttributeSynchronizable) - } - - public func getBool(_ key: String, ignoringAttributeSynchronizable: Bool = true) throws -> Bool? { - guard let value = try getString(key, ignoringAttributeSynchronizable: ignoringAttributeSynchronizable) else { - return nil - } - guard value == true.description || value == false.description else { return nil } - return value == true.description - } - -} diff --git a/Riot/Managers/KeyValueStorage/KeyValueStore.swift b/Riot/Managers/KeyValueStorage/KeyValueStore.swift deleted file mode 100644 index e70bba8ea..000000000 --- a/Riot/Managers/KeyValueStorage/KeyValueStore.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright 2020 Vector Creations Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -typealias KeyValueStoreKey = String - -protocol KeyValueStore { - // setters - func set(_ value: Data?, forKey key: KeyValueStoreKey) throws - func set(_ value: String?, forKey key: KeyValueStoreKey) throws - func set(_ value: Bool?, forKey key: KeyValueStoreKey) throws - func set(_ value: Int?, forKey key: KeyValueStoreKey) throws - func set(_ value: UInt?, forKey key: KeyValueStoreKey) throws - - // getters - func data(forKey key: KeyValueStoreKey) throws -> Data? - func string(forKey key: KeyValueStoreKey) throws -> String? - func bool(forKey key: KeyValueStoreKey) throws -> Bool? - func integer(forKey key: KeyValueStoreKey) throws -> Int? - func unsignedInteger(forKey key: KeyValueStoreKey) throws -> UInt? - - // checkers - func containsObject(forKey key: KeyValueStoreKey) -> Bool - - // remove - func removeObject(forKey key: KeyValueStoreKey) throws - func removeAll() throws -} diff --git a/Riot/Managers/KeyValueStorage/KeychainStore.swift b/Riot/Managers/KeyValueStorage/KeychainStore.swift deleted file mode 100644 index 8f694cb54..000000000 --- a/Riot/Managers/KeyValueStorage/KeychainStore.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// Copyright 2020 Vector Creations Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import KeychainAccess - -class KeychainStore { - - private var keychain: Keychain - - /// Initializer - /// - Parameter keychain: Keychain instance to be used to read/write - init(withKeychain keychain: Keychain) { - self.keychain = keychain - } - -} - -extension KeychainStore: KeyValueStore { - - // setters - func set(_ value: Data?, forKey key: KeyValueStoreKey) throws { - guard let value = value else { - try removeObject(forKey: key) - return - } - - try keychain.set(value, key: key) - } - - func set(_ value: String?, forKey key: KeyValueStoreKey) throws { - guard let value = value else { - try removeObject(forKey: key) - return - } - - try keychain.set(value, key: key) - } - - func set(_ value: Bool?, forKey key: KeyValueStoreKey) throws { - guard let value = value else { - try removeObject(forKey: key) - return - } - - try keychain.set(value, key: key) - } - - func set(_ value: Int?, forKey key: KeyValueStoreKey) throws { - guard let value = value else { - try removeObject(forKey: key) - return - } - - try keychain.set(String(value), key: key) - } - - func set(_ value: UInt?, forKey key: KeyValueStoreKey) throws { - guard let value = value else { - try removeObject(forKey: key) - return - } - - try keychain.set(String(value), key: key) - } - - // getters - func data(forKey key: KeyValueStoreKey) throws -> Data? { - return try keychain.getData(key) - } - - func string(forKey key: KeyValueStoreKey) throws -> String? { - return try keychain.getString(key) - } - - func bool(forKey key: KeyValueStoreKey) throws -> Bool? { - return try keychain.getBool(key) - } - - func integer(forKey key: KeyValueStoreKey) throws -> Int? { - guard let stringValue = try keychain.getString(key) else { - return nil - } - return Int(stringValue) - } - - func unsignedInteger(forKey key: KeyValueStoreKey) throws -> UInt? { - guard let stringValue = try keychain.getString(key) else { - return nil - } - return UInt(stringValue) - } - - // checkers - func containsObject(forKey key: KeyValueStoreKey) -> Bool { - return (try? keychain.contains(key)) ?? false - } - - // remove - func removeObject(forKey key: KeyValueStoreKey) throws { - try keychain.remove(key) - } - - func removeAll() throws { - try keychain.removeAll() - } - -} diff --git a/Riot/Managers/KeyValueStorage/MemoryStore.swift b/Riot/Managers/KeyValueStorage/MemoryStore.swift deleted file mode 100644 index 993f0f4c7..000000000 --- a/Riot/Managers/KeyValueStorage/MemoryStore.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright 2020 Vector Creations Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -class MemoryStore { - - private(set) var map: [KeyValueStoreKey: Any] = [:] - - private func setObject(_ value: Any?, forKey key: KeyValueStoreKey) { - if let value = value { - map[key] = value - } else { - try? removeObject(forKey: key) - } - } - - private func object(forKey key: KeyValueStoreKey) -> Any? { - return map[key] - } - - init(withMap map: [KeyValueStoreKey: Any] = [:]) { - self.map = map - } - -} - -extension MemoryStore: KeyValueStore { - - // setters - func set(_ value: Data?, forKey key: KeyValueStoreKey) throws { - setObject(value, forKey: key) - } - - func set(_ value: String?, forKey key: KeyValueStoreKey) throws { - setObject(value, forKey: key) - } - - func set(_ value: Bool?, forKey key: KeyValueStoreKey) throws { - setObject(value, forKey: key) - } - - func set(_ value: Int?, forKey key: KeyValueStoreKey) throws { - setObject(value, forKey: key) - } - - func set(_ value: UInt?, forKey key: KeyValueStoreKey) throws { - setObject(value, forKey: key) - } - - // getters - func data(forKey key: KeyValueStoreKey) throws -> Data? { - return object(forKey: key) as? Data - } - - func string(forKey key: KeyValueStoreKey) throws -> String? { - return object(forKey: key) as? String - } - - func bool(forKey key: KeyValueStoreKey) throws -> Bool? { - return object(forKey: key) as? Bool - } - - func integer(forKey key: KeyValueStoreKey) throws -> Int? { - return object(forKey: key) as? Int - } - - func unsignedInteger(forKey key: KeyValueStoreKey) throws -> UInt? { - return object(forKey: key) as? UInt - } - - // checkers - func containsObject(forKey key: KeyValueStoreKey) -> Bool { - return object(forKey: key) != nil - } - - // remove - func removeObject(forKey key: KeyValueStoreKey) throws { - map.removeValue(forKey: key) - } - - func removeAll() throws { - map.removeAll() - } - -} diff --git a/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift b/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift index 83b9ec265..2c5a34620 100644 --- a/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift +++ b/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift @@ -1,5 +1,6 @@ // // Copyright 2020 Vector Creations Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -41,6 +42,7 @@ class LocalAuthenticationService: NSObject { private func setup() { NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(biometricsDidFallbackToPin), name: .biometricsDidFallbackToPin, object: nil) } var shouldShowPinCode: Bool { @@ -60,10 +62,15 @@ class LocalAuthenticationService: NSObject { return pinCodePreferences.isPinSet || pinCodePreferences.isBiometricsSet } - func applicationWillResignActive() { + @objc private func applicationWillResignActive() { appLastActiveTime = systemUptime } + @objc private func biometricsDidFallbackToPin() { + // fallback to pin from biometrics, do not care about timing + appLastActiveTime = nil + } + func shouldLogOutUser() -> Bool { if BuildSettings.logOutUserWhenPINFailuresExceeded && pinCodePreferences.numberOfPinFailures >= pinCodePreferences.maxAllowedNumberOfPinFailures { return true diff --git a/Riot/Managers/PushNotification/PushNotificationService.m b/Riot/Managers/PushNotification/PushNotificationService.m index 59fb9604a..87efdaae2 100644 --- a/Riot/Managers/PushNotification/PushNotificationService.m +++ b/Riot/Managers/PushNotification/PushNotificationService.m @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd Copyright 2020 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -42,7 +43,10 @@ Matrix session observer used to detect new opened sessions. if (self = [super init]) { self.pushNotificationStore = pushNotificationStore; - _pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; + if (BuildSettings.allowVoIPUsage) + { + _pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; + } self.shouldReceiveVoIPPushes = YES; } return self; diff --git a/Riot/Managers/PushNotification/PushNotificationStore.swift b/Riot/Managers/PushNotification/PushNotificationStore.swift index 2c54faf7c..6e5daa003 100644 --- a/Riot/Managers/PushNotification/PushNotificationStore.swift +++ b/Riot/Managers/PushNotification/PushNotificationStore.swift @@ -1,5 +1,6 @@ // // Copyright 2020 Vector Creations Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,11 +32,10 @@ final class PushNotificationStore: NSObject { static let pushToken: String = "pushtoken" } - /// Store. Defaults to `KeychainStore` - private let store: KeyValueStore + private let vault: KeyValueVault override init() { - store = KeychainStore(withKeychain: Keychain(service: PushNotificationConstants.pushNotificationKeychainService, + vault = KeychainVault(Keychain(service: PushNotificationConstants.pushNotificationKeychainService, accessGroup: BuildSettings.keychainAccessGroup)) super.init() } @@ -44,22 +44,22 @@ final class PushNotificationStore: NSObject { var pushKitToken: Data? { get { do { - return try store.data(forKey: StoreKeys.pushToken) + return try vault.data(forKey: StoreKeys.pushToken) } catch let error { - MXLog.debug("[PinCodePreferences] Error when reading push token from store: \(error)") + MXLog.debug("[PinCodePreferences] Error when reading push token from vault: \(error)") return nil } } set { do { - try store.set(newValue, forKey: StoreKeys.pushToken) + try vault.set(newValue, forKey: StoreKeys.pushToken) } catch let error { - MXLog.debug("[PinCodePreferences] Error when storing push token to the store: \(error)") + MXLog.debug("[PinCodePreferences] Error when storing push token to the vault: \(error)") } } } func callInvite(forEventId eventId: String) -> MXEvent? { - guard let data = try? store.data(forKey: eventId) else { + guard let data = try? vault.data(forKey: eventId) else { return nil } return NSKeyedUnarchiver.unarchiveObject(with: data) as? MXEvent @@ -67,14 +67,14 @@ final class PushNotificationStore: NSObject { func storeCallInvite(_ event: MXEvent) { let data = NSKeyedArchiver.archivedData(withRootObject: event) - try? store.set(data, forKey: event.eventId) + try? vault.set(data, forKey: event.eventId) } func removeCallInvite(withEventId eventId: String) { - try? store.removeObject(forKey: eventId) + try? vault.removeObject(forKey: eventId) } func reset() { - try? store.removeAll() + try? vault.reset() } } diff --git a/Riot/Managers/Settings/RiotSettings+TimeLine.swift b/Riot/Managers/Settings/RiotSettings+TimeLine.swift new file mode 100644 index 000000000..1f0f8bbd7 --- /dev/null +++ b/Riot/Managers/Settings/RiotSettings+TimeLine.swift @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2021 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + + +extension RiotSettings { + private enum UserDefaultsTimelineKeys { + static let settingsScreenShowSimpleTimeLineOptions = "settingsScreenShowSimpleTimeLineOptions" + static let settingsScreenShowTimeStampOption = "settingsScreenShowTimeStampOption" + static let settingsScreenShowDeletedMessagesOption = "settingsScreenShowDeletedMessagesOption" + static let settingsScreenShowNameChangeOption = "settingsScreenShowNameChangeOption" + static let settingsScreenShowChatEffectsOption = "settingsScreenShowChatEffectsOption" + static let settingsScreenShowRoomAvatarChangeOption = "settingsScreenShowRoomAvatarChangeOption" + static let settingsScreenShowUserAvatarChangeOption = "settingsScreenShowUserAvatarChangeOption" + static let settingsScreenShowEnterRoomOption = "settingsScreenShowEnterRoomOption" + static let settingSimpleTimeLineEnabled = "settingSimpleTimelineEnabled" + static let settingTimeStampEnabled = "settingTimeStampEnabled" + static let settingDeletedMessagesEnabled = "settingDeletedMessagesEnabled" + static let settingNameChangeEnabled = "settingNameChangeEnabled" + static let settingChatEffectsEnabled = "settingChatEffectsEnabled" + static let settingRoomAvatarChangeEnabled = "settingRoomAvatarChangeEnabled" + static let settingUserAvatarChangeEnabled = "settingUserAvatarChangeEnabled" + static let settingEnterRoomEnabled = "settingEnterRoomEnabled" + } + + var settingsScreenShowSimpleTimeLineOptions: Bool { + get { + guard RiotSettings.defaults.object(forKey: UserDefaultsTimelineKeys.settingsScreenShowSimpleTimeLineOptions) != nil else { + return BuildSettings.settingsScreenShowSimpleTimeLineOptions + } + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingsScreenShowSimpleTimeLineOptions) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingsScreenShowSimpleTimeLineOptions) + } + } + + var settingsScreenShowTimeStampOption: Bool { + get { + guard RiotSettings.defaults.object(forKey: UserDefaultsTimelineKeys.settingsScreenShowTimeStampOption) != nil else { + return BuildSettings.settingsScreenShowTimeStampOption + } + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingsScreenShowTimeStampOption) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingsScreenShowTimeStampOption) + } + } + + var settingsScreenShowDeletedMessagesOption: Bool { + get { + guard RiotSettings.defaults.object(forKey: UserDefaultsTimelineKeys.settingsScreenShowDeletedMessagesOption) != nil else { + return BuildSettings.settingsScreenShowDeletedMessagesOption + } + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingsScreenShowDeletedMessagesOption) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingsScreenShowDeletedMessagesOption) + } + } + + var settingsScreenShowNameChangeOption: Bool { + get { + guard RiotSettings.defaults.object(forKey: UserDefaultsTimelineKeys.settingsScreenShowNameChangeOption) != nil else { + return BuildSettings.settingsScreenShowNameChangeOption + } + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingsScreenShowNameChangeOption) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingsScreenShowNameChangeOption) + } + } + + var settingsScreenShowChatEffectsOption: Bool { + get { + guard RiotSettings.defaults.object(forKey: UserDefaultsTimelineKeys.settingsScreenShowChatEffectsOption) != nil else { + return BuildSettings.settingsScreenShowChatEffectsOption + } + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingsScreenShowChatEffectsOption) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingsScreenShowChatEffectsOption) + } + } + + var settingsScreenShowRoomAvatarChangeOption: Bool { + get { + guard RiotSettings.defaults.object(forKey: UserDefaultsTimelineKeys.settingsScreenShowRoomAvatarChangeOption) != nil else { + return BuildSettings.settingsScreenShowRoomAvatarChangeOption + } + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingsScreenShowRoomAvatarChangeOption) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingsScreenShowRoomAvatarChangeOption) + } + } + + var settingsScreenShowUserAvatarChangeOption: Bool { + get { + guard RiotSettings.defaults.object(forKey: UserDefaultsTimelineKeys.settingsScreenShowUserAvatarChangeOption) != nil else { + return BuildSettings.settingsScreenShowUserAvatarChangeOption + } + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingsScreenShowUserAvatarChangeOption) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingsScreenShowUserAvatarChangeOption) + } + } + + var settingsScreenShowEnterRoomOption: Bool { + get { + guard RiotSettings.defaults.object(forKey: UserDefaultsTimelineKeys.settingsScreenShowEnterRoomOption) != nil else { + return BuildSettings.settingsScreenShowEnterRoomOption + } + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingsScreenShowEnterRoomOption) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingsScreenShowEnterRoomOption) + } + } + + var settingSimpleTimelineEnabled: Bool { + get { + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingSimpleTimeLineEnabled) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingSimpleTimeLineEnabled) + } + } + + var settingTimeStampEnabled: Bool { + get { + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingTimeStampEnabled) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingTimeStampEnabled) + } + } + + var settingDeletedMessagesEnabled: Bool { + get { + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingDeletedMessagesEnabled) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingDeletedMessagesEnabled) + } + } + + var settingNameChangeEnabled: Bool { + get { + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingNameChangeEnabled) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingNameChangeEnabled) + } + } + + var settingChatEffectsEnabled: Bool { + get { + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingChatEffectsEnabled) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingChatEffectsEnabled) + } + } + + var settingRoomAvatarChangeEnabled: Bool { + get { + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingRoomAvatarChangeEnabled) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingRoomAvatarChangeEnabled) + } + } + + var settingUserAvatarChangeEnabled: Bool { + get { + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingUserAvatarChangeEnabled) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingUserAvatarChangeEnabled) + } + } + + var settingEnterRoomEnabled: Bool { + get { + return RiotSettings.defaults.bool(forKey: UserDefaultsTimelineKeys.settingEnterRoomEnabled) + } set { + RiotSettings.defaults.set(newValue, forKey: UserDefaultsTimelineKeys.settingEnterRoomEnabled) + } + } +} diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 1b1af2591..8f931b709 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -1,5 +1,6 @@ /* Copyright 2018 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,6 +24,8 @@ final class RiotSettings: NSObject { // MARK: - Constants public enum UserDefaultsKeys { + + static let enableCrashReport = "enableCrashReport" static let enableAnalytics = "enableAnalytics" static let matomoAnalytics = "enableCrashReport" static let notificationsShowDecryptedContent = "showDecryptedContent" @@ -44,6 +47,14 @@ final class RiotSettings: NSObject { private override init() { super.init() + + RiotSettings.defaults.register( + defaults: [ + "settingTimeStampEnabled": true, + "settingUserAvatarChangeEnabled": false, + "settingEnterRoomEnabled": true + ] + ) } /// Indicate if UserDefaults suite has been migrated once. @@ -106,6 +117,9 @@ final class RiotSettings: NSObject { RiotSettings.defaults.object(forKey: UserDefaultsKeys.enableAnalytics) != nil } + @UserDefault(key: UserDefaultsKeys.enableCrashReport, defaultValue: false, storage: defaults) + var enableCrashReport + /// Whether the user has both seen the Matomo analytics prompt and declined it. var hasDeclinedMatomoAnalytics: Bool { RiotSettings.defaults.object(forKey: UserDefaultsKeys.matomoAnalytics) != nil && !RiotSettings.defaults.bool(forKey: UserDefaultsKeys.matomoAnalytics) @@ -127,6 +141,7 @@ final class RiotSettings: NSObject { @UserDefault(key: "isIdentifiedForAnalytics", defaultValue: false, storage: defaults) var isIdentifiedForAnalytics + @UserDefault(key: "enableRageShake", defaultValue: false, storage: defaults) var enableRageShake @@ -161,6 +176,20 @@ final class RiotSettings: NSObject { @UserDefault(key: "matrixApps", defaultValue: false, storage: defaults) var matrixApps + // MARK: - PIN & Biometrics + + @UserDefault(key: "biometricsEnabled", defaultValue: false, storage: defaults) + var biometricsEnabled + + @UserDefault(key: "canUseBiometricsToUnlock", defaultValue: true, storage: defaults) + var canUseBiometricsToUnlock + + @UserDefault(key: "numberOfPinFailures", defaultValue: 0, storage: defaults) + var numberOfPinFailures + + @UserDefault(key: "numberOfBiometricsFailures", defaultValue: 0, storage: defaults) + var numberOfBiometricsFailures + // MARK: - Rooms Screen @UserDefault(key: "roomsAllowToJoinPublicRooms", defaultValue: BuildSettings.roomsAllowToJoinPublicRooms, storage: defaults) @@ -188,6 +217,12 @@ final class RiotSettings: NSObject { @UserDefault(key: "roomScreenAllowFilesAction", defaultValue: BuildSettings.roomScreenAllowFilesAction, storage: defaults) var roomScreenAllowFilesAction + + @UserDefault(key: "roomScreenAllowPollsAction", defaultValue: BuildSettings.pollsEnabled, storage: defaults) + var roomScreenAllowPollsAction + + @UserDefault(key: "roomScreenAllowLocationAction", defaultValue: false, storage: defaults) + var roomScreenAllowLocationAction @UserDefault(key: "roomScreenShowsURLPreviews", defaultValue: true, storage: defaults) var roomScreenShowsURLPreviews @@ -200,6 +235,9 @@ final class RiotSettings: NSObject { } // MARK: - Room Contextual Menu + + @UserDefault(key: "roomContextualMenuShowEncryptionOption", defaultValue: BuildSettings.roomContextualMenuShowEncryptionOption, storage: defaults) + var roomContextualMenuShowEncryptionOption @UserDefault(key: "roomContextualMenuShowMoreOptionForMessages", defaultValue: BuildSettings.roomContextualMenuShowMoreOptionForMessages, storage: defaults) var roomContextualMenuShowMoreOptionForMessages @@ -246,6 +284,9 @@ final class RiotSettings: NSObject { // MARK: - Main Tabs + @UserDefault(key: "homeScreenShowHomeTab", defaultValue: BuildSettings.homeScreenShowHomeTab, storage: defaults) + var homeScreenShowHomeTab + @UserDefault(key: "homeScreenShowFavouritesTab", defaultValue: BuildSettings.homeScreenShowFavouritesTab, storage: defaults) var homeScreenShowFavouritesTab @@ -258,6 +299,26 @@ final class RiotSettings: NSObject { @UserDefault(key: "homeScreenShowCommunitiesTab", defaultValue: BuildSettings.homeScreenShowCommunitiesTab, storage: defaults) var homeScreenShowCommunitiesTab + // MARK bwi Left Settings + + @UserDefault(key: "settingsScreenShowInviteFriends", defaultValue: BuildSettings.settingsScreenShowInviteFriends, storage: defaults) + var settingsScreenShowInviteFriends + + @UserDefault(key: "settingsScreenShowSettings", defaultValue: BuildSettings.settingsScreenShowSettings, storage: defaults) + var settingsScreenShowSettings + + @UserDefault(key: "settingsScreenShowFeedback", defaultValue: BuildSettings.settingsScreenShowFeedback, storage: defaults) + var settingsScreenShowFeedback + + @UserDefault(key: "settingsScreenShowHelp", defaultValue: BuildSettings.settingsScreenShowHelp, storage: defaults) + var settingsScreenShowHelp + + @UserDefault(key: "settingsScreenShowSystemSettingsOption", defaultValue: BuildSettings.settingsScreenShowSystemSettingsOption, storage: defaults) + var settingsScreenShowSystemSettingsOption + + @UserDefault(key: "homeScreenShowHomeTab", defaultValue: BuildSettings.bwiMatomoTrackingDefaultState, storage: defaults) + var bwiEnableMatomoTracking + // MARK: General Settings @UserDefault(key: "settingsScreenShowChangePassword", defaultValue: BuildSettings.settingsScreenShowChangePassword, storage: defaults) @@ -293,6 +354,15 @@ final class RiotSettings: NSObject { @UserDefault(key: "settingsSecurityScreenShowAdvancedBlacklistUnverifiedDevices", defaultValue: BuildSettings.settingsSecurityScreenShowAdvancedUnverifiedDevices, storage: defaults) var settingsSecurityScreenShowAdvancedUnverifiedDevices + @UserDefault(key: "settingsNotificationsShowDefault", defaultValue: BuildSettings.settingsNotificationsShowDefault, storage: defaults) + var settingsNotificationsShowDefault + + @UserDefault(key: "settingsNotificationsShowMentions", defaultValue: BuildSettings.settingsNotificationsShowMentions, storage: defaults) + var settingsNotificationsShowMentions + + @UserDefault(key: "settingsNotificationsShowAdvanced", defaultValue: BuildSettings.settingsNotificationsShowAdvanced, storage: defaults) + var settingsNotificationsShowAdvanced + // MARK: - Room Settings Screen @UserDefault(key: "roomSettingsScreenShowLowPriorityOption", defaultValue: BuildSettings.roomSettingsScreenShowLowPriorityOption, storage: defaults) diff --git a/Riot/Managers/Settings/RiotSettings.swift.orig b/Riot/Managers/Settings/RiotSettings.swift.orig new file mode 100644 index 000000000..564b25070 --- /dev/null +++ b/Riot/Managers/Settings/RiotSettings.swift.orig @@ -0,0 +1,796 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// Store Riot specific app settings. +@objcMembers +final class RiotSettings: NSObject { + + // MARK: - Constants + + private enum UserDefaultsKeys { + static let homeserverUrlString = "homeserverurl" + static let identityServerUrlString = "identityserverurl" + static let enableCrashReport = "enableCrashReport" + static let enableRageShake = "enableRageShake" + static let userInterfaceTheme = "userInterfaceTheme" + static let notificationsShowDecryptedContent = "showDecryptedContent" + static let pinRoomsWithMissedNotifications = "pinRoomsWithMissedNotif" + static let pinRoomsWithUnreadMessages = "pinRoomsWithUnread" + static let allowStunServerFallback = "allowStunServerFallback" + static let hideVerifyThisSessionAlert = "hideVerifyThisSessionAlert" + static let hideReviewSessionsAlert = "hideReviewSessionsAlert" + static let matrixApps = "matrixApps" + static let biometricsEnabled = "biometricsEnabled" + static let canUseBiometricsToUnlock = "canUseBiometricsToUnlock" + static let numberOfPinFailures = "numberOfPinFailures" + static let numberOfBiometricsFailures = "numberOfBiometricsFailures" + static let showNSFWPublicRooms = "showNSFWPublicRooms" + static let settingsScreenShowChangePassword = "settingsScreenShowChangePassword" + static let settingsScreenShowInviteFriends = "settingsScreenShowInviteFriends" + static let settingsScreenShowEnableStunServerFallback = "settingsScreenShowEnableStunServerFallback" + static let settingsSecurityScreenShowSessions = "settingsSecurityScreenShowSessions" + static let settingsSecurityScreenShowSetupBackup = "settingsSecurityScreenShowSetupBackup" + static let settingsSecurityScreenShowRestoreBackup = "settingsSecurityScreenShowRestoreBackup" + static let settingsSecurityScreenShowDeleteBackup = "settingsSecurityScreenShowDeleteBackup" + static let settingsSecurityScreenShowCryptographyInfo = "settingsSecurityScreenShowCryptographyInfo" + static let settingsSecurityScreenShowCryptographyExport = "settingsSecurityScreenShowCryptographyExport" + static let settingsSecurityScreenShowAdvancedUnverifiedDevices = "settingsSecurityScreenShowAdvancedBlacklistUnverifiedDevices" + static let roomCreationScreenAllowEncryptionConfiguration = "roomCreationScreenAllowEncryptionConfiguration" + static let roomCreationScreenRoomIsEncrypted = "roomCreationScreenRoomIsEncrypted" + static let roomCreationScreenAllowRoomTypeConfiguration = "roomCreationScreenAllowRoomTypeConfiguration" + static let roomCreationScreenRoomIsPublic = "roomCreationScreenRoomIsPublic" + static let allowInviteExernalUsers = "allowInviteExernalUsers" +<<<<<<< HEAD +======= + static let enableRingingForGroupCalls = "enableRingingForGroupCalls" + static let roomSettingsScreenShowLowPriorityOption = "roomSettingsScreenShowLowPriorityOption" + static let roomSettingsScreenShowDirectChatOption = "roomSettingsScreenShowDirectChatOption" + static let roomSettingsScreenAllowChangingAccessSettings = "roomSettingsScreenAllowChangingAccessSettings" + static let roomSettingsScreenAllowChangingHistorySettings = "roomSettingsScreenAllowChangingHistorySettings" + static let roomSettingsScreenShowAddressSettings = "roomSettingsScreenShowAddressSettings" + static let roomSettingsScreenShowFlairSettings = "roomSettingsScreenShowFlairSettings" + static let roomSettingsScreenShowAdvancedSettings = "roomSettingsScreenShowAdvancedSettings" + static let roomSettingsScreenAdvancedShowEncryptToVerifiedOption = "roomSettingsScreenAdvancedShowEncryptToVerifiedOption" + static let settingsScreenShowNotificationDecodedContentOption = "settingsScreenShowNotificationDecodedContentOption" + static let settingsScreenShowNsfwRoomsOption = "settingsScreenShowNsfwRoomsOption" + static let roomsAllowToJoinPublicRooms = "roomsAllowToJoinPublicRooms" + static let homeScreenShowFavouritesTab = "homeScreenShowFavouritesTab" + static let homeScreenShowPeopleTab = "homeScreenShowPeopleTab" + static let homeScreenShowRoomsTab = "homeScreenShowRoomsTab" + static let homeScreenShowCommunitiesTab = "homeScreenShowCommunitiesTab" + static let roomScreenAllowVoIPForDirectRoom = "roomScreenAllowVoIPForDirectRoom" + static let roomScreenAllowVoIPForNonDirectRoom = "roomScreenAllowVoIPForNonDirectRoom" + static let roomScreenAllowCameraAction = "roomScreenAllowCameraAction" + static let roomScreenAllowMediaLibraryAction = "roomScreenAllowMediaLibraryAction" + static let roomScreenAllowStickerAction = "roomScreenAllowStickerAction" + static let roomScreenAllowFilesAction = "roomScreenAllowFilesAction" + static let roomContextualMenuShowMoreOptionForMessages = "roomContextualMenuShowMoreOptionForMessages" + static let roomContextualMenuShowMoreOptionForStates = "roomContextualMenuShowMoreOptionForStates" + static let roomContextualMenuShowReportContentOption = "roomContextualMenuShowReportContentOption" + static let roomInfoScreenShowIntegrations = "roomInfoScreenShowIntegrations" + static let roomMemberScreenShowIgnore = "roomMemberScreenShowIgnore" + static let unifiedSearchScreenShowPublicDirectory = "unifiedSearchScreenShowPublicDirectory" + static let hideSpaceBetaAnnounce = "hideSpaceBetaAnnounce" +>>>>>>> 9792605e19cb683f1991f354dbc575b2b1e910bb + } + + static let shared = RiotSettings() + + /// UserDefaults to be used on reads and writes. + private lazy var defaults: UserDefaults = { + guard let userDefaults = UserDefaults(suiteName: BuildSettings.applicationGroupIdentifier) else { + fatalError("[RiotSettings] Fail to load shared UserDefaults") + } + return userDefaults + }() + +<<<<<<< HEAD + // MARK: - Public + + func reset() { + defaults.removeObject(forKey: UserDefaultsKeys.settingsScreenShowChangePassword) + defaults.removeObject(forKey: UserDefaultsKeys.settingsScreenShowInviteFriends) + defaults.removeObject(forKey: UserDefaultsKeys.settingsScreenShowEnableStunServerFallback) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowSessions) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowSetupBackup) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowRestoreBackup) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowDeleteBackup) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyInfo) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyExport) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowAdvancedUnverifiedDevices) + defaults.removeObject(forKey: UserDefaultsKeys.allowInviteExernalUsers) + } + +======= +>>>>>>> 9792605e19cb683f1991f354dbc575b2b1e910bb + // MARK: Servers + + var homeserverUrlString: String { + get { + return defaults.string(forKey: UserDefaultsKeys.homeserverUrlString) ?? BuildSettings.serverConfigDefaultHomeserverUrlString + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.homeserverUrlString) + } + } + + var identityServerUrlString: String { + get { + return defaults.string(forKey: UserDefaultsKeys.identityServerUrlString) ?? BuildSettings.serverConfigDefaultIdentityServerUrlString + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.identityServerUrlString) + } + } + + // MARK: Notifications + + /// Indicate if `showDecryptedContentInNotifications` settings has been set once. + var isShowDecryptedContentInNotificationsHasBeenSetOnce: Bool { + return defaults.object(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) != nil + } + + /// Indicate if UserDefaults suite has been migrated once. + var isUserDefaultsMigrated: Bool { + return defaults.object(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) != nil + } + + func migrate() { + // read all values from standard + let dictionary = UserDefaults.standard.dictionaryRepresentation() + + // write values to suite + // remove redundant values from standard + for (key, value) in dictionary { + defaults.set(value, forKey: key) + UserDefaults.standard.removeObject(forKey: key) + } + } + + /// Indicate if encrypted messages content should be displayed in notifications. + var showDecryptedContentInNotifications: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.notificationsShowDecryptedContent) + } + } + + /// Indicate if rooms with missed notifications should be displayed first on home screen. + var pinRoomsWithMissedNotificationsOnHome: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.pinRoomsWithMissedNotifications) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.pinRoomsWithMissedNotifications) + } + } + + /// Indicate if rooms with unread messages should be displayed first on home screen. + var pinRoomsWithUnreadMessagesOnHome: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.pinRoomsWithUnreadMessages) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.pinRoomsWithUnreadMessages) + } + } + + /// Indicate to show Not Safe For Work public rooms. + var showNSFWPublicRooms: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.showNSFWPublicRooms) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.showNSFWPublicRooms) + } + } + + // MARK: User interface + + var userInterfaceTheme: String? { + get { + return defaults.string(forKey: UserDefaultsKeys.userInterfaceTheme) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.userInterfaceTheme) + } + } + + // MARK: Other + + /// Indicate if `enableCrashReport` settings has been set once. + var isEnableCrashReportHasBeenSetOnce: Bool { + return defaults.object(forKey: UserDefaultsKeys.enableCrashReport) != nil + } + + var enableCrashReport: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.enableCrashReport) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.enableCrashReport) + } + } + + var enableRageShake: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.enableRageShake) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.enableRageShake) + } + } + + // MARK: Labs + + /// Indicates if CallKit ringing is enabled for group calls. This setting does not disable the CallKit integration for group calls, only relates to ringing. + var enableRingingForGroupCalls: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.enableRingingForGroupCalls) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.enableRingingForGroupCalls) + } + } + + // MARK: Calls + + /// Indicate if `allowStunServerFallback` settings has been set once. + var isAllowStunServerFallbackHasBeenSetOnce: Bool { + return defaults.object(forKey: UserDefaultsKeys.allowStunServerFallback) != nil + } + + var allowStunServerFallback: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.allowStunServerFallback) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.allowStunServerFallback) + } + } + + // MARK: Key verification + + var hideVerifyThisSessionAlert: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.hideVerifyThisSessionAlert) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.hideVerifyThisSessionAlert) + } + } + + var hideReviewSessionsAlert: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.hideReviewSessionsAlert) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.hideReviewSessionsAlert) + } + } + + var matrixApps: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.matrixApps) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.matrixApps) + } + } + + // PIN & Biometrics + + var biometricsEnabled: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.biometricsEnabled) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.biometricsEnabled) + } + } + + var canUseBiometricsToUnlock: Bool { + get { + if let value = defaults.object(forKey: UserDefaultsKeys.canUseBiometricsToUnlock) { + return value as? Bool == true + } + // true if not provided + return true + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.canUseBiometricsToUnlock) + } + } + + var numberOfPinFailures: Int { + get { + return defaults.integer(forKey: UserDefaultsKeys.numberOfPinFailures) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.numberOfPinFailures) + } + } + + var numberOfBiometricsFailures: Int { + get { + return defaults.integer(forKey: UserDefaultsKeys.numberOfBiometricsFailures) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.numberOfBiometricsFailures) + } + } + +<<<<<<< HEAD +======= + // MARK: - Rooms Screen + + var roomsAllowToJoinPublicRooms: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomsAllowToJoinPublicRooms) != nil else { + return BuildSettings.roomsAllowToJoinPublicRooms + } + return defaults.bool(forKey: UserDefaultsKeys.roomsAllowToJoinPublicRooms) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomsAllowToJoinPublicRooms) + } + } + + // MARK: - Room Screen + + var roomScreenAllowVoIPForDirectRoom: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomScreenAllowVoIPForDirectRoom) != nil else { + return BuildSettings.roomScreenAllowVoIPForDirectRoom + } + return defaults.bool(forKey: UserDefaultsKeys.roomScreenAllowVoIPForDirectRoom) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomScreenAllowVoIPForDirectRoom) + } + } + var roomScreenAllowVoIPForNonDirectRoom: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomScreenAllowVoIPForNonDirectRoom) != nil else { + return BuildSettings.roomScreenAllowVoIPForNonDirectRoom + } + return defaults.bool(forKey: UserDefaultsKeys.roomScreenAllowVoIPForNonDirectRoom) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomScreenAllowVoIPForNonDirectRoom) + } + } + var roomScreenAllowCameraAction: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomScreenAllowCameraAction) != nil else { + return BuildSettings.roomScreenAllowCameraAction + } + return defaults.bool(forKey: UserDefaultsKeys.roomScreenAllowCameraAction) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomScreenAllowCameraAction) + } + } + var roomScreenAllowMediaLibraryAction: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomScreenAllowMediaLibraryAction) != nil else { + return BuildSettings.roomScreenAllowMediaLibraryAction + } + return defaults.bool(forKey: UserDefaultsKeys.roomScreenAllowMediaLibraryAction) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomScreenAllowMediaLibraryAction) + } + } + var roomScreenAllowStickerAction: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomScreenAllowStickerAction) != nil else { + return BuildSettings.roomScreenAllowStickerAction + } + return defaults.bool(forKey: UserDefaultsKeys.roomScreenAllowStickerAction) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomScreenAllowStickerAction) + } + } + var roomScreenAllowFilesAction: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomScreenAllowFilesAction) != nil else { + return BuildSettings.roomScreenAllowFilesAction + } + return defaults.bool(forKey: UserDefaultsKeys.roomScreenAllowFilesAction) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomScreenAllowFilesAction) + } + } + + // MARK: - Room Contextual Menu + + var roomContextualMenuShowMoreOptionForMessages: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForMessages) != nil else { + return BuildSettings.roomContextualMenuShowMoreOptionForMessages + } + return defaults.bool(forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForMessages) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForMessages) + } + } + var roomContextualMenuShowMoreOptionForStates: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForStates) != nil else { + return BuildSettings.roomContextualMenuShowMoreOptionForStates + } + return defaults.bool(forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForStates) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForStates) + } + } + var roomContextualMenuShowReportContentOption: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomContextualMenuShowReportContentOption) != nil else { + return BuildSettings.roomContextualMenuShowReportContentOption + } + return defaults.bool(forKey: UserDefaultsKeys.roomContextualMenuShowReportContentOption) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomContextualMenuShowReportContentOption) + } + } + + // MARK: - Room Info Screen + + var roomInfoScreenShowIntegrations: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomInfoScreenShowIntegrations) != nil else { + return BuildSettings.roomInfoScreenShowIntegrations + } + return defaults.bool(forKey: UserDefaultsKeys.roomInfoScreenShowIntegrations) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomInfoScreenShowIntegrations) + } + } + + // MARK: - Room Member Screen + + var roomMemberScreenShowIgnore: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomMemberScreenShowIgnore) != nil else { + return BuildSettings.roomMemberScreenShowIgnore + } + return defaults.bool(forKey: UserDefaultsKeys.roomMemberScreenShowIgnore) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomMemberScreenShowIgnore) + } + } + +>>>>>>> 9792605e19cb683f1991f354dbc575b2b1e910bb + // MARK: - Room Creation Screen + + var roomCreationScreenAllowEncryptionConfiguration: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomCreationScreenAllowEncryptionConfiguration) != nil else { + return BuildSettings.roomCreationScreenAllowEncryptionConfiguration + } + return defaults.bool(forKey: UserDefaultsKeys.roomCreationScreenAllowEncryptionConfiguration) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomCreationScreenAllowEncryptionConfiguration) + } + } + var roomCreationScreenRoomIsEncrypted: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomCreationScreenRoomIsEncrypted) != nil else { + return BuildSettings.roomCreationScreenRoomIsEncrypted + } + return defaults.bool(forKey: UserDefaultsKeys.roomCreationScreenRoomIsEncrypted) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomCreationScreenRoomIsEncrypted) + } + } + var roomCreationScreenAllowRoomTypeConfiguration: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomCreationScreenAllowRoomTypeConfiguration) != nil else { + return BuildSettings.roomCreationScreenAllowRoomTypeConfiguration + } + return defaults.bool(forKey: UserDefaultsKeys.roomCreationScreenAllowRoomTypeConfiguration) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomCreationScreenAllowRoomTypeConfiguration) + } + } + var roomCreationScreenRoomIsPublic: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomCreationScreenRoomIsPublic) != nil else { + return BuildSettings.roomCreationScreenRoomIsPublic + } + return defaults.bool(forKey: UserDefaultsKeys.roomCreationScreenRoomIsPublic) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomCreationScreenRoomIsPublic) + } + } + + // MARK: Features + + var allowInviteExernalUsers: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.allowInviteExernalUsers) != nil else { + return BuildSettings.allowInviteExernalUsers + } + return defaults.bool(forKey: UserDefaultsKeys.allowInviteExernalUsers) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.allowInviteExernalUsers) + } + } +<<<<<<< HEAD +======= + + // MARK: - Main Tabs + + var homeScreenShowFavouritesTab: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.homeScreenShowFavouritesTab) != nil else { + return BuildSettings.homeScreenShowFavouritesTab + } + return defaults.bool(forKey: UserDefaultsKeys.homeScreenShowFavouritesTab) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.homeScreenShowFavouritesTab) + } + } + var homeScreenShowPeopleTab: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.homeScreenShowPeopleTab) != nil else { + return BuildSettings.homeScreenShowPeopleTab + } + return defaults.bool(forKey: UserDefaultsKeys.homeScreenShowPeopleTab) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.homeScreenShowPeopleTab) + } + } + var homeScreenShowRoomsTab: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.homeScreenShowRoomsTab) != nil else { + return BuildSettings.homeScreenShowRoomsTab + } + return defaults.bool(forKey: UserDefaultsKeys.homeScreenShowRoomsTab) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.homeScreenShowRoomsTab) + } + } + var homeScreenShowCommunitiesTab: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.homeScreenShowCommunitiesTab) != nil else { + return BuildSettings.homeScreenShowCommunitiesTab + } + return defaults.bool(forKey: UserDefaultsKeys.homeScreenShowCommunitiesTab) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.homeScreenShowCommunitiesTab) + } + } +>>>>>>> 9792605e19cb683f1991f354dbc575b2b1e910bb + + // MARK: General Settings + + var settingsScreenShowChangePassword: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsScreenShowChangePassword) != nil else { + return BuildSettings.settingsScreenShowChangePassword + } + return defaults.bool(forKey: UserDefaultsKeys.settingsScreenShowChangePassword) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowChangePassword) + } + } + var settingsScreenShowInviteFriends: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsScreenShowInviteFriends) != nil else { + return BuildSettings.settingsScreenShowInviteFriends + } + return defaults.bool(forKey: UserDefaultsKeys.settingsScreenShowInviteFriends) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowInviteFriends) + } + } + var settingsScreenShowEnableStunServerFallback: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsScreenShowInviteFriends) != nil else { + return BuildSettings.settingsScreenShowEnableStunServerFallback + } + return defaults.bool(forKey: UserDefaultsKeys.settingsScreenShowEnableStunServerFallback) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowEnableStunServerFallback) + } + } +<<<<<<< HEAD +======= + var settingsScreenShowNotificationDecodedContentOption: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsScreenShowNotificationDecodedContentOption) != nil else { + return BuildSettings.settingsScreenShowNotificationDecodedContentOption + } + return defaults.bool(forKey: UserDefaultsKeys.settingsScreenShowNotificationDecodedContentOption) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowNotificationDecodedContentOption) + } + } + var settingsScreenShowNsfwRoomsOption: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsScreenShowNsfwRoomsOption) != nil else { + return BuildSettings.settingsScreenShowNsfwRoomsOption + } + return defaults.bool(forKey: UserDefaultsKeys.settingsScreenShowNsfwRoomsOption) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowNsfwRoomsOption) + } + } +>>>>>>> 9792605e19cb683f1991f354dbc575b2b1e910bb + var settingsSecurityScreenShowSessions: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowSessions) != nil else { + return BuildSettings.settingsSecurityScreenShowSessions + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowSessions) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowSessions) + } + } + var settingsSecurityScreenShowSetupBackup: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowSetupBackup) != nil else { + return BuildSettings.settingsSecurityScreenShowSetupBackup + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowSetupBackup) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowSetupBackup) + } + } + var settingsSecurityScreenShowRestoreBackup: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowRestoreBackup) != nil else { + return BuildSettings.settingsSecurityScreenShowRestoreBackup + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowRestoreBackup) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowRestoreBackup) + } + } + var settingsSecurityScreenShowDeleteBackup: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowDeleteBackup) != nil else { + return BuildSettings.settingsSecurityScreenShowDeleteBackup + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowDeleteBackup) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowDeleteBackup) + } + } + var settingsSecurityScreenShowCryptographyInfo: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyInfo) != nil else { + return BuildSettings.settingsSecurityScreenShowCryptographyInfo + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyInfo) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyInfo) + } + } + var settingsSecurityScreenShowCryptographyExport: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyExport) != nil else { + return BuildSettings.settingsSecurityScreenShowCryptographyExport + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyExport) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyExport) + } + } + var settingsSecurityScreenShowAdvancedUnverifiedDevices: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowAdvancedUnverifiedDevices) != nil else { + return BuildSettings.settingsSecurityScreenShowAdvancedUnverifiedDevices + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowAdvancedUnverifiedDevices) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowAdvancedUnverifiedDevices) + } + } +<<<<<<< HEAD +======= + + // MARK: - Room Settings Screen + + var roomSettingsScreenShowLowPriorityOption: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomSettingsScreenShowLowPriorityOption) != nil else { + return BuildSettings.roomSettingsScreenShowLowPriorityOption + } + return defaults.bool(forKey: UserDefaultsKeys.roomSettingsScreenShowLowPriorityOption) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomSettingsScreenShowLowPriorityOption) + } + } + var roomSettingsScreenShowDirectChatOption: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomSettingsScreenShowDirectChatOption) != nil else { + return BuildSettings.roomSettingsScreenShowDirectChatOption + } + return defaults.bool(forKey: UserDefaultsKeys.roomSettingsScreenShowDirectChatOption) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomSettingsScreenShowDirectChatOption) + } + } + var roomSettingsScreenAllowChangingAccessSettings: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomSettingsScreenAllowChangingAccessSettings) != nil else { + return BuildSettings.roomSettingsScreenAllowChangingAccessSettings + } + return defaults.bool(forKey: UserDefaultsKeys.roomSettingsScreenAllowChangingAccessSettings) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomSettingsScreenAllowChangingAccessSettings) + } + } + var roomSettingsScreenAllowChangingHistorySettings: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomSettingsScreenAllowChangingHistorySettings) != nil else { + return BuildSettings.roomSettingsScreenAllowChangingHistorySettings + } + return defaults.bool(forKey: UserDefaultsKeys.roomSettingsScreenAllowChangingHistorySettings) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomSettingsScreenAllowChangingHistorySettings) + } + } + var roomSettingsScreenShowAddressSettings: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomSettingsScreenShowAddressSettings) != nil else { + return BuildSettings.roomSettingsScreenShowAddressSettings + } + return defaults.bool(forKey: UserDefaultsKeys.roomSettingsScreenShowAddressSettings) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomSettingsScreenShowAddressSettings) + } + } + var roomSettingsScreenShowFlairSettings: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomSettingsScreenShowFlairSettings) != nil else { + return BuildSettings.roomSettingsScreenShowFlairSettings + } + return defaults.bool(forKey: UserDefaultsKeys.roomSettingsScreenShowFlairSettings) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomSettingsScreenShowFlairSettings) + } + } + var roomSettingsScreenShowAdvancedSettings: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomSettingsScreenShowAdvancedSettings) != nil else { + return BuildSettings.roomSettingsScreenShowAdvancedSettings + } + return defaults.bool(forKey: UserDefaultsKeys.roomSettingsScreenShowAdvancedSettings) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomSettingsScreenShowAdvancedSettings) + } + } + var roomSettingsScreenAdvancedShowEncryptToVerifiedOption: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomSettingsScreenAdvancedShowEncryptToVerifiedOption) != nil else { + return BuildSettings.roomSettingsScreenAdvancedShowEncryptToVerifiedOption + } + return defaults.bool(forKey: UserDefaultsKeys.roomSettingsScreenAdvancedShowEncryptToVerifiedOption) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomSettingsScreenAdvancedShowEncryptToVerifiedOption) + } + } + + // Mark: - Unified Search + + var unifiedSearchScreenShowPublicDirectory: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.unifiedSearchScreenShowPublicDirectory) != nil else { + return BuildSettings.unifiedSearchScreenShowPublicDirectory + } + return defaults.bool(forKey: UserDefaultsKeys.unifiedSearchScreenShowPublicDirectory) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.unifiedSearchScreenShowPublicDirectory) + } + } + + // MARK: - Beta + + var hideSpaceBetaAnnounce: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.hideSpaceBetaAnnounce) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.hideSpaceBetaAnnounce) + } + } +>>>>>>> 9792605e19cb683f1991f354dbc575b2b1e910bb +} diff --git a/Riot/Managers/Settings/Shared/RiotSharedSettings.swift b/Riot/Managers/Settings/Shared/RiotSharedSettings.swift index df70ad310..055e0c8db 100644 --- a/Riot/Managers/Settings/Shared/RiotSharedSettings.swift +++ b/Riot/Managers/Settings/Shared/RiotSharedSettings.swift @@ -1,5 +1,6 @@ /* Copyright 2019 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -198,10 +199,57 @@ class RiotSharedSettings: NSObject { return session.setAccountData(allowedWidgetsDict, forType: Settings.allowedWidgets, success: success, failure: failure) } + // MARK: Top Banner Features + + func topBanner(for feature: String) -> Bool { + guard let featuresDict = getAccountData(forEventType: "de.bwi.top_banner_features") else { + return true + } + return (featuresDict[feature] as? Bool) ?? true + } + + @discardableResult + func setTopBannerFeature(_ feature: String, + enabled: Bool, + success: @escaping () -> Void, + failure: @escaping (Error?) -> Void) -> MXHTTPOperation? { + + let eventType = "de.bwi.top_banner_features" + var featuresDict = getAccountData(forEventType: eventType) ?? [:] + featuresDict[feature] = enabled + + return session.setAccountData(featuresDict, forType: eventType, success: success, failure: failure) + } + + // MARK: Top Banner Features + + func happyBirthdayCampaign(for campaign: String) -> Bool { + guard let featuresDict = getAccountData(forEventType: "de.bwi.ios_happy_birthday_campaign") else { + return true + } + return (featuresDict[campaign] as? Bool) ?? true + } + + @discardableResult + func setHappyBirthdayCampaign(_ campaign: String, + enabled: Bool, + success: @escaping () -> Void, + failure: @escaping (Error?) -> Void) -> MXHTTPOperation? { + + let eventType = "de.bwi.ios_happy_birthday_campaign" + var featuresDict = getAccountData(forEventType: eventType) ?? [:] + featuresDict[campaign] = enabled + + return session.setAccountData(featuresDict, forType: eventType, success: success, failure: failure) + } + // MARK: - Private private func getAccountData(forEventType eventType: String) -> [String: Any]? { - return session.accountData.accountData(forEventType: eventType) as? [String: Any] + guard let accountData = session.accountData else { + return nil + } + return accountData.accountData(forEventType: eventType) as? [String: Any] } private func domainForNativeWidget(_ widget: Widget, fromUrl url: URL? = nil) -> String? { diff --git a/Riot/Modules/Analytics/Analytics.swift b/Riot/Modules/Analytics/Analytics.swift index d3b9c3ac1..ee86909c1 100644 --- a/Riot/Modules/Analytics/Analytics.swift +++ b/Riot/Modules/Analytics/Analytics.swift @@ -1,5 +1,6 @@ // // Copyright 2021 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -191,8 +192,7 @@ extension Analytics { /// - tap: The element that was tapped /// - index: The index of the element, if it's in a list of elements func trackTap(_ tap: AnalyticsUIElement, index: Int?) { - let event = AnalyticsEvent.Click(index: index, name: tap.elementName) - client.capture(event) + } /// Track an element that has been tapped without including an index diff --git a/Riot/Modules/Analytics/AnalyticsUIElement.swift b/Riot/Modules/Analytics/AnalyticsUIElement.swift index 93a08e7e2..983a289ba 100644 --- a/Riot/Modules/Analytics/AnalyticsUIElement.swift +++ b/Riot/Modules/Analytics/AnalyticsUIElement.swift @@ -1,5 +1,6 @@ // // Copyright 2021 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,13 +21,4 @@ import AnalyticsEvents @objc enum AnalyticsUIElement: Int { case sendMessageButton - /// The element name reported to the AnalyticsEvent. - var elementName: AnalyticsEvent.Click.Name { - switch self { - // Note: This is a test element that doesn't need to be captured. - // It will likely be removed when the AnalyticsEvent.Click is updated. - case .sendMessageButton: - return .SendMessageButton - } - } } diff --git a/Riot/Modules/Analytics/DecryptionFailureTracker.m b/Riot/Modules/Analytics/DecryptionFailureTracker.m index ea770a375..a989bd95a 100644 --- a/Riot/Modules/Analytics/DecryptionFailureTracker.m +++ b/Riot/Modules/Analytics/DecryptionFailureTracker.m @@ -1,5 +1,6 @@ /* Copyright 2018 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -164,7 +165,8 @@ NSString *const kDecryptionFailureTrackerAnalyticsCategory = @"e2e.failure"; for (NSNumber *reason in failuresCounts) { - [self.delegate trackE2EEError:reason.integerValue count:failuresCounts[reason].integerValue]; + // FRANK181 + //[self.delegate trackE2EEError:reason.integerValue count:failuresCounts[reason].integerValue]; } } } diff --git a/Riot/Modules/Application/AppCoordinator.swift b/Riot/Modules/Application/AppCoordinator.swift index d60faf924..e6891db0b 100755 --- a/Riot/Modules/Application/AppCoordinator.swift +++ b/Riot/Modules/Application/AppCoordinator.swift @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -250,6 +251,10 @@ extension AppCoordinator: LegacyAppDelegateDelegate { func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didNavigateToSpaceWithId spaceId: String!) { self.sideMenuCoordinator?.select(spaceWithId: spaceId) } + + func legacyAppDelegateDidUnlockPin(_ legacyAppDelegate: LegacyAppDelegate!) { + self.splitViewCoordinator?.pinUnlocked() + } } // MARK: - SplitViewCoordinatorDelegate diff --git a/Riot/Modules/Application/LegacyAppDelegate.h b/Riot/Modules/Application/LegacyAppDelegate.h index 40e3c2377..21a901da4 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.h +++ b/Riot/Modules/Application/LegacyAppDelegate.h @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -290,6 +291,22 @@ UINavigationControllerDelegate */ - (void)checkAppVersion; +#pragma mark - Jailbreak + +/** + Check for jailbreak + @return YES if check has passed + */ +- (BOOL)checkJailbreak; + +#pragma mark - Integrity + +/** + Check integrity of the application + @return YES if check has passed + */ +- (BOOL)checkIntegrity; + #pragma mark - Authentication /// When SSO login succeeded, when SFSafariViewController is used, continue login with success parameters. @@ -315,4 +332,6 @@ UINavigationControllerDelegate - (void)legacyAppDelegate:(LegacyAppDelegate*)legacyAppDelegate didNavigateToSpaceWithId:(NSString*)spaceId; +- (void)legacyAppDelegateDidUnlockPin:(LegacyAppDelegate*)legacyAppDelegate; + @end diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index a462a4bc0..e93a3e122 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -2,6 +2,7 @@ Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -433,10 +434,13 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni _isAppForeground = NO; _handleSelfVerificationRequest = YES; - // Configure our analytics. It will start if the option is enabled - Analytics *analytics = Analytics.shared; - [MXSDKOptions sharedInstance].analyticsDelegate = analytics; - [DecryptionFailureTracker sharedInstance].delegate = analytics; + // Configure our analytics. It will indeed start if the option is enabled + Analytics *analytics = [Analytics shared]; + + BWIAnalytics.sharedTracker.running = RiotSettings.shared.bwiEnableMatomoTracking; + [MXSDKOptions sharedInstance].analyticsDelegate = BWIAnalytics.sharedTracker; + [DecryptionFailureTracker sharedInstance].delegate = BWIAnalytics.sharedTracker; + MXBaseProfiler *profiler = [MXBaseProfiler new]; profiler.analytics = analytics; @@ -466,13 +470,16 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni #endif self.majorUpdateManager = [MajorUpdateManager new]; - + + MXLogDebug(@"[AppDelegate] didFinishLaunchingWithOptions: Done in %.0fms", [[NSDate date] timeIntervalSinceDate:startDate] * 1000); dispatch_async(dispatch_get_main_queue(), ^{ [self configurePinCodeScreenFor:application createIfRequired:YES]; }); + [self setupAppConfig]; + return YES; } @@ -526,8 +533,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni { if (self.setPinCoordinatorBridgePresenter) { - // it's already on screen, convert the viewMode - self.setPinCoordinatorBridgePresenter.viewMode = SetPinCoordinatorViewModeInactive; + // it's already on screen, return return; } self.setPinCoordinatorBridgePresenter = [[SetPinCoordinatorBridgePresenter alloc] initWithSession:mxSessionArray.firstObject viewMode:SetPinCoordinatorViewModeInactive]; @@ -635,6 +641,15 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [self.setPinCoordinatorBridgePresenter presentIn:self.window]; } } + else if (BwiBuildSettings.forcedPinProtection) + { + if (self.setPinCoordinatorBridgePresenter.viewMode == SetPinCoordinatorViewModeInactive) + { + [self.setPinCoordinatorBridgePresenter dismiss]; + self.setPinCoordinatorBridgePresenter = nil; + [self afterAppUnlockedByPin:application]; + } + } else { [self.setPinCoordinatorBridgePresenter dismiss]; @@ -724,6 +739,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Riot has its own dark theme. Prevent iOS from applying its one [application keyWindow].accessibilityIgnoresInvertColors = YES; + [BWIAnalytics.sharedTracker trackEvent:@"Session" action:@"PinLogin"]; + + [_masterTabBarController bwiOnUnlockedByPin]; + [self.delegate legacyAppDelegateDidUnlockPin:self]; + [self handleAppState]; } @@ -1934,8 +1954,18 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [self.configuration setupSettingsWhenLoadedFor:mxSession]; - // Register to user new device sign in notification - [self registerUserDidSignInOnNewDeviceNotificationForSession:mxSession]; + + if (BuildSettings.disableSelfUserVerification) + { + // We should not receive self verification requests + // but disable explicit their receiption in case they come from a buggy client. + self.handleSelfVerificationRequest = NO; + } + else + { + // Register to user new device sign in notification + [self registerUserDidSignInOnNewDeviceNotificationForSession:mxSession]; + } [self registerDidChangeCrossSigningKeysNotificationForSession:mxSession]; @@ -1972,7 +2002,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni account.mxSession.roomSummaryUpdateDelegate = eventFormatter; // Set the push gateway URL. - account.pushGatewayURL = BuildSettings.serverConfigSygnalAPIUrlString; + AppConfigService* service = [AppConfigService shared]; + account.pushGatewayURL = [service pusherUrl]; BOOL isPushRegistered = self.pushNotificationService.isPushRegistered; @@ -2074,7 +2105,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Set this url in the existing accounts when it is undefined. if (!account.pushGatewayURL) { - account.pushGatewayURL = BuildSettings.serverConfigSygnalAPIUrlString; + AppConfigService* service = [AppConfigService shared]; + account.pushGatewayURL = [service pusherUrl]; } } @@ -2271,6 +2303,84 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [self logoutSendingRequestServer:YES completion:^(BOOL isLoggedOut) { if (completion) { + if (isLoggedOut) + { + //[RiotSettings.shared reset]; + [BwiSettings.shared reset]; + [BWIAnalytics.sharedTracker trackEvent:@"Session" action:@"Logout"]; + + } + completion (YES); + } + }]; +} + +- (void)logoutWithoutConfirmation:(BOOL)askConfirmation completion:(void (^)(BOOL isLoggedOut))completion +{ + // Check whether we have to ask confirmation before logging out. + if (askConfirmation) + { + if (self.logoutConfirmation) + { + [self.logoutConfirmation dismissViewControllerAnimated:NO completion:nil]; + self.logoutConfirmation = nil; + } + + __weak typeof(self) weakSelf = self; + + NSString *message = NSLocalizedStringFromTable(@"bwi_mdm_logout_message", @"Bwi", nil); + + // Ask confirmation + self.logoutConfirmation = [UIAlertController alertControllerWithTitle:[VectorL10n settingsSignOut] message:message preferredStyle:UIAlertControllerStyleAlert]; + + [self.logoutConfirmation addAction:[UIAlertAction actionWithTitle:[VectorL10n settingsSignOut] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self.logoutConfirmation = nil; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + + [self logoutWithConfirmation:NO completion:completion]; + + }); + } + + }]]; + + [self.logoutConfirmation mxk_setAccessibilityIdentifier: @"AppDelegateLogoutConfirmationAlert"]; + [self showNotificationAlert:self.logoutConfirmation]; + return; + } + + // Display a loading wheel during the logout process + id topVC; + if (_masterTabBarController && _masterTabBarController == _masterNavigationController.visibleViewController) + { + topVC = _masterTabBarController.selectedViewController; + } + else + { + topVC = _masterNavigationController.visibleViewController; + } + if (topVC && [topVC respondsToSelector:@selector(startActivityIndicator)]) + { + [topVC startActivityIndicator]; + } + + [self logoutSendingRequestServer:YES completion:^(BOOL isLoggedOut) { + if (completion) + { + if (isLoggedOut) + { + //[RiotSettings.shared reset]; + [BwiSettings.shared reset]; + [BWIAnalytics.sharedTracker trackEvent:@"Session" action:@"Logout"]; + + } completion (YES); } }]; @@ -2373,6 +2483,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni case MXSessionStateClosed: case MXSessionStateInitialised: case MXSessionStateBackgroundSyncInProgress: + case MXSessionStateProcessingBackgroundSyncCache: self.roomListDataReady = NO; isLaunching = YES; break; @@ -2411,7 +2522,54 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni return; } + if (mainSession.homeserverWellknown.backupRequired + && mainSession.vc_canSetupSecureBackup) + { + // This only happens at the first login + // Or when migrating an existing user + MXLogDebug(@"[AppDelegate] handleAppState: Force 4S setup"); + [_masterTabBarController presentSecureBackupSetupForMatrixSession:mainSession]; + } + else + { + if (mainSession.crypto.crossSigning) + { + // Get the up-to-date cross-signing state + MXWeakify(self); + [mainSession.crypto.crossSigning refreshStateWithSuccess:^(BOOL stateUpdated) { + MXStrongifyAndReturnIfNil(self); + + MXLogDebug(@"[AppDelegate] handleAppState: crossSigning.state: %@", @(mainSession.crypto.crossSigning.state)); + + switch (mainSession.crypto.crossSigning.state) + { + case MXCrossSigningStateCrossSigningExists: + MXLogDebug(@"[AppDelegate] handleAppState: presentVerifyCurrentSessionAlertIfNeededWithSession"); + [self.masterTabBarController presentVerifyCurrentSessionAlertIfNeededWithSession:mainSession]; + break; + case MXCrossSigningStateCanCrossSign: + MXLogDebug(@"[AppDelegate] handleAppState: presentReviewUnverifiedSessionsAlertIfNeededWithSession"); + [self.masterTabBarController presentReviewUnverifiedSessionsAlertIfNeededWithSession:mainSession]; + break; + default: + break; + } + } failure:^(NSError * _Nonnull error) { + MXLogDebug(@"[AppDelegate] handleAppState: crossSigning.state: %@. Error: %@", @(mainSession.crypto.crossSigning.state), error); + }]; + } + } + + // TODO: We should wait that cross-signing screens are done before going further but it seems fine. Those screens + // protect each other. + + // This is the time to check existing requests + MXLogDebug(@"[AppDelegate] handleAppState: Check pending verification requests"); + [self checkPendingRoomKeyRequests]; + [self checkPendingIncomingKeyVerificationsInSession:mainSession]; + [self ensureRoomListDataReadyWithCompletion:^{ + [self hideLaunchAnimation]; if (self.setPinCoordinatorBridgePresenter) @@ -3880,6 +4038,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [crypto setOutgoingKeyRequestsEnabled:YES onComplete:nil]; } [self dismissKeyVerificationCoordinatorBridgePresenter]; + [BWIAnalytics.sharedTracker trackEvent:@"Session" action:@"Login"]; } - (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter @@ -4485,6 +4644,34 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni } } +#pragma mark - Jailbreak + +- (BOOL)checkJailbreak +{ + JailbreakCheck *jailbreakCheck = [[JailbreakCheck alloc] init]; + + BOOL breached = [jailbreakCheck isBreached]; + if (breached) + { + [self.presentedViewController presentViewController:[jailbreakCheck breachAlert] animated:YES completion:nil]; + } + return !breached; +} + +#pragma mark - Integrity + +- (BOOL)checkIntegrity +{ + IntegrityCheck *integrityCheck = [[IntegrityCheck alloc] init]; + + BOOL breached = [integrityCheck isBreached]; + if (breached) + { + [self.presentedViewController presentViewController:[integrityCheck breachAlert] animated:YES completion:nil]; + } + return !breached; +} + #pragma mark - CallPresenterDelegate - (void)callPresenter:(CallPresenter *)presenter presentCallViewController:(CallViewController *)viewController completion:(void (^)(void))completion @@ -4626,4 +4813,18 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [self openSpaceWithId:spaceId]; } +#pragma mark - bwi App Config + +- (void) setupAppConfig +{ + AppConfigService* service = [AppConfigService shared]; + service.completion = ^(BOOL success) { + if( success) { + [self logoutWithoutConfirmation:YES completion:nil]; + NSLog(@"New AppConfig"); + } + }; + [service registerForAppConfig]; +} + @end diff --git a/Riot/Modules/Authentication/AuthenticationViewController.h b/Riot/Modules/Authentication/AuthenticationViewController.h index 894ea8b45..3c986a788 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.h +++ b/Riot/Modules/Authentication/AuthenticationViewController.h @@ -2,6 +2,7 @@ Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -32,6 +33,7 @@ @property (weak, nonatomic) IBOutlet UIButton *forgotPasswordButton; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *submitButtonMinLeadingConstraint; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *authInputsContainerViewTopConstraint; @property (weak, nonatomic) IBOutlet UIView *serverOptionsContainer; @property (weak, nonatomic) IBOutlet UIButton *customServersTickButton; @@ -46,6 +48,9 @@ @property (weak, nonatomic) IBOutlet UILabel *softLogoutClearDataLabel; @property (weak, nonatomic) IBOutlet UIButton *softLogoutClearDataButton; +@property (weak, nonatomic) IBOutlet UILabel *headlineIntroLabel; +@property (weak, nonatomic) IBOutlet UILabel *subheadlineIntroLabel; + - (void)showCustomHomeserver:(NSString*)homeserver andIdentityServer:(NSString*)identityServer; /// When SSO login succeeded, when SFSafariViewController is used, continue login with success parameters. diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 4f76a029b..614eb3951 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -2,6 +2,7 @@ Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2019 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -82,6 +83,14 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; @property (nonatomic, getter = isFirstViewAppearing) BOOL firstViewAppearing; +@property (nonatomic, strong) UISwitch *bwServerOptionsSwitch; +@property (nonatomic, strong) UILabel *bwServerOptionsLabel; +@property (nonatomic, strong) UISegmentedControl *bwServerOptionsControl; + +@property (nonatomic, strong) UIButton *bwServerPreSelectionButton; + +@property (nonatomic, strong) UIButton *bwRegisterButton; + @property (nonatomic, strong) MXKErrorAlertPresentation *errorPresenter; @end @@ -124,6 +133,11 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; - (void)viewDidLoad { + /* + * An alert message will be displayed when a Man in the middle is detected. + */ + [self displayCouldNotVerifyIdentityOfRemoteServer]; + [super viewDidLoad]; self.navigationItem.title = nil; @@ -132,9 +146,10 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; target:self action:@selector(onButtonPressed:)]; - self.defaultHomeServerUrl = RiotSettings.shared.homeserverUrlString; + AppConfigService *service = AppConfigService.shared; - self.defaultIdentityServerUrl = RiotSettings.shared.identityServerUrlString; + self.defaultHomeServerUrl = service.serverUrl; + self.defaultIdentityServerUrl = service.serverUrl; self.welcomeImageView.image = [UIImage imageNamed:@"horizontal_logo"]; @@ -161,7 +176,15 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; self.serverOptionsContainer.hidden = !BuildSettings.authScreenShowCustomServerOptions; [self hideCustomServers:YES]; - + + if( BuildSettings.authScreenShowTestServerOptions ) { + [self enableTestServerControl]; + } + + if( BuildSettings.bwiEnableRegisterInfo ) { + [self enableRegisterButton]; + } + // Soft logout section self.softLogoutClearDataButton.layer.cornerRadius = 5; self.softLogoutClearDataButton.clipsToBounds = YES; @@ -176,6 +199,10 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; self.homeServerTextField.placeholder = [VectorL10n authHomeServerPlaceholder]; self.identityServerTextField.placeholder = [VectorL10n authIdentityServerPlaceholder]; + // Headline and subheadlineIntroLabel + self.headlineIntroLabel.text = NSLocalizedStringFromTable(@"auth_login_headline_text", @"Vector", nil); + self.subheadlineIntroLabel.text = NSLocalizedStringFromTable(@"auth_login_subheadline_text", @"Vector", nil); + self.authenticationActivityIndicatorContainerView.layer.cornerRadius = 5; [self.authenticationActivityIndicator addObserver:self forKeyPath:@"hidden" @@ -193,6 +220,8 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; [authInputsView setAuthSession:authSession withAuthType:MXKAuthenticationTypeLogin]; self.authInputsView = authInputsView; + [self setupPasswordIndicator: authInputsView]; + // Listen to action within the child view [authInputsView.ssoButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; @@ -210,6 +239,42 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; [self updateUniversalLink]; _keyboardAvoider = [[KeyboardAvoider alloc] initWithScrollViewContainerView:self.view scrollView:self.authenticationScrollView]; + + [BwiSettings.shared reset]; +} + +-(void)setupPasswordIndicator:(AuthInputsView *)authInputsView +{ + // make sure that we always start with password entry field where the user input is hidden + UITextField *passwordTextField = [authInputsView passWordTextField]; + [passwordTextField setSecureTextEntry:TRUE]; + + if (BuildSettings.passwordIndicatorOnLogin) + { + [authInputsView.passwordVisibilityToggleButton setTintColor: ThemeService.shared.theme.tintColor]; + [authInputsView.passwordVisibilityToggleButton addTarget:self action:@selector(onPasswordVisibilityToggleButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + } else { + [authInputsView.passwordVisibilityToggleButton setHidden:TRUE]; + [authInputsView.passwordVisibilityToggleButton setUserInteractionEnabled:FALSE]; + } +} + +- (void)presentForgotPasswordInformationAlert +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"auth_forgot_password", @"Vector", nil) + message:NSLocalizedStringFromTable(@"bwi_auth_forgot_password_alert_text", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:UIAlertActionStyleDefault handler:nil]]; + [self presentViewController:alert animated:YES completion:nil]; +} + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id )coordinator +{ + UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation]; + self.authInputsContainerViewTopConstraint.constant = deviceOrientation == UIDeviceOrientationPortrait ? 190 : 160; + + MXAuthenticationSession *authSession = [MXAuthenticationSession modelFromJSON:@{@"flows":@[@{@"stages":@[kMXLoginFlowTypePassword]}]}]; + [self.authInputsView setAuthSession:authSession withAuthType:MXKAuthenticationTypeLogin]; } - (void)userInterfaceThemeDidChange @@ -269,6 +334,9 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; self.identityServerSeparator.backgroundColor = ThemeService.shared.theme.lineBreakColor; self.identityServerTextField.textColor = ThemeService.shared.theme.textPrimaryColor; self.identityServerLabel.textColor = ThemeService.shared.theme.textSecondaryColor; + + self.headlineIntroLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + self.subheadlineIntroLabel.textColor = ThemeService.shared.theme.textSecondaryColor; self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; @@ -340,6 +408,8 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; [NSException raise:@"False logout. Kill the app" format:@"AuthenticationViewController has been displayed whereas there is an existing account"]; } } + + [[AppDelegate theDelegate] checkJailbreak]; } - (void)viewDidDisappear:(BOOL)animated @@ -357,6 +427,11 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; { [self refreshContentViewHeightConstraint]; } + + // bwRegisterButton has no constraints so we must set the frame position and size here + CGRect passwordframe = self.forgotPasswordButton.frame; + CGRect registerFrame = CGRectMake(self.forgotPasswordButton.frame.origin.x, passwordframe.origin.y + 55, self.forgotPasswordButton.frame.size.width , passwordframe.size.height); + self.bwRegisterButton.frame = registerFrame; } - (void)destroy @@ -478,8 +553,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; // Hide input view when there is only social login actions to present at login if ((self.authType == MXKAuthenticationTypeLogin) && self.currentLoginSSOFlow - && !self.isAuthSessionContainsPasswordFlow - && BuildSettings.authScreenShowSocialLoginSection) + && !self.isAuthSessionContainsPasswordFlow) { hideAuthInputView = YES; } @@ -883,6 +957,18 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; return NO; } + +- (IBAction)onPasswordVisibilityToggleButtonPressed:(UIButton *)button { + // make sure we get the correct view and not the matrix kit superview which we don't want + AuthInputsView* bwiAuthInputView = (AuthInputsView *)[self authInputsView]; + + UITextField *passwordTextField = [bwiAuthInputView passWordTextField]; + [passwordTextField setSecureTextEntry:![passwordTextField isSecureTextEntry]]; + + UIImage *eyeImage = [UIImage imageNamed:passwordTextField.isSecureTextEntry ? @"reveal_password_button" : @"hide_password_button"]; + [button setImage:eyeImage forState:UIControlStateNormal]; +} + - (IBAction)onButtonPressed:(id)sender { if (sender == self.customServersTickButton) @@ -891,7 +977,10 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; } else if (sender == self.forgotPasswordButton) { - if (!self.isIdentityServerConfigured) + if( [BuildSettings forgotPasswordInformationAlert] ) { + [self presentForgotPasswordInformationAlert]; + } + else if (!self.isIdentityServerConfigured) { UIAlertController *alert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n error] message:[VectorL10n authForgotPasswordErrorNoConfiguredIdentityServer] @@ -1081,17 +1170,6 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; } self.forgotPasswordButton.hidden = !showForgotPasswordButton; - - // Adjust minimum leading constraint of the submit button - if (self.forgotPasswordButton.isHidden) - { - self.submitButtonMinLeadingConstraint.constant = 19; - } - else - { - CGRect frame = self.forgotPasswordButton.frame; - self.submitButtonMinLeadingConstraint.constant = frame.origin.x + frame.size.width + 10; - } } - (void)afterSetPinFlowCompletedWithCredentials:(MXCredentials*)credentials @@ -1229,6 +1307,11 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; { constant += [self socialLoginViewHeightFittingWidth:self.contentView.frame.size.width]; } + + if (BuildSettings.bwiEnableRegisterInfo) + { + constant += 100; + } self.contentViewHeightConstraint.constant = constant; @@ -1237,7 +1320,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; - (void)hideCustomServers:(BOOL)hidden { - if (self.customServersContainer.isHidden == hidden) + if (self.customServersContainer.isHidden == hidden || BuildSettings.authScreenShowTestServerOptions) { return; } @@ -1620,6 +1703,40 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; [self hideCustomServers:NO]; } + +-(void)displayCouldNotVerifyIdentityOfRemoteServer +{ + __weak typeof(self) weakSelf = self; + + [self setOnUnrecognizedCertificateBlock:^BOOL(NSData *certificate) { + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + + NSString *homeserverURL = weakSelf.defaultHomeServerUrl; + + NSString *title = [NSBundle mxk_localizedStringForKey:@"ssl_could_not_verify"]; + NSString *homeserverURLStr = [NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"ssl_homeserver_url"], homeserverURL]; + NSString *fingerprint = [NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"ssl_fingerprint_hash"], @"SHA256"]; + NSString *certFingerprint = [certificate mx_SHA256AsHexString]; + + NSString *msg = [NSString stringWithFormat:@"%@\n\n%@\n\n%@\n\n%@\n\n%@\n\n%@", [NSBundle mxk_localizedStringForKey:@"ssl_cert_not_trust"], + [NSBundle mxk_localizedStringForKey:@"ssl_cert_new_account_expl"], homeserverURLStr, fingerprint, certFingerprint, + [NSBundle mxk_localizedStringForKey:@"ssl_only_accept"]]; + + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title + message:msg + preferredStyle:UIAlertControllerStyleAlert]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf presentViewController:alertController animated:YES completion:nil]; + }); + + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + + return false; + }]; +} + #pragma mark - KeyVerificationCoordinatorBridgePresenterDelegate - (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId @@ -1709,7 +1826,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; { SocialLoginButtonMode socialLoginButtonMode = SocialLoginButtonModeContinue; - BOOL showSocialLoginView = BuildSettings.authScreenShowSocialLoginSection && (self.currentLoginSSOFlow ? YES : NO); + BOOL showSocialLoginView = (self.currentLoginSSOFlow ? YES : NO); switch (self.authType) { @@ -1817,4 +1934,142 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; [self loginWithToken:token]; } +#pragma mark - bwi Extentions + +// rotermund: As the goal is to be as less invasive as possible, I use the textfields provided by New Vector as business logic. To make this look nicer I would have to make changes in e.g. updateRestClient + +- (void) enableTestServerControl { + for (UIView *view in self.customServersContainer.subviews) { + view.hidden = YES; + } + self.customServersTickButton.hidden = YES; + self.customServersContainer.hidden = NO; + + if (@available(iOS 14.0, *)) { + self.bwServerOptionsLabel = [[UILabel alloc] initWithFrame:CGRectMake(40, 60, 200, 30)]; + self.bwServerOptionsLabel.text = NSLocalizedStringFromTable(@"bwi_auth_beta_selection_title", @"Bwi", nil); +// [self.customServersContainer addSubview:self.bwServerOptionsLabel]; + + self.homeServerTextField = [self serverSelectionTextField]; + + self.bwServerPreSelectionButton = [self serverSelectionButton]; + [self.customServersContainer addSubview:self.bwServerPreSelectionButton]; + } else { + NSArray *itemArray = [NSArray arrayWithObjects: @"Produktion", @"MSG", @"UAT", nil]; + + self.bwServerOptionsControl = [[UISegmentedControl alloc] initWithItems:itemArray]; + self.bwServerOptionsControl.frame = CGRectMake(40, 90, self.customServersContainer.frame.size.width-80, 30); + [self.bwServerOptionsControl addTarget:self action:@selector(serverOptionsControlChanged:) forControlEvents: UIControlEventValueChanged]; + self.bwServerOptionsControl.selectedSegmentIndex = 0; + + [self.customServersContainer addSubview:self.bwServerOptionsControl]; + + self.bwServerOptionsLabel = [[UILabel alloc] initWithFrame:CGRectMake(40, 60, 200, 30)]; + self.bwServerOptionsLabel.text = NSLocalizedStringFromTable(@"bwi_auth_beta_selection_title", @"Bwi", nil); +// [self.customServersContainer addSubview:self.bwServerOptionsLabel]; + self.customServersContainer.hidden = NO; + } +} + +- (void) enableRegisterButton { + self.bwRegisterButton = [UIButton buttonWithType:UIButtonTypeSystem]; + + NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + paragraphStyle.alignment = NSTextAlignmentCenter; + NSMutableAttributedString *registerString = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"bwi_auth_register_button_title", @"Vector", nil) attributes: @{NSParagraphStyleAttributeName:paragraphStyle}]; + [registerString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, [registerString length])]; + + [self.bwRegisterButton setAttributedTitle:registerString forState:UIControlStateNormal]; + [self.bwRegisterButton setTitleColor:ThemeService.shared.theme.tintColor forState:UIControlStateNormal]; + + [self.bwRegisterButton addTarget:self action:@selector(bwiOnRegisterButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + + [self.optionsContainer addSubview:self.bwRegisterButton]; + [self.optionsContainer bringSubviewToFront:self.bwRegisterButton]; +} + +- (UITextField*)serverSelectionTextField { + UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(40, 90, self.customServersContainer.frame.size.width-80, 30)]; + textField.text = BuildSettings.serverConfigPreSelections[@"Prod"]; + textField.borderStyle = UITextBorderStyleRoundedRect; + textField.font = [UIFont systemFontOfSize:15]; + textField.placeholder = @"enter text"; + textField.autocorrectionType = UITextAutocorrectionTypeNo; + textField.keyboardType = UIKeyboardTypeDefault; + textField.returnKeyType = UIReturnKeyDone; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; + textField.delegate = self; + + [self.customServersContainer addSubview:textField]; + + return textField; +} + +- (UIButton*)serverSelectionButton { + + if (@available(iOS 14.0, *)) { + NSMutableArray* actions = [[NSMutableArray alloc] init]; + + for (NSString *key in BuildSettings.serverConfigPreSelections) { + [actions addObject:[UIAction actionWithTitle:key image:nil identifier:nil handler:^(__kindof UIAction* _Nonnull action) { + [self setHomeServerTextFieldText:BuildSettings.serverConfigPreSelections[key]]; + }]]; + } + + UIMenu* menu = [UIMenu menuWithChildren:actions]; + + UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; + button.menu = menu; + button.showsMenuAsPrimaryAction = true; + + button.frame = CGRectMake(40, 120, self.customServersContainer.frame.size.width-80, 30); + [button setTitle:NSLocalizedStringFromTable(@"bwi_auth_beta_selection_button_title", @"Bwi", nil) forState:UIControlStateNormal]; + button.titleLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + button.backgroundColor = ThemeService.shared.theme.tintColor; + + return button; + } else { + return nil; + } +} + +- (void)serverOptionsControlChanged:(UISegmentedControl *)segment +{ + + switch (segment.selectedSegmentIndex) { + case 0: + [self setHomeServerTextFieldText:BuildSettings.serverConfigPreSelections[@"Prod"]]; + [self setHomeServerTextFieldText:BuildSettings.serverConfigPreSelections[@"Prod"]]; + break; + case 1: + [self setHomeServerTextFieldText:BuildSettings.serverConfigPreSelections[@"MSGDemo"]]; + [self setHomeServerTextFieldText:BuildSettings.serverConfigPreSelections[@"MSGDemo"]]; + break; + case 2: + [self setHomeServerTextFieldText:BuildSettings.serverConfigPreSelections[@"UAT"]]; + [self setHomeServerTextFieldText:BuildSettings.serverConfigPreSelections[@"UAT"]]; + break; + case 3: + [self setHomeServerTextFieldText:BuildSettings.serverConfigPreSelections[@"Lab23"]]; + [self setHomeServerTextFieldText:BuildSettings.serverConfigPreSelections[@"Lab23"]]; + break; + default: + break; + } +} + +- (void) bwiOnRegisterButtonPressed:(id)sender { + [self bwiPresentRegisterInformationAlert]; +} + +- (void) bwiPresentRegisterInformationAlert +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"bwi_auth_register_alert_title", @"Vector", nil) + message:NSLocalizedStringFromTable(@"bwi_auth_register_alert_text", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:UIAlertActionStyleDefault handler:nil]]; + [self presentViewController:alert animated:YES completion:nil]; +} + @end diff --git a/Riot/Modules/Authentication/AuthenticationViewController.xib b/Riot/Modules/Authentication/AuthenticationViewController.xib index c779ced89..04c5ca729 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.xib +++ b/Riot/Modules/Authentication/AuthenticationViewController.xib @@ -1,11 +1,10 @@ - + - + - @@ -16,9 +15,9 @@ + - @@ -46,7 +45,6 @@ - @@ -60,44 +58,23 @@ - - - - - - - - + - + - + - - - + + + - - - + + + + @@ -255,7 +261,7 @@ - + + + + + diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h index 745ab180c..f0f542283 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h @@ -1,6 +1,7 @@ /* Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,11 +31,12 @@ */ typedef NS_ENUM(NSInteger, RecentsDataSourceMode) { - RecentsDataSourceModeHome = 1, RecentsDataSourceModeFavourites, RecentsDataSourceModePeople, - RecentsDataSourceModeRooms -}; + RecentsDataSourceModeRooms, + RecentsDataSourceModeHome + +}; /** List the different secure backup banners that could be displayed. diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 0a7328bef..7337e95fd 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -1,6 +1,7 @@ /* Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -121,8 +122,23 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou return self.recentsListService.invitedRoomListData.rooms; } - (NSArray> *)favoriteCellDataArray -{ - return self.recentsListService.favoritedRoomListData.rooms; +{ + NSMutableArray> *filteredArray = [NSMutableArray arrayWithArray:self.recentsListService.favoritedRoomListData.rooms]; + + if( _recentsDataSourceMode == RecentsDataSourceModeFavourites) { + PersonalNotesDefaultService *service = [[PersonalNotesDefaultService alloc] initWithMxSession:self.mxSession]; + PersonalNotesSettings *settings = [[PersonalNotesSettings alloc] init]; + + if ([service personalNotesRoomId] && settings.personalNotesVisible) { + MXRoom* room = [self.mxSession roomWithRoomId:[service personalNotesRoomId]]; + if (room) { + MXRoomSummary *summary = [self.mxSession roomSummaryWithRoomId:[service personalNotesRoomId]]; + [filteredArray insertObject:summary atIndex:0]; + } + } + } + + return filteredArray; } - (NSArray> *)peopleCellDataArray { @@ -130,7 +146,16 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou } - (NSArray> *)conversationCellDataArray { - return self.recentsListService.conversationRoomListData.rooms; + NSMutableArray> *filteredArray = [NSMutableArray arrayWithCapacity:self.recentsListService.conversationRoomListData.rooms.count]; + + for ( id object in self.recentsListService.conversationRoomListData.rooms) { + MXRoom* room = [self.mxSession roomWithRoomId:object.roomId]; + if (!room.isPersonalNotesRoom) { + [filteredArray addObject:object]; + } + } + + return filteredArray; } - (NSArray> *)lowPriorityCellDataArray { @@ -487,7 +512,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou invitesSection = sectionsCount++; } - if (self.favoriteCellDataArray.count > 0) + if (self.favoriteCellDataArray.count > 0 || _recentsDataSourceMode == RecentsDataSourceModeFavourites) { favoritesSection = sectionsCount++; } diff --git a/Riot/Modules/Common/Recents/RecentsBannerViewController.h b/Riot/Modules/Common/Recents/RecentsBannerViewController.h new file mode 100644 index 000000000..031b45985 --- /dev/null +++ b/Riot/Modules/Common/Recents/RecentsBannerViewController.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "RecentsViewController.h" + +@interface RecentsBannerViewController : RecentsViewController + ++ (bool)isBannerHidden; + +- (void)setupTopBanner; + +@end diff --git a/Riot/Modules/Common/Recents/RecentsBannerViewController.m b/Riot/Modules/Common/Recents/RecentsBannerViewController.m new file mode 100644 index 000000000..23092f03f --- /dev/null +++ b/Riot/Modules/Common/Recents/RecentsBannerViewController.m @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2021 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "RecentsBannerViewController.h" +#import "GeneratedInterface-Swift.h" + +@interface RecentsBannerViewController () +{ + TopBannerViewController *topBannerViewController; + bool bannersInitialized; + bool isBannerVisible; +} +- (void)setTopBannerViewConstraints; +- (void)showTopBanner; +- (void)hideTopBanner; +@end + +@implementation RecentsBannerViewController + ++ (bool)isBannerHidden { + NSDictionary *info = [[NSBundle mainBundle] infoDictionary]; + NSString *version = [info objectForKey:@"CFBundleShortVersionString"]; + if( [version isEqual:[[NSUserDefaults standardUserDefaults] objectForKey:@"de.bwi.messenger.top_banner_confirmation"]] ) + return TRUE; + else + return FALSE; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + bannersInitialized = FALSE; + isBannerVisible = FALSE; +} + +- (void)onMatrixSessionChange +{ + [super onMatrixSessionChange]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + if( [BuildSettings showTopBanner] ) { + [self showTopBanner]; + } +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + + if( bannersInitialized && [BuildSettings showTopBanner] ) { + [self hideTopBanner]; + } +} + +- (void)setTopBannerViewConstraints +{ + NSLayoutConstraint *alignTopConstraint = [NSLayoutConstraint + constraintWithItem:topBannerViewController.view + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:0]; + [[self view] addConstraint:alignTopConstraint]; + + NSLayoutConstraint *alignLeadingConstraint = [NSLayoutConstraint + constraintWithItem:topBannerViewController.view + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeLeading + multiplier:1.0 + constant:0]; + [[self view] addConstraint:alignLeadingConstraint]; + + NSLayoutConstraint *alignTrailingConstraint = [NSLayoutConstraint + constraintWithItem:topBannerViewController.view + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeTrailing + multiplier:1.0 + constant:0]; + [[self view] addConstraint:alignTrailingConstraint]; + + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint + constraintWithItem:topBannerViewController.view + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:NULL + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:130]; + [[self view] addConstraint:heightConstraint]; +} + +- (void)setupTopBanner +{ + NSMutableArray *bannerControllers = [NSMutableArray array]; + + MXSession* session = [self mainSession]; + if( !session || bannersInitialized) + return; + + NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; + + FeatureBannerVisibilityService *service = [[FeatureBannerVisibilityService alloc] initWithMxSession:session]; + [service isUnreadWithVersion:version completion:^(BOOL unread) { + if (unread) { + [bannerControllers addObject:[[FeatureBannerViewController alloc] initWithSession:session version:version]]; + + self->topBannerViewController = [[TopBannerViewController alloc] initWithControllers: bannerControllers]; + + // this notification will be called either if the user clicked on the banner or wants to hide it using a swipe gesture + [[NSNotificationCenter defaultCenter] addObserverForName:@"de.bwi.messenger.hide_top_banner" object:NULL queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { + [self hideTopBanner]; + [self->topBannerViewController removeAdvertiseViewControllers]; + }]; + + self->bannersInitialized = TRUE; + + if( [BuildSettings showTopBanner] ) { + [self showTopBanner]; + } + } + }]; + +} + +- (void)showTopBanner +{ + if( isBannerVisible ) { + return; // nothing to do because the banner is already visible for the user + } + + if( [topBannerViewController.advertiseViewControllers count] > 0 ) + { + // add child view controller + [self addChildViewController:topBannerViewController]; + [[topBannerViewController view] setTranslatesAutoresizingMaskIntoConstraints:FALSE]; + [[topBannerViewController view] setBounds:CGRectMake(0, 0, self.view.bounds.size.width, 100)]; + [[self view] addSubview:[topBannerViewController view]]; + [topBannerViewController didMoveToParentViewController:self]; + + [self setTopBannerViewConstraints]; + + isBannerVisible = TRUE; + } +} + +- (void)hideTopBanner +{ + if( [[topBannerViewController advertiseViewControllers] count] > 0 ) + { + // remove child view controller + [topBannerViewController willMoveToParentViewController:nil]; + [topBannerViewController.view removeFromSuperview]; + [topBannerViewController removeFromParentViewController]; + } + + isBannerVisible = FALSE; +} + +@end diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index 0aa137a5a..a6a3a501e 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -2,6 +2,7 @@ Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -170,10 +171,9 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - MXStrongifyAndReturnIfNil(self); - - [self userInterfaceThemeDidChange]; - + dispatch_async(dispatch_get_main_queue(), ^{ + [self userInterfaceThemeDidChange]; + }); }]; [self userInterfaceThemeDidChange]; } @@ -1076,6 +1076,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro editedRoomId = room.roomId; UIColor *selectedColor = ThemeService.shared.theme.tintColor; + UIColor *selectedWarningColor = ThemeService.shared.theme.warningColor; UIColor *unselectedColor = ThemeService.shared.theme.tabBarUnselectedItemTintColor; UIColor *actionBackgroundColor = ThemeService.shared.theme.baseColor; @@ -1099,38 +1100,44 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro // Notification toggle - BOOL isMuted = room.isMute || room.isMentionsOnly; + UIContextualAction *muteAction = nil; - UIContextualAction *muteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive - title:title - handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { + if (BuildSettings.featureWorkTime) { + muteAction = [self muteActionWithWorkTime:room]; + } else { + BOOL isMuted = room.isMute || room.isMentionsOnly; + muteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive + title:title + handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { + + if ([BuildSettings roomSettingsScreenShowNotificationsV2]) + { + [self changeEditedRoomNotificationSettings]; + } + else + { + [self muteEditedRoomNotifications:!isMuted]; + } + + + completionHandler(YES); + }]; + muteAction.backgroundColor = actionBackgroundColor; + + UIImage *notificationImage; if ([BuildSettings showNotificationsV2]) { - [self changeEditedRoomNotificationSettings]; + notificationImage = isMuted ? [UIImage imageNamed:@"room_action_notification_muted"] : [UIImage imageNamed:@"room_action_notification"]; } else { - [self muteEditedRoomNotifications:!isMuted]; + notificationImage = [UIImage imageNamed:@"room_action_notification"]; } - - - completionHandler(YES); - }]; - muteAction.backgroundColor = actionBackgroundColor; - - UIImage *notificationImage; - if([BuildSettings showNotificationsV2]) - { - notificationImage = isMuted ? [UIImage imageNamed:@"room_action_notification_muted"] : [UIImage imageNamed:@"room_action_notification"]; - } - else - { - notificationImage = [UIImage imageNamed:@"room_action_notification"]; - } - notificationImage = [notificationImage vc_tintedImageUsingColor:isMuted ? unselectedColor : selectedColor]; - muteAction.image = [notificationImage vc_notRenderedImage]; + notificationImage = [notificationImage vc_tintedImageUsingColor:isMuted ? unselectedColor : selectedColor]; + muteAction.image = [notificationImage vc_notRenderedImage]; + } // Favorites management @@ -1179,7 +1186,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro priorityAction.image = [priorityImage vc_notRenderedImage]; // Leave action - + UIContextualAction *leaveAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:title handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { @@ -1189,18 +1196,28 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro leaveAction.backgroundColor = actionBackgroundColor; UIImage *leaveImage = [UIImage imageNamed:@"room_action_leave"]; - leaveImage = [leaveImage vc_tintedImageUsingColor:unselectedColor]; + leaveImage = [leaveImage vc_tintedImageUsingColor:selectedWarningColor]; leaveAction.image = [leaveImage vc_notRenderedImage]; // Create swipe action configuration - NSArray *actions = @[ - leaveAction, - priorityAction, - favouriteAction, - muteAction, - directChatAction - ]; + NSArray *actions = nil; + + if ( [room isPersonalNotesRoom]) { + actions = @[ + favouriteAction + ]; + } else { + actions = @[ + leaveAction, + // priorityAction, + favouriteAction, + muteAction + // directChatAction + ]; + } + + UISwipeActionsConfiguration *swipeActionConfiguration = [UISwipeActionsConfiguration configurationWithActions:actions]; swipeActionConfiguration.performsFirstActionWithFullSwipe = NO; @@ -2374,4 +2391,95 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro self.roomNotificationSettingsCoordinatorBridgePresenter = nil; } +#pragma mark - Bwi Rest Time + +- (UIContextualAction*) muteActionWithWorkTime:(MXRoom*)room { + BOOL isMuted = room.isMute || room.isMentionsOnly; + BOOL isWorkTime = [self isWorkTimeActive:room]; + + UIContextualAction *muteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive + title:@"" + handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { + [self muteEditedRoomNotificationsWithWorkTime:!isMuted]; + completionHandler(YES); + }]; + muteAction.backgroundColor = ThemeService.shared.theme.baseColor; + muteAction.image =[self editedRoomNotififcationImageForMuted:isMuted andWorkTime:isWorkTime]; + + return muteAction; +} + +- (void)muteEditedRoomNotificationsWithWorkTime:(BOOL)mute +{ + if (editedRoomId) + { + // Check whether the user didn't leave the room + MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId]; + if (room) + { + [self startActivityIndicator]; + + if (mute) + { + [room mentionsOnly:^{ + + [self stopActivityIndicator]; + + // Leave editing mode + [self cancelEditionMode:self->isRefreshPending]; + + }]; + } + else + { + [room allMessages:^{ + + [self stopActivityIndicator]; + + // Leave editing mode + [self cancelEditionMode:self->isRefreshPending]; + + }]; + WorkTimeService *service = [WorkTimeService workTimeService:self.mainSession.myUserId]; + + if ([service isWorkTimeGlobalyEnabled]) { + [service activateWorkTimeInRoom:editedRoomId]; + } + } + } + else + { + // Leave editing mode + [self cancelEditionMode:isRefreshPending]; + } + } +} + +- (BOOL) isWorkTimeActive:(MXRoom*)room { + WorkTimeService *service = [WorkTimeService workTimeService:self.mainSession.myUser.userId]; + + if ([service isWorkTimeGlobalyEnabled]) { + if ([service.store isWorkTimeRoom:room.roomId]) { + return [service.store isWorkTimeInRoomActive:room.roomId]; + } else { + return !room.isDirect; + } + } else { + return NO; + } +} + +- (UIImage*)editedRoomNotififcationImageForMuted:(BOOL)isMuted andWorkTime:(BOOL)WorkTimeActive { + UIImage *image = nil; + + if (WorkTimeActive && !isMuted ) { + image = [[UIImage imageNamed:@"room_action_resttime"] vc_tintedImageUsingColor:isMuted ? ThemeService.shared.theme.tabBarUnselectedItemTintColor + : ThemeService.shared.theme.tintColor]; + } else { + image = [[UIImage imageNamed:@"room_action_notification"] vc_tintedImageUsingColor:isMuted ? ThemeService.shared.theme.tabBarUnselectedItemTintColor + : ThemeService.shared.theme.tintColor]; + } + + return [image vc_notRenderedImage]; +} @end diff --git a/Riot/Modules/Common/WebViewController/WebViewViewController.m b/Riot/Modules/Common/WebViewController/WebViewViewController.m index 4826219ec..c3fd5479f 100644 --- a/Riot/Modules/Common/WebViewController/WebViewViewController.m +++ b/Riot/Modules/Common/WebViewController/WebViewViewController.m @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -76,4 +77,21 @@ [super destroy]; } +- (void)setURL:(NSString *)URL +{ + if (webView && BuildSettings.enableJSInWebView == false) { + webView.configuration.preferences.javaScriptEnabled = NO; + } + + [super setURL:URL]; +} + +- (void)setLocalHTMLFile:(NSString *)localHTMLFile +{ + if (webView && BuildSettings.enableJSInWebView == false) { + webView.configuration.preferences.javaScriptEnabled = NO; + } + + [super setLocalHTMLFile:localHTMLFile]; +} @end diff --git a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m index be2fe5596..4455aae9b 100644 --- a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m +++ b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m @@ -2,6 +2,7 @@ Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -204,6 +205,7 @@ self.headerView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; self.contactNameLabel.textColor = ThemeService.shared.theme.textPrimaryColor; self.contactStatusLabel.textColor = ThemeService.shared.theme.tintColor; + self.contactStatusLabel.hidden = BuildSettings.allowLocalContactPresence ? NO : YES; // Check the table view style to select its bg color. self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor); diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.m b/Riot/Modules/Contacts/Views/ContactTableViewCell.m index a1c3b12a2..a7a3a4123 100644 --- a/Riot/Modules/Contacts/Views/ContactTableViewCell.m +++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.m @@ -1,6 +1,7 @@ /* Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -54,6 +55,7 @@ // apply the vector colours self.contactDisplayNameLabel.textColor = ThemeService.shared.theme.textPrimaryColor; self.contactInformationLabel.textColor = ThemeService.shared.theme.textSecondaryColor; + self.contactInformationLabel.hidden = BuildSettings.allowLocalContactPresence ? NO : YES; self.powerLevelLabel.textColor = ThemeService.shared.theme.textSecondaryColor; // Clear the default background color of a MXKImageView instance @@ -337,19 +339,8 @@ presenceText = [VectorL10n roomParticipantsOffline]; } - if (presenceText.length) - { - self.contactInformationLabel.hidden = NO; - } - else - { - // Hide and fill the label with a fake string to harmonize the height of all the cells. - // This is a drawback of the self-sizing cell. - self.contactInformationLabel.hidden = YES; - presenceText = @"No presence"; - } - self.contactInformationLabel.text = presenceText; + } #pragma mark - events diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.xib b/Riot/Modules/Contacts/Views/ContactTableViewCell.xib index 233951636..d0a9647df 100644 --- a/Riot/Modules/Contacts/Views/ContactTableViewCell.xib +++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -15,7 +13,7 @@ - + @@ -33,27 +31,37 @@ - - - + + + + + + + + + + + + + @@ -66,18 +74,13 @@ - - - - - + + + + - - - - diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsCoordinator.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsCoordinator.swift index f310f442c..fd6dcd26e 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsCoordinator.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsCoordinator.swift @@ -2,6 +2,7 @@ // $ createScreen.sh CreateRoom/EnterNewRoomDetails EnterNewRoomDetails /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -92,4 +93,7 @@ extension EnterNewRoomDetailsCoordinator: SingleImagePickerPresenterDelegate { presenter.dismiss(animated: true, completion: nil) } + func singleImagePickerPresenterDidRemoveImage(_ presenter: SingleImagePickerPresenter) { + } + } diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift index 63fe65197..1de7b2907 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift @@ -2,6 +2,7 @@ // $ createScreen.sh CreateRoom/EnterNewRoomDetails EnterNewRoomDetails /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -134,9 +135,13 @@ final class EnterNewRoomDetailsViewController: UIViewController { self.viewModel.roomCreationParameters.isPublic = true self.updateSections() + // scroll bottom to show user new fields - DispatchQueue.main.async { - self.mainTableView.scrollToRow(at: IndexPath(row: 0, section: 6), at: .bottom, animated: true) + if BuildSettings.enableShowInRoomDirectory { + DispatchQueue.main.async { + let roomAddressSection = (section3 == nil ? 5 : 6) + self.mainTableView.scrollToRow(at: IndexPath(row: 0, section: roomAddressSection), at: .bottom, animated: true) + } } } section4 = Section(header: VectorL10n.createRoomSectionHeaderType, @@ -158,24 +163,28 @@ final class EnterNewRoomDetailsViewController: UIViewController { tmpSections.append(section4) } - if viewModel.roomCreationParameters.isPublic { - let row_5_0 = Row(type: .withSwitch(isOn: viewModel.roomCreationParameters.showInDirectory, onValueChanged: { [weak self] (theSwitch) in - self?.viewModel.roomCreationParameters.showInDirectory = theSwitch.isOn - }), text: VectorL10n.createRoomShowInDirectory, accessoryType: .none) { - // no-op - } - let section5 = Section(header: nil, - rows: [row_5_0], - footer: nil) - - let row_6_0 = Row(type: .textField(tag: Constants.roomAddressTextFieldTag, placeholder: VectorL10n.createRoomPlaceholderAddress, delegate: self), text: viewModel.roomCreationParameters.address, accessoryType: .none) { + if BuildSettings.enableShowInRoomDirectory { + if viewModel.roomCreationParameters.isPublic { + let row_5_0 = Row(type: .withSwitch(isOn: viewModel.roomCreationParameters.showInDirectory, onValueChanged: { (theSwitch) in + self.viewModel.roomCreationParameters.showInDirectory = theSwitch.isOn + }), text: VectorL10n.createRoomShowInDirectory, accessoryType: .none) { + // no-op + } + let section5 = Section(header: nil, + rows: [row_5_0], + footer: nil) + let row_6_0 = Row(type: .textField(tag: Constants.roomAddressTextFieldTag, placeholder: VectorL10n.createRoomPlaceholderAddress, delegate: self), text: viewModel.roomCreationParameters.address, accessoryType: .none) { + + } + let section6 = Section(header: VectorL10n.createRoomSectionHeaderAddress, + rows: [row_6_0], + footer: nil) + + tmpSections.append(contentsOf: [section5, section6]) } - let section6 = Section(header: VectorL10n.createRoomSectionHeaderAddress, - rows: [row_6_0], - footer: nil) - - tmpSections.append(contentsOf: [section5, section6]) + } else { + self.viewModel.roomCreationParameters.showInDirectory = viewModel.roomCreationParameters.isPublic ? true : false } sections = tmpSections diff --git a/Riot/Modules/Favorites/FavouritesViewController.h b/Riot/Modules/Favorites/FavouritesViewController.h index fc0e35bab..a9a68a4b2 100644 --- a/Riot/Modules/Favorites/FavouritesViewController.h +++ b/Riot/Modules/Favorites/FavouritesViewController.h @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,12 +15,12 @@ limitations under the License. */ -#import "RecentsViewController.h" +#import "RecentsBannerViewController.h" /** The `FavouritesViewController` screen is the view controller displayed when `Favourites` tab is selected. */ -@interface FavouritesViewController : RecentsViewController +@interface FavouritesViewController : RecentsBannerViewController + (instancetype)instantiate; diff --git a/Riot/Modules/Favorites/FavouritesViewController.m b/Riot/Modules/Favorites/FavouritesViewController.m index 23b43b856..5bcae5f68 100644 --- a/Riot/Modules/Favorites/FavouritesViewController.m +++ b/Riot/Modules/Favorites/FavouritesViewController.m @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -79,6 +80,20 @@ [super destroy]; } +- (BOOL)shouldShowEmptyView +{ + BOOL shouldShow = [super shouldShowEmptyView]; + + PersonalNotesDefaultService *service = [[PersonalNotesDefaultService alloc] initWithMxSession:self.mainSession]; + PersonalNotesSettings *settings = [[PersonalNotesSettings alloc] init]; + + if ([service personalNotesRoomId] && settings.personalNotesVisible) { + shouldShow = NO; + } + + return shouldShow; +} + #pragma mark - - (void)displayList:(MXKRecentsDataSource *)listDataSource diff --git a/Riot/Modules/GlobalSearch/UnifiedSearchViewController.m b/Riot/Modules/GlobalSearch/UnifiedSearchViewController.m index 8c1956330..aae50f8c8 100644 --- a/Riot/Modules/GlobalSearch/UnifiedSearchViewController.m +++ b/Riot/Modules/GlobalSearch/UnifiedSearchViewController.m @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -83,10 +84,13 @@ recentsViewController.enableSearchBar = NO; [viewControllers addObject:recentsViewController]; - [titles addObject:[VectorL10n searchMessages]]; messagesSearchViewController = [HomeMessagesSearchViewController searchViewController]; - messagesSearchViewController.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSearchMessages]; - [viewControllers addObject:messagesSearchViewController]; + + if (BuildSettings.showUnifiedSearchViewMessagesTab) + { + messagesSearchViewController.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSearchMessages]; + [viewControllers addObject:messagesSearchViewController]; + } // Add search People tab [titles addObject:[VectorL10n searchPeople]]; @@ -96,11 +100,13 @@ peopleSearchViewController.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSearchPeople]; [viewControllers addObject:peopleSearchViewController]; - // add Files tab - [titles addObject:[VectorL10n searchFiles]]; filesSearchViewController = [HomeFilesSearchViewController searchViewController]; - filesSearchViewController.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSearchFiles]; - [viewControllers addObject:filesSearchViewController]; + + if (BuildSettings.showUnifiedSearchViewFilesTab) + { + filesSearchViewController.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSearchFiles]; + [viewControllers addObject:filesSearchViewController]; + } [self initWithTitles:titles viewControllers:viewControllers defaultSelected:0]; diff --git a/Riot/Modules/Home/HomeViewController.m b/Riot/Modules/Home/HomeViewController.m index 3a02c310e..524ae9040 100644 --- a/Riot/Modules/Home/HomeViewController.m +++ b/Riot/Modules/Home/HomeViewController.m @@ -1,6 +1,7 @@ /* Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,6 +25,7 @@ #import "TableViewCellWithCollectionView.h" #import "RoomCollectionViewCell.h" +#import "MXSession+Riot.h" #import "MXRoom+Riot.h" @interface HomeViewController () diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift index 19dbc98f8..0b53cfd9a 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift @@ -2,6 +2,7 @@ // $ createRootCoordinator.sh DeviceVerification DeviceVerification DeviceVerificationStart /* Copyright 2019 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -134,9 +135,14 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType { case .incomingSASTransaction(let incomingSASTransaction): rootCoordinator = self.createDataLoadingScreenCoordinator(otherUserId: incomingSASTransaction.otherUserId, otherDeviceId: incomingSASTransaction.otherDeviceId) case .completeSecurity(let isNewSignIn): - let coordinator = self.createCompleteSecurityCoordinator(isNewSignIn: isNewSignIn) - self.completeSecurityCoordinator = coordinator - rootCoordinator = coordinator + if BuildSettings.disableSelfUserVerification { + let coordinator = self.createSecretsRecoveryCoordinator(with: .passphraseOrKey) + rootCoordinator = coordinator + } else { + let coordinator = self.createCompleteSecurityCoordinator(isNewSignIn: isNewSignIn) + self.completeSecurityCoordinator = coordinator + rootCoordinator = coordinator + } } rootCoordinator.start() @@ -155,7 +161,18 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType { } func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() + let controller = self.navigationRouter.toPresentable() + + // If the flow is not cancellable, run it fullscreen + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if session.homeserverWellknown.backupRequired() { + if #available(iOS 13.0, *) { + controller.modalPresentationStyle = .fullScreen + } + } + } + + return controller } // MARK: - Private methods @@ -183,12 +200,17 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType { return coordinator } - - private func showSecretsRecovery(with recoveryMode: SecretsRecoveryMode) { + + private func createSecretsRecoveryCoordinator(with recoveryMode: SecretsRecoveryMode) -> SecretsRecoveryCoordinator { let coordinator = SecretsRecoveryCoordinator(session: self.session, recoveryMode: recoveryMode, recoveryGoal: .verifyDevice, navigationRouter: self.navigationRouter) coordinator.delegate = self - coordinator.start() + return coordinator + } + + private func showSecretsRecovery(with recoveryMode: SecretsRecoveryMode) { + let coordinator = self.createSecretsRecoveryCoordinator(with: recoveryMode) + coordinator.start() self.add(childCoordinator: coordinator) } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift index 3b6e2824a..ff70c0673 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift @@ -2,6 +2,7 @@ // $ createScreen.sh KeyVerification KeyVerificationSelfVerifyWait /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -54,7 +55,18 @@ final class KeyVerificationSelfVerifyWaitCoordinator: KeyVerificationSelfVerifyW } func toPresentable() -> UIViewController { - return self.keyVerificationSelfVerifyWaitViewController + let controller = self.keyVerificationSelfVerifyWaitViewController + + // If the flow is not cancellable, run it fullscreen + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if session.homeserverWellknown.backupRequired() { + if #available(iOS 13.0, *) { + controller.modalPresentationStyle = .fullScreen + } + } + } + + return controller } } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard index 09e47bd40..d243f0c20 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard @@ -1,9 +1,9 @@ - + - + @@ -20,72 +20,77 @@ - + - + - + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + - + - + @@ -100,10 +105,10 @@ Use the latest Riot on your other devices: - + - - - - - - - - - - - - - - + + + + + + @@ -178,6 +176,7 @@ Use the latest Riot on your other devices: + @@ -185,11 +184,11 @@ Use the latest Riot on your other devices: - - + + diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift index dd988c45e..27e68cdf5 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift @@ -2,6 +2,7 @@ // $ createScreen.sh KeyVerification KeyVerificationSelfVerifyWait /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -28,10 +29,13 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController { // MARK: - Properties + var cancellable: Bool = true + // MARK: Outlets @IBOutlet private weak var informationLabel: UILabel! + @IBOutlet private weak var iconSymbolTextStackView: UIStackView! @IBOutlet private weak var desktopClientImageView: UIImageView! @IBOutlet private weak var mobileClientImageView: UIImageView! @@ -94,6 +98,13 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController { if let navigationBar = self.navigationController?.navigationBar { theme.applyStyle(onNavigationBar: navigationBar) } + + /* + * Hide these UI elements when showDetailedVerificationElements is false + */ + self.iconSymbolTextStackView.isHidden = !BuildSettings.showDetailedVerificationElements + self.desktopClientImageView.isHidden = !BuildSettings.showDetailedVerificationElements + self.additionalInformationLabel.isHidden = !BuildSettings.showDetailedVerificationElements self.informationLabel.textColor = theme.textPrimaryColor self.desktopClientImageView.tintColor = theme.tintColor @@ -112,18 +123,36 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController { } private func setupViews() { - let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.skip, style: .plain) { [weak self] in - self?.cancelButtonAction() + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if !session.homeserverWellknown.backupRequired() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.skip, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.vc_removeBackTitle() + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + self.cancelBarButtonItem = cancelBarButtonItem + } } - - self.vc_removeBackTitle() - - self.navigationItem.rightBarButtonItem = cancelBarButtonItem - self.cancelBarButtonItem = cancelBarButtonItem - + self.title = VectorL10n.deviceVerificationSelfVerifyWaitTitle - self.informationLabel.text = VectorL10n.deviceVerificationSelfVerifyWaitInformation(AppInfo.current.displayName) + if BuildSettings.showDetailedVerificationElements { + let headerText = NSLocalizedString("device_verification_self_verify_wait_information", tableName: "Vector", comment: "") + + self.informationLabel.text = headerText + "\n\n" + NSLocalizedString("device_verification_self_verify_wait_information_more", tableName: "Vector", comment: "") + } else { + self.informationLabel.text = NSLocalizedString("device_verification_self_verify_wait_information", tableName: "Vector", comment: "") + + + /* + * Modify height contrain when the mobile icon is the only image which appears. + */ + for constraint in self.mobileClientImageView.constraints where constraint.identifier == "mobileIconHeightConstraint" { + constraint.constant = 80 + } + } self.desktopClientImageView.image = Asset.Images.monitor.image.withRenderingMode(.alwaysTemplate) self.mobileClientImageView.image = Asset.Images.smartphone.image.withRenderingMode(.alwaysTemplate) diff --git a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.swift b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.swift index a020d5c28..714c7b2aa 100644 --- a/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.swift +++ b/Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.swift @@ -2,6 +2,7 @@ // $ createScreen.sh SessionStatus UserVerificationSessionStatus /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -129,6 +130,11 @@ final class UserVerificationSessionStatusViewController: UIViewController { self.verifyButton.layer.masksToBounds = true self.manuallyVerifyButton.setTitle(VectorL10n.userVerificationSessionDetailsVerifyActionCurrentUserManually, for: .normal) + + if BuildSettings.disableSelfUserVerification { + self.verifyButton.isHidden = true + self.manuallyVerifyButton.isHidden = true + } } private func render(viewState: UserVerificationSessionStatusViewState) { @@ -154,7 +160,7 @@ final class UserVerificationSessionStatusViewController: UIViewController { self.untrustedSessionContainerView.isHidden = viewData.isDeviceTrusted - self.manuallyVerifyButton.isHidden = !viewData.isCurrentUser + self.manuallyVerifyButton.isHidden = !viewData.isCurrentUser || BuildSettings.disableSelfUserVerification if viewData.isDeviceTrusted { badgeImage = Asset.Images.encryptionTrusted.image diff --git a/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift b/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift index 1a1d20169..1be56eda0 100644 --- a/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift +++ b/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift @@ -1,5 +1,6 @@ /* Copyright 2019 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,6 +22,7 @@ import AVFoundation @objc protocol SingleImagePickerPresenterDelegate: AnyObject { func singleImagePickerPresenter(_ presenter: SingleImagePickerPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) func singleImagePickerPresenterDidCancel(_ presenter: SingleImagePickerPresenter) + func singleImagePickerPresenterDidRemoveImage(_ presenter: SingleImagePickerPresenter) } /// SingleImagePickerPresenter enables to present an image picker with single selection @@ -36,10 +38,12 @@ final class SingleImagePickerPresenter: NSObject { private weak var presentingViewController: UIViewController? private var cameraPresenter: CameraPresenter? private var mediaPickerPresenter: MediaPickerCoordinatorBridgePresenter? + // MARK: Public weak var delegate: SingleImagePickerPresenterDelegate? + var allowsRemoveImage = false // MARK: - Setup @@ -64,10 +68,19 @@ final class SingleImagePickerPresenter: NSObject { self.presentPhotoLibray(sourceView: sourceView, sourceRect: sourceRect, animated: animated) }) + let removePhotoAction = UIAlertAction(title: VectorL10n.imagePickerActionRemovePhoto, style: .destructive, handler: { _ in + self.removeImage() + }) + let cancelAction = UIAlertAction(title: VectorL10n.cancel, style: .cancel) alert.addAction(cameraAction) alert.addAction(photoLibraryAction) + + if BuildSettings.enableRemoveAvatarImage && allowsRemoveImage { + alert.addAction(removePhotoAction) + } + alert.addAction(cancelAction) if let popoverPresentationController = alert.popoverPresentationController { @@ -112,6 +125,9 @@ final class SingleImagePickerPresenter: NSObject { self.mediaPickerPresenter = mediaPickerPresenter } + private func removeImage() { + self.delegate?.singleImagePickerPresenterDidRemoveImage(self) + } } // MARK: - CameraPresenterDelegate diff --git a/Riot/Modules/Onboarding/OnboardingCoordinator.swift b/Riot/Modules/Onboarding/OnboardingCoordinator.swift index 8a6dcc7c4..9f911520d 100644 --- a/Riot/Modules/Onboarding/OnboardingCoordinator.swift +++ b/Riot/Modules/Onboarding/OnboardingCoordinator.swift @@ -2,6 +2,7 @@ // $ createRootCoordinator.sh Onboarding/SplashScreen Onboarding OnboardingSplashScreen /* Copyright 2021 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -78,7 +79,9 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol { func start() { // TODO: Manage a separate flow for soft logout that just uses AuthenticationCoordinator - if #available(iOS 14.0, *), parameters.softLogoutCredentials == nil, BuildSettings.authScreenShowRegister { + if #available(iOS 14.0, *), BwiBuildSettings.showBwiSplashScreen { + showBwiSplashScreen() + } else if #available(iOS 14.0, *), parameters.softLogoutCredentials == nil, BuildSettings.authScreenShowRegister { showSplashScreen() } else { showAuthenticationScreen() @@ -126,6 +129,27 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol { self.navigationRouter.setRootModule(coordinator, popCompletion: nil) } + @available(iOS 14.0, *) + /// Show the onboarding splash screen as the root module in the flow. + private func showBwiSplashScreen() { + let coordinator = OnboardingBwiSplashScreenCoordinator() + coordinator.completion = { [weak self, weak coordinator] result in + guard let self = self, let coordinator = coordinator else { return } + self.bwiSplashScreenCoordinator(coordinator, didCompleteWith: result) + } + + coordinator.start() + add(childCoordinator: coordinator) + + self.navigationRouter.setRootModule(coordinator, popCompletion: nil) + } + + /// Displays the next view in the flow after the splash screen. + private func bwiSplashScreenCoordinator(_ coordinator: OnboardingBwiSplashScreenCoordinator, didCompleteWith result: OnboardingBwiSplashScreenViewModelResult) { + // splashScreenResult = result + showAuthenticationScreen() + } + /// Displays the next view in the flow after the splash screen. private func splashScreenCoordinator(_ coordinator: OnboardingSplashScreenCoordinator, didCompleteWith result: OnboardingSplashScreenViewModelResult) { splashScreenResult = result diff --git a/Riot/Modules/People/PeopleViewController.h b/Riot/Modules/People/PeopleViewController.h index 696ebbb5b..514333769 100644 --- a/Riot/Modules/People/PeopleViewController.h +++ b/Riot/Modules/People/PeopleViewController.h @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,13 +15,13 @@ limitations under the License. */ -#import "RecentsViewController.h" +#import "RecentsBannerViewController.h" #import "ContactsDataSource.h" /** 'PeopleViewController' instance is used to display/filter the direct rooms and a list of contacts. */ -@interface PeopleViewController : RecentsViewController +@interface PeopleViewController : RecentsBannerViewController + (instancetype)instantiate; diff --git a/Riot/Modules/People/PeopleViewController.m b/Riot/Modules/People/PeopleViewController.m index 43bb1754c..f7247ca57 100644 --- a/Riot/Modules/People/PeopleViewController.m +++ b/Riot/Modules/People/PeopleViewController.m @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -53,6 +54,7 @@ directRoomsSectionNumber = 0; self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenPeople]; + self.tableViewPaginationThrottler = [[MXThrottler alloc] initWithMinimumDelay:0.1]; } @@ -96,6 +98,39 @@ } } +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + if (@available(iOS 14.0, *)) { + MXSession* session = [self mainSession]; + if (session && [BuildSettings bwiShowHappyBirthdayCampaign]) { + __block RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session]; + NSString *identifier = [BuildSettings bwiHappyBirthdayCampaignIdentifier]; + if( [sharedSettings happyBirthdayCampaignFor:identifier] ) { + [self showHappyBirthdayScreen]; + [sharedSettings setHappyBirthdayCampaign:identifier + enabled:FALSE + success:^{ + sharedSettings = nil; + } failure:^(NSError * _Nullable error) { + MXLogDebug(@"[HappyBirthdayCampaign] could not set flag in account data. Error: %@", error); + sharedSettings = nil; + }]; + } + } + } +} + +#pragma mark - Happy Birthday Campaign + +- (void)showHappyBirthdayScreen { + if (@available(iOS 14.0, *)) { + UIViewController *controller = [HappyBirthdayViewController makeViewController] ; + controller.modalPresentationStyle = UIModalPresentationFullScreen; + [self presentViewController:controller animated:YES completion:nil]; + } +} + #pragma mark - UITableView delegate - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section @@ -116,12 +151,14 @@ } [self.tableViewPaginationThrottler throttle:^{ - NSInteger section = indexPath.section; - NSInteger numberOfRowsInSection = [tableView numberOfRowsInSection:section]; - if (tableView.numberOfSections > section - && indexPath.row == numberOfRowsInSection - 1) - { - [self->recentsDataSource paginateInSection:section]; + if (tableView.numberOfSections) { + NSInteger section = indexPath.section; + NSInteger numberOfRowsInSection = [tableView numberOfRowsInSection:section]; + if (tableView.numberOfSections > section + && indexPath.row == numberOfRowsInSection - 1) + { + [self->recentsDataSource paginateInSection:section]; + } } }]; } diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift index 80ccb7122..28cf13851 100644 --- a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift @@ -1,5 +1,6 @@ /* Copyright 2019 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -64,9 +65,12 @@ import Foundation // MARK: - Private private func loadData() { + let history = RecentReactionHistoryDefaultService() + let filteredReactions = history.reactions(defaultReactions: self.reactions) + let reactionCounts = self.aggregatedReactions?.withNonZeroCount()?.reactions ?? [] - var quickReactionsWithUserReactedFlag: [String: Bool] = Dictionary(uniqueKeysWithValues: self.reactions.map { ($0, false) }) + var quickReactionsWithUserReactedFlag: [String: Bool] = Dictionary(uniqueKeysWithValues: filteredReactions.map { ($0, false) }) reactionCounts.forEach { (reactionCount) in if let hasUserReacted = quickReactionsWithUserReactedFlag[reactionCount.reaction], hasUserReacted == false { @@ -74,7 +78,7 @@ import Foundation } } - let reactionMenuItemViewDatas: [ReactionMenuItemViewData] = self.reactions.map { reaction -> ReactionMenuItemViewData in + let reactionMenuItemViewDatas: [ReactionMenuItemViewData] = filteredReactions.map { reaction -> ReactionMenuItemViewData in let isSelected = quickReactionsWithUserReactedFlag[reaction] ?? false return ReactionMenuItemViewData(emoji: reaction, isSelected: isSelected) } diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index f4561638f..13f6fc241 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -1,6 +1,7 @@ /* Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -202,6 +203,12 @@ const CGFloat kTypingCellHeight = 24; return kTypingCellHeight; } + // bwi: Messages like avatar change may be filtered from the displayed timeline depending on settings + DefaultTimelineFilterService *service = [[DefaultTimelineFilterService alloc] init]; + if ([service shouldFilter:[self cellDataAtIndex:index]]) { + return 0.0; + } + return [super cellHeightAtIndex:index withMaximumWidth:maxWidth]; } @@ -241,6 +248,40 @@ const CGFloat kTypingCellHeight = 24; [self.roomDataSourceDelegate roomDataSourceDidUpdateEncryptionTrustLevel:self]; } +- (NSArray *)attachmentsWithThumbnail +{ + NSMutableArray *attachments = [NSMutableArray array]; + + @synchronized(bubbles) + { + for (id bubbleData in bubbles) + { + if (bubbleData.isAttachmentWithThumbnail && bubbleData.attachment.type != MXKAttachmentTypeSticker) + { + if (BuildSettings.enableAntivirusScan) + { + MXKRoomBubbleCellData *roomBubbleCellData; + + if ([bubbleData isKindOfClass:MXKRoomBubbleCellData.class]) + { + roomBubbleCellData = (MXKRoomBubbleCellData*)bubbleData; + } + + // Append attchment only if the scan status is trusted + if (bubbleData.showAntivirusScanStatus && roomBubbleCellData.eventScanForFirstComponent && roomBubbleCellData.eventScanForFirstComponent.antivirusScanStatus != MXAntivirusScanStatusTrusted) + { + continue; + } + } + + [attachments addObject:bubbleData.attachment]; + } + } + } + + return attachments; +} + - (void)roomDidSet { [self enableRoomCreationIntroCellDisplayIfNeeded]; @@ -389,6 +430,20 @@ const CGFloat kTypingCellHeight = 24; BOOL isCollapsableCellCollapsed = cellData.collapsable && cellData.collapsed; + if (BuildSettings.enableLastMessageTimestamp) { + // Display timestamp of the last message + if (cellData.containsLastMessage && !isCollapsableCellCollapsed) + { + [bubbleCell addTimestampLabelForComponent:cellData.mostRecentComponentIndex]; + } + } else { + // Display timestamp of every message + if (!isCollapsableCellCollapsed) + { + [bubbleCell addTimestampLabelForComponent:cellData.mostRecentComponentIndex]; + } + } + // Display timestamp of the message if needed [cellDecorator addTimestampLabelIfNeededToCell:bubbleCell cellData:cellData]; diff --git a/Riot/Modules/Room/Files/RoomFilesViewController.m b/Riot/Modules/Room/Files/RoomFilesViewController.m index 077311835..cf6a15dbf 100644 --- a/Riot/Modules/Room/Files/RoomFilesViewController.m +++ b/Riot/Modules/Room/Files/RoomFilesViewController.m @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -68,6 +69,9 @@ // Setup `MXKViewControllerHandling` properties self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; + + // Show / hide actions button in document preview according BuildSettings + self.allowActionsInDocumentPreview = BuildSettings.messageDetailsAllowShare; } - (void)viewDidLoad diff --git a/Riot/Modules/Room/Location/LocationUserMarkerView.swift b/Riot/Modules/Room/Location/LocationUserMarkerView.swift new file mode 100644 index 000000000..7fcadd3fb --- /dev/null +++ b/Riot/Modules/Room/Location/LocationUserMarkerView.swift @@ -0,0 +1,48 @@ +// File created from SimpleUserProfileExample +// $ createScreen.sh Room/UserSuggestion UserSuggestion +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import Reusable +import Mapbox + +<<<<<<< HEAD:Riot/Modules/Room/Location/LocationUserMarkerView.swift +class LocationUserMarkerView: MGLAnnotationView, NibLoadable { + + @IBOutlet private var avatarView: UserAvatarView! + + override func awakeFromNib() { + super.awakeFromNib() + translatesAutoresizingMaskIntoConstraints = false + } + + func setAvatarData(_ avatarData: AvatarViewDataProtocol) { + avatarView.fill(with: avatarData) + } +======= + +/// Bubble style room cell layout constants +@objcMembers +final class BubbleRoomCellLayoutConstants: NSObject { + + static let outgoingBubbleBackgroundMargins: UIEdgeInsets = UIEdgeInsets(top: 0, left: 80, bottom: 0, right: 34) + + static let incomingBubbleBackgroundMargins: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 80) + + static let threadSummaryViewMargins: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 5, right: 0) +>>>>>>> 6898f51dbcdf5d9940da07aaf4174014cc84addc:Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutConstants.swift +} diff --git a/Riot/Modules/Room/Location/LocationUserMarkerView.xib b/Riot/Modules/Room/Location/LocationUserMarkerView.xib new file mode 100644 index 000000000..26495f925 --- /dev/null +++ b/Riot/Modules/Room/Location/LocationUserMarkerView.xib @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index 687587dfe..20b1c95d5 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -2,6 +2,7 @@ Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -390,17 +391,24 @@ } }]; - NSString* presenceText; - - NSString *userId = self.mxRoomMember.userId; - - if (userId) + if (BuildSettings.allowLocalContactPresence) { - MXUser *user = [self.mxRoom.mxSession userWithUserId:userId]; - presenceText = [Tools presenceText:user]; + NSString* presenceText; + + NSString *userId = self.mxRoomMember.userId; + + if (userId) + { + MXUser *user = [self.mxRoom.mxSession userWithUserId:userId]; + presenceText = [Tools presenceText:user]; + } + + self.roomMemberStatusLabel.text = presenceText; + } + else + { + self.roomMemberStatusLabel.text = @""; } - - self.roomMemberStatusLabel.text = presenceText; self.roomMemberAvatarBadgeImageView.image = [EncryptionTrustLevelBadgeImageHelper userBadgeImageFor:self.encryptionTrustLevel]; @@ -526,11 +534,10 @@ { isOneself = YES; - if (self.enableLeave) - { + if( !BuildSettings.roomMemberDetailsHideLeaveButton) { [otherActionsArray addObject:@(MXKRoomMemberDetailsActionLeave)]; } - + if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomPowerLevels]) { // Check whether the user is admin (in this case he may reduce his power level to become moderator or less, EXCEPT if he is the only admin). @@ -682,7 +689,7 @@ { otherActionsIndex = sectionCount++; } - if (adminActionsArray.count) + if (adminActionsArray.count && !self.mxRoom.isDirect) { adminToolsIndex = sectionCount++; } @@ -716,7 +723,7 @@ } else if (section == directChatsIndex) { - return (directChatsArray.count + 1); + return (directChatsArray.count > 0 ? directChatsArray.count : 1); } else if (section == devicesIndex) { @@ -974,7 +981,7 @@ { RoomTableViewCell *roomCell = [tableView dequeueReusableCellWithIdentifier:[RoomTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; - if (indexPath.row < directChatsArray.count) + if (directChatsArray.count > 0) { MXRoom *room = [self.mainSession roomWithRoomId:directChatsArray[indexPath.row]]; if (room) @@ -1111,18 +1118,29 @@ UIButton *button = (UIButton*)sender; + + BOOL isUpgradable = BuildSettings.bwiLastAdminCanDowngradeHimself || + ![self.mxRoomMember.userId isEqualToString:self.mainSession.myUser.userId] || + ![self isLastAdmin:self.mainSession.myUser.userId]; switch (button.tag) { case MXKRoomMemberDetailsActionSetDefaultPowerLevel: { - [self.mxRoom state:^(MXRoomState *roomState) { - [self setPowerLevel:roomState.powerLevels.usersDefault promptUser:YES]; - }]; + if( isUpgradable ) + [self.mxRoom state:^(MXRoomState *roomState) { + [self setPowerLevel:roomState.powerLevels.usersDefault promptUser:YES]; + }]; + else + [self showAlertChangePowerLevelNotAllowed]; + break; break; } case MXKRoomMemberDetailsActionSetModerator: { - [self setPowerLevel:RoomPowerLevelModerator promptUser:YES]; + if( isUpgradable ) + [self setPowerLevel:RoomPowerLevelModerator promptUser:YES]; + else + [self showAlertChangePowerLevelNotAllowed]; break; } case MXKRoomMemberDetailsActionSetAdmin: @@ -1266,6 +1284,49 @@ } } +- (bool)isLastAdmin:(NSString*) userId { + __block NSInteger numOtherAdmins = 0; + + if (!userId || [self.mxRoomLiveTimeline.state.powerLevels powerLevelOfUserWithUserID:userId] < RoomPowerLevelAdmin) + return FALSE; + + // Update member power level + [self.mxRoom members:^(MXRoomMembers *roomMembers) { + for (MXRoomMember* member in roomMembers.members) { + NSInteger powerLevel = [self.mxRoomLiveTimeline.state.powerLevels powerLevelOfUserWithUserID:member.userId]; + if (member && ![member.userId isEqualToString:userId] && member.membership == MXMembershipJoin && powerLevel >= RoomPowerLevelAdmin) { + numOtherAdmins++; + } + } + } failure:^(NSError *error) { + MXLogDebug(@"[RoomMemberDetailsViewController] Cannot get all room members in isLastAdmin"); + }]; + + return numOtherAdmins > 0 ? FALSE : TRUE; +} + +- (void)showAlertChangePowerLevelNotAllowed { + __weak typeof(self) weakSelf = self; + + currentAlert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"room_member_details_downgrade_last_admin", @"Vector", nil) + message:nil + preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; + + [self presentViewController:currentAlert animated:YES completion:nil]; +} + - (void)handleTapGesture:(UITapGestureRecognizer*)tapGestureRecognizer { UIView *view = tapGestureRecognizer.view; diff --git a/Riot/Modules/Room/Members/RoomParticipantsViewController.m b/Riot/Modules/Room/Members/RoomParticipantsViewController.m index 0c8cee07f..125e6b348 100644 --- a/Riot/Modules/Room/Members/RoomParticipantsViewController.m +++ b/Riot/Modules/Room/Members/RoomParticipantsViewController.m @@ -1,6 +1,7 @@ /* Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,6 +32,9 @@ @interface RoomParticipantsViewController () { + // Add participant FAB image + UIImageView *inviteFabImageView; + // Search result NSString *currentSearchText; NSMutableArray *filteredActualParticipants; @@ -47,7 +51,7 @@ // Observe kMXRoomDidFlushDataNotification to take into account the updated room members when the room history is flushed. id roomDidFlushDataNotificationObserver; - + RoomMemberDetailsViewController *memberDetailsViewController; ContactsTableViewController *contactsPickerViewController; @@ -139,13 +143,6 @@ [self.tableView registerClass:ContactTableViewCell.class forCellReuseIdentifier:@"ParticipantTableViewCellId"]; - - - // Add invite members button programmatically - [self vc_addFABWithImage:[UIImage imageNamed:@"add_member_floating_action"] - target:self - action:@selector(onAddParticipantButtonPressed)]; - // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -350,6 +347,8 @@ { self.searchBarHeader.hidden = NO; + [self updateInviteButtonForRoom:self.mxRoom]; + if (self.mxRoom.summary.roomType == MXRoomTypeSpace) { self.searchBarView.placeholder = [VectorL10n searchDefaultPlaceholder]; @@ -524,6 +523,26 @@ #pragma mark - Internals +- (void)updateInviteButtonForRoom:(MXRoom *)room +{ + if ((room.isDirect && !BwiBuildSettings.allowInviteOnDirectRooms) || room.isPersonalNotesRoom) + { + // Remove invite members button if exists + [self->inviteFabImageView removeFromSuperview]; + self->inviteFabImageView = nil; + } + else + { + // Add invite members button programmatically if does not exist + if (!self->inviteFabImageView) + { + self->inviteFabImageView = [self vc_addFABWithImage:[UIImage imageNamed:@"add_member_floating_action"] + target:self + action:@selector(onAddParticipantButtonPressed)]; + } + } +} + - (void)refreshTableView { [self.tableView reloadData]; @@ -897,6 +916,36 @@ [self pushViewController:memberDetailsViewController]; } +- (void)processInviteError:(NSError*)error withContact:(Contact*)contactId { + if( error && error.userInfo ) { + NSString *code = [error.userInfo valueForKey:@"errcode"]; + + if (code && ([code isEqualToString:kMXErrCodeStringForbidden] || [code isEqualToString:@"M_BAD_STATE"])) { + NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; + + if (msg) { + NSDictionary *betterUserInfo = @{ + NSLocalizedDescriptionKey : [NSString stringWithFormat:[self localizedMessage:msg], contactId.displayName], + @"errcode" : code, + }; + error = [NSError errorWithDomain:error.domain code:error.code userInfo:betterUserInfo]; + } + } + + [[AppDelegate theDelegate] showErrorAsAlert:error]; + } +} + +- (NSString*) localizedMessage:(NSString*)message { + if ([message hasSuffix:@"is already in the room."]) { + return NSLocalizedStringFromTable(@"bwi_error_invite_already_in_room", @"Vector", nil); + } else if ([message hasSuffix:@"who was banned"]) { + return NSLocalizedStringFromTable(@"bwi_error_invite_banned_in_room", @"Vector", nil); + } else { + return NSLocalizedStringFromTable(@"bwi_error_invite_general", @"Vector", nil); + } +} + #pragma mark - UITableView data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView @@ -1559,7 +1608,7 @@ MXLogDebug(@"[RoomParticipantsVC] Invite %@ failed", participantId); // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; + [self processInviteError:error withContact:contact]; }]; } else diff --git a/Riot/Modules/Room/RoomCoordinator.swift b/Riot/Modules/Room/RoomCoordinator.swift index 6bde3450c..6b6dff11f 100644 --- a/Riot/Modules/Room/RoomCoordinator.swift +++ b/Riot/Modules/Room/RoomCoordinator.swift @@ -2,6 +2,7 @@ // $ createScreen.sh Room Room /* Copyright 2021 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -244,6 +245,8 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { guard #available(iOS 14.0, *) else { return } +#if LOCATION + guard let navigationRouter = self.navigationRouter, let mediaManager = mxSession?.mediaManager, @@ -288,6 +291,8 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { navigationRouter.present(coordinator, animated: true) coordinator.start() + +#endif } private func startEditPollCoordinator(startEvent: MXEvent? = nil) { @@ -390,8 +395,11 @@ extension RoomCoordinator: RoomViewControllerDelegate { guard let location = event.location else { return nil } - + #if LOCATION return LocationSharingCoordinator.shareLocationActivityController(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)) + #else + return nil + #endif } func roomViewController(_ roomViewController: RoomViewController, canEndPollWithEventIdentifier eventIdentifier: String) -> Bool { diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift index 4eaf2328d..bd3c9d9fa 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift @@ -2,6 +2,7 @@ // $ createRootCoordinator.sh Room2 RoomInfo RoomInfoList /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -146,6 +147,12 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { return coordinator } + private func createRoomRollsAndRightsCoordinator() -> RoomRollsAndRightsCoordinator { + let coordinator = RoomRollsAndRightsCoordinator(room: room) + coordinator.delegate = self + return coordinator + } + private func showRoomDetails(with target: RoomInfoListTarget, animated: Bool) { switch target { case .integrations: @@ -165,6 +172,11 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { coordinator.start() self.add(childCoordinator: coordinator) self.navigationRouter.push(coordinator, animated: true, popCompletion: nil) + case .rollsAndRights: + let coordinator = createRoomRollsAndRightsCoordinator() + coordinator.start() + self.add(childCoordinator: coordinator) + self.navigationRouter.push(coordinator, animated: true, popCompletion: nil) default: guard let tabIndex = target.tabIndex else { fatalError("No settings tab index for this target.") @@ -206,6 +218,15 @@ extension RoomInfoCoordinator: RoomParticipantsViewControllerDelegate { } +extension RoomInfoCoordinator: RoomRollsAndRightsCoordinatorDelegate { + func roomRollsAndRightsCoordinatorDidComplete(_ coordinator: RoomRollsAndRightsCoordinatorType) { + self.navigationRouter.popModule(animated: true) + } + + func roomRollsAndRightsCoordinatorDidCancel(_ coordinator: RoomRollsAndRightsCoordinatorType) { + } +} + extension RoomInfoCoordinator: RoomNotificationSettingsCoordinatorDelegate { func roomNotificationSettingsCoordinatorDidComplete(_ coordinator: RoomNotificationSettingsCoordinatorType) { self.navigationRouter.popModule(animated: true) diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift index c6e0c8887..f5f603a30 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift @@ -2,6 +2,7 @@ // $ createScreen.sh Room2/RoomInfo RoomInfoList /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,6 +21,7 @@ import Foundation enum RoomInfoListTarget: Equatable { case settings(_ field: RoomSettingsViewControllerField = RoomSettingsViewControllerFieldNone) + case rollsAndRights case members case uploads case integrations diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift index 78b5af425..5d6fc3f50 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift @@ -2,6 +2,7 @@ // $ createScreen.sh Room2/RoomInfo RoomInfoList /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -58,8 +59,9 @@ final class RoomInfoListViewController: UIViewController { }() private lazy var leaveAlertController: UIAlertController = { - let title = self.isRoomDirect ? VectorL10n.roomParticipantsLeavePromptTitleForDm : VectorL10n.roomParticipantsLeavePromptTitle - let message = self.isRoomDirect ? VectorL10n.roomParticipantsLeavePromptMsgForDm : VectorL10n.roomParticipantsLeavePromptMsg + var title = self.isRoomDirect ? VectorL10n.roomParticipantsLeavePromptTitleForDm : VectorL10n.roomParticipantsLeavePromptTitle + var message = self.isRoomDirect ? VectorL10n.roomParticipantsLeavePromptMsgForDm : VectorL10n.roomParticipantsLeavePromptMsg + let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) controller.addAction(UIAlertAction(title: VectorL10n.cancel, style: .cancel, handler: nil)) @@ -72,6 +74,26 @@ final class RoomInfoListViewController: UIViewController { return controller }() + private func getLeaveAlertController() -> UIAlertController { + if self.viewModel.canLeaveRoom() { + let title = self.isRoomDirect ? VectorL10n.roomParticipantsLeavePromptTitleForDm : VectorL10n.roomParticipantsLeavePromptTitle + let message = self.isRoomDirect ? VectorL10n.roomParticipantsLeavePromptMsgForDm : VectorL10n.roomParticipantsLeavePromptMsg + + let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) + + controller.addAction(UIAlertAction(title: VectorL10n.cancel, style: .cancel, handler: nil)) + controller.addAction(UIAlertAction(title: VectorL10n.leave, style: .default, handler: { [weak self] (action) in + guard let self = self else { return } + self.viewModel.process(viewAction: .leave) + })) + controller.mxk_setAccessibilityIdentifier("RoomSettingsVCLeaveAlert") + + return controller + } else { + return self.doNotLeaveUIAlertController() + } + } + private enum RowType { case `default` case destructive @@ -155,12 +177,20 @@ final class RoomInfoListViewController: UIViewController { private func updateSections(with viewData: RoomInfoListViewData) { basicInfoView.configure(withViewData: viewData.basicInfoViewData) + + guard !viewModel.isPersonalNotesRoom() else { + self.updateSectionsAsPersonalNote(with: viewData) + return + } var tmpSections: [Section] = [] let rowSettings = Row(type: .default, icon: Asset.Images.settingsIcon.image, text: VectorL10n.roomDetailsSettings, accessoryType: .disclosureIndicator) { self.viewModel.process(viewAction: .navigate(target: .settings())) } + let rowRollsAndRights = Row(type: .default, icon: Asset.Images.rollsAndRights.image, text: VectorL10n.bwiRollsAndRights, accessoryType: .disclosureIndicator) { + self.viewModel.process(viewAction: .navigate(target: .rollsAndRights)) + } let roomNotifications = Row(type: .default, icon: Asset.Images.notifications.image, text: VectorL10n.roomDetailsNotifs, accessoryType: .disclosureIndicator) { self.viewModel.process(viewAction: .navigate(target: .notifications)) } @@ -174,12 +204,16 @@ final class RoomInfoListViewController: UIViewController { let rowSearch = Row(type: .default, icon: Asset.Images.searchIcon.image, text: VectorL10n.roomDetailsSearch, accessoryType: .disclosureIndicator) { self.viewModel.process(viewAction: .navigate(target: .search)) } + let rowIntegrations = Row(type: .default, icon: Asset.Images.integrationsIcon.image, text: VectorL10n.roomDetailsIntegrations, accessoryType: .disclosureIndicator) { self.viewModel.process(viewAction: .navigate(target: .integrations)) } var rows = [rowSettings] + if BuildSettings.bwiRollsAndRights && !(viewModel.isPersonalNotesRoom() || viewModel.isDirectRoom()) { + rows.append(rowRollsAndRights) + } if BuildSettings.showNotificationsV2 { rows.append(roomNotifications) } @@ -198,7 +232,11 @@ final class RoomInfoListViewController: UIViewController { VectorL10n.roomParticipantsLeavePromptTitleForDm : VectorL10n.roomParticipantsLeavePromptTitle let rowLeave = Row(type: .destructive, icon: Asset.Images.roomActionLeave.image, text: leaveTitle, accessoryType: .none) { - self.present(self.leaveAlertController, animated: true, completion: nil) + if BuildSettings.lastAdminIsNotAllowedToLeaveRoom { + self.present(self.getLeaveAlertController(), animated: true, completion: nil) + } else { + self.present(self.leaveAlertController, animated: true, completion: nil) + } } let sectionLeave = Section(header: nil, rows: [rowLeave], @@ -275,6 +313,18 @@ final class RoomInfoListViewController: UIViewController { self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) } + private func doNotLeaveUIAlertController() -> UIAlertController { + let title = VectorL10n.roomParticipantsLeavePromptTitle + let message = NSLocalizedString("room_participants_cant_leave_prompt_message", tableName: "Vector", comment: "") + let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) + + controller.addAction(UIAlertAction(title: VectorL10n.cancel, style: .cancel, handler: nil)) + controller.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) + controller.mxk_setAccessibilityIdentifier("RoomSettingsVCLeaveAlert") + + return controller + } + // MARK: - Actions @objc private func closeButtonTapped(_ sender: Any) { @@ -363,7 +413,7 @@ extension RoomInfoListViewController: UITableViewDelegate { let view: TextViewTableViewHeaderFooterView? = tableView.dequeueReusableHeaderFooterView() view?.textView.text = header - view?.textView.font = .systemFont(ofSize: 13) + view?.textView.font = .boldSystemFont(ofSize: 15) view?.textViewInsets = UIEdgeInsets(top: 16, left: 16, bottom: 8, right: 16) view?.update(theme: theme) @@ -416,3 +466,40 @@ extension RoomInfoListViewController: RoomInfoListViewModelViewDelegate { } } + +// MARK: - Extention for Personal Notes room + +extension RoomInfoListViewController { + func updateSectionsAsPersonalNote(with viewData: RoomInfoListViewData) { + var tmpSections: [Section] = [] + + let rowSettings = Row(type: .default, icon: Asset.Images.settingsIcon.image, text: VectorL10n.roomDetailsSettings, accessoryType: .disclosureIndicator) { + self.viewModel.process(viewAction: .navigate(target: .settings())) + } + + let rowUploads = Row(type: .default, icon: Asset.Images.scrollup.image, text: VectorL10n.roomDetailsFiles, accessoryType: .disclosureIndicator) { + self.viewModel.process(viewAction: .navigate(target: .uploads)) + } + + var rows = [rowSettings] + + rows.append(rowUploads) + + if BuildSettings.bwiPersonalNotesRoomLeavable { + let leaveTitle = VectorL10n.roomParticipantsLeavePromptTitle + let rowLeave = Row(type: .destructive, icon: Asset.Images.roomActionLeave.image, text: leaveTitle, accessoryType: .none) { + self.present(self.leaveAlertController, animated: true, completion: nil) + } + + rows.append(rowLeave) + } + + let sectionSettings = Section(header: VectorL10n.roomInfoListSectionOther, + rows: rows, + footer: nil) + + tmpSections.append(sectionSettings) + + sections = tmpSections + } +} diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift index 689a91b60..37d846ec7 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift @@ -2,6 +2,7 @@ // $ createScreen.sh Room2/RoomInfo RoomInfoList /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -77,8 +78,73 @@ final class RoomInfoListViewModel: NSObject, RoomInfoListViewModelType { } } + func canLeaveRoom() -> Bool { + let myUserId = self.session.myUserId + + // None Admins can always leave the room + if !self.isAdmin(self.session.myUserId) { + return true + } + + var canLeave = false + + // admins can only leave if there is at least one other admin + self.room.state { (state) in + if let members = state?.members.joinedMembers { + for user in members where user.userId != myUserId { + if self.isAdmin(user.userId) { + canLeave = true + break + } + } + } + } + + // A single admin can leave the room if he is the only member + if !canLeave && self.isOnlyMember() { + canLeave = true + } + + return canLeave + } + + func isDirectRoom() -> Bool { + return room.isDirect + } + + func isPersonalNotesRoom() -> Bool { + return room.isPersonalNotesRoom() + } + + func isOnlyMember() -> Bool { + var memberCount = 0 + + self.room.state { (state) in + if let members = state?.members.joinedMembers { + memberCount = members.count + } + } + return (memberCount == 1) + } + // MARK: - Private + private func isAdmin(_ userId: String) -> Bool { + var admin = false + + self.room.state { (state) in + let powerLevels = state?.powerLevels + let userPowerLevel = powerLevels?.powerLevelOfUser(withUserID: userId) + let roomPowerLevel = RoomPowerLevelHelper.roomPowerLevel(from: userPowerLevel ?? RoomPowerLevel.user.rawValue) + + if roomPowerLevel == RoomPowerLevel.admin { + admin = true + } + } + + return admin + } + private func startObservingSummaryChanges() { NotificationCenter.default.addObserver(self, selector: #selector(roomSummaryUpdated(_:)), name: .mxRoomSummaryDidChange, object: room.summary) } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift index 8a5a1b411..4736798ae 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift @@ -2,6 +2,7 @@ // $ createScreen.sh Room2/RoomInfo RoomInfoList /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -34,5 +35,8 @@ protocol RoomInfoListViewModelType { var viewDelegate: RoomInfoListViewModelViewDelegate? { get set } var coordinatorDelegate: RoomInfoListViewModelCoordinatorDelegate? { get set } - func process(viewAction: RoomInfoListViewAction) + func process(viewAction: RoomInfoListViewAction) + func canLeaveRoom() -> Bool + func isDirectRoom() -> Bool + func isPersonalNotesRoom() -> Bool } diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 966a76e45..a92d8c95c 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -2,6 +2,7 @@ Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -75,6 +76,8 @@ #import "EventFormatter.h" +#import "RoomViewController+ContentScanner.h" + #import "SettingsViewController.h" #import "SecurityViewController.h" @@ -332,8 +335,24 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Register first customized cell view classes used to render bubbles [[RoomTimelineConfiguration shared].currentStyle.cellProvider registerCellsForTableView:self.bubblesTableView]; + [self.bubblesTableView registerClass:PollBubbleCell.class forCellReuseIdentifier:PollBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:PollWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:PollWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:PollWithPaginationTitleBubbleCell.class forCellReuseIdentifier:PollWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + +#ifdef LOCATION + [self.bubblesTableView registerClass:LocationBubbleCell.class forCellReuseIdentifier:LocationBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:LocationWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:LocationWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:LocationWithPaginationTitleBubbleCell.class forCellReuseIdentifier:LocationWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +#endif + [self vc_removeBackTitle]; + if (BuildSettings.enableAntivirusScan) + { + // Register antivirus scan cells + [self registerContentScannerCells]; + } + // Display leftBarButtonItems or leftBarButtonItem to the right of the Back button self.navigationItem.leftItemsSupplementBackButton = YES; @@ -1124,7 +1143,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [super setRoomInputToolbarViewClass:roomInputToolbarViewClass]; // The voice message toolbar cannot be set on DisabledInputToolbarView. - if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class] && BuildSettings.bwiEnableVoiceMessages) { [(RoomInputToolbarView *)self.inputToolbarView setVoiceMessageToolbarView:self.voiceMessageController.voiceMessageToolbarView]; } @@ -1254,27 +1273,43 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)sendTextMessage:(NSString*)msgTxt { + PerformanceProfile *sendTextMessageProfile = [[PerformanceProfile alloc] initWithThreshold:BuildSettings.bwiSendMessageThreshold]; + [sendTextMessageProfile startMeasurement]; + if (self.inputToolBarSendMode == RoomInputToolbarViewSendModeReply && customizedRoomDataSource.selectedEventId) { - [self.roomDataSource sendReplyToEventWithId:customizedRoomDataSource.selectedEventId withTextMessage:msgTxt success:nil failure:^(NSError *error) { + [self.roomDataSource sendReplyToEventWithId:customizedRoomDataSource.selectedEventId withTextMessage:msgTxt success:^(NSString *string) + { + [self finishTextMessageProfil:sendTextMessageProfile]; + } failure:^(NSError *error) { // Just log the error. The message will be displayed in red in the room history MXLogDebug(@"[MXKRoomViewController] sendTextMessage failed."); + [sendTextMessageProfile abortMeasurement]; }]; } else if (self.inputToolBarSendMode == RoomInputToolbarViewSendModeEdit && customizedRoomDataSource.selectedEventId) { - [self.roomDataSource replaceTextMessageForEventWithId:customizedRoomDataSource.selectedEventId withTextMessage:msgTxt success:nil failure:^(NSError *error) { + [self.roomDataSource replaceTextMessageForEventWithId:customizedRoomDataSource.selectedEventId withTextMessage:msgTxt success:^(NSString *string) + { + [self finishTextMessageProfil:sendTextMessageProfile]; + } failure:^(NSError *error) { // Just log the error. The message will be displayed in red MXLogDebug(@"[MXKRoomViewController] sendTextMessage failed."); + [sendTextMessageProfile abortMeasurement]; }]; } else { // Let the datasource send it and manage the local echo - [self.roomDataSource sendTextMessage:msgTxt success:nil failure:^(NSError *error) + [self.roomDataSource sendTextMessage:msgTxt success:^(NSString *string) + { + [self finishTextMessageProfil:sendTextMessageProfile]; + } + failure:^(NSError *error) { // Just log the error. The message will be displayed in red in the room history MXLogDebug(@"[MXKRoomViewController] sendTextMessage failed."); + [sendTextMessageProfile abortMeasurement]; }]; } @@ -2248,7 +2283,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (ScreenPresentationParameters*)buildUniversalLinkPresentationParameters { - return [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:BuildSettings.allowSplitViewDetailsScreenStacking sender:self sourceView:nil]; + return [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:BwiBuildSettings.allowSplitViewDetailsScreenStacking sender:self sourceView:nil]; } - (BOOL)handleUniversalLinkURL:(NSURL*)universalLinkURL @@ -2640,7 +2675,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } BOOL showEncryptionBadge = NO; - RoomTimelineCellIdentifier cellIdentifier; + RoomTimelineCellIdentifier cellIdentifier = RoomTimelineCellIdentifierEmpty; id bubbleData = (id)cellData; @@ -2653,6 +2688,16 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } // Select the suitable table view cell class, by considering first the empty bubble cell. + if (BuildSettings.enableAntivirusScan && bubbleData.showAntivirusScanStatus) + { + // FRANK181 + cellIdentifier = [self contentScannerClassForBubbleCellData:roomBubbleCellData]; + } + + if (cellIdentifier == ContentScannerIdentiferStatus || cellIdentifier == ContentScannerIdentifierThumbnail) { + return cellIdentifier; + } + if (bubbleData.hasNoDisplay) { cellIdentifier = RoomTimelineCellIdentifierEmpty; @@ -2780,6 +2825,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } } +#ifdef LOCATION else if (bubbleData.tag == RoomBubbleCellDataTagLocation) { if (bubbleData.isIncoming) @@ -2813,6 +2859,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } } +#endif else if (roomBubbleCellData.getFirstBubbleComponentWithDisplay.event.isEmote) { if (bubbleData.isIncoming) @@ -3929,7 +3976,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; } - if (!isJitsiCallEvent && self.roomDataSource.room.summary.isEncrypted) + if (!isJitsiCallEvent && self.roomDataSource.room.summary.isEncrypted && RiotSettings.shared.roomContextualMenuShowEncryptionOption) { [self.eventMenuBuilder addItemWithType:EventMenuItemTypeViewEncryption action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewEncryption] @@ -6479,6 +6526,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell; MXKAttachment *attachment = roomBubbleTableViewCell.bubbleData.attachment; + BOOL result = (event.eventType != MXEventTypePollStart && (!attachment || attachment.type != MXKAttachmentTypeSticker)); if (attachment && !BuildSettings.messageDetailsAllowCopyMedia) @@ -6771,6 +6819,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { MXWeakify(self); + [self bwiAddedEmoji:reaction]; + [self hideContextualMenuAnimated:YES completion:^{ [self.roomDataSource addReaction:reaction forEventId:eventId success:^{ @@ -6904,6 +6954,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { MXWeakify(self); + [self bwiAddedEmoji:emoji]; + [coordinatorBridgePresenter dismissWithAnimated:YES completion:^{ [self.roomDataSource addReaction:emoji forEventId:eventId success:^{ @@ -7139,6 +7191,31 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [[LegacyAppDelegate theDelegate] openSpaceWithId:spaceId]; } +#pragma mark - Bwi Measurements + +- (void) finishTextMessageProfil:(PerformanceProfile*)profile { + [profile stopMeasurement]; + if( [profile isLogable] ) { + [self.roomDataSource.room members:^(MXRoomMembers *roomMembers) { + NSUInteger noOfUsers = roomMembers.joinedMembers.count; + NSUInteger noOfDevices = 0; + for (MXRoomMember* member in roomMembers.joinedMembers) { + noOfDevices += [self.mainSession.crypto devicesForUser:member.userId].count; + } + [profile log2AnalyticsWithUsers:noOfUsers devices:noOfDevices]; + } failure:^(NSError *error) { + + }]; + } +} + +#pragma mark - BWI Emoji History + +- (void) bwiAddedEmoji:(NSString*)emoji { + RecentReactionHistoryDefaultService* service = [[RecentReactionHistoryDefaultService alloc] init]; + [service addReactionWithReaction:emoji]; +} + #pragma mark - UserSuggestionCoordinatorBridgeDelegate - (void)userSuggestionCoordinatorBridge:(UserSuggestionCoordinatorBridge *)coordinator diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.m b/Riot/Modules/Room/Settings/RoomSettingsViewController.m index d844deaee..8ae528ed7 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.m +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.m @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -52,6 +53,8 @@ enum ROOM_SETTINGS_MAIN_SECTION_ROW_TOPIC, ROOM_SETTINGS_MAIN_SECTION_ROW_TAG, ROOM_SETTINGS_MAIN_SECTION_ROW_DIRECT_CHAT, + ROOM_SETTINGS_MAIN_SECTION_ROW_REST_TIME, + ROOM_SETTINGS_MAIN_SECTION_ROW_REST_TIME_INFO, ROOM_SETTINGS_MAIN_SECTION_ROW_MUTE_NOTIFICATIONS, ROOM_SETTINGS_MAIN_SECTION_ROW_LEAVE }; @@ -62,7 +65,8 @@ enum ROOM_SETTINGS_ROOM_ACCESS_SECTION_ROW_ANYONE_APART_FROM_GUEST, ROOM_SETTINGS_ROOM_ACCESS_SECTION_ROW_ANYONE, ROOM_SETTINGS_ROOM_ACCESS_DIRECTORY_VISIBILITY, - ROOM_SETTINGS_ROOM_ACCESS_MISSING_ADDRESS_WARNING + ROOM_SETTINGS_ROOM_ACCESS_MISSING_ADDRESS_WARNING, + ROOM_SETTINGS_ROOM_ACCESS_BW }; enum @@ -103,6 +107,7 @@ NSString *const kRoomSettingsNameKey = @"kRoomSettingsNameKey"; NSString *const kRoomSettingsTopicKey = @"kRoomSettingsTopicKey"; NSString *const kRoomSettingsTagKey = @"kRoomSettingsTagKey"; NSString *const kRoomSettingsMuteNotifKey = @"kRoomSettingsMuteNotifKey"; +NSString *const kRoomSettingsWorkTimeNotifKey = @"kRoomSettingsWorkTimeNotifKey"; NSString *const kRoomSettingsDirectChatKey = @"kRoomSettingsDirectChatKey"; NSString *const kRoomSettingsJoinRuleKey = @"kRoomSettingsJoinRuleKey"; NSString *const kRoomSettingsGuestAccessKey = @"kRoomSettingsGuestAccessKey"; @@ -482,6 +487,10 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti - (void)updateSections { + if(mxRoom.isPersonalNotesRoom) { + [self updateSectionsAsPersonalNotes]; + return; + } // update local data // Refresh here the room addresses list. [roomAddresses removeAllObjects]; @@ -529,10 +538,16 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_NAME]; [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_TOPIC]; [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_TAG]; + if (RiotSettings.shared.roomSettingsScreenShowDirectChatOption) { [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_DIRECT_CHAT]; } + if (BuildSettings.featureWorkTime) + { + [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_REST_TIME]; + [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_REST_TIME_INFO]; + } if (!BuildSettings.showNotificationsV2) { [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_MUTE_NOTIFICATIONS]; @@ -540,6 +555,12 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_LEAVE]; [tmpSections addObject:sectionMain]; + if (!BuildSettings.roomSettingsScreenRemoveLeave) { + Section *sectionLeave = [Section sectionWithTag:SECTION_TAG_MAIN]; + [sectionLeave addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_LEAVE]; + [tmpSections addObject:sectionLeave]; + } + if (RiotSettings.shared.roomSettingsScreenAllowChangingAccessSettings) { Section *sectionAccess = [Section sectionWithTag:SECTION_TAG_ACCESS]; @@ -573,6 +594,10 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [tmpSections addObject:sectionAccess]; } + if ( BuildSettings.roomSettingsScreenShowAccessSettingsBW && !mxRoom.isDirect ){ + [tmpSections addObject:[self sectionForAccessSwitch]]; + } + if (RiotSettings.shared.roomSettingsScreenAllowChangingHistorySettings) { Section *sectionHistory = [Section sectionWithTag:SECTION_TAG_HISTORY]; @@ -2227,6 +2252,12 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti cell = roomNotifCell; } + else if( row == ROOM_SETTINGS_MAIN_SECTION_ROW_REST_TIME) { + cell = [self cellForWorkTimeSwitch:tableView indexPath:indexPath]; + } + else if( row == ROOM_SETTINGS_MAIN_SECTION_ROW_REST_TIME_INFO) { + cell = [self cellForWorkTimeInfo:tableView indexPath:indexPath]; + } else if (row == ROOM_SETTINGS_MAIN_SECTION_ROW_DIRECT_CHAT) { MXKTableViewCellWithLabelAndSwitch *roomDirectChat = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; @@ -2440,11 +2471,11 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti { MXKTableViewCellWithButton *leaveCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier] forIndexPath:indexPath]; - NSString* title = [VectorL10n leave]; + NSString* title = [VectorL10n roomParticipantsLeavePromptTitle]; [leaveCell.mxkButton setTitle:title forState:UIControlStateNormal]; [leaveCell.mxkButton setTitle:title forState:UIControlStateHighlighted]; - [leaveCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + [leaveCell.mxkButton setTintColor:ThemeService.shared.theme.warningColor]; leaveCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; [leaveCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; @@ -2500,6 +2531,10 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.textLabel.text = [VectorL10n roomDetailsAccessSectionNoAddressWarning]; } + else if (row == ROOM_SETTINGS_ROOM_ACCESS_BW) + { + cell = [self cellForAccessSwitch:tableView indexPath:indexPath]; + } else { TableViewCellWithCheckBoxAndLabel *roomAccessCell = [tableView dequeueReusableCellWithIdentifier:[TableViewCellWithCheckBoxAndLabel defaultReuseIdentifier] forIndexPath:indexPath]; @@ -4037,6 +4072,14 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [self refreshRoomSettings]; } + +} + +- (void)singleImagePickerPresenterDidRemoveImage:(SingleImagePickerPresenter *)presenter +{ + // This method should never be called here because room settings should not show the remove image option. But nevertheless we do a nice cleanup also for this delegate call + [presenter dismissWithAnimated:YES completion:nil]; + self.imagePickerPresenter = nil; } #pragma mark - TableViewSectionsDelegate @@ -4046,4 +4089,172 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [self.tableView reloadData]; } +#pragma mark - bwi Messenger Additions + +- (Section*) sectionForAccessSwitch { + Section *sectionAccess = [Section sectionWithTag:SECTION_TAG_ACCESS]; + [sectionAccess addRowWithTag:ROOM_SETTINGS_ROOM_ACCESS_BW]; + return sectionAccess; +} + +- (UITableViewCell*) cellForAccessSwitch:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath { + MXKTableViewCellWithLabelAndSwitch *roomAccessToggleCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + roomAccessToggleCell.mxkLabel.text = NSLocalizedStringFromTable(@"room_details_access_section_access_toggle_bw", @"Vector", nil); + + [roomAccessToggleCell.mxkSwitch addTarget:self action:@selector(toggleDirectoryAccessBW:) forControlEvents:UIControlEventValueChanged]; + + if (updatedItemsDict[kRoomSettingsDirectoryKey]) + { + roomAccessToggleCell.mxkSwitch.on = ((NSNumber*) updatedItemsDict[kRoomSettingsDirectoryKey]).boolValue; + } + else + { + // Use the last retrieved value if any + roomAccessToggleCell.mxkSwitch.on = [actualDirectoryVisibility isEqualToString:kMXRoomDirectoryVisibilityPublic]; + } + + // Store the switch to be able to update it + directoryVisibilitySwitch = roomAccessToggleCell.mxkSwitch; + + MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; + + if ([powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId] != RoomPowerLevelAdmin) { + directoryVisibilitySwitch.enabled = NO; + } + + return roomAccessToggleCell; +} + +- (UITableViewCell*) cellForWorkTimeSwitch:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath { + MXKTableViewCellWithLabelAndSwitch *workTimeToggleCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + [workTimeToggleCell.mxkSwitch addTarget:self action:@selector(toggleWorkTime:) forControlEvents:UIControlEventValueChanged]; + + workTimeToggleCell.mxkLabel.text = NSLocalizedStringFromTable(@"room_details_main_section_rest_time", @"Vector", nil); + workTimeToggleCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + workTimeToggleCell.mxkSwitch.on = [self isWorkTimeSwitchOn]; + workTimeToggleCell.mxkSwitch.enabled = [self isWorkTimeSwitchEnabled]; + + return workTimeToggleCell; +} + +- (UITableViewCell*) cellForWorkTimeInfo:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath { + MXKTableViewCell *workTimeInfoCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; + + if (!workTimeInfoCell) + { + workTimeInfoCell = [[MXKTableViewCell alloc] init]; + } + + workTimeInfoCell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + workTimeInfoCell.textLabel.font = [UIFont systemFontOfSize:15.0]; + workTimeInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; + workTimeInfoCell.userInteractionEnabled = NO; + workTimeInfoCell.textLabel.text = [self textForWorkTimeInfoCell]; + workTimeInfoCell.textLabel.adjustsFontSizeToFitWidth = YES; + + return workTimeInfoCell; +} + +- (void)toggleDirectoryAccessBW:(UISwitch*)theSwitch +{ + MXRoomDirectoryVisibility visibility = theSwitch.on ? kMXRoomDirectoryVisibilityPublic : kMXRoomDirectoryVisibilityPrivate; + + // Check whether the actual settings has been retrieved + if (actualDirectoryVisibility) + { + if ([visibility isEqualToString:actualDirectoryVisibility]) + { + [updatedItemsDict removeObjectForKey:kRoomSettingsDirectoryKey]; + [updatedItemsDict removeObjectForKey:kRoomSettingsJoinRuleKey]; + } + else + { + updatedItemsDict[kRoomSettingsDirectoryKey] = visibility; + updatedItemsDict[kRoomSettingsJoinRuleKey] = [visibility isEqualToString:kMXRoomDirectoryVisibilityPublic] ? kMXRoomJoinRulePublic : kMXRoomJoinRuleInvite; + } + } + else + { + updatedItemsDict[kRoomSettingsDirectoryKey] = visibility; + updatedItemsDict[kRoomSettingsJoinRuleKey] = [visibility isEqualToString:kMXRoomDirectoryVisibilityPublic] ? kMXRoomJoinRulePublic : kMXRoomJoinRuleInvite; + } + + [self getNavigationItem].rightBarButtonItem.enabled = (updatedItemsDict.count != 0); +} + +- (void)toggleWorkTime:(UISwitch*)theSwitch { + NSLog(@"Toggle Worktime"); + + WorkTimeService *service = [WorkTimeService workTimeService:self.mainSession.myUser.userId]; + + updatedItemsDict[kRoomSettingsWorkTimeNotifKey] = @(theSwitch.on); + + if (theSwitch.on) { + [service activateWorkTimeInRoom:self.roomId]; + } else { + [service deactivateWorkTimeInRoom:self.roomId]; + } + + [self getNavigationItem].rightBarButtonItem.enabled = (updatedItemsDict.count != 0); +} + +- (NSString*) textForWorkTimeInfoCell { + WorkTimeService *service = [WorkTimeService workTimeService:self.mainSession.myUser.userId]; + NSString *text = @""; + + if (service.isWorkTimeGlobalyEnabled) { + if ([service.store isWorkTimeRoom:self.roomId] ) { + if ([service.store isWorkTimeInRoomActive:self.roomId]) { + text = [NSString stringWithFormat:@"%@ %@", NSLocalizedStringFromTable(@"room_details_main_section_rest_time_locally_enabled", @"Vector", nil), [service localizedWorkTime]]; + } else { + text = NSLocalizedStringFromTable(@"room_details_main_section_rest_time_locally_disabled", @"Vector", nil); + } + } else { + text = [NSString stringWithFormat:@"%@ %@", NSLocalizedStringFromTable(@"room_details_main_section_rest_time_global", @"Vector", nil), [service localizedWorkTime]]; + } + } else { + text = NSLocalizedStringFromTable(@"room_details_main_section_rest_time_global_disabled", @"Vector", nil); + } + + return text; +} + +- (BOOL) isWorkTimeSwitchOn { + WorkTimeService *service = [WorkTimeService workTimeService:self.mainSession.myUser.userId]; + BOOL isOn = NO; + + if (service.isWorkTimeGlobalyEnabled) { + if ([service.store isWorkTimeRoom:self.roomId] ) { + if ([service.store isWorkTimeInRoomActive:self.roomId]) { + isOn = YES; + } + } else { + isOn = !self->mxRoom.isDirect; + } + } + + return isOn; +} + +- (BOOL) isWorkTimeSwitchEnabled { + WorkTimeService *service = [WorkTimeService workTimeService:self.mainSession.myUser.userId]; + return service.isWorkTimeGlobalyEnabled; +} + +- (void) updateSectionsAsPersonalNotes { + // create sections + NSMutableArray *tmpSections = [NSMutableArray arrayWithCapacity:SECTION_TAG_BANNED_ADVANCED + 1]; + + Section *sectionMain = [Section sectionWithTag:SECTION_TAG_MAIN]; + [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_PHOTO]; + [sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_NAME]; + + [tmpSections addObject:sectionMain]; + + // update sections + self.tableViewSections.sections = tmpSections; +} + @end diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroCell.swift b/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroCell.swift index f53c943c2..499b5dc71 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroCell.swift @@ -1,5 +1,6 @@ // // Copyright 2020 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -174,7 +175,9 @@ class RoomCreationIntroCell: MXKRoomBubbleTableViewCell { let discussionType: DiscussionType - if roomSummary.isDirect { + if room.isPersonalNotesRoom() { + discussionType = .personalNotes + } else if roomSummary.isDirect { if roomSummary.membersCount.members > 2 { discussionType = .directMessage } else { diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroCellContentView.swift b/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroCellContentView.swift index 671976e29..f6c78a5b7 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroCellContentView.swift +++ b/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroCellContentView.swift @@ -1,5 +1,6 @@ // // Copyright 2020 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -95,6 +96,8 @@ final class RoomCreationIntroCellContentView: UIView, NibLoadable, Themable { switch viewData.dicussionType { case .room(topic: _): hideAddParticipants = false + case .personalNotes: + hideAddParticipants = true default: hideAddParticipants = true } @@ -135,6 +138,8 @@ final class RoomCreationIntroCellContentView: UIView, NibLoadable, Themable { informationAttributedText = self.buildDMInformationText(with: viewData.roomDisplayName, isDirect: true) case .multipleDirectMessage: informationAttributedText = self.buildDMInformationText(with: viewData.roomDisplayName, isDirect: false) + case.personalNotes: + informationAttributedText = self.buildPersonalNotesInformationText(with: viewData.roomDisplayName, isDirect: false) } return informationAttributedText @@ -175,15 +180,19 @@ final class RoomCreationIntroCellContentView: UIView, NibLoadable, Themable { attributedString.append(firstSentencePart1) attributedString.append(firstSentencePart2) attributedString.append(firstSentencePart3) - - if isDirect { - attributedString.append(NSAttributedString(string: VectorL10n.roomIntroCellInformationDmSentence2, attributes: informationTextDefaultAttributes)) - } else { - attributedString.append(NSAttributedString(string: VectorL10n.roomIntroCellInformationMultipleDmSentence2, attributes: informationTextDefaultAttributes)) - } return attributedString - } + } + + private func buildPersonalNotesInformationText(with roomName: String, isDirect: Bool) -> NSAttributedString { + + let attributedString = NSMutableAttributedString() + + let firstSentence = NSAttributedString(string: NSLocalizedString("bwi_personal_notes_beginning_notes_room", tableName: "Vector", comment: "")) + attributedString.append(firstSentence) + + return attributedString + } private func setupInformationTextTapGestureRecognizer() { let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleInformationTextTap(_:))) diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroViewData.swift b/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroViewData.swift index 3acbc3ddc..c7840cd26 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroViewData.swift +++ b/Riot/Modules/Room/Views/BubbleCells/RoomCreationIntro/RoomCreationIntroViewData.swift @@ -1,5 +1,6 @@ // // Copyright 2020 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,6 +21,7 @@ enum DiscussionType { case directMessage case multipleDirectMessage case room(topic: String?) + case personalNotes } struct RoomCreationIntroViewData { diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h b/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h index fafce9df7..0edd51535 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h +++ b/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h @@ -1,5 +1,6 @@ // // Copyright 2021 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -173,5 +174,10 @@ typedef NS_ENUM(NSUInteger, RoomTimelineCellIdentifier) { RoomTimelineCellIdentifierSelectedSticker, RoomTimelineCellIdentifierRoomPredecessor, RoomTimelineCellIdentifierRoomCreationIntro, - RoomTimelineCellIdentifierTyping + RoomTimelineCellIdentifierTyping, + + // BWI Contentscanner + ContentScannerIdentiferStatus, + ContentScannerIdentifierThumbnail, + ContentScannerIdentifierUndecided }; diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m index 2a9626796..e3aaecb52 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m @@ -1,5 +1,6 @@ // // Copyright 2021 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -110,6 +111,7 @@ - (void)registerLocationCellsForTableView:(UITableView*)tableView { +#if LOCATION // Incoming [tableView registerClass:LocationIncomingBubbleCell.class forCellReuseIdentifier:LocationIncomingBubbleCell.defaultReuseIdentifier]; [tableView registerClass:LocationIncomingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:LocationIncomingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; @@ -118,6 +120,7 @@ // Outgoing [tableView registerClass:LocationOutgoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:LocationOutgoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; [tableView registerClass:LocationOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:LocationOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +#endif } - (void)registerFileWithoutThumbnailCellsForTableView:(UITableView*)tableView @@ -282,6 +285,7 @@ - (NSDictionary*)locationCellsMapping { return @{ +#if LOCATION // Incoming @(RoomTimelineCellIdentifierIncomingLocation) : LocationIncomingBubbleCell.class, @(RoomTimelineCellIdentifierIncomingLocationWithoutSenderInfo) : LocationIncomingWithoutSenderInfoBubbleCell.class, @@ -290,6 +294,7 @@ @(RoomTimelineCellIdentifierOutgoingLocation) : LocationOutgoingWithoutSenderInfoBubbleCell.class, @(RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo) : LocationOutgoingWithoutSenderInfoBubbleCell.class, @(RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle) : LocationOutgoingWithPaginationTitleBubbleCell.class +#endif }; } diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m index 0a68e6609..a9dccb63c 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m @@ -1,5 +1,6 @@ // // Copyright 2021 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -256,9 +257,11 @@ - (void)registerLocationCellsForTableView:(UITableView*)tableView { +#if LOCATION [tableView registerClass:LocationBubbleCell.class forCellReuseIdentifier:LocationBubbleCell.defaultReuseIdentifier]; [tableView registerClass:LocationWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:LocationWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; [tableView registerClass:LocationWithPaginationTitleBubbleCell.class forCellReuseIdentifier:LocationWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +#endif } #pragma mark Cell class association @@ -328,6 +331,9 @@ }; [cellClasses addEntriesFromDictionary:othersCells]; + NSDictionary *bwiContentScannerCellsMapping = [self bwiContentScannerCellsMapping]; + [cellClasses addEntriesFromDictionary:bwiContentScannerCellsMapping]; + return [cellClasses copy]; } @@ -530,6 +536,7 @@ - (NSDictionary*)locationCellsMapping { return @{ +#if LOCATION // Incoming @(RoomTimelineCellIdentifierIncomingLocation) : LocationBubbleCell.class, @(RoomTimelineCellIdentifierIncomingLocationWithoutSenderInfo) : LocationWithoutSenderInfoBubbleCell.class, @@ -538,8 +545,16 @@ @(RoomTimelineCellIdentifierOutgoingLocation) : LocationBubbleCell.class, @(RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo) : LocationWithoutSenderInfoBubbleCell.class, @(RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle) : LocationWithPaginationTitleBubbleCell.class +#endif }; } +- (NSDictionary*)bwiContentScannerCellsMapping +{ + return @{ + @(ContentScannerIdentiferStatus) : ContentScannerStatusCell.class, + @(ContentScannerIdentifierThumbnail) : ContentScannerThumbnailCell.class + }; +} @end diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift index 562f27080..7ce2f805e 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift @@ -1,5 +1,6 @@ // // Copyright 2021 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -316,6 +317,8 @@ class VoiceMessageToolbarView: PassthroughView, NibLoadable, Themable, UIGesture self.lockContainerBackgroundView.backgroundColor = theme.colors.navigation self.lockButtonsContainerView.backgroundColor = theme.colors.navigation + self.slideToCancelLabel.text = NSLocalizedString("voice_message_slide_to_cancel", tableName: "Vector", comment: "") + } completion: { _ in switch details.state { case .idle: diff --git a/Riot/Modules/Rooms/RoomsViewController.h b/Riot/Modules/Rooms/RoomsViewController.h index 62d4b50fb..68e79ea69 100644 --- a/Riot/Modules/Rooms/RoomsViewController.h +++ b/Riot/Modules/Rooms/RoomsViewController.h @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,12 +15,12 @@ limitations under the License. */ -#import "RecentsViewController.h" +#import "RecentsBannerViewController.h" /** The `RoomsViewController` screen is the view controller displayed when `Rooms` tab is selected. */ -@interface RoomsViewController : RecentsViewController +@interface RoomsViewController : RecentsBannerViewController + (instancetype)instantiate; diff --git a/Riot/Modules/Rooms/RoomsViewController.m b/Riot/Modules/Rooms/RoomsViewController.m index 71975e939..672933270 100644 --- a/Riot/Modules/Rooms/RoomsViewController.m +++ b/Riot/Modules/Rooms/RoomsViewController.m @@ -58,7 +58,7 @@ self.recentsTableView.tag = RecentsDataSourceModeRooms; // Add the (+) button programmatically - plusButtonImageView = [self vc_addFABWithImage:[UIImage imageNamed:@"rooms_floating_action"] + plusButtonImageView = [self vc_addFABWithImage:[[UIImage imageNamed:@"rooms_floating_action"] vc_tintedImageUsingColor:ThemeService.shared.theme.tintColor] target:self action:@selector(onPlusButtonPressed)]; } diff --git a/Riot/Modules/Rooms/ShowDirectory/Cells/Network/DirectoryNetworkTableHeaderFooterView.swift b/Riot/Modules/Rooms/ShowDirectory/Cells/Network/DirectoryNetworkTableHeaderFooterView.swift index 13964f93f..ccff2de1e 100644 --- a/Riot/Modules/Rooms/ShowDirectory/Cells/Network/DirectoryNetworkTableHeaderFooterView.swift +++ b/Riot/Modules/Rooms/ShowDirectory/Cells/Network/DirectoryNetworkTableHeaderFooterView.swift @@ -1,5 +1,6 @@ // // Copyright 2020 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,6 +32,7 @@ class DirectoryNetworkTableHeaderFooterView: UITableViewHeaderFooterView { @IBOutlet private weak var switchButton: UIButton! { didSet { switchButton.setTitle(VectorL10n.switch, for: .normal) + switchButton.isHidden = true } } @IBOutlet private weak var separatorView: UIView! diff --git a/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift b/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift index d6b67472b..e6d80df2a 100644 --- a/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift +++ b/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift @@ -1,5 +1,6 @@ // // Copyright 2020 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -35,6 +36,9 @@ class DirectoryRoomTableViewCell: UITableViewCell { @IBOutlet private weak var topicLabel: UILabel! @IBOutlet private weak var joinButton: UIButton! @IBOutlet private weak var joinActivityIndicator: UIActivityIndicatorView! + @IBOutlet private weak var joinedImageView: UIImageView! + @IBOutlet weak var widthLayoutContraint: NSLayoutConstraint! + weak var delegate: DirectoryRoomTableViewCellDelegate? @@ -70,11 +74,18 @@ class DirectoryRoomTableViewCell: UITableViewCell { if viewModel.isJoined { joinButton.setTitle(VectorL10n.joined, for: .normal) + joinButton.isHidden = true joinButton.isUserInteractionEnabled = false + joinedImageView.isHidden = false + widthLayoutContraint.constant = 40 } else { joinButton.setTitle(VectorL10n.join, for: .normal) + joinButton.isHidden = false joinButton.isUserInteractionEnabled = true + joinedImageView.isHidden = true + widthLayoutContraint.constant = 110 } + joinActivityIndicator.stopAnimating() joinActivityIndicator.isHidden = true update(theme: ThemeService.shared().theme) @@ -97,6 +108,7 @@ extension DirectoryRoomTableViewCell: Themable { displaynameLabel.textColor = theme.textPrimaryColor numberOfUsersLabel.textColor = theme.textSecondaryColor topicLabel.textColor = theme.textSecondaryColor + joinedImageView.tintColor = theme.tintColor if let viewModel = viewModel, viewModel.isJoined { joinButton.backgroundColor = theme.backgroundColor diff --git a/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.xib b/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.xib index d4bd7b23c..4d2b9807f 100644 --- a/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.xib +++ b/Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.xib @@ -1,9 +1,9 @@ - + - + @@ -18,7 +18,7 @@ - + @@ -26,7 +26,6 @@ - @@ -35,60 +34,72 @@ - - + + - + - + + + + + + - - - + + - + - - + + - - + - + - + + @@ -123,7 +135,7 @@ - + @@ -140,14 +152,17 @@ - + + + + diff --git a/Riot/Modules/Rooms/ShowDirectory/PublicRoomsDirectoryViewModel.swift b/Riot/Modules/Rooms/ShowDirectory/PublicRoomsDirectoryViewModel.swift index a196bac65..eee9c5540 100644 --- a/Riot/Modules/Rooms/ShowDirectory/PublicRoomsDirectoryViewModel.swift +++ b/Riot/Modules/Rooms/ShowDirectory/PublicRoomsDirectoryViewModel.swift @@ -1,5 +1,6 @@ // // Copyright 2021 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -32,7 +33,11 @@ class PublicRoomsDirectoryViewModel { } var directoryServerDisplayname: String? { - return dataSource.directoryServerDisplayname + if BuildSettings.showCustomServerDisplayName { + return BuildSettings.customServerDisplayName + } else { + return dataSource.directoryServerDisplayname + } } // MARK: - Setup diff --git a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift index c9be632dd..c5a1a817a 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -48,7 +49,18 @@ final class SecretsRecoveryWithKeyCoordinator: SecretsRecoveryWithKeyCoordinator } func toPresentable() -> UIViewController { - return self.secretsRecoveryWithKeyViewController + let controller = self.secretsRecoveryWithKeyViewController + + // If the flow is not cancellable, run it fullscreen + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if session.homeserverWellknown.backupRequired() { + if #available(iOS 13.0, *) { + controller.modalPresentationStyle = .fullScreen + } + } + } + + return controller } } diff --git a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift index 911f08302..6ff0bcd05 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -86,11 +87,16 @@ final class SecretsRecoveryWithKeyViewController: UIViewController { // MARK: - Private private func setupViews() { - let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in - self?.cancelButtonAction() - } - self.navigationItem.rightBarButtonItem = cancelBarButtonItem + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if !session.homeserverWellknown.backupRequired() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + } + } + self.title = VectorL10n.secretsRecoveryWithKeyTitle self.scrollView.keyboardDismissMode = .interactive @@ -151,11 +157,7 @@ final class SecretsRecoveryWithKeyViewController: UIViewController { // Reset secrets button - let resetSecretsAttributedString = NSMutableAttributedString(string: VectorL10n.secretsRecoveryResetActionPart1, attributes: [.foregroundColor: self.theme.textPrimaryColor]) - let resetSecretsAttributedStringPart2 = NSAttributedString(string: VectorL10n.secretsRecoveryResetActionPart2, attributes: [.foregroundColor: self.theme.warningColor]) - - resetSecretsAttributedString.append(resetSecretsAttributedStringPart2) - + let resetSecretsAttributedString = NSMutableAttributedString(string: VectorL10n.secretsRecoveryResetActionPart1, attributes: [.foregroundColor: self.theme.tintColor]) self.resetSecretsButton.setAttributedTitle(resetSecretsAttributedString, for: .normal) } diff --git a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift index bb2390050..ad216a898 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -46,8 +47,20 @@ final class SecretsRecoveryWithPassphraseCoordinator: SecretsRecoveryWithPassphr self.secretsRecoveryWithPassphraseViewModel.coordinatorDelegate = self } + func toPresentable() -> UIViewController { - return self.secretsRecoveryWithPassphraseViewController + let controller = self.secretsRecoveryWithPassphraseViewController + + // If the flow is not cancellable, run it fullscreen + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if session.homeserverWellknown.backupRequired() { + if #available(iOS 13.0, *) { + controller.modalPresentationStyle = .fullScreen + } + } + } + + return controller } } diff --git a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift index 5c64fd51b..ef86b5b81 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -86,10 +87,14 @@ final class SecretsRecoveryWithPassphraseViewController: UIViewController { // MARK: - Private private func setupViews() { - let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in - self?.viewModel.process(viewAction: .cancel) + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if !session.homeserverWellknown.backupRequired() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.viewModel.process(viewAction: .cancel) + } + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + } } - self.navigationItem.rightBarButtonItem = cancelBarButtonItem self.title = VectorL10n.secretsRecoveryWithPassphraseTitle @@ -161,15 +166,15 @@ final class SecretsRecoveryWithPassphraseViewController: UIViewController { useRecoveryKeyAttributedString.append(unknownRecoveryKeyAttributedStringPart2) useRecoveryKeyAttributedString.append(unknownRecoveryKeyAttributedStringPart3) - self.useRecoveryKeyButton.setAttributedTitle(useRecoveryKeyAttributedString, for: .normal) + if BuildSettings.showRecoverWithKey == true { + self.useRecoveryKeyButton.setAttributedTitle(useRecoveryKeyAttributedString, for: .normal) + } else { + self.useRecoveryKeyButton?.visiblity(gone: true, dimension: 0.0) + } // Reset secrets button - let resetSecretsAttributedString = NSMutableAttributedString(string: VectorL10n.secretsRecoveryResetActionPart1, attributes: [.foregroundColor: self.theme.textPrimaryColor]) - let resetSecretsAttributedStringPart2 = NSAttributedString(string: VectorL10n.secretsRecoveryResetActionPart2, attributes: [.foregroundColor: self.theme.warningColor]) - - resetSecretsAttributedString.append(resetSecretsAttributedStringPart2) - + let resetSecretsAttributedString = NSMutableAttributedString(string: VectorL10n.secretsRecoveryResetActionPart1, attributes: [.foregroundColor: self.theme.tintColor]) self.resetSecretsButton.setAttributedTitle(resetSecretsAttributedString, for: .normal) } diff --git a/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift b/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift index fdca1dc7d..3be4f1260 100644 --- a/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift +++ b/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -76,7 +77,18 @@ final class SecretsRecoveryCoordinator: SecretsRecoveryCoordinatorType { } func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() + let controller = self.navigationRouter.toPresentable() + + // If the flow is not cancellable, run it fullscreen + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if session.homeserverWellknown.backupRequired() { + if #available(iOS 13.0, *) { + controller.modalPresentationStyle = .fullScreen + } + } + } + + return controller } // MARK: - Private diff --git a/Riot/Modules/Secrets/Reset/SecretsResetViewController.swift b/Riot/Modules/Secrets/Reset/SecretsResetViewController.swift index 6008a1fb0..1ca0dbb09 100644 --- a/Riot/Modules/Secrets/Reset/SecretsResetViewController.swift +++ b/Riot/Modules/Secrets/Reset/SecretsResetViewController.swift @@ -2,6 +2,7 @@ // $ createScreen.sh Secrets/Reset SecretsReset /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -108,12 +109,15 @@ final class SecretsResetViewController: UIViewController { } private func setupViews() { - let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in - self?.cancelButtonAction() + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if session.homeserverWellknown.backupRequired() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + } } - self.navigationItem.rightBarButtonItem = cancelBarButtonItem - self.title = VectorL10n.secretsResetTitle self.scrollView.keyboardDismissMode = .interactive diff --git a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewController.swift b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewController.swift index e6be0650d..124f953fd 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewController.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewController.swift @@ -2,6 +2,7 @@ // $ createScreen.sh SecretsSetupRecoveryKey SecretsSetupRecoveryKey /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -108,12 +109,16 @@ final class SecretsSetupRecoveryKeyViewController: UIViewController { } private func setupViews() { - let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in - self?.cancelButtonAction() + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if !session.homeserverWellknown.backupRequired() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + } } - self.navigationItem.rightBarButtonItem = cancelBarButtonItem - self.vc_removeBackTitle() self.title = VectorL10n.secretsSetupRecoveryKeyTitle @@ -137,19 +142,21 @@ final class SecretsSetupRecoveryKeyViewController: UIViewController { self.renderLoaded(passphraseOnly: passphraseOnly) case .recoveryCreated(let recoveryKey): self.renderRecoveryCreated(recoveryKey: recoveryKey) + if BuildSettings.skipKeyBackupStep { + self.viewModel.process(viewAction: .done) + } case .error(let error): self.render(error: error) } } - private func renderLoaded(passphraseOnly: Bool) { + private func renderLoaded(passphraseOnly: Bool) { self.isPassphraseOnly = passphraseOnly let title: String let secretsLogoImage: UIImage let informationText: String let recoveryKeyText: String? - if passphraseOnly { title = VectorL10n.secretsSetupRecoveryPassphraseSummaryTitle secretsLogoImage = Asset.Images.secretsSetupPassphrase.image diff --git a/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseViewController.swift b/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseViewController.swift index 0cb5bd2d2..a21699cf7 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseViewController.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseViewController.swift @@ -2,6 +2,7 @@ // $ createScreen.sh Test SecretsSetupRecoveryPassphrase /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,7 +19,7 @@ import UIKit -final class SecretsSetupRecoveryPassphraseViewController: UIViewController { +final class SecretsSetupRecoveryPassphraseViewController: UIViewController, UITextFieldDelegate { // MARK: - Constants @@ -82,6 +83,7 @@ final class SecretsSetupRecoveryPassphraseViewController: UIViewController { self.keyboardAvoider = KeyboardAvoider(scrollViewContainerView: self.view, scrollView: self.scrollView) self.activityPresenter = ActivityIndicatorPresenter() self.errorPresenter = MXKErrorAlertPresentation() + self.passphraseTextField.delegate = self self.registerThemeServiceDidChangeThemeNotification() self.update(theme: self.theme) @@ -119,12 +121,16 @@ final class SecretsSetupRecoveryPassphraseViewController: UIViewController { // MARK: - Private private func setupViews() { - let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in - self?.cancelButtonAction() + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if !session.homeserverWellknown.backupRequired() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + } } - - self.navigationItem.rightBarButtonItem = cancelBarButtonItem - + self.vc_removeBackTitle() self.title = VectorL10n.secretsSetupRecoveryPassphraseTitle @@ -165,16 +171,17 @@ final class SecretsSetupRecoveryPassphraseViewController: UIViewController { if let viewData = self.currentViewData, case .confimPassphrase = viewData.mode { passphraseTitle = VectorL10n.secretsSetupRecoveryPassphraseConfirmPassphrasePlaceholder + self.additionalInformationLabel.isHidden = true } else { passphraseTitle = VectorL10n.keyBackupSetupPassphrasePassphrasePlaceholder + self.additionalInformationLabel.isHidden = false } - + self.passphraseTextField.attributedPlaceholder = NSAttributedString(string: passphraseTitle, attributes: [.foregroundColor: theme.placeholderTextColor]) self.passphraseVisibilityButton.tintColor = theme.tintColor self.additionalInformationLabel.textColor = theme.textSecondaryColor - self.validateButton.update(theme: theme) } @@ -312,6 +319,13 @@ final class SecretsSetupRecoveryPassphraseViewController: UIViewController { private func cancelButtonAction() { self.viewModel.process(viewAction: .cancel) } + + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + self.passphraseTextField.resignFirstResponder() + self.viewModel.process(viewAction: .validate) + return true + } } // MARK: - SecretsSetupRecoveryPassphraseViewModelViewDelegate diff --git a/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift b/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift index 346f0c535..2239161e1 100644 --- a/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift +++ b/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -86,18 +87,21 @@ final class SecureBackupSetupIntroViewController: UIViewController { // MARK: - Private private func setupViews() { - let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in - guard let self = self else { - return - } - self.delegate?.secureBackupSetupIntroViewControllerDidCancel(self, showSkipAlert: true) - } - self.navigationItem.rightBarButtonItem = cancelBarButtonItem - self.title = VectorL10n.secureKeyBackupSetupIntroTitle - self.informationLabel.text = VectorL10n.secureKeyBackupSetupIntroInfo + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if !session.homeserverWellknown.backupRequired() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + guard let self = self else { + return + } + self.delegate?.secureBackupSetupIntroViewControllerDidCancel(self, showSkipAlert: true) + } + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + } + } + self.secureKeyCell.fill(title: VectorL10n.secureKeyBackupSetupIntroUseSecurityKeyTitle, information: VectorL10n.secureKeyBackupSetupIntroUseSecurityKeyInfo, image: Asset.Images.secretsSetupKey.image) @@ -119,6 +123,19 @@ final class SecureBackupSetupIntroViewController: UIViewController { } self.delegate?.secureBackupSetupIntroViewControllerDidTapUsePassphrase(self) } + + setupBackupMethods() + } + + private func setupBackupMethods() { + // Hide setup methods that are not listed + if !self.viewModel.wellKnown.isBackupMethodKeySupported() { + self.secureKeyCell.isHidden = true + } + + if !self.viewModel.wellKnown.isBackupMethodPassphraseSupported() { + self.securePassphraseCell.isHidden = true + } } private func renderLoading() { @@ -172,27 +189,44 @@ final class SecureBackupSetupIntroViewController: UIViewController { // we need to get this private key first. Ask the user to make a key backup restore to catch it if keyBackup.keyBackupVersion != nil && keyBackup.hasPrivateKeyInCryptoStore == false { - let alertController = UIAlertController(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorTitle, - message: VectorL10n.secureKeyBackupSetupExistingBackupErrorInfo, - preferredStyle: .alert) + // don't show dialog if forced, already asked before in mandatory verification recover + if BuildSettings.forceResetBackupIfLost { + let alertController = UIAlertController(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorTitle, + message: VectorL10n.secureKeyBackupSetupExistingBackupErrorInfo, + preferredStyle: .alert) - let connectAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorUnlockIt, style: .default) { (_) in - self.delegate?.secureBackupSetupIntroViewControllerDidTapConnectToKeyBackup(self) + let connectAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorUnlockIt, style: .default) { (_) in + self.delegate?.secureBackupSetupIntroViewControllerDidTapConnectToKeyBackup(self) + } + + let resetAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorDeleteIt, style: .destructive) { (_) in + self.deleteKeybackup() + } + } else { + + let alertContoller = UIAlertController(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorTitle, + message: VectorL10n.secureKeyBackupSetupExistingBackupErrorInfo, + preferredStyle: .alert) + + let connectAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorUnlockIt, style: .default) { (_) in + self.delegate?.secureBackupSetupIntroViewControllerDidTapConnectToKeyBackup(self) + } + + let resetAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorDeleteIt, style: .destructive) { (_) in + self.deleteKeybackup() + } + + let cancelAction = UIAlertAction(title: VectorL10n.cancel, style: .cancel) { (_) in + self.delegate?.secureBackupSetupIntroViewControllerDidCancel(self, showSkipAlert: false) + } + + alertContoller.addAction(connectAction) + alertContoller.addAction(resetAction) + alertContoller.addAction(cancelAction) + + self.present(alertContoller, animated: true) + } - - let resetAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorDeleteIt, style: .destructive) { (_) in - self.deleteKeybackup() - } - - let cancelAction = UIAlertAction(title: VectorL10n.cancel, style: .cancel) { (_) in - self.delegate?.secureBackupSetupIntroViewControllerDidCancel(self, showSkipAlert: false) - } - - alertController.addAction(connectAction) - alertController.addAction(resetAction) - alertController.addAction(cancelAction) - - self.present(alertController, animated: true) } } diff --git a/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModel.swift b/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModel.swift index 3a10148d9..dd17d17d1 100644 --- a/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModel.swift +++ b/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModel.swift @@ -1,5 +1,6 @@ // // Copyright 2020 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,11 +24,13 @@ final class SecureBackupSetupIntroViewModel: SecureBackupSetupIntroViewModelType // TODO: Make these properties private let keyBackup: MXKeyBackup? let checkKeyBackup: Bool + let wellKnown: MXWellKnown // MARK: - Setup - init(keyBackup: MXKeyBackup?, checkKeyBackup: Bool) { + init(keyBackup: MXKeyBackup?, checkKeyBackup: Bool, wellKnown: MXWellKnown) { self.keyBackup = keyBackup self.checkKeyBackup = checkKeyBackup + self.wellKnown = wellKnown } } diff --git a/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModelType.swift b/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModelType.swift index b7d08f5d5..e9c956d44 100644 --- a/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModelType.swift +++ b/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModelType.swift @@ -1,5 +1,6 @@ // // Copyright 2020 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,4 +23,5 @@ protocol SecureBackupSetupIntroViewModelType { // TODO: Hide these properties from interface and use same behavior as other view models var keyBackup: MXKeyBackup? { get } var checkKeyBackup: Bool { get } + var wellKnown: MXWellKnown { get } } diff --git a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift index 543616377..bfd7d01c6 100644 --- a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift +++ b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift @@ -2,6 +2,7 @@ // $ createRootCoordinator.sh KeyBackupSetup/SecureSetup SecureKeyBackupSetup /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,27 +21,34 @@ import UIKit @objcMembers final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType { - + // MARK: - Properties - + // MARK: Private - + private let navigationRouter: NavigationRouterType private let session: MXSession private let recoveryService: MXRecoveryService private let keyBackup: MXKeyBackup? private let checkKeyBackup: Bool + + private var isBackupSetupMethodKeySupported: Bool { + guard let homeserverWellknown = self.session.homeserverWellknown else { + return false + } + return homeserverWellknown.isBackupMethodKeySupported() + } private let allowOverwrite: Bool // MARK: Public // Must be used only internally var childCoordinators: [Coordinator] = [] - + weak var delegate: SecureBackupSetupCoordinatorDelegate? - + // MARK: - Setup - + /// Initializer /// - Parameters: /// - session: The MXSession. @@ -51,52 +59,65 @@ final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType { self.recoveryService = session.crypto.recoveryService self.keyBackup = session.crypto.backup self.checkKeyBackup = checkKeyBackup + self.allowOverwrite = allowOverwrite - + if let navigationRouter = navigationRouter { self.navigationRouter = navigationRouter } else { self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController()) } } - + // MARK: - Public methods - + func start() { let rootViewController = self.createIntro() - + if self.navigationRouter.modules.isEmpty == false { self.navigationRouter.push(rootViewController, animated: true, popCompletion: nil) } else { self.navigationRouter.setRootModule(rootViewController) } } - + func toPresentable() -> UIViewController { - return self.navigationRouter.toPresentable() + let controller = self.navigationRouter.toPresentable() + + // If the flow is not cancellable, run it fullscreen + if let session = AppDelegate.theDelegate().mxSessions.first as? MXSession { + if session.homeserverWellknown.backupRequired() { + if #available(iOS 13.0, *) { + controller.modalPresentationStyle = .fullScreen + } + } + } + + return controller } - + // MARK: - Private methods private func createIntro() -> SecureBackupSetupIntroViewController { // TODO: Use a coordinator - let viewModel = SecureBackupSetupIntroViewModel(keyBackup: self.keyBackup, checkKeyBackup: self.checkKeyBackup) + let viewModel = SecureBackupSetupIntroViewModel(keyBackup: self.keyBackup, checkKeyBackup: self.checkKeyBackup, wellKnown: self.session.homeserverWellknown) let introViewController = SecureBackupSetupIntroViewController.instantiate(with: viewModel) introViewController.delegate = self return introViewController } - + private func showSetupKey(passphraseOnly: Bool, passphrase: String? = nil) { + let coordinator = SecretsSetupRecoveryKeyCoordinator(recoveryService: self.recoveryService, passphrase: passphrase, passphraseOnly: passphraseOnly, allowOverwrite: allowOverwrite) coordinator.delegate = self coordinator.start() - + self.add(childCoordinator: coordinator) self.navigationRouter.push(coordinator, animated: true) { [weak self] in self?.remove(childCoordinator: coordinator) } } - + private func showSetupPassphrase() { let coordinator = SecretsSetupRecoveryPassphraseCoordinator(passphraseInput: .new) coordinator.delegate = self @@ -197,15 +218,17 @@ extension SecureBackupSetupCoordinator: SecretsSetupRecoveryKeyCoordinatorDelega // MARK: - SecretsSetupRecoveryPassphraseCoordinatorDelegate extension SecureBackupSetupCoordinator: SecretsSetupRecoveryPassphraseCoordinatorDelegate { - + func secretsSetupRecoveryPassphraseCoordinator(_ coordinator: SecretsSetupRecoveryPassphraseCoordinatorType, didEnterNewPassphrase passphrase: String) { self.showSetupPassphraseConfirmation(with: passphrase) } - + func secretsSetupRecoveryPassphraseCoordinator(_ coordinator: SecretsSetupRecoveryPassphraseCoordinatorType, didConfirmPassphrase passphrase: String) { - self.showSetupKey(passphraseOnly: false, passphrase: passphrase) + + // Do not present recovery key export screen if secure backup setup key method is not supported + self.showSetupKey(passphraseOnly: !self.isBackupSetupMethodKeySupported, passphrase: passphrase) } - + func secretsSetupRecoveryPassphraseCoordinatorDidCancel(_ coordinator: SecretsSetupRecoveryPassphraseCoordinatorType) { self.didCancel() } diff --git a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewController.storyboard b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewController.storyboard index e3f675ca6..2530d3c2e 100644 --- a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewController.storyboard +++ b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewController.storyboard @@ -1,354 +1,358 @@ - - + + - + + - + - - - + + + - - + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + - + - + + + + diff --git a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewController.swift b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewController.swift index 50c41980d..9cf94594f 100644 --- a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewController.swift +++ b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewController.swift @@ -2,6 +2,7 @@ // $ createScreen.sh SetPinCode/EnterPinCode EnterPinCode /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -161,7 +162,7 @@ final class EnterPinCodeViewController: UIViewController { } private func setupViews() { - let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + let cancelBarButtonItem = MXKBarButtonItem(title: NSLocalizedString("cancel", tableName: "Vector", comment: ""), style: .plain) { [weak self] in self?.cancelButtonAction() } @@ -171,6 +172,7 @@ final class EnterPinCodeViewController: UIViewController { self.title = "" notAllowedPinLabel.text = VectorL10n.pinProtectionNotAllowedPin + forgotPinButton.setTitle(VectorL10n.pinProtectionForgotPin, for: .normal) placeholderStackView.vc_removeAllArrangedSubviews() for i in 0.. Bool { + do { + try secureStore.unlock(passphrase: pinCode) + let pin = try secureStore.string(forKey: StoreKeys.pin) + return pinCode == pin + } catch let error { + MXLog.error("[PinCodePreferences] Error when checking user pin: \(error)") + return false } } - var canUseBiometricsToUnlock: Bool? { + var biometricsEnabled: Bool { get { - do { - return try store.bool(forKey: StoreKeys.canUseBiometricsToUnlock) - } catch let error { - MXLog.debug("[PinCodePreferences] Error when reading canUseBiometricsToUnlock from store: \(error)") - return nil - } + return RiotSettings.shared.biometricsEnabled } set { - do { - try store.set(newValue, forKey: StoreKeys.canUseBiometricsToUnlock) - } catch let error { - MXLog.debug("[PinCodePreferences] Error when storing canUseBiometricsToUnlock to the store: \(error)") - } + RiotSettings.shared.biometricsEnabled = newValue + } + } + + var canUseBiometricsToUnlock: Bool { + get { + return RiotSettings.shared.canUseBiometricsToUnlock + } set { + RiotSettings.shared.canUseBiometricsToUnlock = newValue } } var numberOfPinFailures: Int { get { - do { - return try store.integer(forKey: StoreKeys.numberOfPinFailures) ?? 0 - } catch let error { - MXLog.debug("[PinCodePreferences] Error when reading numberOfPinFailures from store: \(error)") - return 0 - } + return RiotSettings.shared.numberOfPinFailures } set { - do { - try store.set(newValue, forKey: StoreKeys.numberOfPinFailures) - } catch let error { - MXLog.debug("[PinCodePreferences] Error when storing numberOfPinFailures to the store: \(error)") - } + RiotSettings.shared.numberOfPinFailures = newValue } } var numberOfBiometricsFailures: Int { get { - do { - return try store.integer(forKey: StoreKeys.numberOfBiometricsFailures) ?? 0 - } catch let error { - MXLog.debug("[PinCodePreferences] Error when reading numberOfBiometricsFailures from store: \(error)") - return 0 - } + return RiotSettings.shared.numberOfBiometricsFailures } set { - do { - try store.set(newValue, forKey: StoreKeys.numberOfBiometricsFailures) - } catch let error { - MXLog.debug("[PinCodePreferences] Error when storing numberOfBiometricsFailures to the store: \(error)") - } + RiotSettings.shared.numberOfBiometricsFailures = newValue } } var isBiometricsSet: Bool { - return biometricsEnabled == true && (canUseBiometricsToUnlock ?? true) + return biometricsEnabled && canUseBiometricsToUnlock && isBiometricsAvailable } - + func localizedBiometricsName() -> String? { if isBiometricsAvailable { let context = LAContext() @@ -217,8 +246,8 @@ final class PinCodePreferences: NSObject { /// Resets user PIN func reset() { pin = nil - biometricsEnabled = nil - canUseBiometricsToUnlock = nil + biometricsEnabled = false + canUseBiometricsToUnlock = true resetCounters() } diff --git a/Riot/Modules/SetPinCode/SetPinCoordinator.swift b/Riot/Modules/SetPinCode/SetPinCoordinator.swift index 627f14730..979b890c7 100644 --- a/Riot/Modules/SetPinCode/SetPinCoordinator.swift +++ b/Riot/Modules/SetPinCode/SetPinCoordinator.swift @@ -2,6 +2,7 @@ // $ createRootCoordinator.sh SetPinCode SetPin EnterPinCode /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -124,7 +125,7 @@ final class SetPinCoordinator: SetPinCoordinatorType { } private func removeBiometrics() { - pinCodePreferences.biometricsEnabled = nil + pinCodePreferences.biometricsEnabled = false } } @@ -147,7 +148,6 @@ extension SetPinCoordinator: EnterPinCodeCoordinatorDelegate { storePin(pin) if pinCodePreferences.forcePinProtection && pinCodePreferences.isBiometricsAvailable && !pinCodePreferences.isBiometricsSet { viewMode = .setupBiometricsAfterLogin - setRootCoordinator(createSetupBiometricsCoordinator()) } else { self.delegate?.setPinCoordinatorDidComplete(self) } diff --git a/Riot/Modules/SetPinCode/SetupBiometrics/BiometricsAuthenticationPresenter.swift b/Riot/Modules/SetPinCode/SetupBiometrics/BiometricsAuthenticationPresenter.swift index 9cb1bef4f..5c6ae9ed5 100644 --- a/Riot/Modules/SetPinCode/SetupBiometrics/BiometricsAuthenticationPresenter.swift +++ b/Riot/Modules/SetPinCode/SetupBiometrics/BiometricsAuthenticationPresenter.swift @@ -1,5 +1,6 @@ // // Copyright 2020 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -39,9 +40,13 @@ final class BiometricsAuthenticationPresenter: NSObject { guard !BiometricsAuthenticationPresenter.isPresenting else { return } - BiometricsAuthenticationPresenter.isPresenting = true - LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: message) { (success, error) in + BiometricsAuthenticationPresenter.isPresenting = true + + let context = LAContext() + context.localizedFallbackTitle = NSLocalizedString("biometrics_mode_cant_unlock_button_title", tableName: "Vector", comment: "") + + context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: message) { (success, error) in if success { // Complete after a little delay DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { diff --git a/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewController.swift b/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewController.swift index 48cc5e28e..72b20b09d 100644 --- a/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewController.swift +++ b/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewController.swift @@ -2,6 +2,7 @@ // $ createScreen.sh SetPinCode/SetupBiometrics SetupBiometrics /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -113,18 +114,21 @@ final class SetupBiometricsViewController: UIViewController { } private func showSkipButton() { + self.navigationController?.navigationBar.isHidden = false self.navigationItem.rightBarButtonItem = MXKBarButtonItem(title: VectorL10n.skip, style: .plain) { [weak self] in self?.skipCancelButtonAction() } } private func showCancelButton() { + self.navigationController?.navigationBar.isHidden = false self.navigationItem.rightBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in self?.skipCancelButtonAction() } } private func hideSkipCancelButton() { + self.navigationController?.navigationBar.isHidden = true self.navigationItem.rightBarButtonItem = nil } diff --git a/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewModel.swift b/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewModel.swift index 788d0fd01..fe65d1371 100644 --- a/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewModel.swift +++ b/Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewModel.swift @@ -2,6 +2,7 @@ // $ createScreen.sh SetPinCode/SetupBiometrics SetupBiometrics /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -81,7 +82,7 @@ final class SetupBiometricsViewModel: SetupBiometricsViewModelType { biometricsAuthenticationPresenter.present(with: VectorL10n.biometricsUsageReason) { (response) in switch response { case .success: - self.pinCodePreferences.canUseBiometricsToUnlock = nil + self.pinCodePreferences.canUseBiometricsToUnlock = true self.pinCodePreferences.resetCounters() self.coordinatorDelegate?.setupBiometricsViewModelDidComplete(self) case .failure: @@ -94,7 +95,7 @@ final class SetupBiometricsViewModel: SetupBiometricsViewModelType { biometricsAuthenticationPresenter.present(with: VectorL10n.biometricsUsageReason) { (response) in switch response { case .success: - self.pinCodePreferences.canUseBiometricsToUnlock = nil + self.pinCodePreferences.canUseBiometricsToUnlock = true self.pinCodePreferences.resetCounters() self.coordinatorDelegate?.setupBiometricsViewModelDidComplete(self) case .failure(let error): @@ -117,6 +118,7 @@ final class SetupBiometricsViewModel: SetupBiometricsViewModelType { if pinCodePreferences.isPinSet { self.pinCodePreferences.canUseBiometricsToUnlock = false // cascade this cancellation, coordinator should take care of it + NotificationCenter.default.post(name: .biometricsDidFallbackToPin, object: nil) DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.coordinatorDelegate?.setupBiometricsViewModelDidCancel(self) } diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index 42920094f..c316db714 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -378,7 +379,6 @@ enum { MXDeviceInfo *deviceInfo = [self.mainSession.crypto deviceWithDeviceId:deviceId ofUser:self.mainSession.myUser.userId]; cell.textLabel.numberOfLines = 0; - [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; if (deviceInfo.trustLevel.isVerified) { @@ -390,6 +390,12 @@ enum { cell.textLabel.text = [VectorL10n manageSessionNotTrusted]; cell.imageView.image = [UIImage imageNamed:@"encryption_warning"]; } + + if (BuildSettings.bwiDisableSecuritySettingsUntrustedDevices && !deviceInfo.trustLevel.isVerified) { + cell.accessoryType = UITableViewCellAccessoryNone; + } else { + [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; + } return cell; } @@ -550,6 +556,21 @@ enum { return 24; } +- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (BuildSettings.bwiDisableSecuritySettingsUntrustedDevices) { + if (indexPath.section == SECTION_SESSION_INFO && indexPath.row == SESSION_INFO_TRUST) { + NSString *deviceId = device.deviceId; + MXDeviceInfo *deviceInfo = [self.mainSession.crypto deviceWithDeviceId:deviceId ofUser:self.mainSession.myUser.userId]; + + // unverified devices should not be selectable + if (!deviceInfo.trustLevel.isVerified) { + return nil; + } + } + } + return indexPath; +} + - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (self.tableView == tableView) diff --git a/Riot/Modules/Settings/Security/SecurityViewController.m b/Riot/Modules/Settings/Security/SecurityViewController.m index 99b7f7c93..d5d5ca17e 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.m +++ b/Riot/Modules/Settings/Security/SecurityViewController.m @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -297,9 +298,11 @@ TableViewSectionsDelegate> - (void)updateSections { NSMutableArray *sections = [NSMutableArray array]; - + + BOOL isSecuredBackupRequired = [self.mainSession.homeserverWellknown backupRequired]; + // Pin code section - + Section *pinCodeSection = [Section sectionWithTag:SECTION_PIN_CODE]; // Header and footer @@ -823,15 +826,14 @@ TableViewSectionsDelegate> [alertController addAction:[UIAlertAction actionWithTitle:@"Reset" style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) - { - // Setup and reset are the same thing - [self setupCrossSigning:nil]; - }]]; + handler:^(UIAlertAction * action) { + // Setup and reset are the same thing + [self setupCrossSigning:nil]; + }]]; [alertController addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel] - style:UIAlertActionStyleCancel - handler:nil]]; + style:UIAlertActionStyleCancel + handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; currentAlert = alertController; @@ -1417,14 +1419,14 @@ TableViewSectionsDelegate> preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - [self presentCompleteSecurity]; - }]]; + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + [self presentCompleteSecurity]; + }]]; [alertController addAction:[UIAlertAction actionWithTitle:[VectorL10n later] - style:UIAlertActionStyleCancel - handler:nil]]; + style:UIAlertActionStyleCancel + handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; currentAlert = alertController; diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index d1e1f163d..66d4571b9 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -2,6 +2,7 @@ Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -51,6 +52,8 @@ typedef NS_ENUM(NSUInteger, SECTION_TAG) { SECTION_TAG_SIGN_OUT = 0, SECTION_TAG_USER_SETTINGS, + SECTION_TAG_NEW_FEATURES, + SECTION_TAG_LOCATION_SHARING, SECTION_TAG_SENDING_MEDIA, SECTION_TAG_LINKS, SECTION_TAG_SECURITY, @@ -66,7 +69,9 @@ typedef NS_ENUM(NSUInteger, SECTION_TAG) SECTION_TAG_ABOUT, SECTION_TAG_LABS, SECTION_TAG_FLAIR, - SECTION_TAG_DEACTIVATE_ACCOUNT + SECTION_TAG_DEACTIVATE_ACCOUNT, + SECTION_TAG_DOWNTIME_WARNING, + SECTION_TAG_DEVELOPER }; typedef NS_ENUM(NSUInteger, USER_SETTINGS_INDEX) @@ -80,12 +85,27 @@ typedef NS_ENUM(NSUInteger, USER_SETTINGS_INDEX) USER_SETTINGS_ADD_PHONENUMBER_INDEX }; +typedef NS_ENUM(NSUInteger, DEVELOPER_SETTINGS_INDEX) +{ + DEVELOPER_SHOW_SETTINGS_INDEX = 0 +}; + typedef NS_ENUM(NSUInteger, USER_SETTINGS_OFFSET) { USER_SETTINGS_EMAILS_OFFSET = 2000, USER_SETTINGS_PHONENUMBERS_OFFSET = 1000 }; +typedef NS_ENUM(NSUInteger, NEW_FEATURES) +{ + SHOW_NEW_FEATURES +}; + +typedef NS_ENUM(NSUInteger, LOCATION_SHARING) +{ + LOCATION_SHARING_ENABLED +}; + typedef NS_ENUM(NSUInteger, SENDING_MEDIA) { SENDING_MEDIA_CONFIRM_SIZE = 0 @@ -100,6 +120,7 @@ typedef NS_ENUM(NSUInteger, LINKS_SHOW_URL_PREVIEWS) typedef NS_ENUM(NSUInteger, NOTIFICATION_SETTINGS) { NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX = 0, + NOTIFICATION_SETTINGS_ENABLE_REST_TIME, NOTIFICATION_SETTINGS_SYSTEM_SETTINGS, NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT, NOTIFICATION_SETTINGS_PIN_MISSED_NOTIFICATIONS_INDEX, @@ -127,8 +148,18 @@ typedef NS_ENUM(NSUInteger, LOCAL_CONTACTS) typedef NS_ENUM(NSUInteger, USER_INTERFACE) { - USER_INTERFACE_LANGUAGE_INDEX = 0, - USER_INTERFACE_THEME_INDEX, + //USER_INTERFACE_LANGUAGE_INDEX = 0, + USER_INTERFACE_THEME_INDEX = 0, + USER_INTERFACE_PERSONAL_NOTES, + TIMELINE_SIMPLE_INDEX, + TIMELINE_TIMESTAMP_INDEX, + TIMELINE_DELETED_MESSAGES_INDEX, + TIMELINE_NAME_CHANGE_INDEX, + TIMELINE_CHATEFFECTS_INDEX, + TIMELINE_ROOM_AVATAR_INDEX, + TIMELINE_USER_AVATAR_INDEX, + TIMELINE_ENTER_ROOM_INDEX, + USER_INTERFACE_LANGUAGE_INDEX, USER_INTERFACE_TIMELINE_STYLE_INDEX }; @@ -149,10 +180,20 @@ typedef NS_ENUM(NSUInteger, ADVANCED) typedef NS_ENUM(NSUInteger, ABOUT) { - ABOUT_COPYRIGHT_INDEX = 0, + ABOUT_VERSION_INDEX = 0, + ABOUT_OLM_VERSION_INDEX, + ABOUT_ELEMENT_VERSION_INDEX, + ABOUT_COPYRIGHT_INDEX, ABOUT_TERM_CONDITIONS_INDEX, ABOUT_PRIVACY_INDEX, ABOUT_THIRD_PARTY_INDEX, + ABOUT_SUPPORT_INDEX, + ABOUT_SHOW_NSFW_ROOMS_INDEX, + ABOUT_CRASH_REPORT_INDEX, + ABOUT_ENABLE_RAGESHAKE_INDEX, + ABOUT_MARK_ALL_AS_READ_INDEX, + ABOUT_CLEAR_CACHE_INDEX, + ABOUT_REPORT_BUG_INDEX, }; typedef NS_ENUM(NSUInteger, LABS_ENABLE) @@ -165,6 +206,12 @@ typedef NS_ENUM(NSUInteger, LABS_ENABLE) typedef NS_ENUM(NSUInteger, SECURITY) { SECURITY_BUTTON_INDEX = 0, + SECURITY_MATOMO_INDEX, +}; + +enum +{ + DOWNTIME_WARNING = 0 }; typedef void (^blockSettingsViewController_onReadyToDestroy)(void); @@ -196,6 +243,8 @@ TableViewSectionsDelegate> // profile updates // avatar UIImage* newAvatarImage; + BOOL shouldRemoveAvatarImage; + // the avatar image has been uploaded NSString* uploadedAvatarURL; @@ -321,16 +370,20 @@ TableViewSectionsDelegate> isResetPwdInProgress = NO; is3PIDBindingInProgress = NO; + shouldRemoveAvatarImage = false; + self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSettings]; } - (void)updateSections { - NSMutableArray *tmpSections = [NSMutableArray arrayWithCapacity:SECTION_TAG_DEACTIVATE_ACCOUNT + 1]; + NSMutableArray *tmpSections = [NSMutableArray arrayWithCapacity:SECTION_TAG_DOWNTIME_WARNING + 1]; - Section *sectionSignOut = [Section sectionWithTag:SECTION_TAG_SIGN_OUT]; - [sectionSignOut addRowWithTag:0]; - [tmpSections addObject:sectionSignOut]; + if ( [[[ServerDowntimeDefaultService alloc] init] isDowntimePresentable]) { + Section *sectionDowntime = [Section sectionWithTag:SECTION_TAG_DOWNTIME_WARNING]; + [sectionDowntime addRowWithTag:DOWNTIME_WARNING]; + [tmpSections addObject:sectionDowntime]; + } Section *sectionUserSettings = [Section sectionWithTag:SECTION_TAG_USER_SETTINGS]; [sectionUserSettings addRowWithTag:USER_SETTINGS_PROFILE_PICTURE_INDEX]; @@ -348,15 +401,21 @@ TableViewSectionsDelegate> [sectionUserSettings addRowWithTag:USER_SETTINGS_SURNAME_INDEX]; } MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - // add linked emails - for (NSInteger index = 0; index < account.linkedEmails.count; index++) + // add linked emails + if (BuildSettings.settingsScreenAllowAddingLinkedEmails) { - [sectionUserSettings addRowWithTag: USER_SETTINGS_EMAILS_OFFSET + index]; + for (NSInteger index = 0; index < account.linkedEmails.count; index++) + { + [sectionUserSettings addRowWithTag: USER_SETTINGS_EMAILS_OFFSET + index]; + } } // add linked phone numbers - for (NSInteger index = 0; index < account.linkedPhoneNumbers.count; index++) + if (BuildSettings.settingsScreenAllowAddingPhoneNumbers) { - [sectionUserSettings addRowWithTag: USER_SETTINGS_PHONENUMBERS_OFFSET + index]; + for (NSInteger index = 0; index < account.linkedPhoneNumbers.count; index++) + { + [sectionUserSettings addRowWithTag: USER_SETTINGS_PHONENUMBERS_OFFSET + index]; + } } if (BuildSettings.settingsScreenAllowAddingEmailThreepids) { @@ -376,7 +435,15 @@ TableViewSectionsDelegate> sectionUserSettings.headerTitle = [VectorL10n settingsUserSettings]; [tmpSections addObject:sectionUserSettings]; - + + if (BuildSettings.bwiShowNewFeatures) + { + Section *sectionNewFeatures = [Section sectionWithTag:SECTION_TAG_NEW_FEATURES]; + [sectionNewFeatures addRowWithTag:SHOW_NEW_FEATURES]; + sectionNewFeatures.headerTitle = NSLocalizedStringFromTable(@"bwi_settings_new_features_header", @"Vector", nil).uppercaseString; + [tmpSections addObject:sectionNewFeatures]; + } + if (BuildSettings.settingsScreenShowConfirmMediaSize) { Section *sectionMedia = [Section sectionWithTag:SECTION_TAG_SENDING_MEDIA]; @@ -386,20 +453,40 @@ TableViewSectionsDelegate> [tmpSections addObject:sectionMedia]; } - Section *sectionLinks = [Section sectionWithTag:SECTION_TAG_LINKS]; - [sectionLinks addRowWithTag:LINKS_SHOW_URL_PREVIEWS_INDEX]; - sectionLinks.headerTitle = [VectorL10n settingsLinks]; - sectionLinks.footerTitle = VectorL10n.settingsShowUrlPreviewsDescription; - [tmpSections addObject:sectionLinks]; + if (BuildSettings.settingsScreenShowLinkPreviews) + { + Section *sectionLinks = [Section sectionWithTag:SECTION_TAG_LINKS]; + [sectionLinks addRowWithTag:LINKS_SHOW_URL_PREVIEWS_INDEX]; + sectionLinks.headerTitle = [VectorL10n settingsLinks]; + sectionLinks.footerTitle = VectorL10n.settingsShowUrlPreviewsDescription; + [tmpSections addObject:sectionLinks]; + } Section *sectionSecurity = [Section sectionWithTag:SECTION_TAG_SECURITY]; [sectionSecurity addRowWithTag:SECURITY_BUTTON_INDEX]; + + + // bwi 2525 - show matomo settings + if ([BuildSettings bwiShowMatomoSettings]) + { + [sectionSecurity addRowWithTag:SECURITY_MATOMO_INDEX]; + } + sectionSecurity.headerTitle = [VectorL10n settingsSecurity]; [tmpSections addObject:sectionSecurity]; Section *sectionNotificationSettings = [Section sectionWithTag:SECTION_TAG_NOTIFICATIONS]; [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX]; - [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_SYSTEM_SETTINGS]; + + if (BuildSettings.featureWorkTime) + { + [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_ENABLE_REST_TIME]; + } + + if (RiotSettings.shared.settingsScreenShowSystemSettingsOption) { + [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_SYSTEM_SETTINGS]; + } + if (RiotSettings.shared.settingsScreenShowNotificationDecodedContentOption) { [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT]; @@ -415,9 +502,15 @@ TableViewSectionsDelegate> [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_PIN_UNREAD_INDEX]; if (@available(iOS 14.0, *)) { - [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_DEFAULT_SETTINGS_INDEX]; - [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_MENTION_AND_KEYWORDS_SETTINGS_INDEX]; - [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_OTHER_SETTINGS_INDEX]; + if (RiotSettings.shared.settingsNotificationsShowDefault) { + [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_DEFAULT_SETTINGS_INDEX]; + } + if (RiotSettings.shared.settingsNotificationsShowMentions) { + [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_MENTION_AND_KEYWORDS_SETTINGS_INDEX]; + } + if (RiotSettings.shared.settingsNotificationsShowAdvanced) { + [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_OTHER_SETTINGS_INDEX]; + } } else { // Don't add new sections on pre iOS 14 } @@ -504,45 +597,64 @@ TableViewSectionsDelegate> Section *sectionUserInterface = [Section sectionWithTag:SECTION_TAG_USER_INTERFACE]; sectionUserInterface.headerTitle = [VectorL10n settingsUserInterface]; - [sectionUserInterface addRowWithTag:USER_INTERFACE_LANGUAGE_INDEX]; + //[sectionUserInterface addRowWithTag:USER_INTERFACE_LANGUAGE_INDEX]; [sectionUserInterface addRowWithTag:USER_INTERFACE_THEME_INDEX]; - - if (BuildSettings.roomScreenAllowTimelineStyleConfiguration) - { - // NOTE: Message bubbles are under labs section atm - -// [sectionUserInterface addRowWithTag:USER_INTERFACE_TIMELINE_STYLE_INDEX]; - } - + sectionUserInterface = [self updateTimeLine:sectionUserInterface]; [tmpSections addObject: sectionUserInterface]; - Section *sectionAdvanced = [Section sectionWithTag:SECTION_TAG_ADVANCED]; - sectionAdvanced.headerTitle = [VectorL10n settingsAdvanced]; - - if (RiotSettings.shared.settingsScreenShowNsfwRoomsOption) - { - [sectionAdvanced addRowWithTag:ADVANCED_SHOW_NSFW_ROOMS_INDEX]; + if (BuildSettings.bwiPersonalNotesRoom) { + [sectionUserInterface addRowWithTag:USER_INTERFACE_PERSONAL_NOTES]; } - if (BuildSettings.settingsScreenAllowChangingCrashUsageDataSettings) + if (BuildSettings.settingsScreenShowAdvancedSettings) { - [sectionAdvanced addRowWithTag:ADVANCED_CRASH_REPORT_INDEX]; + Section *sectionAdvanced = [Section sectionWithTag:SECTION_TAG_ADVANCED]; + sectionAdvanced.headerTitle = [VectorL10n settingsAdvanced]; + + if (RiotSettings.shared.settingsScreenShowNsfwRoomsOption) + { + [sectionAdvanced addRowWithTag:ADVANCED_SHOW_NSFW_ROOMS_INDEX]; + } + + if (BuildSettings.settingsScreenAllowChangingCrashUsageDataSettings) + { + [sectionAdvanced addRowWithTag:ADVANCED_CRASH_REPORT_INDEX]; + } + if (BuildSettings.settingsScreenAllowChangingRageshakeSettings) + { + [sectionAdvanced addRowWithTag:ADVANCED_ENABLE_RAGESHAKE_INDEX]; + } + [sectionAdvanced addRowWithTag:ADVANCED_MARK_ALL_AS_READ_INDEX]; + [sectionAdvanced addRowWithTag:ADVANCED_CLEAR_CACHE_INDEX]; + if (BuildSettings.settingsScreenAllowBugReportingManually) + { + [sectionAdvanced addRowWithTag:ADVANCED_REPORT_BUG_INDEX]; + } + + [tmpSections addObject:sectionAdvanced]; } - if (BuildSettings.settingsScreenAllowChangingRageshakeSettings) - { - [sectionAdvanced addRowWithTag:ADVANCED_ENABLE_RAGESHAKE_INDEX]; - } - [sectionAdvanced addRowWithTag:ADVANCED_MARK_ALL_AS_READ_INDEX]; - [sectionAdvanced addRowWithTag:ADVANCED_CLEAR_CACHE_INDEX]; - if (BuildSettings.settingsScreenAllowBugReportingManually) - { - [sectionAdvanced addRowWithTag:ADVANCED_REPORT_BUG_INDEX]; - } - - [tmpSections addObject:sectionAdvanced]; Section *sectionAbout = [Section sectionWithTag:SECTION_TAG_ABOUT]; - if (BuildSettings.applicationCopyrightUrlString.length) + [sectionAbout addRowWithTag:ABOUT_VERSION_INDEX]; + [sectionAbout addRowWithTag:ABOUT_OLM_VERSION_INDEX]; + + // bwi 2098 - show element base version + if ([BuildSettings showElementBaseVersion]) + { + [sectionAbout addRowWithTag:ABOUT_ELEMENT_VERSION_INDEX]; + } + + if (BuildSettings.settingsScreenShowSupportSetting) + { + [sectionAbout addRowWithTag:ABOUT_SUPPORT_INDEX]; + } + + if (RiotSettings.shared.settingsScreenShowNsfwRoomsOption) + { + [sectionAbout addRowWithTag:ADVANCED_REPORT_BUG_INDEX]; + } + + if (BuildSettings.settingsScreenAllowChangingCrashUsageDataSettings) { [sectionAbout addRowWithTag:ABOUT_COPYRIGHT_INDEX]; } @@ -557,6 +669,16 @@ TableViewSectionsDelegate> [sectionAbout addRowWithTag:ABOUT_THIRD_PARTY_INDEX]; sectionAbout.headerTitle = VectorL10n.settingsAbout; + [sectionAbout addRowWithTag:ABOUT_MARK_ALL_AS_READ_INDEX]; + [sectionAbout addRowWithTag:ABOUT_CLEAR_CACHE_INDEX]; + + if (BuildSettings.bwiShowDeveloperSettings && @available(iOS 14.0, *)) { + Section *sectionDeveloper = [Section sectionWithTag:SECTION_TAG_DEVELOPER]; + [sectionDeveloper addRowWithTag:DEVELOPER_SHOW_SETTINGS_INDEX]; + sectionDeveloper.headerTitle = NSLocalizedStringFromTable(@"bwi_settings_developer", @"Vector", nil).uppercaseString; + [tmpSections addObject:sectionDeveloper]; + } + if (BuildSettings.settingsScreenShowAdvancedSettings) { sectionAbout.footerTitle = [self buildAboutSectionFooterTitleWithAccount:account]; @@ -597,6 +719,10 @@ TableViewSectionsDelegate> sectionDeactivate.headerTitle = [VectorL10n settingsDeactivateMyAccount]; [tmpSections addObject:sectionDeactivate]; } + + Section *sectionSignOut = [Section sectionWithTag:SECTION_TAG_SIGN_OUT]; + [sectionSignOut addRowWithTag:0]; + [tmpSections addObject:sectionSignOut]; // update sections _tableViewSections.sections = tmpSections; @@ -1649,12 +1775,33 @@ TableViewSectionsDelegate> } cell.textLabel.accessibilityIdentifier = nil; cell.textLabel.font = [UIFont systemFontOfSize:17]; + cell.textLabel.backgroundColor = [UIColor clearColor]; cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; cell.contentView.backgroundColor = UIColor.clearColor; return cell; } +- (MXKTableViewCell*)getWarningTableViewCell:(UITableView*)tableView +{ + MXKTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BwiWarningCell"]; + if (!cell) + { + cell = [[MXKTableViewCell alloc] init]; + } + else + { + cell.selectionStyle = UITableViewCellSelectionStyleDefault; + cell.accessoryType = UITableViewCellAccessoryNone; + cell.accessoryView = nil; + } + cell.textLabel.accessibilityIdentifier = nil; + cell.textLabel.font = [UIFont systemFontOfSize:17]; + cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + + return cell; +} + - (MXKTableViewCellWithTextView*)textViewCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath { MXKTableViewCellWithTextView *textViewCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier] forIndexPath:indexPath]; @@ -1707,7 +1854,7 @@ TableViewSectionsDelegate> [signOutCell.mxkButton setTitle:title forState:UIControlStateNormal]; [signOutCell.mxkButton setTitle:title forState:UIControlStateHighlighted]; - [signOutCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + [signOutCell.mxkButton setTintColor:ThemeService.shared.theme.warningColor]; signOutCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; [signOutCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; @@ -1743,9 +1890,14 @@ TableViewSectionsDelegate> // if the user defines a new avatar if (newAvatarImage) - { + { profileCell.mxkImageView.image = newAvatarImage; } + else if (shouldRemoveAvatarImage) + { + UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:myUser.userId withDisplayName:myUser.displayname]; + profileCell.mxkImageView.image = avatarImage; + } else { UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:myUser.userId withDisplayName:myUser.displayname]; @@ -1969,6 +2121,31 @@ TableViewSectionsDelegate> cell = passwordCell; } } + else if (section == SECTION_TAG_NEW_FEATURES) + { + if (row == SHOW_NEW_FEATURES) + { + MXKTableViewCell *showNewFeaturesCell = [self getDefaultTableViewCell:tableView]; + showNewFeaturesCell.textLabel.text = NSLocalizedStringFromTable(@"bwi_settings_new_features_show_features", @"Vector", nil); + [showNewFeaturesCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; + cell = showNewFeaturesCell; + } + } + else if (section == SECTION_TAG_LOCATION_SHARING) + { + if (row == LOCATION_SHARING_ENABLED) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = VectorL10n.locationSharingSettingsToggleTitle; + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.roomScreenAllowLocationAction; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLocationSharing:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + } else if (section == SECTION_TAG_SENDING_MEDIA) { if (row == SENDING_MEDIA_CONFIRM_SIZE) @@ -2032,6 +2209,10 @@ TableViewSectionsDelegate> cell = labelAndSwitchCell; } + else if (row == NOTIFICATION_SETTINGS_ENABLE_REST_TIME) + { + cell = [self cellForWorkTime:tableView indexPath:indexPath]; + } else if (row == NOTIFICATION_SETTINGS_SYSTEM_SETTINGS) { cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; @@ -2170,34 +2351,35 @@ TableViewSectionsDelegate> } else if (section == SECTION_TAG_USER_INTERFACE) { - if (row == USER_INTERFACE_LANGUAGE_INDEX) - { - cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; - if (!cell) - { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; - } - - NSString *language = [NSBundle mxk_language]; - if (!language) - { - language = [MXKLanguagePickerViewController defaultLanguage]; - } - NSString *languageDescription = [MXKLanguagePickerViewController languageDescription:language]; - - // Capitalise the description in the language locale - NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:language]; - languageDescription = [languageDescription capitalizedStringWithLocale:locale]; - - cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - - cell.textLabel.text = [VectorL10n settingsUiLanguage]; - cell.detailTextLabel.text = languageDescription; - - [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; - cell.selectionStyle = UITableViewCellSelectionStyleDefault; - } - else if (row == USER_INTERFACE_THEME_INDEX) +// if (row == USER_INTERFACE_LANGUAGE_INDEX) +// { +// cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; +// if (!cell) +// { +// cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; +// } +// +// NSString *language = [NSBundle mxk_language]; +// if (!language) +// { +// language = [MXKLanguagePickerViewController defaultLanguage]; +// } +// NSString *languageDescription = [MXKLanguagePickerViewController languageDescription:language]; +// +// // Capitalise the description in the language locale +// NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:language]; +// languageDescription = [languageDescription capitalizedStringWithLocale:locale]; +// +// cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; +// +// cell.textLabel.text = NSLocalizedStringFromTable(@"settings_ui_language", @"Vector", nil); +// cell.detailTextLabel.text = languageDescription; +// +// [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; +// cell.selectionStyle = UITableViewCellSelectionStyleDefault; +// } +// else + if (row == USER_INTERFACE_THEME_INDEX) { cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; if (!cell) @@ -2222,10 +2404,13 @@ TableViewSectionsDelegate> [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell.selectionStyle = UITableViewCellSelectionStyleDefault; - } - else if (row == USER_INTERFACE_TIMELINE_STYLE_INDEX) + } else if (row == USER_INTERFACE_PERSONAL_NOTES) { + cell = [self cellForPersonalNotes:tableView indexPath:indexPath]; + } else if (row == USER_INTERFACE_TIMELINE_STYLE_INDEX) { cell = [self buildMessageBubblesCellForTableView:tableView atIndexPath:indexPath]; + } else { + cell = [self cellForTimeline:tableView indexPath:indexPath row:row]; } } else if (section == SECTION_TAG_IGNORED_USERS) @@ -2274,7 +2459,99 @@ TableViewSectionsDelegate> } else if (section == SECTION_TAG_ADVANCED) { - if (row == ADVANCED_SHOW_NSFW_ROOMS_INDEX) + MXKTableViewCellWithTextView *configCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; + + configCell.mxkTextView.text = [NSString stringWithFormat:@"%@\n%@\n%@", [MatrixKitL10n settingsConfigUserId:account.mxCredentials.userId], [MatrixKitL10n settingsConfigHomeServer:account.mxCredentials.homeServer], [MatrixKitL10n settingsConfigIdentityServer:account.identityServerURL]]; + configCell.mxkTextView.accessibilityIdentifier=@"SettingsVCConfigStaticText"; + + cell = configCell; + } + else if (section == SECTION_TAG_ABOUT) + { + if (row == ABOUT_VERSION_INDEX) + { + MXKTableViewCell *versionCell = [self getDefaultTableViewCell:tableView]; + + NSString* appVersion = [AppDelegate theDelegate].appVersion; + NSString* build = [AppDelegate theDelegate].build; + + versionCell.textLabel.text = [VectorL10n settingsVersion:[NSString stringWithFormat:@"%@ %@", appVersion, build]]; + + versionCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = versionCell; + } + else if (row == ABOUT_OLM_VERSION_INDEX) + { + MXKTableViewCell *versionCell = [self getDefaultTableViewCell:tableView]; + + versionCell.textLabel.text = [VectorL10n settingsOlmVersion:[OLMKit versionString]]; + + versionCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = versionCell; + } + else if (row == ABOUT_ELEMENT_VERSION_INDEX) + { + MXKTableViewCell *versionCell = [self getDefaultTableViewCell:tableView]; + + versionCell.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_element_version", @"Vector", nil), [BuildSettings elementBaseVersion]]; + + versionCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = versionCell; + } + else if (row == ABOUT_TERM_CONDITIONS_INDEX) + { + MXKTableViewCell *termAndConditionCell = [self getDefaultTableViewCell:tableView]; + + termAndConditionCell.textLabel.text = [VectorL10n settingsTermConditions]; + + [termAndConditionCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; + + cell = termAndConditionCell; + } + else if (row == ABOUT_SUPPORT_INDEX) + { + MXKTableViewCell *supportCell = [self getDefaultTableViewCell:tableView]; + + supportCell.textLabel.text = NSLocalizedStringFromTable(@"settings_support", @"Vector", nil); + + [supportCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; + + cell = supportCell; + } + else if (row == ABOUT_COPYRIGHT_INDEX) + { + MXKTableViewCell *copyrightCell = [self getDefaultTableViewCell:tableView]; + + copyrightCell.textLabel.text = [VectorL10n settingsCopyright]; + + [copyrightCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; + + cell = copyrightCell; + } + else if (row == ABOUT_PRIVACY_INDEX) + { + MXKTableViewCell *privacyPolicyCell = [self getDefaultTableViewCell:tableView]; + + privacyPolicyCell.textLabel.text = [VectorL10n settingsPrivacyPolicy]; + + [privacyPolicyCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; + + cell = privacyPolicyCell; + } + else if (row == ABOUT_THIRD_PARTY_INDEX) + { + MXKTableViewCell *thirdPartyCell = [self getDefaultTableViewCell:tableView]; + + thirdPartyCell.textLabel.text = [VectorL10n settingsThirdPartyNotices]; + + [thirdPartyCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; + + cell = thirdPartyCell; + } + else if (row == ABOUT_SHOW_NSFW_ROOMS_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; @@ -2311,7 +2588,7 @@ TableViewSectionsDelegate> cell = enableRageShakeCell; } - else if (row == ADVANCED_MARK_ALL_AS_READ_INDEX) + else if (row == ABOUT_MARK_ALL_AS_READ_INDEX) { MXKTableViewCellWithButton *markAllBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!markAllBtnCell) @@ -2336,7 +2613,7 @@ TableViewSectionsDelegate> cell = markAllBtnCell; } - else if (row == ADVANCED_CLEAR_CACHE_INDEX) + else if (row == ABOUT_CLEAR_CACHE_INDEX) { MXKTableViewCellWithButton *clearCacheBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!clearCacheBtnCell) @@ -2489,13 +2766,22 @@ TableViewSectionsDelegate> } else if (section == SECTION_TAG_SECURITY) { - switch (row) - { - case SECURITY_BUTTON_INDEX: - cell = [self getDefaultTableViewCell:tableView]; - cell.textLabel.text = [VectorL10n securitySettingsTitle]; - [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; - break; + if (row == SECURITY_BUTTON_INDEX) { + cell = [self getDefaultTableViewCell:tableView]; + + cell.textLabel.text = [VectorL10n securitySettingsTitle]; + [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; + } + else if (row == SECURITY_MATOMO_INDEX) { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"MATOMO_SETTINGS_SEND_DIAGNOSTIC_DATA", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.bwiEnableMatomoTracking; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableMatomoTracking:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; } } else if (section == SECTION_TAG_DEACTIVATE_ACCOUNT) @@ -2524,6 +2810,51 @@ TableViewSectionsDelegate> cell = deactivateAccountBtnCell; } + else if (section == SECTION_TAG_DOWNTIME_WARNING) + { + if (row == DOWNTIME_WARNING) + { + MXKTableViewCell *downtimeCell = [self getWarningTableViewCell:tableView]; + + downtimeCell.contentView.backgroundColor = [self bwiDowntimeColor]; + + downtimeCell.textLabel.text = [self bwiDowntimeCellText]; + downtimeCell.textLabel.textColor = [self bwiDowntimeTextColor]; + downtimeCell.textLabel.lineBreakMode = NSLineBreakByWordWrapping; + downtimeCell.textLabel.numberOfLines = 5; + + cell = downtimeCell; + } + } + else if (section == SECTION_TAG_DEVELOPER) + { + if (row == DEVELOPER_SHOW_SETTINGS_INDEX) + { + MXKTableViewCellWithButton *showSettingsBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + + if (!showSettingsBtnCell) + { + showSettingsBtnCell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + showSettingsBtnCell.mxkButton.titleLabel.text = nil; + } + + NSString *btnTitle = NSLocalizedStringFromTable(@"bwi_settings_show_developer_settings", @"Vector", nil); + [showSettingsBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [showSettingsBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [showSettingsBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + showSettingsBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [showSettingsBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [showSettingsBtnCell.mxkButton addTarget:self action:@selector(showDeveloperSettings) forControlEvents:UIControlEventTouchUpInside]; + showSettingsBtnCell.mxkButton.accessibilityIdentifier = nil; + + cell = showSettingsBtnCell; + } + } return cell; } @@ -2662,15 +2993,16 @@ TableViewSectionsDelegate> if (section == SECTION_TAG_USER_INTERFACE) { - if (row == USER_INTERFACE_LANGUAGE_INDEX) - { - // Display the language picker - LanguagePickerViewController *languagePickerViewController = [LanguagePickerViewController languagePickerViewController]; - languagePickerViewController.selectedLanguage = [NSBundle mxk_language]; - languagePickerViewController.delegate = self; - [self pushViewController:languagePickerViewController]; - } - else if (row == USER_INTERFACE_THEME_INDEX) +// if (row == USER_INTERFACE_LANGUAGE_INDEX) +// { +// // Display the language picker +// LanguagePickerViewController *languagePickerViewController = [LanguagePickerViewController languagePickerViewController]; +// languagePickerViewController.selectedLanguage = [NSBundle mxk_language]; +// languagePickerViewController.delegate = self; +// [self pushViewController:languagePickerViewController]; +// } +// else + if (row == USER_INTERFACE_THEME_INDEX) { [self showThemePicker]; } @@ -2772,6 +3104,16 @@ TableViewSectionsDelegate> [self pushViewController:webViewViewController]; } + else if (row == ABOUT_SUPPORT_INDEX) + { + NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"support" ofType:@"html" inDirectory:nil]; + + WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithLocalHTMLFile:htmlFile]; + + webViewViewController.title = NSLocalizedStringFromTable(@"settings_support", @"Vector", nil); + + [self pushViewController:webViewViewController]; + } else if (row == ABOUT_PRIVACY_INDEX) { WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithURL:BuildSettings.applicationPrivacyPolicyUrlString]; @@ -2866,6 +3208,16 @@ TableViewSectionsDelegate> } } } + else if (section == SECTION_TAG_NEW_FEATURES) + { + if (row == SHOW_NEW_FEATURES) + { + NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"new_features" ofType:@"html" inDirectory:nil]; + WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithLocalHTMLFile: htmlFile]; + webViewViewController.title = NSLocalizedStringFromTable(@"bwi_settings_new_features_header", @"Vector", nil); + [self pushViewController:webViewViewController]; + } + } [tableView deselectRowAtIndexPath:indexPath animated:YES]; } @@ -2998,11 +3350,22 @@ TableViewSectionsDelegate> } } +- (void)toggleLocationSharing:(UISwitch *)sender +{ + RiotSettings.shared.roomScreenAllowLocationAction = sender.on; +} + - (void)toggleConfirmMediaSize:(UISwitch *)sender { RiotSettings.shared.showMediaCompressionPrompt = sender.on; } +- (void)toggleEnableMatomoTracking:(UISwitch *)sender +{ + RiotSettings.shared.bwiEnableMatomoTracking = sender.on; + BWIAnalytics.sharedTracker.running = RiotSettings.shared.bwiEnableMatomoTracking; +} + - (void)togglePushNotifications:(UISwitch *)sender { // Check first whether the user allow notification from system settings @@ -3340,13 +3703,24 @@ TableViewSectionsDelegate> return; } - if (newAvatarImage) + if (shouldRemoveAvatarImage) { + + self->uploadedAvatarURL = @""; + self->newAvatarImage = nil; + self->shouldRemoveAvatarImage = false; + dispatch_async(dispatch_get_main_queue(), ^{ + [self onSave:nil]; + }); + + return; + } + else if (newAvatarImage) { // Retrieve the current picture and make sure its orientation is up - UIImage *updatedPicture = [MXKTools forceImageOrientationUp:newAvatarImage]; - - // Upload picture MXMediaLoader *uploader = [MXMediaManager prepareUploaderWithMatrixSession:account.mxSession initialRange:0 andRange:1.0]; + + // Upload picture + UIImage *updatedPicture = [MXKTools forceImageOrientationUp:newAvatarImage]; [uploader uploadData:UIImageJPEGRepresentation(updatedPicture, 0.5) filename:nil mimeType:@"image/jpeg" success:^(NSString *url) { @@ -3430,11 +3804,13 @@ TableViewSectionsDelegate> // Alert user NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; + NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; + if (!title) { title = [VectorL10n settingsFailToUpdateProfile]; + msg = [NSBundle mxk_localizedStringForKey:@"settings_fail_to_update_profile_message"]; } - NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; [currentAlert dismissViewControllerAnimated:NO completion:nil]; @@ -3456,6 +3832,7 @@ TableViewSectionsDelegate> // Discard picture change self->uploadedAvatarURL = nil; self->newAvatarImage = nil; + self->shouldRemoveAvatarImage = false; // Loop to end saving [self onSave:nil]; @@ -3721,7 +4098,7 @@ TableViewSectionsDelegate> MXSession* session = self.mainSession; MXMyUser* myUser = session.myUser; - BOOL saveButtonEnabled = (nil != newAvatarImage); + BOOL saveButtonEnabled = (nil != newAvatarImage || shouldRemoveAvatarImage); if (!saveButtonEnabled) { @@ -3739,6 +4116,7 @@ TableViewSectionsDelegate> { SingleImagePickerPresenter *singleImagePickerPresenter = [[SingleImagePickerPresenter alloc] initWithSession:self.mainSession]; singleImagePickerPresenter.delegate = self; + singleImagePickerPresenter.allowsRemoveImage = self.mainSession.myUser.avatarUrl != nil; NSIndexPath *indexPath = [_tableViewSections exactIndexPathForRowTag:USER_SETTINGS_PROFILE_PICTURE_INDEX sectionTag:SECTION_TAG_USER_SETTINGS]; @@ -3876,6 +4254,15 @@ TableViewSectionsDelegate> RiotSettings.shared.showNSFWPublicRooms = sender.isOn; } + +- (void)showDeveloperSettings +{ + if (@available(iOS 14.0, *)) { + UIViewController *developerSettingsViewController = [DeveloperSettingsViewController makeViewControllerWithSession:self.mainSession]; + [self pushViewController:developerSettingsViewController]; + } +} + - (void)toggleEnableRoomMessageBubbles:(UISwitch *)sender { RiotSettings.shared.roomScreenEnableMessageBubbles = sender.isOn; @@ -3967,8 +4354,8 @@ TableViewSectionsDelegate> { __weak typeof(self) weakSelf = self; [resetPwdAlertController dismissViewControllerAnimated:NO completion:nil]; - - resetPwdAlertController = [UIAlertController alertControllerWithTitle:[VectorL10n settingsChangePassword] message:nil preferredStyle:UIAlertControllerStyleAlert]; + + resetPwdAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_change_password", @"Vector", nil) message:NSLocalizedStringFromTable(@"settings_password_condition", @"Vector", nil) preferredStyle:UIAlertControllerStyleAlert]; resetPwdAlertController.accessibilityLabel=@"ChangePasswordAlertController"; savePasswordAction = [UIAlertAction actionWithTitle:[VectorL10n save] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { @@ -4087,7 +4474,7 @@ TableViewSectionsDelegate> // check if the textfields have the right value savePasswordAction.enabled = NO; - UIAlertAction* cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"cancel", @"Vector", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { if (weakSelf) { @@ -4108,6 +4495,16 @@ TableViewSectionsDelegate> self->currentPasswordTextField.placeholder = [VectorL10n settingsOldPassword]; self->currentPasswordTextField.secureTextEntry = YES; [self->currentPasswordTextField addTarget:self action:@selector(passwordTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:textField + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:Nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:40]; + + [textField addConstraint:heightConstraint]; } }]; @@ -4122,6 +4519,16 @@ TableViewSectionsDelegate> self->newPasswordTextField1.placeholder = [VectorL10n settingsNewPassword]; self->newPasswordTextField1.secureTextEntry = YES; [self->newPasswordTextField1 addTarget:self action:@selector(passwordTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:textField + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:Nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:40]; + + [textField addConstraint:heightConstraint]; } }]; @@ -4136,6 +4543,16 @@ TableViewSectionsDelegate> self->newPasswordTextField2.placeholder = [VectorL10n settingsConfirmPassword]; self->newPasswordTextField2.secureTextEntry = YES; [self->newPasswordTextField2 addTarget:self action:@selector(passwordTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:textField + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:Nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:40]; + + [textField addConstraint:heightConstraint]; } }]; @@ -4394,11 +4811,31 @@ TableViewSectionsDelegate> [presenter dismissWithAnimated:YES completion:nil]; self.imagePickerPresenter = nil; - newAvatarImage = [UIImage imageWithData:imageData]; + NSData *finalImageData = imageData ? [Tools mediaConvertMaxImageData:imageData withUTI:uti] : nil; + + if( finalImageData != nil ) { + newAvatarImage = [UIImage imageWithData:finalImageData]; + NSLog(@"Reduced avantar image width = %.0f and height = %.0f", newAvatarImage.size.width, newAvatarImage.size.height); + } else { + newAvatarImage = nil; + NSLog(@"Removed avantar image"); + } [self updateSections]; } +- (void)singleImagePickerPresenterDidRemoveImage:(SingleImagePickerPresenter *)presenter +{ + [presenter dismissWithAnimated:YES completion:nil]; + self.imagePickerPresenter = nil; + + shouldRemoveAvatarImage = true; + NSLog(@"Removed avatar image"); + + [self updateSections]; + [self.settingsDiscoveryTableViewSection reload]; + [self updateSaveButtonStatus]; +} #pragma mark - Identity server updates @@ -4635,4 +5072,210 @@ TableViewSectionsDelegate> [self.tableView reloadData]; } +#pragma mark - Timeline Additions + +- (Section*) updateTimeLine:(Section*)timelineSection { + if (RiotSettings.shared.settingsScreenShowSimpleTimeLineOptions) { + [timelineSection addRowWithTag:TIMELINE_SIMPLE_INDEX]; + } else { + if (RiotSettings.shared.settingsScreenShowTimeStampOption) { + [timelineSection addRowWithTag:TIMELINE_TIMESTAMP_INDEX]; + } + if (RiotSettings.shared.settingsScreenShowDeletedMessagesOption) { + [timelineSection addRowWithTag:TIMELINE_DELETED_MESSAGES_INDEX]; + } + if (RiotSettings.shared.settingsScreenShowNameChangeOption) { + [timelineSection addRowWithTag:TIMELINE_NAME_CHANGE_INDEX]; + } + if (RiotSettings.shared.settingsScreenShowChatEffectsOption) { + [timelineSection addRowWithTag:TIMELINE_CHATEFFECTS_INDEX]; + } + if (RiotSettings.shared.settingsScreenShowRoomAvatarChangeOption) { + [timelineSection addRowWithTag:TIMELINE_ROOM_AVATAR_INDEX]; + } + if (RiotSettings.shared.settingsScreenShowUserAvatarChangeOption) { + [timelineSection addRowWithTag:TIMELINE_USER_AVATAR_INDEX]; + } + if (RiotSettings.shared.settingsScreenShowEnterRoomOption) { + [timelineSection addRowWithTag:TIMELINE_ENTER_ROOM_INDEX]; + } + } + return timelineSection; +} + +- (UITableViewCell*) cellForTimeline:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath row:(NSInteger)row { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = [self titleForTimeline:row]; + labelAndSwitchCell.mxkSwitch.on = [self enabledForTimeLine:row]; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:[self actionForTimeLine:row] forControlEvents:UIControlEventTouchUpInside]; + + return labelAndSwitchCell; +} + +- (NSString*) titleForTimeline:(NSInteger)row { + switch (row) { + case TIMELINE_SIMPLE_INDEX: + return NSLocalizedStringFromTable(@"settings_enable_simple_time_line", @"Vector", nil); + case TIMELINE_TIMESTAMP_INDEX: + return NSLocalizedStringFromTable(@"settings_enable_time_stamp", @"Vector", nil); + case TIMELINE_DELETED_MESSAGES_INDEX: + return NSLocalizedStringFromTable(@"settings_enable_deleted_messages", @"Vector", nil); + case TIMELINE_NAME_CHANGE_INDEX: + return NSLocalizedStringFromTable(@"settings_enable_name_change", @"Vector", nil); + case TIMELINE_CHATEFFECTS_INDEX: + return NSLocalizedStringFromTable(@"settings_enable_chat_effects", @"Vector", nil); + case TIMELINE_ROOM_AVATAR_INDEX: + return NSLocalizedStringFromTable(@"settings_enable_room_avatar", @"Vector", nil); + case TIMELINE_USER_AVATAR_INDEX: + return NSLocalizedStringFromTable(@"settings_enable_user_avatar", @"Vector", nil); + case TIMELINE_ENTER_ROOM_INDEX: + return NSLocalizedStringFromTable(@"settings_enable_enter_room", @"Vector", nil); + default: + return @""; + } +} + +- (BOOL) enabledForTimeLine:(NSInteger)row { + switch (row) { + case TIMELINE_SIMPLE_INDEX: + return RiotSettings.shared.settingSimpleTimelineEnabled; + case TIMELINE_TIMESTAMP_INDEX: + return RiotSettings.shared.settingTimeStampEnabled; + case TIMELINE_DELETED_MESSAGES_INDEX: + return RiotSettings.shared.settingDeletedMessagesEnabled; + case TIMELINE_NAME_CHANGE_INDEX: + return RiotSettings.shared.settingNameChangeEnabled; + case TIMELINE_CHATEFFECTS_INDEX: + return RiotSettings.shared.settingChatEffectsEnabled; + case TIMELINE_ROOM_AVATAR_INDEX: + return RiotSettings.shared.settingRoomAvatarChangeEnabled; + case TIMELINE_USER_AVATAR_INDEX: + return RiotSettings.shared.settingUserAvatarChangeEnabled; + case TIMELINE_ENTER_ROOM_INDEX: + return RiotSettings.shared.settingEnterRoomEnabled; + default: + return NO; + } +} + +- (SEL) actionForTimeLine:(NSInteger)row { + switch (row) { + case TIMELINE_SIMPLE_INDEX: + return @selector(toggleTimeLineSimpleIndex:); + case TIMELINE_TIMESTAMP_INDEX: + return @selector(toggleTimeLineTimeStamp:); + case TIMELINE_DELETED_MESSAGES_INDEX: + return @selector(toggleTimeLineDeletedMessages:); + case TIMELINE_NAME_CHANGE_INDEX: + return @selector(toggleTimeLineNameChange:); + case TIMELINE_CHATEFFECTS_INDEX: + return @selector(toggleTimeLineChatEffects:); + case TIMELINE_ROOM_AVATAR_INDEX: + return @selector(toggleTimeLineRoomAvatar:); + case TIMELINE_USER_AVATAR_INDEX: + return @selector(toggleTimeLineUserAvatar:); + case TIMELINE_ENTER_ROOM_INDEX: + return @selector(toggleTimeLineEnterRoom:); + default: + return nil; + } +} + +- (void)toggleTimeLineSimpleIndex:(UISwitch*)theSwitch +{ + RiotSettings.shared.settingSimpleTimelineEnabled = theSwitch.isOn; +} + +- (void)toggleTimeLineTimeStamp:(UISwitch*)theSwitch +{ + RiotSettings.shared.settingTimeStampEnabled = theSwitch.isOn; +} + +- (void)toggleTimeLineDeletedMessages:(UISwitch*)theSwitch +{ + RiotSettings.shared.settingDeletedMessagesEnabled = theSwitch.isOn; +} + +- (void)toggleTimeLineNameChange:(UISwitch*)theSwitch +{ + RiotSettings.shared.settingNameChangeEnabled = theSwitch.isOn; +} + +- (void)toggleTimeLineChatEffects:(UISwitch*)theSwitch +{ + RiotSettings.shared.settingChatEffectsEnabled = theSwitch.isOn; +} + +- (void)toggleTimeLineRoomAvatar:(UISwitch*)theSwitch +{ + RiotSettings.shared.settingRoomAvatarChangeEnabled = theSwitch.isOn; +} + +- (void)toggleTimeLineUserAvatar:(UISwitch*)theSwitch +{ + RiotSettings.shared.settingUserAvatarChangeEnabled = theSwitch.isOn; +} + +- (void)toggleTimeLineEnterRoom:(UISwitch*)theSwitch +{ + RiotSettings.shared.settingEnterRoomEnabled = theSwitch.isOn; +} + +#pragma mark - bwi Messenger Additions + +- (UITableViewCell*) cellForWorkTime:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + WorkTimeService *service = [WorkTimeService workTimeService:self.mainSession.myUser.userId]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_enable_rest_time", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = [service isWorkTimeGlobalyEnabled]; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleRestService:) forControlEvents:UIControlEventTouchUpInside]; + + return labelAndSwitchCell; +} + +- (void)toggleRestService:(UISwitch*)theSwitch +{ + WorkTimeService *service = [WorkTimeService workTimeService:self.mainSession.myUser.userId]; + [service enableWorkTime:theSwitch.isOn]; +} + +- (UITableViewCell*) cellForPersonalNotes:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + PersonalNotesSettings *settings = [[PersonalNotesSettings alloc] init]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"bwi_settings_enable_notes_room_title", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = settings.personalNotesVisible; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePersonalNotesService:) forControlEvents:UIControlEventTouchUpInside]; + + return labelAndSwitchCell; +} + +- (void)togglePersonalNotesService:(UISwitch*)theSwitch +{ + PersonalNotesSettings *settings = [[PersonalNotesSettings alloc] init]; + settings.personalNotesVisible = theSwitch.isOn; +} + +- (NSString*) bwiDowntimeCellText { + return [[[ServerDowntimeDefaultService alloc] init] downtimeText]; +} + +- (UIColor*) bwiDowntimeColor { + return [[[ServerDowntimeDefaultService alloc] init] downtimeColor]; +} + +- (UIColor*) bwiDowntimeTextColor { + return [[[ServerDowntimeDefaultService alloc] init] downtimeTextColor]; +} + @end diff --git a/Riot/Modules/SideMenu/SideMenuViewModel.swift b/Riot/Modules/SideMenu/SideMenuViewModel.swift index 7c9d128ee..4bf41fff1 100644 --- a/Riot/Modules/SideMenu/SideMenuViewModel.swift +++ b/Riot/Modules/SideMenu/SideMenuViewModel.swift @@ -2,6 +2,7 @@ // $ createScreen.sh SideMenu SideMenu /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -99,6 +100,18 @@ final class SideMenuViewModel: SideMenuViewModelType { var sideMenuItems: [SideMenuItem] = [] + if RiotSettings.shared.settingsScreenShowInviteFriends { + sideMenuItems.append(.inviteFriends) + } + if RiotSettings.shared.settingsScreenShowSettings { + sideMenuItems.append(.settings) + } + if RiotSettings.shared.settingsScreenShowHelp { + sideMenuItems.append(.help) + } + if RiotSettings.shared.settingsScreenShowFeedback { + sideMenuItems.append(.feedback) + } if BuildSettings.sideMenuShowInviteFriends { sideMenuItems += [.inviteFriends] } diff --git a/Riot/Modules/SplitView/SplitViewCoordinator.swift b/Riot/Modules/SplitView/SplitViewCoordinator.swift index 442da486e..4f76e6e45 100644 --- a/Riot/Modules/SplitView/SplitViewCoordinator.swift +++ b/Riot/Modules/SplitView/SplitViewCoordinator.swift @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -144,6 +145,10 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType { self.tabBarCoordinator?.popToHome(animated: animated, completion: completion) } + func pinUnlocked() { + self.tabBarCoordinator?.pinUnlocked() + } + // MARK: - Private methods private func createPlaceholderDetailsViewController() -> UIViewController { diff --git a/Riot/Modules/SplitView/SplitViewCoordinatorType.swift b/Riot/Modules/SplitView/SplitViewCoordinatorType.swift index 4fd2cfef6..aa5f920ec 100644 --- a/Riot/Modules/SplitView/SplitViewCoordinatorType.swift +++ b/Riot/Modules/SplitView/SplitViewCoordinatorType.swift @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -36,4 +37,6 @@ protocol SplitViewCoordinatorType: Coordinator, Presentable { // TODO: Do not expose publicly this method /// Remove detail screens and display placeholder if needed func resetDetails(animated: Bool) + + func pinUnlocked() } diff --git a/Riot/Modules/StartChat/StartChatViewController.m b/Riot/Modules/StartChat/StartChatViewController.m index 4f2d71dc1..ce976f76d 100644 --- a/Riot/Modules/StartChat/StartChatViewController.m +++ b/Riot/Modules/StartChat/StartChatViewController.m @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -112,7 +113,7 @@ cancelBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(onButtonPressed:)]; self.navigationItem.leftBarButtonItem = cancelBarButtonItem; - createBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:[VectorL10n start] style:UIBarButtonItemStylePlain target:self action:@selector(onButtonPressed:)]; + createBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:[VectorL10n create] style:UIBarButtonItemStylePlain target:self action:@selector(onButtonPressed:)]; self.navigationItem.rightBarButtonItem = createBarButtonItem; _searchBarView.placeholder = [VectorL10n roomCreationInviteAnotherUser]; @@ -145,6 +146,10 @@ - (void)showInviteFriendsHeaderView:(BOOL)show { + if (!BuildSettings.directChatShowInviteFriends) + { + show = NO; + } if (show) { if (!self.inviteFriendsHeaderView) diff --git a/Riot/Modules/TabBar/MasterTabBarController.h b/Riot/Modules/TabBar/MasterTabBarController.h index 6734a1528..0804e0a79 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.h +++ b/Riot/Modules/TabBar/MasterTabBarController.h @@ -1,6 +1,7 @@ /* Copyright 2017 Vector Creations Ltd Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,10 +25,14 @@ #import "RoomsViewController.h" #import "GroupsViewController.h" -#define TABBAR_HOME_INDEX 0 -#define TABBAR_FAVOURITES_INDEX 1 -#define TABBAR_PEOPLE_INDEX 2 -#define TABBAR_ROOMS_INDEX 3 +#import "RoomViewController.h" +#import "ContactDetailsViewController.h" +#import "GroupDetailsViewController.h" + +#define TABBAR_FAVOURITES_INDEX 0 +#define TABBAR_PEOPLE_INDEX 1 +#define TABBAR_ROOMS_INDEX 2 +#define TABBAR_HOME_INDEX 3 #define TABBAR_GROUPS_INDEX 4 #define TABBAR_COUNT 5 @@ -128,6 +133,13 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) { */ - (void)refreshTabBarBadges; +/** + Present the 4S setup flow. + + @param session the matrix session. + */ +- (void)presentSecureBackupSetupForMatrixSession:(MXSession*)session; + /** Verify the current device if needed. @@ -150,9 +162,15 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) { - (void)filterRoomsWithParentId:(NSString*)roomParentId inMatrixSession:(MXSession*)mxSession; + +- (void)bwiOnUnlockedByPin; + // Reference to the current onboarding flow. It is always nil unless the flow is being presented. @property (nonatomic, readonly) OnboardingCoordinatorBridgePresenter *onboardingCoordinatorBridgePresenter; +// Reference to the current splash VC. It is not nil only when the splash screen is displayed. +@property (nonatomic, readonly) UIViewController *splashViewController; + @property (nonatomic, readonly) HomeViewController *homeViewController; @property (nonatomic, readonly) FavouritesViewController *favouritesViewController; @property (nonatomic, readonly) PeopleViewController *peopleViewController; diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index 9b01d88d3..47c9bee84 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -1,6 +1,7 @@ /* Copyright 2017 Vector Creations Ltd Copyright 2018 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -78,6 +79,8 @@ */ @property(nonatomic) BOOL presentAnalyticsPromptOnAddSession; +@property (nonatomic, strong) SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter; + @end @implementation MasterTabBarController @@ -124,6 +127,19 @@ // Note: UITabBarViewController shoud not be embed in a UINavigationController (https://github.com/vector-im/riot-ios/issues/3086) [self vc_removeBackTitle]; + /** + * Remove unused ViewController Home & Groups from TabBarViewController array. + * In a later release it will be may be deactivated again and this code + * can be removed. + * + * Furthermore the code set the select viewcontroller to people. + */ + NSMutableArray *tbViewControllers = [NSMutableArray arrayWithArray:[self viewControllers]]; + [tbViewControllers removeLastObject]; // remove GroupsViewController from tabbar + [tbViewControllers removeLastObject]; // remove HomeViewController from tabbar + self.viewControllers = tbViewControllers; + self.selectedIndex = TABBAR_PEOPLE_INDEX; + [self setupTitleView]; titleView.titleLabel.text = [VectorL10n titleHome]; @@ -183,6 +199,20 @@ MXLogDebug(@"[MasterTabBarController] viewDidAppear"); [super viewDidAppear:animated]; + if (BuildSettings.bwiShowMatomoInfoScreen) { + NSString *matomoInfoScreenShownKey = @"bwi_matomo_info_screen_shown"; + if (![[NSUserDefaults standardUserDefaults] boolForKey: matomoInfoScreenShownKey]) { + [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey: matomoInfoScreenShownKey]; + [self bwiShowMatomoInfoScreen]; + } + } + + /** + * Display the splash screen only one time although + * the viewDidAppear is invoked multiple time in the + * startup workflow. + */ + // Check whether we're not logged in BOOL authIsShown = NO; if (![MXKAccountManager sharedManager].accounts.count) @@ -245,6 +275,9 @@ [childViewControllers removeAllObjects]; } + if ([[AppDelegate theDelegate] checkJailbreak]) { + [[AppDelegate theDelegate] checkIntegrity]; + } [[AppDelegate theDelegate] checkAppVersion]; } } @@ -280,6 +313,7 @@ [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; kThemeServiceDidChangeThemeNotificationObserver = nil; } + if (spaceNotificationCounterDidUpdateNotificationCountObserver) { @@ -364,15 +398,13 @@ [self.roomsViewController displayList:recentsDataSource]; // Restore the right delegate of the shared recent data source. - id recentsDataSourceDelegate = self.homeViewController; - RecentsDataSourceMode recentsDataSourceMode = RecentsDataSourceModeHome; - + id recentsDataSourceDelegate = self.peopleViewController; + RecentsDataSourceMode recentsDataSourceMode = RecentsDataSourceModePeople; + NSInteger tabItemTag = self.tabBar.items[self.selectedIndex].tag; switch (tabItemTag) { - case TABBAR_HOME_INDEX: - break; case TABBAR_FAVOURITES_INDEX: recentsDataSourceDelegate = self.favouritesViewController; recentsDataSourceMode = RecentsDataSourceModeFavourites; @@ -385,7 +417,10 @@ recentsDataSourceDelegate = self.roomsViewController; recentsDataSourceMode = RecentsDataSourceModeRooms; break; - + case TABBAR_HOME_INDEX: + recentsDataSourceDelegate = self.homeViewController; + recentsDataSourceMode = RecentsDataSourceModeHome; + break; default: break; } @@ -409,6 +444,8 @@ } } } + + [self bwiCheckForPersonalNotesRoom]; } } @@ -427,7 +464,7 @@ } // Check whether the controller'€™s view is loaded into memory. - if (self.homeViewController) + if (self.viewControllers) { // Check whether the data sources have been initialized. if (!recentsDataSource) @@ -974,6 +1011,14 @@ } self.reviewSessionAlertHasBeenDisplayed = YES; + + // Force verification if required by the HS configuration + if (session.homeserverWellknown.backupRequired) + { + [[AppDelegate theDelegate] presentCompleteSecurityForSession:session]; + return; + } + [self presentVerifyCurrentSessionAlertWithSession:session]; } @@ -1052,16 +1097,18 @@ [self showSettingsSecurityScreenForSession:session]; }]]; - [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n later] - style:UIAlertActionStyleCancel - handler:nil]]; - - [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n doNotAskAgain] - style:UIAlertActionStyleDestructive - handler:^(UIAlertAction * action) { - RiotSettings.shared.hideReviewSessionsAlert = YES; - }]]; - + if (!session.homeserverWellknown.backupRequired) + { + [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n later] + style:UIAlertActionStyleCancel + handler:nil]]; + + [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n doNotAskAgain] + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction * action) { + RiotSettings.shared.hideReviewSessionsAlert = YES; + }]]; + } [self presentViewController:alert animated:YES completion:nil]; @@ -1116,4 +1163,78 @@ titleView.titleLabel.text = viewController.accessibilityLabel; } +#pragma mark - Hardened - + +#pragma mark - Mandatory SSSS + +- (void)presentSecureBackupSetupForMatrixSession:(MXSession*)session +{ + if (self.secureBackupSetupCoordinatorBridgePresenter) + { + return; + } + + SecureBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:session allowOverwrite:false]; + keyBackupSetupCoordinatorBridgePresenter.delegate = self; + + [keyBackupSetupCoordinatorBridgePresenter presentFrom:self animated:NO]; + + self.secureBackupSetupCoordinatorBridgePresenter = keyBackupSetupCoordinatorBridgePresenter; +} + +#pragma mark - SecureBackupSetupCoordinatorBridgePresenterDelegate + +- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.secureBackupSetupCoordinatorBridgePresenter = nil; +} + +- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.secureBackupSetupCoordinatorBridgePresenter = nil; +} + +#pragma mark - bwi sync management + +- (void) bwiOnUnlockedByPin { + NSInteger tabItemTag = self.tabBar.items[self.selectedIndex].tag; + + switch (tabItemTag) + { + case TABBAR_FAVOURITES_INDEX: + [self.favouritesViewController setupTopBanner]; + break; + case TABBAR_PEOPLE_INDEX: + [self.peopleViewController setupTopBanner]; + break; + case TABBAR_ROOMS_INDEX: + [self.roomsViewController setupTopBanner]; + break; + default: + break; + } +} + +- (void) bwiCheckForPersonalNotesRoom { + PersonalNotesDefaultService *service = [PersonalNotesDefaultService service:self.mxSessions.firstObject]; + if (BuildSettings.bwiResetPersonalNotesAccountData) { + [service resetPersonalNotesRoom]; + } + if (BuildSettings.bwiPersonalNotesRoom) { + [service createPersonalNotesRoomIfNeeded]; + } +} + +#pragma mark - bwi matomo tracking + +- (void) bwiShowMatomoInfoScreen { + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil + message:NSLocalizedStringFromTable(@"MATOMO_USER_INFO", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:NSLocalizedStringFromTable(@"MATOMO_USER_INFO_BUTTON_OK", @"Vector", nil)] style:UIAlertActionStyleDefault handler:nil]]; + [self presentViewController:alert animated:YES completion:nil]; +} + @end diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index c0454e86c..adc0bd253 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -2,6 +2,7 @@ // $ createRootCoordinator.sh TabBar TabBar /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -177,6 +178,22 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { } } + func pinUnlocked() { + let serverDownTime = ServerDowntimeDefaultService() + + if BwiBuildSettings.useTestDataForDowntime { + serverDownTime.fetchDowntimes { + self.createSettingsButton(self.masterTabBarController) + } + } else { + if let session = self.currentMatrixSession { + serverDownTime.fetchDowntimes(session: session, completion: { + self.createSettingsButton(self.masterTabBarController) + }) + } + } + } + // MARK: - SplitViewMasterPresentable var selectedNavigationRouter: NavigationRouterType? { @@ -196,12 +213,8 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { tabBarController.navigationItem.leftBarButtonItem = sideMenuBarButtonItem } else { - let settingsBarButtonItem: MXKBarButtonItem = MXKBarButtonItem(image: Asset.Images.settingsIcon.image, style: .plain) { [weak self] in - self?.showSettings() - } - settingsBarButtonItem.accessibilityLabel = VectorL10n.settingsTitle - tabBarController.navigationItem.leftBarButtonItem = settingsBarButtonItem + self.createSettingsButton(tabBarController) } let searchBarButtonItem: MXKBarButtonItem = MXKBarButtonItem(image: Asset.Images.searchIcon.image, style: .plain) { [weak self] in @@ -209,7 +222,36 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { } searchBarButtonItem.accessibilityLabel = VectorL10n.searchDefaultPlaceholder - tabBarController.navigationItem.rightBarButtonItem = searchBarButtonItem + tabBarController.navigationItem.rightBarButtonItem = searchBarButtonItem + + var viewControllers: [UIViewController] = [] + + if RiotSettings.shared.homeScreenShowHomeTab { + let homeViewController = self.createHomeViewController() + viewControllers.append(homeViewController) + } + + if RiotSettings.shared.homeScreenShowFavouritesTab { + let favouritesViewController = self.createFavouritesViewController() + viewControllers.append(favouritesViewController) + } + + if RiotSettings.shared.homeScreenShowPeopleTab { + let peopleViewController = self.createPeopleViewController() + viewControllers.append(peopleViewController) + } + + if RiotSettings.shared.homeScreenShowRoomsTab { + let roomsViewController = self.createRoomsViewController() + viewControllers.append(roomsViewController) + } + + if RiotSettings.shared.homeScreenShowCommunitiesTab { + let groupsViewController = self.createGroupsViewController() + viewControllers.append(groupsViewController) + } + + tabBarController.updateViewControllers(viewControllers) return tabBarController } @@ -293,11 +335,13 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { // TODO: Avoid to reinstantiate controllers everytime private func updateTabControllers(for tabBarController: MasterTabBarController, showCommunities: Bool) { var viewControllers: [UIViewController] = [] - + let homeViewController = self.createHomeViewController() - viewControllers.append(homeViewController) - + if RiotSettings.shared.homeScreenShowHomeTab { + viewControllers.append(homeViewController) + } + if let existingVersionCheckCoordinator = self.versionCheckCoordinator { self.remove(childCoordinator: existingVersionCheckCoordinator) } @@ -310,7 +354,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { self.versionCheckCoordinator = versionCheckCoordinator } - + if RiotSettings.shared.homeScreenShowFavouritesTab { let favouritesViewController = self.createFavouritesViewController() viewControllers.append(favouritesViewController) @@ -345,7 +389,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { } // FIXME: Should be displayed per tab. - private func showSettings() { + @objc private func showSettings() { let viewController = self.createSettingsViewController() self.navigationRouter.push(viewController, animated: true, popCompletion: nil) @@ -642,6 +686,22 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { self.masterTabBarController.removeTab(at: .groups) } } + + private func createSettingsButton(_ controller: MasterTabBarController) { + var settingsBarButtonItem: UIBarButtonItem + + let serverDownTime = ServerDowntimeDefaultService() + if serverDownTime.isDowntimePresentable() { + settingsBarButtonItem = ServerDowntimeBadge().createBadgeBarButton(color: serverDownTime.downtimeColor(), target: self, selector: #selector(showSettings)) + } else { + settingsBarButtonItem = MXKBarButtonItem(image: Asset.Images.settingsIcon.image, style: .plain) { [weak self] in + self?.showSettings() + } + } + + settingsBarButtonItem.accessibilityLabel = VectorL10n.settingsTitle + controller.navigationItem.leftBarButtonItem = settingsBarButtonItem + } } // MARK: - MasterTabBarControllerDelegate @@ -672,18 +732,22 @@ extension TabBarCoordinator: MasterTabBarControllerDelegate { } func masterTabBarController(_ masterTabBarController: MasterTabBarController!, needsSideMenuIconWithNotification displayNotification: Bool) { - let image = displayNotification ? Asset.Images.sideMenuNotifIcon.image : Asset.Images.sideMenuIcon.image - let sideMenuBarButtonItem: MXKBarButtonItem = MXKBarButtonItem(image: image, style: .plain) { [weak self] in - self?.showSideMenu() + if BuildSettings.enableSideMenu { + let image = displayNotification ? Asset.Images.sideMenuNotifIcon.image : Asset.Images.sideMenuIcon.image + let sideMenuBarButtonItem: MXKBarButtonItem = MXKBarButtonItem(image: image, style: .plain) { [weak self] in + self?.showSideMenu() + } + sideMenuBarButtonItem.accessibilityLabel = VectorL10n.sideMenuRevealActionAccessibilityLabel + + self.masterTabBarController.navigationItem.leftBarButtonItem = sideMenuBarButtonItem } - sideMenuBarButtonItem.accessibilityLabel = VectorL10n.sideMenuRevealActionAccessibilityLabel - - self.masterTabBarController.navigationItem.leftBarButtonItem = sideMenuBarButtonItem } func masterTabBarController(_ masterTabBarController: MasterTabBarController!, shouldPresentAnalyticsPromptForMatrixSession matrixSession: MXSession!) { if #available(iOS 14.0, *) { - presentAnalyticsPrompt(with: matrixSession) + if BuildSettings.bwiPresentAnalyticsPrompt { + presentAnalyticsPrompt(with: matrixSession) + } } } } diff --git a/Riot/Modules/TabBar/TabBarCoordinatorType.swift b/Riot/Modules/TabBar/TabBarCoordinatorType.swift index 8d916606a..4313baadf 100644 --- a/Riot/Modules/TabBar/TabBarCoordinatorType.swift +++ b/Riot/Modules/TabBar/TabBarCoordinatorType.swift @@ -2,6 +2,7 @@ // $ createRootCoordinator.sh TabBar TabBar /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -37,4 +38,6 @@ protocol TabBarCoordinatorType: Coordinator, SplitViewMasterPresentable { // TODO: Remove this method, this implementation detail should not be exposed // Release the current selected item (room/contact/group...). func releaseSelectedItems() + + func pinUnlocked() } diff --git a/Riot/SupportingFiles/Info.plist b/Riot/SupportingFiles/Info.plist index 9d95fe929..a91f568dc 100644 --- a/Riot/SupportingFiles/Info.plist +++ b/Riot/SupportingFiles/Info.plist @@ -36,9 +36,9 @@ CFBundleVersion $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption - + ITSEncryptionExportComplianceCode - d1dd539c-d21c-43e2-92e2-212c5269565c + LSApplicationQueriesSchemes http @@ -66,12 +66,10 @@ NSSiriUsageDescription Siri is used to perform calls even from the lock screen. NSLocationWhenInUseUsageDescription - When you share your location to people, Element needs access to show them a map. + When you share your location to people, the messenger needs access to show them a map. UIBackgroundModes - audio remote-notification - voip UILaunchStoryboardName LaunchScreen diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index 4b517a310..a966e2b5b 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -51,6 +51,8 @@ #import "BubbleRoomTimelineCellProvider.h" #import "RoomSelectedStickerBubbleCell.h" +#import "MXRoom+sendRoomPowerLevels.h" + // MatrixKit common imports, shared with all targets #import "MatrixKit-Bridging-Header.h" diff --git a/Riot/SupportingFiles/Riot.entitlements b/Riot/SupportingFiles/Riot.entitlements index 965a767d4..b7f9c6920 100644 --- a/Riot/SupportingFiles/Riot.entitlements +++ b/Riot/SupportingFiles/Riot.entitlements @@ -5,35 +5,7 @@ aps-environment development com.apple.developer.associated-domains - - applinks:www.vector.im - applinks:vector.im - applinks:riot.im - applinks:www.riot.im - webcredentials:riot.im - applinks:element.io - applinks:app.element.io - applinks:staging.element.io - applinks:develop.element.io - applinks:mobile.element.io - webcredentials:*.element.io - - com.apple.developer.icloud-container-identifiers - - iCloud.$(CFBundleIdentifier) - - com.apple.developer.icloud-services - - CloudDocuments - - com.apple.developer.siri - - com.apple.developer.ubiquity-container-identifiers - - iCloud.$(CFBundleIdentifier) - - com.apple.developer.ubiquity-kvstore-identifier - $(TeamIdentifierPrefix)$(CFBundleIdentifier) + com.apple.security.application-groups $(APPLICATION_GROUP_IDENTIFIER) diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index efb79d40e..748b4ba8a 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -1,6 +1,7 @@ /* Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -517,61 +518,23 @@ static NSString *const kEventFormatterTimeFormat = @"HH:mm"; return nil; } - // Retrieve today date at midnight - NSDate *today = [calendar startOfDayForDate:[NSDate date]]; + // invalid date + if ([date timeIntervalSince1970] == 0) + { + return @""; + } - NSTimeInterval interval = -[date timeIntervalSinceDate:today]; - - if (interval > 60*60*24*364) + if ([[NSCalendar currentCalendar] isDateInToday:date]) { - [dateFormatter setDateFormat:@"MMM dd yyyy"]; - - // Ignore time information here - return [super dateStringFromDate:date withTime:NO]; - } - else if (interval > 60*60*24*6) - { - [dateFormatter setDateFormat:@"MMM dd"]; - - // Ignore time information here - return [super dateStringFromDate:date withTime:NO]; - } - else if (interval > 60*60*24) - { - if (time) - { - [dateFormatter setDateFormat:@"EEE"]; - } - else - { - [dateFormatter setDateFormat:@"EEEE"]; - } - - return [super dateStringFromDate:date withTime:time]; - } - else if (interval > 0) - { - if (time) - { - [dateFormatter setDateFormat:nil]; - return [NSString stringWithFormat:@"%@ %@", [VectorL10n yesterday], [super dateStringFromDate:date withTime:YES]]; - } - return [VectorL10n yesterday]; - } - else if (interval > - 60*60*24) - { - if (time) - { - [dateFormatter setDateFormat:nil]; - return [NSString stringWithFormat:@"%@", [super dateStringFromDate:date withTime:YES]]; - } - return [VectorL10n today]; + return NSLocalizedStringFromTable(@"today", @"Vector", nil); } else { - // Date in future - [dateFormatter setDateFormat:@"EEE MMM dd yyyy"]; - return [super dateStringFromDate:date withTime:time]; + if ([[[NSLocale currentLocale] languageCode] isEqual:@"de"]) + [dateFormatter setDateFormat:@"d. MMMM y"]; + else + [dateFormatter setDateFormat:@"MMMM d y"]; + return [super dateStringFromDate:date withTime:FALSE]; } } diff --git a/Riot/Utils/Tools.h b/Riot/Utils/Tools.h index 2d411a716..96286ecca 100644 --- a/Riot/Utils/Tools.h +++ b/Riot/Utils/Tools.h @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,6 +18,8 @@ #import #import +@class MXKUTI; + @interface Tools : NSObject /** @@ -59,4 +62,15 @@ */ + (NSAttributedString *)setTextColorAlpha:(CGFloat)alpha inAttributedString:(NSAttributedString*)attributedString; +#pragma mark - NSData utilities + +/** + Reduze image size to max 1600x1600px + + @param input image data. + @param universal type identifier. + @return reduzed image data or the original data from image data param. + */ ++ (NSData*)mediaConvertMaxImageData:(NSData*) imageData withUTI:(MXKUTI *)uti; + @end diff --git a/Riot/Utils/Tools.m b/Riot/Utils/Tools.m index f27ab4c33..f5cde39c4 100644 --- a/Riot/Utils/Tools.m +++ b/Riot/Utils/Tools.m @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,7 +16,10 @@ */ #import "Tools.h" +#import "GeneratedInterface-Swift.h" +#import "MXKSwiftHeader.h" +#define MAX_BWI_IMAGE_SIZE 1600 #import "GeneratedInterface-Swift.h" @implementation Tools @@ -138,4 +142,37 @@ return string; } +#pragma mark - NSData utilities + ++ (NSData*)mediaConvertMaxImageData:(NSData*) imageData withUTI:(MXKUTI *)uti +{ + NSData *convertedData; + + /** + * Reduce the image size to MAX_BWI_IMAGE_SIZE to be conform + * with the BWI back-end server system. + */ + CGSize newImageSize = CGSizeMake(MAX_BWI_IMAGE_SIZE, MAX_BWI_IMAGE_SIZE); + UIImage* convertedImage = [MXKTools resizeImageWithData:imageData toFitInSize:newImageSize]; + + if (convertedImage) + { + if ([uti.mimeType isEqualToString:@"image/png"]) + { + convertedData = UIImagePNGRepresentation(convertedImage); + } + else if ([uti.mimeType isEqualToString:@"image/jpeg"]) + { + convertedData = UIImageJPEGRepresentation(convertedImage, 0.9); + } + else + { + // Return the original image data pointer + return imageData; + } + } + + return convertedData; +} + @end diff --git a/Riot/target-messenger.yml b/Riot/target-messenger.yml new file mode 100644 index 000000000..bab9ce906 --- /dev/null +++ b/Riot/target-messenger.yml @@ -0,0 +1,151 @@ +name: BundesMessenger + +schemes: + BundesMessenger: + analyze: + config: Debug + archive: + config: Release + build: + targets: + BundesMessenger: + - running + - testing + - profiling + - analyzing + - archiving + profile: + config: Release + run: + config: Debug + disableMainThreadChecker: true + test: + config: Debug + disableMainThreadChecker: true + gatherCoverageData: true + language: "de" + region: "DE" + environmentVariables: + username: + defaultpin: + defaultpassphrase: + defaultpassword: + targets: + - RiotTests + +targets: + BundesMessenger: + type: application + platform: iOS + + dependencies: + - target: RiotShareExtension + - target: RiotNSE + - target: DesignKit + + configFiles: + Debug: Debug.xcconfig + Release: Release.xcconfig + + preBuildScripts: + - name: ⚠️ SwiftLint + runOnlyWhenInstalling: false + shell: /bin/sh + script: "${PODS_ROOT}/SwiftLint/swiftlint\n" + - name: πŸ›  SwiftGen + runOnlyWhenInstalling: false + shell: /bin/sh + script: "${PODS_ROOT}/SwiftGen/bin/swiftgen config run --config Tools/SwiftGen/swiftgen-config.yml\n" + + sources: + - path: ../RiotSwiftUI/Modules + # Riot will provide it's own LocaleProviderType so exclude. + # Riot will provide it's own LocaleProviderType so exclude. + excludes: + - "Common/Locale/LocaleProvider.swift" + - "**/Test/**" + - "Room/LocationSharing/*" + - path: ../Tools + excludes: + - "Logs" + - "Release" + - "Templates/*.sh" + - path: ../Config + - path: . + excludes: + - "Modules/Room/EmojiPicker/Data/EmojiMart/EmojiJSONStore.swift" + - "Modules/Analytics/Test/Unit/BWIAnalyticsTests.swift" + - "**/*.strings" # Exclude all strings files + - "Modules/Room/Location/*" + - "Modules/Room/LocationSharing/*" + - "Modules/Room/Views/BubbleCells/Location/*" + - "Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/*" + - path: ../bwi + excludes: + - "Tests" + - path: ../RiotShareExtension/Shared + - path: Modules/MatrixKit + excludes: + - "**/*.md" # excludes all files with the .md extension + # Add separately localizable files + # Once a language has enough translations (>80%), it must be declared here + - path: Assets/en.lproj/InfoPlist.strings + - path: Assets/en.lproj/Localizable.strings + - path: Assets/en.lproj/Vector.strings + - path: Assets/en.lproj/Bwi.strings + - path: Assets/fr.lproj/InfoPlist.strings + - path: Assets/fr.lproj/Localizable.strings + - path: Assets/fr.lproj/Vector.strings + - path: Assets/de.lproj/InfoPlist.strings + - path: Assets/de.lproj/Localizable.strings + - path: Assets/de.lproj/Vector.strings + - path: Assets/de.lproj/Bwi.strings + - path: Assets/sq.lproj/InfoPlist.strings + - path: Assets/sq.lproj/Localizable.strings + - path: Assets/sq.lproj/Vector.strings + - path: Assets/vi.lproj/InfoPlist.strings + - path: Assets/vi.lproj/Localizable.strings + - path: Assets/vi.lproj/Vector.strings + - path: Assets/eu.lproj/InfoPlist.strings + - path: Assets/eu.lproj/Localizable.strings + - path: Assets/eu.lproj/Vector.strings + - path: Assets/bg.lproj/InfoPlist.strings + - path: Assets/bg.lproj/Localizable.strings + - path: Assets/bg.lproj/Vector.strings + - path: Assets/nl.lproj/InfoPlist.strings + - path: Assets/nl.lproj/Localizable.strings + - path: Assets/nl.lproj/Vector.strings + - path: Assets/ca.lproj/InfoPlist.strings + - path: Assets/ca.lproj/Localizable.strings + - path: Assets/ca.lproj/Vector.strings + - path: Assets/zh_Hans.lproj/InfoPlist.strings + - path: Assets/zh_Hans.lproj/Localizable.strings + - path: Assets/zh_Hans.lproj/Vector.strings + - path: Assets/ru.lproj/InfoPlist.strings + - path: Assets/ru.lproj/Localizable.strings + - path: Assets/ru.lproj/Vector.strings + - path: Assets/zh_Hant.lproj/InfoPlist.strings + - path: Assets/zh_Hant.lproj/Localizable.strings + - path: Assets/zh_Hant.lproj/Vector.strings + - path: Assets/es.lproj/InfoPlist.strings + - path: Assets/es.lproj/Localizable.strings + - path: Assets/es.lproj/Vector.strings + - path: Assets/ja.lproj/InfoPlist.strings + - path: Assets/ja.lproj/Localizable.strings + - path: Assets/ja.lproj/Vector.strings + - path: Assets/hu.lproj/InfoPlist.strings + - path: Assets/hu.lproj/Localizable.strings + - path: Assets/hu.lproj/Vector.strings + - path: Assets/pl.lproj/InfoPlist.strings + - path: Assets/pl.lproj/Localizable.strings + - path: Assets/pl.lproj/Vector.strings + - path: Assets/cy.lproj/InfoPlist.strings + - path: Assets/cy.lproj/Localizable.strings + - path: Assets/cy.lproj/Vector.strings + - path: Assets/it.lproj/InfoPlist.strings + - path: Assets/it.lproj/Localizable.strings + - path: Assets/it.lproj/Vector.strings + - path: Assets/et.lproj/InfoPlist.strings + - path: Assets/et.lproj/Localizable.strings + - path: Assets/et.lproj/Vector.strings + - path: Assets/is.lproj/Vector.strings diff --git a/Riot/target.yml b/Riot/target.yml index f8f52a7da..996a5b8ff 100644 --- a/Riot/target.yml +++ b/Riot/target.yml @@ -19,6 +19,7 @@ schemes: run: config: Debug disableMainThreadChecker: true + test: config: Debug disableMainThreadChecker: true @@ -32,10 +33,8 @@ targets: dependencies: - target: RiotShareExtension - - target: SiriIntents - target: RiotNSE - target: DesignKit - - package: Mapbox configFiles: Debug: Debug.xcconfig @@ -57,16 +56,28 @@ targets: excludes: - "Common/Locale/LocaleProvider.swift" - "**/Test/**" + - "Room/LocationSharing/*" - path: ../Tools excludes: - "Logs" - "Release" - "Templates/*.sh" - path: ../Config + excludes: + - "beta" + - "bwi" - path: . excludes: - "Modules/Room/EmojiPicker/Data/EmojiMart/EmojiJSONStore.swift" + - "Modules/Analytics/Test/Unit/BWIAnalyticsTests.swift" - "**/*.strings" # Exclude all strings files + - "Modules/Room/Location/*" + - "Modules/Room/LocationSharing/*" + - "Modules/Room/Views/BubbleCells/Location/*" + - "Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/*" + - path: ../bwi + excludes: + - "Tests" - path: ../RiotShareExtension/Shared - path: Modules/MatrixKit excludes: @@ -86,9 +97,11 @@ targets: - path: Assets/de.lproj/InfoPlist.strings - path: Assets/de.lproj/Localizable.strings - path: Assets/de.lproj/Vector.strings + - path: Assets/de.lproj/Bwi.strings - path: Assets/en.lproj/InfoPlist.strings - path: Assets/en.lproj/Localizable.strings - path: Assets/en.lproj/Vector.strings + - path: Assets/en.lproj/Bwi.strings - path: Assets/eo.lproj/InfoPlist.strings - path: Assets/eo.lproj/Localizable.strings - path: Assets/eo.lproj/Vector.strings diff --git a/RiotNSE/NotificationService.swift b/RiotNSE/NotificationService.swift index 0cba64e18..181f09fd5 100644 --- a/RiotNSE/NotificationService.swift +++ b/RiotNSE/NotificationService.swift @@ -1,5 +1,6 @@ /* Copyright 2020 New Vector Ltd + Copyright (c) 2021 BWI GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -66,7 +67,7 @@ class NotificationService: UNNotificationServiceExtension { private static var isLoggerInitialized: Bool = false private lazy var pushGatewayRestClient: MXPushGatewayRestClient = { - let url = URL(string: BuildSettings.serverConfigSygnalAPIUrlString)! + let url = URL(string: AppConfigService.shared.pusherUrl())! return MXPushGatewayRestClient(pushGateway: url.scheme! + "://" + url.host!, andOnUnrecognizedCertificateBlock: nil) }() private var pushNotificationStore: PushNotificationStore = PushNotificationStore() diff --git a/RiotNSE/target.yml b/RiotNSE/target.yml index 68a86ca51..281adc995 100644 --- a/RiotNSE/target.yml +++ b/RiotNSE/target.yml @@ -37,23 +37,25 @@ targets: sources: - path: . + - path: ../bwi/AppConfig + - path: ../bwi/SecureStorage + - path: ../bwi/BwiBuildSettings.swift + - path: ../Config/BwiSettings.swift - path: ../Riot/Managers/Settings/RiotSettings.swift - path: ../Config/BuildSettings.swift + - path: ../bwi/UserAgent/UserAgentService.swift - path: ../Riot/Utils/DataProtectionHelper.swift - path: ../Config/CommonConfiguration.swift - path: ../Riot/Managers/PushNotification/PushNotificationStore.swift - path: ../Riot/Modules/SetPinCode/PinCodePreferences.swift - - path: ../Riot/Managers/KeyValueStorage/Extensions/Keychain.swift - path: ../Riot/Modules/SetPinCode/SetupBiometrics/BiometricsAuthenticationPresenter.swift - path: ../Riot/Categories/UNUserNotificationCenter.swift - - path: ../Riot/Managers/KeyValueStorage/KeyValueStore.swift - path: ../Riot/Managers/Locale/LocaleProvider.swift - path: ../Riot/Managers/Locale/LocaleProviderType.swift - path: ../Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift - path: ../Riot/Categories/Bundle.swift - path: ../Riot/Generated/Strings.swift - path: ../Riot/Generated/Images.swift - - path: ../Riot/Managers/KeyValueStorage/KeychainStore.swift - path: ../Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift - path: ../Config/Configurable.swift - path: ../Riot/Utils/Constants.swift diff --git a/RiotShareExtension/SupportingFiles/Info.plist b/RiotShareExtension/SupportingFiles/Info.plist index fce6e4c3f..5ae2a4c11 100644 --- a/RiotShareExtension/SupportingFiles/Info.plist +++ b/RiotShareExtension/SupportingFiles/Info.plist @@ -25,20 +25,7 @@ NSExtensionAttributes NSExtensionActivationRule - - NSExtensionActivationDictionaryVersion - 2 - NSExtensionActivationSupportsFileWithMaxCount - 5 - NSExtensionActivationSupportsImageWithMaxCount - 5 - NSExtensionActivationSupportsMovieWithMaxCount - 1 - NSExtensionActivationSupportsText - - NSExtensionActivationSupportsWebURLWithMaxCount - 1 - + NSExtensionPointIdentifier com.apple.share-services diff --git a/RiotShareExtension/target.yml b/RiotShareExtension/target.yml index 494407323..a3606362d 100644 --- a/RiotShareExtension/target.yml +++ b/RiotShareExtension/target.yml @@ -37,11 +37,15 @@ targets: sources: - path: . + - path: ../bwi/SecureStorage + - path: ../bwi/BwiBuildSettings.swift + - path: ../Config/BwiSettings.swift - path: ../Riot/Modules/Common/SegmentedViewController/SegmentedViewController.m - path: ../Riot/Categories/Bundle.swift - path: ../Riot/Managers/Theme/ - path: ../Riot/Utils/AvatarGenerator.m - path: ../Config/BuildSettings.swift + - path: ../bwi/UserAgent/UserAgentService.swift - path: ../Riot/Categories/Character.swift - path: ../Riot/Categories/MXKImageView.swift - path: ../Riot/Categories/MXRoom+Riot.m @@ -50,7 +54,6 @@ targets: - path: ../Riot/Utils/UserNameColorGenerator.swift - path: ../Riot/Categories/MXRoomSummary+Riot.m - path: ../Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift - - path: ../Riot/Managers/KeyValueStorage - path: ../Riot/Managers/Settings/RiotSettings.swift - path: ../Riot/Managers/AppInfo/ - path: ../Riot/Categories/UIColor.swift diff --git a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift index bf139307f..f7721e24d 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift @@ -21,7 +21,6 @@ import Foundation enum MockAppScreens { static let appScreens: [MockScreenState.Type] = [ MockOnboardingSplashScreenScreenState.self, - MockLocationSharingScreenState.self, MockAnalyticsPromptScreenState.self, MockUserSuggestionScreenState.self, MockPollEditFormScreenState.self, diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift index e0574c0d7..09a41b11b 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift @@ -1,5 +1,6 @@ // // Copyright 2021 New Vector Ltd +// Copyright (c) 2021 BWI GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -82,10 +83,8 @@ class TimelinePollViewModel: TimelinePollViewModelType, TimelinePollViewModelPro if answerOption.selected { answerOption.selected = false - if(state.poll.answerOptions.count > 0) { - answerOption.count = answerOption.count - 1 - state.poll.totalAnswerCount -= 1 - } + answerOption.count = UInt(max(0, Int(answerOption.count) - 1)) + state.poll.totalAnswerCount = UInt(max(0, Int(state.poll.totalAnswerCount) - 1)) } if answerOption.id == selectedAnswerIdentifier { @@ -114,8 +113,8 @@ class TimelinePollViewModel: TimelinePollViewModelType, TimelinePollViewModelPro if answerOption.selected { answerOption.selected = false - answerOption.count -= 1 - state.poll.totalAnswerCount -= 1 + answerOption.count = UInt(max(0, Int(answerOption.count) - 1)) + state.poll.totalAnswerCount = UInt(max(0, Int(state.poll.totalAnswerCount) - 1)) } else { answerOption.selected = true answerOption.count += 1 diff --git a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationSettingsScreen.swift b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationSettingsScreen.swift index 7049e67bd..229b33102 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationSettingsScreen.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationSettingsScreen.swift @@ -32,13 +32,24 @@ extension NotificationSettingsScreen: Identifiable { extension NotificationSettingsScreen { /// Defines which rules are handled by each of the screens. var pushRules: [NotificationPushRuleId] { - switch self { - case .defaultNotifications: - return [.oneToOneRoom, .allOtherMessages, .oneToOneEncryptedRoom, .encrypted] - case .mentionsAndKeywords: - return [.containDisplayName, .containUserName, .roomNotif, .keywords] - case .other: - return [.inviteMe, .call, .suppressBots, .tombstone] + if BuildSettings.settingsNotificationsBWIDefaultSet { + switch self { + case .defaultNotifications: + return [.oneToOneEncryptedRoom, .encrypted, .inviteMe] + case .mentionsAndKeywords: + return [] + case .other: + return [] + } + } else { + switch self { + case .defaultNotifications: + return [.oneToOneRoom, .allOtherMessages, .oneToOneEncryptedRoom, .encrypted] + case .mentionsAndKeywords: + return [.containDisplayName, .containUserName, .roomNotif, .keywords] + case .other: + return [.inviteMe, .call, .suppressBots, .tombstone] + } } } } diff --git a/RiotSwiftUI/target.yml b/RiotSwiftUI/target.yml index 6929672cd..afff54e55 100644 --- a/RiotSwiftUI/target.yml +++ b/RiotSwiftUI/target.yml @@ -31,7 +31,6 @@ targets: platform: iOS dependencies: - target: DesignKit - - package: Mapbox sources: - path: . excludes: @@ -39,6 +38,7 @@ targets: - "**/MatrixSDK/**" - "**/Coordinator/**" - "**/Test/**" + - "**/LocationSharing/**" - path: ../Riot/Managers/AppInfo/ - path: ../Riot/Categories/Bundle.swift - path: ../Riot/Generated/Strings.swift diff --git a/RiotTests/target.yml b/RiotTests/target.yml index ad56a52c5..f6c401014 100644 --- a/RiotTests/target.yml +++ b/RiotTests/target.yml @@ -57,13 +57,16 @@ targets: - path: . - path: ../Config/Configurable.swift - path: ../Config/BuildSettings.swift + - path: ../bwi/BwiBuildSettings.swift + - path: ../Config/BwiSettings.swift - path: ../Config/AppConfiguration.swift - path: ../Config/CommonConfiguration.swift - path: ../Riot/Categories/Bundle.swift - path: ../Riot/Managers/Settings/RiotSettings.swift - path: ../Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift - - path: ../Riot/Managers/KeyValueStorage/ + - path: ../bwi/SecureStorage/ - path: ../Riot/PropertyWrappers/UserDefaultsBackedPropertyWrapper.swift + - path: ../bwi/Tests/BWIAnalyticsTests.swift - path: ../Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineStyleIdentifier.swift - path: ../Riot/Modules/Room/EventMenu/EventMenuBuilder.swift - path: ../Riot/Modules/Room/EventMenu/EventMenuItemType.swift diff --git a/SiriIntents/target.yml b/SiriIntents/target.yml index ca60c3bc1..1918ad490 100644 --- a/SiriIntents/target.yml +++ b/SiriIntents/target.yml @@ -46,7 +46,6 @@ targets: - path: ../Config/Configurable.swift - path: ../Riot/Managers/Settings/RiotSettings.swift - path: ../Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift - - path: ../Riot/Managers/KeyValueStorage - path: ../Riot/PropertyWrappers/UserDefaultsBackedPropertyWrapper.swift - path: ../Riot/Managers/Locale/LocaleProviderType.swift - path: ../Riot/Managers/Locale/LocaleProvider.swift diff --git a/Tools/Templates/buildable/SimpleScreenTemplate/SimpleScreenTemplateViewController.storyboard b/Tools/Templates/buildable/SimpleScreenTemplate/SimpleScreenTemplateViewController.storyboard index 0503467c2..a87635a26 100644 --- a/Tools/Templates/buildable/SimpleScreenTemplate/SimpleScreenTemplateViewController.storyboard +++ b/Tools/Templates/buildable/SimpleScreenTemplate/SimpleScreenTemplateViewController.storyboard @@ -1,9 +1,9 @@ - + - + @@ -25,7 +25,7 @@ - + @@ -55,7 +55,7 @@ -