Compare commits
9 Commits
v0.4.0
...
feature/ba
| Author | SHA1 | Date | |
|---|---|---|---|
| 44da4932aa | |||
| c2f89b1b09 | |||
| 3a118a9b34 | |||
| a98709b0a1 | |||
| a1ba2f9571 | |||
| 55be2f1e88 | |||
| 1e5b17c4ef | |||
| 204f1a3e1d | |||
| 27119c798d |
@@ -1,17 +1,25 @@
|
||||
# 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/*
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/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
|
||||
# http://git-scm.com/docs/gitignore
|
||||
### Csharp template
|
||||
## 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
|
||||
|
||||
*~
|
||||
*.sw[mnpcod]
|
||||
.tmp
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.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.*
|
||||
UserInterfaceState.xcuserstate
|
||||
$RECYCLE.BIN/
|
||||
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
log.txt
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
/.sourcemaps
|
||||
/.versions
|
||||
/coverage
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Ionic
|
||||
/.ionic
|
||||
/www
|
||||
/platforms
|
||||
/plugins
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# 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/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Miscellaneous
|
||||
/.angular
|
||||
/.angular/cache
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.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/
|
||||
/.nx
|
||||
/.nx/cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
connect.lock/
|
||||
coverage/
|
||||
libpeerconnection.log/
|
||||
npm-debug.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:
|
||||
- install
|
||||
- lint
|
||||
- build
|
||||
- test
|
||||
- publish
|
||||
|
||||
install:
|
||||
install-mobile:
|
||||
stage: install
|
||||
image: node:lts-alpine
|
||||
before_script:
|
||||
- cd src/WorkTime.WebMobile
|
||||
script:
|
||||
- npm install --prefer-offline
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- package.json
|
||||
- src/WorkTime.WebMobile/package.json
|
||||
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
|
||||
needs: ["install"]
|
||||
image: node:lts-alpine
|
||||
needs: ["install-mobile"]
|
||||
before_script:
|
||||
- cd src/WorkTime.WebMobile
|
||||
script:
|
||||
- npm run lint
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- package.json
|
||||
- src/WorkTime.WebMobile/package.json
|
||||
paths:
|
||||
- node_modules
|
||||
- src/WorkTime.WebMobile/node_modules
|
||||
policy: pull
|
||||
|
||||
build:
|
||||
build-mobile:
|
||||
stage: build
|
||||
needs: ["lint"]
|
||||
image: node:lts-alpine
|
||||
needs: ["lint-mobile"]
|
||||
before_script:
|
||||
- cd src/WorkTime.WebMobile
|
||||
script:
|
||||
- npm run build
|
||||
artifacts:
|
||||
paths:
|
||||
- $CI_PROJECT_DIR/www
|
||||
- $CI_PROJECT_DIR/src/WorkTime.WebMobile/www
|
||||
expire_in: 10 minutes
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- package.json
|
||||
- src/WorkTime.WebMobile/package.json
|
||||
paths:
|
||||
- node_modules
|
||||
- src/WorkTime.WebMobile/node_modules
|
||||
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
|
||||
needs: ["build"]
|
||||
image: docker:latest
|
||||
services:
|
||||
- name: docker:dind
|
||||
@@ -57,8 +109,8 @@ publish:
|
||||
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:$VERSION -t registry.leon-hoppe.de/leon.hoppe/worktime:latest .
|
||||
- docker push registry.leon-hoppe.de/leon.hoppe/worktime:$VERSION
|
||||
- docker push 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/api:$VERSION
|
||||
- docker push registry.leon-hoppe.de/leon.hoppe/worktime/api:latest
|
||||
only:
|
||||
- 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