Merge 940d361a95
into c93e27bec0
|
@ -0,0 +1,68 @@
|
|||
# If setting a specific version, wrap it with single quotes like '12.0'
|
||||
# to pass it as string because GitHub trimmes trailing .0 from numbers
|
||||
# due to https://github.com/actions/runner/issues/849
|
||||
|
||||
name: build-and-ship-android
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
jobs:
|
||||
build-and-ship-android:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BUNDLE_GEMFILE: ${{ github.workspace }}/apps/skolplattformen-app-new/Gemfile
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# changed this from GITHUB_PERSONAL_ACCESS_TOKEN_PATH to GITHUB_TOKEN
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
submodules: true
|
||||
- name: Set up our JDK environment
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 11
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.2.2'
|
||||
bundler-cache: true
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18.13'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: ${{ github.workspace }}/apps/skolplattformen-app-new/package-lock.json
|
||||
- name: set npm version
|
||||
run: npm install -g npm@9.6.0 && echo "secrets.GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} secrets.GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} workspace= ${{ github.workspace }} secrets.MAPS_API_KEY=${{secrets.MAPS_API_KEY}}"
|
||||
- run: cd apps/skolplattformen-app-new/android && bundle install
|
||||
- name: install node modules
|
||||
run: cd apps/skolplattformen-app-new && npm ci
|
||||
- name: Setup local.properties
|
||||
run: echo "MAPS_API_KEY=${{ env.MAPS_API_KEY }}" > ${{ github.workspace }}/apps/skolplattformen-app-new/android/local.properties
|
||||
env:
|
||||
MAPS_API_KEY: ${{ secrets.MAPS_API_KEY }}
|
||||
- name: Setup key.json
|
||||
run: echo "${KEY_JSON}" > ${{ github.workspace }}/apps/skolplattformen-app-new/android/key.json
|
||||
env:
|
||||
KEY_JSON: ${{ secrets.KEY_JSON }}
|
||||
- run: cd apps/skolplattformen-app-new/android && bundle exec fastlane android beta
|
||||
env:
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
ALIAS_KEY_PASSWORD: ${{ secrets.ALIAS_KEY_PASSWORD }}
|
||||
- name: 'Store artifact native_debug_symbols.zip'
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: native_debug_symbols.zip
|
||||
path: ${{ github.workspace }}/apps/skolplattformen-app-new/android/native_debug_symbols.zip
|
||||
retention-days: 5
|
||||
- name: 'Store build mappings for difficult crashlytics traces'
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
name: mapping.txt
|
||||
path: ${{ github.workspace }}/apps/skolplattformen-app-new/android/app/build/outputs/mapping/release/mapping.txt
|
||||
retention-days: 500
|
|
@ -0,0 +1,51 @@
|
|||
# If setting a specific version, wrap it with single quotes like '12.0'
|
||||
# to pass it as string because GitHub trimmes trailing .0 from numbers
|
||||
# due to https://github.com/actions/runner/issues/849
|
||||
|
||||
name: build-and-ship-ios
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
jobs:
|
||||
build-and-ship-ios:
|
||||
runs-on: macos-13
|
||||
env:
|
||||
BUNDLE_GEMFILE: ${{ github.workspace }}/apps/skolplattformen-app-new/Gemfile
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
# changed this from GITHUB_PERSONAL_ACCESS_TOKEN_PATH to GITHUB_TOKEN
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
submodules: true
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '14.3.1'
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.2.2'
|
||||
bundler-cache: true
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18.13'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: ${{ github.workspace }}/apps/skolplattformen-app-new/package-lock.json
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ github.workspace }}/apps/skolplattformen-app-new/ios/Pods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-
|
||||
- name: set npm version
|
||||
run: npm install -g npm@9.6.0
|
||||
- name: install node modules
|
||||
run: cd apps/skolplattformen-app-new && npm ci
|
||||
- run: cd apps/skolplattformen-app-new/ios && bundle install
|
||||
- run: cd apps/skolplattformen-app-new/ios && npx pod-install
|
||||
- run: cd apps/skolplattformen-app-new/ios && bundle exec pod update hermes-engine --no-repo-update
|
||||
- run: cd apps/skolplattformen-app-new/ios && export && bundle exec fastlane ios beta version:patch
|
||||
env:
|
||||
ASCAPI_KEY_CONTENT: ${{ secrets.ASCAPI_KEY_CONTENT }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
|
||||
ENVFILE: ${{env.ENVFILE}}
|
|
@ -0,0 +1,39 @@
|
|||
name: Run tests on PR - new
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
test:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18.17.0'
|
||||
|
||||
- name: Setup timezone
|
||||
uses: zcong1993/setup-timezone@master
|
||||
with:
|
||||
timezone: Europe/Stockholm
|
||||
|
||||
- name: set npm version
|
||||
run: npm install -g npm@10.2.0
|
||||
|
||||
- name: install node modules
|
||||
run: cd apps/skolplattformen-app-new && npm ci
|
||||
|
||||
- name: Run tests
|
||||
run: cd apps/skolplattformen-app-new && npm test
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Check linting
|
||||
run: cd apps/skolplattformen-app-new && npm run lint
|
||||
env:
|
||||
CI: true
|
|
@ -22,6 +22,7 @@ The respective README files there contain more detailed descriptions.
|
|||
* [Architecture](#architecture)
|
||||
* [Apps](#apps)
|
||||
* [skolplattformen](#skolplattformen)
|
||||
* [skolplattformen dependency upgrade](#skolplattformen-dependency-upgrade)
|
||||
* [website](#website)
|
||||
* [Libs](#embedded-api)
|
||||
* [api](#api)
|
||||
|
@ -58,6 +59,9 @@ We're starting small, with more features being added over time.
|
|||
|
||||
For more information, check out the [source code](apps/skolplattformen-app).
|
||||
|
||||
#### skolplattformen dependency upgrade
|
||||
A dependency upgrade is currently in the works. To navigate directly to the related readme, click [here](/apps/skolplattformen-app-new/README-NEW.md).
|
||||
|
||||
#### website
|
||||
|
||||
The code for the website at https://skolplattformen.org/. It's built using Next.js.
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
BUNDLE_PATH: "vendor/bundle"
|
||||
BUNDLE_FORCE_RUBY_PLATFORM: 1
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: '@react-native',
|
||||
rules: {
|
||||
semi: ['warn', 'never'],
|
||||
},
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
ios/.xcode.env.local
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
build/
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
.cxx/
|
||||
!debug.keystore
|
||||
release/
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/
|
||||
|
||||
**/fastlane/report.xml
|
||||
**/fastlane/Preview.html
|
||||
**/fastlane/screenshots
|
||||
**/fastlane/test_output
|
||||
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
|
||||
# Ruby / CocoaPods
|
||||
/ios/Pods/
|
||||
/vendor/bundle/
|
||||
|
||||
# Temporary files created by Metro to check the health of the file watcher
|
||||
.metro-health-check*
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
|
||||
libraries.json
|
||||
|
||||
keys.json
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1,111 @@
|
|||
import * as eva from '@eva-design/eva'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import { ApiProvider, Reporter } from './libs/hooks/src'
|
||||
import { ApplicationProvider, IconRegistry, Text } from '@ui-kitten/components'
|
||||
import { EvaIconsPack } from '@ui-kitten/eva-icons'
|
||||
import React from 'react'
|
||||
import { StatusBar, useColorScheme, View } from 'react-native'
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
||||
import { AppNavigator } from './components/navigation.component'
|
||||
import { FeatureProvider } from './context/feature/featureContext'
|
||||
import { LanguageProvider } from './context/language/languageContext'
|
||||
import { SchoolPlatformProvider } from './context/schoolPlatform/schoolPlatformContext'
|
||||
import { schoolPlatforms } from './data/schoolPlatforms'
|
||||
import { default as customMapping } from './design/mapping.json'
|
||||
import { darkTheme, lightTheme } from './design/themes'
|
||||
import useSettingsStorage from './hooks/useSettingsStorage'
|
||||
import { translations } from './utils/translation'
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||
|
||||
const reporter: Reporter | undefined = __DEV__
|
||||
? {
|
||||
log: (message: string) => console.log(message),
|
||||
error: (error: Error, label?: string) => console.log(label, error),
|
||||
}
|
||||
: undefined
|
||||
|
||||
if (__DEV__) {
|
||||
const DevMenu = require('react-native-dev-menu')
|
||||
DevMenu.addItem('Clear AsyncStorage from all contents', () =>
|
||||
AsyncStorage.clear().then(() => logAsyncStorage())
|
||||
)
|
||||
DevMenu.addItem('Log AsyncStorage contents', () => logAsyncStorage())
|
||||
}
|
||||
|
||||
const safeJsonParse = (maybeJson: string) => {
|
||||
if (maybeJson) {
|
||||
try {
|
||||
return JSON.parse(maybeJson)
|
||||
} catch (error) {
|
||||
return maybeJson
|
||||
}
|
||||
}
|
||||
return 'null'
|
||||
}
|
||||
|
||||
const logAsyncStorage = async () => {
|
||||
const allKeys = await AsyncStorage.getAllKeys()
|
||||
const keysAndValues = await AsyncStorage.multiGet(allKeys)
|
||||
console.log('*** AsyncStorage contents:')
|
||||
keysAndValues.forEach((keyAndValue) => {
|
||||
console.log(
|
||||
keyAndValue[0],
|
||||
'=>',
|
||||
keyAndValue[1] ? safeJsonParse(keyAndValue[1]) : 'null'
|
||||
)
|
||||
})
|
||||
console.log('***')
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const [usingSystemTheme] = useSettingsStorage('usingSystemTheme')
|
||||
const [currentSchoolPlatform] = useSettingsStorage('currentSchoolPlatform')
|
||||
const [theme] = useSettingsStorage('theme')
|
||||
const systemTheme = useColorScheme()
|
||||
const colorScheme = usingSystemTheme ? systemTheme : theme
|
||||
|
||||
const platform = schoolPlatforms.find((pf) => pf.id === currentSchoolPlatform)
|
||||
|
||||
if (!platform) {
|
||||
return (
|
||||
<View>
|
||||
<Text>ERROR</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<FeatureProvider features={platform.features}>
|
||||
<SchoolPlatformProvider>
|
||||
<ApiProvider
|
||||
api={platform.api}
|
||||
storage={AsyncStorage}
|
||||
reporter={reporter}
|
||||
>
|
||||
<SafeAreaProvider>
|
||||
<StatusBar
|
||||
backgroundColor={colorScheme === 'dark' ? '#2E3137' : '#FFF'}
|
||||
barStyle={
|
||||
colorScheme === 'dark' ? 'light-content' : 'dark-content'
|
||||
}
|
||||
translucent
|
||||
/>
|
||||
<IconRegistry icons={EvaIconsPack} />
|
||||
<ApplicationProvider
|
||||
{...eva}
|
||||
// @ts-expect-error Unknown error
|
||||
customMapping={customMapping}
|
||||
theme={colorScheme === 'dark' ? darkTheme : lightTheme}
|
||||
>
|
||||
<LanguageProvider cache={true} data={translations}>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<AppNavigator />
|
||||
</GestureHandlerRootView>
|
||||
</LanguageProvider>
|
||||
</ApplicationProvider>
|
||||
</SafeAreaProvider>
|
||||
</ApiProvider>
|
||||
</SchoolPlatformProvider>
|
||||
</FeatureProvider>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
||||
ruby ">= 3.2.2"
|
||||
|
||||
gem 'bundler'
|
||||
gem 'cocoapods', '~> 1.12'
|
||||
gem 'fastlane'
|
||||
gem 'dotenv'
|
|
@ -0,0 +1,297 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
activesupport (7.1.1)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
mutex_m
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.5)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.841.0)
|
||||
aws-sdk-core (3.185.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.72.0)
|
||||
aws-sdk-core (~> 3, >= 3.184.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.136.0)
|
||||
aws-sdk-core (~> 3, >= 3.181.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.6)
|
||||
aws-sigv4 (1.6.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.1.1)
|
||||
bigdecimal (3.1.4)
|
||||
claide (1.1.0)
|
||||
cocoapods (1.13.0)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.13.0)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.6.0, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.6.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (>= 2.3.0, < 3.0)
|
||||
xcodeproj (>= 1.23.0, < 2.0)
|
||||
cocoapods-core (1.13.0)
|
||||
activesupport (>= 5.0, < 8)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (1.6.3)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.1)
|
||||
cocoapods-trunk (1.6.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
concurrent-ruby (1.2.2)
|
||||
connection_pool (2.4.1)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.8.1)
|
||||
drb (2.1.1)
|
||||
ruby2_keywords
|
||||
emoji_regex (3.2.3)
|
||||
escape (0.0.4)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
excon (0.104.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.7)
|
||||
fastlane (2.216.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
ffi (1.16.3)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.51.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.19.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.44.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
jwt (2.7.1)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.20.0)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.3.0)
|
||||
mutex_m (0.1.2)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.1)
|
||||
netrc (0.11.0)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.7.0)
|
||||
public_suffix (4.0.7)
|
||||
rake (13.0.6)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.6)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (2.5.1)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.18.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.5.0)
|
||||
webrick (1.8.1)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.23.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
bundler
|
||||
cocoapods (~> 1.12)
|
||||
dotenv
|
||||
fastlane
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.2.2p53
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.21
|
|
@ -0,0 +1,102 @@
|
|||
# Skolplattformen React Native Project
|
||||
|
||||
Welcome to this new [**React Native**](https://reactnative.dev) project, initiated using [`@react-native-community/cli`](https://github.com/react-native-community/cli).
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
> **Important**: Make sure you have gone through the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) guide up to the "Creating a new application" step before moving forward.
|
||||
|
||||
## Initial Setup
|
||||
|
||||
### Required Software
|
||||
|
||||
1. [Git](https://git-scm.com/)
|
||||
2. [Node](https://nodejs.org/en/)
|
||||
3. [NPM](https://docs.npmjs.com/cli/v8/commands/npm-install)
|
||||
|
||||
### Minimum Node Version
|
||||
Ensure you are using Node version 16 or higher.
|
||||
|
||||
### Cloning the Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Home-Biz-LLS/skolplattformen-react-native
|
||||
```
|
||||
|
||||
### Installing Dependencies
|
||||
|
||||
```bash
|
||||
cd apps/skolplattformen-app-new/ && npm i
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running the App
|
||||
|
||||
### For iOS
|
||||
|
||||
**Note**: Running the iOS app requires a Mac with native support. Windows/Linux are currently not supported.
|
||||
|
||||
#### Essential Guides and Tools
|
||||
|
||||
* [Mac OS Setup Guide](https://reactnative.dev/docs/environment-setup)
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
1. **Install Xcode**: Ensure Xcode is installed on your system.
|
||||
|
||||
2. **Install CocoaPods**: If not already installed, you can do this easily using Homebrew.
|
||||
- [Homebrew Install Guide for CocoaPods](https://formulae.brew.sh/formula/cocoapods)
|
||||
- [Official CocoaPods Guide](https://guides.cocoapods.org/using/getting-started.html)
|
||||
|
||||
3. **Install Pods**
|
||||
```bash
|
||||
cd apps/skolplattformen-app-new/ios && pod install
|
||||
```
|
||||
|
||||
#### Running the iOS App
|
||||
|
||||
|
||||
* Option 1: Using Metro Bundler
|
||||
|
||||
![metro bundler example](/apps/skolplattformen-app-new/docs/assets/MetroBundlerExample.png)
|
||||
```bash
|
||||
cd apps/skolplattformen-app-new && npm run start
|
||||
```
|
||||
then type
|
||||
```bash
|
||||
i
|
||||
```
|
||||
|
||||
* Option 2: Running Directly
|
||||
```bash
|
||||
npm run ios
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### For Android
|
||||
|
||||
**Note**: Choose an appropriate guide based on your operating system:
|
||||
|
||||
* [Mac OS](/apps/skolplattformen-app-new/docs/android_mac.md)
|
||||
* [Windows](/apps/skolplattformen-app-new//docs/android_windows.md)
|
||||
* [Linux](/apps/skolplattformen-app-new//docs/android_linux.md)
|
||||
|
||||
#### Running the Android App
|
||||
|
||||
* Option 1: Using Metro Bundler
|
||||
|
||||
![metro bundler example](/apps/skolplattformen-app-new/docs/assets/MetroBundlerExample.png)
|
||||
```bash
|
||||
cd apps/skolplattformen-app-new && npm run start
|
||||
```
|
||||
then type
|
||||
```bash
|
||||
a
|
||||
```
|
||||
|
||||
* Option 2: Running Directly
|
||||
```bash
|
||||
npm run android
|
||||
```
|
|
@ -0,0 +1,84 @@
|
|||
This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli).
|
||||
|
||||
# Getting Started
|
||||
|
||||
>**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding.
|
||||
|
||||
## Getting started with Development
|
||||
|
||||
### Please use node version 16 or higher
|
||||
|
||||
To clone and build the project, you first need to install [git](https://git-scm.com/), [node](https://nodejs.org/en/) and [npm](https://docs.npmjs.com/cli/v8/commands/npm-install).
|
||||
|
||||
Clone the repo with
|
||||
```bash
|
||||
$ git clone https://github.com/Home-Biz-LLS/skolplattformen-react-native
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
```bash
|
||||
cd apps/skolplattformen-app-new/ && npm i
|
||||
```
|
||||
|
||||
### iOS
|
||||
|
||||
If you wanna run the iOS app, you need to setup a couple of things first, we have a guide that will assist you in getting started with the iOS app. A Mac is required to build projects with native code for iOS so we do not have support for Linux / Windows.
|
||||
|
||||
* [Mac OS](https://reactnative.dev/docs/environment-setup)
|
||||
|
||||
#### Step 1
|
||||
Make sure you have **Xcode** installed
|
||||
|
||||
|
||||
#### Step 2
|
||||
Make sure **CocoaPods** is installed (you can do it easily with homebrew)
|
||||
|
||||
* [CocoaPods homebrew](https://formulae.brew.sh/formula/cocoapods)
|
||||
* [CocoaPods](https://guides.cocoapods.org/using/getting-started.html)
|
||||
|
||||
|
||||
#### Step 3
|
||||
```bash
|
||||
cd apps/skolplattformen-app-new/ios && pod install
|
||||
```
|
||||
|
||||
If you already setup everything, go into the
|
||||
|
||||
"skolplattformen-app-new" directory
|
||||
|
||||
Start the metro
|
||||
|
||||
```
|
||||
npm run start
|
||||
```
|
||||
|
||||
then
|
||||
|
||||
```
|
||||
i
|
||||
```
|
||||
to Start iOS app
|
||||
|
||||
OR
|
||||
|
||||
Start the iOS app directly
|
||||
```
|
||||
npm run ios
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Android
|
||||
|
||||
If you wanna run the Android app, you need to setup a couple of things first, we have created three different guides depending on your operating system.
|
||||
|
||||
* [Mac OS](/docs/android_mac.md)
|
||||
* [Windows](/docs/android_windows.md)
|
||||
* [Linux](/docs/android_linux.md)
|
||||
|
||||
If you already setup everything, you just need to run the following command in the project root:
|
||||
|
||||
```
|
||||
yarn run android
|
||||
```
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export {
|
||||
default,
|
||||
useAsyncStorage,
|
||||
} from '@react-native-async-storage/async-storage/jest/async-storage-mock'
|
|
@ -0,0 +1,81 @@
|
|||
import { CookieJar, Cookie as TCookie } from 'tough-cookie'
|
||||
|
||||
export interface Cookie {
|
||||
name: string
|
||||
value: string
|
||||
path?: string
|
||||
domain?: string
|
||||
version?: string
|
||||
expires?: string
|
||||
secure?: boolean
|
||||
httpOnly?: boolean
|
||||
}
|
||||
|
||||
export interface Cookies {
|
||||
[key: string]: Cookie
|
||||
}
|
||||
|
||||
export interface CookieManagerStatic {
|
||||
set(url: string, cookie: Cookie, useWebKit?: boolean): Promise<boolean>
|
||||
setFromResponse(url: string, cookie: string): Promise<boolean>
|
||||
|
||||
get(url: string, useWebKit?: boolean): Promise<Cookies>
|
||||
|
||||
clearAll(useWebKit?: boolean): Promise<boolean>
|
||||
}
|
||||
|
||||
const convertTtoC = (cookie: string | TCookie): Cookie => {
|
||||
if (typeof cookie === 'string') {
|
||||
return convertTtoC(TCookie.parse(cookie) as TCookie)
|
||||
}
|
||||
return {
|
||||
name: cookie.key,
|
||||
value: cookie.value,
|
||||
domain: cookie.domain || undefined,
|
||||
expires:
|
||||
cookie.expires === 'Infinity' ? undefined : cookie.expires.toUTCString(),
|
||||
httpOnly: cookie.httpOnly || undefined,
|
||||
path: cookie.path || undefined,
|
||||
secure: cookie.secure,
|
||||
}
|
||||
}
|
||||
const convertCtoT = (cookie: Cookie): TCookie =>
|
||||
new TCookie({
|
||||
key: cookie.name,
|
||||
value: cookie.value,
|
||||
domain: cookie.domain,
|
||||
expires: cookie.expires ? new Date(cookie.expires) : undefined,
|
||||
httpOnly: cookie.httpOnly || false,
|
||||
path: cookie.path,
|
||||
secure: cookie.secure || false,
|
||||
})
|
||||
const convertCookies = (cookies: TCookie[]): Cookies =>
|
||||
cookies.reduce(
|
||||
(map, cookie) => ({
|
||||
...map,
|
||||
[cookie.key]: convertTtoC(cookie),
|
||||
}),
|
||||
{} as Cookies
|
||||
)
|
||||
|
||||
const jar = new CookieJar()
|
||||
const CookieManager: CookieManagerStatic = {
|
||||
clearAll: async () => {
|
||||
await jar.removeAllCookies()
|
||||
return true
|
||||
},
|
||||
get: async (url) => {
|
||||
const cookies = await jar.getCookies(url)
|
||||
return convertCookies(cookies)
|
||||
},
|
||||
set: async (url, cookie) => {
|
||||
await jar.setCookie(convertCtoT(cookie), url)
|
||||
return true
|
||||
},
|
||||
setFromResponse: async (url, cookie) => {
|
||||
await jar.setCookie(cookie, url)
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
export default CookieManager
|
|
@ -0,0 +1,40 @@
|
|||
const getLocales = () => [
|
||||
{ countryCode: 'EN', languageTag: 'en-US', languageCode: 'en', isRTL: false },
|
||||
{ countryCode: 'SE', languageTag: 'sv-SE', languageCode: 'sv', isRTL: false },
|
||||
]
|
||||
|
||||
const findBestAvailableLanguage = jest.fn(() => ({
|
||||
languageTag: 'sv',
|
||||
isRTL: false,
|
||||
}))
|
||||
|
||||
const getNumberFormatSettings = () => ({
|
||||
decimalSeparator: '.',
|
||||
groupingSeparator: ',',
|
||||
})
|
||||
|
||||
const getCalendar = () => 'gregorian'
|
||||
const getCountry = () => 'SE'
|
||||
const getCurrencies = () => ['USD', 'SEK']
|
||||
const getTemperatureUnit = () => 'celsius'
|
||||
const getTimeZone = () => 'Europe/Stockholm'
|
||||
const uses24HourClock = () => true
|
||||
const usesMetricSystem = () => true
|
||||
|
||||
const addEventListener = jest.fn()
|
||||
const removeEventListener = jest.fn()
|
||||
|
||||
export {
|
||||
findBestAvailableLanguage,
|
||||
getLocales,
|
||||
getNumberFormatSettings,
|
||||
getCalendar,
|
||||
getCountry,
|
||||
getCurrencies,
|
||||
getTemperatureUnit,
|
||||
getTimeZone,
|
||||
uses24HourClock,
|
||||
usesMetricSystem,
|
||||
addEventListener,
|
||||
removeEventListener,
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// __mocks__/react-native-safe-area-context.js
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
|
||||
const inset = {
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
}
|
||||
|
||||
export const SafeAreaProvider = ({ children }) => children
|
||||
|
||||
export const SafeAreaConsumer = ({ children }) => children(inset)
|
||||
|
||||
export const SafeAreaView = ({ children }) => (
|
||||
<View style={inset}>{children}</View>
|
||||
)
|
||||
|
||||
export const useSafeAreaInsets = () => inset
|
|
@ -0,0 +1,129 @@
|
|||
apply plugin: "com.android.application"
|
||||
apply plugin: "com.facebook.react"
|
||||
|
||||
/**
|
||||
* This is the configuration block to customize your React Native Android app.
|
||||
* By default you don't need to apply any configuration, just uncomment the lines you need.
|
||||
*/
|
||||
react {
|
||||
/* Folders */
|
||||
// The root of your project, i.e. where "package.json" lives. Default is '..'
|
||||
// root = file("../")
|
||||
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
|
||||
// reactNativeDir = file("../node_modules/react-native")
|
||||
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
|
||||
// codegenDir = file("../node_modules/@react-native/codegen")
|
||||
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
|
||||
// cliFile = file("../node_modules/react-native/cli.js")
|
||||
|
||||
/* Variants */
|
||||
// The list of variants to that are debuggable. For those we're going to
|
||||
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
|
||||
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
|
||||
// debuggableVariants = ["liteDebug", "prodDebug"]
|
||||
|
||||
/* Bundling */
|
||||
// A list containing the node command and its flags. Default is just 'node'.
|
||||
// nodeExecutableAndArgs = ["node"]
|
||||
//
|
||||
// The command to run when bundling. By default is 'bundle'
|
||||
// bundleCommand = "ram-bundle"
|
||||
//
|
||||
// The path to the CLI configuration file. Default is empty.
|
||||
// bundleConfig = file(../rn-cli.config.js)
|
||||
//
|
||||
// The name of the generated asset file containing your JS bundle
|
||||
// bundleAssetName = "MyApplication.android.bundle"
|
||||
//
|
||||
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
|
||||
// entryFile = file("../js/MyApplication.android.js")
|
||||
//
|
||||
// A list of extra flags to pass to the 'bundle' commands.
|
||||
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
|
||||
// extraPackagerArgs = []
|
||||
|
||||
/* Hermes Commands */
|
||||
// The hermes compiler command to run. By default it is 'hermesc'
|
||||
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
|
||||
//
|
||||
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
|
||||
// hermesFlags = ["-O", "-output-source-map"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore (JSC)
|
||||
*
|
||||
* For example, to use the international variant, you can use:
|
||||
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
|
||||
*
|
||||
* The international variant includes ICU i18n library and necessary data
|
||||
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
|
||||
* give correct results when using with locales other than en-US. Note that
|
||||
* this variant is about 6MiB larger per architecture than default.
|
||||
*/
|
||||
def jscFlavor = 'org.webkit:android-jsc:+'
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
namespace "com.oppna_skolplattformen_new.app"
|
||||
defaultConfig {
|
||||
applicationId "com.oppna_skolplattformen_new.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 4
|
||||
versionName "1.0"
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('debug.keystore')
|
||||
storePassword 'android'
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
}
|
||||
release {
|
||||
storeFile file('release.jks')
|
||||
storePassword 'leeandseb'
|
||||
keyAlias 'upload'
|
||||
keyPassword 'leeandseb'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
// Caution! In production, you need to generate your own keystore file.
|
||||
// see https://reactnative.dev/docs/signed-apk-android.
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// The version of react-native is set by the React Native Gradle Plugin
|
||||
implementation("com.facebook.react:react-android")
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.squareup.okhttp3', module:'okhttp'
|
||||
}
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
|
||||
if (hermesEnabled.toBoolean()) {
|
||||
implementation("com.facebook.react:hermes-android")
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
|
@ -0,0 +1,10 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="28"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" />
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.oppna_skolplattformen_new.app;
|
||||
|
||||
import android.content.Context;
|
||||
import com.facebook.flipper.android.AndroidFlipperClient;
|
||||
import com.facebook.flipper.android.utils.FlipperUtils;
|
||||
import com.facebook.flipper.core.FlipperClient;
|
||||
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
|
||||
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
|
||||
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
|
||||
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
|
||||
import com.facebook.react.ReactInstanceEventListener;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.network.NetworkingModule;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* Class responsible of loading Flipper inside your React Native application. This is the debug
|
||||
* flavor of it. Here you can add your own plugins and customize the Flipper setup.
|
||||
*/
|
||||
public class ReactNativeFlipper {
|
||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
if (FlipperUtils.shouldEnableFlipper(context)) {
|
||||
final FlipperClient client = AndroidFlipperClient.getInstance(context);
|
||||
|
||||
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
|
||||
client.addPlugin(new DatabasesFlipperPlugin(context));
|
||||
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
|
||||
client.addPlugin(CrashReporterPlugin.getInstance());
|
||||
|
||||
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
|
||||
NetworkingModule.setCustomClientBuilder(
|
||||
new NetworkingModule.CustomClientBuilder() {
|
||||
@Override
|
||||
public void apply(OkHttpClient.Builder builder) {
|
||||
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
|
||||
}
|
||||
});
|
||||
client.addPlugin(networkFlipperPlugin);
|
||||
client.start();
|
||||
|
||||
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
|
||||
// Hence we run if after all native modules have been initialized
|
||||
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
|
||||
if (reactContext == null) {
|
||||
reactInstanceManager.addReactInstanceEventListener(
|
||||
new ReactInstanceEventListener() {
|
||||
@Override
|
||||
public void onReactContextInitialized(ReactContext reactContext) {
|
||||
reactInstanceManager.removeReactInstanceEventListener(this);
|
||||
reactContext.runOnNativeModulesQueueThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
client.addPlugin(new FrescoFlipperPlugin());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
client.addPlugin(new FrescoFlipperPlugin());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:allowBackup="false"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,32 @@
|
|||
package com.oppna_skolplattformen_new.app;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
|
||||
import com.facebook.react.defaults.DefaultReactActivityDelegate;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
||||
* rendering of the component.
|
||||
*/
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "app";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
|
||||
* DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
|
||||
* (aka React 18) with two boolean flags.
|
||||
*/
|
||||
@Override
|
||||
protected ReactActivityDelegate createReactActivityDelegate() {
|
||||
return new DefaultReactActivityDelegate(
|
||||
this,
|
||||
getMainComponentName(),
|
||||
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
|
||||
DefaultNewArchitectureEntryPoint.getFabricEnabled());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package com.oppna_skolplattformen_new.app;
|
||||
|
||||
import android.app.Application;
|
||||
import com.facebook.react.PackageList;
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
private final ReactNativeHost mReactNativeHost =
|
||||
new DefaultReactNativeHost(this) {
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
List<ReactPackage> packages = new PackageList(this).getPackages();
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// packages.add(new MyReactNativePackage());
|
||||
return packages;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getJSMainModuleName() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNewArchEnabled() {
|
||||
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean isHermesEnabled() {
|
||||
return BuildConfig.IS_HERMES_ENABLED;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
DefaultNewArchitectureEntryPoint.load();
|
||||
}
|
||||
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
|
||||
android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
|
||||
android:insetTop="@dimen/abc_edit_text_inset_top_material"
|
||||
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
|
||||
|
||||
<selector>
|
||||
<!--
|
||||
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
|
||||
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
|
||||
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
|
||||
|
||||
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
||||
|
||||
For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.
|
||||
-->
|
||||
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
||||
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
|
||||
</selector>
|
||||
|
||||
</inset>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">Öppna Skolplattformen NEW</string>
|
||||
</resources>
|
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:textColor">#000000</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.oppna_skolplattformen_new.app;
|
||||
|
||||
import android.content.Context;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
||||
/**
|
||||
* Class responsible of loading Flipper inside your React Native application. This is the release
|
||||
* flavor of it so it's empty as we don't want to load Flipper.
|
||||
*/
|
||||
public class ReactNativeFlipper {
|
||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
// Do nothing as we don't want to initialize Flipper on Release.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "33.0.0"
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 33
|
||||
targetSdkVersion = 33
|
||||
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
ndkVersion = "23.1.7779620"
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle")
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
json_key_file("key.json")
|
||||
package_name("com.oppna_skolplattformen_new.app")
|
|
@ -0,0 +1,109 @@
|
|||
# Filename: android/fastlane/Fastfile
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Runs all the tests"
|
||||
lane :test do
|
||||
gradle(task: "test")
|
||||
end
|
||||
|
||||
desc "Submit a new Beta Build to Play Store"
|
||||
lane :beta do |options|
|
||||
|
||||
store_password = ENV['SIGNING_STORE_PASSWORD'] || prompt(text: "Signing Store Password: ", secure_text: true)
|
||||
key_password = ENV['ALIAS_KEY_PASSWORD'] || prompt(text: "Alias Key Password: ", secure_text: true)
|
||||
|
||||
# Fetch version_code from play store and bump it. Annoyingly, we always need
|
||||
# to increment it, even if our version name changes.
|
||||
internalVersionCode = google_play_track_version_codes(track: 'internal').max
|
||||
ENV['VERSION_CODE'] = (internalVersionCode + 1).to_s
|
||||
|
||||
versionNameOverride = nil
|
||||
# versionNameOverride = "1.9.0"
|
||||
if versionNameOverride.nil?
|
||||
releaseNameSemVerArr = google_play_track_release_names(track: 'internal').max.split('.')
|
||||
releaseNameSemVerArr[2] = (releaseNameSemVerArr.last.to_i + 1).to_s
|
||||
ENV['VERSION_NAME'] = releaseNameSemVerArr.join('.')
|
||||
ENV['GITTAGNAME'] = ENV['VERSION_NAME'].gsub(/\s+/, '').match(/\((.*?)\)/)[1] + '.' + ENV['VERSION_CODE']
|
||||
ENV['SUPPLY_VERSION_NAME'] = ENV['VERSION_NAME']
|
||||
versionFile = File.join(Dir.pwd, '..', 'version', 'version.properties').to_s
|
||||
commandargs = "-n \"VERSION=#{ENV['VERSION_NAME']}\" > #{versionFile}".to_s
|
||||
puts "echo #{commandargs}"
|
||||
system("echo", commandargs)
|
||||
else
|
||||
ENV['VERSION_NAME'] = versionNameOverride
|
||||
end
|
||||
puts "Compiling #{ENV['VERSION_NAME']} (#{ENV['VERSION_CODE']}) "
|
||||
|
||||
# Dir.pwd when running through Fastlane is app/android/fastlane
|
||||
releaseFilePath = File.join(Dir.pwd, '..', 'app', "release.jks")
|
||||
mappingFilePath = File.join(
|
||||
Dir.pwd,
|
||||
"..",
|
||||
"app",
|
||||
"build",
|
||||
"outputs",
|
||||
"mapping",
|
||||
"release",
|
||||
"mapping.txt"
|
||||
)
|
||||
puts "Hello there - #{ENV['VERSION_CODE']}"
|
||||
|
||||
gradle(task: 'clean')
|
||||
gradle(
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
print_command: false,
|
||||
properties: {
|
||||
"android.injected.signing.store.file" => releaseFilePath,
|
||||
"android.injected.signing.store.password" => store_password,
|
||||
"android.injected.signing.key.alias" => "upload",
|
||||
"android.injected.signing.key.password" => key_password,
|
||||
"android.injected.version.code" => ENV['VERSION_CODE'],
|
||||
"android.injected.version.name" => ENV['VERSION_NAME'],
|
||||
}
|
||||
)
|
||||
|
||||
symbolsFilePath = File.join(
|
||||
Dir.pwd,
|
||||
"..",
|
||||
"native_debug_symbols.zip"
|
||||
)
|
||||
symbolsFolderPath = File.join(
|
||||
Dir.pwd,
|
||||
"..",
|
||||
"app",
|
||||
"build",
|
||||
"intermediates",
|
||||
"merged_native_libs",
|
||||
"release",
|
||||
"out",
|
||||
"lib"
|
||||
)
|
||||
system("cd #{symbolsFolderPath} && zip -r #{symbolsFilePath} .")
|
||||
upload_to_play_store(
|
||||
track: 'internal',
|
||||
release_status: 'draft',
|
||||
version_code: ENV['VERSION_CODE'],
|
||||
version_name: ENV['VERSION_NAME'],
|
||||
version_codes_to_retain: [],
|
||||
mapping_paths: [mappingFilePath, symbolsFilePath]
|
||||
)
|
||||
|
||||
system('git config user.email "leesheppard2404@gmail.com"')
|
||||
system('git config user.name "Github Actions Android Pipeline"')
|
||||
|
||||
|
||||
add_git_tag(
|
||||
grouping: "builds",
|
||||
includes_lane: true,
|
||||
# prefix: "v#{ENV['VERSION_NAME']}-",
|
||||
# build_number: ENV['VERSION_CODE'],
|
||||
tag: "v#{ENV['GITTAGNAME']}"
|
||||
)
|
||||
push_to_git_remote(
|
||||
tags: true
|
||||
)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
|
||||
# Version of flipper SDK to use with React Native
|
||||
FLIPPER_VERSION=0.182.0
|
||||
|
||||
# Use this property to specify which architecture you want to build.
|
||||
# You can also override it from the CLI using
|
||||
# ./gradlew <task> -PreactNativeArchitectures=x86_64
|
||||
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
|
||||
|
||||
# Use this property to enable support to the new architecture.
|
||||
# This will allow you to use TurboModules and the Fabric render in
|
||||
# your application. You should enable this flag either if you want
|
||||
# to write custom TurboModules/Fabric components OR use libraries that
|
||||
# are providing them.
|
||||
newArchEnabled=false
|
||||
|
||||
# Use this property to enable or disable the Hermes JS engine.
|
||||
# If set to false, you will be using JSC instead.
|
||||
hermesEnabled=true
|
6
apps/skolplattformen-app-new/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
|
@ -0,0 +1,244 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,92 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -0,0 +1,4 @@
|
|||
rootProject.name = 'app'
|
||||
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
|
||||
include ':app'
|
||||
includeBuild('../node_modules/@react-native/gradle-plugin')
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "app",
|
||||
"displayName": "app",
|
||||
"schema": "oppnaskolplattformen://"
|
||||
}
|
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 59 KiB |
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Lager_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 595.3 841.9" enable-background="new 0 0 595.3 841.9" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#479CBE" d="M241.9,423.6l13.2-83.2c-5.2,0-14.1,0-14.1,0c-6.6,0-15.1-3.7-17.6-10.5c-0.8-2.3-2.7-10.2,8.2-17.9
|
||||
c3.9-2.7,6.4-5.7,6.9-8c0.5-2.4-0.1-4.5-1.8-6.1c-2.4-2.3-7.1-3.6-13.1-3.6c-10.1,0-17.2,5.8-17.9,10c-0.5,3.1,1.9,5.6,4,7.2
|
||||
c6.3,4.7,7.8,11.5,3.9,17.9c-4,6.6-12.7,10.9-22,11c0,0-9.2,0-14.4,0c-1.2,8.1-20.8,132.3-22.3,142.1h78.1
|
||||
c0.7-4.4,4.3-27.9,9.2-58.9H241.9z"/>
|
||||
<path fill="#00A5C3" d="M346.5,267.6H267l-10.6,67.3l13.5,0c7.4,0,14.4-3.4,17.4-8.3c1-1.6,1.4-3,1.4-4.3c0-2.8-1.9-4.9-3.8-6.3
|
||||
c-5.2-3.9-6.3-8-6.3-10.9c0-0.6,0-1.1,0.1-1.6c1.1-7.1,10.7-14.8,23.4-14.8c7.6,0,13.4,1.8,16.9,5.1c3.1,2.9,4.3,7,3.4,11.3
|
||||
c-1.1,5.1-6.2,9.3-9.1,11.4c-7.7,5.4-6.7,10.1-6.2,11.5c1.6,4.2,7.7,6.9,12.4,6.9H340c0,0,0,0,0,0.1c28,0.2,43,13.1,38.3,43.1
|
||||
c-4.4,27.9-25.8,39.9-51.3,40.1l-10.1,64.4h14.9c62.9,0,114.3-40.4,124.4-104.2C468.7,299.2,418.5,267.6,346.5,267.6z"/>
|
||||
<path fill="#235971" d="M346.5,267.6H267l-10.6,67.3l13.5,0c7.4,0,14.4-3.4,17.4-8.3c1-1.6,1.4-3,1.4-4.3c0-2.8-1.9-4.9-3.8-6.3
|
||||
c-5.2-3.9-6.3-8-6.3-10.9c0-0.6,0-1.1,0.1-1.6c1.1-7.1,10.7-14.8,23.4-14.8c7.6,0,13.4,1.8,16.9,5.1c3.1,2.9,4.3,7,3.4,11.3
|
||||
c-1.1,5.1-6.2,9.3-9.1,11.4c-7.7,5.4-6.7,10.1-6.2,11.5c1.6,4.2,7.7,6.9,12.4,6.9H340c0,0,0,0,0,0.1c28,0.2,43,13.1,38.3,43.1
|
||||
c-4.4,27.9-25.8,39.9-51.3,40.1l-10.1,64.4h14.9c62.9,0,114.3-40.4,124.4-104.2C468.7,299.2,418.5,267.6,346.5,267.6z"/>
|
||||
<g>
|
||||
<path fill="#235971" d="M150.7,511.2h31.9c13.6,0,16.9,6.9,15.9,13.2c-0.8,5.1-4.3,8.9-10.3,11.4c7.6,2.9,10.6,7.4,9.5,14.5
|
||||
c-1.4,8.9-9.1,15.5-19.2,15.5h-36.3L150.7,511.2z M171.8,533.8c6.2,0,9.1-3.3,9.7-7.2c0.6-4.2-1.3-7.1-7.5-7.1h-5.5l-2.2,14.3
|
||||
H171.8z M168.4,557.4c6.4,0,10.1-2.6,11-7.9c0.7-4.6-1.9-7.3-8.1-7.3H165l-2.4,15.3H168.4z"/>
|
||||
<path fill="#235971" d="M242.4,566.2c-8.3,0.6-12.3-0.3-14.3-3.9c-4.4,2.7-9.3,4.1-14.5,4.1c-9.4,0-12.7-4.9-11.8-10.3
|
||||
c0.4-2.6,1.9-5.1,4.3-7.2c5.2-4.5,18-5.1,23-8.5c0.4-3.8-1.1-5.2-5.8-5.2c-5.5,0-10.1,1.8-18,7.2l1.9-12.4
|
||||
c6.8-4.9,13.4-7.2,21-7.2c9.7,0,18.3,4,16.7,14.6l-1.9,12c-0.7,4.2-0.5,5.5,4.2,5.6L242.4,566.2z M228,547.4
|
||||
c-4.4,2.8-12.6,2.3-13.5,8.1c-0.4,2.7,1.3,4.7,4,4.7c2.6,0,5.8-1.1,8.4-2.9c-0.2-1-0.1-2,0.2-3.9L228,547.4z"/>
|
||||
<path fill="#235971" d="M257.9,523.5h16.6l-0.9,5.5c5.3-4.5,9.3-6.2,14.5-6.2c9.3,0,13.6,5.7,12.1,15l-4.3,27.9h-16.6l3.6-23.1
|
||||
c0.7-4.2-0.6-6.2-3.8-6.2c-2.6,0-5,1.4-7.3,4.5l-3.8,24.7h-16.6L257.9,523.5z"/>
|
||||
<path fill="#235971" d="M313.1,511.2h16.6l-4.2,26.8l15.9-14.5h20.5l-20.4,18l16.4,24.2h-20.9l-12.6-19.5h-0.2l-3,19.5h-16.6
|
||||
L313.1,511.2z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#479CBE" d="M371.9,511.2H391l-8.4,54.5h-19.1L371.9,511.2z"/>
|
||||
<path fill="#479CBE" d="M400.3,511.2h27.3c21.1,0,27.2,15.3,25.2,28c-1.9,12.4-11.7,26.5-30.2,26.5h-30.8L400.3,511.2z M418,552.7
|
||||
c9.3,0,14.4-4.6,15.9-14.3c1.1-7.2-1.1-14.3-11.4-14.3h-5.1l-4.4,28.6H418z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 784 KiB |
After Width: | Height: | Size: 287 KiB |
|
@ -0,0 +1,93 @@
|
|||
Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 510 KiB |
After Width: | Height: | Size: 530 KiB |
|
@ -0,0 +1,128 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="185px" height="63px" viewBox="0 0 185 63" enable-background="new 0 0 185 63" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#1D1D1B" d="M52.791,0H0.037C0.02,0.556-0.321,14.721,1.487,23.643c2.958,14.599,11.115,27.729,24.925,39.422
|
||||
c13.813-11.693,21.966-24.824,24.932-39.422C53.154,14.721,52.804,0.556,52.791,0"/>
|
||||
<path fill="#FFFFFF" d="M33.177,9.163c0.631,0,1.146,0.513,1.146,1.144c0,0.629-0.514,1.147-1.146,1.147
|
||||
c-0.63,0-1.144-0.518-1.144-1.147C32.033,9.676,32.546,9.163,33.177,9.163"/>
|
||||
<path fill="#FFFFFF" d="M19.652,9.163c0.63,0,1.142,0.513,1.142,1.144c0,0.629-0.512,1.147-1.142,1.147
|
||||
c-0.631,0-1.147-0.518-1.147-1.147C18.505,9.676,19.021,9.163,19.652,9.163"/>
|
||||
<path fill="#FFFFFF" d="M16.498,39.622c0.978,0,1.025-0.507,1.069-0.669c-0.188,0.188-0.567,0.317-1.154,0.317
|
||||
c-0.593,0-1.166-0.242-1.474-1.201c-0.155-0.496-0.032-1.065-0.032-1.065s0.648,0.517,1.148,0.489
|
||||
c0.889-0.046,1.56-0.612,1.56-1.632c0-1.203-0.856-2.497-2.539-4.494c-1.5-1.776-2.24-3.69-2.24-5.568
|
||||
c0-4.427,4.411-7.22,4.411-7.22s0.461,11.07,0.552,12.266c0.096,1.195,0.375,3.236,1.087,4.821v3.856
|
||||
c-0.195,0.609-0.963,0.863-1.499,0.863C16.968,40.383,16.445,39.989,16.498,39.622"/>
|
||||
<path fill="#FFFFFF" d="M26.413,17.063c-6.53,0-9.876,0.775-9.876,0.775l-0.856-1.723c0,0,4.53-0.87,10.732-0.87
|
||||
c6.202,0,10.739,0.87,10.739,0.87l-0.858,1.723C36.293,17.838,32.947,17.063,26.413,17.063"/>
|
||||
<path fill="#FFFFFF" d="M32.275,48.641H20.556l-1.932-2.165c0,0,1.222-1.114,1.222-3.152v-6.07
|
||||
c-0.021-0.005,2.254,3.292,6.567,3.292c3.789,0,6.61-3.221,6.571-3.21v5.989c0,2.038,1.222,3.152,1.222,3.152L32.275,48.641z"/>
|
||||
<path fill="#FFFFFF" d="M36.775,37.492c0.498,0.028,1.148-0.489,1.148-0.489s0.122,0.569-0.035,1.065
|
||||
c-0.3,0.959-0.881,1.2-1.467,1.2c-0.592,0-0.972-0.127-1.159-0.315c0.044,0.162,0.094,0.669,1.069,0.669
|
||||
c0.053,0.368-0.468,0.762-0.888,0.762c-0.538,0-1.305-0.254-1.498-0.863v-3.856c0.709-1.585,0.986-3.626,1.081-4.821
|
||||
c0.093-1.195,0.559-12.266,0.559-12.266s4.406,2.793,4.406,7.22c0,1.878-0.736,3.792-2.238,5.568
|
||||
c-1.686,1.997-2.539,3.291-2.539,4.494C35.214,36.88,35.881,37.445,36.775,37.492"/>
|
||||
<path fill="#FFFFFF" d="M37.539,15.344c0,0-4.713-0.949-11.126-0.949c-6.41,0-11.12,0.949-11.12,0.949L12.18,9.11
|
||||
c0,0,0.919-0.637,1.576-0.637c1.156,0,1.416,1.039,1.416,1.403c0,0.288-0.035,0.508-0.207,0.705l-0.049,0.05
|
||||
c-0.26,0.295-0.325,0.371-0.325,0.568c0,0.179,0.125,0.46,0.464,0.46c0.299,0,0.371-0.095,0.505-0.472
|
||||
c0.141-0.382,0.516-0.766,1.049-0.766c0.522,0,1.104,0.454,1.104,1.103c0,0.693-0.565,1.107-1.104,1.107
|
||||
c-0.311,0-0.456-0.088-0.557-0.151c-0.069-0.041-0.104-0.062-0.162-0.062c-0.067,0-0.12,0.046-0.148,0.081
|
||||
c-0.086,0.102-0.12,0.271-0.086,0.436c0.088,0.475,0.989,1.211,2,1.211c1.503,0,2.416-1.354,2.416-2.332
|
||||
c0.578,1.375,1.855,1.666,2.74,1.666c2.382,0,2.875-2.113,2.875-2.679c0-0.38-0.122-0.691-0.234-0.746
|
||||
c-0.016-0.004-0.058-0.024-0.134,0.048l-0.097,0.082c-0.288,0.264-0.83,0.751-1.589,0.751c-0.851,0-1.733-0.639-1.733-1.707
|
||||
c0-1.135,0.87-1.728,1.728-1.728c0.714,0,1.117,0.391,1.391,0.652c0.104,0.104,0.213,0.213,0.271,0.213
|
||||
c0.119,0,0.232-0.05,0.306-0.132c0.056-0.065,0.091-0.15,0.091-0.239c0-0.015,0-0.035-0.004-0.052
|
||||
c-0.01-0.077-0.104-0.17-0.213-0.28c-0.234-0.245-0.558-0.579-0.558-1.226c0-0.743,0.603-1.535,1.502-1.539
|
||||
c0.905,0.003,1.505,0.795,1.505,1.539c0,0.647-0.325,0.981-0.562,1.226c-0.106,0.11-0.196,0.203-0.206,0.28
|
||||
c0,0.017-0.003,0.037-0.003,0.052c0,0.089,0.032,0.173,0.089,0.239c0.07,0.083,0.184,0.132,0.305,0.132
|
||||
c0.061,0,0.169-0.108,0.273-0.213c0.269-0.261,0.675-0.652,1.389-0.652c0.857,0,1.727,0.592,1.727,1.728
|
||||
c0,1.067-0.887,1.707-1.735,1.707c-0.755,0-1.3-0.487-1.588-0.751l-0.09-0.082c-0.082-0.072-0.123-0.052-0.137-0.048
|
||||
c-0.11,0.054-0.234,0.366-0.234,0.746c0,0.565,0.49,2.679,2.87,2.679c0.883,0,2.162-0.291,2.74-1.666
|
||||
c0,0.978,0.913,2.332,2.42,2.332c1.011,0,1.908-0.736,1.995-1.211c0.037-0.165,0-0.333-0.085-0.436
|
||||
c-0.03-0.035-0.081-0.081-0.149-0.081c-0.059,0-0.096,0.021-0.161,0.062c-0.102,0.063-0.245,0.151-0.557,0.151
|
||||
c-0.539,0-1.103-0.414-1.103-1.107c0-0.648,0.583-1.103,1.103-1.103c0.531,0,0.909,0.384,1.05,0.766
|
||||
c0.139,0.376,0.208,0.472,0.507,0.472c0.339,0,0.463-0.281,0.463-0.46c0-0.198-0.062-0.273-0.327-0.568l-0.044-0.05
|
||||
c-0.173-0.197-0.212-0.417-0.212-0.705c0-0.364,0.265-1.403,1.42-1.403c0.658,0,1.574,0.637,1.574,0.637L37.539,15.344z"/>
|
||||
<path fill="#FFFFFF" d="M34.523,18.494c-0.04,0.874-0.331,8.023-0.365,10.033c-0.01,0.824-0.233,3.622-0.443,4.671
|
||||
c-0.465,2.318-2.528,6.241-7.303,6.241c-4.769,0-6.833-3.923-7.297-6.241c-0.212-1.053-0.439-3.85-0.449-4.671
|
||||
c-0.027-2.01-0.322-9.159-0.357-10.033c0.521-0.079,3.444-0.488,8.104-0.488C31.079,18.006,34,18.415,34.523,18.494"/>
|
||||
<path fill="#1D1D1B" d="M31.359,21.949c-0.415,0-0.858,0.066-1.219,0.345c-0.191,0.146-0.253,0.347,0.032,0.347
|
||||
c0.498,0,0.812,0.454,0.812,0.794c0,0.34-0.217,0.512-0.467,0.512c-0.251,0-0.391,0.226-0.128,0.28
|
||||
c0.276,0.053,0.552,0.084,0.965,0.084c1.431,0,2.196-0.465,2.196-0.862C33.551,23.051,32.767,21.949,31.359,21.949"/>
|
||||
<path fill="#1D1D1B" d="M21.978,20.422c0.657,0.17,1.63,0.527,2.349,1.249c0.739,0.743,1.024,1.481,1.126,1.994
|
||||
c0.089,0.432,0.243,0.45,0.374,0.029c0.189-0.606,0.246-1.545-0.617-2.514c-0.794-0.892-2.011-1.088-2.727-1.088
|
||||
c-0.174,0-0.346,0.014-0.5,0.036C21.569,20.18,21.548,20.312,21.978,20.422"/>
|
||||
<path fill="#1D1D1B" d="M29.332,33.816c-0.417-0.2-1.565-0.758-1.84-0.883c-0.153-0.073-0.214-0.083-0.375-0.083h-1.419
|
||||
c-0.163,0-0.226,0.01-0.375,0.083c-0.28,0.125-1.43,0.684-1.838,0.883c-0.231,0.11-0.198,0.332,0.023,0.332h5.799
|
||||
C29.527,34.148,29.558,33.926,29.332,33.816"/>
|
||||
<path fill="#1D1D1B" d="M24.168,23.449c0-0.398-0.779-1.502-2.188-1.502c-0.414,0-0.857,0.068-1.217,0.348
|
||||
c-0.19,0.146-0.252,0.347,0.032,0.347c0.495,0,0.812,0.454,0.812,0.794c0,0.34-0.218,0.512-0.47,0.512
|
||||
c-0.246,0-0.389,0.228-0.126,0.28c0.279,0.053,0.552,0.085,0.968,0.085C23.407,24.312,24.168,23.846,24.168,23.449"/>
|
||||
<path fill="#1D1D1B" d="M28.28,34.977h-3.734c-0.196,0-0.226,0.188-0.091,0.278c0.117,0.083,0.884,0.593,1.057,0.702
|
||||
c0.135,0.083,0.2,0.095,0.365,0.095h1.071c0.167,0,0.234-0.013,0.369-0.095c0.17-0.109,0.936-0.619,1.055-0.702
|
||||
C28.502,35.164,28.477,34.977,28.28,34.977"/>
|
||||
<path fill="#1D1D1B" d="M32.222,20.096c-0.551,0-0.905-0.003-1.822-0.003c-1.886,0-3.193,1.564-3.193,3.462v5.552
|
||||
c0,0.316-0.122,0.511-0.454,0.511h-0.337c-0.182,0-0.373,0.044-0.546,0.191c-0.284,0.238-0.975,0.833-1.068,0.953
|
||||
c-0.1,0.112-0.072,0.25,0.215,0.25h3.73c0.212,0,0.304-0.093,0.13-0.25c-0.234-0.217-0.988-0.941-0.988-0.941v-5.599
|
||||
c0-1.459,0.282-3.255,3.032-3.255c0.731,0,1.379,0.002,1.497,0.002c1.003,0,1.537-0.876,1.537-0.876S32.769,20.096,32.222,20.096"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#1D1D1B" d="M62.277,21.176c0.812,0.977,2.188,1.797,3.627,1.866c1.634,0.079,2.689-0.838,2.689-1.813
|
||||
c0-1.18-0.886-1.871-2.548-2.562l-1.328-0.536c-2.537-1.046-3.793-2.562-3.793-4.613c0-2.538,2.441-4.187,5.004-4.187
|
||||
c1.945,0,3.428,0.645,4.661,2.026l-1.844,1.645c-0.84-0.905-1.646-1.266-2.812-1.266c-1.138,0-2.324,0.637-2.324,1.731
|
||||
c0,1.024,0.823,1.79,2.507,2.46l1.307,0.557c2.299,0.934,3.867,2.316,3.867,4.522c0,2.209-1.772,4.501-5.362,4.501
|
||||
c-2.479,0-4.289-1.123-5.708-2.566L62.277,21.176z"/>
|
||||
<path fill="#1D1D1B" d="M79.538,25.309H77.5c-2.387,0-3.801-1.416-3.801-3.776v-6.21h-2.025v-2.434h2.025V9.963h2.73v2.925h3.108
|
||||
v2.434H76.43v5.64c0,1.266,0.348,1.838,1.765,1.838h1.344V25.309z"/>
|
||||
<path fill="#1D1D1B" d="M80.835,19.099c0-3.626,2.732-6.409,6.435-6.409c3.675,0,6.432,2.782,6.432,6.409
|
||||
c0,3.627-2.757,6.408-6.432,6.408C83.567,25.507,80.835,22.726,80.835,19.099 M90.895,19.099c0-2.161-1.515-3.8-3.625-3.8
|
||||
c-2.138,0-3.626,1.64-3.626,3.8c0,2.161,1.489,3.801,3.626,3.801C89.38,22.9,90.895,21.26,90.895,19.099"/>
|
||||
<g>
|
||||
<path fill="#1D1D1B" d="M95.46,19.099c0-3.626,2.731-6.409,6.409-6.409c1.985,0,3.6,0.821,4.744,2.063l-1.864,1.912
|
||||
c-0.645-0.746-1.589-1.318-2.88-1.318c-2.113,0-3.603,1.591-3.603,3.751c0,2.161,1.49,3.751,3.603,3.751
|
||||
c1.291,0,2.235-0.57,2.88-1.317l1.864,1.913c-1.144,1.241-2.759,2.062-4.744,2.062C98.191,25.507,95.46,22.726,95.46,19.099"/>
|
||||
</g>
|
||||
<polygon fill="#1D1D1B" points="111.33,25.309 108.598,25.309 108.598,7.969 111.33,7.969 111.33,18.503 116.297,12.889
|
||||
119.527,12.889 114.362,18.776 120.147,25.309 116.796,25.309 111.33,19.099 "/>
|
||||
<path fill="#1D1D1B" d="M124.18,17.485v7.825h-2.732V7.969h2.732v6.088c0.869-0.945,2.185-1.367,3.479-1.367
|
||||
c3.079,0,5.042,2.137,5.042,5.341v7.278h-2.735V18.08c0-1.341-0.991-2.782-2.83-2.782
|
||||
C125.721,15.298,124.652,16.043,124.18,17.485"/>
|
||||
<g>
|
||||
<path fill="#1D1D1B" d="M134.916,19.099c0-3.626,2.73-6.409,6.432-6.409c3.676,0,6.433,2.782,6.433,6.409
|
||||
c0,3.627-2.757,6.408-6.433,6.408C137.646,25.507,134.916,22.726,134.916,19.099 M144.974,19.099c0-2.161-1.515-3.8-3.626-3.8
|
||||
c-2.137,0-3.626,1.64-3.626,3.8c0,2.161,1.49,3.801,3.626,3.801C143.459,22.9,144.974,21.26,144.974,19.099"/>
|
||||
</g>
|
||||
<rect x="149.996" y="7.969" fill="#1D1D1B" width="2.732" height="17.34"/>
|
||||
<path fill="#1D1D1B" d="M158.134,17.409v7.9h-2.734v-12.42h2.734v1.168c0.844-0.97,2.061-1.366,3.206-1.366
|
||||
c1.737,0,3.053,0.694,3.872,1.912c1.168-1.541,2.908-1.912,4.099-1.912c3.057,0,4.794,2.135,4.794,5.34v7.277h-2.732V18.08
|
||||
c0-1.341-0.771-2.782-2.558-2.782c-1.243,0-2.236,0.745-2.708,2.162c0.026,0.198,0.026,0.373,0.026,0.571v7.277h-2.733V18.08
|
||||
c0-1.341-0.794-2.782-2.582-2.782C159.6,15.298,158.58,16.043,158.134,17.409"/>
|
||||
<path fill="#1D1D1B" d="M175.632,23.354l0.221,0.201c1.478,1.343,2.901,1.996,4.649,1.996c2.954,0,4.498-1.856,4.498-3.689
|
||||
c0-1.631-1.051-2.835-3.213-3.684l-1.035-0.427c-1.619-0.621-1.805-1.225-1.805-1.629c0-0.657,0.792-1.102,1.558-1.102
|
||||
c0.844,0,1.41,0.297,2.017,0.928l0.18,0.189l1.827-1.576l-0.184-0.197c-1.003-1.086-2.21-1.653-3.844-1.653
|
||||
c-2.074,0-4.22,1.292-4.22,3.453c0,1.655,1.064,2.917,3.163,3.754l1.043,0.404c1.341,0.54,1.837,1.002,1.837,1.709
|
||||
c0,0.549-0.631,1.12-1.69,1.12c-0.047,0-0.093-0.001-0.141-0.003c-0.961-0.044-2.079-0.514-2.734-1.273l-0.178-0.207
|
||||
L175.632,23.354z"/>
|
||||
<path fill="#1D1D1B" d="M61.109,43.729l0.219,0.2c1.479,1.346,2.902,1.997,4.65,1.997c2.952,0,4.497-1.855,4.497-3.687
|
||||
c0-1.632-1.05-2.836-3.212-3.686l-1.038-0.427c-1.618-0.619-1.804-1.225-1.804-1.627c0-0.66,0.793-1.103,1.561-1.103
|
||||
c0.844,0,1.407,0.296,2.015,0.928l0.18,0.188l1.828-1.575l-0.185-0.197c-1.002-1.086-2.21-1.654-3.843-1.654
|
||||
c-2.074,0-4.219,1.292-4.219,3.452c0,1.656,1.064,2.919,3.162,3.757l1.042,0.404c1.341,0.539,1.84,1.001,1.84,1.707
|
||||
c0,0.55-0.633,1.121-1.693,1.121c-0.045,0-0.093-0.001-0.141-0.004c-0.961-0.045-2.077-0.512-2.733-1.273l-0.177-0.208
|
||||
L61.109,43.729z"/>
|
||||
<path fill="#1D1D1B" d="M79.538,45.726H77.5c-2.387,0-3.801-1.416-3.801-3.774v-6.21h-2.025v-2.434h2.025v-2.926h2.73v2.926h3.108
|
||||
v2.434H76.43v5.638c0,1.268,0.348,1.839,1.765,1.839h1.344V45.726z"/>
|
||||
<path fill="#1D1D1B" d="M90.547,44.809c-0.868,0.694-2.01,1.116-3.327,1.116c-3.676,0-6.385-2.781-6.385-6.408
|
||||
c0-3.625,2.709-6.407,6.385-6.407c1.317,0,2.458,0.422,3.327,1.117v-0.917h2.733v12.417h-2.733V44.809z M90.547,41.628v-4.222
|
||||
c-0.646-1.018-1.912-1.689-3.302-1.689c-2.112,0-3.601,1.639-3.601,3.8c0,2.16,1.489,3.801,3.601,3.801
|
||||
C88.635,43.317,89.901,42.647,90.547,41.628"/>
|
||||
<path fill="#1D1D1B" d="M105.171,44.809c-0.868,0.694-2.01,1.116-3.327,1.116c-3.677,0-6.384-2.781-6.384-6.408
|
||||
c0-3.625,2.707-6.407,6.384-6.407c1.316,0,2.458,0.422,3.327,1.117v-5.839h2.733v17.338h-2.733V44.809z M105.171,41.628v-4.222
|
||||
c-0.646-1.018-1.912-1.689-3.302-1.689c-2.113,0-3.603,1.639-3.603,3.8c0,2.16,1.49,3.801,3.603,3.801
|
||||
C103.259,43.317,104.524,42.647,105.171,41.628"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 537 KiB |
|
@ -0,0 +1,23 @@
|
|||
module.exports = {
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: [
|
||||
[
|
||||
'module-resolver',
|
||||
{
|
||||
root: ['./src/apps/skolplattformen-app-new/'],
|
||||
extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],
|
||||
alias: {
|
||||
'@skolplattformen/api': ['libs/api/lib/index.ts'],
|
||||
'@skolplattformen/api-hjarntorget': [
|
||||
'libs/api-hjarntorget/lib/index.ts',
|
||||
],
|
||||
'@skolplattformen/api-skolplattformen': [
|
||||
'libs/api-skolplattformen/lib/index.ts',
|
||||
],
|
||||
'@skolplattformen/curriculum': ['libs/curriculum/src/index.ts'],
|
||||
'@skolplattformen/hooks': ['libs/hooks/src/index.ts'],
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import { useRoute } from '@react-navigation/native'
|
||||
import { useUser } from '../../libs/hooks/src'
|
||||
import { fireEvent, waitFor } from '@testing-library/react-native'
|
||||
import Mockdate from 'mockdate'
|
||||
import React from 'react'
|
||||
import { useSMS } from '../../utils/SMS'
|
||||
import { render } from '../../utils/testHelpers'
|
||||
import Absence from '../absence.component'
|
||||
|
||||
let sendSMS
|
||||
let user = { personalNumber: '201701092395' }
|
||||
|
||||
jest.mock('../../utils/SMS')
|
||||
jest.mock('../../libs/hooks/src')
|
||||
|
||||
const setup = (customProps = {}) => {
|
||||
sendSMS = jest.fn()
|
||||
|
||||
useSMS.mockReturnValue({ sendSMS })
|
||||
useRoute.mockReturnValue({ params: { child: { id: '1' } } })
|
||||
|
||||
const props = {
|
||||
...customProps,
|
||||
}
|
||||
|
||||
return render(<Absence {...props} />)
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
// Hide errors from act
|
||||
// https://github.com/callstack/react-native-testing-library/issues/379
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {
|
||||
// noop
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
useUser.mockReturnValue({
|
||||
data: user,
|
||||
status: 'loaded',
|
||||
})
|
||||
await AsyncStorage.clear()
|
||||
})
|
||||
|
||||
test.skip('can fill out the form with full day absence', async () => {
|
||||
const screen = setup()
|
||||
|
||||
await waitFor(() =>
|
||||
fireEvent.changeText(
|
||||
screen.getByTestId('personalIdentityNumberInput'),
|
||||
'1212121212'
|
||||
)
|
||||
)
|
||||
await waitFor(() => fireEvent.press(screen.getByText('Skicka')))
|
||||
|
||||
expect(screen.queryByText(/starttid/i)).toBeFalsy()
|
||||
expect(screen.queryByText(/sluttid/i)).toBeFalsy()
|
||||
|
||||
expect(sendSMS).toHaveBeenCalledWith('121212-1212')
|
||||
})
|
||||
|
||||
test.skip('handles missing social security number', async () => {
|
||||
const screen = setup()
|
||||
|
||||
await waitFor(() => fireEvent.press(screen.getByText('Skicka')))
|
||||
|
||||
expect(screen.getByText(/Personnummer saknas/i)).toBeTruthy()
|
||||
expect(sendSMS).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test.skip('validates social security number', async () => {
|
||||
const screen = setup()
|
||||
|
||||
await waitFor(() =>
|
||||
fireEvent.changeText(
|
||||
screen.getByTestId('personalIdentityNumberInput'),
|
||||
'12121212'
|
||||
)
|
||||
)
|
||||
await waitFor(() => fireEvent.press(screen.getByText('Skicka')))
|
||||
|
||||
expect(screen.getByText(/Personnumret är ogiltigt/i)).toBeTruthy()
|
||||
expect(sendSMS).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test.skip('can fill out the form with part of day absence', async () => {
|
||||
Mockdate.set('2021-02-18 15:30')
|
||||
|
||||
const screen = setup()
|
||||
|
||||
await waitFor(() =>
|
||||
fireEvent.changeText(
|
||||
screen.getByTestId('personalIdentityNumberInput'),
|
||||
'1212121212'
|
||||
)
|
||||
)
|
||||
await waitFor(() => fireEvent.press(screen.getByText('Heldag')))
|
||||
|
||||
expect(screen.getByText(/starttid/i)).toBeTruthy()
|
||||
expect(screen.getByText(/sluttid/i)).toBeTruthy()
|
||||
|
||||
await waitFor(() => fireEvent.press(screen.getByText('Skicka')))
|
||||
|
||||
expect(sendSMS).toHaveBeenCalledWith('121212-1212 1500-1700')
|
||||
})
|
|
@ -0,0 +1,239 @@
|
|||
import 'setImmediate'
|
||||
import { useNavigation } from '@react-navigation/core'
|
||||
import {
|
||||
useApi,
|
||||
useCalendar,
|
||||
useChildList,
|
||||
useClassmates,
|
||||
useMenu,
|
||||
useNews,
|
||||
useNotifications,
|
||||
useSchedule,
|
||||
useTimetable,
|
||||
} from '../../libs/hooks/src'
|
||||
import React from 'react'
|
||||
import * as RNLocalize from 'react-native-localize'
|
||||
import { render } from '../../utils/testHelpers'
|
||||
import { translate } from '../../utils/translation'
|
||||
import { Children } from '../children.component'
|
||||
|
||||
jest.mock('../../libs/hooks/src')
|
||||
|
||||
const pause = (ms = 0) => new Promise((r) => setTimeout(r, ms))
|
||||
|
||||
const setup = () => {
|
||||
return render(<Children />)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
useApi.mockReturnValue({
|
||||
api: { on: jest.fn(), off: jest.fn(), logout: jest.fn() },
|
||||
isLoggedIn: false,
|
||||
})
|
||||
RNLocalize.findBestAvailableLanguage.mockImplementationOnce(() => ({
|
||||
languageTag: 'sv',
|
||||
isRTL: false,
|
||||
}))
|
||||
useCalendar.mockReturnValueOnce({ data: [], status: 'loaded' })
|
||||
useNotifications.mockReturnValueOnce({ data: [], status: 'loaded' })
|
||||
useNews.mockReturnValueOnce({ data: [], status: 'loaded' })
|
||||
useSchedule.mockReturnValueOnce({ data: [], status: 'loaded' })
|
||||
useMenu.mockReturnValueOnce({ data: [], status: 'loaded' })
|
||||
useTimetable.mockReturnValueOnce({ data: [], status: 'loaded' })
|
||||
useClassmates.mockReturnValueOnce({ data: [], status: 'loaded' })
|
||||
useNavigation.mockReturnValue({ navigate: jest.fn(), setOptions: jest.fn() })
|
||||
})
|
||||
|
||||
test('renders loading state', async () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [],
|
||||
status: 'loading',
|
||||
}))
|
||||
|
||||
const screen = setup()
|
||||
expect(screen.getByText(translate('general.loading'))).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders empty state message', () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [],
|
||||
status: 'loaded',
|
||||
}))
|
||||
const screen = setup()
|
||||
|
||||
expect(
|
||||
screen.getByText(translate('children.noKids_description'))
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders error state message', () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [],
|
||||
status: 'error',
|
||||
}))
|
||||
|
||||
const screen = setup()
|
||||
|
||||
expect(
|
||||
screen.getByText(translate('children.loadingErrorHeading'))
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders child in preschool', () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [
|
||||
{
|
||||
name: 'Test Testsson',
|
||||
status: 'F',
|
||||
},
|
||||
],
|
||||
status: 'loaded',
|
||||
}))
|
||||
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText('Test Testsson')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders child in elementary school', () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [
|
||||
{
|
||||
name: 'Test Testsson',
|
||||
status: 'GR',
|
||||
},
|
||||
],
|
||||
status: 'loaded',
|
||||
}))
|
||||
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText('Test Testsson')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders child in high school', () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [
|
||||
{
|
||||
name: 'Test Testsson',
|
||||
status: 'G',
|
||||
},
|
||||
],
|
||||
status: 'loaded',
|
||||
}))
|
||||
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText('Test Testsson')).toBeTruthy()
|
||||
expect(
|
||||
screen.getByText(translate('abbrevations.upperSecondarySchool'))
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders multiple children', () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [
|
||||
{
|
||||
name: 'Storasyster Testsson',
|
||||
status: 'G',
|
||||
},
|
||||
{
|
||||
name: 'Lillebror Testsson',
|
||||
status: 'GR',
|
||||
},
|
||||
],
|
||||
status: 'loaded',
|
||||
}))
|
||||
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText('Storasyster Testsson')).toBeTruthy()
|
||||
expect(
|
||||
screen.getByText(translate('abbrevations.upperSecondarySchool'))
|
||||
).toBeTruthy()
|
||||
|
||||
expect(screen.getByText('Lillebror Testsson')).toBeTruthy()
|
||||
expect(
|
||||
screen.getByText(translate('abbrevations.compulsorySchool'))
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders child in class', () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [
|
||||
{
|
||||
name: 'Test Testsson',
|
||||
status: 'G',
|
||||
schoolID: 'Vallaskolan',
|
||||
},
|
||||
],
|
||||
status: 'loaded',
|
||||
}))
|
||||
useClassmates.mockReset()
|
||||
useClassmates.mockImplementationOnce(() => ({
|
||||
data: [
|
||||
{
|
||||
className: '8C',
|
||||
},
|
||||
],
|
||||
status: 'loaded',
|
||||
}))
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText('Test Testsson')).toBeTruthy()
|
||||
expect(screen.getByText('8C • Vallaskolan')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('removes any parenthesis from name', () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [
|
||||
{
|
||||
name: 'Test Testsson (elev)',
|
||||
status: 'G',
|
||||
},
|
||||
],
|
||||
status: 'loaded',
|
||||
}))
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText('Test Testsson')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('handles multiple statuses for a child', () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [
|
||||
{
|
||||
name: 'Test Testsson(elev)',
|
||||
status: 'G;GR;F',
|
||||
},
|
||||
],
|
||||
status: 'loaded',
|
||||
}))
|
||||
const screen = setup()
|
||||
|
||||
var multipleStatusesRendered = `${translate(
|
||||
'abbrevations.upperSecondarySchool'
|
||||
)}, ${translate('abbrevations.compulsorySchool')}, ${translate(
|
||||
'abbrevations.leisureTimeCentre'
|
||||
)}`
|
||||
|
||||
expect(screen.getByText('Test Testsson')).toBeTruthy()
|
||||
expect(screen.getByText(multipleStatusesRendered)).toBeTruthy()
|
||||
})
|
||||
|
||||
test('says if there is nothing new this week', () => {
|
||||
useChildList.mockImplementationOnce(() => ({
|
||||
data: [
|
||||
{
|
||||
name: 'Kanye West',
|
||||
status: 'F',
|
||||
},
|
||||
],
|
||||
status: 'loaded',
|
||||
}))
|
||||
const screen = setup()
|
||||
|
||||
expect(
|
||||
screen.getByText(translate('news.noNewNewsItemsThisWeek'))
|
||||
).toBeTruthy()
|
||||
})
|
|
@ -0,0 +1,86 @@
|
|||
import { useClassmates } from '../../libs/hooks/src'
|
||||
import React from 'react'
|
||||
import { render } from '../../utils/testHelpers'
|
||||
import { ChildProvider } from '../childContext.component'
|
||||
import { Classmates } from '../classmates.component'
|
||||
|
||||
jest.mock('../../libs/hooks/src')
|
||||
|
||||
const defaultClassmates = [
|
||||
{
|
||||
className: '2B',
|
||||
firstname: 'Tyrell',
|
||||
lastname: 'Eriksson',
|
||||
guardians: [
|
||||
{
|
||||
firstname: 'Margaery',
|
||||
lastname: 'Eriksson',
|
||||
},
|
||||
{
|
||||
firstname: 'Loras',
|
||||
lastname: 'Eriksson',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
className: '2B',
|
||||
firstname: 'Adam',
|
||||
lastname: 'Svensson',
|
||||
guardians: [
|
||||
{
|
||||
firstname: 'Eva',
|
||||
lastname: 'Svensson',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const setup = ({ classmates } = { classmates: defaultClassmates }) => {
|
||||
useClassmates.mockReturnValue({
|
||||
data: classmates,
|
||||
})
|
||||
|
||||
return render(
|
||||
<ChildProvider child={{ id: 1 }}>
|
||||
<Classmates />
|
||||
</ChildProvider>
|
||||
)
|
||||
}
|
||||
|
||||
test('gets the classmates for a child from context', () => {
|
||||
setup()
|
||||
|
||||
expect(useClassmates).toHaveBeenCalledWith({ id: 1 })
|
||||
})
|
||||
|
||||
test('renders class name', () => {
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText(/^klass 2b$/i)).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders class without name', () => {
|
||||
const screen = setup({
|
||||
classmates: [],
|
||||
})
|
||||
|
||||
expect(screen.getByText(/^klass$/i)).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders classmates sorted by first name', () => {
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByLabelText('Barn 1')).toContainElement(
|
||||
screen.getByText(/adam svensson/i)
|
||||
)
|
||||
expect(screen.getByLabelText('Barn 2')).toContainElement(
|
||||
screen.getByText(/tyrell eriksson/i)
|
||||
)
|
||||
})
|
||||
|
||||
test('renders guardians sorted by first name', () => {
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText(/eva svensson/i)).toBeTruthy()
|
||||
expect(screen.getByText(/^loras eriksson, margaery eriksson$/i)).toBeTruthy()
|
||||
})
|
|
@ -0,0 +1,137 @@
|
|||
import { fireEvent } from '@testing-library/react-native'
|
||||
import React from 'react'
|
||||
import { Linking } from 'react-native'
|
||||
import { render } from '../../utils/testHelpers'
|
||||
import { ContactMenu } from '../contactMenu.component'
|
||||
import { act } from 'react-test-renderer'
|
||||
|
||||
const defaultGuardian = {
|
||||
address: 'Testgatan',
|
||||
email: 'adam@adamsson.se',
|
||||
firstname: 'Adam',
|
||||
lastname: 'Adamsson',
|
||||
mobile: '0701234567',
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
contact: {
|
||||
guardians: [defaultGuardian],
|
||||
},
|
||||
}
|
||||
|
||||
const setup = (customProps = {}) => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
...customProps,
|
||||
}
|
||||
|
||||
return render(<ContactMenu {...props} />)
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
// Hide errors from state illegal state transition
|
||||
// Probably due to mock
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {
|
||||
// noop
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(jest.clearAllMocks)
|
||||
|
||||
test('renders a parent', () => {
|
||||
const screen = setup()
|
||||
|
||||
fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
|
||||
|
||||
expect(screen.getByText(/adam adamsson/i)).toBeTruthy()
|
||||
})
|
||||
|
||||
test('displays option to call and text guardian', () => {
|
||||
const screen = setup()
|
||||
|
||||
fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
|
||||
|
||||
fireEvent.press(screen.getByText(/ring/i))
|
||||
expect(Linking.openURL).toHaveBeenCalledWith('tel:0701234567')
|
||||
|
||||
fireEvent.press(screen.getByText(/sms/i))
|
||||
expect(Linking.openURL).toHaveBeenCalledWith('sms:0701234567')
|
||||
})
|
||||
|
||||
test('hides options to call and text if no phone number', () => {
|
||||
const guardianWithoutPhoneNumber = {
|
||||
contact: {
|
||||
guardians: [
|
||||
{
|
||||
...defaultGuardian,
|
||||
mobile: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const screen = setup(guardianWithoutPhoneNumber)
|
||||
|
||||
fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
|
||||
|
||||
expect(screen.queryByTestId('CallMenuItem')).toBeNull()
|
||||
expect(screen.queryByTestId('SMSMenuItem')).toBeNull()
|
||||
})
|
||||
|
||||
test('displays option to email guardian', () => {
|
||||
const screen = setup()
|
||||
|
||||
fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
|
||||
|
||||
fireEvent.press(screen.getByText(/maila/i))
|
||||
expect(Linking.openURL).toHaveBeenCalledWith('mailto:adam@adamsson.se')
|
||||
})
|
||||
|
||||
test('hides options to email phone number', () => {
|
||||
const guardianWithoutEmail = {
|
||||
contact: {
|
||||
guardians: [
|
||||
{
|
||||
...defaultGuardian,
|
||||
email: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const screen = setup(guardianWithoutEmail)
|
||||
|
||||
fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
|
||||
|
||||
expect(screen.queryByTestId('SendEmailMenuItem')).toBeNull()
|
||||
})
|
||||
|
||||
test('displays address of guardian', () => {
|
||||
const screen = setup()
|
||||
|
||||
fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
|
||||
|
||||
fireEvent.press(screen.getByText(/adress/i))
|
||||
expect(Linking.openURL).toHaveBeenCalledWith(
|
||||
'http://maps.apple.com/?daddr=Testgatan'
|
||||
)
|
||||
})
|
||||
|
||||
test('hides address if it does not exist', () => {
|
||||
const guardianWithoutAddress = {
|
||||
contact: {
|
||||
guardians: [
|
||||
{
|
||||
...defaultGuardian,
|
||||
address: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const screen = setup(guardianWithoutAddress)
|
||||
|
||||
fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
|
||||
|
||||
expect(screen.queryByTestId('ShowHomeMenuItem')).toBeNull()
|
||||
})
|
|
@ -0,0 +1,50 @@
|
|||
import { useMenu } from '../../libs/hooks/src'
|
||||
import React from 'react'
|
||||
import { render } from '../../utils/testHelpers'
|
||||
import { translate } from '../../utils/translation'
|
||||
import { Menu } from '../menu.component'
|
||||
|
||||
jest.mock('../../libs/hooks/src')
|
||||
|
||||
const defaultItemList = [
|
||||
{
|
||||
title: 'Måndag vecka 10',
|
||||
description: 'Krämiga köttbullar',
|
||||
},
|
||||
{
|
||||
title: 'Tisdag vecka 10',
|
||||
description: 'Kryddig falukorv',
|
||||
},
|
||||
{
|
||||
title: 'Onsdag vecka 10',
|
||||
description: 'Sushi',
|
||||
},
|
||||
]
|
||||
|
||||
const setup = (itemList = defaultItemList) => {
|
||||
useMenu.mockReturnValue({
|
||||
data: itemList,
|
||||
})
|
||||
|
||||
return render(<Menu />)
|
||||
}
|
||||
|
||||
test('renders multiple days', () => {
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText('Måndag vecka 10')).toBeTruthy()
|
||||
expect(screen.getByText('Tisdag vecka 10')).toBeTruthy()
|
||||
expect(screen.getByText('Onsdag vecka 10')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders title and description', () => {
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText('Måndag vecka 10')).toBeTruthy()
|
||||
expect(screen.getByText('Krämiga köttbullar')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders empty menu', () => {
|
||||
const screen = setup([])
|
||||
expect(screen.getByText(translate('menu.emptyText'))).toBeTruthy()
|
||||
})
|
|
@ -0,0 +1,86 @@
|
|||
import { useApi, useNewsDetails } from '../../libs/hooks/src'
|
||||
import React from 'react'
|
||||
import { render } from '../../utils/testHelpers'
|
||||
import { NewsItem } from '../newsItem.component'
|
||||
|
||||
jest.mock('../../libs/hooks/src')
|
||||
|
||||
const defaultNewsItem = {
|
||||
author: 'Köket',
|
||||
fullImageUrl: 'test.png',
|
||||
header: 'K-bullar!',
|
||||
published: '2021-02-15T09:13:28.484Z',
|
||||
modified: '2021-02-15T09:13:28.484Z',
|
||||
}
|
||||
|
||||
let navigation
|
||||
|
||||
const setup = (customProps = { newsItem: {} }) => {
|
||||
useApi.mockReturnValue({ api: { getSessionCookie: jest.fn() } })
|
||||
|
||||
useNewsDetails.mockReturnValue({
|
||||
data: {
|
||||
body: 'Nu blir det köttbullar',
|
||||
},
|
||||
})
|
||||
|
||||
navigation = {
|
||||
goBack: jest.fn(),
|
||||
}
|
||||
|
||||
const newsItem = {
|
||||
...defaultNewsItem,
|
||||
...customProps.newsItem,
|
||||
}
|
||||
|
||||
const props = {
|
||||
navigation,
|
||||
route: {
|
||||
params: {
|
||||
child: { id: 1 },
|
||||
newsItem,
|
||||
},
|
||||
},
|
||||
...customProps,
|
||||
}
|
||||
|
||||
return render(<NewsItem {...props} />)
|
||||
}
|
||||
|
||||
test('gets article details using useNewsDetails', async () => {
|
||||
setup()
|
||||
|
||||
expect(useNewsDetails).toHaveBeenCalledWith({ id: 1 }, defaultNewsItem)
|
||||
})
|
||||
|
||||
test('renders an article', () => {
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
|
||||
expect(screen.getByText('Publicerad: 15 feb 2021 10:13')).toBeTruthy()
|
||||
expect(screen.getByText('Uppdaterad: 15 feb 2021 10:13')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders an article without published date if date is invalid', () => {
|
||||
const newsItemWithoutPublishedDate = {
|
||||
...defaultNewsItem,
|
||||
published: '2020-08-16T21:10:00.000+02:0',
|
||||
}
|
||||
const screen = setup({ newsItem: newsItemWithoutPublishedDate })
|
||||
|
||||
expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
|
||||
expect(screen.getByText('Uppdaterad: 15 feb 2021 10:13')).toBeTruthy()
|
||||
expect(screen.queryByText('Publicerad: Invalid DateTime')).toBeFalsy()
|
||||
})
|
||||
|
||||
test('renders an article without modified date if date is invalid', () => {
|
||||
const newsItemWithoutPublishedDate = {
|
||||
...defaultNewsItem,
|
||||
modified: null,
|
||||
}
|
||||
const screen = setup({ newsItem: newsItemWithoutPublishedDate })
|
||||
|
||||
expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
|
||||
expect(screen.getByText('Publicerad: 15 feb 2021 10:13')).toBeTruthy()
|
||||
expect(screen.queryByText('Uppdaterad: Invalid DateTime')).toBeFalsy()
|
||||
})
|
|
@ -0,0 +1,81 @@
|
|||
import { useNavigation } from '@react-navigation/native'
|
||||
import { fireEvent } from '@testing-library/react-native'
|
||||
import MockDate from 'mockdate'
|
||||
import React from 'react'
|
||||
import { render } from '../../utils/testHelpers'
|
||||
import { ChildProvider } from '../childContext.component'
|
||||
import { NewsListItem } from '../newsListItem.component'
|
||||
|
||||
const defaultItem = {
|
||||
author: 'Köket',
|
||||
intro: 'Nu blir det köttbullar',
|
||||
header: 'K-bullar!',
|
||||
published: '2021-02-15T09:13:28.484Z',
|
||||
modified: '2021-02-15T09:13:28.484Z',
|
||||
}
|
||||
|
||||
const setup = (customProps = {}) => {
|
||||
const props = {
|
||||
item: defaultItem,
|
||||
...customProps,
|
||||
}
|
||||
|
||||
return render(
|
||||
<ChildProvider child={{ id: 1 }}>
|
||||
<NewsListItem {...props} />
|
||||
</ChildProvider>
|
||||
)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
MockDate.set('2021-02-15T09:30:28.484Z')
|
||||
})
|
||||
|
||||
test('renders an article', () => {
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText(/k-bullar!/i)).toBeTruthy()
|
||||
expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
|
||||
expect(screen.getByText('Köket • för 17 minuter sedan')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders article without date', () => {
|
||||
const itemWithInvalidDate = {
|
||||
...defaultItem,
|
||||
published: null,
|
||||
modified: null,
|
||||
}
|
||||
|
||||
const screen = setup({ item: itemWithInvalidDate })
|
||||
|
||||
expect(screen.getByText(/k-bullar!/i)).toBeTruthy()
|
||||
expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
|
||||
expect(screen.getByText(/^köket$/i)).toBeTruthy()
|
||||
})
|
||||
|
||||
test('falls back to modified date if no published date', () => {
|
||||
const itemWithInvalidDate = {
|
||||
...defaultItem,
|
||||
published: null,
|
||||
}
|
||||
|
||||
const screen = setup({ item: itemWithInvalidDate })
|
||||
|
||||
expect(screen.getByText(/k-bullar!/i)).toBeTruthy()
|
||||
expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
|
||||
expect(screen.getByText('Köket • för 17 minuter sedan')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('navigates to news article on press', () => {
|
||||
const navigate = jest.fn()
|
||||
useNavigation.mockReturnValue({ navigate })
|
||||
|
||||
const screen = setup()
|
||||
|
||||
fireEvent.press(screen.getByText(/k-bullar!/i))
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith('NewsItem', {
|
||||
child: { id: 1 },
|
||||
newsItem: defaultItem,
|
||||
})
|
||||
})
|
|
@ -0,0 +1,73 @@
|
|||
import React from 'react'
|
||||
import { render } from '../../utils/testHelpers'
|
||||
import { Notification } from '../notification.component'
|
||||
import MockDate from 'mockdate'
|
||||
|
||||
const defaultItem = {
|
||||
sender: 'Planering',
|
||||
category: 'Bedömning',
|
||||
dateCreated: '2021-02-15T09:13:28.484Z',
|
||||
dateModified: '2021-02-15T09:14:28.484Z',
|
||||
}
|
||||
|
||||
// copied from https://github.com/react-native-webview/react-native-webview/issues/2934#issuecomment-1524101977
|
||||
jest.mock('react-native-webview', () => {
|
||||
const { View } = require('react-native')
|
||||
return {
|
||||
WebView: View,
|
||||
}
|
||||
})
|
||||
//
|
||||
|
||||
const setup = (customProps = {}) => {
|
||||
const props = {
|
||||
item: defaultItem,
|
||||
...customProps,
|
||||
}
|
||||
|
||||
return render(<Notification {...props} />)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
MockDate.set('2021-02-15T09:30:28.484Z')
|
||||
})
|
||||
|
||||
test('renders subtitle with modified date', () => {
|
||||
const screen = setup()
|
||||
|
||||
expect(screen.getByText('Bedömning • för 16 minuter sedan')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders subtitle with created date', () => {
|
||||
const itemWithoutModifiedDate = {
|
||||
...defaultItem,
|
||||
dateModified: undefined,
|
||||
}
|
||||
|
||||
const screen = setup({ item: itemWithoutModifiedDate })
|
||||
|
||||
expect(screen.getByText('Bedömning • för 17 minuter sedan')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders subtitle without date', () => {
|
||||
const itemWithoutDate = {
|
||||
...defaultItem,
|
||||
dateCreated: undefined,
|
||||
dateModified: undefined,
|
||||
}
|
||||
|
||||
const screen = setup({ item: itemWithoutDate })
|
||||
|
||||
expect(screen.getByText('Bedömning')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders subtitle without category', () => {
|
||||
const itemWithoutCategory = {
|
||||
...defaultItem,
|
||||
category: undefined,
|
||||
}
|
||||
|
||||
const screen = setup({ item: itemWithoutCategory })
|
||||
|
||||
expect(screen.getByText('för 16 minuter sedan')).toBeTruthy()
|
||||
})
|
|
@ -0,0 +1,133 @@
|
|||
import { fireEvent } from '@testing-library/react-native'
|
||||
import React from 'react'
|
||||
import RNCalendarEvents from 'react-native-calendar-events'
|
||||
import Toast from 'react-native-simple-toast'
|
||||
import { render } from '../../utils/testHelpers'
|
||||
import { SaveToCalendar } from '../saveToCalendar.component'
|
||||
|
||||
const defaultEvent = {
|
||||
title: 'Utvecklingssamtal',
|
||||
startDate: '2021-06-19 13:00',
|
||||
endDate: '2021-06-19 14:00',
|
||||
location: 'Gubbängsskolan',
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
event: defaultEvent,
|
||||
}
|
||||
|
||||
const setup = (customProps = {}) => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
...customProps,
|
||||
}
|
||||
|
||||
return render(<SaveToCalendar {...props} />)
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
// Hide errors from state illegal state transition
|
||||
// Probably due to mock
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {
|
||||
// noop
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(jest.clearAllMocks)
|
||||
|
||||
test('renders save to calendar', () => {
|
||||
const screen = setup()
|
||||
|
||||
fireEvent.press(screen.getByTestId('actionsButton'))
|
||||
|
||||
expect(screen.getByText(/Spara/i)).toBeTruthy()
|
||||
})
|
||||
|
||||
test('requests calendar permissons', () => {
|
||||
const screen = setup()
|
||||
|
||||
fireEvent.press(screen.getByTestId('actionsButton'))
|
||||
fireEvent.press(screen.getByText(/Spara/i))
|
||||
|
||||
expect(RNCalendarEvents.requestPermissions).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('can save an event to the calendar', async () => {
|
||||
const screen = setup({
|
||||
event: {
|
||||
...defaultEvent,
|
||||
location: null,
|
||||
description: null,
|
||||
},
|
||||
})
|
||||
|
||||
fireEvent.press(screen.getByTestId('actionsButton'))
|
||||
fireEvent.press(screen.getByText(/Spara/i))
|
||||
await RNCalendarEvents.requestPermissions()
|
||||
|
||||
expect(RNCalendarEvents.saveEvent).toHaveBeenCalledWith('Utvecklingssamtal', {
|
||||
startDate: '2021-06-19T11:00:00.000Z',
|
||||
endDate: '2021-06-19T12:00:00.000Z',
|
||||
})
|
||||
})
|
||||
|
||||
test('removes any null values from the event', async () => {
|
||||
const screen = setup()
|
||||
|
||||
fireEvent.press(screen.getByTestId('actionsButton'))
|
||||
fireEvent.press(screen.getByText(/Spara/i))
|
||||
await RNCalendarEvents.requestPermissions()
|
||||
|
||||
expect(RNCalendarEvents.saveEvent).toHaveBeenCalledWith('Utvecklingssamtal', {
|
||||
startDate: '2021-06-19T11:00:00.000Z',
|
||||
endDate: '2021-06-19T12:00:00.000Z',
|
||||
location: 'Gubbängsskolan',
|
||||
})
|
||||
})
|
||||
|
||||
test('calls toast with success', async () => {
|
||||
const screen = setup()
|
||||
|
||||
fireEvent.press(screen.getByTestId('actionsButton'))
|
||||
fireEvent.press(screen.getByText(/Spara/i))
|
||||
await RNCalendarEvents.requestPermissions()
|
||||
await RNCalendarEvents.saveEvent()
|
||||
|
||||
expect(Toast.showWithGravity).toHaveBeenCalledWith(
|
||||
'✔️ Sparad till kalender',
|
||||
'short',
|
||||
'bottom'
|
||||
)
|
||||
})
|
||||
|
||||
test('says if something goes wrong', async () => {
|
||||
const screen = setup()
|
||||
RNCalendarEvents.saveEvent.mockRejectedValueOnce()
|
||||
|
||||
fireEvent.press(screen.getByTestId('actionsButton'))
|
||||
fireEvent.press(screen.getByText(/Spara/i))
|
||||
await RNCalendarEvents.requestPermissions()
|
||||
await RNCalendarEvents.saveEvent()
|
||||
|
||||
expect(Toast.showWithGravity).toHaveBeenCalledWith(
|
||||
'Något gick fel',
|
||||
'short',
|
||||
'bottom'
|
||||
)
|
||||
})
|
||||
|
||||
test('tells user if they havent authorized calendar', async () => {
|
||||
const screen = setup()
|
||||
RNCalendarEvents.requestPermissions.mockResolvedValueOnce('not auth')
|
||||
|
||||
fireEvent.press(screen.getByTestId('actionsButton'))
|
||||
fireEvent.press(screen.getByText(/Spara/i))
|
||||
await RNCalendarEvents.requestPermissions()
|
||||
await RNCalendarEvents.saveEvent()
|
||||
|
||||
expect(Toast.showWithGravity).toHaveBeenCalledWith(
|
||||
'Du måste godkänna åtkomst till kalendern',
|
||||
'short',
|
||||
'bottom'
|
||||
)
|
||||
})
|
|
@ -0,0 +1,280 @@
|
|||
import { RouteProp, useRoute } from '@react-navigation/native'
|
||||
import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
|
||||
import { useUser } from '../libs/hooks/src'
|
||||
import {
|
||||
Button,
|
||||
CheckBox,
|
||||
Input,
|
||||
StyleService,
|
||||
Text,
|
||||
useStyleSheet,
|
||||
} from '@ui-kitten/components'
|
||||
import { Formik } from 'formik'
|
||||
import moment from 'moment'
|
||||
import Personnummer from 'personnummer'
|
||||
import React, { useCallback } from 'react'
|
||||
import { View } from 'react-native'
|
||||
import DateTimePickerModal from 'react-native-modal-datetime-picker'
|
||||
import * as Yup from 'yup'
|
||||
import { defaultStackStyling } from '../design/navigationThemes'
|
||||
// import usePersonalStorage from '../hooks/usePersonalStorage';
|
||||
import useSettingsStorage from '../hooks/useSettingsStorage'
|
||||
import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
|
||||
import { studentName } from '../utils/peopleHelpers'
|
||||
import { useSMS } from '../utils/SMS'
|
||||
import { translate } from '../utils/translation'
|
||||
import { AlertIcon } from './icon.component'
|
||||
import { RootStackParamList } from './navigation.component'
|
||||
import { NavigationTitle } from './navigationTitle.component'
|
||||
|
||||
type AbsenceRouteProps = RouteProp<RootStackParamList, 'Absence'>
|
||||
|
||||
interface AbsenceFormValues {
|
||||
displayStartTimePicker: boolean
|
||||
displayEndTimePicker: boolean
|
||||
personalIdentityNumber: string
|
||||
isFullDay: boolean
|
||||
startTime: moment.Moment
|
||||
endTime: moment.Moment
|
||||
}
|
||||
|
||||
export const absenceRouteOptions =
|
||||
(darkMode: boolean) =>
|
||||
({
|
||||
route,
|
||||
}: {
|
||||
route: RouteProp<RootStackParamList, 'Absence'>
|
||||
}): NativeStackNavigationOptions => {
|
||||
const child = route.params.child
|
||||
return {
|
||||
...defaultStackStyling(darkMode),
|
||||
headerTitle: () => (
|
||||
<NavigationTitle
|
||||
title={translate('abscense.title')}
|
||||
subtitle={studentName(child?.name)}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const Absence = () => {
|
||||
const AbsenceSchema = Yup.object().shape({
|
||||
personalIdentityNumber: Yup.string()
|
||||
.required(translate('abscense.personalNumberMissing'))
|
||||
.test('is-valid', translate('abscense.invalidPersonalNumber'), (value) =>
|
||||
value ? Personnummer.valid(value) : true
|
||||
),
|
||||
isFullDay: Yup.bool().required(),
|
||||
})
|
||||
|
||||
const { data: user } = useUser()
|
||||
const route = useRoute<AbsenceRouteProps>()
|
||||
const { sendSMS } = useSMS()
|
||||
const { child } = route.params
|
||||
const [personalIdentityNumber, setPersonalIdentityNumber] = React.useState('')
|
||||
const [personalIdsFromStorage, setPersonalIdInStorage] = useSettingsStorage(
|
||||
'childPersonalIdentityNumber'
|
||||
)
|
||||
const personalIdKey = `@childPersonalIdNumber.${child.id}`
|
||||
const minumumDate = moment().hours(8).minute(0)
|
||||
const maximumDate = moment().hours(17).minute(0)
|
||||
const styles = useStyleSheet(themedStyles)
|
||||
|
||||
const submit = useCallback(
|
||||
async (values: AbsenceFormValues) => {
|
||||
const personalIdNumber = Personnummer.parse(
|
||||
values.personalIdentityNumber
|
||||
).format()
|
||||
|
||||
if (values.isFullDay) {
|
||||
sendSMS(personalIdNumber)
|
||||
} else {
|
||||
sendSMS(
|
||||
`${personalIdNumber} ${moment(values.startTime).format(
|
||||
'HHmm'
|
||||
)}-${moment(values.endTime).format('HHmm')}`
|
||||
)
|
||||
}
|
||||
|
||||
const toStore = {
|
||||
...personalIdsFromStorage,
|
||||
...{ [personalIdKey]: personalIdNumber },
|
||||
}
|
||||
setPersonalIdInStorage(toStore)
|
||||
},
|
||||
[personalIdKey, personalIdsFromStorage, sendSMS, setPersonalIdInStorage]
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
const personalIdFromStorage = personalIdsFromStorage[personalIdKey] || ''
|
||||
setPersonalIdentityNumber(personalIdFromStorage || '')
|
||||
}, [child, personalIdKey, personalIdsFromStorage, user])
|
||||
|
||||
const initialValues: AbsenceFormValues = {
|
||||
displayStartTimePicker: false,
|
||||
displayEndTimePicker: false,
|
||||
personalIdentityNumber: personalIdentityNumber || '',
|
||||
isFullDay: true,
|
||||
startTime: moment().hours(Math.max(8, new Date().getHours())).minute(0),
|
||||
endTime: maximumDate,
|
||||
}
|
||||
|
||||
return (
|
||||
<Formik
|
||||
enableReinitialize
|
||||
validationSchema={AbsenceSchema}
|
||||
initialValues={initialValues}
|
||||
onSubmit={submit}
|
||||
>
|
||||
{({
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
setFieldValue,
|
||||
values,
|
||||
touched,
|
||||
errors,
|
||||
}) => {
|
||||
const hasError = (field: keyof typeof values) =>
|
||||
errors[field] && touched[field]
|
||||
|
||||
return (
|
||||
<View style={styles.wrap}>
|
||||
<View style={styles.field}>
|
||||
<Text style={styles.label}>
|
||||
{translate('abscense.childsPersonalNumber')}
|
||||
</Text>
|
||||
<Input
|
||||
testID="personalIdentityNumberInput"
|
||||
keyboardType="number-pad"
|
||||
onChangeText={handleChange('personalIdentityNumber')}
|
||||
onBlur={handleBlur('personalIdentityNumber')}
|
||||
status={hasError('personalIdentityNumber') ? 'danger' : 'basic'}
|
||||
value={values.personalIdentityNumber}
|
||||
style={styles.input}
|
||||
placeholder="YYYYMMDD-XXXX"
|
||||
accessoryRight={
|
||||
hasError('personalIdentityNumber') ? AlertIcon : undefined
|
||||
}
|
||||
/>
|
||||
{hasError('personalIdentityNumber') && (
|
||||
<Text style={styles.error}>
|
||||
{errors.personalIdentityNumber}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.field}>
|
||||
<CheckBox
|
||||
checked={values.isFullDay}
|
||||
onChange={(checked) => setFieldValue('isFullDay', checked)}
|
||||
>
|
||||
{translate('abscense.entireDay')}
|
||||
</CheckBox>
|
||||
</View>
|
||||
{!values.isFullDay && (
|
||||
<View style={styles.partOfDay}>
|
||||
<View style={styles.inputHalf}>
|
||||
<Text style={styles.label}>
|
||||
{translate('abscense.startTime')}
|
||||
</Text>
|
||||
<Button
|
||||
status="basic"
|
||||
style={styles.pickerButton}
|
||||
onPress={() =>
|
||||
setFieldValue('displayStartTimePicker', true)
|
||||
}
|
||||
>
|
||||
{moment(values.startTime).format('LT')}
|
||||
</Button>
|
||||
<DateTimePickerModal
|
||||
cancelTextIOS={translate('general.cancel')}
|
||||
confirmTextIOS={translate('general.confirm')}
|
||||
date={moment(values.startTime).toDate()}
|
||||
isVisible={values.displayStartTimePicker}
|
||||
locale="sv-SE"
|
||||
maximumDate={maximumDate.toDate()}
|
||||
minimumDate={minumumDate.toDate()}
|
||||
minuteInterval={10}
|
||||
mode="time"
|
||||
onConfirm={(date) => {
|
||||
setFieldValue('startTime', date)
|
||||
setFieldValue('displayStartTimePicker', false)
|
||||
}}
|
||||
onCancel={() =>
|
||||
setFieldValue('displayStartTimePicker', false)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.spacer} />
|
||||
<View style={styles.inputHalf}>
|
||||
<Text style={styles.label}>
|
||||
{translate('abscense.endTime')}
|
||||
</Text>
|
||||
<Button
|
||||
status="basic"
|
||||
style={styles.pickerButton}
|
||||
onPress={() => setFieldValue('displayEndTimePicker', true)}
|
||||
>
|
||||
{moment(values.endTime).format('LT')}
|
||||
</Button>
|
||||
<DateTimePickerModal
|
||||
cancelTextIOS={translate('general.cancel')}
|
||||
confirmTextIOS={translate('general.confirm')}
|
||||
date={moment(values.endTime).toDate()}
|
||||
isVisible={values.displayEndTimePicker}
|
||||
// Todo fix this
|
||||
locale="sv-SE"
|
||||
maximumDate={maximumDate.toDate()}
|
||||
minimumDate={minumumDate.toDate()}
|
||||
minuteInterval={10}
|
||||
mode="time"
|
||||
onConfirm={(date) => {
|
||||
setFieldValue('endTime', date)
|
||||
setFieldValue('displayEndTimePicker', false)
|
||||
}}
|
||||
onCancel={() =>
|
||||
setFieldValue('displayEndTimePicker', false)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<Button onPress={() => handleSubmit()} status="primary">
|
||||
{translate('general.send')}
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}}
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
|
||||
export default Absence
|
||||
|
||||
const themedStyles = StyleService.create({
|
||||
wrap: {
|
||||
...LayoutStyle.flex.full,
|
||||
padding: Sizing.t4,
|
||||
backgroundColor: 'background-basic-color-2',
|
||||
},
|
||||
field: { marginBottom: Sizing.t4 },
|
||||
partOfDay: { ...LayoutStyle.flex.row, marginBottom: Sizing.t4 },
|
||||
spacer: { width: Sizing.t2 },
|
||||
inputHalf: { ...LayoutStyle.flex.full },
|
||||
input: {
|
||||
backgroundColor: 'background-basic-color-1',
|
||||
borderColor: 'color-input-border',
|
||||
},
|
||||
// TODO: Refactor to use mapping.json in eva design
|
||||
pickerButton: {
|
||||
backgroundColor: 'background-basic-color-1',
|
||||
},
|
||||
label: {
|
||||
...Typography.fontSize.sm,
|
||||
...Typography.fontWeight.bold,
|
||||
marginBottom: Sizing.t2,
|
||||
},
|
||||
error: {
|
||||
color: 'color-primary-600',
|
||||
},
|
||||
})
|
|
@ -0,0 +1,166 @@
|
|||
import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import {
|
||||
StyleService,
|
||||
Text,
|
||||
useStyleSheet,
|
||||
useTheme,
|
||||
} from '@ui-kitten/components'
|
||||
import React from 'react'
|
||||
import {
|
||||
Image,
|
||||
ImageStyle,
|
||||
Keyboard,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import { useTranslation } from '../hooks/useTranslation'
|
||||
import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
|
||||
import { fontSize } from '../styles/typography'
|
||||
import { KeyboardAvoidingView } from '../ui/keyboardAvoidingView.component'
|
||||
import { SafeAreaView } from '../ui/safeAreaView.component'
|
||||
import { SettingsIcon } from './icon.component'
|
||||
import { Login } from './login.component'
|
||||
import { RootStackParamList } from './navigation.component'
|
||||
|
||||
const randomWord = (
|
||||
t: (scope: I18n.Scope, options?: I18n.TranslateOptions | undefined) => string
|
||||
) => {
|
||||
const words = t('auth.words')
|
||||
const keys = Object.keys(words)
|
||||
|
||||
const randomIndex: number = Math.floor(Math.random() * keys.length)
|
||||
const argumentKey: string = keys[randomIndex]
|
||||
|
||||
return words[argumentKey]
|
||||
}
|
||||
|
||||
interface AuthProps {
|
||||
navigation: StackNavigationProp<RootStackParamList, 'Login'>
|
||||
}
|
||||
|
||||
export const authRouteOptions = (): NativeStackNavigationOptions => {
|
||||
return {
|
||||
headerShown: false,
|
||||
animationTypeForReplace: 'push',
|
||||
animation: 'fade',
|
||||
}
|
||||
}
|
||||
|
||||
export const Auth: React.FC<AuthProps> = ({ navigation }) => {
|
||||
const styles = useStyleSheet(themeStyles)
|
||||
const colors = useTheme()
|
||||
const { t } = useTranslation()
|
||||
// const t = (key: string) => key;
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View style={LayoutStyle.flex.full}>
|
||||
<TouchableOpacity
|
||||
style={styles.settingsLink}
|
||||
onPress={() => navigation.navigate('Settings')}
|
||||
accessibilityHint={t(
|
||||
'auth.a11y_navigate_to_settings'
|
||||
// defaultValue: 'Navigerar till vyn för inställningar',
|
||||
)}
|
||||
accessibilityLabel={t(
|
||||
'auth.a11y_settings'
|
||||
// {
|
||||
// // defaultValue: 'Inställningar',
|
||||
// }
|
||||
)}
|
||||
>
|
||||
<View style={styles.language}>
|
||||
<SettingsIcon
|
||||
height={28}
|
||||
width={28}
|
||||
fill={colors['color-primary-500']}
|
||||
/>
|
||||
<Text style={styles.languageText}>{t('general.settings')}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<KeyboardAvoidingView>
|
||||
<View style={styles.content}>
|
||||
<View style={styles.imageWrapper}>
|
||||
<Image
|
||||
source={require('../assets/boys.png')}
|
||||
style={styles.image as ImageStyle}
|
||||
accessibilityHint={t(
|
||||
'login.a11y_image_two_boys'
|
||||
// {
|
||||
// defaultValue: 'Bild på två personer som kollar i mobilen',
|
||||
// }
|
||||
)}
|
||||
resizeMode="contain"
|
||||
accessibilityIgnoresInvertColors={false}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.container}>
|
||||
<Text
|
||||
category="h1"
|
||||
style={styles.header}
|
||||
adjustsFontSizeToFit
|
||||
numberOfLines={2}
|
||||
>
|
||||
Öppna skolplattformen
|
||||
</Text>
|
||||
<Login />
|
||||
<Text category="c2" style={styles.subtitle}>
|
||||
{t('auth.subtitle', {
|
||||
word: randomWord(t),
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
const themeStyles = StyleService.create({
|
||||
container: {
|
||||
...LayoutStyle.mainAxis.flexStart,
|
||||
...LayoutStyle.crossAxis.flexEnd,
|
||||
padding: Sizing.t6,
|
||||
},
|
||||
imageWrapper: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
image: {
|
||||
...Sizing.aspectRatio(1.5, Sizing.Ratio['4:3']),
|
||||
},
|
||||
content: {
|
||||
...LayoutStyle.flex.full,
|
||||
},
|
||||
header: {
|
||||
width: '100%',
|
||||
marginBottom: Sizing.t5,
|
||||
fontFamily: 'Poppins-Black',
|
||||
fontWeight: '900',
|
||||
},
|
||||
subtitle: {
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
...Typography.fontSize.xs,
|
||||
marginTop: Sizing.t5,
|
||||
},
|
||||
language: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: Sizing.t3,
|
||||
paddingLeft: Sizing.t5,
|
||||
},
|
||||
languageText: {
|
||||
...fontSize.sm,
|
||||
marginLeft: Sizing.t1,
|
||||
},
|
||||
settingsLink: {
|
||||
alignSelf: 'flex-start',
|
||||
zIndex: 1,
|
||||
},
|
||||
})
|
|
@ -0,0 +1,114 @@
|
|||
import { useCalendar } from '../libs/hooks/src'
|
||||
import { CalendarItem } from '@skolplattformen/api'
|
||||
import {
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
StyleService,
|
||||
Text,
|
||||
useStyleSheet,
|
||||
} from '@ui-kitten/components'
|
||||
import moment from 'moment'
|
||||
import React from 'react'
|
||||
import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
|
||||
import { ListRenderItemInfo, RefreshControl, View } from 'react-native'
|
||||
|
||||
import { translate } from '../utils/translation'
|
||||
import { useChild } from './childContext.component'
|
||||
import { CalendarOutlineIcon } from './icon.component'
|
||||
import { SaveToCalendar } from './saveToCalendar.component'
|
||||
import { Week } from './week.component'
|
||||
|
||||
// const translate = (key: string) => key;
|
||||
|
||||
export const Calendar = () => {
|
||||
const child = useChild()
|
||||
const { data, status, reload } = useCalendar(child)
|
||||
const styles = useStyleSheet(themedStyles)
|
||||
|
||||
const formatStartDate = (startDate: moment.MomentInput) => {
|
||||
const date = moment(startDate)
|
||||
const output = `${date.format('dddd')} ${date.format(
|
||||
'll'
|
||||
)} • ${date.fromNow()}`
|
||||
|
||||
// Hack to remove year if it is this year
|
||||
const currentYear = moment().year().toString(10)
|
||||
return output.replace(currentYear, '')
|
||||
}
|
||||
|
||||
const sortedData = () => {
|
||||
if (!data) {
|
||||
return []
|
||||
}
|
||||
|
||||
return data.sort((a, b) =>
|
||||
a.startDate && b.startDate ? a.startDate.localeCompare(b.startDate) : 0
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Week child={child} />
|
||||
<List
|
||||
data={sortedData()}
|
||||
ItemSeparatorComponent={Divider}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyState}>
|
||||
<Text style={styles.emptyStateHeadline} category="h6">
|
||||
{translate('calender.emptyHeadline')}
|
||||
</Text>
|
||||
<Text style={styles.emptyStateDescription}>
|
||||
{translate('calender.emptyText')}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
renderItem={({ item }: ListRenderItemInfo<CalendarItem>) => (
|
||||
<ListItem
|
||||
disabled={true}
|
||||
title={`${item.title}`}
|
||||
description={(props) => (
|
||||
<Text style={[props?.style, styles.description]}>
|
||||
{formatStartDate(item.startDate)}
|
||||
</Text>
|
||||
)}
|
||||
accessoryLeft={CalendarOutlineIcon}
|
||||
accessoryRight={() => <SaveToCalendar event={item} />}
|
||||
/>
|
||||
)}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={status === 'loading'}
|
||||
onRefresh={reload}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const themedStyles = StyleService.create({
|
||||
container: {
|
||||
backgroundColor: 'background-basic-color-1',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
},
|
||||
description: {
|
||||
...Typography.fontSize.xs,
|
||||
color: 'text-hint-color',
|
||||
},
|
||||
emptyState: {
|
||||
...LayoutStyle.center,
|
||||
...LayoutStyle.flex.full,
|
||||
},
|
||||
emptyStateHeadline: {
|
||||
...Typography.align.center,
|
||||
margin: Sizing.t4,
|
||||
},
|
||||
emptyStateDescription: {
|
||||
...Typography.align.center,
|
||||
lineHeight: 21,
|
||||
paddingHorizontal: Sizing.t3,
|
||||
margin: Sizing.t4,
|
||||
},
|
||||
})
|
|
@ -0,0 +1,216 @@
|
|||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
||||
import {
|
||||
getFocusedRouteNameFromRoute,
|
||||
RouteProp,
|
||||
useNavigation,
|
||||
useRoute,
|
||||
} from '@react-navigation/native'
|
||||
import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
|
||||
// import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import { Icon } from '@ui-kitten/components'
|
||||
import React, { useEffect } from 'react'
|
||||
// import {StyleProp, TextProps} from 'react-native';
|
||||
import { defaultStackStyling } from '../design/navigationThemes'
|
||||
import { useFeature } from '../hooks/useFeature'
|
||||
import { studentName } from '../utils/peopleHelpers'
|
||||
import { translate } from '../utils/translation'
|
||||
import { Calendar } from './calendar.component'
|
||||
import { ChildProvider } from './childContext.component'
|
||||
import { Classmates } from './classmates.component'
|
||||
import { Menu } from './menu.component'
|
||||
import { RootStackParamList } from './navigation.component'
|
||||
import { NavigationTitle } from './navigationTitle.component'
|
||||
import { NewsList } from './newsList.component'
|
||||
import { NotificationsList } from './notificationsList.component'
|
||||
import { TabBarLabel } from './tabBarLabel.component'
|
||||
|
||||
// const translate = (key: string) => key;
|
||||
|
||||
// type ChildNavigationProp = StackNavigationProp<RootStackParamList, 'Child'>;
|
||||
type ChildRouteProps = RouteProp<RootStackParamList, 'Child'>
|
||||
|
||||
export type ChildTabParamList = {
|
||||
News: undefined
|
||||
Notifications: undefined
|
||||
Calendar: undefined
|
||||
Menu: undefined
|
||||
Classmates: undefined
|
||||
}
|
||||
|
||||
// interface TabTitleProps {
|
||||
// children: string;
|
||||
// style?: StyleProp<TextProps>;
|
||||
// }
|
||||
|
||||
const { Navigator, Screen } = createBottomTabNavigator<ChildTabParamList>()
|
||||
|
||||
const NewsScreen = () => <NewsList />
|
||||
const NotificationsScreen = () => <NotificationsList />
|
||||
const CalendarScreen = () => <Calendar />
|
||||
const MenuScreen = () => <Menu />
|
||||
const ClassmatesScreen = () => <Classmates />
|
||||
|
||||
interface ScreenSettings {
|
||||
NEWS_SCREEN: boolean
|
||||
NOTIFICATIONS_SCREEN: boolean
|
||||
CALENDER_SCREEN: boolean
|
||||
MENU_SCREEN: boolean
|
||||
CLASSMATES_SCREEN: boolean
|
||||
}
|
||||
|
||||
const TabNavigator = ({
|
||||
initialRouteName = 'News',
|
||||
screenSettings,
|
||||
}: {
|
||||
initialRouteName?: keyof ChildTabParamList
|
||||
screenSettings: ScreenSettings
|
||||
}) => (
|
||||
<Navigator
|
||||
initialRouteName={initialRouteName}
|
||||
screenOptions={({ route }) => {
|
||||
return {
|
||||
tabBarLabel: ({ focused }) => (
|
||||
<TabBarLabel
|
||||
label={getRouteTitleFromName(route.name)}
|
||||
focused={focused}
|
||||
/>
|
||||
),
|
||||
tabBarIcon: ({ focused, color }) => {
|
||||
let iconName = 'news'
|
||||
|
||||
if (route.name === 'News') {
|
||||
iconName = focused ? 'book-open' : 'book-open-outline'
|
||||
} else if (route.name === 'Notifications') {
|
||||
iconName = focused ? 'alert-circle' : 'alert-circle-outline'
|
||||
} else if (route.name === 'Calendar') {
|
||||
iconName = focused ? 'calendar' : 'calendar-outline'
|
||||
} else if (route.name === 'Menu') {
|
||||
iconName = focused ? 'clipboard' : 'clipboard-outline'
|
||||
} else if (route.name === 'Classmates') {
|
||||
iconName = focused ? 'people' : 'people-outline'
|
||||
}
|
||||
return <Icon name={iconName} fill={color} height={24} width={24} />
|
||||
},
|
||||
}
|
||||
}}
|
||||
>
|
||||
{screenSettings.NEWS_SCREEN && (
|
||||
<Screen
|
||||
name="News"
|
||||
component={NewsScreen}
|
||||
options={{ title: translate('navigation.news'), headerShown: false }}
|
||||
/>
|
||||
)}
|
||||
{screenSettings.NOTIFICATIONS_SCREEN && (
|
||||
<Screen
|
||||
name="Notifications"
|
||||
component={NotificationsScreen}
|
||||
options={{
|
||||
title: translate('navigation.notifications'),
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{screenSettings.CALENDER_SCREEN && (
|
||||
<Screen
|
||||
name="Calendar"
|
||||
component={CalendarScreen}
|
||||
options={{
|
||||
title: translate('navigation.calender'),
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{screenSettings.MENU_SCREEN && (
|
||||
<Screen
|
||||
name="Menu"
|
||||
component={MenuScreen}
|
||||
options={{ title: translate('navigation.menu'), headerShown: false }}
|
||||
/>
|
||||
)}
|
||||
{screenSettings.CLASSMATES_SCREEN && (
|
||||
<Screen
|
||||
name="Classmates"
|
||||
component={ClassmatesScreen}
|
||||
options={{
|
||||
title: translate('navigation.classmates'),
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Navigator>
|
||||
)
|
||||
|
||||
const getHeaderTitle = (route: any) => {
|
||||
const routeName =
|
||||
getFocusedRouteNameFromRoute(route) ??
|
||||
route.params.initialRouteName ??
|
||||
'News'
|
||||
return getRouteTitleFromName(routeName)
|
||||
}
|
||||
|
||||
const getRouteTitleFromName = (routeName: string) => {
|
||||
switch (routeName) {
|
||||
case 'News':
|
||||
return translate('navigation.news')
|
||||
case 'Notifications':
|
||||
return translate('navigation.notifications')
|
||||
case 'Calendar':
|
||||
return translate('navigation.calender')
|
||||
case 'Menu':
|
||||
return translate('navigation.menu')
|
||||
case 'Classmates':
|
||||
return translate('navigation.classmates')
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export const childRouteOptions =
|
||||
(darkMode: boolean) =>
|
||||
({
|
||||
route,
|
||||
}: {
|
||||
route: RouteProp<RootStackParamList, 'Child'>
|
||||
}): NativeStackNavigationOptions => {
|
||||
const { child } = route.params
|
||||
|
||||
return {
|
||||
...defaultStackStyling(darkMode),
|
||||
headerTitle: () => (
|
||||
<NavigationTitle
|
||||
title={getHeaderTitle(route)}
|
||||
subtitle={studentName(child?.name)}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export const Child = () => {
|
||||
const route = useRoute<ChildRouteProps>()
|
||||
const { child, initialRouteName } = route.params
|
||||
const useFoodMenu = useFeature('FOOD_MENU')
|
||||
const useClassList = useFeature('CLASS_LIST')
|
||||
|
||||
const navigation = useNavigation()
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({ title: getHeaderTitle(route) })
|
||||
}, [navigation, route])
|
||||
|
||||
const screenSettings: ScreenSettings = {
|
||||
NEWS_SCREEN: true,
|
||||
NOTIFICATIONS_SCREEN: true,
|
||||
CALENDER_SCREEN: true,
|
||||
MENU_SCREEN: useFoodMenu,
|
||||
CLASSMATES_SCREEN: useClassList,
|
||||
}
|
||||
return (
|
||||
<ChildProvider child={child}>
|
||||
<TabNavigator
|
||||
screenSettings={screenSettings}
|
||||
initialRouteName={initialRouteName as keyof ChildTabParamList}
|
||||
/>
|
||||
</ChildProvider>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { Child } from '@skolplattformen/api'
|
||||
import React, { createContext, useContext } from 'react'
|
||||
|
||||
interface ChildProviderProps {
|
||||
child: Child
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const ChildContext = createContext<Child>({
|
||||
id: '',
|
||||
sdsId: '',
|
||||
name: '',
|
||||
})
|
||||
|
||||
export const ChildProvider = ({ child, children }: ChildProviderProps) => {
|
||||
return <ChildContext.Provider value={child}>{children}</ChildContext.Provider>
|
||||
}
|
||||
|
||||
export const useChild = () => useContext(ChildContext)
|
|
@ -0,0 +1,337 @@
|
|||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { Child } from '@skolplattformen/api'
|
||||
import {
|
||||
useCalendar,
|
||||
useClassmates,
|
||||
useMenu,
|
||||
useNews,
|
||||
useNotifications,
|
||||
useSchedule,
|
||||
} from '../libs/hooks/src'
|
||||
import {
|
||||
Button,
|
||||
StyleService,
|
||||
Text,
|
||||
useStyleSheet,
|
||||
} from '@ui-kitten/components'
|
||||
import moment, { Moment } from 'moment'
|
||||
import React, { useEffect } from 'react'
|
||||
import { Pressable, useColorScheme, View } from 'react-native'
|
||||
import { useTranslation } from '../hooks/useTranslation'
|
||||
import { Colors, Layout, Sizing } from '../styles'
|
||||
import { getMeaningfulStartingDate } from '../utils/calendarHelpers'
|
||||
import { studentName } from '../utils/peopleHelpers'
|
||||
import { DaySummary } from './daySummary.component'
|
||||
import { AlertIcon, RightArrowIcon } from './icon.component'
|
||||
import { RootStackParamList } from './navigation.component'
|
||||
import { StudentAvatar } from './studentAvatar.component'
|
||||
|
||||
interface ChildListItemProps {
|
||||
child: Child
|
||||
color: string
|
||||
updated: string
|
||||
currentDate?: Moment
|
||||
}
|
||||
type ChildListItemNavigationProp = StackNavigationProp<
|
||||
RootStackParamList,
|
||||
'Children'
|
||||
>
|
||||
|
||||
export const ChildListItem = ({
|
||||
child,
|
||||
color,
|
||||
updated,
|
||||
currentDate = moment(),
|
||||
}: ChildListItemProps) => {
|
||||
// Forces rerender when child.id changes
|
||||
React.useEffect(() => {
|
||||
// noop
|
||||
}, [child.id])
|
||||
|
||||
const navigation = useNavigation<ChildListItemNavigationProp>()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: notifications, reload: notificationsReload } =
|
||||
useNotifications(child)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { data: news, status: newsStatus, reload: newsReload } = useNews(child)
|
||||
const { data: classmates, reload: classmatesReload } = useClassmates(child)
|
||||
const { data: calendar, reload: calendarReload } = useCalendar(child)
|
||||
const { data: menu, reload: menuReload } = useMenu(child)
|
||||
const { data: schedule, reload: scheduleReload } = useSchedule(
|
||||
child,
|
||||
moment(currentDate).toISOString(),
|
||||
moment(currentDate).add(7, 'days').toISOString()
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
// Do not refresh if updated is empty (first render of component)
|
||||
if (updated === '') {
|
||||
return
|
||||
}
|
||||
|
||||
newsReload()
|
||||
classmatesReload()
|
||||
notificationsReload()
|
||||
calendarReload()
|
||||
menuReload()
|
||||
scheduleReload()
|
||||
|
||||
// Without eslint-disable below we get into a forever loop
|
||||
// because the function pointers to reload functions change on every reload.
|
||||
// I do not know a workaround for this.
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [updated])
|
||||
|
||||
const notificationsThisWeek = notifications.filter(
|
||||
({ dateCreated, dateModified }) => {
|
||||
const date = dateModified || dateCreated
|
||||
return date ? moment(date).isSame(moment(), 'week') : false
|
||||
}
|
||||
)
|
||||
|
||||
const newsThisWeek = news.filter(({ modified, published }) => {
|
||||
const newsDate = modified || published
|
||||
return newsDate ? moment(newsDate).isSame(currentDate, 'week') : false
|
||||
})
|
||||
|
||||
const scheduleAndCalendarThisWeek = [
|
||||
...(calendar ?? []),
|
||||
...(schedule ?? []),
|
||||
].filter(({ startDate }) =>
|
||||
startDate
|
||||
? moment(startDate).isBetween(
|
||||
moment(currentDate).startOf('day'),
|
||||
moment(currentDate).add(7, 'days')
|
||||
)
|
||||
: false
|
||||
)
|
||||
|
||||
const displayDate = (inputDate: moment.MomentInput) => {
|
||||
return moment(inputDate).fromNow()
|
||||
}
|
||||
|
||||
const getClassName = () => {
|
||||
// hack: we can find the class name (ex. 8C) from the classmates.
|
||||
// let's pick the first one and select theirs class
|
||||
// hack 2: we can find school namn in skola24 if child data is there
|
||||
if (classmates.length > 0) {
|
||||
return (
|
||||
classmates[0].className +
|
||||
(child.schoolID == null ? '' : ' • ' + child.schoolID)
|
||||
)
|
||||
}
|
||||
|
||||
// Taken from Skolverket
|
||||
// https://www.skolverket.se/skolutveckling/anordna-och-administrera-utbildning/administrera-utbildning/skoltermer-pa-engelska
|
||||
const abbrevations = {
|
||||
G: t('abbrevations.upperSecondarySchool'),
|
||||
GR: t('abbrevations.compulsorySchool'),
|
||||
F: t('abbrevations.leisureTimeCentre'),
|
||||
FS: t('abbrevations.preSchool'),
|
||||
}
|
||||
|
||||
return child.status
|
||||
? child.status
|
||||
.split(';')
|
||||
.map((status) => {
|
||||
const statusAsAbbreviation = status as keyof typeof abbrevations
|
||||
|
||||
return abbrevations[statusAsAbbreviation] || status
|
||||
})
|
||||
.join(', ')
|
||||
: null
|
||||
}
|
||||
|
||||
const className = getClassName()
|
||||
const styles = useStyleSheet(themeStyles)
|
||||
const isDarkMode = useColorScheme() === 'dark'
|
||||
const meaningfulStartingDate = getMeaningfulStartingDate(currentDate)
|
||||
|
||||
// Hide menu if we want to show monday but it is not monday yet.
|
||||
// The menu for next week is not available until monday
|
||||
const shouldShowLunchMenu =
|
||||
menu[meaningfulStartingDate.isoWeekday() - 1] &&
|
||||
!(
|
||||
meaningfulStartingDate.isoWeekday() === 1 &&
|
||||
currentDate.isoWeekday() !== 1
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardHeader}>
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
styles.cardHeaderLeft || {},
|
||||
{ opacity: pressed ? 0.5 : 1 },
|
||||
]}
|
||||
onPress={() => navigation.navigate('Child', { child, color })}
|
||||
>
|
||||
<View style={styles.cardHeaderLeft}>
|
||||
<StudentAvatar name={studentName(child.name)} color={color} />
|
||||
<View style={styles.cardHeaderText}>
|
||||
<Text category="h6">{studentName(child.name)}</Text>
|
||||
{className ? <Text category="s1">{className}</Text> : null}
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.cardHeaderRight}>
|
||||
<RightArrowIcon
|
||||
style={styles.icon}
|
||||
fill={
|
||||
isDarkMode ? Colors.neutral.gray200 : Colors.neutral.gray800
|
||||
}
|
||||
name="star"
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
<Pressable
|
||||
style={({ pressed }) => ['' || {}, { opacity: pressed ? 0.5 : 1 }]}
|
||||
onPress={() =>
|
||||
navigation.navigate('Child', {
|
||||
child,
|
||||
color,
|
||||
initialRouteName: 'Calendar',
|
||||
})
|
||||
}
|
||||
>
|
||||
<DaySummary child={child} date={meaningfulStartingDate} />
|
||||
|
||||
{scheduleAndCalendarThisWeek.slice(0, 3).map((calendarItem, i) => (
|
||||
<Text category="p1" key={i}>
|
||||
{`${calendarItem.title} (${displayDate(calendarItem.startDate)})`}
|
||||
</Text>
|
||||
))}
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={({ pressed }) => ['' || {}, { opacity: pressed ? 0.5 : 1 }]}
|
||||
onPress={() =>
|
||||
navigation.navigate('Child', {
|
||||
child,
|
||||
color,
|
||||
initialRouteName: 'News',
|
||||
})
|
||||
}
|
||||
>
|
||||
<Text category="c2" style={styles.label}>
|
||||
{t('navigation.news')}
|
||||
</Text>
|
||||
{notificationsThisWeek.slice(0, 3).map((notification, i) => (
|
||||
<Text category="p1" key={i}>
|
||||
{notification.message}
|
||||
</Text>
|
||||
))}
|
||||
{newsThisWeek.slice(0, 3).map((newsItem, i) => (
|
||||
<Text category="p1" key={i}>
|
||||
{newsItem.header ?? ''}
|
||||
</Text>
|
||||
))}
|
||||
</Pressable>
|
||||
|
||||
{scheduleAndCalendarThisWeek.length ||
|
||||
notificationsThisWeek.length ||
|
||||
newsThisWeek.length ? null : (
|
||||
<Text category="p1" style={styles.noNewNewsItemsText}>
|
||||
{t('news.noNewNewsItemsThisWeek')}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{shouldShowLunchMenu ? (
|
||||
<Pressable
|
||||
style={({ pressed }) => ['' || {}, { opacity: pressed ? 0.5 : 1 }]}
|
||||
onPress={() =>
|
||||
navigation.navigate('Child', {
|
||||
child,
|
||||
color,
|
||||
initialRouteName: 'Menu',
|
||||
})
|
||||
}
|
||||
>
|
||||
<Text category="c2" style={styles.label}>
|
||||
{meaningfulStartingDate.format(
|
||||
'[' + t('schedule.lunch') + '] dddd'
|
||||
)}
|
||||
</Text>
|
||||
<Text>
|
||||
{menu[meaningfulStartingDate.isoWeekday() - 1]?.description}
|
||||
</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
|
||||
<View style={styles.itemFooter}>
|
||||
<Button
|
||||
accessible
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`${child.name}, ${t('abscense.title')}`}
|
||||
appearance="ghost"
|
||||
accessoryLeft={AlertIcon}
|
||||
status="primary"
|
||||
style={styles.absenceButton}
|
||||
onPress={() => navigation.navigate('Absence', { child })}
|
||||
>
|
||||
{t('abscense.title')}
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const themeStyles = StyleService.create({
|
||||
card: {
|
||||
borderRadius: 25,
|
||||
padding: Sizing.t5,
|
||||
marginBottom: Sizing.t4,
|
||||
backgroundColor: 'background-basic-color-1',
|
||||
},
|
||||
cardHeader: {
|
||||
...Layout.flex.row,
|
||||
...Layout.mainAxis.center,
|
||||
...Layout.crossAxis.spaceBetween,
|
||||
marginBottom: Sizing.t4,
|
||||
},
|
||||
cardHeaderLeft: {
|
||||
...Layout.flex.row,
|
||||
...Layout.mainAxis.center,
|
||||
flex: 10,
|
||||
},
|
||||
cardHeaderRight: {
|
||||
...Layout.flex.row,
|
||||
...Layout.crossAxis.flexEnd,
|
||||
flex: 1,
|
||||
},
|
||||
cardHeaderText: {
|
||||
marginHorizontal: Sizing.t4,
|
||||
flex: 10,
|
||||
},
|
||||
icon: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
},
|
||||
label: {
|
||||
marginTop: 10,
|
||||
},
|
||||
itemFooter: {
|
||||
...Layout.flex.row,
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-end',
|
||||
marginTop: Sizing.t4,
|
||||
},
|
||||
absenceButton: {
|
||||
marginLeft: -20,
|
||||
},
|
||||
itemFooterSpinner: {
|
||||
alignSelf: 'flex-end',
|
||||
},
|
||||
item: {
|
||||
marginRight: 12,
|
||||
paddingHorizontal: 2,
|
||||
paddingVertical: 0,
|
||||
marginBottom: 0,
|
||||
},
|
||||
noNewNewsItemsText: {},
|
||||
})
|