├── .env.example
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── pull_request_template.md
└── workflows
│ ├── assign-reviewers.yml
│ ├── publish_default.yml
│ ├── publish_develop.yml
│ └── publish_preview.yml
├── .gitignore
├── .storybook
├── main.js
└── preview.js
├── .vscode
└── snipsnap.code-snippets
├── App.tsx
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── STYLEGUIDE.md
├── app.json
├── babel.config.js
├── package-lock.json
├── package.json
├── src
├── assets
│ ├── adaptive-icon.png
│ ├── favicon.png
│ ├── fonts
│ │ ├── Montserrat-Bold.ttf
│ │ ├── Montserrat-Light.ttf
│ │ ├── Montserrat-Medium.ttf
│ │ ├── Montserrat-Regular.ttf
│ │ ├── Montserrat-SemiBold.ttf
│ │ ├── Roboto-Medium.ttf
│ │ └── fonts.ts
│ ├── icon.png
│ ├── icons
│ │ ├── AboutIcon.tsx
│ │ ├── Android254Icon.tsx
│ │ ├── AndroidIcon.tsx
│ │ ├── AppsLabIcon.tsx
│ │ ├── ArrowLeftIcon.tsx
│ │ ├── BackArrowIcon.tsx
│ │ ├── DroidconKeIcon.tsx
│ │ ├── FacebookIcon.tsx
│ │ ├── FeedIcon.tsx
│ │ ├── GoogleIcon.tsx
│ │ ├── HomeIcon.tsx
│ │ ├── LockIcon.tsx
│ │ ├── PolygonIcon.tsx
│ │ ├── SendIcon.png
│ │ ├── SessionListSeparator.tsx
│ │ ├── SessionsIcon.tsx
│ │ ├── ShareIcon.tsx
│ │ ├── SmileyIcon.png
│ │ ├── Star.tsx
│ │ ├── TelegramIcon.tsx
│ │ ├── TiskosIcon.tsx
│ │ ├── TwitterIcon.tsx
│ │ ├── VolumeOff.tsx
│ │ ├── VolumeUp.tsx
│ │ ├── WhatsAppIcon.tsx
│ │ └── images.ts
│ ├── img
│ │ ├── FeedbackBanner.png
│ │ ├── FeedbackBanner@2x.png
│ │ ├── FeedbackBanner@3x.png
│ │ ├── about.png
│ │ ├── about@2x.png
│ │ ├── about@3x.png
│ │ ├── about@4x.png
│ │ ├── andela_landscape_blue.png
│ │ ├── bg_single_speaker.png
│ │ ├── cloud_confetti.png
│ │ ├── cone_confetti.png
│ │ ├── droidcon_ke_banner.png
│ │ ├── droidconkeplaceholder.png
│ │ ├── early_camp.png
│ │ ├── flutter_kenya.png
│ │ ├── google.png
│ │ ├── hover_logo.png
│ │ ├── jetbrains.png
│ │ ├── john_doe.png
│ │ ├── kotlin.png
│ │ ├── profilepicture.png
│ │ └── sessions.png
│ ├── splash.png
│ └── video
│ │ └── video_2022-09-29_22-16-14.mp4
├── components
│ ├── buttons
│ │ ├── PrimaryButton.stories.tsx
│ │ ├── PrimaryButton.tsx
│ │ └── SocialButton.tsx
│ ├── cards
│ │ ├── Card.tsx
│ │ ├── FeedsCard.tsx
│ │ ├── SessionCard.tsx
│ │ ├── SessionsVerticalList.stories.tsx
│ │ ├── SessionsVerticalList.tsx
│ │ ├── SessionsVerticalListCard.stories.tsx
│ │ ├── SessionsVerticalListCard.tsx
│ │ ├── SpeakerCard.stories.tsx
│ │ ├── SpeakerCard.tsx
│ │ ├── SpeakerImageCard.tsx
│ │ └── TeamMemberCard.tsx
│ ├── dateToggle
│ │ ├── DateToggle.stories.tsx
│ │ ├── DateToggle.tsx
│ │ ├── DateToggleList.stories.tsx
│ │ └── DateToggleList.tsx
│ ├── forms
│ │ ├── Input.tsx
│ │ ├── Radio.tsx
│ │ └── Select.tsx
│ ├── iconSwitch
│ │ ├── IconSwitch.tsx
│ │ ├── Thumb.tsx
│ │ └── Track.tsx
│ └── layouts
│ │ ├── BrandLogo.tsx
│ │ ├── DroidconOrganizers.tsx
│ │ ├── DroidconSponsors.tsx
│ │ ├── ImageTextLayout.tsx
│ │ ├── MainHeader.tsx
│ │ └── MainLayout.tsx
├── constants
│ ├── Colors.ts
│ ├── Properties.ts
│ ├── ScreenNames.ts
│ └── SponsorTypes.ts
├── contexts
│ └── ConfigContext.tsx
├── declarations
│ └── react-native-svg.d.ts
├── hooks
│ ├── useCachedResources.ts
│ └── useTypedRedux.ts
├── navigation
│ ├── BottomTabsNavigator.tsx
│ ├── MainStackNavigator.tsx
│ └── StroriesMockNavigation.tsx
├── screens
│ ├── AboutScreen.tsx
│ ├── BioScreen.tsx
│ ├── FeedScreen.tsx
│ ├── FeedbackScreen.tsx
│ ├── HomeScreen.tsx
│ ├── HomeScreenNotLoggedIn.tsx
│ ├── SessionDetailsScreen.tsx
│ ├── SessionsScreen.stories.tsx
│ ├── SessionsScreen.tsx
│ ├── SpeakersScreen.stories.tsx
│ └── SpeakersScreen.tsx
├── services
│ ├── api.ts
│ └── auth.ts
├── state
│ ├── feeds.ts
│ ├── schedule.ts
│ ├── sessions.ts
│ ├── store.ts
│ └── user.ts
├── types
│ ├── Feed.ts
│ ├── Navigation.ts
│ ├── Pagination.ts
│ ├── ReactNativeDotenv.d.ts
│ ├── Room.ts
│ ├── Schedule.ts
│ ├── Session.ts
│ ├── Speaker.ts
│ └── Users.ts
└── utils
│ ├── authStorage.ts
│ ├── calculations.ts
│ └── formatTime.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | GOOGLE_AUTH_CLIENT_ID=YOUR_GOOGLE_AUTH_CLIENT_ID
2 | API_URL=THE_API_URL
3 | EVENT_SLUG=THE_EVENT_SLUG
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
4 |
5 | Fixes # (issue)
6 |
7 | ## Type of change
8 |
9 | - [ ] Bug fix (non-breaking change which fixes an issue)
10 | - [ ] New feature (non-breaking change which adds functionality)
11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
12 | - [ ] This change requires a documentation update
13 |
14 | # How Has This Been Tested?
15 |
16 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
17 |
18 | - [ ] Test A
19 | - [ ] Test B
20 |
21 | # Checklist:
22 |
23 | - [ ] My code follows the style guidelines of this project
24 | - [ ] I have performed a self-review of my own code
25 | - [ ] I have commented my code, particularly in hard-to-understand areas
26 | - [ ] I have made corresponding changes to the documentation
27 | - [ ] My changes generate no new warnings
28 | - [ ] I have added tests that prove my fix is effective or that my feature works
29 | - [ ] New and existing unit tests pass locally with my changes
30 | - [ ] Any dependent changes have been merged and published in downstream modules
31 |
--------------------------------------------------------------------------------
/.github/workflows/assign-reviewers.yml:
--------------------------------------------------------------------------------
1 | name: Assign reviewers to PR
2 |
3 | on:
4 | pull_request:
5 | types: [opened, ready_for_review, reopened, review_requested, review_request_removed]
6 |
7 | jobs:
8 | assign-pr-reviewers:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: "Assign Reviewers to PR"
12 | uses: itsOliverBott/assign-pr-reviewers@release
13 | with:
14 | token: ${{ secrets.GITHUB_TOKEN }}
15 | users: "brianwachira, wafulasam, orama254, makunomark"
16 | ignore-drafts: true
17 |
--------------------------------------------------------------------------------
/.github/workflows/publish_default.yml:
--------------------------------------------------------------------------------
1 | name: Publish App
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | publish:
10 | name: 🚀 Publish App to Default Channel
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: 🏗 Setup repo
14 | uses: actions/checkout@v3
15 |
16 | - name: 🏗 Setup Node
17 | uses: actions/setup-node@v3
18 | with:
19 | node-version: 14.x
20 | cache: npm
21 |
22 | - name: 🏗 Setup Expo
23 | uses: expo/expo-github-action@v7
24 | with:
25 | expo-version: latest
26 | token: ${{ secrets.EXPO_TOKEN }}
27 |
28 | - name: 📦 Install dependencies
29 | run: npm install
30 |
31 | - name: 🚀 Publish app
32 | run: expo publish --non-interactive
33 |
--------------------------------------------------------------------------------
/.github/workflows/publish_develop.yml:
--------------------------------------------------------------------------------
1 | name: Publish Develop App
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 |
8 | jobs:
9 | publish:
10 | name: 🚀 Publish App to Develop Channel
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: 🏗 Setup repo
14 | uses: actions/checkout@v3
15 |
16 | - name: 🏗 Setup Node
17 | uses: actions/setup-node@v3
18 | with:
19 | node-version: 14.x
20 | cache: npm
21 |
22 | - name: 🏗 Setup Expo
23 | uses: expo/expo-github-action@v7
24 | with:
25 | expo-version: latest
26 | token: ${{ secrets.EXPO_TOKEN }}
27 |
28 | - name: 📦 Install dependencies
29 | run: npm install
30 |
31 | - name: 🚀 Publish develop app
32 | run: expo publish --release-channel=develop --non-interactive
33 |
--------------------------------------------------------------------------------
/.github/workflows/publish_preview.yml:
--------------------------------------------------------------------------------
1 | name: Publish Preview
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | publish:
7 | name: 🚀 Publish PR to Release Channel
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: 🏗 Setup repo
11 | uses: actions/checkout@v3
12 |
13 | - name: 🏗 Setup Node
14 | uses: actions/setup-node@v3
15 | with:
16 | node-version: 14.x
17 | cache: npm
18 |
19 | - name: 🏗 Setup Expo
20 | uses: expo/expo-github-action@v7
21 | with:
22 | expo-version: latest
23 | token: ${{ secrets.EXPO_TOKEN }}
24 |
25 | - name: 📦 Install dependencies
26 | run: npm install
27 |
28 | - name: 🚀 Publish preview
29 | run: expo publish --release-channel=pr-${{ github.event.number }} --non-interactive
30 |
31 | - name: 💬 Comment preview
32 | uses: expo/expo-github-action/preview-comment@v7
33 | with:
34 | channel: pr-${{ github.event.number }}
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
16 | # env files
17 | .env*
18 | .env.development
19 | .env.staging
20 | .env.production
21 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
3 | addons: [
4 | "@storybook/addon-links",
5 | "@storybook/addon-essentials",
6 | "@storybook/addon-interactions",
7 | "@storybook/addon-react-native-web",
8 | ],
9 | framework: "@storybook/react",
10 | webpackFinal: async config => {
11 | config.resolve.alias = {
12 | ...(config.resolve.alias || {}),
13 | // Transform all direct `react-native` imports to `react-native-web`
14 | 'react-native$': 'react-native-web',
15 | // make sure we're rendering output using **web** Storybook not react-native
16 | '@storybook/react-native': '@storybook/react',
17 | // plugin-level react-native-web extensions
18 | 'react-native-svg': 'react-native-svg/lib/commonjs/ReactNativeSVG.web',
19 | // ...
20 | };
21 |
22 | // handle SVG support inside Storybook
23 | const fileLoaderRule = config.module.rules.find(rule =>
24 | rule.test.test('.svg'),
25 | );
26 | fileLoaderRule.exclude = /\.svg$/;
27 | config.module.rules.push({
28 | test: /\.svg$/,
29 | loader: 'svg-react-loader',
30 | });
31 | return config;
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import { Provider } from "react-redux";
2 | import { store } from "../src/state/store";
3 | import { NavigationContainer } from "@react-navigation/native";
4 |
5 | export const parameters = {
6 | actions: { argTypesRegex: "^on[A-Z].*" },
7 | controls: {
8 | matchers: {
9 | color: /(background|color)$/i,
10 | date: /Date$/,
11 | },
12 | },
13 | };
14 |
15 | export const decorators = [
16 | (Story) => (
17 |
18 |
19 |
20 |
21 |
22 | ),
23 | ];
24 |
--------------------------------------------------------------------------------
/.vscode/snipsnap.code-snippets:
--------------------------------------------------------------------------------
1 |
2 |
404 Not Found
3 |
4 | 404 Not Found
5 | nginx
6 |
7 |
8 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Provider } from "react-redux";
3 | import { store } from "./src/state/store";
4 | import { NavigationContainer } from "@react-navigation/native";
5 | import useCachedResources from "./src/hooks/useCachedResources";
6 | import MainStackNavigator from "./src/navigation/MainStackNavigator";
7 |
8 |
9 | export default function App() {
10 | // Load cached resources before the app starts.
11 | const isLoadingComplete = useCachedResources();
12 |
13 | // Show nothing ( but the splashscreen ) till cached resources load.
14 | if (!isLoadingComplete) {
15 | return null;
16 | }
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # DroidconKe-RN Code of Conduct
2 |
3 | ### Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 |
8 |
9 |
10 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
11 |
12 |
13 |
14 |
15 | ### Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our community include:
18 |
19 |
20 |
21 |
22 | Demonstrating empathy and kindness toward other people
23 |
24 | Being respectful of differing opinions, viewpoints, and experiences
25 |
26 | Giving and gracefully accepting constructive feedback
27 |
28 | Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
29 |
30 | Focusing on what is best not just for us as individuals, but for the overall community
31 |
32 | Examples of unacceptable behavior include:
33 |
34 |
35 |
36 |
37 | The use of sexualized language or imagery, and sexual attention or advances of any kind
38 |
39 | Trolling, insulting or derogatory comments, and personal or political attacks
40 |
41 | Public or private harassment
42 |
43 | Publishing others' private information, such as a physical or email address, without their explicit permission
44 |
45 | Other conduct which could reasonably be considered inappropriate in a professional setting
46 |
47 | ### Enforcement Responsibilities
48 |
49 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
50 |
51 |
52 |
53 |
54 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
55 |
56 |
57 |
58 |
59 | ### Scope
60 |
61 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
62 |
63 |
64 |
65 |
66 | ### Enforcement
67 |
68 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [Android254](https://twitter.com/254androiddevs). All complaints will be reviewed and investigated promptly and fairly.
69 |
70 |
71 |
72 |
73 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
74 |
75 |
76 |
77 |
78 | ### Enforcement Guidelines
79 |
80 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
81 |
82 |
83 |
84 |
85 | 1. Correction
86 |
87 | Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
88 |
89 |
90 |
91 |
92 | Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
93 |
94 |
95 |
96 |
97 | 2. Warning
98 |
99 | Community Impact: A violation through a single incident or series of actions.
100 |
101 |
102 |
103 |
104 | Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
105 |
106 |
107 |
108 |
109 | 3. Temporary Ban
110 |
111 | Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
112 |
113 |
114 |
115 |
116 | Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
117 |
118 |
119 |
120 |
121 | 4. Permanent Ban
122 |
123 | Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
124 |
125 |
126 |
127 |
128 | Consequence: A permanent ban from any sort of public interaction within the community.
129 |
130 |
131 |
132 |
133 | ### Attribution
134 |
135 | This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
136 |
137 |
138 |
139 |
140 | Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder.
141 |
142 |
143 |
144 |
145 | For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to DroidconKe-RN App
2 |
3 |
4 | You can contribute to the DroidconKe-RN app by beta testing or submitting code.
5 | If you plan to make a contribution please do so through [our detailed contribution workflow.](#contribution-steps). You can also join us on Slack to discuss ideas.
6 |
7 |
8 | When submitting code, please make every effort to follow existing [conventions and style](https://github.com/droidconKE/droidconKE2022ReactNative/blob/main/STYLEGUIDE.md) in order to keep the code as readable as possible.
9 |
10 |
11 |
12 |
13 | Please note we have a [code of conduct](https://github.com/droidconKE/droidconKE2022ReactNative/blob/main/CODE_OF_CONDUCT.md), follow it in all your interactions with the project.
14 |
15 |
16 | ## Submitting a PR
17 |
18 | - For every PR there should be an accompanying [issue](https://github.com/droidconKE/droidconKE2022ReactNative/issues) which the PR solves
19 |
20 | - The PR itself should only contain code which is the solution for the given issue
21 |
22 | - If you are a first time contributor check if there is a [good first issue](https://github.com/droidconKE/droidconKE2022ReactNative/labels/good%20first%20issue) for you
23 |
24 |
25 |
26 |
27 | ## Contribution steps
28 |
29 |
30 |
31 |
32 | 1. Fork this repository to your own repositiry.
33 |
34 | 2. Clone the forked repository to your local machine.
35 |
36 | 3. Run it locally with our [guide](#how-to-run-locally)
37 |
38 | 4. Create your feature branch: `git checkout -b feature-my-new-feature`
39 |
40 | 5. Make changes to the project.
41 |
42 | 6. Commit your changes: `git commit -m 'Add some feature'`
43 |
44 | 7. Push to the branch: `git push origin feature-my-new-feature`
45 |
46 | 8. Submit a pull request :D
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | ## How To Run locally?
56 | ### Install
57 |
58 | To install the project, navigate to the directory and run:
59 |
60 | - `npm install -g expo-cli`
61 | - `npm install`
62 |
63 | ### Setup
64 | - Create `.env.development` file and copy the contents from `.env.example`
65 | - Add Google Client ID to your `.env.development`. Follow [https://docs.expo.dev/guides/authentication/#google](https://docs.expo.dev/guides/authentication/#google) on how to get a Google Client ID
66 |
67 | ### Run
68 |
69 | To run the project, run the following commands:
70 |
71 | - `npm run android`
72 | - `npm run ios`
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | ## Financial contributions
97 |
98 | We also welcome financial contributions. It helps us to grow better and faster.
99 |
100 |
101 |
102 |
103 | ## License
104 |
105 |
106 |
107 |
108 | By contributing your code, you agree to license your contribution under the terms of the [MIT License](https://github.com/droidconKE/droidconKE2022ReactNative/blob/main/LICENSE) license.
109 |
110 | All files are released with the MIT License license.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 droidconKE
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # droidconKE2022ReactNative
2 |
3 | React Native App for Kenya's 3rd Android Developer Conference - droidcon to be held in Nairobi from November 16-18th 2022.
4 |
5 | ## Run the published app on your phone
6 |
7 | Download the [Expo Go](https://expo.dev/client) Android / iOS app and scan this QR code to run the app on your phone.
8 |
9 | ### Production app
10 |
11 |
12 |
13 | ### Development app
14 |
15 |
16 |
17 | # Tech Stack
18 |
19 | - Expo - [https://docs.expo.dev/](https://docs.expo.dev/)
20 | - Typescript - [https://www.typescriptlang.org/](https://www.typescriptlang.org/)
21 |
22 | # Dependencies
23 |
24 | # Features
25 |
26 | - Authentication
27 | - Home
28 | - About
29 | - Sponsors
30 | - Sessions
31 | - Speakers
32 | - Feed
33 | - Feedback
34 |
35 | # Designs
36 |
37 | This is the link to app designs:
38 | Light Theme: [https://xd.adobe.com/view/dd5d0245-b92b-4678-9d4a-48b3a6f48191-880e/](https://xd.adobe.com/view/dd5d0245-b92b-4678-9d4a-48b3a6f48191-880e/)
39 | Dark Theme: [https://xd.adobe.com/view/5ec235b6-c3c6-49a9-b783-1f1303deb1a8-0b91/](https://xd.adobe.com/view/5ec235b6-c3c6-49a9-b783-1f1303deb1a8-0b91/)
40 |
41 | ## Contributing
42 |
43 | Contributions are always welcome!
44 |
45 | See [`CONTRIBUTING.md`](CONTRIBUTING.md) for ways to get started.
46 |
--------------------------------------------------------------------------------
/STYLEGUIDE.md:
--------------------------------------------------------------------------------
1 | # Style Guide for Droidconke-RN app
2 |
3 | Here are the style rules to follow:
4 |
5 | ## #1 Be consistent with the rest of the codebase
6 |
7 | This is the number one rule and should help determine what to do in most cases.
8 |
9 | ## #2 Respect Prettier and Linter rules
10 |
11 | We use a linter and prettier to automatically help you make style guide decisions easy.
12 |
13 | ## #3 File Name
14 |
15 | Generally file names are PascalCase if they are components or classes, and camelCase otherwise. Filenames' extension must be .tsx for component files and .ts otherwise.
16 |
17 | ## #4 Respect Google JavaScript style guide
18 |
19 | The style guide accessible
20 | [here](https://google.github.io/styleguide/jsguide.html) should be
21 | respected. However, if a rule is not consistent with the rest of the codebase,
22 | rule #1 takes precedence. Same thing goes with any of the above rules taking precedence over this rule.
23 |
24 | ## #5 Follow these grammar rules
25 |
26 | - Functions descriptions have to start with a verb using the third person of the
27 | singular.
28 | - _Ex: `/\*\* Tests the validity of the input. _/`\*
29 | - Inline comments within procedures should always use the imperative.
30 | - _Ex: `// Check whether the value is true.`_
31 | - Acronyms have to be uppercased in comments.
32 | - _Ex: `// IP, DOM, CORS, URL...`_
33 | - _Exception: Identity Provider = IdP_
34 | - Acronyms have to be capitalized (but not uppercased) in variable names.
35 | - _Ex: `redirectUrl()`, `signInIdp()`_
36 | - Never use login/log in in comments. Use “sign-in” if it’s a noun, “sign in” if
37 | it’s a verb. The same goes for the variable name. Never use `login`; always use
38 | `signIn`.
39 | - _Ex: `// The sign-in method.`_
40 | - _Ex: `// Signs in the user.`_
41 | - Always start an inline comment with a capital (unless referring to the name of
42 | a variable/function), and end it with a period.
43 | - _Ex: `// This is a valid inline comment.`_
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "droidconKE2022ReactNative",
4 | "slug": "droidconKE2022ReactNative",
5 | "version": "1.0.0",
6 | "owner": "reactdevske-reactnative",
7 | "orientation": "portrait",
8 | "icon": "./src/assets/icon.png",
9 | "userInterfaceStyle": "light",
10 | "splash": {
11 | "image": "./src/assets/splash.png",
12 | "resizeMode": "contain",
13 | "backgroundColor": "#FFFFFF"
14 | },
15 | "updates": {
16 | "fallbackToCacheTimeout": 0
17 | },
18 | "assetBundlePatterns": ["**/*"],
19 | "ios": {
20 | "supportsTablet": true
21 | },
22 | "android": {
23 | "adaptiveIcon": {
24 | "foregroundImage": "./src/assets/adaptive-icon.png",
25 | "backgroundColor": "#FFFFFF"
26 | }
27 | },
28 | "web": {
29 | "favicon": "./src/assets/favicon.png"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | "plugins": [
6 | ["module:react-native-dotenv", {
7 | "moduleName": "react-native-dotenv",
8 | "envName": "APP_ENV",
9 | "moduleName": "@env",
10 | "path": ".env",
11 | "blocklist": null,
12 | "allowlist": null,
13 | "safe": false,
14 | "allowUndefined": true,
15 | "verbose": false
16 | }]
17 | ]
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "droidconke2022reactnative",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web",
10 | "storybook": "start-storybook -p 6006",
11 | "build-storybook": "build-storybook"
12 | },
13 | "dependencies": {
14 | "@react-navigation/bottom-tabs": "^6.3.3",
15 | "@react-navigation/native": "^6.0.12",
16 | "@react-navigation/native-stack": "^6.8.0",
17 | "@reduxjs/toolkit": "^1.8.5",
18 | "expo": "~46.0.9",
19 | "expo-application": "^4.2.2",
20 | "expo-auth-session": "^3.7.1",
21 | "expo-av": "~12.0.4",
22 | "expo-font": "~10.2.1",
23 | "expo-random": "^12.3.0",
24 | "expo-splash-screen": "~0.16.2",
25 | "expo-status-bar": "~1.4.0",
26 | "expo-updates": "~0.14.6",
27 | "react": "18.0.0",
28 | "react-native": "0.69.6",
29 | "react-native-dotenv": "^3.3.1",
30 | "react-native-safe-area-context": "4.3.1",
31 | "react-native-screens": "~3.15.0",
32 | "react-native-svg": "12.3.0",
33 | "react-redux": "^8.0.2",
34 | "@react-native-async-storage/async-storage": "~1.17.3"
35 | },
36 | "devDependencies": {
37 | "@babel/core": "^7.12.9",
38 | "@storybook/addon-actions": "^6.5.12",
39 | "@storybook/addon-essentials": "^6.5.12",
40 | "@storybook/addon-interactions": "^6.5.12",
41 | "@storybook/addon-links": "^6.5.12",
42 | "@storybook/addon-react-native-web": "0.0.18",
43 | "@storybook/builder-webpack4": "^6.5.12",
44 | "@storybook/manager-webpack4": "^6.5.12",
45 | "@storybook/react": "^6.5.12",
46 | "@storybook/testing-library": "^0.0.13",
47 | "@types/react": "^18.0.18",
48 | "@types/react-native": "~0.69.1",
49 | "@types/react-redux": "^7.1.24",
50 | "babel-loader": "^8.2.5",
51 | "babel-plugin-react-native-web": "^0.18.9",
52 | "react-dom": "^18.0.0",
53 | "react-native-web": "^0.18.9",
54 | "svg-react-loader": "^0.4.6",
55 | "typescript": "~4.3.5"
56 | },
57 | "private": true,
58 | "engines": {
59 | "node": "14.7.0",
60 | "npm": "^7.0.0"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/src/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/favicon.png
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/fonts/Montserrat-Bold.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/fonts/Montserrat-Light.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/fonts/Montserrat-Medium.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/fonts/Montserrat-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/fonts/Montserrat-SemiBold.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Roboto-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/fonts/Roboto-Medium.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/fonts.ts:
--------------------------------------------------------------------------------
1 | // placeholder file
2 | // Fonts to be used on the app.
3 | export enum fonts {
4 | MONTSERRAT_LIGHT = "montserrat-light",
5 | MONTSERRAT_REGULAR = "montserrat-regular",
6 | MONTSERRAT_MEDIUM = "montserrat-medium",
7 | MONTSERRAT_SEMIBOLD = "montserrat-semibold",
8 | MONTSERRAT_BOLD = "montserrat-bold",
9 | ROBOTO_MEDIUM = "roboto-medium"
10 | }
--------------------------------------------------------------------------------
/src/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/icon.png
--------------------------------------------------------------------------------
/src/assets/icons/AboutIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path } from "react-native-svg"
3 |
4 | const AboutIcon = (props: SvgProps) => (
5 |
11 |
12 |
13 |
18 |
19 |
20 |
21 | )
22 |
23 | export default AboutIcon
24 |
--------------------------------------------------------------------------------
/src/assets/icons/Android254Icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path } from "react-native-svg"
3 |
4 | const Android254Icon = (props: SvgProps) => (
5 |
12 |
13 |
18 |
23 |
28 |
33 |
38 |
43 |
48 |
53 |
58 |
63 |
68 |
73 |
78 |
79 |
80 | )
81 |
82 | export default Android254Icon
83 |
--------------------------------------------------------------------------------
/src/assets/icons/AndroidIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, Rect, Path } from "react-native-svg"
3 |
4 | const AndroidIcon = (props: SvgProps) => (
5 |
11 |
15 |
16 | )
17 |
18 | export default AndroidIcon
--------------------------------------------------------------------------------
/src/assets/icons/AppsLabIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path, Circle } from "react-native-svg"
3 |
4 | const AppsLabIcon = (props: SvgProps) => (
5 |
11 |
12 |
13 |
18 |
23 |
28 |
36 |
44 |
52 |
57 |
58 |
59 |
60 | )
61 |
62 | export default AppsLabIcon
63 |
--------------------------------------------------------------------------------
/src/assets/icons/ArrowLeftIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path } from "react-native-svg"
3 |
4 | const ArrowLeftIcon = (props: SvgProps) => (
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 | )
18 |
19 | export default ArrowLeftIcon
20 |
--------------------------------------------------------------------------------
/src/assets/icons/BackArrowIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path , Rect } from "react-native-svg"
3 | import { colors } from "../../constants/Colors";
4 |
5 | const BackArrowIcon = (props : SvgProps ) =>
6 | (
7 |
12 |
13 |
14 |
19 |
23 |
24 |
25 |
26 | )
27 |
28 |
29 | export default BackArrowIcon
--------------------------------------------------------------------------------
/src/assets/icons/DroidconKeIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, {
3 | SvgProps,
4 | Defs,
5 | ClipPath,
6 | Path,
7 | G,
8 | Text,
9 | TSpan,
10 | } from "react-native-svg"
11 |
12 | const DroidconKeIcon = (props: SvgProps) => (
13 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
82 |
83 | {"K"}
84 |
85 | {"e."}
86 |
87 |
88 |
89 | )
90 |
91 | export default DroidconKeIcon
92 |
--------------------------------------------------------------------------------
/src/assets/icons/FacebookIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, Path } from "react-native-svg"
3 |
4 | const FacebookIcon = (props: SvgProps) => (
5 |
10 |
11 |
17 |
18 | )
19 |
20 | export default FacebookIcon
--------------------------------------------------------------------------------
/src/assets/icons/FeedIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path } from "react-native-svg"
3 |
4 | const FeedIcon = (props: SvgProps) => (
5 |
12 |
13 |
18 |
19 |
20 | )
21 |
22 | export default FeedIcon
23 |
--------------------------------------------------------------------------------
/src/assets/icons/GoogleIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, Path } from "react-native-svg"
3 |
4 | const GoogleIcon = (props: SvgProps) => (
5 |
11 |
15 |
19 |
23 |
27 |
28 | )
29 |
30 | export default GoogleIcon
31 |
--------------------------------------------------------------------------------
/src/assets/icons/HomeIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path } from "react-native-svg"
3 |
4 | const HomeIcon = (props: SvgProps) => (
5 |
12 |
13 |
18 |
19 |
20 | )
21 |
22 | export default HomeIcon;
23 |
--------------------------------------------------------------------------------
/src/assets/icons/LockIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path } from "react-native-svg"
3 |
4 | const LockIcon = (props: SvgProps) => (
5 |
12 |
13 |
18 |
23 |
24 |
25 | )
26 |
27 | export default LockIcon
28 |
--------------------------------------------------------------------------------
/src/assets/icons/PolygonIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, Path } from "react-native-svg"
3 |
4 | const PolygonIcon = (props: SvgProps) => (
5 |
6 |
11 |
12 | )
13 |
14 | export default PolygonIcon
15 |
--------------------------------------------------------------------------------
/src/assets/icons/SendIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/icons/SendIcon.png
--------------------------------------------------------------------------------
/src/assets/icons/SessionListSeparator.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Svg, { Ellipse, G, Line, SvgProps } from "react-native-svg";
3 |
4 | const SessionListSeparator = (props: SvgProps) => (
5 |
11 |
12 |
21 |
31 |
32 |
33 | );
34 |
35 | export default SessionListSeparator;
36 |
--------------------------------------------------------------------------------
/src/assets/icons/SessionsIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path } from "react-native-svg"
3 |
4 | const SessionsIcon = (props: SvgProps) => (
5 |
12 |
13 |
14 |
15 |
20 |
21 |
22 | )
23 |
24 | export default SessionsIcon
25 |
--------------------------------------------------------------------------------
/src/assets/icons/ShareIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path } from "react-native-svg"
3 |
4 | const ShareIcon = (props: SvgProps) => (
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 | )
18 |
19 | export default ShareIcon
20 |
--------------------------------------------------------------------------------
/src/assets/icons/SmileyIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/icons/SmileyIcon.png
--------------------------------------------------------------------------------
/src/assets/icons/Star.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Svg, { SvgProps, G, Path } from "react-native-svg";
3 |
4 | const Star = (props: SvgProps) => (
5 |
13 |
14 |
15 |
16 |
21 |
27 |
28 |
29 |
30 | );
31 |
32 | export default Star;
33 |
--------------------------------------------------------------------------------
/src/assets/icons/TelegramIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, Path } from "react-native-svg"
3 |
4 | const TelegramIcon = (props: SvgProps) => (
5 |
9 |
11 |
12 | )
13 |
14 | export default TelegramIcon
--------------------------------------------------------------------------------
/src/assets/icons/TiskosIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path } from "react-native-svg"
3 |
4 | const TiskosIcon = (props: SvgProps) => (
5 |
12 |
13 |
14 |
18 |
22 |
26 |
30 |
34 |
35 |
36 |
37 |
38 |
43 |
44 |
45 |
50 |
51 |
52 |
57 |
58 |
59 |
64 |
65 |
66 |
71 |
72 |
73 |
78 |
79 |
80 |
81 |
82 |
83 | )
84 |
85 | export default TiskosIcon
86 |
--------------------------------------------------------------------------------
/src/assets/icons/TwitterIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, G, Path } from "react-native-svg"
3 |
4 | const TwitterIcon = (props: SvgProps) => (
5 |
11 |
12 |
16 |
17 |
18 | )
19 |
20 | export default TwitterIcon;
21 |
--------------------------------------------------------------------------------
/src/assets/icons/VolumeOff.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, Path } from "react-native-svg"
3 |
4 | const VolumeOff = (props: SvgProps) => (
5 |
11 |
12 |
13 | )
14 |
15 | export default VolumeOff
16 |
--------------------------------------------------------------------------------
/src/assets/icons/VolumeUp.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, Path } from "react-native-svg"
3 |
4 | const VolumeUp = (props: SvgProps) => (
5 |
11 |
12 |
13 | )
14 |
15 | export default VolumeUp
16 |
--------------------------------------------------------------------------------
/src/assets/icons/WhatsAppIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import Svg, { SvgProps, Path } from "react-native-svg"
3 |
4 | const WhatsappIcon = (props: SvgProps) => (
5 |
9 |
11 |
12 | )
13 |
14 | export default WhatsappIcon
--------------------------------------------------------------------------------
/src/assets/icons/images.ts:
--------------------------------------------------------------------------------
1 | // placeholder file
--------------------------------------------------------------------------------
/src/assets/img/FeedbackBanner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/FeedbackBanner.png
--------------------------------------------------------------------------------
/src/assets/img/FeedbackBanner@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/FeedbackBanner@2x.png
--------------------------------------------------------------------------------
/src/assets/img/FeedbackBanner@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/FeedbackBanner@3x.png
--------------------------------------------------------------------------------
/src/assets/img/about.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/about.png
--------------------------------------------------------------------------------
/src/assets/img/about@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/about@2x.png
--------------------------------------------------------------------------------
/src/assets/img/about@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/about@3x.png
--------------------------------------------------------------------------------
/src/assets/img/about@4x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/about@4x.png
--------------------------------------------------------------------------------
/src/assets/img/andela_landscape_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/andela_landscape_blue.png
--------------------------------------------------------------------------------
/src/assets/img/bg_single_speaker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/bg_single_speaker.png
--------------------------------------------------------------------------------
/src/assets/img/cloud_confetti.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/cloud_confetti.png
--------------------------------------------------------------------------------
/src/assets/img/cone_confetti.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/cone_confetti.png
--------------------------------------------------------------------------------
/src/assets/img/droidcon_ke_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/droidcon_ke_banner.png
--------------------------------------------------------------------------------
/src/assets/img/droidconkeplaceholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/droidconkeplaceholder.png
--------------------------------------------------------------------------------
/src/assets/img/early_camp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/early_camp.png
--------------------------------------------------------------------------------
/src/assets/img/flutter_kenya.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/flutter_kenya.png
--------------------------------------------------------------------------------
/src/assets/img/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/google.png
--------------------------------------------------------------------------------
/src/assets/img/hover_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/hover_logo.png
--------------------------------------------------------------------------------
/src/assets/img/jetbrains.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/jetbrains.png
--------------------------------------------------------------------------------
/src/assets/img/john_doe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/john_doe.png
--------------------------------------------------------------------------------
/src/assets/img/kotlin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/kotlin.png
--------------------------------------------------------------------------------
/src/assets/img/profilepicture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/profilepicture.png
--------------------------------------------------------------------------------
/src/assets/img/sessions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/img/sessions.png
--------------------------------------------------------------------------------
/src/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/splash.png
--------------------------------------------------------------------------------
/src/assets/video/video_2022-09-29_22-16-14.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/assets/video/video_2022-09-29_22-16-14.mp4
--------------------------------------------------------------------------------
/src/components/buttons/PrimaryButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ComponentStory, ComponentMeta } from "@storybook/react";
3 | import PrimaryButton from "./PrimaryButton";
4 |
5 | export default {
6 | title: "DroidconKe/Example/Button",
7 | component: PrimaryButton,
8 | parameters: {
9 | // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
10 | layout: "centered",
11 | },
12 | } as ComponentMeta;
13 |
14 | const Template: ComponentStory = (_) => ;
15 |
16 | export const PrimaryButtonStory = Template.bind({});
17 |
--------------------------------------------------------------------------------
/src/components/buttons/PrimaryButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button } from "react-native";
3 |
4 | export default function PrimaryButton(): JSX.Element {
5 | function dummyOnPress() {
6 | console.log("Primary button pressed");
7 | }
8 |
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/buttons/SocialButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { View, Text, TouchableOpacity , StyleSheet, Alert, GestureResponderEvent} from "react-native";
3 | import TwitterIcon from "../../assets/icons/TwitterIcon";
4 | import WhatsappIcon from "../../assets/icons/WhatsAppIcon";
5 | import TelegramIcon from "../../assets/icons/TelegramIcon";
6 | import { colors } from "../../constants/Colors";
7 | import { fonts } from "../../assets/fonts/fonts";
8 | import FacebookIcon from "../../assets/icons/FacebookIcon";
9 |
10 | export type SocialMedia = "WhatsApp" | "Facebook" | "Telegram" | "Twitter"
11 |
12 | const getSocialMediaIcon = (socialmedia : SocialMedia) : JSX.Element => {
13 | switch(socialmedia) {
14 | case "Twitter" :
15 | return ( )
16 | case "Telegram":
17 | return ()
18 | case "WhatsApp":
19 | return ( )
20 | case "Facebook":
21 | return ()
22 | default:
23 | throw new Error("Social Media Icon not yet implemented")
24 | }
25 | }
26 |
27 | const getOnclickAction = (socialmedia : SocialMedia) : ((event : GestureResponderEvent) => void )=> {
28 | const twitteronclick = () => {Alert.alert("Twitter clicked")}
29 | const telegramonclick = () => {Alert.alert("Telegram clicked")}
30 | const whatsapponclick = () => {Alert.alert("Whatsapp clicked")}
31 | const facebookonclick = () => {Alert.alert("Facebook clicked")}
32 | switch(socialmedia){
33 | case "Twitter":
34 | return twitteronclick
35 | case "Telegram":
36 | return telegramonclick
37 | case "WhatsApp":
38 | return whatsapponclick
39 | case "Facebook":
40 | return facebookonclick
41 | default:
42 | throw new Error("Social media on click action not yet implemented")
43 | }
44 | }
45 |
46 | export default function SocialButton(props : {socialmedia : SocialMedia}): JSX.Element {
47 | const SocialMediaIcon = getSocialMediaIcon(props.socialmedia)
48 | return(
49 |
50 |
51 | {SocialMediaIcon}
52 |
53 |
54 | {props.socialmedia}
55 |
56 |
57 | )
58 | }
59 |
60 | const styles = StyleSheet.create({
61 | socialButtonContainer: {
62 | height : "100%" ,
63 | width : "42%" ,
64 | borderWidth :1 ,
65 | borderColor : colors.DROIDCONKE_LIGHT_GREEN,
66 | borderRadius : 10 ,
67 | flexDirection : "row",
68 | alignItems : "center"
69 | },
70 | mediaIconContainer : {
71 | marginLeft : 20 ,
72 | marginRight : 20
73 | },
74 | socialMediaName : {
75 | fontFamily : fonts.MONTSERRAT_REGULAR,
76 | fontSize : 16,
77 | textAlign : "left",
78 | color : colors.DROIDCONKE_BLACK_TEXT_AND_LABEL,
79 | marginRight : 10
80 | }
81 | });
82 |
--------------------------------------------------------------------------------
/src/components/cards/Card.tsx:
--------------------------------------------------------------------------------
1 | // card
2 |
--------------------------------------------------------------------------------
/src/components/cards/FeedsCard.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, TouchableOpacity, Image, TouchableHighlight } from "react-native";
2 | import React from "react";
3 | import { colors } from "../../constants/Colors";
4 | import { fonts } from "../../assets/fonts/fonts";
5 | import { formatDate } from "../../utils/formatTime";
6 | import FeedIcon from "../../assets/icons/FeedIcon";
7 | import ShareIcon from "../../assets/icons/ShareIcon";
8 |
9 | interface Feed {
10 | body: string,
11 | image: string | null | undefined,
12 | created_at: string
13 | }
14 | interface FeedCardProps {
15 | body: string;
16 | image: string | null | undefined;
17 | created_at: string;
18 | handleShare: (item: Feed) => void;
19 | }
20 |
21 | const FeedsCard = ({ body, image, created_at, handleShare }: FeedCardProps) => {
22 | return (
23 |
24 | {body}
25 |
31 |
32 | handleShare({body, image, created_at})}>
33 | Share
34 |
35 |
36 | {formatDate(created_at)}
37 |
38 |
39 | );
40 | };
41 |
42 | export default FeedsCard;
43 |
44 | const styles = StyleSheet.create({
45 | container: {
46 | paddingHorizontal: 1,
47 | borderBottomColor: colors.DROIDCONKE_BORDER_BOTTOM,
48 | borderBottomWidth: 1,
49 | padding: 25,
50 | },
51 | post_details: {
52 | flexDirection: "row",
53 | justifyContent: "space-between",
54 | marginTop: 5,
55 | fontSize: 16,
56 | alignItems: "center",
57 | },
58 | text: {
59 | fontFamily: fonts.MONTSERRAT_REGULAR,
60 | fontSize: 16,
61 | color: colors.DROIDCONKE_BLACK,
62 | },
63 | image: {
64 | width: '100%',
65 | height: 200,
66 | borderRadius: 10,
67 | marginBottom: 10,
68 | marginTop: 10,
69 | },
70 | share_btn: {
71 | color: colors.DROIDCONKE_BLUE,
72 | fontSize: 16,
73 | fontFamily: fonts.MONTSERRAT_BOLD,
74 | marginRight: 10,
75 | },
76 | time: {
77 | color: colors.DROIDCONKE_DARK_GREY,
78 | fontSize: 12,
79 | fontFamily: fonts.MONTSERRAT_REGULAR,
80 | },
81 | feed_icon: {
82 | width: 20,
83 | height: 20,
84 | color: colors.DROIDCONKE_BLUE,
85 | },
86 | flex_row: {
87 | flexDirection: "row",
88 | alignItems: 'center',
89 | }
90 | });
91 |
--------------------------------------------------------------------------------
/src/components/cards/SessionCard.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Image , View , TouchableOpacity , Text, StyleSheet, ImageSourcePropType, Dimensions} from "react-native"
3 | import { colors } from "../../constants/Colors";
4 | import { fonts } from '../../assets/fonts/fonts';
5 | import Session from "../../types/Session";
6 |
7 | interface SessionCardPropsWithOnPress {
8 | onPress: () => void;
9 | item: Session
10 | disabled?: boolean
11 |
12 | }
13 | const formatTime = (time: string): string => {
14 |
15 | const date = new Date(time)
16 |
17 | const formattedTime = date.toLocaleTimeString("en-US", {
18 | hour: "2-digit",
19 | minute: "2-digit",
20 | });
21 | return formattedTime;
22 | }
23 |
24 | const placeholder: ImageSourcePropType = require("../../assets/img/droidconkeplaceholder.png")
25 |
26 | const SessionCard = (props: SessionCardPropsWithOnPress) => {
27 | return (
28 |
29 | {props.item.session_image === null ?
30 | <>
31 |
32 | >
33 | :
34 | <>
35 |
36 | >
37 | }
38 |
39 | {props.item.title}
40 | @ {formatTime(props.item.start_date_time)} | {props.item.rooms[0].title}
41 |
42 |
43 | )
44 | };
45 |
46 | const styles = StyleSheet.create({
47 | cardContainer: {
48 | borderRadius: 10,
49 | marginRight: 20,
50 | },
51 | contentWrapper: {
52 | backgroundColor: colors.DROIDCONKE_PEARL,
53 | paddingVertical: 20,
54 | paddingHorizontal: 10,
55 | width: Dimensions.get('screen').width / 1.62,
56 | borderBottomLeftRadius: 10,
57 | borderBottomRightRadius: 10,
58 |
59 | },
60 | title: {
61 | fontFamily: fonts.MONTSERRAT_BOLD,
62 | fontSize: 14,
63 | lineHeight: 20,
64 | color: colors.DROIDCONKE_BLACK,
65 | marginBottom: 10,
66 | },
67 | timeAndVenue: {
68 | fontFamily: fonts.MONTSERRAT_REGULAR,
69 | fontSize: 11,
70 | color: colors.DROIDCONKE_DARK_GREY,
71 | lineHeight: 14,
72 | },
73 | poster: {
74 | width: Dimensions.get('screen').width / 1.62,
75 | height: Dimensions.get('screen').height / 7.5,
76 | borderTopLeftRadius: 10,
77 | borderTopRightRadius: 10,
78 | }
79 | })
80 |
81 | export default SessionCard;
--------------------------------------------------------------------------------
/src/components/cards/SessionsVerticalList.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentStory } from "@storybook/react";
2 | import React from "react";
3 | import SessionsVerticalList, {
4 | SessionsVerticalListProps,
5 | } from "./SessionsVerticalList";
6 |
7 | const DEFAULT_PROPS: SessionsVerticalListProps = {
8 | items: [
9 | {
10 | id: 13,
11 | title: "Cloud Firestore - Getting Started",
12 | description:
13 | "Cloud Firestore, Firebase's new flagship NoSQL database for mobile app development improves on the successes of the Realtime Database with a new, more intuitive data model. Cloud Firestore also features richer, faster queries and scales better than the Realtime Database. This session discusses how you can integrate it into mobile apps and benefit from these improvements.",
14 | slug: "cloud-firestore-getting-started-1655727475",
15 | session_format: "Codelab / Workshop",
16 | session_level: "Intermediate",
17 | session_image: null,
18 | backgroundColor: "#BE5E9B",
19 | borderColor: "#BE5E9B",
20 | is_serviceSession: false,
21 | is_keynote: false,
22 | is_bookmarked: false,
23 | start_date_time: "2022-11-10 10:00:00",
24 | start_time: "10:00:00",
25 | end_date_time: "2022-11-10 10:45:00",
26 | end_time: "10:45:00",
27 | speakers: [
28 | {
29 | name: "Otieno Rowland",
30 | tagline: "Android Hoax Slayer",
31 | biography:
32 | "Rowland is an Software Engineer and mentor. He has been crafting apps for 5 years now and has gathered a lot of experience in those years. In his free time, he likes riding a bike around his hood, and occasionally takes his dog pet for a stroll.",
33 | avatar:
34 | "https://sessionize.com/image/0333-400o400o2-45-6488-47f0-b262-7ce4085d0b5d.8ddd355b-125c-48ae-9bb1-090819b39a0a.jpg",
35 | twitter: "https://twitter.com/rowlandoti",
36 | facebook: null,
37 | linkedin: null,
38 | instagram: null,
39 | blog: "https://www.linkedin.com/in/rowlandoti/",
40 | company_website: null,
41 | },
42 | ],
43 | rooms: [
44 | {
45 | title: "Room A",
46 | id: 1,
47 | },
48 | ],
49 | },
50 | {
51 | id: 13,
52 | title: "Cloud Firestore - Getting Started",
53 | description:
54 | "Cloud Firestore, Firebase's new flagship NoSQL database for mobile app development improves on the successes of the Realtime Database with a new, more intuitive data model. Cloud Firestore also features richer, faster queries and scales better than the Realtime Database. This session discusses how you can integrate it into mobile apps and benefit from these improvements.",
55 | slug: "cloud-firestore-getting-started-1655727475",
56 | session_format: "Codelab / Workshop",
57 | session_level: "Intermediate",
58 | session_image: null,
59 | backgroundColor: "#BE5E9B",
60 | borderColor: "#BE5E9B",
61 | is_serviceSession: false,
62 | is_keynote: false,
63 | is_bookmarked: false,
64 | start_date_time: "2022-11-10 10:00:00",
65 | start_time: "10:00:00",
66 | end_date_time: "2022-11-10 10:45:00",
67 | end_time: "10:45:00",
68 | speakers: [
69 | {
70 | name: "Otieno Rowland",
71 | tagline: "Android Hoax Slayer",
72 | biography:
73 | "Rowland is an Software Engineer and mentor. He has been crafting apps for 5 years now and has gathered a lot of experience in those years. In his free time, he likes riding a bike around his hood, and occasionally takes his dog pet for a stroll.",
74 | avatar:
75 | "https://sessionize.com/image/0333-400o400o2-45-6488-47f0-b262-7ce4085d0b5d.8ddd355b-125c-48ae-9bb1-090819b39a0a.jpg",
76 | twitter: "https://twitter.com/rowlandoti",
77 | facebook: null,
78 | linkedin: null,
79 | instagram: null,
80 | blog: "https://www.linkedin.com/in/rowlandoti/",
81 | company_website: null,
82 | },
83 | ],
84 | rooms: [
85 | {
86 | title: "Room A",
87 | id: 1,
88 | },
89 | ],
90 | },
91 | {
92 | id: 13,
93 | title: "Cloud Firestore - Getting Started",
94 | description:
95 | "Cloud Firestore, Firebase's new flagship NoSQL database for mobile app development improves on the successes of the Realtime Database with a new, more intuitive data model. Cloud Firestore also features richer, faster queries and scales better than the Realtime Database. This session discusses how you can integrate it into mobile apps and benefit from these improvements.",
96 | slug: "cloud-firestore-getting-started-1655727475",
97 | session_format: "Codelab / Workshop",
98 | session_level: "Intermediate",
99 | session_image: null,
100 | backgroundColor: "#BE5E9B",
101 | borderColor: "#BE5E9B",
102 | is_serviceSession: false,
103 | is_keynote: false,
104 | is_bookmarked: false,
105 | start_date_time: "2022-11-10 10:00:00",
106 | start_time: "10:00:00",
107 | end_date_time: "2022-11-10 10:45:00",
108 | end_time: "10:45:00",
109 | speakers: [
110 | {
111 | name: "Otieno Rowland",
112 | tagline: "Android Hoax Slayer",
113 | biography:
114 | "Rowland is an Software Engineer and mentor. He has been crafting apps for 5 years now and has gathered a lot of experience in those years. In his free time, he likes riding a bike around his hood, and occasionally takes his dog pet for a stroll.",
115 | avatar:
116 | "https://sessionize.com/image/0333-400o400o2-45-6488-47f0-b262-7ce4085d0b5d.8ddd355b-125c-48ae-9bb1-090819b39a0a.jpg",
117 | twitter: "https://twitter.com/rowlandoti",
118 | facebook: null,
119 | linkedin: null,
120 | instagram: null,
121 | blog: "https://www.linkedin.com/in/rowlandoti/",
122 | company_website: null,
123 | },
124 | ],
125 | rooms: [
126 | {
127 | title: "Room A",
128 | id: 1,
129 | },
130 | ],
131 | },
132 | {
133 | id: 13,
134 | title: "Cloud Firestore - Getting Started",
135 | description:
136 | "Cloud Firestore, Firebase's new flagship NoSQL database for mobile app development improves on the successes of the Realtime Database with a new, more intuitive data model. Cloud Firestore also features richer, faster queries and scales better than the Realtime Database. This session discusses how you can integrate it into mobile apps and benefit from these improvements.",
137 | slug: "cloud-firestore-getting-started-1655727475",
138 | session_format: "Codelab / Workshop",
139 | session_level: "Intermediate",
140 | session_image: null,
141 | backgroundColor: "#BE5E9B",
142 | borderColor: "#BE5E9B",
143 | is_serviceSession: false,
144 | is_keynote: false,
145 | is_bookmarked: false,
146 | start_date_time: "2022-11-10 10:00:00",
147 | start_time: "10:00:00",
148 | end_date_time: "2022-11-10 10:45:00",
149 | end_time: "10:45:00",
150 | speakers: [
151 | {
152 | name: "Otieno Rowland",
153 | tagline: "Android Hoax Slayer",
154 | biography:
155 | "Rowland is an Software Engineer and mentor. He has been crafting apps for 5 years now and has gathered a lot of experience in those years. In his free time, he likes riding a bike around his hood, and occasionally takes his dog pet for a stroll.",
156 | avatar:
157 | "https://sessionize.com/image/0333-400o400o2-45-6488-47f0-b262-7ce4085d0b5d.8ddd355b-125c-48ae-9bb1-090819b39a0a.jpg",
158 | twitter: "https://twitter.com/rowlandoti",
159 | facebook: null,
160 | linkedin: null,
161 | instagram: null,
162 | blog: "https://www.linkedin.com/in/rowlandoti/",
163 | company_website: null,
164 | },
165 | ],
166 | rooms: [
167 | {
168 | title: "Room A",
169 | id: 1,
170 | },
171 | ],
172 | },
173 | ],
174 | };
175 |
176 | export default {
177 | title: "DroidconKe/cards/SessionsVerticalList",
178 | component: SessionsVerticalList,
179 | args: DEFAULT_PROPS,
180 | };
181 |
182 | const Template: ComponentStory = (args) => (
183 |
184 | );
185 |
186 | export const SessionsVerticalListStory = Template.bind({});
187 |
--------------------------------------------------------------------------------
/src/components/cards/SessionsVerticalList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { StyleSheet, View } from "react-native";
3 | import SessionListSeparator from "../../assets/icons/SessionListSeparator";
4 | import { colors } from "../../constants/Colors";
5 | import Session from "../../types/Session";
6 | import SessionsVerticalListCard from "./SessionsVerticalListCard";
7 |
8 | export type SessionsVerticalListProps = {
9 | items: Session[];
10 | };
11 |
12 | export default function SessionsVerticalList(props: SessionsVerticalListProps) {
13 | return (
14 |
15 |
16 |
17 | {props.items.map((item, index) => (
18 | <>
19 |
20 | {index < props.items.length - 1 && (
21 |
22 |
29 |
30 | )}
31 | >
32 | ))}
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | const styles = StyleSheet.create({
40 | listContainer: { paddingBottom: 80 },
41 | cardContainer: { marginLeft: 50, marginVertical: 6 },
42 | listHolder: {
43 | marginTop: 20,
44 | paddingLeft: 20,
45 | paddingRight: 20,
46 | },
47 | });
48 |
--------------------------------------------------------------------------------
/src/components/cards/SessionsVerticalListCard.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentMeta, ComponentStory } from "@storybook/react";
2 | import React from "react";
3 | import { Provider } from "react-redux";
4 | import { store } from "../../state/store";
5 | import Session from "../../types/Session";
6 | import SessionsVerticalListCard from "./SessionsVerticalListCard";
7 |
8 | const DEFAULT_PROPS: Session = {
9 | id: 13,
10 | title: "Cloud Firestore - Getting Started",
11 | description:
12 | "Cloud Firestore, Firebase's new flagship NoSQL database for mobile app development improves on the successes of the Realtime Database with a new, more intuitive data model. Cloud Firestore also features richer, faster queries and scales better than the Realtime Database. This session discusses how you can integrate it into mobile apps and benefit from these improvements.",
13 | slug: "cloud-firestore-getting-started-1655727475",
14 | session_format: "Codelab / Workshop",
15 | session_level: "Intermediate",
16 | session_image: null,
17 | backgroundColor: "#BE5E9B",
18 | borderColor: "#BE5E9B",
19 | is_serviceSession: false,
20 | is_keynote: false,
21 | is_bookmarked: false,
22 | start_date_time: "2022-11-10 10:00:00",
23 | start_time: "10:00:00",
24 | end_date_time: "2022-11-10 10:45:00",
25 | end_time: "10:45:00",
26 | speakers: [
27 | {
28 | name: "Otieno Rowland",
29 | tagline: "Android Hoax Slayer",
30 | biography:
31 | "Rowland is an Software Engineer and mentor. He has been crafting apps for 5 years now and has gathered a lot of experience in those years. In his free time, he likes riding a bike around his hood, and occasionally takes his dog pet for a stroll.",
32 | avatar:
33 | "https://sessionize.com/image/0333-400o400o2-45-6488-47f0-b262-7ce4085d0b5d.8ddd355b-125c-48ae-9bb1-090819b39a0a.jpg",
34 | twitter: "https://twitter.com/rowlandoti",
35 | facebook: null,
36 | linkedin: null,
37 | instagram: null,
38 | blog: "https://www.linkedin.com/in/rowlandoti/",
39 | company_website: null,
40 | },
41 | ],
42 | rooms: [
43 | {
44 | title: "Room A",
45 | id: 1,
46 | },
47 | ],
48 | };
49 |
50 | export default {
51 | title: "DroidconKe/cards/SessionsVerticalListCard",
52 | component: SessionsVerticalListCard,
53 | args: DEFAULT_PROPS,
54 | } as ComponentMeta;
55 |
56 | const Template: ComponentStory = (args) => (
57 |
58 | );
59 |
60 | export const SessionsVerticalListCardStory = Template.bind({});
61 |
--------------------------------------------------------------------------------
/src/components/cards/SessionsVerticalListCard.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { View, Text, StyleSheet } from "react-native";
3 | import { fonts } from "../../assets/fonts/fonts";
4 | import AndroidIcon from "../../assets/icons/AndroidIcon";
5 | import Star from "../../assets/icons/Star";
6 | import { colors } from "../../constants/Colors";
7 | import useCachedResources from "../../hooks/useCachedResources";
8 | import Session from "../../types/Session";
9 |
10 | export default function SessionsVerticalListCard(
11 | props: Session
12 | ): JSX.Element | null {
13 | const isLoadingCompleted = useCachedResources();
14 | if (!isLoadingCompleted) {
15 | return null;
16 | }
17 |
18 | function formatTime(time: string, withMeridiem: boolean = false): string {
19 | const date = new Date();
20 | date.setTime(Date.parse(time));
21 | const dateWithMeridiem = date.toLocaleTimeString("en-US", {
22 | hour: "2-digit",
23 | minute: "2-digit",
24 | });
25 | if (withMeridiem) return dateWithMeridiem;
26 | return dateWithMeridiem.split(" ")[0];
27 | }
28 |
29 | function getMeridiem(time: string): string {
30 | const date = new Date();
31 | date.setTime(Date.parse(time));
32 | return date.toLocaleTimeString("en-US", { hour12: true }).split(" ")[1];
33 | }
34 |
35 | return (
36 |
37 |
38 |
39 |
40 | {formatTime(props.start_date_time)}
41 |
42 |
43 | {getMeridiem(props.start_date_time)}
44 |
45 |
46 |
47 | {props.title}
48 |
49 | {props.description}
50 |
51 | {props.rooms.length > 0 && (
52 |
53 | {`${formatTime(props.start_date_time, true)} - ${formatTime(
54 | props.end_date_time,
55 | true
56 | )} | ${props.rooms[0].title.toUpperCase()}`}
57 |
58 | )}
59 | {props.speakers.length > 0 && (
60 |
61 |
62 | {props.speakers[0]?.name}
63 |
64 | )}
65 |
66 |
67 |
68 |
69 | );
70 | }
71 |
72 | const styles = StyleSheet.create({
73 | container: {
74 | backgroundColor: "white",
75 | borderRadius: 10,
76 | flexDirection: "row",
77 | padding: 16,
78 | },
79 | startTime: {
80 | fontFamily: fonts.MONTSERRAT_MEDIUM,
81 | fontSize: 18,
82 | color: colors.DROIDCONKE_BLACK,
83 | },
84 | meridiem: {
85 | fontFamily: fonts.MONTSERRAT_MEDIUM,
86 | fontSize: 15,
87 | textAlign: "right",
88 | color: colors.DROIDCONKE_BLACK,
89 | },
90 | programTitle: {
91 | fontFamily: fonts.MONTSERRAT_BOLD,
92 | fontSize: 18,
93 | },
94 | description: {
95 | marginTop: 24,
96 | fontFamily: fonts.MONTSERRAT_REGULAR,
97 | fontSize: 16,
98 | color: colors.DROIDCONKE_DARK_GREY,
99 | },
100 | durationAndVenue: {
101 | marginTop: 14,
102 | fontFamily: fonts.MONTSERRAT_REGULAR,
103 | color: colors.DROIDCONKE_DARK_GREY,
104 | },
105 | speakerContainer: {
106 | flexDirection: "row",
107 | marginTop: 4,
108 | },
109 | speakerName: {
110 | fontSize: 14,
111 | marginLeft: 12,
112 | color: colors.DROIDCONKE_BLUE,
113 | fontFamily: fonts.MONTSERRAT_REGULAR,
114 | },
115 | containerSessionDetails: {
116 | marginStart: 24,
117 | flex: 1,
118 | },
119 | timeDetailsContainer: {
120 | flexDirection: "row",
121 | flex: 1,
122 | },
123 | });
124 |
--------------------------------------------------------------------------------
/src/components/cards/SpeakerCard.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ComponentStory, ComponentMeta } from "@storybook/react";
3 | import SpeakerCard from "./SpeakerCard";
4 | const placeholder = require("../../assets/img/john_doe.png");
5 |
6 | export default {
7 | title: "DroidconKe/Cards/SpeakerCard",
8 | component: SpeakerCard,
9 | } as ComponentMeta;
10 |
11 | const Template: ComponentStory = () => (
12 |
18 | );
19 |
20 | export const SpeakerCardStory = Template.bind({});
21 |
--------------------------------------------------------------------------------
/src/components/cards/SpeakerCard.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Image,
4 | View,
5 | TouchableOpacity,
6 | Text,
7 | StyleSheet,
8 | ImageSourcePropType,
9 | Dimensions,
10 | } from "react-native";
11 | import { colors } from "../../constants/Colors";
12 | import { fonts } from "../../assets/fonts/fonts";
13 | import Speaker from "../../types/Speaker";
14 | import Session from "../../types/Session";
15 |
16 | export interface SpeakerCardProps {
17 | itemSpeaker : Speaker,
18 | itemSession: Session,
19 | SessionButtonOnPress: (item: Session) => void,
20 | SpeakerImageOnPress: (item: Speaker) => void,
21 | }
22 |
23 | export const placeholder : ImageSourcePropType = require('../../assets/img/john_doe.png')
24 |
25 | export default function (props: SpeakerCardProps): JSX.Element {
26 | return (
27 |
28 | props.SpeakerImageOnPress(props.itemSpeaker)}>
29 |
30 |
31 |
32 | {props.itemSpeaker.name}
33 | {props.itemSpeaker.tagline}
34 |
35 | props.SessionButtonOnPress(props.itemSession)}>
36 | SESSION
37 |
38 |
39 | );
40 | }
41 |
42 | const styles = StyleSheet.create({
43 | container: {
44 | width: Dimensions.get('screen').width / 2 - 15,
45 | backgroundColor: colors.DROIDCONKE_PEARL,
46 | alignItems: "center",
47 | borderRadius: 10,
48 | margin: 5,
49 | paddingVertical: 25,
50 | paddingHorizontal:15,
51 | },
52 | image: {
53 | borderColor: colors.DROIDCONKE_GREEN,
54 | borderWidth: 2,
55 | height: 109,
56 | width: 109,
57 | borderRadius: 10,
58 | marginBottom: 10,
59 | },
60 | title: {
61 | color: colors.DROIDCONKE_BLUE,
62 | marginBottom: 6,
63 | fontFamily: fonts.MONTSERRAT_BOLD,
64 | fontSize: 14,
65 | textAlign: "center",
66 | },
67 | contentContainer: {
68 | flex: 1,
69 | },
70 | content: {
71 | textAlign: "center",
72 | marginBottom: 10,
73 | fontFamily: fonts.MONTSERRAT_REGULAR,
74 | fontSize: 11,
75 | color: colors.DROIDCONKE_DARK_GREY,
76 | },
77 | button: {
78 | width: "80%",
79 | height: 45,
80 | borderColor: colors.DROIDCONKE_LIGHT_GREEN,
81 | borderRadius: 10,
82 | borderWidth: 2,
83 | alignContent: "center",
84 | justifyContent: "center",
85 | },
86 | buttontext: {
87 | color: colors.DROIDCONKE_LIGHT_GREEN,
88 | textAlign: "center",
89 | fontFamily: fonts.MONTSERRAT_SEMIBOLD,
90 | fontSize: 14,
91 | },
92 | });
93 |
--------------------------------------------------------------------------------
/src/components/cards/SpeakerImageCard.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Image , TouchableOpacity , Text, StyleSheet, ImageSourcePropType} from "react-native"
3 | import { colors } from "../../constants/Colors";
4 | import { fonts } from '../../assets/fonts/fonts';
5 | import { layoutProperties } from "../../constants/Properties";
6 | import Speaker from "../../types/Speaker";
7 |
8 | interface SpeakerImageCardPropsWithOnPress {
9 | onPress: (item: Speaker) => void;
10 | item: Speaker
11 | }
12 |
13 | const SpeakerImageCard = (props : SpeakerImageCardPropsWithOnPress) => {
14 | return (
15 | props.onPress(props.item)}>
16 |
17 | {props.item.name}
18 |
19 | )
20 | }
21 |
22 | const styles = StyleSheet.create({
23 | container: {
24 | ...layoutProperties.itemsCenter,
25 | borderRadius : 10,
26 | },
27 | image : {
28 | borderColor : colors.DROIDCONKE_GREEN,
29 | borderWidth : 2,
30 | height : 76,
31 | width : 76,
32 | borderRadius : 12
33 | },
34 | name : {
35 | color : colors.DROIDCONKE_BLACK,
36 | fontFamily : fonts.MONTSERRAT_MEDIUM,
37 | width : 76,
38 | fontSize : 11 ,
39 | textAlign : "center",
40 | marginVertical: 10,
41 | lineHeight: 14,
42 | },
43 | })
44 |
45 | export default SpeakerImageCard;
--------------------------------------------------------------------------------
/src/components/cards/TeamMemberCard.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Image,
3 | ImageSourcePropType,
4 | StyleSheet,
5 | Text,
6 | View,
7 | TouchableOpacity,
8 | } from "react-native";
9 | import React from "react";
10 | import { colors } from "../../constants/Colors";
11 | import { fonts } from "../../assets/fonts/fonts";
12 |
13 | interface TeamMemberCardProps {
14 | name: string;
15 | title: string;
16 | profileImage: ImageSourcePropType;
17 | onPress: () => void;
18 | }
19 |
20 | const TeamMemberCard = (props: TeamMemberCardProps) => {
21 | return (
22 |
23 |
24 |
29 | {props.name}
30 | {props.title}
31 |
32 |
33 | );
34 | };
35 |
36 | export default TeamMemberCard;
37 |
38 | const styles = StyleSheet.create({
39 | container: {
40 | alignItems: "center",
41 | width: "33.3%",
42 | marginBottom: 10,
43 | },
44 | image: {
45 | width: 99,
46 | height: 99,
47 | borderRadius: 8,
48 | borderWidth: 2,
49 | borderColor: colors.DROIDCONKE_GREEN,
50 | },
51 | name: {
52 | fontSize: 14,
53 | color: colors.DROIDCONKE_BLACK_TEXT_AND_LABEL,
54 | lineHeight: 20,
55 | },
56 | title: {
57 | fontSize: 11,
58 | color: colors.DROIDCONKE_DARK_GREY,
59 | lineHeight: 16,
60 | },
61 | textCommon: {
62 | fontFamily: fonts.MONTSERRAT_REGULAR,
63 | textAlign: "center",
64 | },
65 | });
66 |
--------------------------------------------------------------------------------
/src/components/dateToggle/DateToggle.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ComponentStory, ComponentMeta } from "@storybook/react";
3 | import DateToggle, { DateToggleProps } from "./DateToggle";
4 |
5 | const DEFAULT_PROPS: DateToggleProps = {
6 | selected: false,
7 | date: "17th",
8 | day: "Day 2",
9 | };
10 |
11 | export default {
12 | title: "DroidconKe/components/DateToggle",
13 | component: DateToggle,
14 | args: DEFAULT_PROPS,
15 | } as ComponentMeta;
16 |
17 | const Template: ComponentStory = (args) => (
18 |
19 | );
20 |
21 | export const DefaultDateToggle = Template.bind({});
22 |
23 | export const SelectedDateToggle = Template.bind({});
24 | SelectedDateToggle.args = {
25 | selected: true,
26 | };
27 |
--------------------------------------------------------------------------------
/src/components/dateToggle/DateToggle.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { View, Text, StyleSheet } from "react-native";
3 | import { fonts } from "../../assets/fonts/fonts";
4 | import { colors } from "../../constants/Colors";
5 | import useCachedResources from "../../hooks/useCachedResources";
6 |
7 | export type DateToggleProps = {
8 | selected: boolean;
9 | date: string;
10 | day: string;
11 | fullDate: string;
12 | };
13 |
14 | export default function DateToggle({
15 | selected,
16 | day,
17 | date,
18 | }: DateToggleProps): JSX.Element | null {
19 | const isLoadingComplete = useCachedResources();
20 | if (!isLoadingComplete) {
21 | return null;
22 | }
23 |
24 | return (
25 |
26 | {date}
27 | {day}
28 |
29 | );
30 | }
31 |
32 | const styles = StyleSheet.create({
33 | container: {
34 | backgroundColor: colors.DROIDCONKE_GREEN_TRANSLUCENT,
35 | borderRadius: 5,
36 | display: "flex",
37 | justifyContent: "center",
38 | padding: 12,
39 | },
40 | containerSelected: {
41 | backgroundColor: colors.DROIDCONKE_BRICK_RED,
42 | },
43 | date: {
44 | fontSize: 18,
45 | fontFamily: fonts.MONTSERRAT_BOLD,
46 | color: colors.DROIDCONKE_BLACK,
47 | alignSelf: "flex-start",
48 | },
49 | day: {
50 | fontSize: 11,
51 | color: colors.DROIDCONKE_BLACK,
52 | fontFamily: fonts.MONTSERRAT_REGULAR,
53 | marginTop: 4,
54 | alignSelf: "flex-start",
55 | },
56 | textSelected: {
57 | color: "white",
58 | },
59 | });
60 |
--------------------------------------------------------------------------------
/src/components/dateToggle/DateToggleList.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ComponentStory, ComponentMeta } from "@storybook/react";
3 | import DateToggleList, { DateToggleListProps } from "./DateToggleList";
4 |
5 | const DEFAULT_PROPS: DateToggleListProps = {
6 | items: [
7 | { selected: true, date: "16th", day: "Day 1" },
8 | { selected: false, date: "17th", day: "Day 2" },
9 | { selected: false, date: "18th", day: "Day 3" },
10 | ],
11 | };
12 |
13 | export default {
14 | title: "DroidconKe/components/DateToggleList",
15 | component: DateToggleList,
16 | args: DEFAULT_PROPS,
17 | } as ComponentMeta;
18 |
19 | const Template: ComponentStory = (args) => (
20 |
21 | );
22 |
23 | export const DefaultDateToggleList = Template.bind({});
24 |
--------------------------------------------------------------------------------
/src/components/dateToggle/DateToggleList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | View,
4 | StyleSheet,
5 | Touchable,
6 | TouchableWithoutFeedback,
7 | } from "react-native";
8 | import DateToggle, { DateToggleProps } from "./DateToggle";
9 |
10 | export type DateToggleListProps = {
11 | items: DateToggleProps[];
12 | onChange: (day: string) => void;
13 | };
14 |
15 | export default function DateToggleList({
16 | items,
17 | onChange,
18 | }: DateToggleListProps): JSX.Element | null {
19 | return (
20 |
21 | {items.map((item) => (
22 | onChange(item.fullDate)}>
23 |
24 |
25 |
26 |
27 | ))}
28 |
29 | );
30 | }
31 |
32 | const styles = StyleSheet.create({
33 | container: { flexDirection: "row" },
34 | dateToggle: { marginEnd: 20 },
35 | });
36 |
--------------------------------------------------------------------------------
/src/components/forms/Input.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/components/forms/Input.tsx
--------------------------------------------------------------------------------
/src/components/forms/Radio.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/components/forms/Radio.tsx
--------------------------------------------------------------------------------
/src/components/forms/Select.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/components/forms/Select.tsx
--------------------------------------------------------------------------------
/src/components/iconSwitch/IconSwitch.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import {
3 | Animated,
4 | TouchableWithoutFeedback,
5 | View,
6 | StyleSheet,
7 | } from "react-native";
8 | import Thumb from "./Thumb";
9 | import Track from "./Track";
10 |
11 | type IconSwitchProps = {
12 | value: boolean;
13 | animationDuration?: number;
14 | onValueChange: (value: boolean) => void;
15 | style?: { height: number; width: number };
16 | trackColor: {
17 | true: string;
18 | false: string;
19 | };
20 | thumbColor: {
21 | true: string;
22 | false: string;
23 | };
24 | iconColors: {
25 | true: string;
26 | false: string;
27 | };
28 | };
29 |
30 | export default function SwitchWithIcons(props: IconSwitchProps) {
31 | const {
32 | value,
33 | animationDuration,
34 | onValueChange,
35 | style,
36 | trackColor,
37 | iconColors,
38 | thumbColor,
39 | } = props;
40 |
41 | const [pressIndicator, setPressIndicator] = useState(false);
42 |
43 | let _animatedValue: Animated.Value;
44 | let _maxAnimatedValue: number = 0;
45 | let _minAnimatedValue: number = 180;
46 | let _animationDuration: number;
47 | let _animatedRange: number[];
48 | let _listenerId: string;
49 |
50 | _animatedValue = new Animated.Value(
51 | props.value ? _maxAnimatedValue : _minAnimatedValue
52 | );
53 |
54 | useEffect(() => {
55 | setPressIndicator(false);
56 |
57 | _listenerId = _animatedValue.addListener(({ value }) => {
58 | if (
59 | pressIndicator &&
60 | (value === _minAnimatedValue || value === _maxAnimatedValue)
61 | ) {
62 | setPressIndicator(false);
63 | }
64 | });
65 |
66 | if (pressIndicator) {
67 | Animated.timing(_animatedValue, {
68 | toValue: props.value ? _maxAnimatedValue : _minAnimatedValue,
69 | duration: _animationDuration,
70 | useNativeDriver: false,
71 | }).start();
72 | }
73 |
74 | return () => {
75 | _animatedValue.removeListener(_listenerId);
76 | };
77 | }, [value]);
78 |
79 | _animationDuration = animationDuration || 200;
80 |
81 | _maxAnimatedValue = 100;
82 | _minAnimatedValue = 0;
83 | _animatedRange = [_minAnimatedValue, _maxAnimatedValue];
84 |
85 | function _handlePress() {
86 | setPressIndicator(true);
87 |
88 | const value = !props.value;
89 |
90 | if (onValueChange) {
91 | onValueChange(value);
92 | }
93 | }
94 |
95 | return (
96 |
97 |
98 |
99 |
106 |
117 |
118 |
119 |
120 | );
121 | }
122 |
123 | const styles = StyleSheet.create({
124 | container: { justifyContent: "center" },
125 | });
126 |
--------------------------------------------------------------------------------
/src/components/iconSwitch/Thumb.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Animated, StyleSheet, View } from "react-native";
3 | import Star from "../../assets/icons/Star";
4 |
5 | type ThumbProps = {
6 | range: number[];
7 | animatedValue: Animated.Value;
8 | width: number;
9 | height: number;
10 | colors: {
11 | true: string;
12 | false: string;
13 | };
14 | size: number;
15 | value: boolean;
16 | iconColors: {
17 | true: string;
18 | false: string;
19 | };
20 | pressIndicator: boolean;
21 | };
22 |
23 | export default function Thumb(props: ThumbProps) {
24 | const { range, animatedValue, width, size, value, colors } = props;
25 |
26 | const radius = Math.round(size / 2);
27 |
28 | const color = animatedValue.interpolate({
29 | inputRange: range,
30 | outputRange: [colors.false, colors.true],
31 | });
32 |
33 | const position = animatedValue.interpolate({
34 | inputRange: range,
35 | outputRange: [0, width - size],
36 | });
37 |
38 | const pressedIndicatorPosition = animatedValue.interpolate({
39 | inputRange: range,
40 | outputRange: [0 - radius, width - radius - size],
41 | });
42 |
43 | return (
44 | <>
45 | {props.pressIndicator && (
46 |
58 | )}
59 |
70 |
75 |
76 | >
77 | );
78 | }
79 |
80 | export const styles = StyleSheet.create({
81 | thumb: {
82 | position: "absolute",
83 | flexDirection: "row",
84 | alignItems: "center",
85 | justifyContent: "center",
86 | elevation: 1,
87 | shadowOpacity: 0.1,
88 | shadowRadius: 1,
89 | shadowOffset: {
90 | height: 0,
91 | width: 0,
92 | },
93 | alignContent: "center",
94 | aspectRatio: 1,
95 | },
96 | pressedIndicator: {
97 | position: "absolute",
98 | opacity: 0.2,
99 | aspectRatio: 1,
100 | },
101 | });
102 |
--------------------------------------------------------------------------------
/src/components/iconSwitch/Track.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Animated } from "react-native";
3 |
4 | type TrackProps = {
5 | range: number[];
6 | animatedValue: Animated.Value;
7 | width: number;
8 | height: number;
9 | colors: {
10 | true: string;
11 | false: string;
12 | };
13 | };
14 |
15 | export default function Track(props: TrackProps) {
16 | const { range, animatedValue, width, height, colors } = props;
17 |
18 | const margin = Math.round(height / 26);
19 | const radius = Math.round(height / 2);
20 |
21 | const color = animatedValue.interpolate({
22 | inputRange: range,
23 | outputRange: [colors.false, colors.true],
24 | });
25 |
26 | const trackStyle = {
27 | backgroundColor: color,
28 | height: height - margin * 2,
29 | width: width - margin * 2,
30 | borderRadius: radius - margin,
31 | margin: margin,
32 | };
33 |
34 | return ;
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/layouts/BrandLogo.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Image,
3 | ImageStyle,
4 | StyleProp,
5 | StyleSheet,
6 | View,
7 | ViewStyle,
8 | } from "react-native";
9 | import React from "react";
10 |
11 | interface LogoData {
12 | logoUri: string;
13 | imageStyles?: StyleProp;
14 | imageContainerStyles?: StyleProp;
15 | }
16 |
17 | const BrandLogo = ({
18 | logoUri,
19 | imageStyles,
20 | imageContainerStyles,
21 | }: LogoData) => {
22 | return (
23 |
24 |
31 |
32 | );
33 | };
34 |
35 | export default BrandLogo;
36 |
37 | const styles = StyleSheet.create({
38 | container: {
39 | marginVertical: 10,
40 | },
41 | image: {
42 | width: 80,
43 | height: 80,
44 | },
45 | });
46 |
--------------------------------------------------------------------------------
/src/components/layouts/DroidconOrganizers.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, Text, View } from "react-native";
2 | import React, { useState, useEffect } from "react";
3 | import { layoutProperties } from "../../constants/Properties";
4 | import { colors } from "../../constants/Colors";
5 | import { fonts } from "../../assets/fonts/fonts";
6 | import BrandLogo from "./BrandLogo";
7 |
8 | const MOCK_DATA = {
9 | data: [
10 | {
11 | id: 1,
12 | name: "android 254",
13 | email: "droidconke@gmail.com",
14 | description: "droidcon Ke",
15 | facebook: "droidconke",
16 | twitter: "droidcon.co.ke",
17 | instagram: "droidcon.co.ke",
18 | logo: "https://secure.meetupstatic.com/photos/event/b/c/1/1/clean_482388145.jpeg",
19 | slug: "droidcon-ke-645",
20 | status: "active",
21 | created_at: "2021-09-10 16:26:32",
22 | upcoming_events_count: 1,
23 | total_events_count: 1,
24 | },
25 | {
26 | id: 2,
27 | name: "kotlin kenya",
28 | email: "droidconke@gmail.com",
29 | description: "droidcon Ke",
30 | facebook: "droidconke",
31 | twitter: "droidcon.co.ke",
32 | instagram: "droidcon.co.ke",
33 | logo: "https://res.cloudinary.com/droidconke/image/upload/v1631955399/prod/upload/org_team/dpkitydnkbbih6n9ktoz.png",
34 | slug: "droidcon-ke-645",
35 | status: "active",
36 | created_at: "2021-09-10 16:26:32",
37 | upcoming_events_count: 1,
38 | total_events_count: 1,
39 | },
40 | {
41 | id: 3,
42 | name: "flutter kenya",
43 | email: "droidconke@gmail.com",
44 | description: "droidcon Ke",
45 | facebook: "droidconke",
46 | twitter: "droidcon.co.ke",
47 | instagram: "droidcon.co.ke",
48 | logo: "https://secure.meetupstatic.com/photos/event/8/d/7/4/clean_489516212.jpeg",
49 | slug: "droidcon-ke-645",
50 | status: "active",
51 | created_at: "2021-09-10 16:26:32",
52 | upcoming_events_count: 1,
53 | total_events_count: 1,
54 | },
55 | {
56 | id: 4,
57 | name: "app labs",
58 | email: "droidconke@gmail.com",
59 | description: "droidcon Ke",
60 | facebook: "droidconke",
61 | twitter: "droidcon.co.ke",
62 | instagram: "droidcon.co.ke",
63 | logo: "https://appslab.co.ke/assets/img/logo.png",
64 | slug: "droidcon-ke-645",
65 | status: "active",
66 | created_at: "2021-09-10 16:26:32",
67 | upcoming_events_count: 1,
68 | total_events_count: 1,
69 | },
70 | {
71 | id: 5,
72 | name: "early camp",
73 | email: "droidconke@gmail.com",
74 | description: "droidcon Ke",
75 | facebook: "droidconke",
76 | twitter: "droidcon.co.ke",
77 | instagram: "droidcon.co.ke",
78 | logo: "https://media-exp1.licdn.com/dms/image/C4D0BAQFbXBKf5A1wxQ/company-logo_200_200/0/1533933103212?e=2147483647&v=beta&t=BIObWjP27BUIyzN5H9finiwLhwSlrlKc5fEcvTiki4s",
79 | slug: "droidcon-ke-645",
80 | status: "active",
81 | created_at: "2021-09-10 16:26:32",
82 | upcoming_events_count: 1,
83 | total_events_count: 1,
84 | },
85 | {
86 | id: 6,
87 | name: "tiskos kenya",
88 | email: "droidconke@gmail.com",
89 | description: "droidcon Ke",
90 | facebook: "droidconke",
91 | twitter: "droidcon.co.ke",
92 | instagram: "droidcon.co.ke",
93 | logo: "https://pbs.twimg.com/profile_images/1297998197630087169/zvpGWoVq_400x400.jpg",
94 | slug: "droidcon-ke-645",
95 | status: "active",
96 | created_at: "2021-09-10 16:26:32",
97 | upcoming_events_count: 1,
98 | total_events_count: 1,
99 | },
100 | ],
101 | };
102 |
103 | interface OrganizersData {
104 | id: number;
105 | name: string;
106 | email?: string;
107 | description?: string;
108 | facebook?: string;
109 | twitter?: string;
110 | instagram?: string;
111 | logo: string;
112 | slug?: string;
113 | status?: string;
114 | created_at?: string;
115 | upcoming_events_count?: number;
116 | total_events_count?: number;
117 | }
118 |
119 | const DroidconOrganizers = () => {
120 | const [organizersData, setOrganizersData] = useState([]);
121 |
122 | const loadOrganizersData = async () => {
123 | //TODO: obtain data/response from the api service
124 | //set data
125 | setOrganizersData(MOCK_DATA.data);
126 | };
127 |
128 | useEffect(() => {
129 | loadOrganizersData();
130 | }, []);
131 |
132 | return (
133 |
134 |
137 | Organized by :
138 |
139 |
142 | {organizersData.map((brand) => (
143 |
144 | ))}
145 |
146 |
147 | );
148 | };
149 |
150 | export default DroidconOrganizers;
151 |
152 | const styles = StyleSheet.create({
153 | marginVerticalSeparator2: {
154 | marginVertical: 18,
155 | },
156 | sponsorsContainer: {
157 | backgroundColor: colors.DROIDCONKE_PEARL,
158 | padding: 20,
159 | borderRadius: 10,
160 | },
161 | sponsorsContainerTitle: {
162 | fontFamily: fonts.MONTSERRAT_BOLD,
163 | fontSize: 18,
164 | lineHeight: 20,
165 | color: colors.DROIDCONKE_BLUE,
166 | textAlign: "center",
167 | },
168 | sponsorsIconsContainer: {
169 | ...layoutProperties.flexRow,
170 | flexWrap: "wrap",
171 | justifyContent: "space-around",
172 | },
173 | marginVerticalSeparator: {
174 | marginVertical: 15,
175 | },
176 | });
177 |
--------------------------------------------------------------------------------
/src/components/layouts/DroidconSponsors.tsx:
--------------------------------------------------------------------------------
1 | import { Image, StyleSheet, Text, View } from "react-native";
2 | import React, { useEffect, useState } from "react";
3 | import { layoutProperties } from "../../constants/Properties";
4 | import { colors } from "../../constants/Colors";
5 | import { fonts } from "../../assets/fonts/fonts";
6 | import { sponser_types } from "../../constants/SponsorTypes";
7 | import BrandLogo from "./BrandLogo";
8 |
9 | const MOCK_DATA = {
10 | data: [
11 | {
12 | name: "Google",
13 | tagline: "just google",
14 | link: "https://www.google.com/",
15 | sponsor_type: "platinum",
16 | logo: "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png",
17 | created_at: "2022-10-17 13:37:26",
18 | },
19 | {
20 | name: "Andela - 1",
21 | tagline: "Discover brilliant talent around the world",
22 | link: "https://andela.com/",
23 | sponsor_type: "gold",
24 | logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSq94i4KUPtkBIbK5wbGadB-rOyNlZpseLH7HhqKEaMri1-XK8UAKQPPcuPpffTcE4gww&usqp=CAU",
25 | created_at: "2022-10-17 13:37:26",
26 | },
27 | {
28 | name: "Andela - 2",
29 | tagline: "Discover brilliant talent around the world",
30 | link: "https://andela.com/",
31 | sponsor_type: "gold",
32 | logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSq94i4KUPtkBIbK5wbGadB-rOyNlZpseLH7HhqKEaMri1-XK8UAKQPPcuPpffTcE4gww&usqp=CAU",
33 | created_at: "2022-10-17 13:37:26",
34 | },
35 | {
36 | name: "JetBrains - 1",
37 | tagline: "Essential tools for software developers and teams",
38 | link: "https://www.jetbrains.com/",
39 | sponsor_type: "silver",
40 | logo: "https://download.logo.wine/logo/JetBrains/JetBrains-Logo.wine.png",
41 | created_at: "2022-10-17 13:37:26",
42 | },
43 | {
44 | name: "JetBrains - 2",
45 | tagline: "Essential tools for software developers and teams",
46 | link: "https://www.jetbrains.com/",
47 | sponsor_type: "silver",
48 | logo: "https://download.logo.wine/logo/JetBrains/JetBrains-Logo.wine.png",
49 | created_at: "2022-10-17 13:37:26",
50 | },
51 | {
52 | name: "JetBrains - 3",
53 | tagline: "Essential tools for software developers and teams",
54 | link: "https://www.jetbrains.com/",
55 | sponsor_type: "silver",
56 | logo: "https://download.logo.wine/logo/JetBrains/JetBrains-Logo.wine.png",
57 | created_at: "2022-10-17 13:37:26",
58 | },
59 | {
60 | name: "Hover - 1",
61 | tagline: "USSD for android devs",
62 | link: "https://www.usehover.com/",
63 | sponsor_type: "bronze",
64 | logo: "https://remoteworker-live-superbrnds.s3.amazonaws.com/uploads/company/logo/3625/logo.png",
65 | created_at: "2022-10-17 13:37:26",
66 | },
67 | {
68 | name: "Hover - 2",
69 | tagline: "USSD for android devs",
70 | link: "https://www.usehover.com/",
71 | sponsor_type: "bronze",
72 | logo: "https://remoteworker-live-superbrnds.s3.amazonaws.com/uploads/company/logo/3625/logo.png",
73 | created_at: "2022-10-17 13:37:26",
74 | },
75 | {
76 | name: "Hover - 3",
77 | tagline: "USSD for android devs",
78 | link: "https://www.usehover.com/",
79 | sponsor_type: "bronze",
80 | logo: "https://remoteworker-live-superbrnds.s3.amazonaws.com/uploads/company/logo/3625/logo.png",
81 | created_at: "2022-10-17 13:37:26",
82 | },
83 | {
84 | name: "Hover - 4",
85 | tagline: "USSD for android devs",
86 | link: "https://www.usehover.com/",
87 | sponsor_type: "bronze",
88 | logo: "https://remoteworker-live-superbrnds.s3.amazonaws.com/uploads/company/logo/3625/logo.png",
89 | created_at: "2022-10-17 13:37:26",
90 | },
91 | ],
92 | };
93 |
94 | interface SponsorsData {
95 | name: string;
96 | tagline?: string;
97 | link?: string;
98 | sponsor_type: string;
99 | logo: string;
100 | created_at?: string;
101 | }
102 |
103 | const DroidconSponsors = () => {
104 | const [platinumSponsors, setPlatinumSponsors] = useState([]);
105 | const [goldSponsors, setGoldSponsors] = useState([]);
106 | const [silverSponsors, setSilverSponsors] = useState([]);
107 | const [bronzeSponsors, setBronzeSponsors] = useState([]);
108 |
109 | const loadSponsorsData = async () => {
110 | //TODO: obtain data from api service
111 | const data = MOCK_DATA.data;
112 |
113 | setPlatinumSponsors(
114 | data.filter((s) => s.sponsor_type === sponser_types.PLATINUM)
115 | );
116 | setGoldSponsors(data.filter((s) => s.sponsor_type === sponser_types.GOLD));
117 | setSilverSponsors(
118 | data.filter((s) => s.sponsor_type === sponser_types.SILVER)
119 | );
120 | setBronzeSponsors(
121 | data.filter((s) => s.sponsor_type === sponser_types.BRONZE)
122 | );
123 | };
124 |
125 | useEffect(() => {
126 | loadSponsorsData();
127 | }, []);
128 |
129 | return (
130 |
131 |
134 | Sponsors
135 |
136 |
137 | {platinumSponsors.map((s) => (
138 |
144 | ))}
145 |
146 |
147 | {goldSponsors.map((s) => (
148 |
154 | ))}
155 |
156 |
157 | {silverSponsors.map((s) => (
158 |
164 | ))}
165 |
166 |
167 | {bronzeSponsors.map((s) => (
168 |
174 | ))}
175 |
176 |
177 | );
178 | };
179 |
180 | export default DroidconSponsors;
181 |
182 | const styles = StyleSheet.create({
183 | sponsorsContainer: {
184 | backgroundColor: colors.DROIDCONKE_PEARL,
185 | padding: 20,
186 | borderRadius: 10,
187 | },
188 | sponsorsContainerTitle: {
189 | fontFamily: fonts.MONTSERRAT_BOLD,
190 | fontSize: 18,
191 | lineHeight: 20,
192 | color: colors.DROIDCONKE_BLUE,
193 | textAlign: "center",
194 | },
195 | sponsorsIconsContainer: {
196 | ...layoutProperties.flexRow,
197 | ...layoutProperties.itemsCenter,
198 | ...layoutProperties.justifyAround,
199 | flexWrap: "wrap",
200 | },
201 | marginVerticalSeparator2: {
202 | marginVertical: 18,
203 | },
204 | marginVerticalSeparator: {
205 | marginVertical: 15,
206 | },
207 | marginVerticalIcons: {
208 | marginVertical: 10,
209 | },
210 | platinumLogo: {
211 | width: 200,
212 | height: 100,
213 | },
214 | logoContainer: {
215 | marginVertical: 5,
216 | },
217 | goldLogo: {
218 | width: 125,
219 | height: 75,
220 | },
221 | silverLogo: {
222 | width: 90,
223 | height: 75,
224 | },
225 | bronzeLogo: {
226 | width: 60,
227 | height: 50,
228 | },
229 | });
230 |
--------------------------------------------------------------------------------
/src/components/layouts/ImageTextLayout.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/components/layouts/ImageTextLayout.tsx
--------------------------------------------------------------------------------
/src/components/layouts/MainHeader.tsx:
--------------------------------------------------------------------------------
1 | import { Image, StyleSheet, Text, TouchableOpacity, View } from "react-native";
2 | import React from "react";
3 | import DroidconKeIcon from "../../assets/icons/DroidconKeIcon";
4 | import { layoutProperties } from "../../constants/Properties";
5 | import { fonts } from "../../assets/fonts/fonts";
6 | import { colors } from "../../constants/Colors";
7 | import { useAppSelector } from "../../hooks/useTypedRedux";
8 | import LockIcon from "../../assets/icons/LockIcon";
9 | import { useNavigation } from "@react-navigation/native";
10 | import { NativeStackNavigationProp } from "@react-navigation/native-stack";
11 | import { RootStackParamList } from "../../types/Navigation";
12 | import { screen_names } from "../../constants/ScreenNames";
13 |
14 | interface MainHeaderProps {
15 | onPress?: () => void; //allow consumer of this component to handle the press event differently in the not-signed-in state.
16 | }
17 |
18 | const MainHeader = (props: MainHeaderProps) => {
19 | const { user } = useAppSelector((state) => state.user);
20 |
21 | const navigation =
22 | useNavigation>();
23 |
24 |
25 | const goToFeedbackScreen = () => navigation.navigate(screen_names.FEEDBACK, {sessionSlug: undefined});
26 |
27 |
28 | if (!user)
29 | return (
30 |
31 |
32 |
36 |
37 |
38 |
39 | );
40 |
41 | return (
42 |
49 |
50 |
57 |
61 |
66 |
72 | Feedback
73 |
74 |
78 |
79 |
80 |
85 |
86 |
87 |
88 | );
89 | };
90 |
91 | export default MainHeader;
92 |
93 | const styles = StyleSheet.create({
94 | signedOutHeader: {
95 | ...layoutProperties.flexRow,
96 | ...layoutProperties.justifyBetween,
97 | ...layoutProperties.itemsCenter,
98 | marginRight: 3,
99 | paddingHorizontal: 20,
100 | },
101 | signedInHeader: {
102 | ...layoutProperties.flexRow,
103 | ...layoutProperties.justifyAround,
104 | ...layoutProperties.itemsCenter,
105 | marginRight: 20,
106 | marginLeft: 10,
107 | },
108 | marginVerticalSeparator: {
109 | marginVertical: 15,
110 | },
111 | paddingVertical: {
112 | paddingVertical: 20,
113 | },
114 | paddingHorizontal: {
115 | paddingHorizontal: 20,
116 | },
117 | droidconkeIcon: {
118 | marginVertical: -100,
119 | },
120 | buttonFeedback: {
121 | backgroundColor: colors.DROIDCONKE_GREEN_TRANSLUCENT,
122 | padding: 12,
123 | borderRadius: 10,
124 | ...layoutProperties.flexRow,
125 | ...layoutProperties.itemsCenter,
126 | ...layoutProperties.justifyEvenly,
127 | marginRight: 25,
128 | },
129 | buttonFeedbackText: {
130 | fontFamily: fonts.MONTSERRAT_REGULAR,
131 | fontSize: 12,
132 | },
133 | buttonFeedbackContentMargin: {
134 | marginRight: 8,
135 | },
136 | iconWrapper: {
137 | backgroundColor: colors.DROIDCONKE_GREEN,
138 | width: 29,
139 | height: 29,
140 | ...layoutProperties.justifyCenter,
141 | ...layoutProperties.itemsCenter,
142 | borderRadius: 14.45,
143 | },
144 | marginBottomSeparator: {
145 | marginBottom: 10,
146 | },
147 | avatar: {width: 45, height: 45, borderRadius: 22.5}
148 | });
149 |
--------------------------------------------------------------------------------
/src/components/layouts/MainLayout.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droidconKE/droidconKE2022ReactNative/6db236f84bb4b2da3861b968d49eee9b277c7836/src/components/layouts/MainLayout.tsx
--------------------------------------------------------------------------------
/src/constants/Colors.ts:
--------------------------------------------------------------------------------
1 | // note: this helps modify / alter colors from a central point
2 |
3 | export enum colors {
4 | LIGHT_THEME_BACKGROUND_COLOR = "#F4F5F7",
5 | DARK_THEME_BACKGROUND_COLOR = "#000000",
6 | LIGHT_THEME_TEXT_COLOR = "#4D4D4D",
7 | DARK_THEME_TEXT_COLOR = "#4D4D4D",
8 | DROIDCONKE_BLUE = "#000CEB",
9 | DROIDCONKE_GREEN = "#00E2C3",
10 | DROIDCONKE_LIGHT_GREEN = "#7DE1C3",
11 | DROIDCONKE_BRICK_RED = "#FF6E4D",
12 | DROIDCONKE_BLACK = "#20201E",
13 | DROIDCONKE_DARK_GREY = "#707070",
14 | DROIDCONKE_LIGHT_GREY = "#00000075",
15 | DROIDCONKE_WHITE = "#FFFFFF",
16 | DROIDCONKE_PEARL = "#F5F5F5",
17 | DROIDCONKE_GREEN_TRANSLUCENT = "rgba(0, 226, 195, 0.21)",
18 | DROIDCONKE_BLUE_TRANSLUCENT = "rgba(0, 12, 235, 0.11)",
19 | DROIDCONKE_BLACK_TRANSLUCENT = "rgba(32, 32, 30, 0.61)",
20 | DROIDCONKE_BLACK_TEXT_AND_LABEL = "#191D1D",
21 | DROIDCONKE_BLACK_GOOGLESIGNIN_TRANSLUCENT = "rgba(25, 29, 29, 0.1)",
22 | DROIDCONKE_MODAL_OVERLAY = 'rgba(0,0,0,0.52)',
23 | DROIDCONKE_FEEDBACK_INPUT_TEXT = "#C3C3C3",
24 | DROIDCONKE_BORDER_BOTTOM= '#7070702B',
25 | DROIDCONKE_SHARE_MODAL_BACKGROUND = "#F6F6F8",
26 | DROIDCONKE_SHARE_TEXT = "#20201E",
27 | DROIDCONKE_CANCEL_TEXT = "#707070",
28 | DROIDCONKE_DARK_BLACK = "#191D1D",
29 | DROIDCONKE_MEDIUM_AQUAMARINE = "#68DEA442",
30 | }
31 |
--------------------------------------------------------------------------------
/src/constants/Properties.ts:
--------------------------------------------------------------------------------
1 | // includes globally reusable CSS properties like border-radius, box-shadows e.t.c
2 | import { StyleSheet } from "react-native"
3 | enum properties {
4 | BORDER_RADIUS = 8,
5 | BORDER_RADIUS_MAX = 50,
6 | BOX_SHADOW = "",
7 | }
8 |
9 | // Layout properties.
10 | export const layoutProperties = StyleSheet.create({
11 | justifyCenter: {
12 | justifyContent: 'center',
13 | },
14 | justifyBetween: {
15 | justifyContent: 'space-between',
16 | },
17 | justifyAround: {
18 | justifyContent: 'space-around',
19 | },
20 | justifyEvenly: {
21 | justifyContent: 'space-evenly',
22 | },
23 | justifyStart: {
24 | justifyContent: 'flex-start',
25 | },
26 | flexRow: {
27 | flexDirection: 'row',
28 | },
29 | flexColumn: {
30 | flexDirection: 'column',
31 | },
32 | itemsCenter: {
33 | alignItems: 'center',
34 | },
35 |
36 | })
--------------------------------------------------------------------------------
/src/constants/ScreenNames.ts:
--------------------------------------------------------------------------------
1 | // screen names
2 |
3 | export enum screen_names {
4 | WELCOME = "WelcomeScreen",
5 | SIGNUP = "SignupScreen",
6 | HOME = "Home",
7 | EXAMPLE = "ExampleScreen",
8 | FEED = "Feed",
9 | SESSIONS = "Sessions",
10 | ABOUT = "About",
11 | HOMETABS = "HomeTabs",
12 | SPEAKERS = "Speakers",
13 | FEEDBACK = "Feedback",
14 | SINGLESPEAKER = "SingleSpeakerScreen",
15 | BIO = "BioScreen",
16 | SESSION_DETAILS = "Session Details"
17 | }
18 |
--------------------------------------------------------------------------------
/src/constants/SponsorTypes.ts:
--------------------------------------------------------------------------------
1 | export enum sponser_types {
2 | PLATINUM = "platinum",
3 | GOLD = "gold",
4 | SILVER = "silver",
5 | BRONZE = "bronze",
6 | }
7 |
--------------------------------------------------------------------------------
/src/contexts/ConfigContext.tsx:
--------------------------------------------------------------------------------
1 | // Contains all contexts configuration items
2 |
--------------------------------------------------------------------------------
/src/declarations/react-native-svg.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import 'react-native-svg';
3 |
4 | // declaration file to add missing props on react-native-svg
5 | declare module 'react-native-svg' {
6 | // svgprops
7 | export interface SvgProps {
8 | xmlns?: string;
9 | xmlnsXlink?: string;
10 | }
11 |
12 | // gprops
13 | export interface GProps {
14 | children?: React.ReactNode;
15 | }
16 |
17 | // clippathprops
18 | export interface ClipPathProps {
19 | children?: React.ReactNode;
20 | }
21 |
22 | // textprops
23 | export interface TextProps {
24 | children?: React.ReactNode;
25 | }
26 |
27 | // TSpanProps
28 | export interface TSpanProps {
29 | children?: React.ReactNode;
30 | }
31 | }
--------------------------------------------------------------------------------
/src/hooks/useCachedResources.ts:
--------------------------------------------------------------------------------
1 | // This is a hook that will load fonts on application startup
2 | import * as SplashScreen from 'expo-splash-screen';
3 | import * as Font from 'expo-font';
4 | import { useEffect, useState } from 'react';
5 |
6 | const useCachedResources = () => {
7 | const [isLoadingComplete, setLoadingComplete] = useState(false);
8 |
9 | // This useEffect will load any resource or data needed prior to rendering the app
10 | useEffect(() => {
11 | const loadResourcesAndDataAsync = async () => {
12 | try {
13 | // Keep the splash screen visible while we fetch resources
14 | SplashScreen.preventAutoHideAsync();
15 |
16 | // Load fonts
17 | await Font.loadAsync({
18 | 'montserrat-light': require('../assets/fonts/Montserrat-Light.ttf'),
19 | 'montserrat-regular': require('../assets/fonts/Montserrat-Regular.ttf'),
20 | 'montserrat-medium': require('../assets/fonts/Montserrat-Medium.ttf'),
21 | 'montserrat-semibold': require('../assets/fonts/Montserrat-SemiBold.ttf'),
22 | 'montserrat-bold': require('../assets/fonts/Montserrat-Bold.ttf'),
23 | 'roboto-medium': require('../assets/fonts/Roboto-Medium.ttf'),
24 | });
25 | } catch (error) {
26 | // We might want to provide this error information to an error reporting service
27 | console.warn(error);
28 | } finally {
29 | setLoadingComplete(true);
30 | SplashScreen.hideAsync();
31 | }
32 | };
33 | loadResourcesAndDataAsync();
34 | },[]);
35 | return isLoadingComplete;
36 | }
37 |
38 | export default useCachedResources;
--------------------------------------------------------------------------------
/src/hooks/useTypedRedux.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
2 | import { AppDispatch, RootState } from "../state/store";
3 |
4 | export const useAppSelector: TypedUseSelectorHook = useSelector;
5 | export const useAppDispatch: () => AppDispatch = useDispatch;
6 |
--------------------------------------------------------------------------------
/src/navigation/BottomTabsNavigator.tsx:
--------------------------------------------------------------------------------
1 | // Bottom tab navigation.
2 | import React from "react";
3 | import { StyleSheet } from "react-native";
4 | import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
5 | import { screen_names } from "../constants/ScreenNames";
6 |
7 | // Create Bottom Navigator.
8 | const Tab = createBottomTabNavigator();
9 |
10 | // Import screens.
11 | import HomeScreen from "../screens/HomeScreen";
12 | import FeedScreen from "../screens/FeedScreen";
13 | import SessionsScreen from "../screens/SessionsScreen";
14 | import AboutScreen from "../screens/AboutScreen";
15 | import HomeIcon from "../assets/icons/HomeIcon";
16 | import { colors } from "../constants/Colors";
17 | import FeedIcon from "../assets/icons/FeedIcon";
18 | import SessionsIcon from "../assets/icons/SessionsIcon";
19 | import AboutIcon from "../assets/icons/AboutIcon";
20 | import { fonts } from "../assets/fonts/fonts";
21 |
22 | const BottomTabsNavigator = () => {
23 | return (
24 | ({
26 | tabBarActiveTintColor: colors.DROIDCONKE_BRICK_RED,
27 | tabBarInactiveTintColor: colors.DROIDCONKE_BLACK,
28 | tabBarStyle: styles.tabBarStyle,
29 | tabBarLabelStyle: styles.tabBarLabelStyle,
30 | })}
31 | >
32 | (
42 |
47 | ),
48 | }}
49 | />
50 | (
60 |
65 | ),
66 | }}
67 | />
68 | (
78 |
83 | ),
84 | }}
85 | />
86 | (
96 |
101 | ),
102 | }}
103 | />
104 |
105 | );
106 | };
107 |
108 | // Tab bar Styles
109 | const styles = StyleSheet.create({
110 | tabBarStyle: {
111 | height: 65,
112 | },
113 | tabBarLabelStyle: {
114 | fontFamily: fonts.MONTSERRAT_LIGHT,
115 | marginTop: -10,
116 | marginBottom: 10,
117 | },
118 | });
119 | export default BottomTabsNavigator;
120 |
--------------------------------------------------------------------------------
/src/navigation/MainStackNavigator.tsx:
--------------------------------------------------------------------------------
1 | // Main navigation.
2 | import { createNativeStackNavigator } from "@react-navigation/native-stack";
3 | import React from "react";
4 | import { screen_names } from "../constants/ScreenNames";
5 |
6 | // Import screens.
7 | import FeedBackScreen from "../screens/FeedbackScreen";
8 | import SpeakersScreen from "../screens/SpeakersScreen";
9 | import BottomTabsNavigator from "./BottomTabsNavigator";
10 |
11 | import {
12 | ImageBackground,
13 | Platform,
14 | StyleSheet,
15 | Text,
16 | TouchableOpacity,
17 | View,
18 | } from "react-native";
19 | import { fonts } from "../assets/fonts/fonts";
20 | import BackArrowIcon from "../assets/icons/BackArrowIcon";
21 | import { colors } from "../constants/Colors";
22 | import BioScreen, { MOCK_BIO } from "../screens/BioScreen";
23 | import { RootStackParamList } from "../types/Navigation";
24 | import SessionDetailsScreen from "../screens/SessionDetailsScreen";
25 |
26 | // Create stack navigator.
27 | const Stack = createNativeStackNavigator();
28 |
29 | const MainStackNavigator = () => {
30 |
31 | return (
32 |
33 |
40 | ({
44 | headerShadowVisible: false,
45 | headerTitleAlign: "left",
46 | headerTitleStyle: {
47 | fontFamily: fonts.MONTSERRAT_REGULAR,
48 | fontSize: 18,
49 | },
50 | headerLeft: () => (
51 | navigation.goBack()}
54 | >
55 |
56 |
57 | ),
58 | })}
59 | />
60 | ({
67 | headerShown: false,
68 | })}
69 | />
70 | ({
74 | headerShadowVisible: false,
75 | headerTitleAlign: "left",
76 | headerTitleStyle: {
77 | fontFamily: fonts.MONTSERRAT_REGULAR,
78 | fontSize: 18,
79 | color: "#FFFFFF",
80 | },
81 | header: () => (
82 |
86 |
87 | navigation.goBack()}
90 | >
91 |
92 |
93 | Feedback
94 |
95 |
96 | ),
97 | })}
98 | />
99 | ({
103 | headerShadowVisible: false,
104 | headerTitleStyle: {
105 | fontFamily: fonts.MONTSERRAT_REGULAR,
106 | fontSize: 18,
107 | color : colors.DROIDCONKE_SHARE_TEXT
108 | },
109 | headerLeft: () => (
110 | navigation.goBack()}
113 | >
114 |
115 |
116 | ),
117 | })}
118 | />
119 |
120 | );
121 | };
122 |
123 | const styles = StyleSheet.create({
124 | feedback_backbutton: {
125 | position: "absolute",
126 | left: 14,
127 | },
128 | feedback_headertext: {
129 | color: "#FFFFFF",
130 | fontFamily: fonts.MONTSERRAT_REGULAR,
131 | fontSize: 18,
132 | position: "absolute",
133 | left: 45,
134 | top: Platform.OS == "android" ? 2 : 4,
135 | },
136 | feedback_backgroundimage: {
137 | height: 160,
138 | },
139 | feedback_rowcontainer: {
140 | flexDirection: "row",
141 | marginTop: 20,
142 | position: "absolute",
143 | },
144 | speakersbackbutton: {
145 | marginRight: 8,
146 | marginBottom: 8,
147 | },
148 | });
149 |
150 | export default MainStackNavigator;
151 |
--------------------------------------------------------------------------------
/src/navigation/StroriesMockNavigation.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createNativeStackNavigator } from "@react-navigation/native-stack";
3 | import { NavigationContainer } from '@react-navigation/native';
4 | import { screen_names } from "../constants/ScreenNames";
5 | import { SafeAreaView } from "react-native";
6 |
7 | type MockScreenStoryProps = {
8 | screen_name : screen_names,
9 | screen_component : any,
10 | options?: {}
11 | }
12 |
13 | const Stack = createNativeStackNavigator();
14 |
15 | const StoriesMockNavigation = (prop : MockScreenStoryProps) => {
16 | return (
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 |
32 | export default StoriesMockNavigation
--------------------------------------------------------------------------------
/src/screens/AboutScreen.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NativeStackScreenProps } from "@react-navigation/native-stack";
3 | import {
4 | Image,
5 | ImageSourcePropType,
6 | SafeAreaView,
7 | ScrollView,
8 | StatusBar,
9 | StyleSheet,
10 | Text,
11 | View,
12 | } from "react-native";
13 | import { screen_names } from "../constants/ScreenNames";
14 | import { colors } from "../constants/Colors";
15 | import { fonts } from "../assets/fonts/fonts";
16 | import TeamMemberCard from "../components/cards/TeamMemberCard";
17 | import { MOCK_BIO, ScreenTitle } from "./BioScreen";
18 | import { RootStackParamList } from "../types/Navigation";
19 | import MainHeader from "../components/layouts/MainHeader";
20 | import DroidconOrganizers from "../components/layouts/DroidconOrganizers";
21 |
22 | //Dummy About Text. Hardcoded for now, to change once data from server is available
23 | const introText = `Droidcon is a global conference focused on the engineering of Android applications. Droidcon provides a forum for developers to network with other developers, share techniques, announce apps and products, and to learn and teach.
24 |
25 | This three-day developer focused gathering will be held in Nairobi Kenya on August 16th to 18th 2022 and will be the largest of its kind in Africa.
26 |
27 | It will have workshops and codelabs focused on the building of Android applications and will give participants an excellent chance to learn about the local Android development ecosystem, opportunities and services as well as meet the engineers and companies who work on them.`;
28 |
29 | const profileImage: ImageSourcePropType = require("../assets/img/john_doe.png");
30 |
31 | //Temporary function to make dummy data for the organizing team
32 | const makeDummyData = (n = 17) => {
33 | let data = [];
34 | for (let i = 1; i <= n; i++) {
35 | data.push({
36 | id: i.toString(),
37 | name: `Member ${i}`,
38 | title: `Title ${i}`,
39 | image: profileImage,
40 | });
41 | }
42 | return data;
43 | };
44 |
45 | const MOCK_DATA_ORGANIZING_TEAM = makeDummyData();
46 |
47 | const AboutScreen = ({
48 | navigation,
49 | }: NativeStackScreenProps<
50 | RootStackParamList,
51 | screen_names.ABOUT,
52 | undefined
53 | >) => {
54 | return (
55 |
56 |
60 |
61 |
62 |
67 |
68 | About
69 | {introText}
70 | Organizing Team
71 |
72 | {MOCK_DATA_ORGANIZING_TEAM.map((member) => (
73 |
78 | navigation.navigate(screen_names.BIO, {
79 | bioData: {
80 | ...MOCK_BIO,
81 | screenTitle: ScreenTitle.Team,
82 | title: member.title,
83 | name: member.name,
84 | img: member.image,
85 | },
86 | })
87 | }
88 | key={member.id}
89 | />
90 | ))}
91 |
92 |
93 |
94 |
95 |
96 | );
97 | };
98 |
99 | const styles = StyleSheet.create({
100 | heroImage: {
101 | width: "100%",
102 | height: 235,
103 | },
104 | headingText: {
105 | fontFamily: fonts.MONTSERRAT_BOLD,
106 | fontSize: 21,
107 | color: colors.DROIDCONKE_BLUE,
108 | },
109 | teamContainer: {
110 | marginVertical: 40,
111 | flexDirection: "row",
112 | flexWrap: "wrap",
113 | marginBottom: 20,
114 | },
115 | descriptionText: {
116 | fontFamily: fonts.MONTSERRAT_REGULAR,
117 | fontSize: 16,
118 | color: colors.DROIDCONKE_BLACK,
119 | lineHeight: 20,
120 | marginTop: 10,
121 | marginBottom: 28,
122 | },
123 | container: {
124 | backgroundColor: colors.DROIDCONKE_WHITE,
125 | flex: 1,
126 | },
127 | paddingVertical: {
128 | paddingVertical: 20,
129 | },
130 | paddingHorizontal: {
131 | paddingHorizontal: 20,
132 | },
133 | });
134 |
135 | export default AboutScreen;
136 |
--------------------------------------------------------------------------------
/src/screens/BioScreen.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NativeStackScreenProps } from "@react-navigation/native-stack";
3 | import {
4 | Text,
5 | View,
6 | StyleSheet,
7 | ImageBackground,
8 | Image,
9 | ScrollView,
10 | TouchableOpacity,
11 | } from "react-native";
12 | import { screen_names } from "../constants/ScreenNames";
13 | import { SafeAreaView } from "react-native-safe-area-context";
14 | import { colors } from "../constants/Colors";
15 | import { fonts } from "../assets/fonts/fonts";
16 | import TwitterIcon from "../assets/icons/TwitterIcon";
17 | import ArrowLeftIcon from "../assets/icons/ArrowLeftIcon";
18 | import AndroidIcon from "../assets/icons/AndroidIcon";
19 | import { RootStackParamList } from "../types/Navigation";
20 | import { getTwitterHandle } from "./SessionDetailsScreen";
21 | import * as Linking from 'expo-linking';
22 |
23 |
24 | export enum ScreenTitle {
25 | Speaker = "Speaker",
26 | Team = "Team",
27 | }
28 | export interface BioDetails {
29 | screenTitle: ScreenTitle;
30 | id?: string;
31 | title?: string; //Applicable where screenTitle === "Team"
32 | img: string;
33 | name: string;
34 | occupation?: string;
35 | skills: string[];
36 | content: string;
37 | twitterHandle: string;
38 | }
39 |
40 | //Mock bio to be replaced with data from the server
41 | export const MOCK_BIO: BioDetails = {
42 | screenTitle: ScreenTitle.Speaker,
43 | id: "1",
44 | name: "Frank Tamre",
45 | img: require("../assets/img/john_doe.png"),
46 | occupation: "Kenya Partner Lead at doridcon Berlin",
47 | skills: ["Android", "Kotlin", "Flutter", "C++"],
48 | content:
49 | "Worked at Intel, co-Founded Moringa School, then started @earlycamp to train young children from 5-16 on how to solve problems with technology.Started @interactive to tell African stories with Games to a global audience.Community wise I organize Android & Kotlin developers every month for a meetUp to chat about technology.I Lead a cool team in organizing droidConKE the largest android developer focussed event in Sub Saharan Africa.I train people,mentor them, build things, am highly experimental, read alot and socialize vertically.",
50 | twitterHandle: "PriestTamzi",
51 | };
52 |
53 | const BioScreen = ({
54 | navigation,
55 | route,
56 | }: NativeStackScreenProps) => {
57 | const { bioData } = route.params;
58 |
59 | //Loop through listed skills, format them ready for display and store them in a skills array
60 | const skills = []; //Stores speaker skills Ex: Android, Kotlin
61 |
62 | if(bioData.skills.length < 1) {
63 |
64 | skills.push({''} );
65 |
66 | } else {
67 |
68 | for (let i = 0; i <= bioData.skills.length; i++) {
69 | skills.push(| {bioData.skills[i]} );
70 | }
71 |
72 | }
73 |
74 | const parragraphArray = bioData.content.split("."); //Create a new array by diving the speaker bio string. Fullstop denotes a new paragraph
75 | const textElements = []; //Array to hold the bio 'paragraphs' with line break added
76 | let j = 0;
77 | while (j < parragraphArray.length) {
78 | textElements.push(parragraphArray[j] + "\n\n");
79 | j++;
80 | }
81 |
82 | const goToSpeakersTwitterProfile = (profileUrl: string) => Linking.openURL(profileUrl)
83 |
84 | return (
85 |
86 |
87 |
88 |
92 |
93 | navigation.goBack()}>
94 |
95 |
96 |
97 | {bioData.screenTitle}
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | {bioData.screenTitle === ScreenTitle.Speaker ? (
108 | <>
109 |
114 | Speaker:
115 | >
116 | ) : (
117 | {bioData.title}
118 | )}
119 |
120 | {bioData.name}
121 | {bioData.occupation}
122 | {skills}
123 |
124 |
125 |
126 | Bio
127 | {textElements}
128 |
129 |
130 |
131 | {bioData.twitterHandle !== null && bioData.twitterHandle.length > 0 ? <>
132 |
133 | Twitter Handle
134 | goToSpeakersTwitterProfile(bioData.twitterHandle)}
136 | >
137 |
138 |
143 |
149 | {getTwitterHandle(bioData.twitterHandle)}
150 |
151 |
152 |
153 |
154 |
155 | >
156 | :
157 | <>
158 | {''}
159 | >}
160 |
161 |
162 | );
163 | };
164 |
165 | export default BioScreen;
166 |
167 | const styles = StyleSheet.create({
168 | container: {
169 | width: "100%",
170 | height: "100%",
171 | display: "flex",
172 | },
173 | header: {
174 | flex: 2,
175 | position: "relative",
176 | },
177 | bgimage: {
178 | flex: 1,
179 | },
180 | body: {
181 | backgroundColor: colors.DROIDCONKE_WHITE,
182 | flex: 7,
183 | },
184 | footer: {
185 | marginTop: 1,
186 | backgroundColor: colors.DROIDCONKE_WHITE,
187 | flex: 1,
188 | flexDirection: "row",
189 | justifyContent: "space-between",
190 | alignItems: "center",
191 | paddingHorizontal: 10,
192 | },
193 | footerText: {
194 | fontSize: 16,
195 | fontFamily: fonts.MONTSERRAT_REGULAR,
196 | },
197 | arrowleft: {
198 | backgroundColor: "red",
199 | width: 50,
200 | height: 50,
201 | },
202 | profilepic: {
203 | width: 100,
204 | height: 100,
205 | position: "absolute",
206 | left: "37%",
207 | top: "75%",
208 | zIndex: 1,
209 | borderRadius: 50,
210 | borderWidth: 3,
211 | borderColor: colors.DROIDCONKE_LIGHT_GREEN,
212 | },
213 | bioSummary: {
214 | alignItems: "center",
215 | paddingTop: 65,
216 | },
217 | title: {
218 | color: colors.DROIDCONKE_BRICK_RED,
219 | fontSize: 15,
220 | fontFamily: fonts.MONTSERRAT_REGULAR,
221 | },
222 | name: {
223 | color: colors.DROIDCONKE_BLUE,
224 | fontSize: 20,
225 | marginBottom: 10,
226 | fontFamily: fonts.MONTSERRAT_BOLD,
227 | padding: 0,
228 | lineHeight: 20,
229 | },
230 | descriptionText: {
231 | fontWeight: "500",
232 | color: colors.DROIDCONKE_LIGHT_GREY,
233 | fontFamily: fonts.MONTSERRAT_REGULAR,
234 | },
235 | bioDetailsContainer: {
236 | height: "100%",
237 | paddingHorizontal: 10,
238 | },
239 | bioDetailsHeader: {
240 | marginTop: 20,
241 | color: colors.DROIDCONKE_BLUE,
242 | fontSize: 20,
243 | fontWeight: "900",
244 | fontFamily: fonts.MONTSERRAT_BOLD,
245 | },
246 | bioDetailsContent: {
247 | color: colors.DROIDCONKE_BLACK,
248 | fontWeight: "400",
249 | fontFamily: fonts.MONTSERRAT_REGULAR,
250 | },
251 | twitterHandleButton: {
252 | borderWidth: 2,
253 | paddingHorizontal: 25,
254 | borderColor: colors.DROIDCONKE_BLUE,
255 | borderRadius: 7,
256 | color: colors.DROIDCONKE_BLUE,
257 | display: "flex",
258 | flexDirection: "row",
259 | paddingVertical: 5,
260 | },
261 | backbutton: {
262 | stydisplay: "flex",
263 | flexDirection: "row",
264 | width: 140,
265 | alignItems: "center",
266 | paddingVertical: 10,
267 | paddingHorizontal: 10,
268 | },
269 | titleContainer: {
270 | display: "flex",
271 | flexDirection: "row",
272 | alignItems: "center",
273 | padding: 0,
274 | margin: 0,
275 | },
276 | speakerheadertext: {
277 | fontFamily: fonts.MONTSERRAT_REGULAR,
278 | color: colors.DROIDCONKE_PEARL,
279 | fontSize: 18,
280 | marginLeft: 20,
281 | },
282 | });
283 |
--------------------------------------------------------------------------------
/src/screens/FeedScreen.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { NativeStackScreenProps } from "@react-navigation/native-stack";
3 | import { SafeAreaView, Text, StyleSheet, FlatList , Modal, View , TouchableOpacity } from "react-native";
4 | import { screen_names } from "../constants/ScreenNames";
5 | import { ParamListBase } from "@react-navigation/native";
6 | import MainHeader from "../components/layouts/MainHeader";
7 | import { colors } from "../constants/Colors";
8 | import FeedsCard from "../components/cards/FeedsCard";
9 | import ShareIcon from "../assets/icons/ShareIcon";
10 | import type { SocialMedia } from "../components/buttons/SocialButton";
11 | import SocialButton from "../components/buttons/SocialButton";
12 | import { fonts } from "../assets/fonts/fonts";
13 | import { useAppDispatch, useAppSelector } from "../hooks/useTypedRedux";
14 | import { useGetFeedsQuery } from "../services/auth";
15 | import { setFeeds } from "../state/feeds";
16 |
17 | const FEED = [
18 | {
19 | data: [
20 | {
21 | title: "Test",
22 | body: "Good one",
23 | topic: "droidconweb",
24 | url: "https://droidcon.co.ke",
25 | image: "https://rebrand.ly/5a6672",
26 | created_at: "2020-03-19 18:45:49",
27 | },
28 | {
29 | title: "niko fine",
30 | body: "this is a test",
31 | topic: "droidconweb",
32 | url: "https://droidcon.co.ke",
33 | image: "https://rebrand.ly/5a6672",
34 | created_at: "2020-03-19 18:43:38",
35 | },
36 | {
37 | title: "niko fine",
38 | body: "this is a test",
39 | topic: "droidconweb",
40 | url: "https://droidcon.co.ke",
41 | image: "https://rebrand.ly/5a6672",
42 | created_at: "2020-03-19 18:43:38",
43 | },
44 | {
45 | title: "niko fine",
46 | body: "this is a test",
47 | topic: "droidconweb",
48 | url: "https://droidcon.co.ke",
49 | image: "https://rebrand.ly/5a6672",
50 | created_at: "2020-03-19 18:43:38",
51 | },
52 | {
53 | title: "niko fine",
54 | body: "this is a test",
55 | topic: "droidconweb",
56 | url: "https://droidcon.co.ke",
57 | image: "https://rebrand.ly/5a6672",
58 | created_at: "2020-03-19 18:43:38",
59 | },
60 | ],
61 | meta: {
62 | paginator: {
63 | count: 2,
64 | per_page: "10",
65 | current_page: 1,
66 | next_page: null,
67 | has_more_pages: false,
68 | next_page_url: null,
69 | previous_page_url: null,
70 | },
71 | },
72 | },
73 | ];
74 |
75 | const FeedScreen = ({
76 | navigation,
77 | }: NativeStackScreenProps) => {
78 |
79 | // Redux dispatch.
80 | const dispatch = useAppDispatch();
81 |
82 | // State variables feeds and user from redux state.
83 | const { feeds, meta } = useAppSelector((state) => state.feeds);
84 |
85 | const { user } = useAppSelector((state) => state.user);
86 |
87 | const [page, setPage] = useState('1');
88 |
89 | const { data: feedsData, error: feedsError, isLoading: feedsIsLoading, isSuccess: feedsIsSuccess, isError: feedsIsError } = useGetFeedsQuery({page: page},{skip: user === null})
90 |
91 | useEffect(() => {
92 | console.log({feedsData, feedsError, feedsIsLoading, feedsIsSuccess, feedsIsError})
93 | if(feedsIsSuccess && !feedsIsLoading && feedsData) {
94 | dispatch(setFeeds({ feeds: feedsData.data , meta: feedsData.meta}))
95 | }
96 |
97 | },[feedsData, feedsError, feedsIsLoading, feedsIsSuccess, feedsIsError])
98 |
99 | // Function to load more feeds when flatlist reaches the end of scroll.
100 | const loadMoreFeeds = () => {
101 | if (meta?.has_more_pages === true) {
102 | setPage(meta?.next_page)
103 | }
104 | }
105 | const [showmodal,setModalVisibility] = useState(false)
106 |
107 | const shareFeed = (item) => {
108 | toggleModalVisibility()
109 | }
110 |
111 | const toggleModalVisibility = () => setModalVisibility(!showmodal)
112 | return (
113 |
114 |
115 |
116 | }
121 | onEndReached={loadMoreFeeds}
122 | onEndReachedThreshold ={0.1}
123 | />
124 |
125 |
126 | );
127 | };
128 |
129 | export type ShareModalProps = {
130 | showModal : boolean,
131 | setModalVisibility : Function
132 | }
133 |
134 | const ShareFeedModal = (props : ShareModalProps) : JSX.Element => {
135 | const visible = props.showModal
136 | const setvisibility = props.setModalVisibility
137 | return (
138 |
139 |
140 |
141 |
142 |
143 | Share
144 | {setvisibility(false)}}>
145 |
146 | CANCEL
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 | )
156 | }
157 |
158 |
159 | export type DoubleSocialButtonRowProps = {
160 | leftSocialButton : SocialMedia
161 | rightSocialButton : SocialMedia
162 | }
163 |
164 | const DoubleSocialButtonRow = (props : DoubleSocialButtonRowProps) : JSX.Element => {
165 | return (
166 |
167 |
168 |
169 |
170 | )
171 | }
172 |
173 | export default FeedScreen;
174 |
175 | const styles = StyleSheet.create({
176 | container: {
177 | backgroundColor: colors.DROIDCONKE_WHITE,
178 | flex: 1,
179 | },
180 | paddingVertical: {
181 | paddingVertical: 20,
182 | },
183 | paddingHorizontal: {
184 | paddingHorizontal: 20,
185 | },
186 | shareModalOverlay : {
187 | flex:1,
188 | backgroundColor : colors.DROIDCONKE_MODAL_OVERLAY ,
189 | flexDirection : "column-reverse"
190 | },
191 | shareModalContainer : {
192 | backgroundColor : colors.DROIDCONKE_SHARE_MODAL_BACKGROUND ,
193 | height : "30%" ,
194 | borderTopRightRadius : 14 ,
195 | borderTopLeftRadius : 14
196 | },
197 | shareModalUpperRow : {
198 | flexDirection : "row",
199 | marginVertical : 35,
200 | alignItems : "center"
201 | },
202 | shareIcon : {
203 | marginLeft : 20,
204 | marginRight : 10
205 | },
206 | cancelButton : {
207 | position : "absolute" ,
208 | right : 20 ,
209 | marginTop : 5
210 | },
211 | shareText : {
212 | fontFamily : fonts.MONTSERRAT_BOLD,
213 | fontSize : 18,
214 | textAlign : "left",
215 | color : colors.DROIDCONKE_SHARE_TEXT
216 | },
217 | cancelText : {
218 | fontFamily : fonts.MONTSERRAT_LIGHT,
219 | fontSize : 13,
220 | color : colors.DROIDCONKE_CANCEL_TEXT
221 | },
222 | doubleSocialButtonRowContainer : {
223 | flexDirection : "row" ,
224 | marginBottom : 25 ,
225 | justifyContent : "space-around",
226 | height : "18%"
227 | }
228 | });
229 |
--------------------------------------------------------------------------------
/src/screens/HomeScreenNotLoggedIn.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | SafeAreaView,
4 | ScrollView,
5 | StatusBar,
6 | StyleSheet,
7 | TouchableOpacity,
8 | View,
9 | Text,
10 | Image,
11 | Modal,
12 | Dimensions,
13 | } from "react-native";
14 | import { fonts } from "../assets/fonts/fonts";
15 | import GoogleIcon from "../assets/icons/GoogleIcon";
16 | import PolygonIcon from "../assets/icons/PolygonIcon";
17 | import DroidconOrganizers from "../components/layouts/DroidconOrganizers";
18 | import DroidconSponsors from "../components/layouts/DroidconSponsors";
19 | import MainHeader from "../components/layouts/MainHeader";
20 | import { colors } from "../constants/Colors";
21 | import { layoutProperties } from "../constants/Properties";
22 |
23 | const HomeScreenNotLoggedIn = ({
24 | handleLogin,
25 | }: {
26 | handleLogin: () => void;
27 | }) => {
28 | const [modalVisible, setModalVisible] = useState(false);
29 |
30 | // Function to togle modal.
31 | const openModal = () => setModalVisible(true);
32 | const closeModal = () => setModalVisible(false);
33 |
34 | return (
35 |
36 |
44 |
45 |
51 |
52 |
53 | Welcome to the largest Focused Android Developer community in
54 | Africa!
55 |
56 |
57 |
58 |
63 |
64 |
65 |
70 |
71 | Call for speakers
72 | Apply to be a speaker
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | CANCEL
86 |
87 |
88 |
89 |
90 | Sign in with Google
91 |
92 |
93 |
94 |
95 |
96 |
97 | );
98 | };
99 |
100 | const styles = StyleSheet.create({
101 | container: {
102 | backgroundColor: colors.DROIDCONKE_WHITE,
103 | flex: 1,
104 | },
105 | paddingVertical: {
106 | paddingVertical: 20,
107 | },
108 | paddingHorizontal: {
109 | paddingHorizontal: 20,
110 | },
111 | marginVerticalSeparator: {
112 | marginVertical: 15,
113 | },
114 | marginVerticalIcons: {
115 | marginVertical: 10,
116 | },
117 | droidcon_ke_banner: {
118 | width: Dimensions.get("screen").width - 40,
119 | height: 175,
120 | },
121 | confetti: {
122 | width: "35%",
123 | height: 65,
124 | },
125 | welcomeText: {
126 | fontFamily: fonts.MONTSERRAT_SEMIBOLD,
127 | fontSize: 16,
128 | lineHeight: 20,
129 | marginHorizontal: 3,
130 | },
131 | cfpContainer: {
132 | backgroundColor: colors.DROIDCONKE_GREEN,
133 | padding: 20,
134 | ...layoutProperties.flexRow,
135 | ...layoutProperties.justifyBetween,
136 | ...layoutProperties.itemsCenter,
137 | borderRadius: 10,
138 | },
139 | marginBottomSeparator2: {
140 | marginBottom: 5,
141 | },
142 | marginVerticalSeparator2: {
143 | marginVertical: 18,
144 | },
145 | cfpTitle: {
146 | fontFamily: fonts.MONTSERRAT_BOLD,
147 | fontSize: 17,
148 | lineHeight: 20,
149 | color: colors.DROIDCONKE_WHITE,
150 | },
151 | modalContainer: {
152 | flex: 1,
153 | backgroundColor: colors.DROIDCONKE_MODAL_OVERLAY,
154 | ...layoutProperties.justifyCenter,
155 | ...layoutProperties.itemsCenter,
156 | },
157 | modalContentContainer: {
158 | height: Dimensions.get("screen").height / 2.1,
159 | width: Dimensions.get("screen").width - 100,
160 | backgroundColor: colors.DROIDCONKE_WHITE,
161 | borderRadius: 14,
162 | elevation: 20,
163 | padding: 20,
164 | },
165 | textCancel: {
166 | fontFamily: fonts.MONTSERRAT_LIGHT,
167 | fontSize: 13,
168 | lineHeight: 16,
169 | color: colors.DROIDCONKE_DARK_GREY,
170 | textAlign: "right",
171 | },
172 | googleBtnContainer: {
173 | flex: 1,
174 | ...layoutProperties.justifyCenter,
175 | ...layoutProperties.itemsCenter,
176 | },
177 | googleBtn: {
178 | width: 208,
179 | paddingHorizontal: 10,
180 | ...layoutProperties.flexRow,
181 | ...layoutProperties.itemsCenter,
182 | ...layoutProperties.justifyEvenly,
183 | borderColor: colors.DROIDCONKE_BLACK_GOOGLESIGNIN_TRANSLUCENT,
184 | borderRadius: 7,
185 | borderWidth: 2,
186 | },
187 | googleBtnLabel: {
188 | fontFamily: fonts.ROBOTO_MEDIUM,
189 | fontSize: 14,
190 | lineHeight: 19,
191 | color: colors.DROIDCONKE_BLACK_TEXT_AND_LABEL,
192 | marginHorizontal: 5,
193 | },
194 | });
195 | export default HomeScreenNotLoggedIn;
196 |
--------------------------------------------------------------------------------
/src/screens/SessionDetailsScreen.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NativeStackScreenProps } from "@react-navigation/native-stack";
3 | import { SafeAreaView, Text, StyleSheet, ScrollView, Button , Modal, View , TouchableOpacity, Image, ImageSourcePropType} from "react-native";
4 | import { screen_names } from "../constants/ScreenNames";
5 | import { colors } from "../constants/Colors";
6 | import { fonts } from "../assets/fonts/fonts";
7 | import { RootStackParamList } from "../types/Navigation";
8 | import AndroidIcon from "../assets/icons/AndroidIcon";
9 | import Star from "../assets/icons/Star";
10 | import ShareIcon from "../assets/icons/ShareIcon";
11 | import TwitterIcon from "../assets/icons/TwitterIcon";
12 | import { layoutProperties } from "../constants/Properties";
13 | import * as Linking from 'expo-linking';
14 |
15 | export const getTwitterHandle = (profileLink : string) => {
16 | const twitterHandle = profileLink.split("/")[3]
17 | return twitterHandle
18 | }
19 |
20 | const placeholder : ImageSourcePropType = require("../assets/img/droidconkeplaceholder.png")
21 |
22 | const SessionDetailsScreen = ({
23 | navigation,
24 | route
25 | }: NativeStackScreenProps) => {
26 |
27 | const {sessionData} = route.params
28 | const room = sessionData.rooms[0].title
29 |
30 | const get12hourformat = (timeString : string) : string => {
31 | const [hourString, minute, seconds ] = timeString.split(":")
32 | const hour = +hourString % 24;
33 | return (hour % 12 || 12) + ":" + minute + (hour < 12 ? "AM" : "PM");
34 | }
35 |
36 | const starttime = get12hourformat(sessionData.start_time)
37 | const endtime = get12hourformat(sessionData.end_time)
38 |
39 | const goToSpeakersTwitterProfile = (profileUrl: string) => Linking.openURL(profileUrl)
40 |
41 | return (
42 |
43 |
44 |
45 |
46 |
47 |
48 | {sessionData.speakers.length > 1 ? 'Speakers' : 'Speaker'}
49 |
50 |
51 |
52 |
53 | {sessionData.speakers.map((speaker,index )=>
54 | <>
55 |
56 | {speaker.name}
57 |
58 | {index!== sessionData.speakers.length - 1 &&
59 | &
60 | }
61 | >
62 | )}
63 |
64 |
65 |
66 |
67 | {sessionData.title}
68 |
69 |
70 | {sessionData.description}
71 |
72 | {sessionData.session_image === null ?
73 | <>
74 |
75 | >:
76 | <>
77 |
78 | >}
79 |
80 | {starttime} - {endtime} | {room}
81 |
82 |
83 |
84 | #{sessionData.session_level}
85 |
86 |
87 | {sessionData.speakers.map(speaker =>
88 | <>
89 | {speaker?.twitter?.length > 0 ?
90 | <>
91 |
92 | Twitter Handle
93 | goToSpeakersTwitterProfile(speaker.twitter)} style={styles.twitterHandleButton}>
94 |
99 |
105 | {getTwitterHandle(speaker?.twitter)}
106 |
107 |
108 |
109 | >
110 | :
111 | <>
112 | {''}
113 | >
114 | }
115 | >
116 | )}
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | );
126 | };
127 |
128 | const styles = StyleSheet.create({
129 | scrollableContainer : {
130 | height : "85%"
131 | },
132 | footerContainer : {
133 | height : "15%"
134 | },
135 | mainContainer : {
136 | flex : 1,
137 | backgroundColor : colors.DROIDCONKE_WHITE,
138 | padding : 20
139 | },
140 | rowContainer : {
141 | flexDirection : "row",
142 | alignItems : "center"
143 | },
144 | androidIcon : {
145 | marginRight : 8
146 | },
147 | speakerTitle : {
148 | fontFamily : fonts.MONTSERRAT_REGULAR,
149 | color : colors.DROIDCONKE_BRICK_RED,
150 | fontSize : 11
151 | },
152 | speakerName : {
153 | color : colors.DROIDCONKE_BLUE,
154 | fontFamily : fonts.MONTSERRAT_BOLD,
155 | fontSize : 20,
156 | textAlign: 'center',
157 | },
158 | starIcon : {
159 | position : "absolute",
160 | right : 10
161 | },
162 | titleText : {
163 | marginTop : 25,
164 | fontSize : 18,
165 | fontFamily : fonts.MONTSERRAT_BOLD,
166 | color : colors.DROIDCONKE_BLACK
167 | },
168 | contentText : {
169 | fontFamily : fonts.MONTSERRAT_REGULAR,
170 | fontSize : 16,
171 | textAlign : "left",
172 | color : colors.DROIDCONKE_DARK_GREY,
173 | marginTop : 15
174 | },
175 | sessionImage : {
176 | borderColor : colors.DROIDCONKE_MEDIUM_AQUAMARINE,
177 | borderRadius : 10,
178 | borderWidth : 1,
179 | width : "100%",
180 | height: 190,
181 | marginTop : 10,
182 | marginBottom : 30
183 | },
184 | timeAndroomText : {
185 | fontFamily : fonts.MONTSERRAT_REGULAR,
186 | color: colors.DROIDCONKE_DARK_GREY,
187 | fontSize : 12,
188 | textAlign : "left"
189 | },
190 | sessionLevelContainer: {
191 | backgroundColor : colors.DROIDCONKE_DARK_BLACK,
192 | paddingHorizontal: 9,
193 | flexWrap: 'wrap',
194 | marginTop : 15,
195 | marginBottom : 30,
196 | alignSelf: 'flex-start',
197 | borderRadius : 5,
198 | },
199 | sessionLevelText : {
200 | color : colors.DROIDCONKE_WHITE,
201 | fontSize : 13,
202 | fontFamily : fonts.MONTSERRAT_REGULAR,
203 | },
204 | twitterText : {
205 | fontFamily : fonts.MONTSERRAT_REGULAR,
206 | fontSize : 16,
207 | color : colors.DROIDCONKE_BLACK,
208 | textAlign : "left"
209 | },
210 | ShareIconContainer : {
211 | position : "absolute",
212 | backgroundColor : colors.DROIDCONKE_BRICK_RED,
213 | height : 44,
214 | width : 44,
215 | right : 20,
216 | bottom : 30,
217 | borderRadius : 22,
218 | justifyContent : "center",
219 | alignItems : "center"
220 | },
221 | twitterHandleButton: {
222 | borderWidth: 1,
223 | paddingHorizontal: 25,
224 | borderColor: colors.DROIDCONKE_BLUE,
225 | borderRadius: 10,
226 | color: colors.DROIDCONKE_BLUE,
227 | display: "flex",
228 | flexDirection: "row",
229 | paddingVertical: 5,
230 | position : "absolute",
231 | right : 0
232 | },
233 | twitterRowContainer : {
234 | flexDirection : "row",
235 | marginBottom : 20,
236 | alignItems: 'center',
237 | }
238 | })
239 |
240 | export default SessionDetailsScreen
241 |
--------------------------------------------------------------------------------
/src/screens/SessionsScreen.stories.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentStory } from "@storybook/react";
2 | import React from "react";
3 | import SessionsScreen, { SessionsScreenProps } from "./SessionsScreen";
4 |
5 | const DEFAULT_PROPS: SessionsScreenProps = {};
6 |
7 | export default {
8 | title: "DroidconKe/Screens/SessionsScreen",
9 | component: SessionsScreen,
10 | args: DEFAULT_PROPS,
11 | };
12 |
13 | const Template: ComponentStory = (args) => (
14 |
15 | );
16 |
17 | export const SessionsScreenStory = Template.bind({});
18 |
--------------------------------------------------------------------------------
/src/screens/SpeakersScreen.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ComponentStory, ComponentMeta } from "@storybook/react";
3 | import SpeakersScreen from "./SpeakersScreen";
4 | import StoriesMockNavigation from "../navigation/StroriesMockNavigation";
5 | import { screen_names } from "../constants/ScreenNames";
6 | import { fonts } from '../assets/fonts/fonts';
7 |
8 | export default {
9 | title: "DroidconKe/Screens/SpeakersScreen",
10 | component: StoriesMockNavigation,
11 | } as ComponentMeta;
12 |
13 | const Template: ComponentStory = () =>
14 | ;
22 |
23 | export const SpeakersScreenStory = Template.bind({});
--------------------------------------------------------------------------------
/src/screens/SpeakersScreen.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import {
3 | FlatList,
4 | SafeAreaView,
5 | StyleSheet,
6 | } from "react-native";
7 | import SpeakerCard from "../components/cards/SpeakerCard";
8 | import { NativeStackScreenProps } from "@react-navigation/native-stack";
9 | import { screen_names } from "../constants/ScreenNames";
10 | import { ParamListBase } from "@react-navigation/native";
11 | import { colors } from "../constants/Colors";
12 | import { useAppSelector } from "../hooks/useTypedRedux";
13 | import Session from "../types/Session";
14 | import Speaker from "../types/Speaker";
15 | import { ScreenTitle } from "./BioScreen";
16 |
17 | const SpeakersScreen = ({
18 | navigation,
19 | }: NativeStackScreenProps) => {
20 |
21 | const { schedule } = useAppSelector((state) => state.schedule);
22 | const [sessions, setSessions] = useState();
23 |
24 | useEffect(() => {
25 | if(schedule?.data){
26 | const listOfSessions: Session[] = []
27 | Object.values(schedule?.data).forEach(daySessions => listOfSessions.push(...daySessions));
28 | setSessions(listOfSessions)
29 | }
30 | },[schedule?.data])
31 |
32 | console.log(sessions)
33 | const filteredSessions = sessions?.filter(session => session.speakers.length !== 0)
34 |
35 | const goToSessionScreen = (session : Session) => navigation.navigate(screen_names.SESSION_DETAILS,{sessionData: session})
36 |
37 | const goToSpeakerScreen = (speaker: Speaker) => {
38 |
39 | const ScreenBio = {
40 | screenTitle: ScreenTitle.Speaker,
41 | id: speaker.name,
42 | title: speaker.tagline,
43 | img: speaker.avatar,
44 | name: speaker.name,
45 | occupation: speaker.tagline,
46 | skills: [],
47 | content: speaker.biography,
48 | twitterHandle: speaker.twitter
49 | }
50 | navigation.navigate(screen_names.BIO, { bioData: ScreenBio})
51 | }
52 |
53 | return (
54 |
55 | {
58 | if(item.speakers.length < 1) {
59 | return null
60 | }
61 | return (
62 | <>
63 | {item.speakers.map(speaker => (
64 |
69 | ))}
70 | >
71 | )
72 |
73 | }}
74 | keyExtractor={(item: Session) => item.id.toString()}
75 | numColumns={2}
76 | />
77 |
78 | );
79 | };
80 |
81 | const styles = StyleSheet.create({
82 | container: {
83 | backgroundColor: colors.DROIDCONKE_WHITE,
84 | padding: 5,
85 | },
86 | });
87 |
88 | export default SpeakersScreen;
89 |
--------------------------------------------------------------------------------
/src/services/api.ts:
--------------------------------------------------------------------------------
1 | // example
2 |
--------------------------------------------------------------------------------
/src/services/auth.ts:
--------------------------------------------------------------------------------
1 | import { API_URL, EVENT_SLUG } from "@env";
2 | import { createApi, fetchBaseQuery, retry } from "@reduxjs/toolkit/query/react";
3 | import { RootState } from "../state/store";
4 | import User from "../types/Users";
5 | import Schedule from "../types/Schedule";
6 | import { Pagination } from "../types/Pagination";
7 | import Feed from "../types/Feed";
8 |
9 | interface LoginResponse {
10 | user?: User | null;
11 | token?: string | null;
12 | message?: string;
13 | errors?: { [key: string]: string[] }[];
14 | }
15 |
16 | interface LoginRequest {
17 | access_token: string;
18 | }
19 |
20 | interface FeedbackResponse {
21 | message: string;
22 | }
23 |
24 | interface EventFeedbackRequest {
25 | feedback: string;
26 | rating: string;
27 | }
28 |
29 | interface SessionFeedbackRequest {
30 | feedback: string;
31 | rating: string;
32 | sessionSlug: string;
33 | }
34 | interface FeedsResponse {
35 | data: T[];
36 | meta: Pagination;
37 | }
38 |
39 | // Define a service using a base URL and expected endpoints
40 | export const userApi = createApi({
41 | baseQuery: fetchBaseQuery({
42 | baseUrl: API_URL,
43 |
44 | // add token if it exists
45 | prepareHeaders: (headers, { getState }) => {
46 | const user = (getState() as RootState).user;
47 |
48 | // If we have a token set in state, let's assume that we should be passing it.
49 | if (user.token) {
50 | headers.append("Api-Authorization-Key", "droidconKe-2020");
51 | headers.append("Authorization", `Bearer ${user.token}`);
52 | }
53 |
54 | return headers;
55 | },
56 | }),
57 | endpoints: (builder) => ({
58 | googleSocialAuth: builder.mutation({
59 | query: (arg) => {
60 | const formData = new FormData();
61 | formData.append("access_token", arg.access_token);
62 |
63 | return {
64 | url: "/social_login/google",
65 | method: "POST",
66 | body: formData,
67 | headers: {
68 | "Content-Type": "multipart/form-data",
69 | Accept: "application/json",
70 | "Api-Authorization-Key": "droidconKe-2020",
71 | },
72 | };
73 | },
74 | }),
75 | getSchedule: builder.query({
76 | query: () => {
77 | return {
78 | url: `/events/${EVENT_SLUG}/schedule?grouped=true`,
79 | method: "GET",
80 | };
81 | },
82 | }),
83 | sendEventFeedback: builder.mutation(
84 | {
85 | query: (arg) => {
86 | const formData = new FormData();
87 | formData.append("feedback", arg.feedback);
88 | formData.append("rating", arg.rating);
89 | return {
90 | url: `/events/${EVENT_SLUG}/feedback`,
91 | method: "POST",
92 | body: formData,
93 | headers: {
94 | "Content-Type": "multipart/form-data",
95 | },
96 | // This is the same as passing 'text'
97 | responseHandler: (response) => response.text(),
98 | };
99 | },
100 | }
101 | ),
102 | sendSessionFeedback: builder.mutation({
103 | query: (arg) => {
104 | const formData = new FormData();
105 | formData.append("feedback", arg.feedback);
106 | formData.append("rating", arg.rating);
107 |
108 | return {
109 | url: `/events/${EVENT_SLUG}/feedback/sessions/${arg.sessionSlug}`,
110 | method: "POST",
111 | body: formData,
112 | headers: {
113 | "Content-Type": "multipart/form-data",
114 | },
115 | // This is the same as passing 'text'
116 | responseHandler: (response) => response.text(),
117 | };
118 | },
119 | }),
120 | getFeeds: builder.query, string | Blob>({
121 | query: (page = "1") => {
122 | const searchParams = new URLSearchParams();
123 | searchParams.append("per_page", "10");
124 | searchParams.append("page", page as string);
125 | return {
126 | url: `/events/${EVENT_SLUG}/feeds?${searchParams}`,
127 | method: "GET",
128 | };
129 | },
130 | }),
131 | }),
132 | });
133 |
134 | // Export hooks for usage in functional components, which are
135 | // auto-generated based on the defined endpoints
136 | export const {
137 | useGoogleSocialAuthMutation,
138 | useGetScheduleQuery,
139 | useGetFeedsQuery,
140 | useSendEventFeedbackMutation,
141 | useSendSessionFeedbackMutation,
142 | } = userApi;
143 |
--------------------------------------------------------------------------------
/src/state/feeds.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2 | import { Pagination } from "../types/Pagination";
3 | import Feed from "../types/Feed";
4 |
5 | interface FeedState {
6 | feeds: Feed[] | null | undefined;
7 | meta: Pagination | null | undefined;
8 | }
9 |
10 | const feedsSlice = createSlice({
11 | name: 'feeds',
12 | initialState: {
13 | feeds: [],
14 | meta: null,
15 | } as FeedState,
16 | reducers: {
17 | setFeeds: (state, {payload: { feeds, meta}}: PayloadAction) => {
18 | state.feeds = feeds;
19 | state.meta = meta
20 | }
21 | }
22 | })
23 |
24 | export const { setFeeds } = feedsSlice.actions;
25 | export default feedsSlice.reducer;
--------------------------------------------------------------------------------
/src/state/schedule.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2 | import Schedule from "../types/Schedule";
3 |
4 | const scheduleSlice = createSlice({
5 | name: "schedule",
6 | initialState: {
7 | schedule: undefined as Schedule | undefined,
8 | selectedDay: undefined as string | undefined,
9 | },
10 | reducers: {
11 | setSchedule: (state, action: PayloadAction) => {
12 | state.schedule = action.payload;
13 | },
14 | setSelectedDay: (state, action: PayloadAction) => {
15 | state.selectedDay = action.payload;
16 | },
17 | },
18 | });
19 |
20 | export const { setSchedule, setSelectedDay } = scheduleSlice.actions;
21 | export default scheduleSlice.reducer;
22 |
--------------------------------------------------------------------------------
/src/state/sessions.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2 | import { Pagination } from "../types/Pagination";
3 | import Session from "../types/Session";
4 |
5 | interface SessionState {
6 | sessions: Session[] | null | undefined;
7 | meta: Pagination | null | undefined;
8 | }
9 | const sessionsSlice = createSlice({
10 | name: 'sessions',
11 | initialState: {
12 | sessions: [],
13 | meta: null,
14 | } as SessionState,
15 | reducers: {
16 | setSessions: (state, {payload: { sessions, meta}}: PayloadAction) => {
17 | state.sessions = sessions;
18 | state.meta = meta
19 | }
20 | }
21 | });
22 |
23 | export const { setSessions } = sessionsSlice.actions;
24 | export default sessionsSlice.reducer;
--------------------------------------------------------------------------------
/src/state/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import userReducer, { setUser } from "./user";
3 | import scheduleReducer from "./schedule";
4 | import sessionsReducer from './sessions';
5 | import feedsReducer from './feeds';
6 | import { userApi } from "../services/auth";
7 | import { setupListeners } from "@reduxjs/toolkit/dist/query";
8 | import AuthStorage from "../utils/authStorage";
9 |
10 |
11 |
12 | const authStorage = new AuthStorage();
13 |
14 | export const store = configureStore({
15 | reducer: {
16 | user: userReducer,
17 | schedule: scheduleReducer,
18 | sessions: sessionsReducer,
19 | feeds: feedsReducer,
20 | // Add the generated reducer as a specific top-level slice
21 | [userApi.reducerPath]: userApi.reducer,
22 | },
23 | // Adding the api middleware enables caching, invalidation, polling,
24 | // and other useful features of `rtk-query`.
25 | middleware: (getDefaultMiddleware) =>
26 | getDefaultMiddleware().concat(userApi.middleware),
27 | });
28 |
29 | // optional, but required for refetchOnFocus/refetchOnReconnect behaviors
30 | // see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
31 | setupListeners(store.dispatch);
32 |
33 | // Function that sets user from async storage to redux store
34 | const initializeUser = () => {
35 | return (dispatch : typeof store.dispatch) => {
36 | authStorage.getUser().then(user => {
37 | if(user !== null) {
38 | //console.log('token from asyncstorage is' + user.token)
39 | dispatch(setUser({user: user.user, token: user.token}))
40 | }
41 | })
42 | }
43 | }
44 |
45 | store.dispatch(initializeUser());
46 |
47 | export type RootState = ReturnType;
48 | export type AppDispatch = typeof store.dispatch;
49 |
--------------------------------------------------------------------------------
/src/state/user.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2 | import User from "../types/Users";
3 | import AuthStorage from "../utils/authStorage";
4 |
5 | interface UserState {
6 | user: User | null | undefined;
7 | token: string | null | undefined;
8 | }
9 |
10 | const authStorage = new AuthStorage();
11 |
12 | const userSlice = createSlice({
13 | name: "user",
14 | initialState: {
15 | user: null,
16 | token: null,
17 | } as UserState,
18 | reducers: {
19 | setUser: (
20 | state,
21 | { payload: { user, token } }: PayloadAction
22 | ) => {
23 | state.user = user;
24 | state.token = token;
25 | //authStorage.setUser({user, token})
26 | },
27 | saveUser: (
28 | state,
29 | { payload: { user, token } }: PayloadAction
30 | ) => {
31 | authStorage.setUser({user, token})
32 | },
33 | removeUser: () => {
34 | authStorage.removeUser()
35 | },
36 | },
37 | });
38 |
39 | export const { setUser, saveUser, removeUser } = userSlice.actions;
40 | export default userSlice.reducer;
41 |
--------------------------------------------------------------------------------
/src/types/Feed.ts:
--------------------------------------------------------------------------------
1 | export default interface Feed {
2 | title: string,
3 | body: string,
4 | topic: string,
5 | url: string,
6 | image: string,
7 | created_at: string,
8 | }
--------------------------------------------------------------------------------
/src/types/Navigation.ts:
--------------------------------------------------------------------------------
1 | import { BioDetails } from "./../screens/BioScreen";
2 | import { screen_names } from "./../constants/ScreenNames";
3 | import Session from "./Session";
4 |
5 | //Enable type checking for route parameters
6 | export type RootStackParamList = {
7 | [screen_names.BIO]: { bioData: BioDetails };
8 | [screen_names.HOME]: undefined;
9 | [screen_names.HOMETABS]: undefined;
10 | [screen_names.SPEAKERS]: undefined;
11 | [screen_names.ABOUT]: undefined;
12 | [screen_names.FEEDBACK]: {sessionSlug?: string | undefined};
13 | [screen_names.SESSION_DETAILS] : {sessionData : Session}
14 | };
15 |
--------------------------------------------------------------------------------
/src/types/Pagination.ts:
--------------------------------------------------------------------------------
1 | export interface Pagination {
2 | count: number,
3 | per_page: number,
4 | current_page: number,
5 | next_page: string,
6 | has_more_pages: boolean,
7 | next_page_url: string | null,
8 | previous_page_url: string | null,
9 | }
--------------------------------------------------------------------------------
/src/types/ReactNativeDotenv.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@env' {
2 | export const GOOGLE_AUTH_CLIENT_ID: string;
3 | export const API_URL: string;
4 | export const EVENT_SLUG: string;
5 | }
--------------------------------------------------------------------------------
/src/types/Room.ts:
--------------------------------------------------------------------------------
1 | export default interface Room {
2 | id: number;
3 | title: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/types/Schedule.ts:
--------------------------------------------------------------------------------
1 | import Session from "./Session";
2 |
3 | export default interface Schedule {
4 | data: {
5 | [key: string]: Session[];
6 | };
7 | }
8 |
--------------------------------------------------------------------------------
/src/types/Session.ts:
--------------------------------------------------------------------------------
1 | import Room from "./Room";
2 | import Speaker from "./Speaker";
3 |
4 | export default interface Session {
5 | id: number;
6 | title: string;
7 | description: string;
8 | slug: string;
9 | session_format: string;
10 | session_level: string;
11 | session_image?: string | null;
12 | backgroundColor: string;
13 | borderColor: string;
14 | is_serviceSession: boolean;
15 | is_keynote: boolean;
16 | is_bookmarked: boolean;
17 | start_date_time: string;
18 | start_time: string;
19 | end_date_time: string;
20 | end_time: string;
21 | speakers: Speaker[];
22 | rooms: Room[];
23 | }
24 |
--------------------------------------------------------------------------------
/src/types/Speaker.ts:
--------------------------------------------------------------------------------
1 | export default interface Speaker {
2 | name: string;
3 | tagline?: string | null;
4 | biography?: string | null;
5 | avatar?: string;
6 | twitter?: string | null;
7 | facebook?: string | null;
8 | linkedin?: string | null;
9 | instagram?: string | null;
10 | blog?: string | null;
11 | company_website?: string | null;
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/Users.ts:
--------------------------------------------------------------------------------
1 | export default interface User {
2 | name: string;
3 | email: string;
4 | gender: string | null;
5 | avatar: string | null;
6 | created_at: string;
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/authStorage.ts:
--------------------------------------------------------------------------------
1 | import AsyncStorage from "@react-native-async-storage/async-storage";
2 | import User from "../types/Users";
3 |
4 | interface UserState {
5 | user: User | null | undefined;
6 | token: string | null | undefined;
7 | }
8 |
9 | class AuthStorage {
10 | namespace: string;
11 |
12 | constructor(namespace = 'auth') {
13 | this.namespace = namespace;
14 | }
15 |
16 | async getUser() {
17 | // Get user from storage.
18 | const savedUser = await AsyncStorage.getItem(`${this.namespace}:userstate`);
19 | return savedUser ? JSON.parse(savedUser): { user: null, token: null };
20 | }
21 |
22 | async setUser(newUser: UserState) {
23 | // Add user to storage.
24 | await AsyncStorage.setItem(
25 | `${this.namespace}:userstate`,
26 | JSON.stringify(newUser),
27 | )
28 | }
29 |
30 | async removeUser() {
31 | // Remove User object from storage.
32 | await AsyncStorage.removeItem(`${this.namespace}:userstate`);
33 | }
34 | }
35 |
36 | export default AuthStorage
--------------------------------------------------------------------------------
/src/utils/calculations.ts:
--------------------------------------------------------------------------------
1 | // example
2 |
--------------------------------------------------------------------------------
/src/utils/formatTime.ts:
--------------------------------------------------------------------------------
1 | const units = [
2 | { label: 'year', seconds: 31536000 },
3 | { label: 'month', seconds: 2592000 },
4 | { label: 'week', seconds: 604800 },
5 | { label: 'day', seconds: 86400 },
6 | { label: 'hour', seconds: 3600 },
7 | { label: 'minute', seconds: 60 },
8 | { label: 'second', seconds: 1 }
9 | ];
10 |
11 | const formatDate = (date: string | number | Date) => {
12 | const _date = new Date(date);
13 | const time = Math.floor(
14 | (new Date().valueOf() - new Date(_date).valueOf()) / 1000
15 | );
16 | const { interval, unit } = calculateTimeDifference(time);
17 | const suffix = interval === 1 ? '' : 's';
18 | return `${interval} ${unit}${suffix} ago`;
19 | };
20 |
21 | const calculateTimeDifference = (time: number) => {
22 | for (let { label, seconds } of units) {
23 | const interval = Math.floor(time / seconds);
24 | if (interval >= 1) {
25 | return {
26 | interval: interval,
27 | unit: label
28 | };
29 | }
30 | }
31 | return {
32 | interval: 0,
33 | unit: ''
34 | };
35 | };
36 |
37 | export { formatDate };
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "jsx": "react",
5 | "strict": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------