├── .eslintrc.js
├── .expo-shared
└── assets.json
├── .github
└── FUNDING.yml
├── .gitignore
├── .watchmanconfig
├── AUTHORS
├── App.ts
├── LICENSES
└── GPL-3.0-only
├── README.md
├── android
├── app
│ ├── build.gradle
│ ├── expo.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── assets
│ │ └── kernel-manifest.json
│ │ ├── java
│ │ └── host
│ │ │ └── exp
│ │ │ └── exponent
│ │ │ ├── MainActivity.java
│ │ │ ├── MainApplication.java
│ │ │ └── generated
│ │ │ ├── AppConstants.java
│ │ │ └── DetachBuildConstants.java
│ │ └── res
│ │ ├── drawable-hdpi
│ │ ├── ic_arrow_back_white_36dp.png
│ │ ├── ic_home_white_36dp.png
│ │ ├── ic_logo_white_32dp.png
│ │ ├── ic_refresh_white_36dp.png
│ │ └── ic_share_white_36dp.png
│ │ ├── drawable-mdpi
│ │ ├── ic_arrow_back_white_36dp.png
│ │ ├── ic_home_white_36dp.png
│ │ ├── ic_logo_white_32dp.png
│ │ ├── ic_refresh_white_36dp.png
│ │ └── ic_share_white_36dp.png
│ │ ├── drawable-xhdpi
│ │ ├── ic_arrow_back_white_36dp.png
│ │ ├── ic_home_white_36dp.png
│ │ ├── ic_logo_white_32dp.png
│ │ ├── ic_refresh_white_36dp.png
│ │ └── ic_share_white_36dp.png
│ │ ├── drawable-xxhdpi
│ │ ├── ic_arrow_back_white_36dp.png
│ │ ├── ic_home_white_36dp.png
│ │ ├── ic_logo_white_32dp.png
│ │ ├── ic_refresh_white_36dp.png
│ │ └── ic_share_white_36dp.png
│ │ ├── drawable-xxxhdpi
│ │ ├── big_logo_dark.png
│ │ ├── big_logo_dark_filled.png
│ │ ├── big_logo_filled.png
│ │ ├── big_logo_new_filled.png
│ │ ├── ic_arrow_back_white_36dp.png
│ │ ├── ic_home_white_36dp.png
│ │ ├── ic_logo_white_32dp.png
│ │ ├── ic_refresh_white_36dp.png
│ │ ├── ic_share_white_36dp.png
│ │ ├── notification_icon.png
│ │ ├── pin_white.png
│ │ ├── pin_white_fade.png
│ │ ├── shell_launch_background_image.png
│ │ └── shell_notification_icon.png
│ │ ├── drawable
│ │ └── splash_background.xml
│ │ ├── layout
│ │ ├── error_activity_new.xml
│ │ ├── error_console_fragment.xml
│ │ ├── error_console_list_item.xml
│ │ ├── error_fragment.xml
│ │ ├── notification.xml
│ │ └── notification_shell_app.xml
│ │ ├── mipmap-hdpi
│ │ ├── dev_icon.png
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ ├── dev_icon.png
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ ├── dev_icon.png
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ ├── dev_icon.png
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── dev_icon.png
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
├── build.gradle
├── debug.keystore
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
└── settings.gradle
├── app.json
├── assets
├── icon.png
└── splash.png
├── babel.config.js
├── build-instructions.md
├── deploy_dist.sh
├── etesync.mobileconfig
├── ios
├── EteSyncTests
│ ├── EteSyncTests.swift
│ └── Info.plist
├── Podfile
├── Podfile.lock
├── etesync.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── etesync.xcscheme
├── etesync.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── etesync
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ ├── AppIcon1024x1024.png
│ │ ├── AppIcon20x20@2x.png
│ │ ├── AppIcon20x20@3x.png
│ │ ├── AppIcon29x29@2x.png
│ │ ├── AppIcon29x29@3x.png
│ │ ├── AppIcon40x40@2x.png
│ │ ├── AppIcon40x40@3x.png
│ │ ├── AppIcon60x60@2x.png
│ │ ├── AppIcon60x60@3x.png
│ │ ├── AppIcon76x76@2x~ipad.png
│ │ ├── AppIcon76x76~ipad.png
│ │ ├── AppIcon83.5x83.5@2x~ipad.png
│ │ └── Contents.json
│ ├── Supporting
│ ├── EXSDKVersions.plist
│ ├── EXShell.json
│ ├── EXShell.plist
│ ├── Info.plist
│ ├── LaunchScreen.xib
│ ├── launch_background_image.png
│ ├── launch_icon.png
│ ├── main.m
│ └── sdkVersions.json
│ ├── etesync.entitlements
│ └── modules
│ ├── ContactSerializer.swift
│ ├── EteEXCalendar.h
│ ├── EteEXCalendar.m
│ ├── EtesyncNative.swift
│ ├── EtesyncNativeBridge.m
│ ├── EtesyncNativeTest.swift
│ ├── Utils.swift
│ └── etesync-Bridging-Header.h
├── license-gen.js
├── licenses.json
├── package.json
├── shim.js
├── src
├── AboutScreen.tsx
├── AccountWizardScreen.tsx
├── App.tsx
├── CollectionChangelogScreen.tsx
├── CollectionEditScreen.tsx
├── CollectionImportScreen.tsx
├── CollectionItemContact.tsx
├── CollectionItemEvent.tsx
├── CollectionItemScreen.tsx
├── CollectionItemTask.tsx
├── CollectionMemberAddDialog.tsx
├── CollectionMembersScreen.tsx
├── DebugLogsScreen.tsx
├── Drawer.tsx
├── ErrorBoundary.tsx
├── EteSyncNative.ts
├── HomeScreen.tsx
├── InvitationsScreen.tsx
├── JournalEditScreen.tsx
├── JournalEntriesScreen.tsx
├── JournalImportScreen.tsx
├── JournalItemContact.tsx
├── JournalItemEvent.tsx
├── JournalItemHeader.tsx
├── JournalItemSaveScreen.tsx
├── JournalItemScreen.tsx
├── JournalItemTask.tsx
├── JournalMembersScreen.tsx
├── LegacyHomeScreen.tsx
├── LogoutDialog.tsx
├── Permissions.tsx
├── RootNavigator.tsx
├── SettingsGate.tsx
├── SettingsScreen.tsx
├── SettingsScreenLegacy.tsx
├── SyncGate.tsx
├── SyncHandler.tsx
├── components
│ ├── EncryptionLoginForm.tsx
│ ├── JournalListScreen.tsx
│ ├── JournalListScreenEb.tsx
│ ├── LoginForm.tsx
│ └── WebviewKeygen.tsx
├── constants
│ └── index.ts
├── credentials.tsx
├── data
│ └── zones.json
├── etesync-helpers.ts
├── helpers.test.tsx
├── helpers.tsx
├── images
│ └── icon.png
├── index.tsx
├── logging.ts
├── login
│ ├── LoginScreen.tsx
│ └── index.tsx
├── pim-types.ts
├── store
│ ├── actions.ts
│ ├── construct.ts
│ ├── index.test.ts
│ ├── index.ts
│ ├── promise-middleware.ts
│ └── reducers.ts
├── sync
│ ├── SyncManager.ts
│ ├── SyncManagerAddressBook.ts
│ ├── SyncManagerBase.ts
│ ├── SyncManagerCalendar.ts
│ ├── SyncManagerTaskList.ts
│ ├── SyncSettings.tsx
│ ├── helpers.ts
│ ├── index.ts
│ └── legacy
│ │ ├── SyncManager.ts
│ │ ├── SyncManagerAddressBook.ts
│ │ ├── SyncManagerBase.ts
│ │ ├── SyncManagerCalendar.ts
│ │ └── SyncManagerTaskList.ts
├── types
│ ├── ical.js.d.ts
│ └── redux-persist.d.ts
└── widgets
│ ├── Alert.tsx
│ ├── Checkbox.tsx
│ ├── ColorBox.tsx
│ ├── ColorPicker.tsx
│ ├── ConfirmationDialog.tsx
│ ├── Container.tsx
│ ├── ErrorDialog.tsx
│ ├── ErrorOrLoadingDialog.tsx
│ ├── ExternalLink.tsx
│ ├── LoadingIndicator.tsx
│ ├── Markdown.tsx
│ ├── PasswordInput.tsx
│ ├── PrettyFingerprint.tsx
│ ├── PrettyFingerprintEb.tsx
│ ├── Row.tsx
│ ├── ScrollView.tsx
│ ├── Select.tsx
│ ├── Small.tsx
│ ├── TextInput.tsx
│ ├── Typography.tsx
│ └── Wizard.tsx
├── tsconfig.json
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "shared-node-browser": true,
4 | "es6": true,
5 | },
6 | "parser": "@typescript-eslint/parser",
7 | "parserOptions": {
8 | "sourceType": "module",
9 | "ecmaFeatures": {
10 | "jsx": true
11 | }
12 | },
13 | "settings": {
14 | "react": {
15 | "version": "detect",
16 | },
17 | },
18 | "plugins": [
19 | "@typescript-eslint",
20 | ],
21 | "extends": [
22 | "eslint:recommended",
23 | "plugin:react/recommended",
24 | "plugin:@typescript-eslint/eslint-recommended",
25 | "plugin:@typescript-eslint/recommended"
26 | ],
27 | "rules": {
28 | "@typescript-eslint/explicit-function-return-type": "off",
29 | "@typescript-eslint/no-use-before-define": "off",
30 | "@typescript-eslint/no-non-null-assertion": "off",
31 | "@typescript-eslint/no-explicit-any": "off",
32 | "@typescript-eslint/member-delimiter-style": ["error", {
33 | "multiline": {
34 | "delimiter": "semi",
35 | "requireLast": true
36 | },
37 | "singleline": {
38 | "delimiter": "comma",
39 | "requireLast": false
40 | }
41 | }],
42 | "@typescript-eslint/no-unused-vars": ["warn", {
43 | "vars": "all",
44 | "args": "all",
45 | "ignoreRestSiblings": true,
46 | "argsIgnorePattern": "^_",
47 | }],
48 |
49 | "react/display-name": "off",
50 | "react/no-unescaped-entities": "off",
51 | "react/jsx-tag-spacing": ["error", {
52 | "closingSlash": "never",
53 | "beforeSelfClosing": "always",
54 | "afterOpening": "never",
55 | "beforeClosing": "never"
56 | }],
57 | "react/jsx-boolean-value": ["error", "never"],
58 | "react/jsx-curly-spacing": ["error", { "when": "never", "children": true }],
59 | "react/jsx-equals-spacing": ["error", "never"],
60 | "react/jsx-indent-props": ["error", 2],
61 | "react/jsx-curly-brace-presence": ["error", "never"],
62 | "react/jsx-key": ["error", { "checkFragmentShorthand": true }],
63 | "react/jsx-indent": ["error", 2, { checkAttributes: true, indentLogicalExpressions: true }],
64 | "react/void-dom-elements-no-children": ["error"],
65 | "react/no-unknown-property": ["error"],
66 |
67 | "quotes": "off",
68 | "@typescript-eslint/quotes": ["error", "double", { "allowTemplateLiterals": true, "avoidEscape": true }],
69 | "semi": "off",
70 | "@typescript-eslint/semi": ["error", "always", { "omitLastInOneLineBlock": true }],
71 | "comma-dangle": ["error", {
72 | "arrays": "always-multiline",
73 | "objects": "always-multiline",
74 | "imports": "always-multiline",
75 | "exports": "always-multiline",
76 | "functions": "never"
77 | }],
78 | "comma-spacing": ["error"],
79 | "eqeqeq": ["error", "smart"],
80 | "indent": "off",
81 | "@typescript-eslint/indent": ["error", 2, {
82 | "SwitchCase": 1,
83 | }],
84 | "no-multi-spaces": "error",
85 | "object-curly-spacing": ["error", "always"],
86 | "arrow-parens": "error",
87 | "arrow-spacing": "error",
88 | "key-spacing": "error",
89 | "keyword-spacing": "error",
90 | "func-call-spacing": "off",
91 | "@typescript-eslint/func-call-spacing": ["error"],
92 | "space-before-function-paren": ["error", {
93 | "anonymous": "always",
94 | "named": "never",
95 | "asyncArrow": "always"
96 | }],
97 | "space-in-parens": ["error", "never"],
98 | "space-before-blocks": "error",
99 | "curly": ["error", "all"],
100 | "space-infix-ops": "error",
101 | "consistent-return": "error",
102 | "jsx-quotes": ["error"],
103 | "array-bracket-spacing": "error",
104 | "brace-style": "off",
105 | "@typescript-eslint/brace-style": [
106 | "error",
107 | "1tbs",
108 | { allowSingleLine: true },
109 | ],
110 | "no-useless-constructor": "off",
111 | "@typescript-eslint/no-useless-constructor": "warn",
112 | }
113 | };
114 |
--------------------------------------------------------------------------------
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "bcbfadc7151fde99a5ae3871cdabbd22f007ea6cbaee2be3b3d87dc27101c53a": true,
3 | "af0ce96885147c05d7b5bf4162cce9dc050bdbf338b0bae9a3ec86b4f9a833a9": true,
4 | "baddda505ed7aa3bd17ad9541c791e7834f15e9433bb3b044683427021e414e4": true
5 | }
6 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: etesync
2 | custom: https://www.etesync.com/contribute/#donate
3 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Tom Hacohen
2 |
--------------------------------------------------------------------------------
/App.ts:
--------------------------------------------------------------------------------
1 | import './shim';
2 | import Index from './src/index';
3 | export default Index;
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
EteSync - Secure Data Sync
4 |
5 |
6 | Secure, end-to-end encrypted, and privacy respecting sync for your contacts, calendars and tasks (iOS client).
7 |
8 | 
9 | [](https://www.etebase.com/community-chat/)
10 |
11 | # Overview
12 |
13 | Please see the [EteSync website](https://www.etesync.com) for more information.
14 |
15 | EteSync is licensed under the [GPLv3 License](LICENSE).
16 |
17 | # Setup
18 |
19 | For setup instructions please take a look at the [user guide](https://www.etesync.com/user-guide/ios/).
20 |
21 | # Thanks
22 |
23 | EteSync iOS is made possible with financial support from NLnet Foundation, courtesy of NGI0 Discovery and the European Commission DG
28 | CNECT's Next Generation Internet
29 | programme.
30 |
--------------------------------------------------------------------------------
/android/app/expo.gradle:
--------------------------------------------------------------------------------
1 | // Gradle script for detached apps.
2 |
3 | import org.apache.tools.ant.taskdefs.condition.Os
4 |
5 | void runBefore(String dependentTaskName, Task task) {
6 | Task dependentTask = tasks.findByPath(dependentTaskName);
7 | if (dependentTask != null) {
8 | dependentTask.dependsOn task
9 | }
10 | }
11 |
12 | afterEvaluate {
13 | def expoRoot = file("../../")
14 | def inputExcludes = ["android/**", "ios/**"]
15 |
16 | task exponentPrebuildStep(type: Exec) {
17 | workingDir expoRoot
18 | if (Os.isFamily(Os.FAMILY_WINDOWS)) {
19 | commandLine "cmd", "/c", ".\\node_modules\\expokit\\detach-scripts\\run-exp.bat"
20 | } else {
21 | commandLine "./node_modules/expokit/detach-scripts/run-exp.sh", "prepare-detached-build", "--platform", "android", expoRoot
22 | }
23 | }
24 | runBefore("preBuild", exponentPrebuildStep)
25 |
26 | // Based on https://github.com/facebook/react-native/blob/master/react.gradle
27 |
28 | android.applicationVariants.each { variant ->
29 | def folderName = variant.name
30 | def targetName = folderName.capitalize()
31 |
32 | def assetsDir = file("$buildDir/intermediates/merged_assets/${folderName}/out")
33 |
34 | // Bundle task name for variant
35 | def bundleExpoAssetsTaskName = "bundle${targetName}ExpoAssets"
36 |
37 | def currentBundleTask = tasks.create(
38 | name: bundleExpoAssetsTaskName,
39 | type: Exec) {
40 | description = "Expo bundle assets for ${targetName}."
41 |
42 | // Create dirs if they are not there (e.g. the "clean" task just ran)
43 | doFirst {
44 | assetsDir.mkdirs()
45 | }
46 |
47 | // Set up inputs and outputs so gradle can cache the result
48 | inputs.files fileTree(dir: expoRoot, excludes: inputExcludes)
49 | outputs.dir assetsDir
50 |
51 | // Set up the call to exp
52 | workingDir expoRoot
53 |
54 | if (Os.isFamily(Os.FAMILY_WINDOWS)) {
55 | commandLine("cmd", "/c", ".\\node_modules\\expokit\\detach-scripts\\run-exp.bat", "bundle-assets", expoRoot, "--platform", "android", "--dest", assetsDir)
56 | } else {
57 | commandLine("./node_modules/expokit/detach-scripts/run-exp.sh", "bundle-assets", expoRoot, "--platform", "android", "--dest", assetsDir)
58 | }
59 |
60 | enabled targetName.toLowerCase().contains("release") || targetName.toLowerCase().contains("prod")
61 | }
62 |
63 | currentBundleTask.dependsOn("merge${targetName}Resources")
64 | currentBundleTask.dependsOn("merge${targetName}Assets")
65 |
66 | runBefore("process${targetName}Resources", currentBundleTask)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/android/app/src/main/assets/kernel-manifest.json:
--------------------------------------------------------------------------------
1 | {"ios":{"supportsTablet":true,"bundleIdentifier":"host.exp.exponent","publishBundlePath":"../ios/Exponent/Supporting/kernel.ios.bundle"},"icon":"https://s3.amazonaws.com/exp-brand-assets/ExponentEmptyManifest_192.png","name":"expo-home","slug":"home","extra":{"amplitudeApiKey":"081e5ec53f869b440b225d5e40ec73f9"},"kernel":{"iosManifestPath":"../ios/Exponent/Supporting/kernel-manifest.json","androidManifestPath":"../android/app/src/main/assets/kernel-manifest.json"},"scheme":"exp","android":{"package":"host.exp.exponent","publishBundlePath":"../android/app/src/main/assets/kernel.android.bundle"},"iconUrl":"https://s3.amazonaws.com/exp-brand-assets/ExponentEmptyManifest_192.png","locales":{},"privacy":"unlisted","updates":{"checkAutomatically":"ON_LOAD","fallbackToCacheTimeout":0},"version":"36.0.0","isKernel":true,"platforms":["ios","android"],"sdkVersion":"UNVERSIONED","description":"The Expo client app","orientation":"portrait","dependencies":["@expo/react-native-action-sheet","@expo/react-native-touchable-native-feedback-safe","@react-native-community/netinfo","@react-navigation/web","apollo-boost","apollo-cache-inmemory","dedent","es6-error","expo","expo-analytics-amplitude","expo-asset","expo-barcode-scanner","expo-blur","expo-camera","expo-constants","expo-font","expo-linear-gradient","expo-location","expo-permissions","expo-task-manager","expo-web-browser","graphql","graphql-tag","immutable","lodash","prop-types","querystring","react","react-apollo","react-native","react-native-appearance","react-native-fade-in-image","react-native-gesture-handler","react-native-infinite-scroll-view","react-native-maps","react-navigation","react-navigation-material-bottom-tabs","react-navigation-stack","react-navigation-tabs","react-redux","redux","redux-thunk","semver","sha1","url"],"packagerOpts":{"config":"metro.config.js"},"primaryColor":"#cccccc","userInterfaceStyle":"automatic","id":"@exponent/home","revisionId":"36.0.0-r.SygW7r7mpr","publishedTime":"2019-12-02T23:58:17.039Z","commitTime":"2019-12-02T23:58:17.148Z","bundleUrl":"https://exp.host/@exponent/home/bundle","releaseChannel":"default","hostUri":"exp.host/@exponent/home"}
--------------------------------------------------------------------------------
/android/app/src/main/java/host/exp/exponent/MainActivity.java:
--------------------------------------------------------------------------------
1 | package host.exp.exponent;
2 |
3 | import android.os.Bundle;
4 |
5 | import com.facebook.react.ReactPackage;
6 |
7 | import org.unimodules.core.interfaces.Package;
8 |
9 | import java.util.List;
10 |
11 | import host.exp.exponent.experience.DetachActivity;
12 | import host.exp.exponent.generated.DetachBuildConstants;
13 |
14 | public class MainActivity extends DetachActivity {
15 |
16 | @Override
17 | public String publishedUrl() {
18 | return "exp://exp.host/@etesync/etesync-ios";
19 | }
20 |
21 | @Override
22 | public String developmentUrl() {
23 | return DetachBuildConstants.DEVELOPMENT_URL;
24 | }
25 |
26 | @Override
27 | public List reactPackages() {
28 | return ((MainApplication) getApplication()).getPackages();
29 | }
30 |
31 | @Override
32 | public List expoPackages() {
33 | return ((MainApplication) getApplication()).getExpoPackages();
34 | }
35 |
36 | @Override
37 | public boolean isDebug() {
38 | return BuildConfig.DEBUG;
39 | }
40 |
41 | @Override
42 | public Bundle initialProps(Bundle expBundle) {
43 | // Add extra initialProps here
44 | return expBundle;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/android/app/src/main/java/host/exp/exponent/MainApplication.java:
--------------------------------------------------------------------------------
1 | package host.exp.exponent;
2 |
3 | import com.facebook.react.ReactPackage;
4 |
5 | import org.unimodules.core.interfaces.Package;
6 |
7 | import java.util.Arrays;
8 | import java.util.List;
9 |
10 | import expo.loaders.provider.interfaces.AppLoaderPackagesProviderInterface;
11 | import host.exp.exponent.generated.BasePackageList;
12 | import okhttp3.OkHttpClient;
13 |
14 | // Needed for `react-native link`
15 | // import com.facebook.react.ReactApplication;
16 | import com.RNRSA.RNRSAPackage;
17 |
18 | public class MainApplication extends ExpoApplication implements AppLoaderPackagesProviderInterface {
19 |
20 | @Override
21 | public boolean isDebug() {
22 | return BuildConfig.DEBUG;
23 | }
24 |
25 | // Needed for `react-native link`
26 | public List getPackages() {
27 | return Arrays.asList(
28 | // Add your own packages here!
29 | // TODO: add native modules!
30 |
31 | // Needed for `react-native link`
32 | // new MainReactPackage(),
33 | new RNRSAPackage()
34 | );
35 | }
36 |
37 | public List getExpoPackages() {
38 | return new BasePackageList().getPackageList();
39 | }
40 |
41 | @Override
42 | public String gcmSenderId() {
43 | return getString(R.string.gcm_defaultSenderId);
44 | }
45 |
46 | public static OkHttpClient.Builder okHttpClientBuilder(OkHttpClient.Builder builder) {
47 | // Customize/override OkHttp client here
48 | return builder;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/android/app/src/main/java/host/exp/exponent/generated/AppConstants.java:
--------------------------------------------------------------------------------
1 | package host.exp.exponent.generated;
2 |
3 | import com.facebook.common.internal.DoNotStrip;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import host.exp.exponent.BuildConfig;
9 | import host.exp.exponent.Constants;
10 |
11 | @DoNotStrip
12 | public class AppConstants {
13 |
14 | public static final String VERSION_NAME = "1.1.3";
15 | public static String INITIAL_URL = "exp://exp.host/@etesync/etesync-ios";
16 | public static final String SHELL_APP_SCHEME = "exp2904f03229f24a3ca9b3fcd8485120ca";
17 | public static final String RELEASE_CHANNEL = "default";
18 | public static boolean SHOW_LOADING_VIEW_IN_SHELL_APP = true;
19 | public static boolean ARE_REMOTE_UPDATES_ENABLED = true;
20 | public static final List EMBEDDED_RESPONSES;
21 | public static boolean FCM_ENABLED = false;
22 |
23 | static {
24 | List embeddedResponses = new ArrayList<>();
25 |
26 |
27 | // ADD EMBEDDED RESPONSES HERE
28 | // START EMBEDDED RESPONSES
29 | embeddedResponses.add(new Constants.EmbeddedResponse("https://expo.etesync.com/release/5/android-index.json", "assets://shell-app-manifest.json", "application/json"));
30 | embeddedResponses.add(new Constants.EmbeddedResponse("https://expo.etesync.com/release/5/bundles/android-d9c315522a5c2e60cdc6c397465e7e31.js", "assets://shell-app.bundle", "application/javascript"));
31 | // END EMBEDDED RESPONSES
32 | EMBEDDED_RESPONSES = embeddedResponses;
33 | }
34 |
35 | // Called from expoview/Constants
36 | public static Constants.ExpoViewAppConstants get() {
37 | Constants.ExpoViewAppConstants constants = new Constants.ExpoViewAppConstants();
38 | constants.VERSION_NAME = VERSION_NAME;
39 | constants.INITIAL_URL = INITIAL_URL;
40 | constants.SHELL_APP_SCHEME = SHELL_APP_SCHEME;
41 | constants.RELEASE_CHANNEL = RELEASE_CHANNEL;
42 | constants.SHOW_LOADING_VIEW_IN_SHELL_APP = SHOW_LOADING_VIEW_IN_SHELL_APP;
43 | constants.ARE_REMOTE_UPDATES_ENABLED = ARE_REMOTE_UPDATES_ENABLED;
44 | constants.EMBEDDED_RESPONSES = EMBEDDED_RESPONSES;
45 | constants.ANDROID_VERSION_CODE = BuildConfig.VERSION_CODE;
46 | constants.FCM_ENABLED = FCM_ENABLED;
47 | return constants;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/android/app/src/main/java/host/exp/exponent/generated/DetachBuildConstants.java:
--------------------------------------------------------------------------------
1 | package host.exp.exponent.generated;
2 |
3 | // This file is auto-generated. Please don't rename!
4 | public class DetachBuildConstants {
5 |
6 | public static final String DEVELOPMENT_URL = "";
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_arrow_back_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-hdpi/ic_arrow_back_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_home_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-hdpi/ic_home_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_logo_white_32dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-hdpi/ic_logo_white_32dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_refresh_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-hdpi/ic_refresh_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_share_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-hdpi/ic_share_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_arrow_back_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-mdpi/ic_arrow_back_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_home_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-mdpi/ic_home_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_logo_white_32dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-mdpi/ic_logo_white_32dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_refresh_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-mdpi/ic_refresh_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_share_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-mdpi/ic_share_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_arrow_back_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xhdpi/ic_arrow_back_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_home_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xhdpi/ic_home_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_logo_white_32dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xhdpi/ic_logo_white_32dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_refresh_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xhdpi/ic_refresh_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_share_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xhdpi/ic_share_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_arrow_back_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxhdpi/ic_arrow_back_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_home_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxhdpi/ic_home_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_logo_white_32dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxhdpi/ic_logo_white_32dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_refresh_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxhdpi/ic_refresh_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_share_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxhdpi/ic_share_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/big_logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/big_logo_dark.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/big_logo_dark_filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/big_logo_dark_filled.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/big_logo_filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/big_logo_filled.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/big_logo_new_filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/big_logo_new_filled.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_arrow_back_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/ic_arrow_back_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_home_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/ic_home_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_logo_white_32dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/ic_logo_white_32dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_share_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/ic_share_white_36dp.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/notification_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/pin_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/pin_white.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/pin_white_fade.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/pin_white_fade.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/shell_launch_background_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/shell_launch_background_image.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/shell_notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/drawable-xxxhdpi/shell_notification_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/splash_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/error_activity_new.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/error_console_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
24 |
25 |
34 |
35 |
44 |
45 |
46 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/error_console_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
19 |
20 |
25 |
26 |
31 |
32 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/error_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
21 |
22 |
31 |
32 |
44 |
45 |
56 |
57 |
66 |
67 |
76 |
77 |
78 |
79 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/notification.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
19 |
20 |
36 |
37 |
46 |
47 |
56 |
57 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/notification_shell_app.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
35 |
36 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/dev_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/mipmap-hdpi/dev_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/dev_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/mipmap-mdpi/dev_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/dev_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/mipmap-xhdpi/dev_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/dev_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/mipmap-xxhdpi/dev_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/dev_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/mipmap-xxxhdpi/dev_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #1b73b4
4 | #011A2D
5 | #1b73b4
6 | #FFFFFF
7 | #FFFFFF
8 | #FFFFFF
9 | #FF0000
10 | #66FFFFFF
11 | #F6F6F7
12 | #fdf6df
13 | #FFFFFF
14 |
15 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | EteSync
3 | host.exp.exponent.SharedPreferences
4 | Something went wrong.
5 | Sorry about that. You can go back to Expo home or try to reload the project.
6 | Sorry about that. Press the reload button to try again.
7 | Unable to load Experience.
8 | Uncaught Error: %s
9 | View error log
10 | Default
11 | Experience notifications
12 | Persistent notifications that provide debug info about open experiences
13 |
14 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
18 |
19 |
22 |
23 |
29 |
30 |
35 |
36 |
47 |
48 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext {
5 | minSdkVersion = 21
6 | targetSdkVersion = 28
7 | compileSdkVersion = 28
8 |
9 | dbFlowVersion = '4.2.4'
10 | buildToolsVersion = '28.0.0'
11 | supportLibVersion = '28.0.0'
12 | kotlinVersion = '1.3.50'
13 | repositoryUrl = "file:${System.env.HOME}/.m2/repository/"
14 | }
15 | repositories {
16 | google()
17 | jcenter()
18 | maven { url 'https://dl.bintray.com/android/android-tools/' }
19 | }
20 | dependencies {
21 | classpath 'com.android.tools.build:gradle:3.5.1'
22 | classpath 'com.google.gms:google-services:3.2.1'
23 | classpath 'de.undercouch:gradle-download-task:3.4.3'
24 |
25 | // https://github.com/awslabs/aws-device-farm-gradle-plugin/releases
26 | classpath 'com.amazonaws:aws-devicefarm-gradle-plugin:1.3'
27 |
28 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
29 | }
30 | }
31 |
32 | allprojects {
33 | repositories {
34 | // For non-detach
35 | maven {
36 | url "$rootDir/maven"
37 | }
38 | // For old expoviews to work
39 | maven {
40 | url "$rootDir/versioned-abis/expoview-abi36_0_0/maven"
41 | }
42 | maven {
43 | url "$rootDir/versioned-abis/expoview-abi33_0_0/maven"
44 | }
45 | maven {
46 | url "$rootDir/versioned-abis/expoview-abi34_0_0/maven"
47 | }
48 | maven {
49 | url "$rootDir/versioned-abis/expoview-abi35_0_0/maven"
50 | }
51 | maven {
52 | url "$rootDir/versioned-abis/maven"
53 | }
54 | // For detach
55 | maven {
56 | url "$rootDir/../node_modules/expokit/maven"
57 | }
58 | maven {
59 | // We use a modified build of com.android.support.test:runner:1.0.1. Explanation in maven-test/README
60 | url "$rootDir/maven-test"
61 | }
62 | google()
63 | jcenter()
64 | maven {
65 | // Local Maven repo containing AARs with JSC built for Android
66 | url "$rootDir/../node_modules/jsc-android/dist"
67 | }
68 | flatDir {
69 | dirs 'libs'
70 | // dirs project(':expoview').file('libs')
71 | }
72 | // https://github.com/google/ExoPlayer/issues/5225#issuecomment-445739013
73 | maven { url 'https://google.bintray.com/exoplayer' }
74 | // Using www.jitpack.io instead of plain jitpack.io due to
75 | // https://github.com/jitpack/jitpack.io/issues/4002
76 | maven { url "https://www.jitpack.io" }
77 |
78 | // Want this last so that we never end up with a stale cache
79 | mavenLocal()
80 | }
81 | }
82 |
83 | task clean(type: Delete) {
84 | delete rootProject.buildDir
85 | }
86 |
--------------------------------------------------------------------------------
/android/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/debug.keystore
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.parallel=true
2 | org.gradle.daemon=true
3 | org.gradle.jvmargs=-Xmx9216M -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
4 | org.gradle.configureondemand=true
5 | org.gradle.internal.repository.initial.backoff=1000
6 | android.useAndroidX=true
7 | android.enableJetifier=true
8 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Sep 19 13:00:59 CEST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include ':react-native-rsa-native'
3 | project(':react-native-rsa-native').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-rsa-native/android')
4 |
5 |
6 | // Import gradle helpers for unimodules.
7 | apply from: '../node_modules/react-native-unimodules/gradle.groovy'
8 |
9 | // Include unimodules.
10 | includeUnimodulesProjects(
11 | )
12 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "EteSync",
4 | "description": "Secure, end-to-end encrypted, and privacy respecting sync for your contacts, calendars and tasks.",
5 | "slug": "etesync-ios",
6 | "privacy": "public",
7 | "sdkVersion": "36.0.0",
8 | "platforms": [
9 | "ios"
10 | ],
11 | "version": "1.6.1",
12 | "githubUrl": "https://github.com/etesync/ios",
13 | "icon": "./assets/icon.png",
14 | "splash": {
15 | "image": "./assets/splash.png",
16 | "resizeMode": "contain",
17 | "backgroundColor": "#fdf6df"
18 | },
19 | "assetBundlePatterns": [
20 | "**/*"
21 | ],
22 | "ios": {
23 | "bundleIdentifier": "com.etesync.ios",
24 | "buildNumber": "1.6.101",
25 | "userInterfaceStyle": "automatic",
26 | "supportsTablet": true,
27 | "infoPlist": {
28 | "NSContactsUsageDescription": "Access to your contacts is needed in order to load and save EteSync contacts.",
29 | "NSCalendarsUsageDescription": "Access to your calendars is needed in order to load and save EteSync calendars.",
30 | "NSRemindersUsageDescription": "Access to your reminders is needed in order to load and save EteSync reminders.",
31 | "UIBackgroundModes": [
32 | "fetch"
33 | ]
34 | },
35 | "config": {
36 | "usesNonExemptEncryption": false
37 | },
38 | "publishBundlePath": "ios/etesync/Supporting/shell-app.bundle",
39 | "publishManifestPath": "ios/etesync/Supporting/shell-app-manifest.json"
40 | },
41 | "isDetached": true,
42 | "detach": {
43 | "iosExpoViewUrl": "https://s3.amazonaws.com/exp-exponent-view-code/ios-v2.14.2-sdk36.0.0-e055f8a6-47bf-455c-9f14-d5a631ea2dd3.tar.gz",
44 | "androidExpoViewUrl": "https://s3.amazonaws.com/exp-exponent-view-code/android-v2.14.0-sdk36.0.0-fb8689e6-fd42-4b18-b01f-ac2ef3bc8681.tar.gz"
45 | },
46 | "scheme": "exp2904f03229f24a3ca9b3fcd8485120ca",
47 | "android": {
48 | "package": "com.etesync.android",
49 | "publishBundlePath": "android/app/src/main/assets/shell-app.bundle",
50 | "publishManifestPath": "android/app/src/main/assets/shell-app-manifest.json"
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/assets/icon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/build-instructions.md:
--------------------------------------------------------------------------------
1 | These are high-level instructions for building the app.
2 |
3 | # Basic build
4 |
5 | 1. Run `yarn` to install the latest version of the deps.
6 | 2. From the `ios` directory run `pod install`
7 | 3. Build from xcode
8 |
9 | # Update deploy version (and include a new bundle)
10 |
11 | Before doing the above build do the following:
12 |
13 | 1. Update the deploy version (the number in the export url) in:
14 | 1. ios/etesync/Supporting/EXShell.{plist,json}
15 | 2. `deploy_dist.sh`
16 | 2. run `yarn export` the same way it's run in `deploy_dist.sh` (you can essentially run the script and just let the deployment itself fail).
17 | 3. Copy over the bundle and the manifest to the right place:
18 | 1. `cp dist/bundles/ios-*.js ios/etesync/Supporting/shell-app.bundle`
19 | 2. `cp dist/ios-index.json ios/etesync/Supporting/shell-app-manifest.json`
20 | 4. Follow the basic build instructions above.
21 |
--------------------------------------------------------------------------------
/deploy_dist.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | SSH_HOST=expo.etesync.com
6 | SSH_PORT=22
7 | SSH_USER=etesync
8 | SSH_TARGET_DIR=sites/expo.etesync.com
9 |
10 | OUTPUTDIR='dist'
11 |
12 | PUBLIC_URL=https://expo.etesync.com
13 | DEPLOY_PATH='release'
14 | # DEPLOY_PATH='test'
15 |
16 | APP_VERSION=5
17 |
18 | yarn lint --max-warnings 0
19 | yarn tsc
20 |
21 | rm -rf "$OUTPUTDIR"
22 | yarn run expo export --dump-sourcemap --public-url ${PUBLIC_URL}/${DEPLOY_PATH}/${APP_VERSION}
23 | rsync -e "ssh -p ${SSH_PORT}" -P --delete --exclude '*android*' -rvc -zz ${OUTPUTDIR}/ ${SSH_USER}@${SSH_HOST}:${SSH_TARGET_DIR}/${DEPLOY_PATH}/${APP_VERSION}
24 |
--------------------------------------------------------------------------------
/etesync.mobileconfig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PayloadVersion
6 | 1
7 |
8 | PayloadUUID
9 | 83f2618b-c486-4c1b-8a6d-9c285d72cf98
10 |
11 | PayloadType
12 | Configuration
13 |
14 | PayloadIdentifier
15 | com.etesync.ios
16 |
17 | PayloadDisplayName
18 | EteSync Accounts
19 |
20 | PayloadOrganization
21 | EteSync
22 |
23 | Label
24 | Account configuration for the EteSync app
25 |
26 | PayloadContent
27 |
28 |
29 | CardDAVAccountDescription
30 | etesync
31 |
32 | CardDAVHostName
33 | 127.0.0.1
34 |
35 | CardDAVUsername
36 | etesync
37 |
38 | CardDAVPassword
39 | etesync
40 |
41 | PayloadDescription
42 | Configures CardDAV account
43 |
44 | PayloadIdentifier
45 | com.etesync.ios.carddav
46 |
47 | PayloadType
48 | com.apple.carddav.account
49 |
50 | PayloadUUID
51 | 83f2618b-c486-4c1b-8a6d-9c285d72cf96
52 |
53 | PayloadVersion
54 | 1
55 |
56 |
57 | CalDAVAccountDescription
58 | etesync
59 |
60 | CalDAVHostName
61 | 127.0.0.1
62 |
63 | CalDAVUsername
64 | etesync
65 |
66 | CalDAVPassword
67 | etesync
68 |
69 | PayloadDescription
70 | Configures CalDAV account
71 |
72 | PayloadIdentifier
73 | com.etesync.ios.caldav
74 |
75 | PayloadType
76 | com.apple.caldav.account
77 |
78 | PayloadUUID
79 | 83f2618b-c486-4c1b-8a6d-9c285d72cf95
80 |
81 | PayloadVersion
82 | 1
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/ios/EteSyncTests/EteSyncTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EteSyncTests.swift
3 | // EteSyncTests
4 | //
5 | // Created by Me Me on 26/12/2019.
6 | // Copyright © 2019 650 Industries, Inc. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class EteSyncTests: XCTestCase {
12 |
13 | override func setUp() {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDown() {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | }
25 |
26 | func testPerformanceExample() {
27 | // This is an example of a performance test case.
28 | self.measure {
29 | // Put the code you want to measure the time of here.
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/ios/EteSyncTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://github.com/CocoaPods/Specs.git'
2 | platform :ios, '10.0'
3 |
4 | target 'etesync' do
5 | pod 'ExpoKit',
6 | :git => "http://github.com/expo/expo.git",
7 | :tag => "ios/2.14.2",
8 | :subspecs => [
9 | "Core"
10 | ],
11 | :inhibit_warnings => true
12 |
13 | # Install unimodules
14 | require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
15 | use_unimodules!(
16 | modules_paths: ['../node_modules'],
17 | exclude: [
18 | 'expo-bluetooth',
19 | 'expo-in-app-purchases',
20 | 'expo-payments-stripe',
21 | ],
22 | )
23 |
24 | # Install React Native and its dependencies
25 | require_relative '../node_modules/react-native/scripts/autolink-ios.rb'
26 | use_react_native!
27 |
28 | pod 'react-native-rsa-native', :path => '../node_modules/react-native-rsa-native'
29 | pod 'react-native-sodium', :path => '../node_modules/react-native-sodium'
30 | pod 'react-native-get-random-values', :path => '../node_modules/react-native-get-random-values'
31 | pod 'MessagePack.swift', '~> 3.0'
32 |
33 | post_install do |installer|
34 | installer.pods_project.main_group.tab_width = '2';
35 | installer.pods_project.main_group.indent_width = '2';
36 |
37 | installer.target_installation_results.pod_target_installation_results
38 | .each do |pod_name, target_installation_result|
39 |
40 | if pod_name == 'ExpoKit'
41 | target_installation_result.native_target.build_configurations.each do |config|
42 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)']
43 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'EX_DETACHED=1'
44 |
45 | # Enable Google Maps support
46 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'HAVE_GOOGLE_MAPS=1'
47 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'HAVE_GOOGLE_MAPS_UTILS=1'
48 |
49 | end
50 | end
51 |
52 |
53 |
54 | target_installation_result.native_target.build_configurations.each do |config|
55 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0'
56 | end
57 |
58 |
59 | # Can't specify this in the React podspec because we need to use those podspecs for detached
60 | # projects which don't reference ExponentCPP.
61 | if pod_name.start_with?('React')
62 | target_installation_result.native_target.build_configurations.each do |config|
63 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0'
64 | config.build_settings['HEADER_SEARCH_PATHS'] ||= ['$(inherited)']
65 | end
66 | end
67 |
68 | # Build React Native with RCT_DEV enabled and RCT_ENABLE_INSPECTOR and
69 | # RCT_ENABLE_PACKAGER_CONNECTION disabled
70 | next unless pod_name.start_with?('React')
71 | target_installation_result.native_target.build_configurations.each do |config|
72 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)']
73 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'RCT_DEV=1'
74 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'RCT_ENABLE_INSPECTOR=0'
75 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'ENABLE_PACKAGER_CONNECTION=0'
76 | end
77 |
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/ios/etesync.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/etesync.xcodeproj/xcshareddata/xcschemes/etesync.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
66 |
72 |
73 |
74 |
75 |
79 |
80 |
81 |
82 |
83 |
84 |
90 |
92 |
98 |
99 |
100 |
101 |
103 |
104 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/ios/etesync.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/etesync.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/etesync/AppDelegate.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-present 650 Industries. All rights reserved.
2 |
3 | #import
4 | #import
5 |
6 | @interface AppDelegate : EXStandaloneAppDelegate
7 |
8 | @end
9 |
--------------------------------------------------------------------------------
/ios/etesync/AppDelegate.m:
--------------------------------------------------------------------------------
1 | // Copyright 2015-present 650 Industries. All rights reserved.
2 |
3 | #import "AppDelegate.h"
4 |
5 | @implementation AppDelegate
6 |
7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
8 | {
9 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
10 | }
11 |
12 | - (void)applicationWillEnterForeground:(UIApplication *)application
13 | {
14 | [super applicationWillEnterForeground:application];
15 | }
16 |
17 | #pragma mark - Background Fetch
18 |
19 | - (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
20 | {
21 | [super application:application performFetchWithCompletionHandler:completionHandler];
22 | }
23 |
24 | #pragma mark - Handling URLs
25 |
26 | - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
27 | {
28 | return [super application:app openURL:url options:options];
29 | }
30 |
31 | - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler
32 | {
33 | return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
34 | }
35 |
36 | #pragma mark - Notifications
37 |
38 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token
39 | {
40 | [super application:application didRegisterForRemoteNotificationsWithDeviceToken:token];
41 | }
42 |
43 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err
44 | {
45 | [super application:application didFailToRegisterForRemoteNotificationsWithError:err];
46 | }
47 |
48 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
49 | {
50 | [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
51 | }
52 |
53 | @end
54 |
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon1024x1024.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon20x20@2x.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon20x20@3x.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon29x29@3x.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon40x40@3x.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon60x60@3x.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon76x76@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon76x76@2x~ipad.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon76x76~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon76x76~ipad.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon83.5x83.5@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Assets.xcassets/AppIcon.appiconset/AppIcon83.5x83.5@2x~ipad.png
--------------------------------------------------------------------------------
/ios/etesync/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "AppIcon20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "AppIcon20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "idiom" : "iphone",
17 | "size" : "29x29",
18 | "scale" : "1x"
19 | },
20 | {
21 | "size" : "29x29",
22 | "idiom" : "iphone",
23 | "filename" : "AppIcon29x29@2x.png",
24 | "scale" : "2x"
25 | },
26 | {
27 | "size" : "29x29",
28 | "idiom" : "iphone",
29 | "filename" : "AppIcon29x29@3x.png",
30 | "scale" : "3x"
31 | },
32 | {
33 | "size" : "40x40",
34 | "idiom" : "iphone",
35 | "filename" : "AppIcon40x40@2x.png",
36 | "scale" : "2x"
37 | },
38 | {
39 | "size" : "40x40",
40 | "idiom" : "iphone",
41 | "filename" : "AppIcon40x40@3x.png",
42 | "scale" : "3x"
43 | },
44 | {
45 | "idiom" : "iphone",
46 | "size" : "57x57",
47 | "scale" : "1x"
48 | },
49 | {
50 | "idiom" : "iphone",
51 | "size" : "57x57",
52 | "scale" : "2x"
53 | },
54 | {
55 | "size" : "60x60",
56 | "idiom" : "iphone",
57 | "filename" : "AppIcon60x60@2x.png",
58 | "scale" : "2x"
59 | },
60 | {
61 | "size" : "60x60",
62 | "idiom" : "iphone",
63 | "filename" : "AppIcon60x60@3x.png",
64 | "scale" : "3x"
65 | },
66 | {
67 | "idiom" : "ipad",
68 | "size" : "20x20",
69 | "scale" : "1x"
70 | },
71 | {
72 | "idiom" : "ipad",
73 | "size" : "20x20",
74 | "scale" : "2x"
75 | },
76 | {
77 | "idiom" : "ipad",
78 | "size" : "29x29",
79 | "scale" : "1x"
80 | },
81 | {
82 | "idiom" : "ipad",
83 | "size" : "29x29",
84 | "scale" : "2x"
85 | },
86 | {
87 | "idiom" : "ipad",
88 | "size" : "40x40",
89 | "scale" : "1x"
90 | },
91 | {
92 | "idiom" : "ipad",
93 | "size" : "40x40",
94 | "scale" : "2x"
95 | },
96 | {
97 | "idiom" : "ipad",
98 | "size" : "50x50",
99 | "scale" : "1x"
100 | },
101 | {
102 | "idiom" : "ipad",
103 | "size" : "50x50",
104 | "scale" : "2x"
105 | },
106 | {
107 | "idiom" : "ipad",
108 | "size" : "72x72",
109 | "scale" : "1x"
110 | },
111 | {
112 | "idiom" : "ipad",
113 | "size" : "72x72",
114 | "scale" : "2x"
115 | },
116 | {
117 | "size" : "76x76",
118 | "idiom" : "ipad",
119 | "filename" : "AppIcon76x76~ipad.png",
120 | "scale" : "1x"
121 | },
122 | {
123 | "size" : "76x76",
124 | "idiom" : "ipad",
125 | "filename" : "AppIcon76x76@2x~ipad.png",
126 | "scale" : "2x"
127 | },
128 | {
129 | "size" : "83.5x83.5",
130 | "idiom" : "ipad",
131 | "filename" : "AppIcon83.5x83.5@2x~ipad.png",
132 | "scale" : "2x"
133 | },
134 | {
135 | "size" : "1024x1024",
136 | "idiom" : "ios-marketing",
137 | "filename" : "AppIcon1024x1024.png",
138 | "scale" : "1x"
139 | }
140 | ],
141 | "info" : {
142 | "version" : 1,
143 | "author" : "xcode"
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/ios/etesync/Supporting/EXSDKVersions.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | detachedNativeVersions
6 |
7 | kernel
8 | 36.0.0
9 | shell
10 | 36.0.0
11 |
12 | sdkVersions
13 |
14 | 36.0.0
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/ios/etesync/Supporting/EXShell.json:
--------------------------------------------------------------------------------
1 | {"isShell":true,"manifestUrl":"https://expo.etesync.com/release/5/ios-index.json","releaseChannel":"default","isManifestVerificationBypassed":true}
2 |
--------------------------------------------------------------------------------
/ios/etesync/Supporting/EXShell.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | isManifestVerificationBypassed
6 |
7 | isShell
8 |
9 | manifestUrl
10 | https://expo.etesync.com/release/5/ios-index.json
11 | releaseChannel
12 | default
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ios/etesync/Supporting/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | EteSync
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | com.etesync.ios
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleLocalizations
16 |
17 | en
18 |
19 | CFBundleName
20 | EteSync
21 | CFBundlePackageType
22 | APPL
23 | CFBundleShortVersionString
24 | $(MARKETING_VERSION)
25 | CFBundleURLTypes
26 |
27 |
28 | CFBundleURLSchemes
29 |
30 | exp2904f03229f24a3ca9b3fcd8485120ca
31 |
32 |
33 |
34 | CFBundleURLName
35 | OAuthRedirect
36 | CFBundleURLSchemes
37 |
38 | com.etesync.ios
39 |
40 |
41 |
42 | CFBundleVersion
43 | $(CURRENT_PROJECT_VERSION)
44 | Fabric
45 |
46 | APIKey
47 | 81130e95ea13cd7ed9a4f455e96214902c721c99
48 | Kits
49 |
50 |
51 | KitInfo
52 |
53 | KitName
54 | Crashlytics
55 |
56 |
57 |
58 | FacebookAdvertiserIDCollectionEnabled
59 |
60 | FacebookAutoInitEnabled
61 |
62 | FacebookAutoLogAppEventsEnabled
63 |
64 | GADApplicationIdentifier
65 | ca-app-pub-3940256099942544~1458002511
66 | GADDelayAppMeasurementInit
67 |
68 | ITSAppUsesNonExemptEncryption
69 |
70 | LSRequiresIPhoneOS
71 |
72 | NSAppTransportSecurity
73 |
74 | NSAllowsArbitraryLoads
75 |
76 |
77 | NSCalendarsUsageDescription
78 | Access to your calendars is needed in order to load and save EteSync calendars.
79 | NSCameraUsageDescription
80 | Allow EteSync to use your camera
81 | NSContactsUsageDescription
82 | Access to your contacts is needed in order to load and save EteSync contacts (including notes).
83 | NSLocationWhenInUseUsageDescription
84 | Allow EteSync to use your location
85 | NSMicrophoneUsageDescription
86 | Allow EteSync to access your microphone
87 | NSMotionUsageDescription
88 | Allow EteSync to access your device's accelerometer
89 | NSPhotoLibraryAddUsageDescription
90 | Give EteSync permission to save photos
91 | NSPhotoLibraryUsageDescription
92 | Give EteSync permission to access your photos
93 | NSRemindersUsageDescription
94 | Access to your reminders is needed in order to load and save EteSync reminders.
95 | UIBackgroundModes
96 |
97 | fetch
98 |
99 | UILaunchStoryboardName
100 | LaunchScreen
101 | UIRequiredDeviceCapabilities
102 |
103 | UIRequiresFullScreen
104 |
105 | UIStatusBarStyle
106 | UIStatusBarStyleLightContent
107 | UISupportedInterfaceOrientations
108 |
109 | UIInterfaceOrientationPortrait
110 | UIInterfaceOrientationLandscapeLeft
111 | UIInterfaceOrientationLandscapeRight
112 | UIInterfaceOrientationPortraitUpsideDown
113 |
114 | UIUserInterfaceStyle
115 | Automatic
116 | UIViewControllerBasedStatusBarAppearance
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/ios/etesync/Supporting/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/ios/etesync/Supporting/launch_background_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Supporting/launch_background_image.png
--------------------------------------------------------------------------------
/ios/etesync/Supporting/launch_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/ios/etesync/Supporting/launch_icon.png
--------------------------------------------------------------------------------
/ios/etesync/Supporting/main.m:
--------------------------------------------------------------------------------
1 | // Copyright © 2016 650 Industries, Inc. All rights reserved.
2 |
3 | #import
4 | #import "AppDelegate.h"
5 |
6 | int main(int argc, char * argv[]) {
7 | @autoreleasepool {
8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ios/etesync/Supporting/sdkVersions.json:
--------------------------------------------------------------------------------
1 | {"sdkVersions":["33.0.0","34.0.0","35.0.0","36.0.0"]}
--------------------------------------------------------------------------------
/ios/etesync/etesync.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.contacts.notes
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/etesync/modules/EteEXCalendar.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-present 650 Industries. All rights reserved.
2 |
3 | #import
4 |
5 | @interface EteEXCalendar : NSObject
6 |
7 | - (EKEvent *)deserializeEvent:(EKEvent *)calendarEvent details:(NSDictionary *)details reject:(RCTPromiseRejectBlock)reject;
8 | - (EKReminder *)deserializeReminder:(EKReminder *)reminder details:(NSDictionary *)details reject:(RCTPromiseRejectBlock)reject;
9 |
10 | @end
11 |
--------------------------------------------------------------------------------
/ios/etesync/modules/EtesyncNativeBridge.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface RCT_EXTERN_MODULE(EteSyncNative, NSObject)
4 |
5 | RCT_EXTERN_METHOD(hashEvent:(NSString *)eventId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
6 | RCT_EXTERN_METHOD(calculateHashesForEvents:(NSString *)calendarId from:(nonnull NSNumber *)from to:(nonnull NSNumber *)to resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
7 | RCT_EXTERN_METHOD(processEventsChanges:(NSString *)containerId changes: (NSArray *)changes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
8 |
9 | RCT_EXTERN_METHOD(hashReminder:(NSString *)reminderId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
10 | RCT_EXTERN_METHOD(calculateHashesForReminders:(NSString *)calendarId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
11 | RCT_EXTERN_METHOD(processRemindersChanges:(NSString *)containerId changes: (NSArray *)changes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
12 |
13 | RCT_EXTERN_METHOD(hashContact:(NSString *)contactId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
14 | RCT_EXTERN_METHOD(calculateHashesForContacts:(NSString *)containerId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
15 | RCT_EXTERN_METHOD(processContactsChanges:(NSString *)containerId groupId:(NSString *)groupId changes: (NSArray *)changes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
16 | RCT_EXTERN_METHOD(deleteContactGroupAndMembers:(NSString *)groupId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
17 | RCT_EXTERN_METHOD(getContainers:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
18 |
19 | RCT_EXTERN_METHOD(beginBackgroundTask:(NSString *)name resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
20 | RCT_EXTERN_METHOD(endBackgroundTask:(nonnull NSNumber *)taskId)
21 |
22 | RCT_EXTERN_METHOD(playground:(NSDictionary *)dictionary)
23 | @end
24 |
--------------------------------------------------------------------------------
/ios/etesync/modules/EtesyncNativeTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EtesyncNativeTest.swift
3 | // etesync
4 | //
5 | // Created by Me Me on 26/12/2019.
6 | // Copyright © 2019 650 Industries, Inc. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | import EventKit
12 |
13 | class EtesyncNativeTest: XCTestCase {
14 |
15 | override func setUp() {
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | }
22 |
23 | func testSha256() {
24 | var sha = Sha256()
25 | sha.update(string: "test")
26 | var hash = sha.finalize()
27 | var lastHash = hash
28 |
29 | sha = Sha256()
30 | sha.update(string: "test")
31 | hash = sha.finalize()
32 | XCTAssertEqual(hash, lastHash)
33 | lastHash = hash
34 |
35 | sha = Sha256()
36 | sha.update(string: "test")
37 | sha.update(number: 1)
38 | hash = sha.finalize()
39 | XCTAssertNotEqual(hash, lastHash)
40 | lastHash = hash
41 |
42 | sha = Sha256()
43 | sha.update(string: "test")
44 | sha.update(number: 1)
45 | sha.update(date: Date())
46 | hash = sha.finalize()
47 | XCTAssertNotEqual(hash, lastHash)
48 | lastHash = hash
49 |
50 | sha = Sha256()
51 | sha.update(string: "test")
52 | sha.update(number: 1)
53 | sha.update(date: Date())
54 | hash = sha.finalize()
55 | XCTAssertNotEqual(hash, lastHash)
56 | lastHash = hash
57 | }
58 |
59 | func testEventHash() {
60 | let calendar = Calendar.current
61 | let now = Date()
62 |
63 | var yearAgoComp = DateComponents()
64 | yearAgoComp.year = -1
65 | let yearAgo = calendar.date(byAdding: yearAgoComp, to: now)
66 |
67 | let store = EKEventStore()
68 | let ev1 = EKEvent(eventStore: store)
69 | ev1.title = "Test title"
70 | ev1.location = "Somewhere"
71 | // ev1.timeZone
72 | // ev1.url
73 | // ev1.notes
74 | ev1.startDate = now
75 | ev1.endDate = now
76 | ev1.isAllDay = false
77 |
78 | let ev1hash = hashEvent(event: ev1)
79 |
80 | let ev2 = EKEvent(eventStore: store)
81 | ev2.title = "Test title"
82 | ev2.location = "Somewhere"
83 | ev2.startDate = yearAgo
84 | ev2.endDate = yearAgo
85 | ev2.isAllDay = false
86 |
87 | let ev2hash = hashEvent(event: ev2)
88 |
89 | XCTAssertNotEqual(ev1hash, ev2hash)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/ios/etesync/modules/etesync-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import
6 | #import
7 | #import
8 | #import "EteEXCalendar.h"
9 |
--------------------------------------------------------------------------------
/license-gen.js:
--------------------------------------------------------------------------------
1 | // Running: node license-gen.js > licenses.json
2 |
3 | const checker = require('license-checker');
4 |
5 | const packageJson = require('./package.json');
6 |
7 | const dependencies = packageJson.dependencies;
8 | const devDependencies = packageJson.devDependencies;
9 |
10 | function filterProperties(pkg) {
11 | const allowed = ['licenses', 'repository', 'url', 'publisher'];
12 | Object.keys(pkg).forEach((key) => {
13 | if (!allowed.includes(key)) {
14 | delete pkg[key];
15 | }
16 | });
17 |
18 | return pkg;
19 | }
20 |
21 | checker.init({
22 | start: '.',
23 | }, function (err, packages) {
24 | const output = {
25 | dependencies: {},
26 | devDependencies: {},
27 | };
28 |
29 | if (err) {
30 | console.error(err);
31 | process.exit(1);
32 | } else {
33 | Object.keys(packages).forEach((pkg) => {
34 | const pkgName = pkg.replace(/@[^@]+$/, '');
35 | if (dependencies[pkgName]) {
36 | output.dependencies[pkgName] = filterProperties(packages[pkg]);
37 | }
38 | if (devDependencies[pkgName]) {
39 | output.devDependencies[pkgName] = filterProperties(packages[pkg]);
40 | }
41 | });
42 |
43 | console.log(JSON.stringify(output, null, 2));
44 | }
45 | });
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "test": "jest --watch",
8 | "check-types": "tsc",
9 | "lint": "eslint --ext js,jsx,ts,tsx src",
10 | "eject": "expo eject"
11 | },
12 | "dependencies": {
13 | "@expo/vector-icons": "^10.0.0",
14 | "@react-native-community/masked-view": "^0.1.6",
15 | "@react-native-community/netinfo": "4.6.0",
16 | "@react-navigation/drawer": "^5.0.5",
17 | "@react-navigation/native": "^5.0.5",
18 | "@react-navigation/stack": "^5.0.5",
19 | "assert": "^1.4.1",
20 | "buffer": "^5.2.1",
21 | "color": "^3.1.2",
22 | "constants": "^0.0.2",
23 | "crypto": "git+https://github.com/etesync/expo-crypto",
24 | "etebase": "^0.30.0",
25 | "etesync": "^0.3.1",
26 | "expo": "^36.0.0",
27 | "expo-background-fetch": "~8.0.0",
28 | "expo-calendar": "~8.0.0",
29 | "expo-contacts": "~8.0.0",
30 | "expo-random": "~8.0.0",
31 | "expo-secure-store": "^8.0.0",
32 | "expo-task-manager": "~8.0.0",
33 | "expokit": "36.0.0",
34 | "ical.js": "^1.3.0",
35 | "immutable": "^4.0.0-rc.12",
36 | "moment": "^2.24.0",
37 | "react": "16.9.0",
38 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.1.tar.gz",
39 | "react-native-appearance": "~0.3.1",
40 | "react-native-etebase": "^0.1.5",
41 | "react-native-gesture-handler": "^1.6.0",
42 | "react-native-get-random-values": "1.4.0",
43 | "react-native-keyboard-aware-scroll-view": "^0.9.1",
44 | "react-native-markdown-display": "^6.1.6",
45 | "react-native-paper": "^3.9.0",
46 | "react-native-reanimated": "^1.7.0",
47 | "react-native-rsa-native": "^1.1.3",
48 | "react-native-safe-area-context": "^0.7.3",
49 | "react-native-screens": "^2.0.0-beta.4",
50 | "react-native-sodium": "^0.3.8",
51 | "react-native-unimodules": "^0.7.0",
52 | "react-native-vector-icons": "^6.6.0",
53 | "react-native-webview": "7.4.3",
54 | "react-redux": "^7.1.0",
55 | "redux": "^4.0.1",
56 | "redux-actions": "^2.6.5",
57 | "redux-logger": "^3.0.6",
58 | "redux-persist": "^6.0.0",
59 | "redux-persist-expo-securestore": "^2.0.0",
60 | "redux-thunk": "^2.3.0"
61 | },
62 | "devDependencies": {
63 | "@types/color": "^3.0.0",
64 | "@types/jest": "^24.0.5",
65 | "@types/node": "^11.9.4",
66 | "@types/node-rsa": "^1.0.0",
67 | "@types/react": "^16.8.23",
68 | "@types/react-native": "^0.57.65",
69 | "@types/react-native-vector-icons": "^6.4.6",
70 | "@types/react-redux": "^7.1.1",
71 | "@types/redux": "^3.6.0",
72 | "@types/redux-actions": "^2.3.2",
73 | "@types/redux-logger": "^3.0.7",
74 | "@types/sjcl": "^1.0.28",
75 | "@types/urijs": "^1.15.38",
76 | "@types/uuid": "^3.4.4",
77 | "@typescript-eslint/eslint-plugin": "^2.6.1",
78 | "@typescript-eslint/parser": "^2.6.1",
79 | "@typescript-eslint/typescript-estree": "^2.6.1",
80 | "babel-preset-expo": "^7.0.0",
81 | "eslint": "^6.6.0",
82 | "eslint-plugin-react": "^7.16.0",
83 | "jest-expo": "^36.0.0",
84 | "license-checker": "^25.0.1",
85 | "react-test-renderer": "^16.8.6",
86 | "typescript": "^3.9.2"
87 | },
88 | "jest": {
89 | "preset": "jest-expo"
90 | },
91 | "private": true
92 | }
93 |
--------------------------------------------------------------------------------
/shim.js:
--------------------------------------------------------------------------------
1 | if (typeof __dirname === 'undefined') global.__dirname = '/'
2 | if (typeof __filename === 'undefined') global.__filename = ''
3 | if (typeof process === 'undefined') {
4 | global.process = require('process')
5 | } else {
6 | const bProcess = require('process')
7 | for (var p in bProcess) {
8 | if (!(p in process)) {
9 | process[p] = bProcess[p]
10 | }
11 | }
12 | }
13 |
14 | process.browser = false
15 | if (typeof Buffer === 'undefined') global.Buffer = require('buffer').Buffer
16 |
17 | // global.location = global.location || { port: 80 }
18 | const isDev = typeof __DEV__ === 'boolean' && __DEV__
19 | process.env['NODE_ENV'] = isDev ? 'development' : 'production'
20 | if (typeof localStorage !== 'undefined') {
21 | localStorage.debug = isDev ? '*' : ''
22 | }
23 |
24 | // If using the crypto shim, uncomment the following line to ensure
25 | // crypto is loaded first, so it can populate global.crypto
26 | // require('crypto')
27 |
--------------------------------------------------------------------------------
/src/AboutScreen.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { Linking, FlatList } from "react-native";
6 | import { Text, List, TouchableRipple, useTheme } from "react-native-paper";
7 |
8 | import { Title } from "./widgets/Typography";
9 | import Container from "./widgets/Container";
10 | import Markdown from "./widgets/Markdown";
11 |
12 | import { expo } from "../app.json";
13 | import * as C from "./constants";
14 |
15 | import * as licenses from "../licenses.json";
16 |
17 | function generateRenderLicenseItem(pkgLicenses: any) {
18 | return function renderLicense(param: { item: string }) {
19 | const pkgName = param.item;
20 | const pkg = pkgLicenses[pkgName]!;
21 | const { publisher, repository, url } = pkg;
22 | const description = (publisher && (publisher.toLowerCase() !== pkgName.toLowerCase())) ? `${pkg.licenses} by ${publisher}` : pkg.licenses;
23 | const link = repository ?? url;
24 | return (
25 | ()}
30 | onPress={link && (() => { Linking.openURL(link) })}
31 | />
32 | );
33 | };
34 | }
35 |
36 | const markdownContent = `
37 | This app is made possible with financial support from [NLnet Foundation](https://nlnet.nl/), courtesy of [NGI0 Discovery](https://nlnet.nl/discovery) and the [European Commission](https://ec.europa.eu) [DG CNECT](https://ec.europa.eu/info/departments/communications-networks-content-and-technology_en)'s [Next Generation Internet](https://ngi.eu) programme.
38 | `;
39 |
40 | export default function AboutScreen() {
41 | const theme = useTheme();
42 |
43 | return (
44 | (
47 |
48 | {C.appName} {expo.version}
49 | { Linking.openURL(C.homePage) }}>
50 | {C.homePage}
51 |
52 |
53 |
54 | Open Source Licenses
55 |
56 | )}
57 | data={Object.keys(licenses.dependencies)}
58 | keyExtractor={(item) => item}
59 | renderItem={generateRenderLicenseItem(licenses.dependencies)}
60 | />
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { StatusBar } from "react-native";
6 | import { DarkTheme, DefaultTheme, Provider as PaperProvider, Theme, Colors } from "react-native-paper";
7 |
8 | import { AppearanceProvider, useColorScheme } from "react-native-appearance";
9 |
10 | import { NavigationContainer } from "@react-navigation/native";
11 | import { createDrawerNavigator } from "@react-navigation/drawer";
12 | import RootNavigator from "./RootNavigator";
13 |
14 | import ErrorBoundary from "./ErrorBoundary";
15 | import Drawer from "./Drawer";
16 | import SettingsGate from "./SettingsGate";
17 |
18 | import "react-native-gesture-handler";
19 | import { enableScreens } from "react-native-screens";
20 | enableScreens();
21 |
22 | const DrawerNavigation = createDrawerNavigator();
23 |
24 | function InnerApp() {
25 | const colorScheme = useColorScheme();
26 |
27 | const baseTheme = (colorScheme === "dark") ? DarkTheme : DefaultTheme;
28 |
29 | const theme: Theme = {
30 | ...baseTheme,
31 | mode: "exact",
32 | colors: {
33 | ...baseTheme.colors,
34 | primary: Colors.amber500,
35 | accent: Colors.lightBlueA700, // Not the real etesync theme but better for accessibility
36 | },
37 | };
38 |
39 | return (
40 |
41 |
42 |
43 |
44 | }>
45 |
46 |
47 |
48 |
49 |
50 |
51 | );
52 | }
53 |
54 | class App extends React.Component {
55 | public render() {
56 | return (
57 |
58 |
59 |
60 |
61 | );
62 | }
63 | }
64 |
65 | export default App;
66 |
--------------------------------------------------------------------------------
/src/CollectionItemContact.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import moment from "moment";
6 |
7 | import { Clipboard, Linking } from "react-native";
8 | import { Text, List, Divider } from "react-native-paper";
9 |
10 | import { DecryptedCollection, DecryptedItem } from "./store";
11 |
12 | import Container from "./widgets/Container";
13 |
14 | import { ContactType } from "./pim-types";
15 |
16 | import JournalItemHeader from "./JournalItemHeader";
17 |
18 | interface PropsType {
19 | collection: DecryptedCollection;
20 | item: DecryptedItem;
21 | }
22 |
23 | export default React.memo(function CollectionItemContact(props: PropsType) {
24 | const entry = props.item;
25 | const contact = ContactType.parse(entry.content);
26 |
27 | const revProp = contact.comp.getFirstProperty("rev");
28 | const lastModified = (revProp) ? moment(revProp.getFirstValue().toJSDate()).format("LLLL") : undefined;
29 |
30 | const lists = [];
31 |
32 | function getAllType(
33 | propName: string,
34 | leftIcon: string,
35 | valueToHref?: (value: string, type: string) => string,
36 | primaryTransform?: (value: string, type: string) => string,
37 | secondaryTransform?: (value: string, type: string) => string) {
38 |
39 | return contact.comp.getAllProperties(propName).map((prop, idx) => {
40 | const type = prop.toJSON()[1].type;
41 | const values = prop.getValues().map((val) => {
42 | const primaryText = primaryTransform ? primaryTransform(val, type) : val;
43 |
44 | const href = valueToHref?.(val, type);
45 | const onPress = (href && Linking.canOpenURL(href)) ? (() => { Linking.openURL(href) }) : undefined;
46 |
47 | return (
48 | Clipboard.setString(primaryText)}
53 | left={(props) => }
54 | description={secondaryTransform ? secondaryTransform(val, type) : type}
55 | />
56 | );
57 | });
58 | return values;
59 | });
60 | }
61 |
62 | lists.push(getAllType(
63 | "tel",
64 | "phone",
65 | (x) => ("tel:" + x)
66 | ));
67 |
68 | lists.push(getAllType(
69 | "email",
70 | "email",
71 | (x) => ("mailto:" + x)
72 | ));
73 |
74 | lists.push(getAllType(
75 | "impp",
76 | "chat",
77 | (x) => x,
78 | (x) => (x.substring(x.indexOf(":") + 1)),
79 | (x) => (x.substring(0, x.indexOf(":")))
80 | ));
81 |
82 | lists.push(getAllType(
83 | "adr",
84 | "home"
85 | ));
86 |
87 | lists.push(getAllType(
88 | "bday",
89 | "calendar",
90 | undefined,
91 | ((x: any) => (x.toJSDate) ? moment(x.toJSDate()).format("dddd, LL") : x),
92 | () => "Birthday"
93 | ));
94 |
95 | lists.push(getAllType(
96 | "anniversary",
97 | "calendar",
98 | undefined,
99 | ((x: any) => (x.toJSDate) ? moment(x.toJSDate()).format("dddd, LL") : x),
100 | () => "Anniversary"
101 | ));
102 |
103 | const skips = ["tel", "email", "impp", "adr", "bday", "anniversary", "rev",
104 | "prodid", "uid", "fn", "n", "version", "photo"];
105 | const theRest = contact.comp.getAllProperties().filter((prop) => (
106 | skips.indexOf(prop.name) === -1
107 | )).map((prop, idx) => {
108 | const values = prop.getValues().map((_val) => {
109 | const val = (_val instanceof String) ? _val : _val.toString();
110 | return (
111 | Clipboard.setString(val)}
115 | description={prop.name}
116 | />
117 | );
118 | });
119 | return values;
120 | });
121 |
122 | function listIfNotEmpty(items: JSX.Element[][]) {
123 | if (items.length > 0) {
124 | return (
125 |
126 | {items}
127 |
128 |
129 | );
130 | } else {
131 | return undefined;
132 | }
133 | }
134 |
135 | return (
136 | <>
137 |
138 | {lastModified && (
139 | Modified: {lastModified}
140 | )}
141 |
142 |
143 | {lists.map((list, idx) => (
144 |
145 | {listIfNotEmpty(list)}
146 |
147 | ))}
148 | {theRest}
149 |
150 | >
151 | );
152 | });
153 |
--------------------------------------------------------------------------------
/src/CollectionItemEvent.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 |
6 | import Color from "color";
7 |
8 | import { Text } from "react-native-paper";
9 |
10 | import { DecryptedCollection, DecryptedItem } from "./store";
11 |
12 | import Container from "./widgets/Container";
13 | import Small from "./widgets/Small";
14 |
15 | import { EventType } from "./pim-types";
16 | import { formatDateRange, formatOurTimezoneOffset } from "./helpers";
17 |
18 | import JournalItemHeader from "./JournalItemHeader";
19 |
20 | interface PropsType {
21 | collection: DecryptedCollection;
22 | item: DecryptedItem;
23 | }
24 |
25 | export default React.memo(function CollectionItemEvent(props: PropsType) {
26 | const entry = props.item;
27 | const event = EventType.parse(entry.content);
28 |
29 | const timezone = event.timezone;
30 |
31 | const backgroundColor = props.collection.meta.color;
32 | const foregroundColor = Color(backgroundColor).isLight() ? "black" : "white";
33 |
34 | return (
35 | <>
36 |
37 | {formatDateRange(event.startDate, event.endDate)} {timezone && ({formatOurTimezoneOffset()})}
38 | {event.location}
39 |
40 |
41 | {event.description}
42 | {(event.attendees.length > 0) && (
43 | Attendees: {event.attendees.map((x) => (x.getFirstValue())).join(", ")})}
44 |
45 | >
46 | );
47 | });
48 |
--------------------------------------------------------------------------------
/src/CollectionItemScreen.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 |
6 | import { useSelector } from "react-redux";
7 | import { StyleSheet } from "react-native";
8 | import { Text, FAB } from "react-native-paper";
9 | import { RouteProp } from "@react-navigation/native";
10 |
11 | import { useSyncGateEb } from "./SyncGate";
12 | import { StoreState } from "./store";
13 |
14 | import ScrollView from "./widgets/ScrollView";
15 | import Container from "./widgets/Container";
16 |
17 | import CollectionItemContact from "./CollectionItemContact";
18 | import CollectionItemEvent from "./CollectionItemEvent";
19 | import CollectionItemTask from "./CollectionItemTask";
20 |
21 | type RootStackParamList = {
22 | CollectionItemScreen: {
23 | colUid: string;
24 | itemUid: string;
25 | };
26 | };
27 |
28 | interface PropsType {
29 | route: RouteProp;
30 | }
31 |
32 | export default function CollectionItemScreen(props: PropsType) {
33 | const [showRaw, setShowRaw] = React.useState(false);
34 | const syncGate = useSyncGateEb();
35 | const decryptedCollections = useSelector((state: StoreState) => state.cache2.decryptedCollections);
36 | const decryptedItems = useSelector((state: StoreState) => state.cache2.decryptedItems);
37 |
38 | if (syncGate) {
39 | return syncGate;
40 | }
41 |
42 | const { colUid, itemUid } = props.route.params;
43 | const collection = decryptedCollections.get(colUid)!;
44 | const items = decryptedItems.get(colUid)!;
45 |
46 | const item = items.get(itemUid)!;
47 |
48 | let content;
49 | let fabContentIcon = "";
50 | switch (collection.collectionType) {
51 | case "etebase.vcard":
52 | content = ;
53 | fabContentIcon = "account-card-details";
54 | break;
55 | case "etebase.vevent":
56 | content = ;
57 | fabContentIcon = "calendar";
58 | break;
59 | case "etebase.vtodo":
60 | content = ;
61 | fabContentIcon = "format-list-checkbox";
62 | break;
63 | }
64 |
65 | return (
66 | <>
67 |
68 | {showRaw ? (
69 |
70 | {item.content}
71 |
72 | ) : (
73 | content
74 | )}
75 |
76 | setShowRaw(!showRaw)}
82 | />
83 | >
84 | );
85 | }
86 |
87 | const styles = StyleSheet.create({
88 | fab: {
89 | position: "absolute",
90 | margin: 16,
91 | right: 0,
92 | bottom: 0,
93 | },
94 | });
95 |
--------------------------------------------------------------------------------
/src/CollectionItemTask.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 |
6 | import Color from "color";
7 |
8 | import { Text } from "react-native-paper";
9 |
10 | import { DecryptedCollection, DecryptedItem } from "./store";
11 |
12 | import Container from "./widgets/Container";
13 | import Small from "./widgets/Small";
14 |
15 | import { TaskType } from "./pim-types";
16 | import { formatDate, formatOurTimezoneOffset } from "./helpers";
17 |
18 | import JournalItemHeader from "./JournalItemHeader";
19 |
20 | interface PropsType {
21 | collection: DecryptedCollection;
22 | item: DecryptedItem;
23 | }
24 |
25 | export default React.memo(function CollectionItemTask(props: PropsType) {
26 | const entry = props.item;
27 | const task = TaskType.parse(entry.content);
28 |
29 | const timezone = task.timezone;
30 |
31 | const backgroundColor = props.collection.meta.color;
32 | const foregroundColor = Color(backgroundColor).isLight() ? "black" : "white";
33 |
34 | return (
35 | <>
36 |
37 | {task.startDate &&
38 | Start: {formatDate(task.startDate)} {timezone && ({formatOurTimezoneOffset()})}
39 | }
40 | {task.dueDate &&
41 | Due: {formatDate(task.dueDate)} {timezone && ({formatOurTimezoneOffset()})}
42 | }
43 | {task.location}
44 |
45 |
46 | {task.description}
47 | {(task.attendees.length > 0) && (
48 | Attendees: {task.attendees.map((x) => (x.getFirstValue())).join(", ")})}
49 |
50 | >
51 | );
52 | });
53 |
--------------------------------------------------------------------------------
/src/CollectionMemberAddDialog.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2017 EteSync Authors
2 | // SPDX-License-Identifier: AGPL-3.0-only
3 |
4 | import * as React from "react";
5 |
6 | import * as Etebase from "etebase";
7 |
8 | import { Paragraph } from "react-native-paper";
9 |
10 | import { useCredentials } from "./credentials";
11 |
12 | import TextInput from "./widgets/TextInput";
13 | import Checkbox from "./widgets/Checkbox";
14 | import PrettyFingerprint from "./widgets/PrettyFingerprintEb";
15 | import LoadingIndicator from "./widgets/LoadingIndicator";
16 | import ConfirmationDialog from "./widgets/ConfirmationDialog";
17 |
18 |
19 | interface PropsType {
20 | onOk: (username: string, publicKey: Uint8Array, accessLevel: Etebase.CollectionAccessLevel) => void;
21 | onClose: () => void;
22 | }
23 |
24 | export default function CollectionMemberAddDialog(props: PropsType) {
25 | const etebase = useCredentials()!;
26 | const [publicKey, setPublicKey] = React.useState();
27 | const [readOnly, setReadOnly] = React.useState(false);
28 | const [userChosen, setUserChosen] = React.useState(false);
29 | const [username, setUsername] = React.useState("");
30 | const [error, setError] = React.useState();
31 |
32 | async function onAddRequest() {
33 | setUserChosen(true);
34 | const inviteMgr = etebase.getInvitationManager();
35 | try {
36 | const userProfile = await inviteMgr.fetchUserProfile(username);
37 | setPublicKey(userProfile.pubkey);
38 | } catch (e) {
39 | setError(e);
40 | }
41 | }
42 |
43 | function onOk() {
44 | props.onOk(username, publicKey!, readOnly ? Etebase.CollectionAccessLevel.ReadOnly : Etebase.CollectionAccessLevel.ReadWrite);
45 | }
46 |
47 | const { onClose } = props;
48 |
49 | if (error) {
50 | return (
51 | <>
52 |
59 |
60 | User ({username}) not found. Have they setup their encryption password from one of the apps?
61 |
62 |
63 | >
64 | );
65 | }
66 |
67 | if (publicKey) {
68 | return (
69 | <>
70 |
77 |
78 | Verify {username}'s security fingerprint to ensure the encryption is secure.
79 |
80 |
81 |
82 | >
83 | );
84 | } else {
85 | return (
86 | <>
87 |
94 | {userChosen ?
95 |
96 | :
97 | <>
98 |
108 | { setReadOnly(!readOnly) }}
112 | />
113 | >
114 | }
115 |
116 | >
117 | );
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/DebugLogsScreen.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { View, Clipboard } from "react-native";
6 | import { Button, Text } from "react-native-paper";
7 |
8 | import ScrollView from "./widgets/ScrollView";
9 | import Container from "./widgets/Container";
10 | import { getLogs, clearLogs } from "./logging";
11 |
12 | export default function DebugLogsScreen() {
13 | const [logs, setLogs] = React.useState();
14 |
15 | React.useEffect(() => {
16 | getLogs().then((value) => setLogs(value.join("\n")));
17 | }, []);
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {(logs) ? logs : "No logs found"}
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/src/EteSyncNative.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import { NativeModules } from "react-native";
5 | import * as Contacts from "expo-contacts";
6 | import { NativeContact, NativeEvent, NativeTask } from "./sync/helpers";
7 |
8 | export type HashesForItem = [string, string, string | undefined];
9 |
10 | export type HashDictionary = { [key: string]: HashesForItem };
11 |
12 | export enum BatchAction {
13 | Add = 1,
14 | Change = 2,
15 | Delete = 3
16 | }
17 |
18 | interface EteSyncNativeModule {
19 | hashEvent(eventId: string): Promise;
20 | calculateHashesForEvents(calendarId: string, from: number, to: number): Promise;
21 | processEventsChanges(containerId: string, events: ([BatchAction, NativeEvent])[]): Promise;
22 | hashReminder(eventId: string): Promise;
23 | calculateHashesForReminders(calendarId: string): Promise;
24 | processRemindersChanges(containerId: string, reminders: ([BatchAction, NativeTask])[]): Promise;
25 | hashContact(contactId: string): Promise;
26 | calculateHashesForContacts(containerId: string): Promise;
27 | deleteContactGroupAndMembers(groupId: string): Promise;
28 | getContainers(): Promise<(Contacts.Container & { default: boolean })[]>;
29 | processContactsChanges(containerId: string, groupId: string | null, contacts: ([BatchAction, NativeContact])[]): Promise;
30 |
31 | beginBackgroundTask(name: string): Promise;
32 | endBackgroundTask(taskId: number): void;
33 |
34 | playground(param: {}): void;
35 | }
36 |
37 | const EteSyncNative = NativeModules.EteSyncNative as EteSyncNativeModule;
38 |
39 | export function calculateHashesForEvents(calendarId: string, from: Date, to: Date): Promise {
40 | return EteSyncNative.calculateHashesForEvents(calendarId, from.getTime() / 1000, to.getTime() / 1000);
41 | }
42 |
43 | export const hashEvent = EteSyncNative.hashEvent;
44 |
45 | export function calculateHashesForReminders(calendarId: string): Promise {
46 | return EteSyncNative.calculateHashesForReminders(calendarId);
47 | }
48 | export const processEventsChanges = EteSyncNative.processEventsChanges;
49 |
50 | export const hashReminder = EteSyncNative.hashReminder;
51 |
52 | export function calculateHashesForContacts(containerId: string): Promise {
53 | return EteSyncNative.calculateHashesForContacts(containerId);
54 | }
55 | export const processRemindersChanges = EteSyncNative.processRemindersChanges;
56 |
57 | export const hashContact = EteSyncNative.hashContact;
58 | export const deleteContactGroupAndMembers = EteSyncNative.deleteContactGroupAndMembers;
59 | export const getContainers = EteSyncNative.getContainers;
60 | export const processContactsChanges = EteSyncNative.processContactsChanges;
61 |
62 | export const { beginBackgroundTask, endBackgroundTask } = EteSyncNative;
63 |
64 |
65 | export const playground = EteSyncNative.playground;
66 |
--------------------------------------------------------------------------------
/src/HomeScreen.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { Appbar } from "react-native-paper";
7 | import { useNavigation } from "@react-navigation/native";
8 |
9 | import { SyncManager } from "./sync/SyncManager";
10 |
11 | import JournalListScreen from "./components/JournalListScreenEb";
12 | import { usePermissions } from "./Permissions";
13 |
14 | import { StoreState } from "./store";
15 | import { performSync } from "./store/actions";
16 |
17 | import { useCredentials } from "./credentials";
18 | import { registerSyncTask } from "./sync/SyncManager";
19 |
20 |
21 | export default React.memo(function HomeScreen() {
22 | const etebase = useCredentials()!;
23 | const dispatch = useDispatch();
24 | const navigation = useNavigation();
25 | const syncCount = useSelector((state: StoreState) => state.syncCount);
26 | const permissionsStatus = usePermissions();
27 |
28 | React.useEffect(() => {
29 | if (etebase && !permissionsStatus) {
30 | registerSyncTask(etebase.user.username);
31 | }
32 | }, [etebase, !permissionsStatus]);
33 |
34 | function refresh() {
35 | const syncManager = SyncManager.getManager(etebase);
36 | dispatch(performSync(syncManager.sync()));
37 | }
38 |
39 | navigation.setOptions({
40 | headerRight: () => (
41 | 0} onPress={refresh} />
42 | ),
43 | });
44 |
45 | if (permissionsStatus) {
46 | return permissionsStatus;
47 | }
48 |
49 | return (
50 |
51 | );
52 | });
53 |
--------------------------------------------------------------------------------
/src/InvitationsScreen.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import * as Etebase from "etebase";
6 | import { List, Paragraph, IconButton } from "react-native-paper";
7 |
8 | import { useSyncGateEb } from "./SyncGate";
9 | import { useCredentials } from "./credentials";
10 |
11 | import ScrollView from "./widgets/ScrollView";
12 | import Container from "./widgets/Container";
13 | import LoadingIndicator from "./widgets/LoadingIndicator";
14 | import ConfirmationDialog from "./widgets/ConfirmationDialog";
15 | import PrettyFingerprint from "./widgets/PrettyFingerprintEb";
16 |
17 |
18 | async function loadInvitations(etebase: Etebase.Account) {
19 | const ret: Etebase.SignedInvitation[] = [];
20 | const invitationManager = etebase.getInvitationManager();
21 |
22 | let iterator: string | null = null;
23 | let done = false;
24 | while (!done) {
25 | const invitations = await invitationManager.listIncoming({ iterator, limit: 30 });
26 | iterator = invitations.iterator as string;
27 | done = invitations.done;
28 |
29 | ret.push(...invitations.data);
30 | }
31 |
32 | return ret;
33 | }
34 |
35 | export default function InvitationsScreen() {
36 | const [invitations, setInvitations] = React.useState();
37 | const [chosenInvitation, setChosenInvitation] = React.useState();
38 | const etebase = useCredentials()!;
39 | const syncGate = useSyncGateEb();
40 |
41 | React.useEffect(() => {
42 | loadInvitations(etebase).then(setInvitations);
43 | }, [etebase]);
44 |
45 | function removeInvitation(invite: Etebase.SignedInvitation) {
46 | setInvitations(invitations?.filter((x) => x.uid !== invite.uid));
47 | }
48 |
49 | async function reject(invite: Etebase.SignedInvitation) {
50 | const invitationManager = etebase.getInvitationManager();
51 | await invitationManager.reject(invite);
52 | removeInvitation(invite);
53 | }
54 |
55 | async function accept(invite: Etebase.SignedInvitation) {
56 | const invitationManager = etebase.getInvitationManager();
57 | await invitationManager.accept(invite);
58 | setChosenInvitation(undefined);
59 | removeInvitation(invite);
60 | }
61 |
62 | if (syncGate) {
63 | return syncGate;
64 | }
65 |
66 | return (
67 |
68 |
69 | {invitations ?
70 | <>
71 | {(invitations.length > 0 ?
72 | invitations.map((invite) => (
73 | (
77 | <>
78 | { reject(invite) }} />
79 | { setChosenInvitation(invite) }} />
80 | >
81 | )}
82 | />
83 | ))
84 | :
85 |
88 | )}
89 | >
90 | :
91 |
92 | }
93 |
94 | {chosenInvitation && (
95 | accept(chosenInvitation)}
100 | onCancel={() => setChosenInvitation(undefined)}
101 | >
102 |
103 | Please verify the inviter's security fingerprint to ensure the invitation is secure:
104 |
105 |
106 |
107 | )}
108 |
109 | );
110 | }
111 |
--------------------------------------------------------------------------------
/src/JournalItemContact.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import moment from "moment";
6 | import * as EteSync from "etesync";
7 |
8 | import { Clipboard, Linking } from "react-native";
9 | import { Text, List, Divider } from "react-native-paper";
10 |
11 | import { SyncInfoItem } from "./store";
12 |
13 | import Container from "./widgets/Container";
14 |
15 | import { ContactType } from "./pim-types";
16 |
17 | import JournalItemHeader from "./JournalItemHeader";
18 |
19 | interface PropsType {
20 | collection: EteSync.CollectionInfo;
21 | entry: SyncInfoItem;
22 | }
23 |
24 | export default React.memo(function JournalItemContact(props: PropsType) {
25 | const entry = props.entry;
26 | const contact = ContactType.parse(entry.content);
27 |
28 | const revProp = contact.comp.getFirstProperty("rev");
29 | const lastModified = (revProp) ? moment(revProp.getFirstValue().toJSDate()).format("LLLL") : undefined;
30 |
31 | const lists = [];
32 |
33 | function getAllType(
34 | propName: string,
35 | leftIcon: string,
36 | valueToHref?: (value: string, type: string) => string,
37 | primaryTransform?: (value: string, type: string) => string,
38 | secondaryTransform?: (value: string, type: string) => string) {
39 |
40 | return contact.comp.getAllProperties(propName).map((prop, idx) => {
41 | const type = prop.toJSON()[1].type;
42 | const values = prop.getValues().map((val) => {
43 | const primaryText = primaryTransform ? primaryTransform(val, type) : val;
44 |
45 | const href = valueToHref?.(val, type);
46 | const onPress = (href && Linking.canOpenURL(href)) ? (() => { Linking.openURL(href) }) : undefined;
47 |
48 | return (
49 | Clipboard.setString(primaryText)}
54 | left={(props) => }
55 | description={secondaryTransform ? secondaryTransform(val, type) : type}
56 | />
57 | );
58 | });
59 | return values;
60 | });
61 | }
62 |
63 | lists.push(getAllType(
64 | "tel",
65 | "phone",
66 | (x) => ("tel:" + x)
67 | ));
68 |
69 | lists.push(getAllType(
70 | "email",
71 | "email",
72 | (x) => ("mailto:" + x)
73 | ));
74 |
75 | lists.push(getAllType(
76 | "impp",
77 | "chat",
78 | (x) => x,
79 | (x) => (x.substring(x.indexOf(":") + 1)),
80 | (x) => (x.substring(0, x.indexOf(":")))
81 | ));
82 |
83 | lists.push(getAllType(
84 | "adr",
85 | "home"
86 | ));
87 |
88 | lists.push(getAllType(
89 | "bday",
90 | "calendar",
91 | undefined,
92 | ((x: any) => (x.toJSDate) ? moment(x.toJSDate()).format("dddd, LL") : x),
93 | () => "Birthday"
94 | ));
95 |
96 | lists.push(getAllType(
97 | "anniversary",
98 | "calendar",
99 | undefined,
100 | ((x: any) => (x.toJSDate) ? moment(x.toJSDate()).format("dddd, LL") : x),
101 | () => "Anniversary"
102 | ));
103 |
104 | const skips = ["tel", "email", "impp", "adr", "bday", "anniversary", "rev",
105 | "prodid", "uid", "fn", "n", "version", "photo"];
106 | const theRest = contact.comp.getAllProperties().filter((prop) => (
107 | skips.indexOf(prop.name) === -1
108 | )).map((prop, idx) => {
109 | const values = prop.getValues().map((_val) => {
110 | const val = (_val instanceof String) ? _val : _val.toString();
111 | return (
112 | Clipboard.setString(val)}
116 | description={prop.name}
117 | />
118 | );
119 | });
120 | return values;
121 | });
122 |
123 | function listIfNotEmpty(items: JSX.Element[][]) {
124 | if (items.length > 0) {
125 | return (
126 |
127 | {items}
128 |
129 |
130 | );
131 | } else {
132 | return undefined;
133 | }
134 | }
135 |
136 | return (
137 | <>
138 |
139 | {lastModified && (
140 | Modified: {lastModified}
141 | )}
142 |
143 |
144 | {lists.map((list, idx) => (
145 |
146 | {listIfNotEmpty(list)}
147 |
148 | ))}
149 | {theRest}
150 |
151 | >
152 | );
153 | });
154 |
--------------------------------------------------------------------------------
/src/JournalItemEvent.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import * as EteSync from "etesync";
6 |
7 | import Color from "color";
8 |
9 | import { Text } from "react-native-paper";
10 |
11 | import { SyncInfoItem } from "./store";
12 |
13 | import Container from "./widgets/Container";
14 | import Small from "./widgets/Small";
15 |
16 | import { EventType } from "./pim-types";
17 | import { formatDateRange, formatOurTimezoneOffset, colorIntToHtml } from "./helpers";
18 |
19 | import JournalItemHeader from "./JournalItemHeader";
20 |
21 | interface PropsType {
22 | collection: EteSync.CollectionInfo;
23 | entry: SyncInfoItem;
24 | }
25 |
26 | export default React.memo(function JournalItemEvent(props: PropsType) {
27 | const entry = props.entry;
28 | const event = EventType.parse(entry.content);
29 |
30 | const timezone = event.timezone;
31 |
32 | const backgroundColor = colorIntToHtml(props.collection.color);
33 | const foregroundColor = Color(backgroundColor).isLight() ? "black" : "white";
34 |
35 | return (
36 | <>
37 |
38 | {formatDateRange(event.startDate, event.endDate)} {timezone && ({formatOurTimezoneOffset()})}
39 | {event.location}
40 |
41 |
42 | {event.description}
43 | {(event.attendees.length > 0) && (
44 | Attendees: {event.attendees.map((x) => (x.getFirstValue())).join(", ")})}
45 |
46 | >
47 | );
48 | });
49 |
--------------------------------------------------------------------------------
/src/JournalItemHeader.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 |
6 | import { useTheme } from "react-native-paper";
7 |
8 | import Container from "./widgets/Container";
9 | import { Title } from "./widgets/Typography";
10 |
11 | interface HeaderPropsType {
12 | title: string;
13 | foregroundColor?: string;
14 | backgroundColor?: string;
15 | }
16 |
17 | export default function JournalItemHeader(props: React.PropsWithChildren) {
18 | const theme = useTheme();
19 |
20 | return (
21 |
22 | {props.title}
23 | {props.children}
24 |
25 | );
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/JournalItemScreen.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 |
6 | import { useSelector } from "react-redux";
7 | import { StyleSheet } from "react-native";
8 | import { Text, FAB, Appbar } from "react-native-paper";
9 | import { RouteProp, useNavigation } from "@react-navigation/native";
10 |
11 | import { useSyncGate } from "./SyncGate";
12 | import { StoreState } from "./store";
13 |
14 | import ScrollView from "./widgets/ScrollView";
15 | import Container from "./widgets/Container";
16 |
17 | import JournalItemContact from "./JournalItemContact";
18 | import JournalItemEvent from "./JournalItemEvent";
19 | import JournalItemTask from "./JournalItemTask";
20 |
21 | type RootStackParamList = {
22 | JournalItemScreen: {
23 | journalUid: string;
24 | entryUid: string;
25 | };
26 | };
27 |
28 | interface PropsType {
29 | route: RouteProp;
30 | }
31 |
32 | export default function JournalItemScreen(props: PropsType) {
33 | const [showRaw, setShowRaw] = React.useState(false);
34 | const navigation = useNavigation();
35 | const syncGate = useSyncGate();
36 | const syncInfoCollections = useSelector((state: StoreState) => state.cache.syncInfoCollection);
37 | const syncInfoEntries = useSelector((state: StoreState) => state.cache.syncInfoItem);
38 |
39 | if (syncGate) {
40 | return syncGate;
41 | }
42 |
43 | const { journalUid, entryUid } = props.route.params;
44 | const collection = syncInfoCollections.get(journalUid)!;
45 | const entries = syncInfoEntries.get(journalUid)!;
46 |
47 | const entry = entries.get(entryUid)!;
48 |
49 | let content;
50 | let fabContentIcon = "";
51 | switch (collection.type) {
52 | case "ADDRESS_BOOK":
53 | content = ;
54 | fabContentIcon = "account-card-details";
55 | break;
56 | case "CALENDAR":
57 | content = ;
58 | fabContentIcon = "calendar";
59 | break;
60 | case "TASKS":
61 | content = ;
62 | fabContentIcon = "format-list-checkbox";
63 | break;
64 | }
65 |
66 | navigation.setOptions({
67 | headerRight: () => (
68 | { navigation.navigate("JournalItemSave", { journalUid, entryUid }) }} />
69 | ),
70 | });
71 |
72 | return (
73 | <>
74 |
75 | {showRaw ? (
76 |
77 | {entry.content}
78 |
79 | ) : (
80 | content
81 | )}
82 |
83 | setShowRaw(!showRaw)}
89 | />
90 | >
91 | );
92 | }
93 |
94 | const styles = StyleSheet.create({
95 | fab: {
96 | position: "absolute",
97 | margin: 16,
98 | right: 0,
99 | bottom: 0,
100 | },
101 | });
102 |
--------------------------------------------------------------------------------
/src/JournalItemTask.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import * as EteSync from "etesync";
6 |
7 | import Color from "color";
8 |
9 | import { Text } from "react-native-paper";
10 |
11 | import { SyncInfoItem } from "./store";
12 |
13 | import Container from "./widgets/Container";
14 | import Small from "./widgets/Small";
15 |
16 | import { TaskType } from "./pim-types";
17 | import { formatDate, formatOurTimezoneOffset, colorIntToHtml } from "./helpers";
18 |
19 | import JournalItemHeader from "./JournalItemHeader";
20 |
21 | interface PropsType {
22 | collection: EteSync.CollectionInfo;
23 | entry: SyncInfoItem;
24 | }
25 |
26 | export default React.memo(function JournalItemTask(props: PropsType) {
27 | const entry = props.entry;
28 | const task = TaskType.parse(entry.content);
29 |
30 | const timezone = task.timezone;
31 |
32 | const backgroundColor = colorIntToHtml(props.collection.color);
33 | const foregroundColor = Color(backgroundColor).isLight() ? "black" : "white";
34 |
35 | return (
36 | <>
37 |
38 | {task.startDate &&
39 | Start: {formatDate(task.startDate)} {timezone && ({formatOurTimezoneOffset()})}
40 | }
41 | {task.dueDate &&
42 | Due: {formatDate(task.dueDate)} {timezone && ({formatOurTimezoneOffset()})}
43 | }
44 | {task.location}
45 |
46 |
47 | {task.description}
48 | {(task.attendees.length > 0) && (
49 | Attendees: {task.attendees.map((x) => (x.getFirstValue())).join(", ")})}
50 |
51 | >
52 | );
53 | });
54 |
--------------------------------------------------------------------------------
/src/LegacyHomeScreen.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { Appbar } from "react-native-paper";
7 | import { useNavigation } from "@react-navigation/native";
8 |
9 | import { SyncManager } from "./sync/SyncManager";
10 |
11 | import JournalListScreen from "./components/JournalListScreen";
12 | import { usePermissions } from "./Permissions";
13 |
14 | import { StoreState } from "./store";
15 | import { performSync } from "./store/actions";
16 |
17 | import { useCredentials } from "./login";
18 | import { useSyncGate } from "./SyncGate";
19 | import { registerSyncTask } from "./sync/SyncManager";
20 |
21 |
22 | export default React.memo(function HomeScreen() {
23 | const etesync = useCredentials()!;
24 | const dispatch = useDispatch();
25 | const SyncGate = useSyncGate();
26 | const navigation = useNavigation();
27 | const syncCount = useSelector((state: StoreState) => state.syncCount);
28 | const permissionsStatus = usePermissions();
29 |
30 | React.useEffect(() => {
31 | if (etesync && !permissionsStatus) {
32 | registerSyncTask(etesync.credentials.email);
33 | }
34 | }, [etesync, !permissionsStatus]);
35 |
36 | function refresh() {
37 | const syncManager = SyncManager.getManagerLegacy(etesync);
38 | dispatch(performSync(syncManager.sync()));
39 | }
40 |
41 | navigation.setOptions({
42 | headerRight: () => (
43 | 0} onPress={refresh} />
44 | ),
45 | });
46 |
47 | if (permissionsStatus) {
48 | return permissionsStatus;
49 | }
50 |
51 | if (SyncGate) {
52 | return SyncGate;
53 | }
54 |
55 | return (
56 |
57 | );
58 | });
59 |
--------------------------------------------------------------------------------
/src/Permissions.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { Button, Paragraph } from "react-native-paper";
7 |
8 | import * as Permissions from "expo-permissions";
9 |
10 | import { Title } from "./widgets/Typography";
11 | import LoadingIndicator from "./widgets/LoadingIndicator";
12 |
13 | import { StoreState } from "./store";
14 | import { setPermission } from "./store/actions";
15 |
16 | import { logger } from "./logging";
17 |
18 | const wantedPermissions: Permissions.PermissionType[] = [Permissions.CALENDAR, Permissions.REMINDERS, Permissions.CONTACTS, Permissions.USER_FACING_NOTIFICATIONS];
19 |
20 | export function AskForPermissions() {
21 | const dispatch = useDispatch();
22 | const permissions = useSelector((state: StoreState) => state.permissions);
23 | const alreadyAsked = wantedPermissions.length === permissions.size;
24 |
25 | return (
26 | <>
27 | Permissions
28 | {(alreadyAsked) ?
29 |
30 | EteSync has already asked for the permissions before, so they can now only be changed from the device's Settings app.
31 |
32 | :
33 | EteSync requires access to your contacts, calendars and reminders in order to be able save them to your device. You can either give EteSync access now or do it later from the device Settings.
34 | }
35 |
46 | >
47 | );
48 | }
49 |
50 |
51 | export function usePermissions() {
52 | const dispatch = useDispatch();
53 | const [shouldAsk, setShouldAsk] = React.useState(null);
54 | const [asked, setAsked] = React.useState(false);
55 |
56 | if (!asked) {
57 | setAsked(true);
58 | (async () => {
59 | for (const permission of wantedPermissions) {
60 | const { status } = await Permissions.getAsync(permission);
61 | logger.info(`Permissions status for ${permission}: ${status}`);
62 | if (status === Permissions.PermissionStatus.UNDETERMINED) {
63 | setShouldAsk(true);
64 | return;
65 | } else {
66 | dispatch(setPermission(permission, status === Permissions.PermissionStatus.GRANTED));
67 | }
68 | }
69 |
70 | setShouldAsk(false);
71 | })();
72 | }
73 |
74 | if (shouldAsk === null) {
75 | return ();
76 | } else {
77 | return null;
78 | }
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/src/SettingsGate.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { useSelector } from "react-redux";
6 | import NetInfo, { NetInfoState } from "@react-native-community/netinfo";
7 |
8 | import moment from "moment";
9 | import "moment/locale/en-gb";
10 |
11 | import { StoreState, store } from "./store";
12 | import { setConnectionInfo } from "./store/actions";
13 | import { logger, setLogLevel } from "./logging";
14 |
15 | function handleConnectivityChange(connectionInfo: NetInfoState) {
16 | logger.info(`ConnectionfInfo: ${connectionInfo.isConnected} ${connectionInfo.type}`);
17 | store.dispatch(setConnectionInfo({ type: connectionInfo.type, isConnected: connectionInfo.isConnected }));
18 | }
19 |
20 | export default React.memo(function SettingsGate(props: React.PropsWithChildren<{}>) {
21 | const settings = useSelector((state: StoreState) => state.settings);
22 |
23 | React.useEffect(() => {
24 | setLogLevel(settings.logLevel);
25 | }, [settings.logLevel]);
26 |
27 | React.useEffect(() => {
28 | moment.locale(settings.locale);
29 | }, [settings.locale]);
30 |
31 | // Not really settings but the app's general state.
32 | React.useEffect(() => {
33 | const unsubscribe = NetInfo.addEventListener(handleConnectivityChange);
34 | return unsubscribe;
35 | }, []);
36 |
37 | return (
38 | <>{props.children}>
39 | );
40 | });
41 |
--------------------------------------------------------------------------------
/src/SyncGate.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { useSelector } from "react-redux";
6 |
7 | import { useCredentials } from "./login";
8 | import { useCredentials as useCredentialsEb } from "./credentials";
9 |
10 | import LoadingIndicator from "./widgets/LoadingIndicator";
11 |
12 | import { StoreState } from "./store";
13 |
14 | import { syncInfoSelector } from "./SyncHandler";
15 |
16 | export function useSyncGate() {
17 | const etesync = useCredentials();
18 | const journals = useSelector((state: StoreState) => state.cache.journals);
19 | const entries = useSelector((state: StoreState) => state.cache.entries);
20 | const userInfo = useSelector((state: StoreState) => state.cache.userInfo);
21 | const syncCount = useSelector((state: StoreState) => state.syncCount);
22 | const syncStatus = useSelector((state: StoreState) => state.syncStatus);
23 |
24 | if ((syncCount > 0) || !etesync || !journals || !entries || !userInfo) {
25 | return ();
26 | }
27 |
28 | syncInfoSelector({ etesync, entries, journals, userInfo });
29 |
30 | return null;
31 | }
32 |
33 | export function useSyncGateEb() {
34 | const etebase = useCredentialsEb();
35 | const syncCount = useSelector((state: StoreState) => state.syncCount);
36 | const syncStatus = useSelector((state: StoreState) => state.syncStatus);
37 |
38 | if ((syncCount > 0) || !etebase) {
39 | return ();
40 | }
41 |
42 | return null;
43 | }
44 |
--------------------------------------------------------------------------------
/src/SyncHandler.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import { createSelector } from "reselect";
5 |
6 | import * as EteSync from "etesync";
7 | import { byte } from "etesync";
8 |
9 | import { store, JournalsData, EntriesData, CredentialsData, UserInfoData, SyncInfoItem } from "./store";
10 | import { setSyncInfoCollection, setSyncInfoItem, unsetSyncInfoCollection } from "./store/actions";
11 |
12 | interface SyncInfoSelectorProps {
13 | etesync: CredentialsData;
14 | journals: JournalsData;
15 | entries: EntriesData;
16 | userInfo: UserInfoData;
17 | }
18 |
19 | export const syncInfoSelector = createSelector(
20 | (props: SyncInfoSelectorProps) => props.etesync,
21 | (props: SyncInfoSelectorProps) => props.journals,
22 | (props: SyncInfoSelectorProps) => props.entries,
23 | (props: SyncInfoSelectorProps) => props.userInfo,
24 | (etesync, journals, entries, userInfo) => {
25 | const syncInfoCollection = store.getState().cache.syncInfoCollection;
26 | const syncInfoItem = store.getState().cache.syncInfoItem;
27 | const derived = etesync.encryptionKey;
28 | const userInfoCryptoManager = userInfo.getCryptoManager(etesync.encryptionKey);
29 | let asymmetricCryptoManager: EteSync.AsymmetricCryptoManager;
30 | try {
31 | userInfo.verify(userInfoCryptoManager);
32 | } catch (error) {
33 | if (error instanceof EteSync.IntegrityError) {
34 | throw new EteSync.EncryptionPasswordError(error.message);
35 | } else {
36 | throw error;
37 | }
38 | }
39 |
40 | const handled = {};
41 | journals.forEach((journal) => {
42 | const journalEntries = entries.get(journal.uid);
43 | let prevUid: string | null = null;
44 |
45 | if (!journalEntries) {
46 | return;
47 | }
48 |
49 | let cryptoManager: EteSync.CryptoManager;
50 | let derivedJournalKey: byte[] | undefined;
51 | if (journal.key) {
52 | if (!asymmetricCryptoManager) {
53 | const keyPair = userInfo.getKeyPair(userInfoCryptoManager);
54 | asymmetricCryptoManager = new EteSync.AsymmetricCryptoManager(keyPair);
55 | }
56 | derivedJournalKey = asymmetricCryptoManager.decryptBytes(journal.key);
57 | cryptoManager = EteSync.CryptoManager.fromDerivedKey(derivedJournalKey, journal.version);
58 | } else {
59 | cryptoManager = new EteSync.CryptoManager(derived, journal.uid, journal.version);
60 | }
61 |
62 | const collectionInfo = journal.getInfo(cryptoManager);
63 | store.dispatch(setSyncInfoCollection(etesync, collectionInfo));
64 |
65 | journalEntries.forEach((entry: EteSync.Entry) => {
66 | const cacheEntry = syncInfoItem.getIn([journal.uid, entry.uid]);
67 | if (cacheEntry) {
68 | prevUid = entry.uid;
69 | return cacheEntry;
70 | }
71 |
72 | const syncEntry = entry.getSyncEntry(cryptoManager, prevUid);
73 | prevUid = entry.uid;
74 |
75 | store.dispatch(setSyncInfoItem(etesync, journal.uid, syncEntry as SyncInfoItem));
76 | return syncEntry;
77 | });
78 |
79 | handled[journal.uid] = true;
80 | });
81 |
82 | for (const collection of syncInfoCollection.values()) {
83 | if (!handled[collection.uid]) {
84 | store.dispatch(unsetSyncInfoCollection(etesync, collection));
85 | }
86 | }
87 | }
88 | );
89 |
--------------------------------------------------------------------------------
/src/components/EncryptionLoginForm.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { View } from "react-native";
6 | import { Text, HelperText, Button } from "react-native-paper";
7 | import PasswordInput from "../widgets/PasswordInput";
8 |
9 |
10 | interface FormErrors {
11 | encryptionPassword?: string;
12 | }
13 |
14 | interface PropsType {
15 | onSubmit: (encryptionPassword: string) => void;
16 | }
17 |
18 | export default function _EncryptionLognForm(props: PropsType) {
19 | const [errors, setErrors] = React.useState({});
20 | const [encryptionPassword, setEncryptionPassword] = React.useState();
21 |
22 | function onSave() {
23 | const saveErrors: FormErrors = {};
24 | const fieldRequired = "This field is required!";
25 |
26 | if (!encryptionPassword) {
27 | saveErrors.encryptionPassword = fieldRequired;
28 | }
29 |
30 | if (Object.keys(saveErrors).length > 0) {
31 | setErrors(saveErrors);
32 | return;
33 | }
34 |
35 | props.onSubmit(encryptionPassword!);
36 | }
37 |
38 | return (
39 |
40 |
48 |
52 | {errors.encryptionPassword}
53 |
54 |
55 |
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/JournalListScreenEb.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { useSelector } from "react-redux";
6 | import { View } from "react-native";
7 | import { Avatar, IconButton, Card, Menu, List, Colors, Text } from "react-native-paper";
8 | import { useNavigation } from "@react-navigation/native";
9 |
10 | import moment from "moment";
11 |
12 | import { defaultColor } from "../helpers";
13 |
14 | import ScrollView from "../widgets/ScrollView";
15 | import ColorBox from "../widgets/ColorBox";
16 | import { useSyncGateEb } from "../SyncGate";
17 |
18 | import { StoreState } from "../store";
19 |
20 | const backgroundPrimary = Colors.amber700;
21 |
22 | const JournalsMoreMenu = React.memo(function _JournalsMoreMenu(props: { colType: string }) {
23 | const [showMenu, setShowMenu] = React.useState(false);
24 | const navigation = useNavigation();
25 |
26 | return (
27 |
42 | );
43 | });
44 |
45 |
46 | export default function JournalListScreen() {
47 | const navigation = useNavigation();
48 | const syncGate = useSyncGateEb();
49 | const decryptedCollections = useSelector((state: StoreState) => state.cache2.decryptedCollections);
50 | const lastSync = useSelector((state: StoreState) => state.sync.lastSync);
51 |
52 | if (syncGate) {
53 | return syncGate;
54 | }
55 |
56 | const collectionsMap = {
57 | "etebase.vevent": [] as React.ReactNode[],
58 | "etebase.vcard": [] as React.ReactNode[],
59 | "etebase.vtodo": [] as React.ReactNode[],
60 | };
61 |
62 | for (const [uid, { meta, collectionType }] of decryptedCollections.entries()) {
63 | if (!collectionsMap[collectionType]) {
64 | continue;
65 | }
66 | const readOnly = false; // FIXME-eb
67 | const shared = false; // FIXME-eb
68 |
69 | let colorBox: any;
70 | switch (collectionType) {
71 | case "etebase.vevent":
72 | case "etebase.vtodo":
73 | colorBox = (
74 |
75 | );
76 | break;
77 | }
78 |
79 | const rightIcon = (props: any) => (
80 |
81 | {shared &&
82 |
83 | }
84 | {readOnly &&
85 |
86 | }
87 | {colorBox}
88 |
89 | );
90 |
91 | collectionsMap[collectionType].push(
92 | navigation.navigate("Collection", { colUid: uid })}
95 | title={meta.name}
96 | right={rightIcon}
97 | />
98 | );
99 | }
100 |
101 | const cards = [
102 | {
103 | title: "Address Books",
104 | lookup: "etebase.vcard",
105 | icon: "contacts",
106 | },
107 | {
108 | title: "Calendars",
109 | lookup: "etebase.vevent",
110 | icon: "calendar",
111 | },
112 | {
113 | title: "Tasks",
114 | lookup: "etebase.vtodo",
115 | icon: "format-list-checkbox",
116 | },
117 | ];
118 |
119 | const shadowStyle = {
120 | shadowColor: "#000",
121 | shadowOffset: {
122 | width: 0,
123 | height: 1,
124 | },
125 | shadowOpacity: 0.20,
126 | shadowRadius: 1.41,
127 |
128 | elevation: 2,
129 | };
130 |
131 | return (
132 |
133 | Last sync: {lastSync ? moment(lastSync).format("lll") : "never"}
134 | {cards.map((card) => (
135 |
136 | (
141 |
142 |
143 |
144 | )}
145 | right={() => (
146 |
147 | )}
148 | />
149 | {collectionsMap[card.lookup]}
150 |
151 | ))}
152 |
153 | );
154 | }
155 |
--------------------------------------------------------------------------------
/src/components/WebviewKeygen.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { WebView } from "react-native-webview";
6 |
7 | interface Keys {
8 | privateKey: string;
9 | publicKey: string;
10 | error?: string;
11 | }
12 |
13 | interface PropsType {
14 | onFinish: (keys: Keys) => void;
15 | }
16 |
17 | export default React.memo(function WebviewKeygen(props: PropsType) {
18 | return (
19 |
24 |
25 |
26 |
59 |
60 |
61 | ` }}
62 | onMessage={({ nativeEvent: state }) => {
63 | const keys = JSON.parse(state.data);
64 | props.onFinish(keys);
65 | }}
66 | />
67 | );
68 | });
69 |
--------------------------------------------------------------------------------
/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | export const appName = "EteSync";
5 | export const homePage = "https://www.etesync.com/";
6 | export const faq = homePage + "faq/";
7 | export const dashboard = homePage + "dashboard/";
8 | export const sourceCode = "https://github.com/etesync/ios";
9 | export const reportIssue = sourceCode + "/issues";
10 | export const contactEmail = "contact-ios@etesync.com";
11 | export const reportsEmail = "reports-ios@etesync.com";
12 |
13 | export const forgotPassword = "https://www.etesync.com/accounts/password/reset/";
14 |
15 | export const serviceApiBase = "https://api.etesync.com/";
16 | export const serviceApiBaseEb = "https://api.etebase.com/partner/etesync/";
17 |
18 | // In generic mode we don't have anything etesync.com specific
19 | export const genericMode = true;
20 | // Sync app mode is an experimental mode for controlling sync settings
21 | export const syncAppMode = true;
22 |
--------------------------------------------------------------------------------
/src/credentials.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2017 Etebase Authors
2 | // SPDX-License-Identifier: AGPL-3.0-only
3 |
4 | import { useSelector } from "react-redux";
5 | import { createSelector } from "reselect";
6 |
7 | import * as Etebase from "etebase";
8 |
9 | import * as store from "./store";
10 | import { usePromiseMemo } from "./helpers";
11 |
12 | export const credentialsSelector = createSelector(
13 | (state: store.StoreState) => state.credentials2.storedSession,
14 | (storedSession) => {
15 | if (storedSession) {
16 | return Etebase.Account.restore(storedSession);
17 | } else {
18 | return Promise.resolve(null);
19 | }
20 | }
21 | );
22 |
23 | export function useCredentials() {
24 | const credentialsPromise = useSelector(credentialsSelector);
25 | return usePromiseMemo(credentialsPromise, [credentialsPromise]);
26 | }
27 |
--------------------------------------------------------------------------------
/src/etesync-helpers.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as EteSync from "etesync";
5 |
6 | import { CredentialsData, UserInfoData } from "./store";
7 | import { addEntries } from "./store/actions";
8 |
9 | export function createJournalEntry(
10 | etesync: CredentialsData,
11 | userInfo: UserInfoData,
12 | journal: EteSync.Journal,
13 | prevUid: string | null,
14 | action: EteSync.SyncEntryAction,
15 | content: string) {
16 |
17 | const syncEntry = new EteSync.SyncEntry();
18 | syncEntry.action = action;
19 |
20 | syncEntry.content = content;
21 | return createJournalEntryFromSyncEntry(etesync, userInfo, journal, prevUid, syncEntry);
22 | }
23 |
24 | export function createJournalEntryFromSyncEntry(
25 | etesync: CredentialsData,
26 | userInfo: UserInfoData,
27 | journal: EteSync.Journal,
28 | prevUid: string | null,
29 | syncEntry: EteSync.SyncEntry) {
30 |
31 | const derived = etesync.encryptionKey;
32 |
33 | const keyPair = userInfo.getKeyPair(userInfo.getCryptoManager(derived));
34 | const cryptoManager = journal.getCryptoManager(derived, keyPair);
35 | const entry = new EteSync.Entry();
36 | entry.setSyncEntry(cryptoManager, syncEntry, prevUid);
37 |
38 | return entry;
39 | }
40 |
41 | export function addJournalEntry(
42 | etesync: CredentialsData,
43 | userInfo: UserInfoData,
44 | journal: EteSync.Journal,
45 | prevUid: string | null,
46 | action: EteSync.SyncEntryAction,
47 | content: string) {
48 |
49 | const entry = createJournalEntry(etesync, userInfo, journal, prevUid, action, content);
50 | return addEntries(etesync, journal.uid, [entry], prevUid);
51 | }
52 |
--------------------------------------------------------------------------------
/src/helpers.test.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import { colorHtmlToInt, colorIntToHtml } from "./helpers";
5 |
6 | it("Color conversion", () => {
7 | const testColors = [
8 | "#aaaaaaaa",
9 | "#00aaaaaa",
10 | "#0000aaaa",
11 | "#000000aa",
12 | "#00000000",
13 | "#bb00bbbb",
14 | "#bb0000bb",
15 | "#bb000000",
16 | "#11110011",
17 | "#11110000",
18 | "#11111100",
19 | ];
20 |
21 | for (const color of testColors) {
22 | expect(color).toEqual(colorIntToHtml(colorHtmlToInt(color)));
23 | }
24 | });
25 |
26 |
--------------------------------------------------------------------------------
/src/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etesync/ios/966c112bd6a92daaee2ab128cea29118a13188d8/src/images/icon.png
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { Provider } from "react-redux";
6 | import { PersistGate } from "redux-persist/es/integration/react";
7 | import App from "./App";
8 |
9 | import "react-native-etebase";
10 | import * as Etebase from "etebase";
11 | import { store, persistor } from "./store";
12 |
13 | function MyPersistGate(props: React.PropsWithChildren<{}>) {
14 | const [loading, setLoading] = React.useState(true);
15 |
16 | React.useEffect(() => {
17 | Etebase.ready.then(() => {
18 | setLoading(false);
19 | persistor.persist();
20 | });
21 | }, []);
22 |
23 | if (loading) {
24 | return ();
25 | }
26 |
27 | return (
28 |
29 | {props.children}
30 |
31 | );
32 | }
33 |
34 | class Index extends React.Component {
35 | public render() {
36 | return (
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | export default Index;
47 |
--------------------------------------------------------------------------------
/src/logging.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import { AsyncStorage } from "react-native";
5 |
6 | export enum LogLevel {
7 | Off = 0,
8 | Critical,
9 | Warning,
10 | Info,
11 | Debug,
12 | }
13 |
14 | let logLevel = (__DEV__) ? LogLevel.Debug : LogLevel.Off;
15 |
16 | export function setLogLevel(level: LogLevel) {
17 | if (!__DEV__) {
18 | logLevel = level;
19 | }
20 | }
21 |
22 | function shouldLog(messageLevel: LogLevel) {
23 | return messageLevel <= logLevel;
24 | }
25 |
26 | function logPrint(messageLevel: LogLevel, message: any) {
27 | if (!shouldLog(messageLevel)) {
28 | return;
29 | }
30 |
31 | switch (messageLevel) {
32 | case LogLevel.Critical:
33 | case LogLevel.Warning:
34 | console.warn(message);
35 | break;
36 | default:
37 | console.log(message);
38 | }
39 | }
40 |
41 | const logPrefix = "__logging_";
42 |
43 | function logToBuffer(messageLevel: LogLevel, message: any) {
44 | if (!shouldLog(messageLevel)) {
45 | return;
46 | }
47 |
48 | AsyncStorage.setItem(`${logPrefix}${new Date().toISOString()}`, `[${LogLevel[messageLevel].substr(0, 1)}] ${message}`);
49 | }
50 |
51 | async function getLogKeys() {
52 | const keys = await AsyncStorage.getAllKeys();
53 | return keys.filter((key) => key.startsWith(logPrefix));
54 | }
55 |
56 | export async function getLogs() {
57 | const wantedKeys = await getLogKeys();
58 | if (wantedKeys.length === 0) {
59 | return [];
60 | }
61 |
62 | const wantedItems = await AsyncStorage.multiGet(wantedKeys);
63 | return wantedItems.sort(([a], [b]) => {
64 | return a.localeCompare(b);
65 | }).map(([_key, value]) => value);
66 | }
67 |
68 | export async function clearLogs() {
69 | const wantedKeys = await getLogKeys();
70 | if (wantedKeys.length === 0) {
71 | return;
72 | }
73 | await AsyncStorage.multiRemove(wantedKeys);
74 | }
75 |
76 | const logHandler = (__DEV__) ? logPrint : logToBuffer;
77 |
78 | class Logger {
79 | public debug(message: string) {
80 | logHandler(LogLevel.Debug, message);
81 | }
82 |
83 | public info(message: string) {
84 | logHandler(LogLevel.Info, message);
85 | }
86 |
87 | public warn(message: string) {
88 | logHandler(LogLevel.Warning, message);
89 | }
90 |
91 | public critical(message: string) {
92 | logHandler(LogLevel.Critical, message);
93 | }
94 | }
95 |
96 | export const logger = new Logger();
97 |
--------------------------------------------------------------------------------
/src/login/index.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import { shallowEqual, useSelector } from "react-redux";
5 | import { createSelector } from "reselect";
6 |
7 | import * as store from "../store";
8 |
9 | export const remoteCredentialsSelector = createSelector(
10 | (state: store.StoreState) => state.credentials.credentials ?? state.legacyCredentials.credentials,
11 | (state: store.StoreState) => state.credentials.serviceApiUrl ?? state.legacyCredentials.serviceApiUrl,
12 | (credentials, serviceApiUrl) => {
13 | if (!credentials) {
14 | return null;
15 | }
16 |
17 | const ret: store.CredentialsDataRemote = {
18 | credentials,
19 | serviceApiUrl,
20 | };
21 | return ret;
22 | }
23 | );
24 |
25 | export function useRemoteCredentials() {
26 | return useSelector(remoteCredentialsSelector, shallowEqual);
27 | }
28 |
29 | export const credentialsSelector = createSelector(
30 | (state: store.StoreState) => remoteCredentialsSelector(state),
31 | (state: store.StoreState) => state.encryptionKey.encryptionKey ?? state.legacyEncryptionKey.key,
32 | (remoteCredentials, encryptionKey) => {
33 | if (!remoteCredentials || !encryptionKey) {
34 | return null;
35 | }
36 |
37 | const ret: store.CredentialsData = {
38 | ...remoteCredentials,
39 | encryptionKey,
40 | };
41 | return ret;
42 | }
43 | );
44 |
45 | export function useCredentials() {
46 | return useSelector(credentialsSelector, shallowEqual);
47 | }
48 |
--------------------------------------------------------------------------------
/src/store/index.test.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import { addEntries, fetchEntries } from "./actions";
5 | import { entries, EntriesData } from "./reducers";
6 |
7 | import { Map } from "immutable";
8 |
9 | import * as EteSync from "etesync";
10 |
11 | it("Entries reducer", () => {
12 | const jId = "24324324324";
13 | let state = Map({}) as EntriesData;
14 |
15 | const entry = new EteSync.Entry();
16 | entry.deserialize({
17 | content: "someContent",
18 | uid: "6355209e2a2c26a6c1e6e967c2032737d538f602cf912474da83a2902f8a0a83",
19 | });
20 |
21 | const action = {
22 | type: fetchEntries.toString(),
23 | meta: { journal: jId, prevUid: null as string | null },
24 | payload: [entry],
25 | };
26 |
27 | let journal;
28 | let entry2;
29 |
30 | state = entries(state, action as any);
31 | journal = state.get(jId)!;
32 | entry2 = journal.get(0)!;
33 | expect(entry2.serialize()).toEqual(entry.serialize());
34 |
35 | // We replace if there's no prevUid
36 | state = entries(state, action as any);
37 | journal = state.get(jId)!;
38 | entry2 = journal.get(0)!;
39 | expect(entry2.serialize()).toEqual(entry.serialize());
40 | expect(journal.size).toBe(1);
41 |
42 | // We extend if prevUid is set
43 | action.meta.prevUid = entry.uid;
44 | state = entries(state, action as any);
45 | journal = state.get(jId)!;
46 | expect(journal.size).toBe(2);
47 |
48 | // Creating entries should also work the same
49 | action.type = addEntries.toString();
50 | state = entries(state, action as any);
51 | journal = state.get(jId)!;
52 | expect(journal.size).toBe(3);
53 | });
54 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import { createStore, applyMiddleware } from "redux";
5 | import { persistStore } from "redux-persist";
6 | import thunkMiddleware from "redux-thunk";
7 | import { createLogger } from "redux-logger";
8 | import { useDispatch } from "react-redux";
9 | import { ActionMeta } from "redux-actions";
10 |
11 | import promiseMiddleware from "./promise-middleware";
12 |
13 | import reducers from "./construct";
14 | import * as actions from "./actions";
15 |
16 | // Workaround babel limitation
17 | export * from "./reducers";
18 | export * from "./construct";
19 |
20 | const middleware = [
21 | thunkMiddleware,
22 | promiseMiddleware,
23 | ];
24 |
25 | if (__DEV__) {
26 | const ignoreActions = [
27 | "persist/PERSIST",
28 | "persist/REHYDRATE",
29 | actions.setSyncStateJournal.toString(),
30 | actions.unsetSyncStateJournal.toString(),
31 | actions.setSyncStateEntry.toString(),
32 | actions.unsetSyncStateEntry.toString(),
33 | actions.setSyncInfoCollection.toString(),
34 | actions.unsetSyncInfoCollection.toString(),
35 | actions.setSyncInfoItem.toString(),
36 | actions.unsetSyncInfoItem.toString(),
37 | ];
38 |
39 | const predicate = (_: any, action: { type: string }) => {
40 | return !ignoreActions.includes(action.type);
41 | };
42 |
43 | const logger = {
44 | log: (msg: string) => {
45 | if (msg[0] === "#") {
46 | console.log(msg);
47 | }
48 | },
49 | };
50 |
51 | middleware.push(createLogger({
52 | predicate,
53 | logger,
54 | stateTransformer: () => "state",
55 | actionTransformer: ({ type, error, payload }) => ({ type, error, payload: (payload !== undefined) }),
56 | titleFormatter: (action: { type: string, error: any, payload: boolean }, time: string, took: number) => {
57 | let prefix = "->";
58 | if (action.error) {
59 | prefix = "xx";
60 | } else if (action.payload) {
61 | prefix = "==";
62 | }
63 | return `# ${prefix} ${action.type} @ ${time} (in ${took.toFixed(2)} ms)`;
64 | },
65 | colors: {
66 | title: false,
67 | prevState: false,
68 | action: false,
69 | nextState: false,
70 | error: false,
71 | },
72 | }));
73 | }
74 |
75 | // FIXME: Hack, we don't actually return a promise when one is not passed.
76 | export function asyncDispatch(action: ActionMeta | T, V>): Promise> {
77 | return store.dispatch(action) as any;
78 | }
79 |
80 | export function useAsyncDispatch() {
81 | const dispatch = useDispatch();
82 | return function (action: any): any {
83 | return dispatch(action) as any;
84 | } as typeof asyncDispatch;
85 | }
86 |
87 | export const store = createStore(
88 | reducers,
89 | applyMiddleware(...middleware)
90 | );
91 |
92 | export const persistor = persistStore(store, { manualPersist: true } as any);
93 |
--------------------------------------------------------------------------------
/src/store/promise-middleware.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | // Based on: https://github.com/acdlite/redux-promise/blob/master/src/index.js
5 |
6 | function isPromise(val: any): val is Promise {
7 | return val && typeof val.then === "function";
8 | }
9 |
10 | export default function promiseMiddleware({ dispatch }: any) {
11 | return (next: any) => (action: any) => {
12 | if (isPromise(action.payload)) {
13 | dispatch({ ...action, payload: undefined });
14 |
15 | return action.payload
16 | .then((result: any) => dispatch({ ...action, payload: result }))
17 | .catch((error: Error) => {
18 | dispatch({ ...action, payload: error, error: true });
19 | return Promise.reject(error);
20 | });
21 | } else {
22 | return next(action);
23 | }
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/src/sync/SyncManagerTaskList.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as Calendar from "expo-calendar";
5 |
6 | import { calculateHashesForReminders, BatchAction, HashDictionary, processRemindersChanges } from "../EteSyncNative";
7 |
8 | import { logger } from "../logging";
9 |
10 | import { store } from "../store";
11 |
12 | import { NativeTask, taskVobjectToNative, taskNativeToVobject } from "./helpers";
13 | import { TaskType } from "../pim-types";
14 |
15 | import { SyncManagerCalendarBase } from "./SyncManagerCalendar";
16 | import { PushEntry } from "./SyncManagerBase";
17 |
18 | export class SyncManagerTaskList extends SyncManagerCalendarBase {
19 | protected permissionsType = "TASKS";
20 | protected collectionType = "etebase.vtodo";
21 | protected collectionTypeDisplay = "Tasks";
22 | protected entityType = Calendar.EntityTypes.REMINDER;
23 |
24 | protected async syncPush() {
25 | const storeState = store.getState();
26 | const decryptedCollections = storeState.cache2.decryptedCollections;
27 | const syncStateJournals = storeState.sync.stateJournals;
28 | const syncStateEntries = storeState.sync.stateEntries;
29 |
30 | for (const [uid, { collectionType }] of decryptedCollections.entries()) {
31 | if (collectionType !== this.collectionType) {
32 | continue;
33 | }
34 |
35 | logger.info(`Pushing ${uid}`);
36 |
37 | const syncStateEntriesReverse = syncStateEntries.get(uid)!.mapEntries((_entry) => {
38 | const entry = _entry[1];
39 | return [entry.localId, entry];
40 | }).asMutable();
41 |
42 | const syncEntries: PushEntry[] = [];
43 |
44 | const syncStateJournal = syncStateJournals.get(uid)!;
45 | const localId = syncStateJournal.localId;
46 | const existingReminders = await calculateHashesForReminders(localId);
47 | for (const [reminderId, reminderHash] of existingReminders) {
48 | const syncStateEntry = syncStateEntriesReverse.get(reminderId!);
49 |
50 | if (syncStateEntry?.lastHash !== reminderHash) {
51 | const _reminder = await Calendar.getReminderAsync(reminderId);
52 | const reminder = { ..._reminder, uid: (syncStateEntry) ? syncStateEntry.uid : _reminder.id! };
53 | const syncEntry = await this.syncPushHandleAddChange(syncStateJournal, syncStateEntry, reminder, reminderHash);
54 | if (syncEntry) {
55 | syncEntries.push(syncEntry);
56 | }
57 | }
58 |
59 | if (syncStateEntry) {
60 | syncStateEntriesReverse.delete(syncStateEntry.uid);
61 | }
62 | }
63 |
64 | for (const syncStateEntry of syncStateEntriesReverse.values()) {
65 | // Deleted
66 | let existingReminder: Calendar.Reminder | undefined;
67 | try {
68 | existingReminder = await Calendar.getReminderAsync(syncStateEntry.localId);
69 | } catch (e) {
70 | // Skip
71 | }
72 |
73 | let shouldDelete = !existingReminder;
74 | if (existingReminder) {
75 | // FIXME: handle the case of the event still existing and on the same calendar. Probably means we are just not in the range.
76 | if (existingReminder.calendarId !== localId) {
77 | shouldDelete = true;
78 | }
79 | }
80 |
81 | if (shouldDelete) {
82 | // If the reminder still exists it means it's not deleted.
83 | const syncEntry = await this.syncPushHandleDeleted(syncStateJournal, syncStateEntry);
84 | if (syncEntry) {
85 | syncEntries.push(syncEntry);
86 | }
87 | }
88 | }
89 |
90 | await this.pushJournalEntries(syncStateJournal, syncEntries);
91 | }
92 | }
93 |
94 | protected contentToVobject(content: string) {
95 | return TaskType.parse(content);
96 | }
97 |
98 | protected vobjectToNative(vobject: TaskType) {
99 | return taskVobjectToNative(vobject);
100 | }
101 |
102 | protected nativeToVobject(nativeItem: NativeTask) {
103 | return taskNativeToVobject(nativeItem);
104 | }
105 |
106 | protected processSyncEntries(containerLocalId: string, batch: [BatchAction, NativeTask][]): Promise {
107 | return processRemindersChanges(containerLocalId, batch);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/sync/index.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | export { SyncManagerAddressBook } from "./SyncManagerAddressBook";
5 | export { SyncManagerCalendar } from "./SyncManagerCalendar";
6 | export { SyncManager } from "./SyncManager";
7 |
--------------------------------------------------------------------------------
/src/sync/legacy/SyncManagerTaskList.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as EteSync from "etesync";
5 | import * as Calendar from "expo-calendar";
6 |
7 | import { calculateHashesForReminders, BatchAction, HashDictionary, processRemindersChanges } from "../../EteSyncNative";
8 |
9 | import { logger } from "../../logging";
10 |
11 | import { store } from "../../store";
12 |
13 | import { NativeTask, taskVobjectToNative, taskNativeToVobject } from "../helpers";
14 | import { TaskType } from "../../pim-types";
15 |
16 | import { SyncManagerCalendarBase } from "./SyncManagerCalendar";
17 | import { PushEntry } from "./SyncManagerBase";
18 |
19 | export class SyncManagerTaskList extends SyncManagerCalendarBase {
20 | protected collectionType = "TASKS";
21 | protected entityType = Calendar.EntityTypes.REMINDER;
22 |
23 | protected async syncPush() {
24 | const storeState = store.getState();
25 | const syncInfoCollections = storeState.cache.syncInfoCollection;
26 | const syncStateJournals = storeState.sync.stateJournals;
27 | const syncStateEntries = storeState.sync.stateEntries;
28 |
29 | for (const collection of syncInfoCollections.values()) {
30 | const uid = collection.uid;
31 |
32 | if (collection.type !== this.collectionType) {
33 | continue;
34 | }
35 |
36 | logger.info(`Pushing ${uid}`);
37 |
38 | const syncStateEntriesReverse = syncStateEntries.get(uid)!.mapEntries((_entry) => {
39 | const entry = _entry[1];
40 | return [entry.localId, entry];
41 | }).asMutable();
42 |
43 | const syncEntries: PushEntry[] = [];
44 |
45 | const syncStateJournal = syncStateJournals.get(uid)!;
46 | const localId = syncStateJournal.localId;
47 | const existingReminders = await calculateHashesForReminders(localId);
48 | for (const [reminderId, reminderHash] of existingReminders) {
49 | const syncStateEntry = syncStateEntriesReverse.get(reminderId!);
50 |
51 | if (syncStateEntry?.lastHash !== reminderHash) {
52 | const _reminder = await Calendar.getReminderAsync(reminderId);
53 | const reminder = { ..._reminder, uid: (syncStateEntry) ? syncStateEntry.uid : _reminder.id! };
54 | const syncEntry = this.syncPushHandleAddChange(syncStateJournal, syncStateEntry, reminder, reminderHash);
55 | if (syncEntry) {
56 | syncEntries.push(syncEntry);
57 | }
58 | }
59 |
60 | if (syncStateEntry) {
61 | syncStateEntriesReverse.delete(syncStateEntry.uid);
62 | }
63 | }
64 |
65 | for (const syncStateEntry of syncStateEntriesReverse.values()) {
66 | // Deleted
67 | let existingReminder: Calendar.Reminder | undefined;
68 | try {
69 | existingReminder = await Calendar.getReminderAsync(syncStateEntry.localId);
70 | } catch (e) {
71 | // Skip
72 | }
73 |
74 | let shouldDelete = !existingReminder;
75 | if (existingReminder) {
76 | // FIXME: handle the case of the event still existing and on the same calendar. Probably means we are just not in the range.
77 | if (existingReminder.calendarId !== localId) {
78 | shouldDelete = true;
79 | }
80 | }
81 |
82 | if (shouldDelete) {
83 | // If the reminder still exists it means it's not deleted.
84 | const syncEntry = this.syncPushHandleDeleted(syncStateJournal, syncStateEntry);
85 | if (syncEntry) {
86 | syncEntries.push(syncEntry);
87 | }
88 | }
89 | }
90 |
91 | await this.pushJournalEntries(syncStateJournal, syncEntries);
92 | }
93 | }
94 |
95 | protected syncEntryToVobject(syncEntry: EteSync.SyncEntry) {
96 | return TaskType.parse(syncEntry.content);
97 | }
98 |
99 | protected vobjectToNative(vobject: TaskType) {
100 | return taskVobjectToNative(vobject);
101 | }
102 |
103 | protected nativeToVobject(nativeItem: NativeTask) {
104 | return taskNativeToVobject(nativeItem);
105 | }
106 |
107 | protected processSyncEntries(containerLocalId: string, batch: [BatchAction, NativeTask][]): Promise {
108 | return processRemindersChanges(containerLocalId, batch);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/types/redux-persist.d.ts:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | declare module "redux-persist";
5 | declare module "redux-persist/lib/storage/session";
6 | declare module "redux-persist/es/integration/react";
7 |
--------------------------------------------------------------------------------
/src/widgets/Alert.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { View, ViewProps, StyleSheet } from "react-native";
6 | import { Text, useTheme } from "react-native-paper";
7 | import Icon from "react-native-vector-icons/MaterialCommunityIcons";
8 |
9 | type PropsType = ViewProps & {
10 | severity: "error" | "warning" | "info" | "success";
11 | };
12 |
13 | export default function Alert(props_: React.PropsWithChildren) {
14 | const theme = useTheme();
15 | const { children, style, severity, ...props } = props_;
16 | const icons = {
17 | error: "information-outline",
18 | warning: "alert-outline",
19 | info: "information-outline",
20 | success: "checkbox-marked-circle-outline",
21 | };
22 | const stylesColor = theme.dark ? stylesDark : stylesLight;
23 |
24 | return (
25 |
26 |
27 |
31 | {children}
32 |
33 |
34 | );
35 | }
36 |
37 | const styles = StyleSheet.create({
38 | root: {
39 | flexDirection: "row",
40 | alignItems: "center",
41 | paddingHorizontal: 16,
42 | paddingVertical: 6,
43 | borderRadius: 4,
44 | },
45 | icon: {
46 | marginRight: 12,
47 | },
48 | text: {
49 | flex: 1,
50 | },
51 | });
52 |
53 | const stylesIcon = StyleSheet.create({
54 | error: {
55 | color: "#f44336",
56 | },
57 | warning: {
58 | color: "#ff9800",
59 | },
60 | info: {
61 | color: "#2196f3",
62 | },
63 | success: {
64 | color: "#4caf50",
65 | },
66 | });
67 |
68 | const stylesLight = StyleSheet.create({
69 | error: {
70 | color: "rgb(97, 26, 21)",
71 | backgroundColor: "rgb(253, 236, 234)",
72 | },
73 | warning: {
74 | color: "rgb(102, 60, 0)",
75 | backgroundColor: "rgb(255, 244, 229)",
76 | },
77 | info: {
78 | color: "rgb(13, 60, 97)",
79 | backgroundColor: "rgb(232, 244, 253)",
80 | },
81 | success: {
82 | color: "rgb(30, 70, 32)",
83 | backgroundColor: "rgb(237, 247, 237)",
84 | },
85 | });
86 |
87 | const stylesDark = StyleSheet.create({
88 | error: {
89 | color: "rgb(250, 179, 174)",
90 | backgroundColor: "rgb(24, 6, 5)",
91 | },
92 | warning: {
93 | color: "rgb(255, 213, 153)",
94 | backgroundColor: "rgb(25, 15, 0)",
95 | },
96 | info: {
97 | color: "rgb(166, 213, 250)",
98 | backgroundColor: "rgb(3, 14, 24)",
99 | },
100 | success: {
101 | color: "rgb(183, 223, 185)",
102 | backgroundColor: "rgb(7, 17, 7)",
103 | },
104 | });
105 |
--------------------------------------------------------------------------------
/src/widgets/Checkbox.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { Checkbox as PaperCheckbox, Paragraph, TouchableRipple } from "react-native-paper";
6 | import { View, StyleProp, ViewStyle } from "react-native";
7 |
8 | interface PropsType {
9 | style?: StyleProp;
10 | title: string;
11 | status: boolean;
12 | onPress: () => void;
13 | }
14 |
15 | export default function Checkbox(props: PropsType) {
16 | return (
17 |
21 |
22 | {props.title}
23 |
24 |
27 |
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/src/widgets/ColorBox.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { View, StyleProp, ViewStyle } from "react-native";
6 |
7 | interface PropsType {
8 | color: string;
9 | size?: number;
10 | style?: StyleProp;
11 | }
12 |
13 | export default function ColorBox(props: PropsType) {
14 | const size = (props.size) ? props.size : 64;
15 | const style = { ...(props.style as any), backgroundColor: props.color, width: size, height: size };
16 |
17 | return (
18 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/widgets/ColorPicker.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { View } from "react-native";
6 | import { TouchableRipple, HelperText } from "react-native-paper";
7 |
8 | import TextInput from "./TextInput";
9 | import ColorBox from "./ColorBox";
10 | import { colorHtmlToInt } from "../helpers";
11 |
12 | interface PropsType {
13 | color: string;
14 | defaultColor: string;
15 | label?: string;
16 | placeholder?: string;
17 | error?: string;
18 | onChange: (color: string) => void;
19 | }
20 |
21 | export default function ColorPicker(props: PropsType) {
22 | const colors = [
23 | [
24 | "#F44336",
25 | "#E91E63",
26 | "#673AB7",
27 | "#3F51B5",
28 | "#2196F3",
29 | ],
30 | [
31 | "#03A9F4",
32 | "#4CAF50",
33 | "#8BC34A",
34 | "#FFEB3B",
35 | "#FF9800",
36 | ],
37 | ];
38 | const color = props.color;
39 |
40 | return (
41 |
42 | {colors.map((colorGroup, idx) => (
43 |
44 | {colorGroup.map((colorOption) => (
45 | props.onChange(colorOption)}
49 | >
50 |
51 |
52 | ))}
53 |
54 | ))}
55 |
56 |
57 |
65 |
69 | {props.error}
70 |
71 |
72 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/src/widgets/ConfirmationDialog.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { Keyboard } from "react-native";
6 | import { Card, Portal, Modal, Button, ProgressBar, Paragraph, useTheme } from "react-native-paper";
7 |
8 | import { isPromise, useIsMounted } from "../helpers";
9 |
10 | interface PropsType {
11 | title: string;
12 | children: React.ReactNode | React.ReactNode[];
13 | visible: boolean;
14 | dismissable?: boolean;
15 | onCancel?: () => void;
16 | onOk?: () => void | Promise;
17 | labelCancel?: string;
18 | labelOk?: string;
19 | loading?: boolean;
20 | loadingText?: string;
21 | }
22 |
23 | export default React.memo(function ConfirmationDialog(props: PropsType) {
24 | const isMounted = useIsMounted();
25 | const [loading, setLoading] = React.useState(props.loading ?? false);
26 | const [error, setError] = React.useState(undefined);
27 | const theme = useTheme();
28 | const labelCancel = props.labelCancel ?? "Cancel";
29 | const labelOk = props.labelOk ?? "OK";
30 | const loadingText = props.loadingText ?? "Loading...";
31 | const buttonThemeOverride = { colors: { primary: theme.colors.accent } };
32 |
33 | React.useEffect(() => {
34 | Keyboard.dismiss();
35 | }, [props.visible]);
36 |
37 | function onOk() {
38 | const ret = props.onOk?.();
39 | if (isPromise(ret)) {
40 | // If it's a promise, we update the loading state based on it.
41 | setLoading(true);
42 | ret.catch((e) => {
43 | if (isMounted.current) {
44 | setError(e.toString());
45 | }
46 | }).finally(() => {
47 | if (isMounted.current) {
48 | setLoading(false);
49 | }
50 | });
51 | }
52 | }
53 |
54 | let content: React.ReactNode | React.ReactNode[];
55 | if (error !== undefined) {
56 | content = (
57 | Error: {error.toString()}
58 | );
59 | } else if (loading) {
60 | content = (
61 | <>
62 | {loadingText}
63 |
64 | >
65 | );
66 | } else {
67 | content = props.children;
68 | }
69 |
70 | return (
71 |
72 |
77 |
78 |
79 |
80 | {content}
81 |
82 |
83 | {props.onCancel &&
84 |
85 | }
86 | {!error && props.onOk &&
87 |
88 | }
89 |
90 |
91 |
92 |
93 | );
94 | });
95 |
--------------------------------------------------------------------------------
/src/widgets/Container.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { ViewProps, View } from "react-native";
6 | import { useTheme } from "react-native-paper";
7 |
8 | export default function Container(inProps: React.PropsWithChildren) {
9 | const { style, ...props } = inProps;
10 | const theme = useTheme();
11 |
12 | return (
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/widgets/ErrorDialog.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { Paragraph } from "react-native-paper";
6 |
7 | import ConfirmationDialog from "./ConfirmationDialog";
8 |
9 | interface PropsType {
10 | title?: string;
11 | error?: string;
12 | onOk?: () => void | Promise;
13 | labelOk?: string;
14 | loadingText?: string;
15 | }
16 |
17 | export default React.memo(function ErrorDialog(props: PropsType) {
18 | return (
19 |
27 |
28 | {props.error}
29 |
30 |
31 | );
32 | });
33 |
--------------------------------------------------------------------------------
/src/widgets/ErrorOrLoadingDialog.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 |
6 | import ErrorDialog from "./ErrorDialog";
7 | import ConfirmationDialog from "./ConfirmationDialog";
8 |
9 | interface PropsType {
10 | loading?: boolean;
11 | error?: Error;
12 | onDismiss: () => void;
13 | loadingText?: string;
14 | }
15 |
16 | export default React.memo(function ErrorOrLoadingDialog(props: PropsType) {
17 | if (props.error) {
18 | return (
19 |
23 | );
24 | } else if (props.loading) {
25 | return (
26 |
33 |
34 |
35 | );
36 | }
37 |
38 | return null;
39 | });
40 |
--------------------------------------------------------------------------------
/src/widgets/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { ViewProps, Linking } from "react-native";
6 | import { Button } from "react-native-paper";
7 |
8 | type PropsType = {
9 | href: string;
10 | } & ViewProps;
11 |
12 | class ExternalLink extends React.PureComponent {
13 | public render() {
14 | const { href, children, ...props } = this.props;
15 | return (
16 |
23 | );
24 | }
25 | }
26 |
27 | export default ExternalLink;
28 |
--------------------------------------------------------------------------------
/src/widgets/LoadingIndicator.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { View } from "react-native";
6 | import { ActivityIndicator, Text } from "react-native-paper";
7 |
8 | import Container from "./Container";
9 | export default function LoadingIndicator(_props: any) {
10 | const { style, status, notice, ...props } = _props;
11 | return (
12 |
13 |
14 |
15 | {status && {status}}
16 |
17 | {notice && {notice}}
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/widgets/Markdown.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { Linking } from "react-native";
6 | import { useTheme } from "react-native-paper";
7 | import MarkdownDisplay from "react-native-markdown-display";
8 |
9 | const Markdown = React.memo(function _Markdown(props: { content: string }) {
10 | const theme = useTheme();
11 |
12 | const blockBackgroundColor = (theme.dark) ? "#555555" : "#cccccc";
13 |
14 | return (
15 | {
27 | Linking.openURL(url);
28 | return true;
29 | }}
30 | >
31 | {props.content}
32 |
33 | );
34 | });
35 |
36 | export default Markdown;
37 |
38 |
--------------------------------------------------------------------------------
/src/widgets/PasswordInput.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { View } from "react-native";
6 | import { IconButton } from "react-native-paper";
7 |
8 | import TextInput from "./TextInput";
9 |
10 | const PasswordInput = React.memo(React.forwardRef(function _PasswordInput(inProps: React.ComponentPropsWithoutRef, ref) {
11 | const [isPassword, setIsPassword] = React.useState(true);
12 | const {
13 | style,
14 | ...props
15 | } = inProps;
16 |
17 | return (
18 |
19 |
26 | setIsPassword(!isPassword)}
31 | />
32 |
33 | );
34 | }));
35 |
36 | export default PasswordInput;
37 |
--------------------------------------------------------------------------------
/src/widgets/PrettyFingerprint.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import sjcl from "sjcl";
6 |
7 | import { Paragraph } from "react-native-paper";
8 |
9 | import { byte, base64 } from "etesync";
10 |
11 | function byteArray4ToNumber(bytes: byte[], offset: number) {
12 | // tslint:disable:no-bitwise
13 | return (
14 | ((bytes[offset + 0] & 0xff) * (1 << 24)) +
15 | ((bytes[offset + 1] & 0xff) * (1 << 16)) +
16 | ((bytes[offset + 2] & 0xff) * (1 << 8)) +
17 | ((bytes[offset + 3] & 0xff))
18 | );
19 | }
20 |
21 | function getEncodedChunk(publicKey: byte[], offset: number) {
22 | const chunk = byteArray4ToNumber(publicKey, offset) % 100000;
23 | return chunk.toString().padStart(5, "0");
24 | }
25 |
26 | interface PropsType {
27 | publicKey: base64;
28 | }
29 |
30 | class PrettyFingerprint extends React.PureComponent {
31 | public render() {
32 | const fingerprint = sjcl.codec.bytes.fromBits(
33 | sjcl.hash.sha256.hash(sjcl.codec.base64.toBits(this.props.publicKey))
34 | );
35 |
36 | const spacing = " ";
37 | const prettyPublicKey =
38 | getEncodedChunk(fingerprint, 0) + spacing +
39 | getEncodedChunk(fingerprint, 4) + spacing +
40 | getEncodedChunk(fingerprint, 8) + spacing +
41 | getEncodedChunk(fingerprint, 12) + "\n" +
42 | getEncodedChunk(fingerprint, 16) + spacing +
43 | getEncodedChunk(fingerprint, 20) + spacing +
44 | getEncodedChunk(fingerprint, 24) + spacing +
45 | getEncodedChunk(fingerprint, 28);
46 |
47 | return (
48 | {prettyPublicKey}
49 | );
50 | }
51 | }
52 |
53 | export default PrettyFingerprint;
54 |
--------------------------------------------------------------------------------
/src/widgets/PrettyFingerprintEb.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2017 EteSync Authors
2 | // SPDX-License-Identifier: AGPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { TextProps } from "react-native";
6 | import * as Etebase from "etebase";
7 |
8 | import { Paragraph } from "react-native-paper";
9 |
10 | interface PropsType extends TextProps {
11 | publicKey: Uint8Array;
12 | }
13 |
14 | export default function PrettyFingerprint(props: PropsType) {
15 | const prettyFingerprint = Etebase.getPrettyFingerprint(props.publicKey);
16 |
17 | return (
18 | {prettyFingerprint}
19 | );
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/src/widgets/Row.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { StyleSheet, ViewProps, View } from "react-native";
6 |
7 | class Row extends React.Component {
8 | public render() {
9 | const { children, style } = this.props;
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | }
17 | }
18 |
19 | const styles = StyleSheet.create({
20 | row: {
21 | flexDirection: "row",
22 | },
23 | });
24 |
25 | export default Row;
26 |
--------------------------------------------------------------------------------
/src/widgets/ScrollView.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { ScrollViewProps, ScrollView as NativeScrollView } from "react-native";
6 | import { useTheme } from "react-native-paper";
7 | import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
8 |
9 | export default function ScrollView(inProps: React.PropsWithChildren & { keyboardAware?: boolean }) {
10 | const { keyboardAware, style, ...props } = inProps;
11 | const theme = useTheme();
12 |
13 | const Scroller = (keyboardAware) ? KeyboardAwareScrollView : NativeScrollView;
14 |
15 | return (
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/src/widgets/Select.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { ViewProps } from "react-native";
6 | import { Menu } from "react-native-paper";
7 |
8 | interface PropsType extends ViewProps {
9 | visible: boolean;
10 | anchor: React.ReactNode;
11 | options: T[];
12 | noneString?: string;
13 | titleAccossor?: (item: T) => string;
14 | onChange: (item: T | null) => void;
15 | onDismiss: () => void;
16 | }
17 |
18 | export default function Select(inProps: React.PropsWithChildren>) {
19 | const { visible, anchor, options, onDismiss, noneString, titleAccossor, onChange, ...props } = inProps;
20 |
21 | const getTitle = titleAccossor ?? ((item: T) => item);
22 |
23 | return (
24 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/widgets/Small.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 |
6 | import { Text } from "react-native";
7 |
8 | export default React.memo(function Small(props: React.PropsWithChildren<{}>) {
9 | return (
10 | {props.children}
11 | );
12 | });
13 |
--------------------------------------------------------------------------------
/src/widgets/TextInput.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { TextInput as PaperTextInput, useTheme } from "react-native-paper";
6 |
7 | export default React.memo(React.forwardRef(function PasswordInput(inProps: React.ComponentPropsWithoutRef, ref) {
8 | const theme = useTheme();
9 | const {
10 | style,
11 | ...props
12 | } = inProps;
13 |
14 | return (
15 |
21 | );
22 | }));
23 |
--------------------------------------------------------------------------------
/src/widgets/Typography.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { Subheading as PaperSubheading, Title as PaperTitle, Headline as PaperHeadline } from "react-native-paper";
6 |
7 | export const Subheading = React.memo(function Subheading(props: React.ComponentProps) {
8 | return (
9 |
14 | );
15 | });
16 |
17 | export const Title = React.memo(function Subheading(props: React.ComponentProps) {
18 | return (
19 |
24 | );
25 | });
26 |
27 | export const Headline = React.memo(function Subheading(props: React.ComponentProps) {
28 | return (
29 |
34 | );
35 | });
36 |
--------------------------------------------------------------------------------
/src/widgets/Wizard.tsx:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: © 2019 EteSync Authors
2 | // SPDX-License-Identifier: GPL-3.0-only
3 |
4 | import * as React from "react";
5 | import { ViewProps, View } from "react-native";
6 | import { Button } from "react-native-paper";
7 |
8 | import Container from "./Container";
9 | import ScrollView from "./ScrollView";
10 |
11 | export interface PagePropsType {
12 | prev?: () => void;
13 | next?: () => void;
14 | currentPage: number;
15 | totalPages: number;
16 | }
17 |
18 | export function WizardNavigationBar(props: PagePropsType) {
19 | const first = props.currentPage === 0;
20 | const last = props.currentPage === props.totalPages - 1;
21 |
22 | return (
23 |
24 |
32 |
39 |
40 | );
41 | }
42 |
43 | interface PropsType extends ViewProps {
44 | pages: ((props: PagePropsType) => React.ReactNode)[];
45 | onFinish: () => void;
46 | }
47 |
48 | export default function Wizard(inProps: PropsType) {
49 | const [currentPage, setCurrentPage] = React.useState(0);
50 | const { pages, onFinish, ...props } = inProps;
51 |
52 | const Content = pages[currentPage];
53 |
54 | const first = currentPage === 0;
55 | const last = currentPage === pages.length - 1;
56 | const prev = !first ? () => setCurrentPage(currentPage - 1) : undefined;
57 | const next = !last ? () => setCurrentPage(currentPage + 1) : onFinish;
58 |
59 | return (
60 |
61 |
62 | {Content({ prev, next, currentPage, totalPages: pages.length })}
63 |
64 |
65 | );
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "jsx": "react-native",
5 | "downlevelIteration": true,
6 | "noUnusedLocals": true,
7 | "moduleResolution": "node",
8 | "skipLibCheck": true,
9 | "esModuleInterop": true,
10 | "allowSyntheticDefaultImports": true,
11 | "strict": false,
12 | "noImplicitReturns": true,
13 | "noImplicitAny": true,
14 | "noImplicitThis": true,
15 | "noUnusedParameters": false,
16 | "strictNullChecks": true,
17 | "strictBindCallApply": true,
18 | "strictFunctionTypes": true,
19 | "suppressImplicitAnyIndexErrors": true,
20 | "resolveJsonModule": true,
21 | "isolatedModules": true,
22 | "noEmit": true
23 | }
24 | }
25 |
--------------------------------------------------------------------------------