Compare commits
9 Commits
main
...
feature/ba
| Author | SHA1 | Date | |
|---|---|---|---|
| 44da4932aa | |||
| c2f89b1b09 | |||
| 3a118a9b34 | |||
| a98709b0a1 | |||
| a1ba2f9571 | |||
| 55be2f1e88 | |||
| 1e5b17c4ef | |||
| 204f1a3e1d | |||
| 27119c798d |
@@ -1,17 +1,25 @@
|
|||||||
# Ignore the node_modules directory
|
**/.dockerignore
|
||||||
node_modules
|
**/.env
|
||||||
|
**/.git
|
||||||
# Ignore the dist directory
|
**/.gitignore
|
||||||
www
|
**/.project
|
||||||
|
**/.settings
|
||||||
# Ignore the .git directory
|
**/.toolstarget
|
||||||
.git
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
# Ignore the .gitignore file
|
**/.idea
|
||||||
.gitignore
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
# Ignore the .dockerignore file
|
**/*.jfm
|
||||||
.dockerignore
|
**/azds.yaml
|
||||||
Dockerfile
|
**/bin
|
||||||
|
**/charts
|
||||||
.vscode/*
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
462
.gitignore
vendored
462
.gitignore
vendored
@@ -1,70 +1,432 @@
|
|||||||
# Specifies intentionally untracked files to ignore when using Git
|
### Csharp template
|
||||||
# http://git-scm.com/docs/gitignore
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
*~
|
# User-specific files
|
||||||
*.sw[mnpcod]
|
*.rsuser
|
||||||
.tmp
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
*.tmp
|
*.tmp
|
||||||
*.tmp.*
|
*.tmp_proj
|
||||||
UserInterfaceState.xcuserstate
|
*_wpftmp.csproj
|
||||||
$RECYCLE.BIN/
|
|
||||||
|
|
||||||
*.log
|
*.log
|
||||||
log.txt
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
/.sourcemaps
|
# Visual C++ cache files
|
||||||
/.versions
|
ipch/
|
||||||
/coverage
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
# Ionic
|
# Visual Studio profiler
|
||||||
/.ionic
|
*.psess
|
||||||
/www
|
*.vsp
|
||||||
/platforms
|
*.vspx
|
||||||
/plugins
|
*.sap
|
||||||
|
|
||||||
# Compiled output
|
# Visual Studio Trace Files
|
||||||
/dist
|
*.e2e
|
||||||
/tmp
|
|
||||||
/out-tsc
|
|
||||||
/bazel-out
|
|
||||||
|
|
||||||
# Node
|
# TFS 2012 Local Workspace
|
||||||
/node_modules
|
$tf/
|
||||||
npm-debug.log
|
|
||||||
yarn-error.log
|
|
||||||
|
|
||||||
# IDEs and editors
|
# Guidance Automation Toolkit
|
||||||
.idea/
|
*.gpState
|
||||||
.project
|
|
||||||
.classpath
|
|
||||||
.c9/
|
|
||||||
*.launch
|
|
||||||
.settings/
|
|
||||||
*.sublime-project
|
|
||||||
*.sublime-workspace
|
|
||||||
|
|
||||||
# Visual Studio Code
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
*.vbp
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
*.ncb
|
||||||
|
*.aps
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
.history/*
|
*.code-workspace
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
# Miscellaneous
|
# Windows Installer files from build outputs
|
||||||
/.angular
|
*.cab
|
||||||
/.angular/cache
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
### Angular template
|
||||||
|
## Angular ##
|
||||||
|
# compiled output
|
||||||
|
dist/
|
||||||
|
tmp/
|
||||||
|
app/**/*.js
|
||||||
|
app/**/*.js.map
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
bower_components/
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# misc
|
||||||
.sass-cache/
|
.sass-cache/
|
||||||
/.nx
|
connect.lock/
|
||||||
/.nx/cache
|
coverage/
|
||||||
/connect.lock
|
libpeerconnection.log/
|
||||||
/coverage
|
npm-debug.log
|
||||||
/libpeerconnection.log
|
|
||||||
testem.log
|
testem.log
|
||||||
/typings
|
typings/
|
||||||
|
.angular/
|
||||||
|
|
||||||
|
# e2e
|
||||||
|
e2e/*.js
|
||||||
|
e2e/*.map
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store/
|
||||||
|
|
||||||
# System files
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|||||||
@@ -1,55 +1,107 @@
|
|||||||
image: node:lts-alpine
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- install
|
- install
|
||||||
- lint
|
- lint
|
||||||
- build
|
- build
|
||||||
|
- test
|
||||||
- publish
|
- publish
|
||||||
|
|
||||||
install:
|
install-mobile:
|
||||||
stage: install
|
stage: install
|
||||||
|
image: node:lts-alpine
|
||||||
|
before_script:
|
||||||
|
- cd src/WorkTime.WebMobile
|
||||||
script:
|
script:
|
||||||
- npm install --prefer-offline
|
- npm install --prefer-offline
|
||||||
cache:
|
cache:
|
||||||
key:
|
key:
|
||||||
files:
|
files:
|
||||||
- package.json
|
- src/WorkTime.WebMobile/package.json
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- src/WorkTime.WebMobile/node_modules
|
||||||
|
|
||||||
lint:
|
install-backend:
|
||||||
|
stage: install
|
||||||
|
image: mcr.microsoft.com/dotnet/sdk:9.0
|
||||||
|
script:
|
||||||
|
- dotnet restore
|
||||||
|
|
||||||
|
lint-mobile:
|
||||||
stage: lint
|
stage: lint
|
||||||
needs: ["install"]
|
image: node:lts-alpine
|
||||||
|
needs: ["install-mobile"]
|
||||||
|
before_script:
|
||||||
|
- cd src/WorkTime.WebMobile
|
||||||
script:
|
script:
|
||||||
- npm run lint
|
- npm run lint
|
||||||
cache:
|
cache:
|
||||||
key:
|
key:
|
||||||
files:
|
files:
|
||||||
- package.json
|
- src/WorkTime.WebMobile/package.json
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- src/WorkTime.WebMobile/node_modules
|
||||||
policy: pull
|
policy: pull
|
||||||
|
|
||||||
build:
|
build-mobile:
|
||||||
stage: build
|
stage: build
|
||||||
needs: ["lint"]
|
image: node:lts-alpine
|
||||||
|
needs: ["lint-mobile"]
|
||||||
|
before_script:
|
||||||
|
- cd src/WorkTime.WebMobile
|
||||||
script:
|
script:
|
||||||
- npm run build
|
- npm run build
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- $CI_PROJECT_DIR/www
|
- $CI_PROJECT_DIR/src/WorkTime.WebMobile/www
|
||||||
expire_in: 10 minutes
|
expire_in: 10 minutes
|
||||||
cache:
|
cache:
|
||||||
key:
|
key:
|
||||||
files:
|
files:
|
||||||
- package.json
|
- src/WorkTime.WebMobile/package.json
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- src/WorkTime.WebMobile/node_modules
|
||||||
policy: pull
|
policy: pull
|
||||||
|
|
||||||
publish:
|
build-backend:
|
||||||
|
stage: build
|
||||||
|
image: mcr.microsoft.com/dotnet/sdk:9.0
|
||||||
|
script:
|
||||||
|
- dotnet restore
|
||||||
|
- dotnet build --configuration Release --no-restore
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- "**/bin/Release"
|
||||||
|
expire_in: 10 minutes
|
||||||
|
dependencies:
|
||||||
|
- install-backend
|
||||||
|
|
||||||
|
test-backend:
|
||||||
|
stage: test
|
||||||
|
image: mcr.microsoft.com/dotnet/sdk:9.0
|
||||||
|
script:
|
||||||
|
- dotnet test --verbosity normal
|
||||||
|
dependencies:
|
||||||
|
- build-backend
|
||||||
|
|
||||||
|
publish-mobile:
|
||||||
|
stage: publish
|
||||||
|
needs: ["build-mobile"]
|
||||||
|
image: docker:latest
|
||||||
|
services:
|
||||||
|
- name: docker:dind
|
||||||
|
alias: docker
|
||||||
|
before_script:
|
||||||
|
- cd src/WorkTime.WebMobile
|
||||||
|
script:
|
||||||
|
- export VERSION=$(echo $CI_COMMIT_TAG | sed 's/^v//')
|
||||||
|
- docker login -u leon.hoppe -p ${CI_REGISTRY_PASSWORD} registry.leon-hoppe.de
|
||||||
|
- docker build -t registry.leon-hoppe.de/leon.hoppe/worktime/mobile:$VERSION -t registry.leon-hoppe.de/leon.hoppe/worktime/mobile:latest .
|
||||||
|
- docker push registry.leon-hoppe.de/leon.hoppe/worktime/mobile:$VERSION
|
||||||
|
- docker push registry.leon-hoppe.de/leon.hoppe/worktime/mobile:latest
|
||||||
|
only:
|
||||||
|
- tags
|
||||||
|
|
||||||
|
publish-backend:
|
||||||
stage: publish
|
stage: publish
|
||||||
needs: ["build"]
|
|
||||||
image: docker:latest
|
image: docker:latest
|
||||||
services:
|
services:
|
||||||
- name: docker:dind
|
- name: docker:dind
|
||||||
@@ -57,8 +109,8 @@ publish:
|
|||||||
script:
|
script:
|
||||||
- export VERSION=$(echo $CI_COMMIT_TAG | sed 's/^v//')
|
- export VERSION=$(echo $CI_COMMIT_TAG | sed 's/^v//')
|
||||||
- docker login -u leon.hoppe -p ${CI_REGISTRY_PASSWORD} registry.leon-hoppe.de
|
- docker login -u leon.hoppe -p ${CI_REGISTRY_PASSWORD} registry.leon-hoppe.de
|
||||||
- docker build -t registry.leon-hoppe.de/leon.hoppe/worktime:$VERSION -t registry.leon-hoppe.de/leon.hoppe/worktime:latest .
|
- docker build -t registry.leon-hoppe.de/leon.hoppe/worktime/api:$VERSION -t registry.leon-hoppe.de/leon.hoppe/worktime/api:latest -f src/WorkTime.Api/Dockerfile .
|
||||||
- docker push registry.leon-hoppe.de/leon.hoppe/worktime:$VERSION
|
- docker push registry.leon-hoppe.de/leon.hoppe/worktime/api:$VERSION
|
||||||
- docker push registry.leon-hoppe.de/leon.hoppe/worktime:latest
|
- docker push registry.leon-hoppe.de/leon.hoppe/worktime/api:latest
|
||||||
only:
|
only:
|
||||||
- tags
|
- tags
|
||||||
|
|||||||
13
.idea/.gitignore
generated
vendored
Normal file
13
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/contentModel.xml
|
||||||
|
/.idea.WorkTime.iml
|
||||||
|
/modules.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
13
.idea/.idea.WorkTime/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.WorkTime/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/modules.xml
|
||||||
|
/contentModel.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/.idea.WorkTime.iml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
4
.idea/.idea.WorkTime/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.WorkTime/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
11
.idea/.idea.WorkTime/.idea/indexLayout.xml
generated
Normal file
11
.idea/.idea.WorkTime/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders>
|
||||||
|
<Path>src/WorkTime.Mobile</Path>
|
||||||
|
<Path>src/WorkTime.WebMobile</Path>
|
||||||
|
</attachedFolders>
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/.idea.WorkTime/.idea/misc.xml
generated
Normal file
14
.idea/.idea.WorkTime/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="ASK" />
|
||||||
|
<option name="description" value="" />
|
||||||
|
<option name="applicationTheme" value="default" />
|
||||||
|
<option name="iconsTheme" value="default" />
|
||||||
|
<option name="button1Title" value="" />
|
||||||
|
<option name="button1Url" value="" />
|
||||||
|
<option name="button2Title" value="" />
|
||||||
|
<option name="button2Url" value="" />
|
||||||
|
<option name="customApplicationId" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/.idea.WorkTime/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.WorkTime/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
4
.idea/encodings.xml
generated
Normal file
4
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
8
.idea/indexLayout.xml
generated
Normal file
8
.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/misc.xml
generated
Normal file
14
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="ASK" />
|
||||||
|
<option name="description" value="" />
|
||||||
|
<option name="applicationTheme" value="default" />
|
||||||
|
<option name="iconsTheme" value="default" />
|
||||||
|
<option name="button1Title" value="" />
|
||||||
|
<option name="button1Url" value="" />
|
||||||
|
<option name="button2Title" value="" />
|
||||||
|
<option name="button2Url" value="" />
|
||||||
|
<option name="customApplicationId" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
50
WorkTime.sln
Normal file
50
WorkTime.sln
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{25C5A6B2-A1F9-4244-9538-18E3FE76D382}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkTime.Host", "src\WorkTime.Host\WorkTime.Host.csproj", "{6F5D4D47-1484-44EA-A5DD-D00AAD2F2F68}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkTime.ServiceDefaults", "src\WorkTime.ServiceDefaults\WorkTime.ServiceDefaults.csproj", "{B66AA463-03D5-4814-B1D4-71663804248C}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkTime.Shared", "src\WorkTime.Shared\WorkTime.Shared.csproj", "{E6A73E21-39B8-4FA7-8D6D-D5DDB8FB8AF0}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkTime.Api", "src\WorkTime.Api\WorkTime.Api.csproj", "{CED653D6-A0B6-432B-9C36-FBB58EEA8229}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{8CB1F4C6-F95B-4935-81AA-751015E69FEC}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkTime.Mobile", "src\WorkTime.Mobile\WorkTime.Mobile.csproj", "{640DF179-F955-4497-B798-43ABE2AAABF6}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{6F5D4D47-1484-44EA-A5DD-D00AAD2F2F68} = {25C5A6B2-A1F9-4244-9538-18E3FE76D382}
|
||||||
|
{B66AA463-03D5-4814-B1D4-71663804248C} = {25C5A6B2-A1F9-4244-9538-18E3FE76D382}
|
||||||
|
{CED653D6-A0B6-432B-9C36-FBB58EEA8229} = {25C5A6B2-A1F9-4244-9538-18E3FE76D382}
|
||||||
|
{640DF179-F955-4497-B798-43ABE2AAABF6} = {8CB1F4C6-F95B-4935-81AA-751015E69FEC}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{6F5D4D47-1484-44EA-A5DD-D00AAD2F2F68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6F5D4D47-1484-44EA-A5DD-D00AAD2F2F68}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6F5D4D47-1484-44EA-A5DD-D00AAD2F2F68}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6F5D4D47-1484-44EA-A5DD-D00AAD2F2F68}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B66AA463-03D5-4814-B1D4-71663804248C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B66AA463-03D5-4814-B1D4-71663804248C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B66AA463-03D5-4814-B1D4-71663804248C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B66AA463-03D5-4814-B1D4-71663804248C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E6A73E21-39B8-4FA7-8D6D-D5DDB8FB8AF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E6A73E21-39B8-4FA7-8D6D-D5DDB8FB8AF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E6A73E21-39B8-4FA7-8D6D-D5DDB8FB8AF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E6A73E21-39B8-4FA7-8D6D-D5DDB8FB8AF0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{CED653D6-A0B6-432B-9C36-FBB58EEA8229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CED653D6-A0B6-432B-9C36-FBB58EEA8229}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CED653D6-A0B6-432B-9C36-FBB58EEA8229}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CED653D6-A0B6-432B-9C36-FBB58EEA8229}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{640DF179-F955-4497-B798-43ABE2AAABF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{640DF179-F955-4497-B798-43ABE2AAABF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{640DF179-F955-4497-B798-43ABE2AAABF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{640DF179-F955-4497-B798-43ABE2AAABF6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
60
src/WorkTime.Api/AuthHandler.cs
Normal file
60
src/WorkTime.Api/AuthHandler.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.OpenApi;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using WorkTime.Shared.Services;
|
||||||
|
|
||||||
|
namespace WorkTime.Api;
|
||||||
|
|
||||||
|
public class AuthHandler(
|
||||||
|
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||||
|
ILoggerFactory logger,
|
||||||
|
UrlEncoder encoder,
|
||||||
|
IAuthService authService)
|
||||||
|
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder) {
|
||||||
|
|
||||||
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
|
||||||
|
if (! await authService.IsAuthenticated()) {
|
||||||
|
return AuthenticateResult.Fail("Invalid or missing Guid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var guid = await authService.GetCurrentUserId();
|
||||||
|
|
||||||
|
var claims = new[] { new Claim(ClaimTypes.NameIdentifier, guid.ToString()) };
|
||||||
|
var identity = new ClaimsIdentity(claims, Scheme.Name);
|
||||||
|
var principal = new ClaimsPrincipal(identity);
|
||||||
|
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
||||||
|
|
||||||
|
return AuthenticateResult.Success(ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task ConfigureOpenApi(OpenApiDocument document, OpenApiDocumentTransformerContext transformerContext, CancellationToken token) {
|
||||||
|
document.Components ??= new OpenApiComponents();
|
||||||
|
document.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>();
|
||||||
|
document.SecurityRequirements ??= new List<OpenApiSecurityRequirement>();
|
||||||
|
|
||||||
|
document.Components.SecuritySchemes.Add(nameof(AuthHandler), new OpenApiSecurityScheme {
|
||||||
|
Type = SecuritySchemeType.Http,
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
Name = IAuthService.HeaderName,
|
||||||
|
Scheme = "Bearer",
|
||||||
|
Description = "GUID Authorization header using a custom scheme.\nExample: \"Authorization: {GUID}\""
|
||||||
|
});
|
||||||
|
|
||||||
|
//TODO: only add security requirement to authorized endpoints
|
||||||
|
document.SecurityRequirements.Add(new() {
|
||||||
|
{
|
||||||
|
new() {
|
||||||
|
Reference = new OpenApiReference {
|
||||||
|
Type = ReferenceType.SecurityScheme,
|
||||||
|
Id = nameof(AuthHandler)
|
||||||
|
}
|
||||||
|
}, []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/WorkTime.Api/Controllers/AuthController.cs
Normal file
13
src/WorkTime.Api/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace WorkTime.Api.Controllers;
|
||||||
|
|
||||||
|
[ApiController, Route("auth")]
|
||||||
|
public class AuthController : ControllerBase {
|
||||||
|
|
||||||
|
[HttpGet("register")]
|
||||||
|
public ActionResult<Guid> Register() {
|
||||||
|
return Ok(Guid.NewGuid().ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
src/WorkTime.Api/Controllers/EntryController.cs
Normal file
35
src/WorkTime.Api/Controllers/EntryController.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using WorkTime.Api.Logic;
|
||||||
|
using WorkTime.Shared.Models;
|
||||||
|
|
||||||
|
namespace WorkTime.Api.Controllers;
|
||||||
|
|
||||||
|
[ApiController, Route("entries"), Authorize]
|
||||||
|
public class EntryController(EntryLogic logic) : ControllerBase {
|
||||||
|
|
||||||
|
[HttpGet("{id:guid}")]
|
||||||
|
public async Task<IResult> GetAllEntries(Guid id) {
|
||||||
|
var result = await logic.GetAllEntries(id);
|
||||||
|
return result.MapResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id:guid}/{day:datetime}")]
|
||||||
|
public async Task<IResult> GetEntries(Guid id, DateTime day) {
|
||||||
|
var result = await logic.GetEntries(id, DateOnly.FromDateTime(day));
|
||||||
|
return result.MapResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IResult> AddEntry(TimeEntry entry) {
|
||||||
|
var result = await logic.AddEntry(entry);
|
||||||
|
return result.MapResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id:int}")]
|
||||||
|
public async Task<IResult> DeleteEntry(int id) {
|
||||||
|
var result = await logic.DeleteEntry(id);
|
||||||
|
return result.MapResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
src/WorkTime.Api/DatabaseContext.cs
Normal file
10
src/WorkTime.Api/DatabaseContext.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using WorkTime.Shared.Models;
|
||||||
|
|
||||||
|
namespace WorkTime.Api;
|
||||||
|
|
||||||
|
public class DatabaseContext(DbContextOptions<DatabaseContext> options) : DbContext(options) {
|
||||||
|
|
||||||
|
public DbSet<TimeEntry> Entries { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
23
src/WorkTime.Api/Dockerfile
Normal file
23
src/WorkTime.Api/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||||
|
USER $APP_UID
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
EXPOSE 8081
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["src/WorkTime.Api/WorkTime.Api.csproj", "src/WorkTime.Api/"]
|
||||||
|
RUN dotnet restore "src/WorkTime.Api/WorkTime.Api.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/src/WorkTime.Api"
|
||||||
|
RUN dotnet build "WorkTime.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "WorkTime.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "WorkTime.Api.dll"]
|
||||||
57
src/WorkTime.Api/Logic/EntryLogic.cs
Normal file
57
src/WorkTime.Api/Logic/EntryLogic.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
using WorkTime.ServiceDefaults.Results;
|
||||||
|
using WorkTime.Shared.Models;
|
||||||
|
using WorkTime.Shared.Repositories;
|
||||||
|
using WorkTime.Shared.Services;
|
||||||
|
|
||||||
|
namespace WorkTime.Api.Logic;
|
||||||
|
|
||||||
|
public class EntryLogic(IEntryRepository entryRepository, IAuthService authService) {
|
||||||
|
|
||||||
|
public async Task<LogicResult<TimeEntry[]>> GetAllEntries(Guid owner) {
|
||||||
|
if (!await authService.IsAuthenticated())
|
||||||
|
return new(Results.Unauthorized());
|
||||||
|
|
||||||
|
if (await authService.GetCurrentUserId() != owner)
|
||||||
|
return new(Results.Unauthorized());
|
||||||
|
|
||||||
|
return await entryRepository.GetAllEntries(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogicResult<TimeEntry[]>> GetEntries(Guid owner, DateOnly date) {
|
||||||
|
if (!await authService.IsAuthenticated())
|
||||||
|
return new(Results.Unauthorized());
|
||||||
|
|
||||||
|
if (await authService.GetCurrentUserId() != owner)
|
||||||
|
return new(Results.Unauthorized());
|
||||||
|
|
||||||
|
return await entryRepository.GetEntries(owner, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogicResult> AddEntry(TimeEntry entry) {
|
||||||
|
if (!await authService.IsAuthenticated())
|
||||||
|
return new(Results.Unauthorized());
|
||||||
|
|
||||||
|
if (await authService.GetCurrentUserId() != entry.Owner)
|
||||||
|
return new(Results.Unauthorized());
|
||||||
|
|
||||||
|
await entryRepository.AddEntry(entry);
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LogicResult> DeleteEntry(int id) {
|
||||||
|
if (!await authService.IsAuthenticated())
|
||||||
|
return new(Results.Unauthorized());
|
||||||
|
|
||||||
|
var entry = await entryRepository.GetEntry(id);
|
||||||
|
if (entry is null)
|
||||||
|
return new(Results.NotFound());
|
||||||
|
|
||||||
|
if (await authService.GetCurrentUserId() != entry.Owner)
|
||||||
|
return new(Results.Unauthorized());
|
||||||
|
|
||||||
|
await entryRepository.DeleteEntry(entry);
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
56
src/WorkTime.Api/Program.cs
Normal file
56
src/WorkTime.Api/Program.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using MartinCostello.OpenApi;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using WorkTime.Api;
|
||||||
|
using WorkTime.Api.Logic;
|
||||||
|
using WorkTime.Api.Repositories;
|
||||||
|
using WorkTime.Api.Services;
|
||||||
|
using WorkTime.Shared.Repositories;
|
||||||
|
using WorkTime.Shared.Services;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||||
|
builder.Services.AddOpenApi(options => {
|
||||||
|
options.AddDocumentTransformer(AuthHandler.ConfigureOpenApi);
|
||||||
|
});
|
||||||
|
builder.Services.AddOpenApiExtensions(options => {
|
||||||
|
options.AddServerUrls = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.AddServiceDefaults();
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
builder.Services.AddProblemDetails();
|
||||||
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
|
builder.Services.AddNpgsql<DatabaseContext>(builder.Configuration.GetConnectionString("data"));
|
||||||
|
builder.Services.AddScoped<IEntryRepository, EntryRepository>();
|
||||||
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||||
|
|
||||||
|
builder.Services.AddScoped<EntryLogic>();
|
||||||
|
|
||||||
|
builder.Services.AddAuthentication(nameof(AuthHandler))
|
||||||
|
.AddScheme<AuthenticationSchemeOptions, AuthHandler>(nameof(AuthHandler), _ => { });
|
||||||
|
builder.Services.AddAuthorization();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment()) {
|
||||||
|
app.MapOpenApi();
|
||||||
|
app.UseSwaggerUI(options => {
|
||||||
|
options.SwaggerEndpoint("/openapi/v1.json", "WorkTime.Api");
|
||||||
|
});
|
||||||
|
|
||||||
|
await using var scope = app.Services.CreateAsyncScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||||
|
db.Database.EnsureCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
23
src/WorkTime.Api/Properties/launchSettings.json
Normal file
23
src/WorkTime.Api/Properties/launchSettings.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "http://localhost:5212",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "https://localhost:7091;http://localhost:5212",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/WorkTime.Api/Repositories/EntryRepository.cs
Normal file
39
src/WorkTime.Api/Repositories/EntryRepository.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using WorkTime.Shared.Models;
|
||||||
|
using WorkTime.Shared.Repositories;
|
||||||
|
|
||||||
|
namespace WorkTime.Api.Repositories;
|
||||||
|
|
||||||
|
internal class EntryRepository(DatabaseContext context) : IEntryRepository {
|
||||||
|
|
||||||
|
public async Task<TimeEntry[]> GetAllEntries(Guid owner) {
|
||||||
|
return await context.Entries
|
||||||
|
.Where(entry => entry.Owner == owner)
|
||||||
|
.OrderBy(entry => entry.Timestamp)
|
||||||
|
.ToArrayAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TimeEntry[]> GetEntries(Guid owner, DateOnly date) {
|
||||||
|
return await context.Entries
|
||||||
|
.Where(entry => entry.Owner == owner)
|
||||||
|
.Where(entry => DateOnly.FromDateTime(entry.Timestamp) == date)
|
||||||
|
.OrderBy(entry => entry.Timestamp)
|
||||||
|
.ToArrayAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TimeEntry?> GetEntry(int id) {
|
||||||
|
return await context.Entries
|
||||||
|
.FindAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddEntry(TimeEntry entry) {
|
||||||
|
await context.Entries.AddAsync(entry);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteEntry(TimeEntry entry) {
|
||||||
|
context.Entries.Remove(entry);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
src/WorkTime.Api/Services/AuthService.cs
Normal file
29
src/WorkTime.Api/Services/AuthService.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using WorkTime.Shared.Services;
|
||||||
|
|
||||||
|
namespace WorkTime.Api.Services;
|
||||||
|
|
||||||
|
internal class AuthService(IHttpContextAccessor accessor) : IAuthService {
|
||||||
|
|
||||||
|
public Task<bool> IsAuthenticated() {
|
||||||
|
var header = accessor.HttpContext?.Request.Headers[IAuthService.HeaderName];
|
||||||
|
if (header is not { Count: 1 })
|
||||||
|
return Task.FromResult(false);
|
||||||
|
|
||||||
|
var value = header.Value[0]!.Replace("Bearer ", "");
|
||||||
|
if (Guid.TryParse(value, out var guid))
|
||||||
|
return Task.FromResult(false);
|
||||||
|
|
||||||
|
return Task.FromResult(guid != Guid.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Guid> GetCurrentUserId() {
|
||||||
|
if (!await IsAuthenticated())
|
||||||
|
return Guid.Empty;
|
||||||
|
|
||||||
|
var header = accessor.HttpContext?.Request.Headers[IAuthService.HeaderName]!;
|
||||||
|
var value = header.Value[0]!.Replace("Bearer ", "");
|
||||||
|
|
||||||
|
return Guid.Parse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
28
src/WorkTime.Api/WorkTime.Api.csproj
Normal file
28
src/WorkTime.Api/WorkTime.Api.csproj
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.1.0" />
|
||||||
|
<PackageReference Include="MartinCostello.OpenApi.Extensions" Version="1.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0"/>
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="7.3.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="..\..\.dockerignore">
|
||||||
|
<Link>.dockerignore</Link>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\WorkTime.ServiceDefaults\WorkTime.ServiceDefaults.csproj" />
|
||||||
|
<ProjectReference Include="..\WorkTime.Shared\WorkTime.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
8
src/WorkTime.Api/appsettings.Development.json
Normal file
8
src/WorkTime.Api/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/WorkTime.Api/appsettings.json
Normal file
9
src/WorkTime.Api/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
10
src/WorkTime.Host/Program.cs
Normal file
10
src/WorkTime.Host/Program.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
var builder = DistributedApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
var db = builder.AddPostgres("db")
|
||||||
|
.WithDataVolume();
|
||||||
|
|
||||||
|
builder.AddProject<Projects.WorkTime_Api>("api")
|
||||||
|
.WithReference(db.AddDatabase("data"))
|
||||||
|
.WaitFor(db);
|
||||||
|
|
||||||
|
builder.Build().Run();
|
||||||
29
src/WorkTime.Host/Properties/launchSettings.json
Normal file
29
src/WorkTime.Host/Properties/launchSettings.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "https://localhost:17121;http://localhost:15199",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"DOTNET_ENVIRONMENT": "Development",
|
||||||
|
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21125",
|
||||||
|
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22099"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "http://localhost:15199",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"DOTNET_ENVIRONMENT": "Development",
|
||||||
|
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19218",
|
||||||
|
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20104"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/WorkTime.Host/WorkTime.Host.csproj
Normal file
23
src/WorkTime.Host/WorkTime.Host.csproj
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0"/>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsAspireHost>true</IsAspireHost>
|
||||||
|
<UserSecretsId>ba72af6f-0952-417d-bef6-ab77ed6fa624</UserSecretsId>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.1.0" />
|
||||||
|
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.1.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\WorkTime.Api\WorkTime.Api.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
8
src/WorkTime.Host/appsettings.Development.json
Normal file
8
src/WorkTime.Host/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/WorkTime.Host/appsettings.json
Normal file
9
src/WorkTime.Host/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning",
|
||||||
|
"Aspire.Hosting.Dcp": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/WorkTime.Mobile/App.xaml
Normal file
14
src/WorkTime.Mobile/App.xaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version = "1.0" encoding = "UTF-8" ?>
|
||||||
|
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:local="clr-namespace:WorkTime.Mobile"
|
||||||
|
x:Class="WorkTime.Mobile.App">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
||||||
|
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
14
src/WorkTime.Mobile/App.xaml.cs
Normal file
14
src/WorkTime.Mobile/App.xaml.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using MauiIcons.Core;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
public partial class App : Application {
|
||||||
|
public App() {
|
||||||
|
InitializeComponent();
|
||||||
|
_ = new MauiIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Window CreateWindow(IActivationState? activationState) {
|
||||||
|
return new Window(new AppShell());
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/WorkTime.Mobile/AppShell.xaml
Normal file
26
src/WorkTime.Mobile/AppShell.xaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<Shell
|
||||||
|
x:Class="WorkTime.Mobile.AppShell"
|
||||||
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:pages="clr-namespace:WorkTime.Mobile.Views.Pages"
|
||||||
|
xmlns:icons="http://www.aathifmahir.com/dotnet/2022/maui/icons"
|
||||||
|
FlyoutBehavior="Disabled"
|
||||||
|
Title="Zeiterfassung">
|
||||||
|
|
||||||
|
<Shell.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<FontImageSource x:Key="CaptureIcon" Glyph="{icons:Material Schedule}" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Shell.Resources>
|
||||||
|
|
||||||
|
<TabBar>
|
||||||
|
|
||||||
|
<ShellContent
|
||||||
|
Title="Erfassen"
|
||||||
|
Icon="{StaticResource CaptureIcon}"
|
||||||
|
ContentTemplate="{DataTemplate pages:CapturePage}"/>
|
||||||
|
|
||||||
|
</TabBar>
|
||||||
|
|
||||||
|
</Shell>
|
||||||
7
src/WorkTime.Mobile/AppShell.xaml.cs
Normal file
7
src/WorkTime.Mobile/AppShell.xaml.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
public partial class AppShell : Shell {
|
||||||
|
public AppShell() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/WorkTime.Mobile/DatabaseContext.cs
Normal file
10
src/WorkTime.Mobile/DatabaseContext.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using WorkTime.Shared.Models;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
public class DatabaseContext(DbContextOptions<DatabaseContext> options) : DbContext(options) {
|
||||||
|
|
||||||
|
public DbSet<TimeEntry> Entries { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
17
src/WorkTime.Mobile/Extensions.cs
Normal file
17
src/WorkTime.Mobile/Extensions.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using WorkTime.Shared.Models;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
public static class Extensions {
|
||||||
|
|
||||||
|
public static string GetActionName(this TimeEntryType type) {
|
||||||
|
return type switch {
|
||||||
|
TimeEntryType.Login => "Einstempeln",
|
||||||
|
TimeEntryType.Logout => "Ausstempeln",
|
||||||
|
TimeEntryType.LoginDrive => "Dienstreise starten",
|
||||||
|
TimeEntryType.LogoutDrive => "Dienstreise beenden",
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
55
src/WorkTime.Mobile/MauiProgram.cs
Normal file
55
src/WorkTime.Mobile/MauiProgram.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using MauiIcons.Material;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using WorkTime.Mobile.Repositories;
|
||||||
|
using WorkTime.Mobile.Services;
|
||||||
|
using WorkTime.Mobile.ViewModels;
|
||||||
|
using WorkTime.Shared.Repositories;
|
||||||
|
using WorkTime.Shared.Services;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
public static class MauiProgram {
|
||||||
|
|
||||||
|
private static string BackendUrl { get; set; } = string.Empty; //TODO: Set production endpoint
|
||||||
|
|
||||||
|
public static MauiApp CreateMauiApp() {
|
||||||
|
var builder = MauiApp.CreateBuilder();
|
||||||
|
builder
|
||||||
|
.UseMauiApp<App>()
|
||||||
|
.ConfigureFonts(fonts => {
|
||||||
|
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||||
|
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||||
|
})
|
||||||
|
.UseMaterialMauiIcons();
|
||||||
|
|
||||||
|
builder.Services.AddSqlite<DatabaseContext>($"Filename={Path.Combine(FileSystem.AppDataDirectory, "data.db")}");
|
||||||
|
builder.Services.AddSingleton(SecureStorage.Default);
|
||||||
|
builder.Services.AddScoped<HttpClient>(_ => {
|
||||||
|
var client = new HttpClient();
|
||||||
|
client.BaseAddress = new Uri(BackendUrl);
|
||||||
|
return client;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddKeyedScoped<IEntryRepository, ServerEntryRepository>("server");
|
||||||
|
builder.Services.AddKeyedScoped<IEntryRepository, ClientEntryRepository>("client");
|
||||||
|
builder.Services.AddKeyedScoped<IHttpService, InsecureHttpService>("insecure");
|
||||||
|
builder.Services.AddScoped<IHttpService, HttpService>();
|
||||||
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||||
|
builder.Services.AddScoped<EntryService>();
|
||||||
|
|
||||||
|
builder.Services.AddTransient<CaptureViewModel>();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
builder.Logging.AddDebug();
|
||||||
|
BackendUrl = "https://localhost:7091/";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
using var scope = app.Services.CreateScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||||
|
db.Database.EnsureCreated();
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
</manifest>
|
||||||
10
src/WorkTime.Mobile/Platforms/Android/MainActivity.cs
Normal file
10
src/WorkTime.Mobile/Platforms/Android/MainActivity.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Android.App;
|
||||||
|
using Android.Content.PM;
|
||||||
|
using Android.OS;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop,
|
||||||
|
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode |
|
||||||
|
ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||||
|
public class MainActivity : MauiAppCompatActivity { }
|
||||||
12
src/WorkTime.Mobile/Platforms/Android/MainApplication.cs
Normal file
12
src/WorkTime.Mobile/Platforms/Android/MainApplication.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Android.App;
|
||||||
|
using Android.Runtime;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
[Application]
|
||||||
|
public class MainApplication : MauiApplication {
|
||||||
|
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||||
|
: base(handle, ownership) { }
|
||||||
|
|
||||||
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#512BD4</color>
|
||||||
|
<color name="colorPrimaryDark">#2B0B98</color>
|
||||||
|
<color name="colorAccent">#2B0B98</color>
|
||||||
|
</resources>
|
||||||
8
src/WorkTime.Mobile/Platforms/MacCatalyst/AppDelegate.cs
Normal file
8
src/WorkTime.Mobile/Platforms/MacCatalyst/AppDelegate.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Foundation;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
[Register("AppDelegate")]
|
||||||
|
public class AppDelegate : MauiUIApplicationDelegate {
|
||||||
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
}
|
||||||
14
src/WorkTime.Mobile/Platforms/MacCatalyst/Entitlements.plist
Normal file
14
src/WorkTime.Mobile/Platforms/MacCatalyst/Entitlements.plist
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<!-- See https://aka.ms/maui-publish-app-store#add-entitlements for more information about adding entitlements.-->
|
||||||
|
<dict>
|
||||||
|
<!-- App Sandbox must be enabled to distribute a MacCatalyst app through the Mac App Store. -->
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<!-- When App Sandbox is enabled, this value is required to open outgoing network connections. -->
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
||||||
38
src/WorkTime.Mobile/Platforms/MacCatalyst/Info.plist
Normal file
38
src/WorkTime.Mobile/Platforms/MacCatalyst/Info.plist
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<!-- The Mac App Store requires you specify if the app uses encryption. -->
|
||||||
|
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
|
||||||
|
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
|
||||||
|
<!-- Please indicate <true/> or <false/> here. -->
|
||||||
|
|
||||||
|
<!-- Specify the category for your app here. -->
|
||||||
|
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
|
||||||
|
<!-- <key>LSApplicationCategoryType</key> -->
|
||||||
|
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
|
||||||
|
<key>UIDeviceFamily</key>
|
||||||
|
<array>
|
||||||
|
<integer>2</integer>
|
||||||
|
</array>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>XSAppIconAssets</key>
|
||||||
|
<string>Assets.xcassets/appicon.appiconset</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
13
src/WorkTime.Mobile/Platforms/MacCatalyst/Program.cs
Normal file
13
src/WorkTime.Mobile/Platforms/MacCatalyst/Program.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using ObjCRuntime;
|
||||||
|
using UIKit;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
public class Program {
|
||||||
|
// This is the main entry point of the application.
|
||||||
|
static void Main(string[] args) {
|
||||||
|
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||||
|
// you can specify it here.
|
||||||
|
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/WorkTime.Mobile/Platforms/Tizen/Main.cs
Normal file
14
src/WorkTime.Mobile/Platforms/Tizen/Main.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Maui;
|
||||||
|
using Microsoft.Maui.Hosting;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
class Program : MauiApplication {
|
||||||
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
|
||||||
|
static void Main(string[] args) {
|
||||||
|
var app = new Program();
|
||||||
|
app.Run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/WorkTime.Mobile/Platforms/Tizen/tizen-manifest.xml
Normal file
15
src/WorkTime.Mobile/Platforms/Tizen/tizen-manifest.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="9" xmlns="http://tizen.org/ns/packages">
|
||||||
|
<profile name="common" />
|
||||||
|
<ui-application appid="maui-application-id-placeholder" exec="WorkTime.Mobile.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
|
||||||
|
<label>maui-application-title-placeholder</label>
|
||||||
|
<icon>maui-appicon-placeholder</icon>
|
||||||
|
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
|
||||||
|
</ui-application>
|
||||||
|
<shortcut-list />
|
||||||
|
<privileges>
|
||||||
|
<privilege>http://tizen.org/privilege/internet</privilege>
|
||||||
|
</privileges>
|
||||||
|
<dependencies />
|
||||||
|
<provides-appdefined-privileges />
|
||||||
|
</manifest>
|
||||||
8
src/WorkTime.Mobile/Platforms/Windows/App.xaml
Normal file
8
src/WorkTime.Mobile/Platforms/Windows/App.xaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<maui:MauiWinUIApplication
|
||||||
|
x:Class="WorkTime.Mobile.WinUI.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:maui="using:Microsoft.Maui"
|
||||||
|
xmlns:local="using:WorkTime.Mobile.WinUI">
|
||||||
|
|
||||||
|
</maui:MauiWinUIApplication>
|
||||||
21
src/WorkTime.Mobile/Platforms/Windows/App.xaml.cs
Normal file
21
src/WorkTime.Mobile/Platforms/Windows/App.xaml.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile.WinUI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides application-specific behavior to supplement the default Application class.
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : MauiWinUIApplication {
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the singleton application object. This is the first line of authored code
|
||||||
|
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||||
|
/// </summary>
|
||||||
|
public App() {
|
||||||
|
this.InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
}
|
||||||
46
src/WorkTime.Mobile/Platforms/Windows/Package.appxmanifest
Normal file
46
src/WorkTime.Mobile/Platforms/Windows/Package.appxmanifest
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Package
|
||||||
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
|
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||||
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||||
|
IgnorableNamespaces="uap rescap">
|
||||||
|
|
||||||
|
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
|
||||||
|
|
||||||
|
<mp:PhoneIdentity PhoneProductId="1D7A4BF8-7BA9-4017-B89B-DBC4682E080F" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||||
|
|
||||||
|
<Properties>
|
||||||
|
<DisplayName>$placeholder$</DisplayName>
|
||||||
|
<PublisherDisplayName>User Name</PublisherDisplayName>
|
||||||
|
<Logo>$placeholder$.png</Logo>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<Dependencies>
|
||||||
|
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||||
|
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||||
|
</Dependencies>
|
||||||
|
|
||||||
|
<Resources>
|
||||||
|
<Resource Language="x-generate" />
|
||||||
|
</Resources>
|
||||||
|
|
||||||
|
<Applications>
|
||||||
|
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
|
||||||
|
<uap:VisualElements
|
||||||
|
DisplayName="$placeholder$"
|
||||||
|
Description="$placeholder$"
|
||||||
|
Square150x150Logo="$placeholder$.png"
|
||||||
|
Square44x44Logo="$placeholder$.png"
|
||||||
|
BackgroundColor="transparent">
|
||||||
|
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
|
||||||
|
<uap:SplashScreen Image="$placeholder$.png" />
|
||||||
|
</uap:VisualElements>
|
||||||
|
</Application>
|
||||||
|
</Applications>
|
||||||
|
|
||||||
|
<Capabilities>
|
||||||
|
<rescap:Capability Name="runFullTrust" />
|
||||||
|
</Capabilities>
|
||||||
|
|
||||||
|
</Package>
|
||||||
15
src/WorkTime.Mobile/Platforms/Windows/app.manifest
Normal file
15
src/WorkTime.Mobile/Platforms/Windows/app.manifest
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="WorkTime.Mobile.WinUI.app"/>
|
||||||
|
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<!-- The combination of below two tags have the following effect:
|
||||||
|
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||||
|
2) System < Windows 10 Anniversary Update
|
||||||
|
-->
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
8
src/WorkTime.Mobile/Platforms/iOS/AppDelegate.cs
Normal file
8
src/WorkTime.Mobile/Platforms/iOS/AppDelegate.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Foundation;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
[Register("AppDelegate")]
|
||||||
|
public class AppDelegate : MauiUIApplicationDelegate {
|
||||||
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
}
|
||||||
32
src/WorkTime.Mobile/Platforms/iOS/Info.plist
Normal file
32
src/WorkTime.Mobile/Platforms/iOS/Info.plist
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIDeviceFamily</key>
|
||||||
|
<array>
|
||||||
|
<integer>1</integer>
|
||||||
|
<integer>2</integer>
|
||||||
|
</array>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>XSAppIconAssets</key>
|
||||||
|
<string>Assets.xcassets/appicon.appiconset</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
13
src/WorkTime.Mobile/Platforms/iOS/Program.cs
Normal file
13
src/WorkTime.Mobile/Platforms/iOS/Program.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using ObjCRuntime;
|
||||||
|
using UIKit;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile;
|
||||||
|
|
||||||
|
public class Program {
|
||||||
|
// This is the main entry point of the application.
|
||||||
|
static void Main(string[] args) {
|
||||||
|
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||||
|
// you can specify it here.
|
||||||
|
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
This is the minimum required version of the Apple Privacy Manifest for .NET MAUI apps.
|
||||||
|
The contents below are needed because of APIs that are used in the .NET framework and .NET MAUI SDK.
|
||||||
|
|
||||||
|
You are responsible for adding extra entries as needed for your application.
|
||||||
|
|
||||||
|
More information: https://aka.ms/maui-privacy-manifest
|
||||||
|
-->
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>C617.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>35F9.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>E174.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<!--
|
||||||
|
The entry below is only needed when you're using the Preferences API in your app.
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>CA92.1</string>
|
||||||
|
</array>
|
||||||
|
</dict> -->
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
8
src/WorkTime.Mobile/Properties/launchSettings.json
Normal file
8
src/WorkTime.Mobile/Properties/launchSettings.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Windows Machine": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"nativeDebugging": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/WorkTime.Mobile/Repositories/ClientEntryRepository.cs
Normal file
49
src/WorkTime.Mobile/Repositories/ClientEntryRepository.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using WorkTime.Shared.Models;
|
||||||
|
using WorkTime.Shared.Repositories;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile.Repositories;
|
||||||
|
|
||||||
|
public class ClientEntryRepository(DatabaseContext context) : IEntryRepository {
|
||||||
|
|
||||||
|
public async Task<TimeEntry[]> GetAllEntries(Guid owner) {
|
||||||
|
return await context.Entries
|
||||||
|
.Where(entry => entry.Owner == owner)
|
||||||
|
.OrderBy(entry => entry.Timestamp)
|
||||||
|
.ToArrayAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TimeEntry[]> GetEntries(Guid owner, DateOnly date) {
|
||||||
|
return await context.Entries
|
||||||
|
.Where(entry => entry.Owner == owner)
|
||||||
|
.Where(entry => DateOnly.FromDateTime(entry.Timestamp) == date)
|
||||||
|
.OrderBy(entry => entry.Timestamp)
|
||||||
|
.ToArrayAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TimeEntry?> GetEntry(int id) {
|
||||||
|
return await context.Entries
|
||||||
|
.FindAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddEntry(TimeEntry entry) {
|
||||||
|
await context.Entries.AddAsync(entry);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteEntry(TimeEntry entry) {
|
||||||
|
context.Entries.Remove(entry);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReplaceEntries(IEnumerable<TimeEntry> entries, DateOnly date) {
|
||||||
|
var oldEntries = await context.Entries
|
||||||
|
.Where(entry => DateOnly.FromDateTime(entry.Timestamp) == date)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
context.Entries.RemoveRange(oldEntries);
|
||||||
|
await context.Entries.AddRangeAsync(entries);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
src/WorkTime.Mobile/Repositories/ServerEntryRepository.cs
Normal file
29
src/WorkTime.Mobile/Repositories/ServerEntryRepository.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using WorkTime.Mobile.Services;
|
||||||
|
using WorkTime.Shared.Models;
|
||||||
|
using WorkTime.Shared.Repositories;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile.Repositories;
|
||||||
|
|
||||||
|
public class ServerEntryRepository(IHttpService client) : IEntryRepository {
|
||||||
|
|
||||||
|
public async Task<TimeEntry[]> GetAllEntries(Guid owner) {
|
||||||
|
var response = await client.SendRequest<TimeEntry[]>(HttpMethod.Get, $"entries/{owner}");
|
||||||
|
return response.Result ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TimeEntry[]> GetEntries(Guid owner, DateOnly date) {
|
||||||
|
var response = await client.SendRequest<TimeEntry[]>(HttpMethod.Get, $"entries/{owner}/{date.ToDateTime(TimeOnly.MinValue)}");
|
||||||
|
return response.Result ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TimeEntry?> GetEntry(int id) => Task.FromResult<TimeEntry?>(null);
|
||||||
|
|
||||||
|
public async Task AddEntry(TimeEntry entry) {
|
||||||
|
await client.SendRequest(HttpMethod.Post, "entries", entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteEntry(TimeEntry entry) {
|
||||||
|
await client.SendRequest(HttpMethod.Delete, $"entries/{entry.Id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
4
src/WorkTime.Mobile/Resources/AppIcon/appicon.svg
Normal file
4
src/WorkTime.Mobile/Resources/AppIcon/appicon.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 228 B |
8
src/WorkTime.Mobile/Resources/AppIcon/appiconfg.svg
Normal file
8
src/WorkTime.Mobile/Resources/AppIcon/appiconfg.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
7919
src/WorkTime.Mobile/Resources/Fonts/FluentUI.cs
Normal file
7919
src/WorkTime.Mobile/Resources/Fonts/FluentUI.cs
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/WorkTime.Mobile/Resources/Fonts/OpenSans-Regular.ttf
Normal file
BIN
src/WorkTime.Mobile/Resources/Fonts/OpenSans-Regular.ttf
Normal file
Binary file not shown.
BIN
src/WorkTime.Mobile/Resources/Fonts/OpenSans-Semibold.ttf
Normal file
BIN
src/WorkTime.Mobile/Resources/Fonts/OpenSans-Semibold.ttf
Normal file
Binary file not shown.
BIN
src/WorkTime.Mobile/Resources/Images/dotnet_bot.png
Normal file
BIN
src/WorkTime.Mobile/Resources/Images/dotnet_bot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
15
src/WorkTime.Mobile/Resources/Raw/AboutAssets.txt
Normal file
15
src/WorkTime.Mobile/Resources/Raw/AboutAssets.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Any raw assets you want to be deployed with your application can be placed in
|
||||||
|
this directory (and child directories). Deployment of the asset to your application
|
||||||
|
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
|
||||||
|
|
||||||
|
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||||
|
|
||||||
|
These files will be deployed with your package and will be accessible using Essentials:
|
||||||
|
|
||||||
|
async Task LoadMauiAsset()
|
||||||
|
{
|
||||||
|
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
|
||||||
|
using var reader = new StreamReader(stream);
|
||||||
|
|
||||||
|
var contents = reader.ReadToEnd();
|
||||||
|
}
|
||||||
8
src/WorkTime.Mobile/Resources/Splash/splash.svg
Normal file
8
src/WorkTime.Mobile/Resources/Splash/splash.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
45
src/WorkTime.Mobile/Resources/Styles/Colors.xaml
Normal file
45
src/WorkTime.Mobile/Resources/Styles/Colors.xaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<?xaml-comp compile="true" ?>
|
||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||||
|
|
||||||
|
<!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
|
||||||
|
|
||||||
|
<Color x:Key="Primary">#512BD4</Color>
|
||||||
|
<Color x:Key="PrimaryDark">#ac99ea</Color>
|
||||||
|
<Color x:Key="PrimaryDarkText">#242424</Color>
|
||||||
|
<Color x:Key="Secondary">#DFD8F7</Color>
|
||||||
|
<Color x:Key="SecondaryDarkText">#9880e5</Color>
|
||||||
|
<Color x:Key="Tertiary">#2B0B98</Color>
|
||||||
|
|
||||||
|
<Color x:Key="White">White</Color>
|
||||||
|
<Color x:Key="Black">Black</Color>
|
||||||
|
<Color x:Key="Magenta">#D600AA</Color>
|
||||||
|
<Color x:Key="MidnightBlue">#190649</Color>
|
||||||
|
<Color x:Key="OffBlack">#1f1f1f</Color>
|
||||||
|
|
||||||
|
<Color x:Key="Gray100">#E1E1E1</Color>
|
||||||
|
<Color x:Key="Gray200">#C8C8C8</Color>
|
||||||
|
<Color x:Key="Gray300">#ACACAC</Color>
|
||||||
|
<Color x:Key="Gray400">#919191</Color>
|
||||||
|
<Color x:Key="Gray500">#6E6E6E</Color>
|
||||||
|
<Color x:Key="Gray600">#404040</Color>
|
||||||
|
<Color x:Key="Gray900">#212121</Color>
|
||||||
|
<Color x:Key="Gray950">#141414</Color>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
|
||||||
|
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
|
||||||
|
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
|
||||||
|
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
|
||||||
|
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
|
||||||
|
</ResourceDictionary>
|
||||||
451
src/WorkTime.Mobile/Resources/Styles/Styles.xaml
Normal file
451
src/WorkTime.Mobile/Resources/Styles/Styles.xaml
Normal file
@@ -0,0 +1,451 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<?xaml-comp compile="true" ?>
|
||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||||
|
|
||||||
|
<Style TargetType="ActivityIndicator">
|
||||||
|
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="IndicatorView">
|
||||||
|
<Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
|
||||||
|
<Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
<Setter Property="StrokeShape" Value="Rectangle"/>
|
||||||
|
<Setter Property="StrokeThickness" Value="1"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="BoxView">
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource PrimaryDarkText}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="BorderWidth" Value="0"/>
|
||||||
|
<Setter Property="CornerRadius" Value="8"/>
|
||||||
|
<Setter Property="Padding" Value="14,10"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="PointerOver" />
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="CheckBox">
|
||||||
|
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="DatePicker">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Editor">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Entry">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Frame">
|
||||||
|
<Setter Property="HasShadow" Value="False" />
|
||||||
|
<Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||||
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ImageButton">
|
||||||
|
<Setter Property="Opacity" Value="1" />
|
||||||
|
<Setter Property="BorderColor" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderWidth" Value="0"/>
|
||||||
|
<Setter Property="CornerRadius" Value="0"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="PointerOver" />
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Label">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Span">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Label" x:Key="Headline">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="FontSize" Value="32" />
|
||||||
|
<Setter Property="HorizontalOptions" Value="Center" />
|
||||||
|
<Setter Property="HorizontalTextAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Label" x:Key="SubHeadline">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="FontSize" Value="24" />
|
||||||
|
<Setter Property="HorizontalOptions" Value="Center" />
|
||||||
|
<Setter Property="HorizontalTextAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ListView">
|
||||||
|
<Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
<Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Picker">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ProgressBar">
|
||||||
|
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="RadioButton">
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent"/>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="RefreshView">
|
||||||
|
<Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="SearchBar">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||||
|
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="SearchHandler">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Shadow">
|
||||||
|
<Setter Property="Radius" Value="15" />
|
||||||
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
|
<Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="Offset" Value="10,10" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Slider">
|
||||||
|
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||||
|
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||||
|
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="SwipeItem">
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Switch">
|
||||||
|
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="ThumbColor" Value="{StaticResource White}" />
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="On">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="Off">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TimePicker">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent"/>
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<Style TargetType="TitleBar">
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="32"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="TitleActiveStates">
|
||||||
|
<VisualState x:Name="TitleBarTitleActive">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="TitleBarTitleInactive">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||||
|
<Setter Property="ForegroundColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<Style TargetType="Page" ApplyToDerivedTypes="True">
|
||||||
|
<Setter Property="Padding" Value="0"/>
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Shell" ApplyToDerivedTypes="True">
|
||||||
|
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
||||||
|
<Setter Property="Shell.ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
|
||||||
|
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
|
||||||
|
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||||
|
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="Shell.NavBarHasShadow" Value="False" />
|
||||||
|
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||||
|
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="NavigationPage">
|
||||||
|
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
||||||
|
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TabbedPage">
|
||||||
|
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
|
||||||
|
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||||
|
<Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
23
src/WorkTime.Mobile/Services/AuthService.cs
Normal file
23
src/WorkTime.Mobile/Services/AuthService.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using WorkTime.Shared.Services;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile.Services;
|
||||||
|
|
||||||
|
public class AuthService(ISecureStorage storage, [FromKeyedServices("insecure")] IHttpService httpService) : IAuthService {
|
||||||
|
|
||||||
|
public async Task<bool> IsAuthenticated() {
|
||||||
|
var value = await storage.GetAsync(IAuthService.HeaderName);
|
||||||
|
return !string.IsNullOrWhiteSpace(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Guid> GetCurrentUserId() {
|
||||||
|
var value = await storage.GetAsync(IAuthService.HeaderName);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(value)) {
|
||||||
|
var response = await httpService.SendRequest<string>(HttpMethod.Get, "auth/register");
|
||||||
|
value = response.Result ?? Guid.NewGuid().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Guid.Parse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
66
src/WorkTime.Mobile/Services/EntryService.cs
Normal file
66
src/WorkTime.Mobile/Services/EntryService.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using WorkTime.Mobile.Repositories;
|
||||||
|
using WorkTime.Shared.Models;
|
||||||
|
using WorkTime.Shared.Repositories;
|
||||||
|
using WorkTime.Shared.Services;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile.Services;
|
||||||
|
|
||||||
|
public class EntryService(
|
||||||
|
[FromKeyedServices("server")] IEntryRepository serverRepository,
|
||||||
|
[FromKeyedServices("client")] IEntryRepository clientRepository,
|
||||||
|
IAuthService authService) {
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<UpdatingEntriesResponse> GetEntries(DateOnly date) {
|
||||||
|
var userId = await authService.GetCurrentUserId();
|
||||||
|
var clientEntries = await clientRepository.GetEntries(userId, date);
|
||||||
|
yield return new() {
|
||||||
|
NewBatch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var entry in clientEntries) {
|
||||||
|
yield return new() {
|
||||||
|
Entry = entry
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverEntries = await serverRepository.GetEntries(userId, date);
|
||||||
|
if (serverEntries.Length == 0) yield break;
|
||||||
|
|
||||||
|
yield return new() {
|
||||||
|
NewBatch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var entry in serverEntries) {
|
||||||
|
yield return new() {
|
||||||
|
Entry = entry
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await ((ClientEntryRepository)clientRepository).ReplaceEntries(serverEntries, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddEntry(TimeEntry entry) {
|
||||||
|
var userId = await authService.GetCurrentUserId();
|
||||||
|
entry.Owner = userId;
|
||||||
|
|
||||||
|
await Task.WhenAll(
|
||||||
|
clientRepository.AddEntry(entry),
|
||||||
|
serverRepository.AddEntry(entry)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteEntry(TimeEntry entry) {
|
||||||
|
await Task.WhenAll(
|
||||||
|
clientRepository.DeleteEntry(entry),
|
||||||
|
serverRepository.DeleteEntry(entry)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct UpdatingEntriesResponse {
|
||||||
|
|
||||||
|
public bool NewBatch { get; init; }
|
||||||
|
|
||||||
|
public TimeEntry? Entry { get; init; }
|
||||||
|
|
||||||
|
}
|
||||||
101
src/WorkTime.Mobile/Services/HttpService.cs
Normal file
101
src/WorkTime.Mobile/Services/HttpService.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using WorkTime.Shared.Services;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile.Services;
|
||||||
|
|
||||||
|
public interface IHttpService {
|
||||||
|
Task<HttpResponse<TResult>> SendRequest<TResult>(HttpMethod method, [StringSyntax(StringSyntaxAttribute.Uri)] string uri, object? body = null);
|
||||||
|
|
||||||
|
Task<HttpResponse> SendRequest(HttpMethod method, [StringSyntax(StringSyntaxAttribute.Uri)] string uri, object? body = null);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class HttpService(HttpClient client, IAuthService authService) : IHttpService {
|
||||||
|
|
||||||
|
private InsecureHttpService? _service;
|
||||||
|
|
||||||
|
public async Task<HttpResponse<TResult>> SendRequest<TResult>(HttpMethod method, string uri, object? body = null) {
|
||||||
|
var service = await GetInternalService();
|
||||||
|
return await service.SendRequest<TResult>(method, uri, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HttpResponse> SendRequest(HttpMethod method, string uri, object? body = null) {
|
||||||
|
var service = await GetInternalService();
|
||||||
|
return await service.SendRequest(method, uri, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IHttpService> GetInternalService() {
|
||||||
|
if (_service is null) {
|
||||||
|
var id = await authService.GetCurrentUserId();
|
||||||
|
client.DefaultRequestHeaders.Add(IAuthService.HeaderName, id.ToString());
|
||||||
|
_service = new InsecureHttpService(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class InsecureHttpService(HttpClient client) : IHttpService {
|
||||||
|
|
||||||
|
public async Task<HttpResponse<TResult>> SendRequest<TResult>(HttpMethod method, string uri, object? body = null) {
|
||||||
|
try {
|
||||||
|
var request = new HttpRequestMessage(method, uri);
|
||||||
|
if (body is not null)
|
||||||
|
request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
return new HttpResponse<TResult> {
|
||||||
|
Result = typeof(TResult) == typeof(string) ? (TResult)(object)responseContent : JsonSerializer.Deserialize<TResult>(responseContent),
|
||||||
|
ResponseCode = response.StatusCode,
|
||||||
|
IsSuccessful = response.IsSuccessStatusCode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception) {
|
||||||
|
return new() {
|
||||||
|
IsSuccessful = false,
|
||||||
|
ResponseCode = HttpStatusCode.InternalServerError
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HttpResponse> SendRequest(HttpMethod method, string uri, object? body = null) {
|
||||||
|
try {
|
||||||
|
var request = new HttpRequestMessage(method, uri);
|
||||||
|
if (body is not null)
|
||||||
|
request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
return new HttpResponse {
|
||||||
|
ResponseCode = response.StatusCode,
|
||||||
|
IsSuccessful = response.IsSuccessStatusCode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception) {
|
||||||
|
return new() {
|
||||||
|
IsSuccessful = false,
|
||||||
|
ResponseCode = HttpStatusCode.InternalServerError
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct HttpResponse<TResult> {
|
||||||
|
public TResult? Result { get; init; }
|
||||||
|
public HttpStatusCode ResponseCode { get; init; }
|
||||||
|
public bool IsSuccessful { get; init; }
|
||||||
|
|
||||||
|
public static implicit operator TResult?(HttpResponse<TResult> response) {
|
||||||
|
return response.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct HttpResponse {
|
||||||
|
public HttpStatusCode ResponseCode { get; init; }
|
||||||
|
public bool IsSuccessful { get; init; }
|
||||||
|
}
|
||||||
77
src/WorkTime.Mobile/ViewModels/CaptureViewModel.cs
Normal file
77
src/WorkTime.Mobile/ViewModels/CaptureViewModel.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using WorkTime.Mobile.Services;
|
||||||
|
using WorkTime.Shared.Models;
|
||||||
|
using WorkTime.Shared.Services;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile.ViewModels;
|
||||||
|
|
||||||
|
public partial class CaptureViewModel(EntryService entryService, IAuthService authService) : ObservableObject {
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial ObservableCollection<TimeEntry> Entries { get; set; } = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial TimeEntryType CurrentAction { get; set; } = TimeEntryType.Login;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial bool CurrentlyMoba { get; set; }
|
||||||
|
|
||||||
|
public string CurrentActionName => CurrentAction.GetActionName();
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task OnDateSelected(DateOnly date) {
|
||||||
|
await foreach (var entryResponse in entryService.GetEntries(date)) {
|
||||||
|
if (entryResponse.NewBatch)
|
||||||
|
Entries.Clear();
|
||||||
|
|
||||||
|
if (entryResponse.Entry is not null)
|
||||||
|
Entries.Add(entryResponse.Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateCurrentAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task RegisterEntry(TimeEntry? entry = null) {
|
||||||
|
entry ??= new TimeEntry {
|
||||||
|
Type = CurrentAction,
|
||||||
|
IsMoba = CurrentlyMoba,
|
||||||
|
Owner = await authService.GetCurrentUserId()
|
||||||
|
};
|
||||||
|
|
||||||
|
var insertIndex = Entries.Index()
|
||||||
|
.LastOrDefault(e => e.Item.Timestamp < entry.Timestamp)
|
||||||
|
.Index;
|
||||||
|
|
||||||
|
if (Entries.Count == insertIndex + 1 || Entries.Count == 0)
|
||||||
|
Entries.Add(entry);
|
||||||
|
else Entries.Insert(insertIndex + 1, entry);
|
||||||
|
|
||||||
|
await entryService.AddEntry(entry);
|
||||||
|
UpdateCurrentAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCurrentAction() {
|
||||||
|
if (Entries.Count == 0) {
|
||||||
|
CurrentAction = TimeEntryType.Login;
|
||||||
|
CurrentlyMoba = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastEntry = Entries[^1];
|
||||||
|
CurrentAction = lastEntry.Type switch {
|
||||||
|
TimeEntryType.Login => TimeEntryType.Logout,
|
||||||
|
TimeEntryType.Logout => TimeEntryType.Login,
|
||||||
|
TimeEntryType.LoginDrive => TimeEntryType.LogoutDrive,
|
||||||
|
TimeEntryType.LogoutDrive => TimeEntryType.Login,
|
||||||
|
_ => TimeEntryType.Login
|
||||||
|
};
|
||||||
|
CurrentlyMoba = lastEntry is { Type: TimeEntryType.Login, IsMoba: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnCurrentActionChanged(TimeEntryType value) {
|
||||||
|
OnPropertyChanged(nameof(CurrentActionName));
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/WorkTime.Mobile/Views/Components/DateSelector.xaml
Normal file
25
src/WorkTime.Mobile/Views/Components/DateSelector.xaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:components="clr-namespace:WorkTime.Mobile.Views.Components"
|
||||||
|
x:Class="WorkTime.Mobile.Views.Components.DateSelector"
|
||||||
|
x:DataType="components:DateSelector"
|
||||||
|
x:Name="Component">
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
|
||||||
|
<Label
|
||||||
|
Grid.Column="0"
|
||||||
|
Text="Tag"
|
||||||
|
VerticalOptions="Center"/>
|
||||||
|
|
||||||
|
<DatePicker
|
||||||
|
Grid.Column="1"
|
||||||
|
Date="{Binding CurrentDate, Source={x:Reference Component}}"
|
||||||
|
MaximumDate="{Binding MaxDate, Source={x:Reference Component}}"
|
||||||
|
DateSelected="OnDateSelected"/>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</ContentView>
|
||||||
47
src/WorkTime.Mobile/Views/Components/DateSelector.xaml.cs
Normal file
47
src/WorkTime.Mobile/Views/Components/DateSelector.xaml.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile.Views.Components;
|
||||||
|
|
||||||
|
public partial class DateSelector : ContentView {
|
||||||
|
|
||||||
|
public static readonly BindableProperty CurrentDateProperty = BindableProperty.Create(
|
||||||
|
nameof(CurrentDate),
|
||||||
|
typeof(DateTime),
|
||||||
|
typeof(DateSelector),
|
||||||
|
DateTime.Now);
|
||||||
|
|
||||||
|
public static readonly BindableProperty MaxDateProperty = BindableProperty.Create(
|
||||||
|
nameof(MaxDate),
|
||||||
|
typeof(DateTime),
|
||||||
|
typeof(DateSelector),
|
||||||
|
DateTime.Now);
|
||||||
|
|
||||||
|
public static readonly BindableProperty CommandProperty = BindableProperty.Create(
|
||||||
|
nameof(Command),
|
||||||
|
typeof(IRelayCommand<DateOnly>),
|
||||||
|
typeof(DateSelector));
|
||||||
|
|
||||||
|
public DateTime CurrentDate {
|
||||||
|
get => (DateTime)GetValue(CurrentDateProperty);
|
||||||
|
set => SetValue(CurrentDateProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime MaxDate {
|
||||||
|
get => (DateTime)GetValue(MaxDateProperty);
|
||||||
|
set => SetValue(MaxDateProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRelayCommand<DateOnly>? Command {
|
||||||
|
get => (IRelayCommand<DateOnly>)GetValue(CommandProperty);
|
||||||
|
set => SetValue(CommandProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateSelector() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDateSelected(object? sender, DateChangedEventArgs e) {
|
||||||
|
var date = DateOnly.FromDateTime(CurrentDate);
|
||||||
|
Command?.Execute(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/WorkTime.Mobile/Views/Pages/CapturePage.xaml
Normal file
39
src/WorkTime.Mobile/Views/Pages/CapturePage.xaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:components="clr-namespace:WorkTime.Mobile.Views.Components"
|
||||||
|
xmlns:viewModels="clr-namespace:WorkTime.Mobile.ViewModels"
|
||||||
|
xmlns:models="clr-namespace:WorkTime.Shared.Models;assembly=WorkTime.Shared"
|
||||||
|
x:Class="WorkTime.Mobile.Views.Pages.CapturePage"
|
||||||
|
x:DataType="viewModels:CaptureViewModel"
|
||||||
|
Padding="24, 0, 24, 24">
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto,*,Auto">
|
||||||
|
|
||||||
|
<components:DateSelector
|
||||||
|
Grid.Row="0"
|
||||||
|
Command="{Binding DateSelectedCommand}"/>
|
||||||
|
|
||||||
|
<CollectionView
|
||||||
|
Grid.Row="1"
|
||||||
|
ItemsSource="{Binding Entries}">
|
||||||
|
<CollectionView.ItemTemplate>
|
||||||
|
<DataTemplate
|
||||||
|
x:DataType="models:TimeEntry">
|
||||||
|
<HorizontalStackLayout Spacing="5">
|
||||||
|
<Label Text="{Binding Timestamp}" />
|
||||||
|
<Label Text="{Binding Type}" />
|
||||||
|
</HorizontalStackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
</CollectionView.ItemTemplate>
|
||||||
|
</CollectionView>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
Grid.Row="2"
|
||||||
|
Text="{Binding CurrentActionName}"
|
||||||
|
Command="{Binding RegisterEntryCommand}"/>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</ContentPage>
|
||||||
15
src/WorkTime.Mobile/Views/Pages/CapturePage.xaml.cs
Normal file
15
src/WorkTime.Mobile/Views/Pages/CapturePage.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WorkTime.Mobile.ViewModels;
|
||||||
|
|
||||||
|
namespace WorkTime.Mobile.Views.Pages;
|
||||||
|
|
||||||
|
public partial class CapturePage : ContentPage {
|
||||||
|
public CapturePage(CaptureViewModel model) {
|
||||||
|
InitializeComponent();
|
||||||
|
BindingContext = model;
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/WorkTime.Mobile/WorkTime.Mobile.csproj
Normal file
76
src/WorkTime.Mobile/WorkTime.Mobile.csproj
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
|
||||||
|
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
|
||||||
|
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||||
|
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
|
||||||
|
|
||||||
|
<!-- Note for MacCatalyst:
|
||||||
|
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
|
||||||
|
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
|
||||||
|
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
|
||||||
|
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
|
||||||
|
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->
|
||||||
|
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>WorkTime.Mobile</RootNamespace>
|
||||||
|
<UseMaui>true</UseMaui>
|
||||||
|
<SingleProject>true</SingleProject>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>preview</LangVersion>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
|
||||||
|
<!-- Display name -->
|
||||||
|
<ApplicationTitle>Zeiterfassung</ApplicationTitle>
|
||||||
|
|
||||||
|
<!-- App Identifier -->
|
||||||
|
<ApplicationId>de.leon-hoppe.worktime.mobile</ApplicationId>
|
||||||
|
|
||||||
|
<!-- Versions -->
|
||||||
|
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||||
|
<ApplicationVersion>1</ApplicationVersion>
|
||||||
|
|
||||||
|
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||||
|
<WindowsPackageType>None</WindowsPackageType>
|
||||||
|
|
||||||
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
|
||||||
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
|
||||||
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
|
||||||
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||||
|
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- App Icon -->
|
||||||
|
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4"/>
|
||||||
|
|
||||||
|
<!-- Splash Screen -->
|
||||||
|
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128"/>
|
||||||
|
|
||||||
|
<!-- Images -->
|
||||||
|
<MauiImage Include="Resources\Images\*"/>
|
||||||
|
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185"/>
|
||||||
|
|
||||||
|
<!-- Custom Fonts -->
|
||||||
|
<MauiFont Include="Resources\Fonts\*"/>
|
||||||
|
|
||||||
|
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||||
|
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AathifMahir.Maui.MauiIcons.Material" Version="4.0.0" />
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)"/>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\WorkTime.Shared\WorkTime.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
110
src/WorkTime.ServiceDefaults/Extensions.cs
Normal file
110
src/WorkTime.ServiceDefaults/Extensions.cs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.ServiceDiscovery;
|
||||||
|
using OpenTelemetry;
|
||||||
|
using OpenTelemetry.Metrics;
|
||||||
|
using OpenTelemetry.Trace;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
|
||||||
|
// This project should be referenced by each service project in your solution.
|
||||||
|
// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
|
||||||
|
public static class Extensions {
|
||||||
|
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder)
|
||||||
|
where TBuilder : IHostApplicationBuilder {
|
||||||
|
builder.ConfigureOpenTelemetry();
|
||||||
|
|
||||||
|
builder.AddDefaultHealthChecks();
|
||||||
|
|
||||||
|
builder.Services.AddServiceDiscovery();
|
||||||
|
|
||||||
|
builder.Services.ConfigureHttpClientDefaults(http => {
|
||||||
|
// Turn on resilience by default
|
||||||
|
http.AddStandardResilienceHandler();
|
||||||
|
|
||||||
|
// Turn on service discovery by default
|
||||||
|
http.AddServiceDiscovery();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Uncomment the following to restrict the allowed schemes for service discovery.
|
||||||
|
// builder.Services.Configure<ServiceDiscoveryOptions>(options =>
|
||||||
|
// {
|
||||||
|
// options.AllowedSchemes = ["https"];
|
||||||
|
// });
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder)
|
||||||
|
where TBuilder : IHostApplicationBuilder {
|
||||||
|
builder.Logging.AddOpenTelemetry(logging => {
|
||||||
|
logging.IncludeFormattedMessage = true;
|
||||||
|
logging.IncludeScopes = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddOpenTelemetry()
|
||||||
|
.WithMetrics(metrics => {
|
||||||
|
metrics.AddAspNetCoreInstrumentation()
|
||||||
|
.AddHttpClientInstrumentation()
|
||||||
|
.AddRuntimeInstrumentation();
|
||||||
|
})
|
||||||
|
.WithTracing(tracing => {
|
||||||
|
tracing.AddSource(builder.Environment.ApplicationName)
|
||||||
|
.AddAspNetCoreInstrumentation()
|
||||||
|
// Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
|
||||||
|
//.AddGrpcClientInstrumentation()
|
||||||
|
.AddHttpClientInstrumentation();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.AddOpenTelemetryExporters();
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builder)
|
||||||
|
where TBuilder : IHostApplicationBuilder {
|
||||||
|
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
|
||||||
|
|
||||||
|
if (useOtlpExporter) {
|
||||||
|
builder.Services.AddOpenTelemetry().UseOtlpExporter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
|
||||||
|
//if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
|
||||||
|
//{
|
||||||
|
// builder.Services.AddOpenTelemetry()
|
||||||
|
// .UseAzureMonitor();
|
||||||
|
//}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder)
|
||||||
|
where TBuilder : IHostApplicationBuilder {
|
||||||
|
builder.Services.AddHealthChecks()
|
||||||
|
// Add a default liveness check to ensure app is responsive
|
||||||
|
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebApplication MapDefaultEndpoints(this WebApplication app) {
|
||||||
|
// Adding health checks endpoints to applications in non-development environments has security implications.
|
||||||
|
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
|
||||||
|
if (app.Environment.IsDevelopment()) {
|
||||||
|
// All health checks must pass for app to be considered ready to accept traffic after starting
|
||||||
|
app.MapHealthChecks("/health");
|
||||||
|
|
||||||
|
// Only health checks tagged with the "live" tag must pass for app to be considered alive
|
||||||
|
app.MapHealthChecks("/alive", new HealthCheckOptions {
|
||||||
|
Predicate = r => r.Tags.Contains("live")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
98
src/WorkTime.ServiceDefaults/Results/LogicResult.cs
Normal file
98
src/WorkTime.ServiceDefaults/Results/LogicResult.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace WorkTime.ServiceDefaults.Results;
|
||||||
|
|
||||||
|
public interface ILogicResult<TResult, TError> {
|
||||||
|
|
||||||
|
public TResult? Result { get; init; }
|
||||||
|
|
||||||
|
public TError? Error { get; init; }
|
||||||
|
|
||||||
|
public bool IsSuccessful => Result is not null && Error is null;
|
||||||
|
|
||||||
|
IResult MapResult();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ILogicResult<TResult> : ILogicResult<TResult, IResult>;
|
||||||
|
|
||||||
|
public interface ILogicResult : ILogicResult<object>;
|
||||||
|
|
||||||
|
public readonly struct LogicResult<TResult, TError> : ILogicResult<TResult, TError> {
|
||||||
|
|
||||||
|
public TResult? Result { get; init; }
|
||||||
|
public TError? Error { get; init; }
|
||||||
|
|
||||||
|
public bool IsSuccessful => Result is not null && Error is null;
|
||||||
|
|
||||||
|
public static implicit operator LogicResult<TResult, TError>(TResult result) {
|
||||||
|
return new LogicResult<TResult, TError> {
|
||||||
|
Result = result
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator LogicResult<TResult, TError>(TError error) {
|
||||||
|
return new LogicResult<TResult, TError> {
|
||||||
|
Error = error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public IResult MapResult() {
|
||||||
|
if (!IsSuccessful)
|
||||||
|
return Microsoft.AspNetCore.Http.Results.Problem();
|
||||||
|
|
||||||
|
return Microsoft.AspNetCore.Http.Results.Ok(Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct LogicResult<TResult> : ILogicResult<TResult> {
|
||||||
|
|
||||||
|
public TResult? Result { get; init; }
|
||||||
|
public IResult? Error { get; init; }
|
||||||
|
|
||||||
|
public bool IsSuccessful => Error is null;
|
||||||
|
|
||||||
|
public static implicit operator LogicResult<TResult>(TResult result) {
|
||||||
|
return new LogicResult<TResult> {
|
||||||
|
Result = result
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogicResult() { }
|
||||||
|
|
||||||
|
public LogicResult(IResult error) {
|
||||||
|
Error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IResult MapResult() {
|
||||||
|
if (!IsSuccessful) {
|
||||||
|
return Error!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Microsoft.AspNetCore.Http.Results.Ok(Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct LogicResult : ILogicResult {
|
||||||
|
|
||||||
|
public object? Result { get; init; }
|
||||||
|
public IResult? Error { get; init; }
|
||||||
|
|
||||||
|
public bool IsSuccessful => Error is null;
|
||||||
|
|
||||||
|
public LogicResult() { }
|
||||||
|
|
||||||
|
public LogicResult(IResult error) {
|
||||||
|
Error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IResult MapResult() {
|
||||||
|
if (!IsSuccessful) {
|
||||||
|
return Error!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Microsoft.AspNetCore.Http.Results.Ok(Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/WorkTime.ServiceDefaults/WorkTime.ServiceDefaults.csproj
Normal file
22
src/WorkTime.ServiceDefaults/WorkTime.ServiceDefaults.csproj
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsAspireSharedProject>true</IsAspireSharedProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||||
|
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0"/>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.0.0"/>
|
||||||
|
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0"/>
|
||||||
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0"/>
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0"/>
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0"/>
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
24
src/WorkTime.Shared/Models/TimeEntry.cs
Normal file
24
src/WorkTime.Shared/Models/TimeEntry.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace WorkTime.Shared.Models;
|
||||||
|
|
||||||
|
public class TimeEntry {
|
||||||
|
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public required Guid Owner { get; set; }
|
||||||
|
|
||||||
|
public required TimeEntryType Type { get; set; }
|
||||||
|
|
||||||
|
public DateTime Timestamp { get; set; } = DateTime.Now;
|
||||||
|
|
||||||
|
public bool IsMoba { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TimeEntryType {
|
||||||
|
Login,
|
||||||
|
Logout,
|
||||||
|
LoginDrive,
|
||||||
|
LogoutDrive
|
||||||
|
}
|
||||||
17
src/WorkTime.Shared/Repositories/IEntryRepository.cs
Normal file
17
src/WorkTime.Shared/Repositories/IEntryRepository.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using WorkTime.Shared.Models;
|
||||||
|
|
||||||
|
namespace WorkTime.Shared.Repositories;
|
||||||
|
|
||||||
|
public interface IEntryRepository {
|
||||||
|
|
||||||
|
Task<TimeEntry[]> GetAllEntries(Guid owner);
|
||||||
|
|
||||||
|
Task<TimeEntry[]> GetEntries(Guid owner, DateOnly date);
|
||||||
|
|
||||||
|
Task<TimeEntry?> GetEntry(int id);
|
||||||
|
|
||||||
|
Task AddEntry(TimeEntry entry);
|
||||||
|
|
||||||
|
Task DeleteEntry(TimeEntry entry);
|
||||||
|
|
||||||
|
}
|
||||||
11
src/WorkTime.Shared/Services/IAuthService.cs
Normal file
11
src/WorkTime.Shared/Services/IAuthService.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace WorkTime.Shared.Services;
|
||||||
|
|
||||||
|
public interface IAuthService {
|
||||||
|
|
||||||
|
public const string HeaderName = "Authentication";
|
||||||
|
|
||||||
|
public Task<bool> IsAuthenticated();
|
||||||
|
|
||||||
|
public Task<Guid> GetCurrentUserId();
|
||||||
|
|
||||||
|
}
|
||||||
13
src/WorkTime.Shared/WorkTime.Shared.csproj
Normal file
13
src/WorkTime.Shared/WorkTime.Shared.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
17
src/WorkTime.WebMobile/.dockerignore
Normal file
17
src/WorkTime.WebMobile/.dockerignore
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Ignore the node_modules directory
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Ignore the dist directory
|
||||||
|
www
|
||||||
|
|
||||||
|
# Ignore the .git directory
|
||||||
|
.git
|
||||||
|
|
||||||
|
# Ignore the .gitignore file
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Ignore the .dockerignore file
|
||||||
|
.dockerignore
|
||||||
|
Dockerfile
|
||||||
|
|
||||||
|
.vscode/*
|
||||||
70
src/WorkTime.WebMobile/.gitignore
vendored
Normal file
70
src/WorkTime.WebMobile/.gitignore
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Specifies intentionally untracked files to ignore when using Git
|
||||||
|
# http://git-scm.com/docs/gitignore
|
||||||
|
|
||||||
|
*~
|
||||||
|
*.sw[mnpcod]
|
||||||
|
.tmp
|
||||||
|
*.tmp
|
||||||
|
*.tmp.*
|
||||||
|
UserInterfaceState.xcuserstate
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
*.log
|
||||||
|
log.txt
|
||||||
|
|
||||||
|
|
||||||
|
/.sourcemaps
|
||||||
|
/.versions
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# Ionic
|
||||||
|
/.ionic
|
||||||
|
/www
|
||||||
|
/platforms
|
||||||
|
/plugins
|
||||||
|
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular
|
||||||
|
/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
|
/.nx
|
||||||
|
/.nx/cache
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user