Added Main Project
This commit is contained in:
parent
3272781a12
commit
724c410f36
454
.gitignore
vendored
Normal file
454
.gitignore
vendored
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
## 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/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# 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/
|
||||||
|
|
||||||
|
# Tye
|
||||||
|
.tye/
|
||||||
|
|
||||||
|
# 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_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# 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 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/
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
##
|
||||||
|
## Visual studio for Mac
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
# globs
|
||||||
|
Makefile.in
|
||||||
|
*.userprefs
|
||||||
|
*.usertasks
|
||||||
|
config.make
|
||||||
|
config.status
|
||||||
|
aclocal.m4
|
||||||
|
install-sh
|
||||||
|
autom4te.cache/
|
||||||
|
*.tar.gz
|
||||||
|
tarballs/
|
||||||
|
test-results/
|
||||||
|
|
||||||
|
# Mac bundle stuff
|
||||||
|
*.dmg
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
##
|
||||||
|
## Visual Studio Code
|
||||||
|
##
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
37
TeamsNetphoneLink.sln
Normal file
37
TeamsNetphoneLink.sln
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.10.35027.167
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamsLocalLibary", "TeamsLocalAPI\TeamsLocalLibary.csproj", "{0075EE3E-C82F-4EAA-A9A3-32BED89C90EA}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamsNetphoneLinkWPF", "TeamsNetphoneLinkWPF\TeamsNetphoneLinkWPF.csproj", "{5904F1F3-755E-4D10-8906-5FC69F85F389}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsNetphoneLinkUpdater", "TeamsNetphoneLinkUpdater\TeamsNetphoneLinkUpdater.csproj", "{BB18A636-95F8-4555-9E96-31575E9CB6A2}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{0075EE3E-C82F-4EAA-A9A3-32BED89C90EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0075EE3E-C82F-4EAA-A9A3-32BED89C90EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0075EE3E-C82F-4EAA-A9A3-32BED89C90EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0075EE3E-C82F-4EAA-A9A3-32BED89C90EA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{5904F1F3-755E-4D10-8906-5FC69F85F389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5904F1F3-755E-4D10-8906-5FC69F85F389}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5904F1F3-755E-4D10-8906-5FC69F85F389}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5904F1F3-755E-4D10-8906-5FC69F85F389}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BB18A636-95F8-4555-9E96-31575E9CB6A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BB18A636-95F8-4555-9E96-31575E9CB6A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BB18A636-95F8-4555-9E96-31575E9CB6A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BB18A636-95F8-4555-9E96-31575E9CB6A2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {32340761-08D0-41C6-A769-B606A5B73F01}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
63
TeamsNetphoneLinkWPF/App.config
Normal file
63
TeamsNetphoneLinkWPF/App.config
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<configSections>
|
||||||
|
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||||
|
<section name="TeamsNetphoneLink.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
|
||||||
|
<section name="TeamsNetphoneLink.Settings1" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
|
||||||
|
<section name="TeamsNetphoneLinkWPF.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
|
||||||
|
</sectionGroup>
|
||||||
|
</configSections>
|
||||||
|
<userSettings>
|
||||||
|
<TeamsNetphoneLink.Settings>
|
||||||
|
<setting name="Token" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="TeamsPermission" serializeAs="String">
|
||||||
|
<value>False</value>
|
||||||
|
</setting>
|
||||||
|
<setting name="TenantID" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="AppID" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="SaveEntraCredentials" serializeAs="String">
|
||||||
|
<value>False</value>
|
||||||
|
</setting>
|
||||||
|
<setting name="UseGraphForMeetingState" serializeAs="String">
|
||||||
|
<value>False</value>
|
||||||
|
</setting>
|
||||||
|
<setting name="CurrentVersion" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
</TeamsNetphoneLink.Settings>
|
||||||
|
<TeamsNetphoneLink.Settings1>
|
||||||
|
<setting name="Token" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="TeamsPermission" serializeAs="String">
|
||||||
|
<value>True</value>
|
||||||
|
</setting>
|
||||||
|
<setting name="TenatID" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="AppID" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
</TeamsNetphoneLink.Settings1>
|
||||||
|
<TeamsNetphoneLinkWPF.Settings>
|
||||||
|
<setting name="Token" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="TeamsPermission" serializeAs="String">
|
||||||
|
<value>True</value>
|
||||||
|
</setting>
|
||||||
|
<setting name="TenatID" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="AppID" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
</TeamsNetphoneLinkWPF.Settings>
|
||||||
|
</userSettings>
|
||||||
|
</configuration>
|
9
TeamsNetphoneLinkWPF/App.xaml
Normal file
9
TeamsNetphoneLinkWPF/App.xaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<Application x:Class="TeamsNetphoneLink.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:TeamsNetphoneLink.WPF"
|
||||||
|
>
|
||||||
|
<Application.Resources>
|
||||||
|
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
79
TeamsNetphoneLinkWPF/App.xaml.cs
Normal file
79
TeamsNetphoneLinkWPF/App.xaml.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using Azure.Identity.Broker;
|
||||||
|
using Microsoft.Identity.Client;
|
||||||
|
using Microsoft.Identity.Client.NativeInterop;
|
||||||
|
using System;
|
||||||
|
using System.Configuration;
|
||||||
|
using System.Data;
|
||||||
|
using System.Printing;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using TeamsNetphoneLink.WPF;
|
||||||
|
using TeamsNetphoneLink.WPF.MVVM;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool AllocConsole();
|
||||||
|
|
||||||
|
public App()
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
// Konsole für Debugging-Zwecke öffnen
|
||||||
|
AllocConsole();
|
||||||
|
#endif
|
||||||
|
// Initialisierung der Anwendungskomponenten
|
||||||
|
InitializeApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeApplication()
|
||||||
|
{
|
||||||
|
UpdateCheck.UpgradeSettingsIfRequired();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Erstellen der benötigten Instanzen
|
||||||
|
var TeamsLocalAPI = new Teams.TeamsLocalAPI();
|
||||||
|
var Netphone = new Netphone.NetPhoneEvents();
|
||||||
|
var TeamsGraph = new Teams.TeamsGraph();
|
||||||
|
var dashboardViewModel = new DashboardViewModel();
|
||||||
|
var LogEntries = new LogViewModel();
|
||||||
|
var finishPanelViewModel = new FinishPanelViewModel();
|
||||||
|
|
||||||
|
var latestTag = UpdateCheck.GetLatestReleaseTagAsync().GetAwaiter().GetResult();
|
||||||
|
var currentTag = UpdateCheck.GetCurrentVersion();
|
||||||
|
dashboardViewModel.VersionText = currentTag;
|
||||||
|
|
||||||
|
// Erstellen der Synchronisationsinstanz
|
||||||
|
var sync = new Syncronisation(Netphone, TeamsLocalAPI, TeamsGraph, dashboardViewModel, LogEntries, finishPanelViewModel);
|
||||||
|
|
||||||
|
// Erstellen und Anzeigen des Dashboard-Fensters
|
||||||
|
var dashboard = new WPF.DashboardWindow(sync);
|
||||||
|
dashboard.Show();
|
||||||
|
|
||||||
|
if (UpdateCheck.IsVersionLower(currentTag, latestTag))
|
||||||
|
{
|
||||||
|
UpdateCheck.UpdateAvailable = true;
|
||||||
|
dashboardViewModel.VersionText = String.Format("Version {0} verfügbar!", latestTag);
|
||||||
|
dashboardViewModel.VersionBackground = new SolidColorBrush(Colors.Orange);
|
||||||
|
|
||||||
|
// Erstellen und Anzeigen des Update-Fensters
|
||||||
|
var updateWindow = new WPF.UpdateWindow(currentTag, latestTag);
|
||||||
|
updateWindow.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asynchrone Initialisierung von Netphone und TeamsLocalAPI
|
||||||
|
sync.InitializeTeamsGraphAsync();
|
||||||
|
sync.InitializeNetphoneAsync();
|
||||||
|
sync.InitializeTeamsLocalAPIAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
TeamsNetphoneLinkWPF/AssemblyInfo.cs
Normal file
10
TeamsNetphoneLinkWPF/AssemblyInfo.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
121
TeamsNetphoneLinkWPF/Communication/ClientSdkEvents.cs
Normal file
121
TeamsNetphoneLinkWPF/Communication/ClientSdkEvents.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using CLMGRLib;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.Netphone
|
||||||
|
{
|
||||||
|
public class ClientSdkEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public CLMgrMessage Msg;
|
||||||
|
public int Param;
|
||||||
|
|
||||||
|
public ClientSdkEventArgs(CLMgrMessage msg, int param)
|
||||||
|
{
|
||||||
|
Msg = msg;
|
||||||
|
Param = param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void LineManagerMessageHandler(ClientSdkEventArgs e);
|
||||||
|
|
||||||
|
public class ClientSdkEventSink
|
||||||
|
{
|
||||||
|
private ClientLineMgrClass ConnectedLineManager;
|
||||||
|
private IClientLineMgrEventsPub_PubOnLineMgrNotificationEventHandler EventHandler;
|
||||||
|
private LineManagerMessageHandler LineManagerMessageDelegateOfForm;
|
||||||
|
|
||||||
|
public ClientSdkEventSink()
|
||||||
|
{
|
||||||
|
EventHandler = new IClientLineMgrEventsPub_PubOnLineMgrNotificationEventHandler(clmgr_EventSink);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect(ClientLineMgrClass lineManager, LineManagerMessageHandler lineManagerMessageDelegateOfForm)
|
||||||
|
{
|
||||||
|
ConnectedLineManager = lineManager;
|
||||||
|
LineManagerMessageDelegateOfForm = lineManagerMessageDelegateOfForm;
|
||||||
|
|
||||||
|
//add eventhandler for the PubOnlineMgrNotification Events
|
||||||
|
ConnectedLineManager.PubOnLineMgrNotification += EventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
//remove eventhandler for the PubOnlineMgrNotification Events
|
||||||
|
ConnectedLineManager.PubOnLineMgrNotification -= EventHandler;
|
||||||
|
ConnectedLineManager = null;
|
||||||
|
LineManagerMessageDelegateOfForm = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clmgr_EventSink(int msg, int param)
|
||||||
|
{
|
||||||
|
//this method receives the COM events from the client line manger
|
||||||
|
if ((LineManagerMessageDelegateOfForm != null))
|
||||||
|
{
|
||||||
|
LineManagerMessageDelegateOfForm(new ClientSdkEventArgs((CLMgrMessage)msg, param));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CLMgrMessage
|
||||||
|
{
|
||||||
|
CLMgrLineStateChangedMessage = 0, //state of at least one line has changed
|
||||||
|
CLMgrLineSelectionChangedMessage = 1, //line in focus has changed
|
||||||
|
CLMgrLineDetailsChangedMessage = 2, //details of at least one line have changed
|
||||||
|
CLMgrCallDetailsMessage = 4, //details of last call are available, post mortem for logging purpose
|
||||||
|
CLMgrServerDownMessage = 5, //server goes down, keep line manager, wait for ServerUp message
|
||||||
|
CLMgrServerUpMessage = 6, //server is up again, keep interfaces to line manger
|
||||||
|
CLMgrWaveDeviceChanged = 7, //speaker / micro has been switched on / off
|
||||||
|
CLMgrGroupCallNotificationMessage = 8, //notification about group call
|
||||||
|
CLMgrNumberOfLinesChangedMessage = 10, //the number of lines has changed
|
||||||
|
CLMgrClientShutDownRequest = 11, //Client Line Manager requests client to shutdown and release all interfaces
|
||||||
|
CLMgrLineStateChangedMessageEx = 28, //state of certain line has changed, lParam: LOWORD: line index of line that changed its state (starting with 0) HIWORD: new state of this line
|
||||||
|
CLMgrSIPRegistrationStateChanged = 30, //registration state of SIP account has changed
|
||||||
|
//lParam: LOBYTE: Account index
|
||||||
|
// HIBYTE: new state
|
||||||
|
CLMgrWaveFilePlayed = 31, //wave file playback finished
|
||||||
|
//lParam: line index;
|
||||||
|
//if -1, the message is related to a LineMgr function PlaySoundFile or PlayToRtp
|
||||||
|
//if >=0 the message is related to a line function PlaySoundFile of line with this index
|
||||||
|
PubCLMgrFirstDataReceived = 32 //first RTP data received on line, might be silence
|
||||||
|
//lParam: line index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LineState
|
||||||
|
{
|
||||||
|
Inactive = 0, //line is inactive
|
||||||
|
HookOffInternal = 1, //off hook, internal dialtone
|
||||||
|
HookOffExternal = 2, //off hook, external dialtone
|
||||||
|
Ringing = 3, //incoming call, ringing
|
||||||
|
Dialing = 4, //outgoing call, we are dialing, no sound
|
||||||
|
Alerting = 5, //outgoing call, alerting = ringing on destination
|
||||||
|
Knocking = 6, //outgoing call, knocking = second call ringing on destination
|
||||||
|
Busy = 7, //outgoing call, destination is busy
|
||||||
|
Active = 8, //incoming / outgoing call, logical and physical connection is established
|
||||||
|
OnHold = 9, //incoming / outgoing call, logical connection is established, destination gets music on hold
|
||||||
|
ConferenceActive = 10, //incoming / outgoing conference, logical and physical connection is established
|
||||||
|
ConferenceOnHold = 11, //incoming / outgoing conference, logical connection is established, not physcically connected
|
||||||
|
Terminated = 12, //incoming / outgoing connection / call has been disconnected
|
||||||
|
Transferring = 13, //special LSOnHold, call is awaiting to be transferred, peer gets special music on hold
|
||||||
|
Disabled = 14 //special LSInactive: wrap up time
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DisconnectReason
|
||||||
|
{
|
||||||
|
Normal = 0,
|
||||||
|
Busy = 1,
|
||||||
|
Rejected = 2,
|
||||||
|
Cancelled = 3,
|
||||||
|
Transferred = 4,
|
||||||
|
JoinedConference = 5,
|
||||||
|
NoAnswer = 6,
|
||||||
|
TooLate = 7,
|
||||||
|
DirectCallImpossible = 8,
|
||||||
|
WrongNumber = 9,
|
||||||
|
Unreachable = 10,
|
||||||
|
CallDiverted = 11,
|
||||||
|
CallRoutingFailed = 12,
|
||||||
|
PermissionDenied = 13,
|
||||||
|
NetworkCongestion = 14,
|
||||||
|
NoChannelAvailable = 15,
|
||||||
|
NumberChanged = 16,
|
||||||
|
IncompatibleDestination = 17
|
||||||
|
}
|
||||||
|
}
|
200
TeamsNetphoneLinkWPF/Communication/NetPhone.cs
Normal file
200
TeamsNetphoneLinkWPF/Communication/NetPhone.cs
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
using CLMGRLib;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using TeamsLocalLibary.EventArgs;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.Netphone
|
||||||
|
{
|
||||||
|
public delegate void LineStateChangedEventHandler(LineState newLineState);
|
||||||
|
public delegate void LoggedInStateEventHandler(bool loggedInState);
|
||||||
|
|
||||||
|
public class NetPhoneEvents : IDisposable
|
||||||
|
{
|
||||||
|
private ClientLine SelectedLine;
|
||||||
|
private ClientLineMgrClass pCLMgr;
|
||||||
|
private ClientSdkEventSink MyEventSink;
|
||||||
|
private LineState LastLineState;
|
||||||
|
private bool lastLoggedInState;
|
||||||
|
|
||||||
|
private CancellationTokenSource aliveCheckTokenSource;
|
||||||
|
private CancellationToken aliveCheckToken;
|
||||||
|
|
||||||
|
private Dictionary<LineState, List<LineStateChangedEventHandler>> lineStateEvents = new Dictionary<LineState, List<LineStateChangedEventHandler>>();
|
||||||
|
private List<LoggedInStateEventHandler> loggedInStateEvents = new List<LoggedInStateEventHandler>();
|
||||||
|
|
||||||
|
|
||||||
|
private Timer loggedInStateTimer;
|
||||||
|
private Timer aliveTimer;
|
||||||
|
|
||||||
|
public event EventHandler<TokenReceivedEventArgs>? TokenReceived;
|
||||||
|
|
||||||
|
public NetPhoneEvents()
|
||||||
|
{
|
||||||
|
loggedInStateTimer = new Timer(CheckLoggedInState, null, Timeout.Infinite, 1000);
|
||||||
|
aliveTimer = new Timer(AliveCheck, null, Timeout.Infinite, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
loggedInStateTimer?.Change(Timeout.Infinite, 0);
|
||||||
|
loggedInStateTimer?.Dispose();
|
||||||
|
RemoveAllEventHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Initialize(bool waitForNetphone = false, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (Process.GetProcessesByName("CLMgr").Length == 0 && !waitForNetphone)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (Process.GetProcessesByName("CLMgr").Length == 0)
|
||||||
|
await Task.Delay(1000, cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
|
Console.WriteLine("new interface");
|
||||||
|
pCLMgr = new ClientLineMgrClass();
|
||||||
|
MyEventSink = new ClientSdkEventSink();
|
||||||
|
MyEventSink.Connect(pCLMgr, new LineManagerMessageHandler(OnLineManagerMessage));
|
||||||
|
|
||||||
|
aliveTimer.Change(0, 1000);
|
||||||
|
loggedInStateTimer.Change(0, 1500);
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AliveCheck(object state)
|
||||||
|
{
|
||||||
|
if (Process.GetProcessesByName("CLMgr").Length == 0)
|
||||||
|
{
|
||||||
|
_ = Initialize(true).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckLoggedInState(object state)
|
||||||
|
{
|
||||||
|
bool currentLoggedInState = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
currentLoggedInState = pCLMgr.DispIsLoggedIn != 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
currentLoggedInState = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLoggedInState != lastLoggedInState)
|
||||||
|
{
|
||||||
|
lastLoggedInState = currentLoggedInState;
|
||||||
|
OnLoggedInStateChanged(currentLoggedInState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnLoggedInStateChanged(bool isLoggedIn)
|
||||||
|
{
|
||||||
|
foreach (var handler in loggedInStateEvents)
|
||||||
|
{
|
||||||
|
handler?.Invoke(isLoggedIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddLineStateEventHandler(LineState lineState, LineStateChangedEventHandler handler)
|
||||||
|
{
|
||||||
|
lock (lineStateEvents)
|
||||||
|
{
|
||||||
|
if (!lineStateEvents.ContainsKey(lineState))
|
||||||
|
{
|
||||||
|
lineStateEvents[lineState] = new List<LineStateChangedEventHandler>();
|
||||||
|
}
|
||||||
|
lineStateEvents[lineState].Add(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddLoggedInStateEventHandler(LoggedInStateEventHandler handler)
|
||||||
|
{
|
||||||
|
lock (loggedInStateEvents)
|
||||||
|
{
|
||||||
|
loggedInStateEvents.Add(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveLineStateEventHandler(LineState lineState, LineStateChangedEventHandler handler)
|
||||||
|
{
|
||||||
|
lock (lineStateEvents)
|
||||||
|
{
|
||||||
|
if (lineStateEvents.ContainsKey(lineState))
|
||||||
|
{
|
||||||
|
lineStateEvents[lineState].Remove(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveLoggedInStateEventHandler(LoggedInStateEventHandler handler)
|
||||||
|
{
|
||||||
|
lock (loggedInStateEvents)
|
||||||
|
{
|
||||||
|
loggedInStateEvents.Remove(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAllEventHandlers()
|
||||||
|
{
|
||||||
|
lock (lineStateEvents)
|
||||||
|
{
|
||||||
|
lineStateEvents.Clear();
|
||||||
|
}
|
||||||
|
lock (loggedInStateEvents)
|
||||||
|
{
|
||||||
|
loggedInStateEvents.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLineManagerMessage(ClientSdkEventArgs e)
|
||||||
|
{
|
||||||
|
SelectedLine = (ClientLine)pCLMgr.DispSelectedLine;
|
||||||
|
|
||||||
|
if (e.Msg == CLMgrMessage.CLMgrClientShutDownRequest)
|
||||||
|
{
|
||||||
|
//aliveCheckTokenSource.Cancel();
|
||||||
|
//MyEventSink.Disconnect();
|
||||||
|
//OnConnectionStateChanged(false,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.Msg == CLMgrMessage.CLMgrLineStateChangedMessageEx)
|
||||||
|
{
|
||||||
|
int line = e.Param & 0xff;
|
||||||
|
int high = e.Param >> 8;
|
||||||
|
|
||||||
|
LineState NewLineState = (LineState)high;
|
||||||
|
|
||||||
|
if (LastLineState != NewLineState)
|
||||||
|
{
|
||||||
|
LastLineState = NewLineState;
|
||||||
|
|
||||||
|
lock (lineStateEvents)
|
||||||
|
{
|
||||||
|
if (lineStateEvents.ContainsKey(NewLineState))
|
||||||
|
{
|
||||||
|
foreach (var handler in lineStateEvents[NewLineState])
|
||||||
|
{
|
||||||
|
handler?.Invoke(NewLineState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRichPresenceStatus(int away, int dnd, DateTime expires)
|
||||||
|
{
|
||||||
|
var clientConfig = (ClientConfig)this.pCLMgr.ClientConfig;
|
||||||
|
clientConfig.SetRichPresenceStatus(away, dnd, expires);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAppointmentText(string appointmentText, DateTime expires)
|
||||||
|
{
|
||||||
|
var clientConfig = (ClientConfig)this.pCLMgr.ClientConfig;
|
||||||
|
clientConfig.SetAppointmentText(appointmentText, expires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
290
TeamsNetphoneLinkWPF/Communication/TeamsGraph.cs
Normal file
290
TeamsNetphoneLinkWPF/Communication/TeamsGraph.cs
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
using Azure.Core;
|
||||||
|
using Azure.Identity;
|
||||||
|
using Azure.Identity.Broker;
|
||||||
|
using Microsoft.Graph;
|
||||||
|
using Microsoft.Graph.Drives.Item.Items.Item.Workbook.Functions.Cosh;
|
||||||
|
using Microsoft.Graph.Me.Presence.SetUserPreferredPresence;
|
||||||
|
using Microsoft.Identity.Client;
|
||||||
|
using Microsoft.Identity.Client.Broker;
|
||||||
|
using Microsoft.Identity.Client.Extensions.Msal;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows;
|
||||||
|
using TeamsLocalLibary;
|
||||||
|
using static System.Formats.Asn1.AsnWriter;
|
||||||
|
using Microsoft.Identity.Client.NativeInterop;
|
||||||
|
using TeamsNetphoneLink.WPF;
|
||||||
|
using TeamsNetphoneLink.Communication;
|
||||||
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.Teams
|
||||||
|
{
|
||||||
|
public delegate void PresenceStausEventHandler(Microsoft.Graph.Models.Presence presence);
|
||||||
|
|
||||||
|
public class TeamsGraph : TeamsGraphEventHandlers
|
||||||
|
{
|
||||||
|
private GraphServiceClient graphClient;
|
||||||
|
public bool Authenticated { get; private set; }
|
||||||
|
|
||||||
|
private Timer presenceStatusTimer;
|
||||||
|
private Microsoft.Graph.Models.Presence lastPresence;
|
||||||
|
public void CheckPresenceStatusTimer()
|
||||||
|
{
|
||||||
|
presenceStatusTimer = new Timer(CheckPresenceStatus, null, Timeout.Infinite, 2000);
|
||||||
|
presenceStatusTimer.Change(0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CheckPresenceStatus(object state)
|
||||||
|
{
|
||||||
|
if (graphClient is not null && Authenticated)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var presence = graphClient.Me.Presence.GetAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
// If lastPresence is null, initialize it with the current presence
|
||||||
|
if (lastPresence == null)
|
||||||
|
{
|
||||||
|
lastPresence = presence;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Availability has changed
|
||||||
|
if (lastPresence.Availability != presence.Availability)
|
||||||
|
{
|
||||||
|
OnAvailabilityChanged(presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Activity has changed
|
||||||
|
if (lastPresence.Activity != presence.Activity)
|
||||||
|
{
|
||||||
|
OnActivityChanged(presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update lastPresence to the current presence
|
||||||
|
lastPresence = presence;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error checking presence state: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsCachedAccounts()
|
||||||
|
{
|
||||||
|
IPublicClientApplication app = PublicClientApplicationBuilder.Create(Settings.Default.AppID)
|
||||||
|
.WithDefaultRedirectUri()
|
||||||
|
.WithAuthority(String.Format("https://login.microsoftonline.com/{0}", Settings.Default.TenantID))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Register MSAL cache
|
||||||
|
|
||||||
|
var storage = new StorageCreationPropertiesBuilder("teamsnetphonelink.msal.cache", MsalCacheHelper.UserRootDirectory).Build();
|
||||||
|
|
||||||
|
var cacheHelper = await MsalCacheHelper.CreateAsync(storage);
|
||||||
|
|
||||||
|
cacheHelper.RegisterCache(app.UserTokenCache);
|
||||||
|
|
||||||
|
IEnumerable<IAccount> accounts = await app.GetAccountsAsync();
|
||||||
|
|
||||||
|
return accounts.Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentifizierungsmethode mit Azure Identity
|
||||||
|
public async Task<bool> AuthenticateAsync(bool clearCache = false, bool silent = true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if caching is enabled in settings
|
||||||
|
bool useCache = Settings.Default.SaveEntraCredentials;
|
||||||
|
|
||||||
|
IPublicClientApplication app = PublicClientApplicationBuilder.Create(Settings.Default.AppID)
|
||||||
|
.WithDefaultRedirectUri()
|
||||||
|
.WithAuthority(String.Format("https://login.microsoftonline.com/{0}", Settings.Default.TenantID))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Register MSAL cache only if caching is enabled
|
||||||
|
if (useCache)
|
||||||
|
{
|
||||||
|
var storage = new StorageCreationPropertiesBuilder("teamsnetphonelink.msal.cache", MsalCacheHelper.UserRootDirectory).Build();
|
||||||
|
|
||||||
|
var cacheHelper = await MsalCacheHelper.CreateAsync(storage);
|
||||||
|
|
||||||
|
cacheHelper.RegisterCache(app.UserTokenCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<IAccount> accounts = await app.GetAccountsAsync();
|
||||||
|
|
||||||
|
//Alle Accounts aus dem Cache entfernen wenn clearCache gesetzt ist
|
||||||
|
if (clearCache)
|
||||||
|
{
|
||||||
|
foreach(var account in accounts)
|
||||||
|
await app.RemoveAsync(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to use the previously signed-in account from the cache
|
||||||
|
var existingAccount = accounts.FirstOrDefault();
|
||||||
|
|
||||||
|
AuthenticationResult authentication;
|
||||||
|
|
||||||
|
if (existingAccount is not null && useCache && !clearCache)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Attempting to acquire token silently using cached account.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
authentication = await app.AcquireTokenSilent(new[] { "Presence.ReadWrite", "offline_access" }, existingAccount)
|
||||||
|
.ExecuteAsync();
|
||||||
|
|
||||||
|
await InitializeGraphClient(authentication.AccessToken);
|
||||||
|
|
||||||
|
return Authenticated;
|
||||||
|
}
|
||||||
|
catch (MsalUiRequiredException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Silent token acquisition failed. Falling back to interactive authentication.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no cached account or silent authentication fails, prompt the user for authentication
|
||||||
|
Console.WriteLine("Prompting user for authentication.");
|
||||||
|
authentication = await app.AcquireTokenInteractive(new[] { "Presence.ReadWrite", "offline_access" })
|
||||||
|
.ExecuteAsync();
|
||||||
|
|
||||||
|
await InitializeGraphClient(authentication.AccessToken);
|
||||||
|
|
||||||
|
return Authenticated;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler bei der Authentifizierung", ex.Message, ex.StackTrace);
|
||||||
|
Console.WriteLine($"Error during authentication: {ex.Message}");
|
||||||
|
return Authenticated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to initialize GraphServiceClient
|
||||||
|
private async Task InitializeGraphClient(string accessToken)
|
||||||
|
{
|
||||||
|
graphClient = new GraphServiceClient(new HttpClient(new AuthHandler(accessToken, new HttpClientHandler())));
|
||||||
|
Authenticated = await TestAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testung der Anmeldung und Zugriffsrechte
|
||||||
|
public async Task<bool> TestAuthentication()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Test-Anfrage, um Authentifizierung zu validieren
|
||||||
|
await graphClient.Me.Presence.GetAsync();
|
||||||
|
return true; // Anmeldung und Zugriffsrechte korrekt
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Authentifizierung fehlerhaft", ex.Message, ex.StackTrace);
|
||||||
|
return false; // Anmeldung und Zugriffsrechte felerhaft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methode zum Setzen des Präsenzstatus
|
||||||
|
public async Task<bool> SetPresenceAsync(PresenceState presenceState)
|
||||||
|
{
|
||||||
|
if (graphClient is null || !Authenticated)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Authentifizierung nicht abgeschlossen. Präsenz kann nicht gesetzt werden.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zuordnen des Präsenzstatus zu Verfügbarkeit und Aktivität
|
||||||
|
var presenceMap = new Dictionary<PresenceState, (string Availability, string Activity)>
|
||||||
|
{
|
||||||
|
{ PresenceState.Available, ("Available", "Available") },
|
||||||
|
{ PresenceState.Busy, ("Busy", "Busy") },
|
||||||
|
{ PresenceState.DoNotDisturb, ("DoNotDisturb", "DoNotDisturb") },
|
||||||
|
{ PresenceState.BeRightBack, ("BeRightBack", "BeRightBack") },
|
||||||
|
{ PresenceState.Away, ("Away", "Away") },
|
||||||
|
{ PresenceState.Offline, ("Offline", "OffWork") }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!presenceMap.ContainsKey(presenceState))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Ungültiger Präsenzstatus.");
|
||||||
|
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Ungültiger Präsenzstatus", presenceState.ToString());
|
||||||
|
return false; // Ungültiger Status
|
||||||
|
}
|
||||||
|
|
||||||
|
var (availability, activity) = presenceMap[presenceState];
|
||||||
|
|
||||||
|
var requestBody = new SetUserPreferredPresencePostRequestBody
|
||||||
|
{
|
||||||
|
Availability = availability,
|
||||||
|
Activity = activity,
|
||||||
|
ExpirationDuration = TimeSpan.FromHours(2) // Ablaufzeit der Präsenz
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await graphClient.Me.Presence.SetUserPreferredPresence.PostAsync(requestBody);
|
||||||
|
return true; // Präsenz erfolgreich gesetzt
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if(ex.GetType() != typeof(Microsoft.Graph.Models.ODataErrors.ODataError))
|
||||||
|
{
|
||||||
|
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler beim Setzen der Präsenz", ex.Message, ex.StackTrace);
|
||||||
|
}
|
||||||
|
return false; // Fehler beim Setzen der Präsenz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enum für die verschiedenen Präsenzstatus
|
||||||
|
public enum PresenceState
|
||||||
|
{
|
||||||
|
Available,
|
||||||
|
Busy,
|
||||||
|
DoNotDisturb,
|
||||||
|
BeRightBack,
|
||||||
|
Away,
|
||||||
|
Offline
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Regex isGuid =
|
||||||
|
new Regex(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
public static bool IsGuid(string candidate)
|
||||||
|
{
|
||||||
|
bool isValid = false;
|
||||||
|
|
||||||
|
if (candidate != null)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (isGuid.IsMatch(candidate))
|
||||||
|
{
|
||||||
|
isValid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom HttpMessageHandler to inject the access token
|
||||||
|
public class AuthHandler : DelegatingHandler
|
||||||
|
{
|
||||||
|
private readonly string _accessToken;
|
||||||
|
|
||||||
|
public AuthHandler(string accessToken, HttpMessageHandler innerHandler)
|
||||||
|
: base(innerHandler)
|
||||||
|
{
|
||||||
|
_accessToken = accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Add the access token to the request headers
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
|
||||||
|
return await base.SendAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.Communication
|
||||||
|
{
|
||||||
|
public class TeamsGraphEventHandlers
|
||||||
|
{
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
private readonly List<EventHandler<PresenceChangedEventArgs>> _activityChangedHandlers = new List<EventHandler<PresenceChangedEventArgs>>();
|
||||||
|
private readonly List<EventHandler<PresenceChangedEventArgs>> _availabilityChangedHandlers = new List<EventHandler<PresenceChangedEventArgs>>();
|
||||||
|
|
||||||
|
public event EventHandler<PresenceChangedEventArgs> ActivityChanged
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_activityChangedHandlers.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_activityChangedHandlers.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<PresenceChangedEventArgs> AvailabilityChanged
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_availabilityChangedHandlers.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_availabilityChangedHandlers.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnActivityChanged(Microsoft.Graph.Models.Presence presence)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var args = new PresenceChangedEventArgs(presence);
|
||||||
|
foreach (var handler in _activityChangedHandlers)
|
||||||
|
{
|
||||||
|
handler?.Invoke(this, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnAvailabilityChanged(Microsoft.Graph.Models.Presence presence)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var args = new PresenceChangedEventArgs(presence);
|
||||||
|
foreach (var handler in _availabilityChangedHandlers)
|
||||||
|
{
|
||||||
|
handler?.Invoke(this, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAllEventHandlers()
|
||||||
|
{
|
||||||
|
foreach (var handler in _activityChangedHandlers)
|
||||||
|
{
|
||||||
|
_availabilityChangedHandlers.Remove(handler);
|
||||||
|
}
|
||||||
|
foreach (var handler in _availabilityChangedHandlers)
|
||||||
|
{
|
||||||
|
_availabilityChangedHandlers.Remove(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PresenceChangedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public Microsoft.Graph.Models.Presence Presence { get; }
|
||||||
|
|
||||||
|
public PresenceChangedEventArgs(Microsoft.Graph.Models.Presence presence)
|
||||||
|
{
|
||||||
|
Presence = presence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
TeamsNetphoneLinkWPF/Communication/TeamsLocalAPI.cs
Normal file
118
TeamsNetphoneLinkWPF/Communication/TeamsLocalAPI.cs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Reflection;
|
||||||
|
using TeamsLocalLibary;
|
||||||
|
using TeamsLocalLibary.EventArgs;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.Teams
|
||||||
|
{
|
||||||
|
public class TeamsLocalAPI
|
||||||
|
{
|
||||||
|
public event EventHandler<ConnectionStateChangedEventArgs>? ConnectionState;
|
||||||
|
public event EventHandler<TokenReceivedEventArgs>? TokenReceived;
|
||||||
|
|
||||||
|
private Client teamsClient;
|
||||||
|
private string token = Settings.Default.Token != string.Empty ? Settings.Default.Token : null;
|
||||||
|
private Dictionary<string, List<PropertyChangedEventHandler>> propertyChangedHandlers = new Dictionary<string, List<PropertyChangedEventHandler>>();
|
||||||
|
private CancellationTokenSource cts = new();
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
~TeamsLocalAPI()
|
||||||
|
{
|
||||||
|
RemoveAllEventHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Initialize()
|
||||||
|
{
|
||||||
|
// Initialize the TeamsClient with token and no auto-connect
|
||||||
|
teamsClient = new Client(autoConnect: false, token: token) ?? throw new Exception("Could not create client");
|
||||||
|
|
||||||
|
// Event-handler for token reception
|
||||||
|
teamsClient.TokenReceived += (_, args) =>
|
||||||
|
{
|
||||||
|
Settings.Default.Token = args.Token;
|
||||||
|
Settings.Default.Save();
|
||||||
|
|
||||||
|
TokenReceived?.Invoke(_, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
WebSocketState lastConnectionState = WebSocketState.None;
|
||||||
|
teamsClient.ConnectionState += (sender, args) =>
|
||||||
|
{
|
||||||
|
if (args.WebSocketState != lastConnectionState)
|
||||||
|
{
|
||||||
|
lastConnectionState = args.WebSocketState;
|
||||||
|
// Raise the ConnectionState event in TeamsLocalAPI
|
||||||
|
ConnectionState?.Invoke(sender, args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
teamsClient.PropertyChanged += HandlePropertyChanged;
|
||||||
|
|
||||||
|
teamsClient.ErrorReceived += (_, args) => Console.WriteLine("Event: ErrorReceived: {0}", args.ErrorMessage);
|
||||||
|
|
||||||
|
return await teamsClient.Connect(true, cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTokenRecievedHandler(EventHandler<TokenReceivedEventArgs> handler)
|
||||||
|
{
|
||||||
|
TokenReceived += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveTokenRecievedHandler(EventHandler<TokenReceivedEventArgs> handler)
|
||||||
|
{
|
||||||
|
TokenReceived -= handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddConnectionStateHandler(EventHandler<ConnectionStateChangedEventArgs> handler)
|
||||||
|
{
|
||||||
|
ConnectionState += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveConnectionStateHandler(EventHandler<ConnectionStateChangedEventArgs> handler)
|
||||||
|
{
|
||||||
|
ConnectionState -= handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void SendDummyCommand()
|
||||||
|
{
|
||||||
|
var dummy = teamsClient.IsMuted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName is not null && propertyChangedHandlers.ContainsKey(e.PropertyName))
|
||||||
|
{
|
||||||
|
foreach (var handler in propertyChangedHandlers[e.PropertyName])
|
||||||
|
{
|
||||||
|
handler?.Invoke(sender, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEventHandler(string propertyName, PropertyChangedEventHandler handler)
|
||||||
|
{
|
||||||
|
if (!propertyChangedHandlers.ContainsKey(propertyName))
|
||||||
|
{
|
||||||
|
propertyChangedHandlers[propertyName] = new List<PropertyChangedEventHandler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyChangedHandlers[propertyName].Add(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveEventHandler(string propertyName, PropertyChangedEventHandler handler)
|
||||||
|
{
|
||||||
|
if (propertyChangedHandlers.ContainsKey(propertyName))
|
||||||
|
{
|
||||||
|
propertyChangedHandlers[propertyName].Remove(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAllEventHandlers()
|
||||||
|
{
|
||||||
|
propertyChangedHandlers.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
110
TeamsNetphoneLinkWPF/Settings.Designer.cs
generated
Normal file
110
TeamsNetphoneLinkWPF/Settings.Designer.cs
generated
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// Dieser Code wurde von einem Tool generiert.
|
||||||
|
// Laufzeitversion:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
|
||||||
|
// der Code erneut generiert wird.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink {
|
||||||
|
|
||||||
|
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")]
|
||||||
|
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||||
|
|
||||||
|
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||||
|
|
||||||
|
public static Settings Default {
|
||||||
|
get {
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||||
|
public string Token {
|
||||||
|
get {
|
||||||
|
return ((string)(this["Token"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["Token"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||||
|
public bool TeamsPermission {
|
||||||
|
get {
|
||||||
|
return ((bool)(this["TeamsPermission"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["TeamsPermission"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||||
|
public string TenantID {
|
||||||
|
get {
|
||||||
|
return ((string)(this["TenantID"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["TenantID"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||||
|
public string AppID {
|
||||||
|
get {
|
||||||
|
return ((string)(this["AppID"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["AppID"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||||
|
public bool SaveEntraCredentials {
|
||||||
|
get {
|
||||||
|
return ((bool)(this["SaveEntraCredentials"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["SaveEntraCredentials"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||||
|
public bool UseGraphForMeetingState {
|
||||||
|
get {
|
||||||
|
return ((bool)(this["UseGraphForMeetingState"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["UseGraphForMeetingState"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||||
|
public string CurrentVersion {
|
||||||
|
get {
|
||||||
|
return ((string)(this["CurrentVersion"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["CurrentVersion"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
TeamsNetphoneLinkWPF/Settings.settings
Normal file
27
TeamsNetphoneLinkWPF/Settings.settings
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="TeamsNetphoneLink" GeneratedClassName="Settings">
|
||||||
|
<Profiles />
|
||||||
|
<Settings>
|
||||||
|
<Setting Name="Token" Type="System.String" Scope="User">
|
||||||
|
<Value Profile="(Default)" />
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="TeamsPermission" Type="System.Boolean" Scope="User">
|
||||||
|
<Value Profile="(Default)">False</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="TenantID" Type="System.String" Scope="User">
|
||||||
|
<Value Profile="(Default)" />
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="AppID" Type="System.String" Scope="User">
|
||||||
|
<Value Profile="(Default)" />
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="SaveEntraCredentials" Type="System.Boolean" Scope="User">
|
||||||
|
<Value Profile="(Default)">False</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="UseGraphForMeetingState" Type="System.Boolean" Scope="User">
|
||||||
|
<Value Profile="(Default)">False</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="CurrentVersion" Type="System.String" Scope="User">
|
||||||
|
<Value Profile="(Default)" />
|
||||||
|
</Setting>
|
||||||
|
</Settings>
|
||||||
|
</SettingsFile>
|
273
TeamsNetphoneLinkWPF/Syncronisation.cs
Normal file
273
TeamsNetphoneLinkWPF/Syncronisation.cs
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using TeamsLocalLibary.EventArgs;
|
||||||
|
using TeamsNetphoneLink.Teams;
|
||||||
|
using TeamsNetphoneLink.Netphone;
|
||||||
|
using TeamsNetphoneLink.WPF.MVVM;
|
||||||
|
using TeamsNetphoneLink.Communication;
|
||||||
|
using System.Reflection;
|
||||||
|
using TeamsNetphoneLink.WPF;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink
|
||||||
|
{
|
||||||
|
public class Syncronisation
|
||||||
|
{
|
||||||
|
public NetPhoneEvents NetphoneEvents { get; set; }
|
||||||
|
public TeamsGraph TeamsGraph { get; set; }
|
||||||
|
public TeamsLocalAPI TeamsEvents { get; set; }
|
||||||
|
|
||||||
|
//MVVM View Models
|
||||||
|
public DashboardViewModel _dashboardViewModel { get; }
|
||||||
|
public LogViewModel LogEntries { get; }
|
||||||
|
public FinishPanelViewModel FinishPanelViewModel { get; }
|
||||||
|
|
||||||
|
public readonly SemaphoreSlim TeamsGraphAuthenticationSemaphore = new(1);
|
||||||
|
|
||||||
|
private bool _isCallActive = false;
|
||||||
|
private bool _isMeetingActive = false;
|
||||||
|
private bool NetphoneConnected = false;
|
||||||
|
|
||||||
|
|
||||||
|
public Syncronisation(NetPhoneEvents netPhoneEvents, TeamsLocalAPI teamsEvents, TeamsGraph teamsGraph, DashboardViewModel dashboardViewModel, LogViewModel logEntries, FinishPanelViewModel finishPanelViewModel)
|
||||||
|
{
|
||||||
|
_dashboardViewModel = dashboardViewModel ?? throw new ArgumentNullException(nameof(dashboardViewModel));
|
||||||
|
LogEntries = logEntries ?? throw new ArgumentNullException(nameof(logEntries));
|
||||||
|
FinishPanelViewModel = finishPanelViewModel ?? throw new ArgumentNullException(nameof(finishPanelViewModel));
|
||||||
|
NetphoneEvents = netPhoneEvents ?? throw new ArgumentNullException(nameof(netPhoneEvents));
|
||||||
|
TeamsEvents = teamsEvents ?? throw new ArgumentNullException(nameof(teamsEvents));
|
||||||
|
TeamsGraph = teamsGraph ?? throw new ArgumentNullException(nameof(teamsGraph));
|
||||||
|
|
||||||
|
// Event-Handler für Netphone-Events registrieren
|
||||||
|
NetphoneEvents.AddLineStateEventHandler(LineState.Dialing, NetphoneEventCall);
|
||||||
|
NetphoneEvents.AddLineStateEventHandler(LineState.Ringing, NetphoneEventCall);
|
||||||
|
NetphoneEvents.AddLineStateEventHandler(LineState.Active, NetphoneEventCall);
|
||||||
|
NetphoneEvents.AddLineStateEventHandler(LineState.Inactive, NetphoneEventTerminated);
|
||||||
|
NetphoneEvents.AddLineStateEventHandler(LineState.Terminated, NetphoneEventTerminated);
|
||||||
|
NetphoneEvents.AddLoggedInStateEventHandler(NetphoneOnLoggedInStateChanged);
|
||||||
|
|
||||||
|
if (!Settings.Default.UseGraphForMeetingState)
|
||||||
|
{
|
||||||
|
// Event-Handler für Teams-Events registrieren
|
||||||
|
TeamsEvents.AddEventHandler("IsInMeeting", TeamsIsInMeetingHandler);
|
||||||
|
TeamsEvents.AddConnectionStateHandler(TeamsOnConnectionStateChanged);
|
||||||
|
TeamsEvents.AddTokenRecievedHandler(TeamsTokenRecievedHandler);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TeamsGraph.ActivityChanged += TeamsGraphOnActivityChanged;
|
||||||
|
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsLocalAPIStatus, Colors.Orange, "Deaktiviert");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event-Handler für Änderungen des Verbindungsstatus in Teams
|
||||||
|
private void TeamsOnConnectionStateChanged(object? sender, ConnectionStateChangedEventArgs args)
|
||||||
|
{
|
||||||
|
var status = args.WebSocketState switch
|
||||||
|
{
|
||||||
|
System.Net.WebSockets.WebSocketState.Open when Settings.Default.TeamsPermission => (Colors.Green, "Verbunden"),
|
||||||
|
System.Net.WebSockets.WebSocketState.Open => (Colors.Orange, "Authorisieren (Klicken)"),
|
||||||
|
System.Net.WebSockets.WebSocketState.Connecting => (Colors.Blue, "Verbinden..."),
|
||||||
|
System.Net.WebSockets.WebSocketState.Closed => (Colors.Red, "Nicht verbunden"),
|
||||||
|
_ => (Colors.Gray, "Unbekannt")
|
||||||
|
};
|
||||||
|
|
||||||
|
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsLocalAPIStatus, status.Item1, status.Item2);
|
||||||
|
|
||||||
|
if(Settings.Default.TeamsPermission)
|
||||||
|
LogEntries.AddLogEntry(args.WebSocketState == System.Net.WebSockets.WebSocketState.Open ? "Info" : "Warn", "TeamsLocalAPI", status.Item2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NetphoneOnLoggedInStateChanged(bool isLoggedIn)
|
||||||
|
{
|
||||||
|
NetphoneConnected = isLoggedIn;
|
||||||
|
|
||||||
|
var state = isLoggedIn ? ( Colors.Green, "Verbunden" ) : ( Colors.Red, "Nicht verbunden" );
|
||||||
|
|
||||||
|
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.NetphoneCLMGRStatus, state.Item1, state.Item2);
|
||||||
|
LogEntries.AddLogEntry(isLoggedIn ? "Info" : "Warn", "Netphone", state.Item2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event-Handler für den Empfang eines Teams-Tokens
|
||||||
|
private void TeamsTokenRecievedHandler(object sender, TokenReceivedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!Settings.Default.TeamsPermission)
|
||||||
|
{
|
||||||
|
Settings.Default.TeamsPermission = true;
|
||||||
|
Settings.Default.Save();
|
||||||
|
|
||||||
|
FinishPanelViewModel.FinishPanelEffect = null;
|
||||||
|
FinishPanelViewModel.FinishPanelEnabled = true;
|
||||||
|
FinishPanelViewModel.FinishPanelFinishTextText = "Verbindung erfolgreich";
|
||||||
|
FinishPanelViewModel.SetFinishPanelFinishTextColor(Colors.Green);
|
||||||
|
|
||||||
|
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsLocalAPIStatus, Colors.Green, "Verbunden");
|
||||||
|
LogEntries.AddLogEntry("TeamsLocalAPI", "Token erfolgreich abgerufen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event-Handler für eingehende Anrufe
|
||||||
|
private async void NetphoneEventCall(LineState newLineState)
|
||||||
|
{
|
||||||
|
if (_isCallActive) return;
|
||||||
|
|
||||||
|
_isCallActive = true;
|
||||||
|
await TeamsGraph.SetPresenceAsync(TeamsGraph.PresenceState.Busy);
|
||||||
|
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.NetphoneCallStatus, Colors.DarkGreen, "Ongoing");
|
||||||
|
LogEntries.AddLogEntry("Syncronisation", "Netphone Anruf gestartet");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event-Handler für beendete Anrufe
|
||||||
|
private async void NetphoneEventTerminated(LineState newLineState)
|
||||||
|
{
|
||||||
|
if (!_isCallActive) return;
|
||||||
|
|
||||||
|
_isCallActive = false;
|
||||||
|
await TeamsGraph.SetPresenceAsync(TeamsGraph.PresenceState.Available);
|
||||||
|
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.NetphoneCallStatus, Colors.DarkOrange, "Not active");
|
||||||
|
LogEntries.AddLogEntry("Syncronisation", "Netphone Anruf gestoppt");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void TeamsGraphOnActivityChanged(object sender, PresenceChangedEventArgs args)
|
||||||
|
{
|
||||||
|
// Define activities that indicate the user is in a meeting
|
||||||
|
var meetingActivities = new HashSet<string>
|
||||||
|
{
|
||||||
|
"InACall",
|
||||||
|
"InAConferenceCall",
|
||||||
|
"InAMeeting",
|
||||||
|
"Presenting"
|
||||||
|
};
|
||||||
|
|
||||||
|
if(args.Presence.Activity is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if the current activity is in the meetingActivities set
|
||||||
|
bool isInMeeting = meetingActivities.Contains(args.Presence.Activity);
|
||||||
|
|
||||||
|
|
||||||
|
if (!isInMeeting && _isMeetingActive)
|
||||||
|
{
|
||||||
|
_isMeetingActive = false;
|
||||||
|
SetNetphoneStatus(isInMeeting);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInMeeting && !_isMeetingActive)
|
||||||
|
{
|
||||||
|
_isMeetingActive = true;
|
||||||
|
SetNetphoneStatus(isInMeeting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event-Handler für Änderungen des "IsInMeeting"-Status in Teams
|
||||||
|
private void TeamsIsInMeetingHandler(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var isInMeeting = (bool)(sender.GetType()?.GetProperty(e.PropertyName)?.GetValue(sender) ?? false);
|
||||||
|
SetNetphoneStatus(isInMeeting);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetNetphoneStatus(bool meeting)
|
||||||
|
{
|
||||||
|
if (meeting)
|
||||||
|
{
|
||||||
|
if (NetphoneConnected)
|
||||||
|
{
|
||||||
|
NetphoneEvents.SetRichPresenceStatus(0, 1, DateTime.MaxValue);
|
||||||
|
NetphoneEvents.SetAppointmentText("In einem Teams Meeting", DateTime.MaxValue);
|
||||||
|
}
|
||||||
|
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsMeetingStatus, Colors.DarkGreen, "Ongoing");
|
||||||
|
LogEntries.AddLogEntry("Syncronisation", "Teams Meeting gestartet");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (NetphoneConnected)
|
||||||
|
{
|
||||||
|
NetphoneEvents.SetRichPresenceStatus(0, 0, DateTime.MaxValue);
|
||||||
|
NetphoneEvents.SetAppointmentText("", DateTime.MaxValue);
|
||||||
|
}
|
||||||
|
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsMeetingStatus, Colors.DarkOrange, "Not active");
|
||||||
|
LogEntries.AddLogEntry("Syncronisation", "Teams Meeting gestoppt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Methode zur Authentifizierung bei der Graph API
|
||||||
|
public async Task<bool> GraphAuthenticate(bool clearCache = false)
|
||||||
|
{
|
||||||
|
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsGraphAPIStatus, Colors.Blue, "Anmeldung läuft...");
|
||||||
|
LogEntries.AddLogEntry("TeamsGraphAPI", String.Format("Anmeldung läuft"));
|
||||||
|
|
||||||
|
var authResult = await TeamsGraph.AuthenticateAsync(clearCache);
|
||||||
|
|
||||||
|
var status = authResult ? (Colors.Green, "Angemeldet") : (Colors.Red, "Fehler - Anmelden (Klicken)");
|
||||||
|
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsGraphAPIStatus, status.Item1, status.Item2);
|
||||||
|
LogEntries.AddLogEntry("TeamsGraphAPI", status.Item2);
|
||||||
|
|
||||||
|
return authResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void InitializeNetphoneAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.NetphoneEvents.Initialize(waitForNetphone: true).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Fehlerbehandlung für die Netphone-Initialisierung
|
||||||
|
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler bei der Initialisierung von der NetphoneAPI", ex.Message, ex.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void InitializeTeamsGraphAsync()
|
||||||
|
{
|
||||||
|
if (!Teams.TeamsGraph.IsGuid(Settings.Default.AppID) || !Teams.TeamsGraph.IsGuid(Settings.Default.TenantID))
|
||||||
|
{
|
||||||
|
this._dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsGraphAPIStatus, Colors.Red, "Konfiguration fehlt");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.TeamsGraphAuthenticationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||||
|
if (await this.TeamsGraph.IsCachedAccounts() && Settings.Default.SaveEntraCredentials)
|
||||||
|
{
|
||||||
|
await this.GraphAuthenticate().ConfigureAwait(false); ;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsGraphAPIStatus, Colors.Orange, "Anmelden");
|
||||||
|
}
|
||||||
|
this.TeamsGraphAuthenticationSemaphore.Release();
|
||||||
|
|
||||||
|
if (Settings.Default.UseGraphForMeetingState)
|
||||||
|
{
|
||||||
|
this.TeamsGraph.CheckPresenceStatusTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Fehlerbehandlung für die TeamsGraph-Initialisierung
|
||||||
|
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler bei der Initialisierung von TeamsGraph", ex.Message, ex.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void InitializeTeamsLocalAPIAsync()
|
||||||
|
{
|
||||||
|
if (!Settings.Default.UseGraphForMeetingState)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.TeamsEvents.Initialize().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Fehlerbehandlung für die TeamsLocalAPI-Initialisierung
|
||||||
|
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler bei der Initialisierung von der TeamsLocalAPI", ex.Message, ex.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
TeamsNetphoneLinkWPF/TeamsNetphoneLinkWPF.csproj
Normal file
62
TeamsNetphoneLinkWPF/TeamsNetphoneLinkWPF.csproj
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<RootNamespace>TeamsNetphoneLink</RootNamespace>
|
||||||
|
<ApplicationIcon>TeamsNetphoneSyncIcon.ico</ApplicationIcon>
|
||||||
|
<BaseOutputPath>..\build\</BaseOutputPath>
|
||||||
|
<SignAssembly>False</SignAssembly>
|
||||||
|
<AssemblyOriginatorKeyFile></AssemblyOriginatorKeyFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<COMReference Include="CLMGRLib">
|
||||||
|
<Guid>{F8E552F7-4C00-11D3-80BC-00105A653379}</Guid>
|
||||||
|
<VersionMajor>2</VersionMajor>
|
||||||
|
<VersionMinor>0</VersionMinor>
|
||||||
|
<Lcid>0</Lcid>
|
||||||
|
<WrapperTool>tlbimp</WrapperTool>
|
||||||
|
<Isolated>False</Isolated>
|
||||||
|
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||||
|
</COMReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="TeamsNetphoneSyncIcon.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Azure.Identity" Version="1.13.2" />
|
||||||
|
<PackageReference Include="Azure.Identity.Broker" Version="1.2.0" />
|
||||||
|
<PackageReference Include="Emoji.Wpf" Version="0.3.4" />
|
||||||
|
<PackageReference Include="Microsoft.Graph" Version="5.74.0" />
|
||||||
|
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.67.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\TeamsLocalAPI\TeamsLocalLibary.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Settings.Designer.cs">
|
||||||
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="WPF\UpdateWindow.xaml.cs">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Settings.settings">
|
||||||
|
<Generator>SettingsSingleFileGenerator</Generator>
|
||||||
|
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
BIN
TeamsNetphoneLinkWPF/TeamsNetphoneSyncIcon.ico
Normal file
BIN
TeamsNetphoneLinkWPF/TeamsNetphoneSyncIcon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
79
TeamsNetphoneLinkWPF/UpdateCheck.cs
Normal file
79
TeamsNetphoneLinkWPF/UpdateCheck.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using TeamsNetphoneLink.WPF;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink
|
||||||
|
{
|
||||||
|
public class UpdateCheck
|
||||||
|
{
|
||||||
|
public static bool UpdateAvailable;
|
||||||
|
|
||||||
|
public static async Task<string> GetLatestReleaseTagAsync()
|
||||||
|
{
|
||||||
|
using (var client = new HttpClient())
|
||||||
|
{
|
||||||
|
var url = "https://git.jan.sx/api/v1/repos/krjan02/TeamsNetphoneLink/releases/latest";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await client.GetStringAsync(url);
|
||||||
|
|
||||||
|
var json = JsonDocument.Parse(response);
|
||||||
|
|
||||||
|
if (json.RootElement.TryGetProperty("tag_name", out var tagName))
|
||||||
|
{
|
||||||
|
return tagName.GetString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExceptionWindowHelper.Show("UpdateCheck", MethodBase.GetCurrentMethod().Name, "Update Check nicht erfolgreich", "tag_name is not existing");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ExceptionWindowHelper.Show("UpdateCheck", MethodBase.GetCurrentMethod().Name, "Update Check nicht erfolgreich", ex.Message, ex.StackTrace);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsVersionLower(string currentVersion, string releaseVersion)
|
||||||
|
{
|
||||||
|
if(releaseVersion.Length == 0) { return false; }
|
||||||
|
|
||||||
|
Version current = Version.Parse(currentVersion);
|
||||||
|
Version release = Version.Parse(releaseVersion);
|
||||||
|
|
||||||
|
return current.CompareTo(release) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetCurrentVersion()
|
||||||
|
{
|
||||||
|
var assembly = Assembly.GetEntryAssembly();
|
||||||
|
var versionAttribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||||||
|
var productVersion = versionAttribute?.InformationalVersion;
|
||||||
|
|
||||||
|
return productVersion.Split("+").FirstOrDefault(); ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpgradeSettingsIfRequired()
|
||||||
|
{
|
||||||
|
var version = GetCurrentVersion();
|
||||||
|
if (Settings.Default.CurrentVersion != version)
|
||||||
|
{
|
||||||
|
Settings.Default.Upgrade();
|
||||||
|
Settings.Default.CurrentVersion = version;
|
||||||
|
Settings.Default.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
263
TeamsNetphoneLinkWPF/WPF/DashboardWindow.xaml
Normal file
263
TeamsNetphoneLinkWPF/WPF/DashboardWindow.xaml
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
<Window x:Class="TeamsNetphoneLink.WPF.DashboardWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:TeamsNetphoneLink.WPF.MVVM"
|
||||||
|
Title="TeamsNetphoneLink Dashboard" Height="554" Width="800"
|
||||||
|
Background="#FF1E1E1E" FontFamily="Segoe UI" ResizeMode="CanMinimize"
|
||||||
|
WindowStartupLocation="CenterScreen">
|
||||||
|
|
||||||
|
<Window.DataContext>
|
||||||
|
<local:DashboardViewModel />
|
||||||
|
</Window.DataContext>
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<!-- Button Style -->
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="#FF0078D4"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="Padding" Value="10,5"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
CornerRadius="4"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="#0063B1"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Status Border Style -->
|
||||||
|
<Style TargetType="Border" x:Key="StatusBorder">
|
||||||
|
<Setter Property="CornerRadius" Value="12"/>
|
||||||
|
<Setter Property="Padding" Value="10,5"/>
|
||||||
|
<Setter Property="Background" Value="#FF252526"/>
|
||||||
|
<Setter Property="MinWidth" Value="100"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Status Text Style -->
|
||||||
|
<Style TargetType="TextBlock" x:Key="StatusText">
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ListView Styling -->
|
||||||
|
<Style TargetType="ListView">
|
||||||
|
<Setter Property="Background" Value="#FF252526"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
|
||||||
|
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- GridViewColumnHeader Style -->
|
||||||
|
<Style TargetType="GridViewColumnHeader">
|
||||||
|
<Setter Property="Background" Value="#FF252526"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
|
||||||
|
<Setter Property="Padding" Value="5"/>
|
||||||
|
<Setter Property="Height" Value="32"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Left"/>
|
||||||
|
</Style>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<!-- Header -->
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<!-- Status Panels -->
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<!-- Log Section -->
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<!-- Footer -->
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,15">
|
||||||
|
<TextBlock FontSize="24" FontWeight="Bold" Text="{Binding AppTitle2}" Foreground="White"/>
|
||||||
|
<Border Visibility="{Binding VersionVisibility}" Background="{Binding VersionBackground}" CornerRadius="12" Padding="12,4" Margin="15,0">
|
||||||
|
<TextBlock Text="{Binding VersionText}" Foreground="White" FontSize="16"/>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Status Indicators -->
|
||||||
|
<Grid Grid.Row="1" Margin="15,20">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Left Status Column -->
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<!-- Teams Local API Status -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,8">
|
||||||
|
<TextBlock Text="Teams Local API Status" Width="180" FontWeight="Bold" Foreground="White"/>
|
||||||
|
<Border Style="{StaticResource StatusBorder}"
|
||||||
|
Background="{Binding TeamsLocalAPIStatusBackground}"
|
||||||
|
MouseDown="TeamsLocalAPIBorder_MouseDown">
|
||||||
|
<TextBlock Style="{StaticResource StatusText}" Text="{Binding TeamsLocalAPIStatusText}"/>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Teams Graph API Status -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,8">
|
||||||
|
<TextBlock Text="Teams Graph API Status" Width="180" FontWeight="Bold" Foreground="White"/>
|
||||||
|
<Border Style="{StaticResource StatusBorder}"
|
||||||
|
Background="{Binding TeamsGraphAPIStatusBackground}"
|
||||||
|
MouseDown="TeamsGraphAPIBorder_MouseDown">
|
||||||
|
<TextBlock Style="{StaticResource StatusText}" Text="{Binding TeamsGraphAPIStatusText}"/>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- NetPhone CLMGR Status -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,8">
|
||||||
|
<TextBlock Text="NetPhone CLMGR Status" Width="180" FontWeight="Bold" Foreground="White"/>
|
||||||
|
<Border Style="{StaticResource StatusBorder}"
|
||||||
|
Background="{Binding NetphoneCLMGRStatusBackground}">
|
||||||
|
<TextBlock Style="{StaticResource StatusText}" Text="{Binding NetphoneCLMGRStatusText}"/>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Right Status Column -->
|
||||||
|
<StackPanel Grid.Column="1">
|
||||||
|
<!-- Teams Meeting Status -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,8">
|
||||||
|
<TextBlock Text="Teams Meeting" Width="180" FontWeight="Bold" Foreground="White"/>
|
||||||
|
<Border Style="{StaticResource StatusBorder}"
|
||||||
|
Background="{Binding TeamsMeetingStatusBackground}">
|
||||||
|
<TextBlock Style="{StaticResource StatusText}" Text="{Binding TeamsMeetingStatusText}"/>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- NetPhone Call Status -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,8">
|
||||||
|
<TextBlock Text="NetPhone Call" Width="180" FontWeight="Bold" Foreground="White"/>
|
||||||
|
<Border Style="{StaticResource StatusBorder}"
|
||||||
|
Background="{Binding NetphoneCallStatusBackground}">
|
||||||
|
<TextBlock Style="{StaticResource StatusText}" Text="{Binding NetphoneCallStatusText}"/>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Log Section -->
|
||||||
|
<Border Grid.Row="2" Margin="15,0,15,10" Background="#FF252526" CornerRadius="8">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="Activity Log"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="White"
|
||||||
|
Margin="15,10"/>
|
||||||
|
|
||||||
|
<ListView x:Name="Log" Grid.Row="1"
|
||||||
|
ItemsSource="{Binding LogEntries}"
|
||||||
|
Margin="10,0,10,10"
|
||||||
|
VirtualizingPanel.IsVirtualizing="True"
|
||||||
|
IsHitTestVisible="False">
|
||||||
|
<ListView.View>
|
||||||
|
<GridView>
|
||||||
|
<GridViewColumn Width="80" Header="Time">
|
||||||
|
<GridViewColumn.DisplayMemberBinding>
|
||||||
|
<Binding Path="Time" StringFormat="{}{0:HH:mm:ss}"/>
|
||||||
|
</GridViewColumn.DisplayMemberBinding>
|
||||||
|
</GridViewColumn>
|
||||||
|
|
||||||
|
<GridViewColumn Width="60" Header="Type">
|
||||||
|
<GridViewColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Type}" Foreground="White">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="#FF0078D4"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Type}" Value="Warn">
|
||||||
|
<Setter Property="Foreground" Value="Orange"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Type}" Value="Error">
|
||||||
|
<Setter Property="Foreground" Value="Red"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</DataTemplate>
|
||||||
|
</GridViewColumn.CellTemplate>
|
||||||
|
</GridViewColumn>
|
||||||
|
|
||||||
|
<GridViewColumn Width="120" Header="Subsystem">
|
||||||
|
<GridViewColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Subsystem}">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Subsystem}" Value="Netphone">
|
||||||
|
<Setter Property="Foreground" Value="#E20074"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Subsystem}" Value="TeamsGraphAPI">
|
||||||
|
<Setter Property="Foreground" Value="#4E5FBF"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Subsystem}" Value="TeamsLocalAPI">
|
||||||
|
<Setter Property="Foreground" Value="#4E5FBF"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</DataTemplate>
|
||||||
|
</GridViewColumn.CellTemplate>
|
||||||
|
</GridViewColumn>
|
||||||
|
|
||||||
|
<GridViewColumn Width="Auto" Header="Message"
|
||||||
|
DisplayMemberBinding="{Binding Message}"/>
|
||||||
|
</GridView>
|
||||||
|
</ListView.View>
|
||||||
|
</ListView>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="15,0,0,8">
|
||||||
|
<Button Click="SettingsButton_Click"
|
||||||
|
Padding="8"
|
||||||
|
FontSize="18"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ToolTip="Settings">
|
||||||
|
<Button.Content>
|
||||||
|
<Path Data="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.68 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"
|
||||||
|
Fill="White" Stretch="Uniform"/>
|
||||||
|
</Button.Content>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<TextBlock Text="Entwickelt von Jan Krampitz"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="White"
|
||||||
|
FontSize="10"
|
||||||
|
Margin="15,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
95
TeamsNetphoneLinkWPF/WPF/DashboardWindow.xaml.cs
Normal file
95
TeamsNetphoneLinkWPF/WPF/DashboardWindow.xaml.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using TeamsNetphoneLink.WPF.MVVM;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.WPF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
///
|
||||||
|
public partial class DashboardWindow : Window
|
||||||
|
{
|
||||||
|
private bool isDarkMode = true;
|
||||||
|
private Syncronisation sync;
|
||||||
|
private Task<bool> graphAuthenticationTask;
|
||||||
|
|
||||||
|
public DashboardViewModel _dashboardViewModel { get; }
|
||||||
|
|
||||||
|
public DashboardWindow(Syncronisation _syncronisation)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
sync = _syncronisation;
|
||||||
|
_dashboardViewModel = sync._dashboardViewModel;
|
||||||
|
DataContext = _dashboardViewModel;
|
||||||
|
// Set the ViewModel instance & Set the DataContext to the ViewModel
|
||||||
|
//Set the DataContext to the ViewModel
|
||||||
|
Log.DataContext = sync.LogEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleDarkMode(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
isDarkMode = !isDarkMode;
|
||||||
|
|
||||||
|
if (isDarkMode)
|
||||||
|
{
|
||||||
|
this.Style = (Style)FindResource("DarkModeStyle");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.Style = (Style)FindResource("LightModeStyle");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Einstellungsfenster öffnen
|
||||||
|
SettingsWindow settingsWindow = new SettingsWindow(sync);
|
||||||
|
settingsWindow.ShowDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void TeamsGraphAPIBorder_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (!Teams.TeamsGraph.IsGuid(Settings.Default.AppID) || !Teams.TeamsGraph.IsGuid(Settings.Default.TenantID))
|
||||||
|
{
|
||||||
|
SettingsWindow settingsWindow = new SettingsWindow(sync);
|
||||||
|
settingsWindow.ShowDialog();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sync.TeamsGraphAuthenticationSemaphore.CurrentCount == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool saveCredentials = Settings.Default.SaveEntraCredentials;
|
||||||
|
if (!sync.TeamsGraph.Authenticated)
|
||||||
|
{
|
||||||
|
await sync.TeamsGraphAuthenticationSemaphore.WaitAsync();
|
||||||
|
_ = await sync.GraphAuthenticate(saveCredentials);
|
||||||
|
sync.TeamsGraphAuthenticationSemaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TeamsLocalAPIBorder_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (!Settings.Default.TeamsPermission)
|
||||||
|
{
|
||||||
|
var setupLocalTeams = new WPF.SetupLocalTeamsAPI(sync);
|
||||||
|
setupLocalTeams.ShowDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogListView_UpdateColumnsWidth(object sender, SizeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
ListView _ListView = sender as ListView;
|
||||||
|
GridView _GridView = _ListView.View as GridView;
|
||||||
|
var _ActualWidth = _ListView.ActualWidth - SystemParameters.VerticalScrollBarWidth;
|
||||||
|
for (Int32 i = 1; i < _GridView.Columns.Count; i++)
|
||||||
|
{
|
||||||
|
_ActualWidth = _ActualWidth - _GridView.Columns[i].ActualWidth;
|
||||||
|
}
|
||||||
|
_GridView.Columns[0].Width = _ActualWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
TeamsNetphoneLinkWPF/WPF/ExceptionWindow.xaml
Normal file
123
TeamsNetphoneLinkWPF/WPF/ExceptionWindow.xaml
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<Window x:Class="TeamsNetphoneLink.WPF.ExceptionWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="Fehlerbericht" Height="400" Width="600"
|
||||||
|
Background="#FF1E1E1E" FontFamily="Segoe UI"
|
||||||
|
WindowStyle="ToolWindow" ResizeMode="NoResize"
|
||||||
|
WindowStartupLocation="CenterScreen" Topmost="True">
|
||||||
|
|
||||||
|
<!-- Dark Mode Resources -->
|
||||||
|
<Window.Resources>
|
||||||
|
<!-- Button Style -->
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="#FF0078D4"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="Padding" Value="10,5"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
CornerRadius="4"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- TextBox Style -->
|
||||||
|
<Style TargetType="TextBox">
|
||||||
|
<Setter Property="Background" Value="#FF252526"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Padding" Value="3,0,0,0"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="TextBox">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="4">
|
||||||
|
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- TextBlock Style -->
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid Margin="10">
|
||||||
|
<!-- Fehlertext -->
|
||||||
|
<TextBlock x:Name="ErrorText"
|
||||||
|
Text="Es ist ein Fehler aufgetreten!"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Margin="10,10,10,20"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Foreground="White"/>
|
||||||
|
|
||||||
|
<!-- Modul Feld -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="10,50,10,0">
|
||||||
|
<TextBlock Text="Modul:" VerticalAlignment="Top" Width="60" Foreground="White" Margin="0,15,0,0"/>
|
||||||
|
<TextBox x:Name="ModuleText"
|
||||||
|
FontSize="14"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Width="480"
|
||||||
|
Margin="5,5,5,0"
|
||||||
|
IsEnabled="False"
|
||||||
|
Background="#FF252526"
|
||||||
|
Foreground="White" Text="tetet" Height="37"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Scrollbare Fehlerdetails -->
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
Margin="10,100,10,50">
|
||||||
|
<TextBox x:Name="TraceText"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
IsReadOnly="True"
|
||||||
|
FontSize="14"
|
||||||
|
Padding="3,3,0,0"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
VerticalContentAlignment="Top"
|
||||||
|
Height="214"
|
||||||
|
Background="#FF252526"
|
||||||
|
Foreground="White" Text="tttt">
|
||||||
|
<!-- Beispiel für Fehlerdetails -->
|
||||||
|
</TextBox>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- Button zum Schließen -->
|
||||||
|
<Button Content="Schließen"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Width="100"
|
||||||
|
Margin="10,0,0,10"
|
||||||
|
Click="CloseButton_Click"/>
|
||||||
|
|
||||||
|
<!-- Button zum Issue melden -->
|
||||||
|
<Button Content="Issue melden"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Width="120"
|
||||||
|
Margin="0,0,10,10"
|
||||||
|
Click="ReportButton_Click"/>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
67
TeamsNetphoneLinkWPF/WPF/ExceptionWindow.xaml.cs
Normal file
67
TeamsNetphoneLinkWPF/WPF/ExceptionWindow.xaml.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Media;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.WPF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaktionslogik für ExceptionWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class ExceptionWindow : Window
|
||||||
|
{
|
||||||
|
public ExceptionWindow(string module, string trace)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
ModuleText.Text = module;
|
||||||
|
TraceText.Text = trace;
|
||||||
|
SystemSounds.Hand.Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button zum Schließen
|
||||||
|
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
this.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button zum Issue melden
|
||||||
|
private void ReportButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Fehlerdetails aus der TextBox holen
|
||||||
|
string moduleDetails = ModuleText.Text;
|
||||||
|
string errorDetails = TraceText.Text;
|
||||||
|
|
||||||
|
// URL für das GitHub Issue, Fehlerdetails werden als Parameter angehängt
|
||||||
|
string url = $"https://git.jan.sx/krjan02/TeamsNetphoneLink/issues/new?body={Uri.EscapeDataString(String.Format("Modul: {0}\n\nTrace:\n{1}", moduleDetails, errorDetails))}";
|
||||||
|
|
||||||
|
// GitHub im Standardbrowser öffnen
|
||||||
|
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExceptionWindowHelper
|
||||||
|
{
|
||||||
|
public static void Show(string classname, string method, string message, string trace)
|
||||||
|
{
|
||||||
|
var modulestr = String.Format("{0} - {1}", classname, method);
|
||||||
|
var errorstr = String.Format("{0}\n{1}", message, trace);
|
||||||
|
|
||||||
|
// Use the Dispatcher to show the window on the UI thread
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
new ExceptionWindow(modulestr, errorstr).Show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Show(string classname, string method, string message, string tracemessage, string trace)
|
||||||
|
{
|
||||||
|
var modulestr = String.Format("{0} - {1}", classname, method);
|
||||||
|
var errorstr = String.Format("{0}\n{1}\n{2}", message, tracemessage, trace);
|
||||||
|
|
||||||
|
// Use the Dispatcher to show the window on the UI thread
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
new ExceptionWindow(modulestr, errorstr).Show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
250
TeamsNetphoneLinkWPF/WPF/MVVM/DashboardViewModel.cs
Normal file
250
TeamsNetphoneLinkWPF/WPF/MVVM/DashboardViewModel.cs
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.WPF.MVVM
|
||||||
|
{
|
||||||
|
public static class StatusPanelNames
|
||||||
|
{
|
||||||
|
public const string TeamsLocalAPIStatus = nameof(TeamsLocalAPIStatus);
|
||||||
|
public const string TeamsGraphAPIStatus = nameof(TeamsGraphAPIStatus);
|
||||||
|
public const string NetphoneCLMGRStatus = nameof(NetphoneCLMGRStatus);
|
||||||
|
public const string TeamsMeetingStatus = nameof(TeamsMeetingStatus);
|
||||||
|
public const string NetphoneCallStatus = nameof(NetphoneCallStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DashboardViewModel : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
// Parameterloser Konstruktor
|
||||||
|
public DashboardViewModel()
|
||||||
|
{
|
||||||
|
// Initialisieren Sie hier Standardwerte, falls erforderlich
|
||||||
|
TeamsLocalAPIStatusText = "Nicht verbunden";
|
||||||
|
TeamsLocalAPIStatusBackground = new SolidColorBrush(Colors.Red);
|
||||||
|
TeamsGraphAPIStatusText = "Konfiguration fehlt";
|
||||||
|
TeamsGraphAPIStatusBackground = new SolidColorBrush(Colors.Red);
|
||||||
|
NetphoneCLMGRStatusText = "Nicht verbunden";
|
||||||
|
NetphoneCLMGRStatusBackground = new SolidColorBrush(Colors.Red);
|
||||||
|
TeamsMeetingStatusText = "Not Active";
|
||||||
|
TeamsMeetingStatusBackground = new SolidColorBrush(Colors.DarkOrange);
|
||||||
|
NetphoneCallStatusText = "Not Active";
|
||||||
|
NetphoneCallStatusBackground = new SolidColorBrush(Colors.DarkOrange);
|
||||||
|
|
||||||
|
AppTitle2 = "Teams Netphone Link";
|
||||||
|
VersionText = "Aktuell";
|
||||||
|
VersionVisibility = Visibility.Visible;
|
||||||
|
VersionBackground = new SolidColorBrush(Colors.Green);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private string _appTitle;
|
||||||
|
|
||||||
|
private string _versionText;
|
||||||
|
private Visibility _versionVisibility;
|
||||||
|
private Brush _versionBackground;
|
||||||
|
|
||||||
|
private string _teamsLocalAPIStatusText;
|
||||||
|
private Brush _teamsLocalAPIStatusBackground;
|
||||||
|
private string _teamsGraphAPIStatusText;
|
||||||
|
private Brush _teamsGraphAPIStatusBackground;
|
||||||
|
private string _netphoneCLMGRStatusText;
|
||||||
|
private Brush _netphoneCLMGRStatusBackground;
|
||||||
|
private string _teamsMeetingStatusText;
|
||||||
|
private Brush _teamsMeetingStatusBackground;
|
||||||
|
private string _netphoneCallStatusText;
|
||||||
|
private Brush _netphoneCallStatusBackground;
|
||||||
|
|
||||||
|
public string TeamsLocalAPIStatusText
|
||||||
|
{
|
||||||
|
get => _teamsLocalAPIStatusText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_teamsLocalAPIStatusText = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush TeamsLocalAPIStatusBackground
|
||||||
|
{
|
||||||
|
get => _teamsLocalAPIStatusBackground;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_teamsLocalAPIStatusBackground = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TeamsGraphAPIStatusText
|
||||||
|
{
|
||||||
|
get => _teamsGraphAPIStatusText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_teamsGraphAPIStatusText = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush TeamsGraphAPIStatusBackground
|
||||||
|
{
|
||||||
|
get => _teamsGraphAPIStatusBackground;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_teamsGraphAPIStatusBackground = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NetphoneCLMGRStatusText
|
||||||
|
{
|
||||||
|
get => _netphoneCLMGRStatusText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_netphoneCLMGRStatusText = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush NetphoneCLMGRStatusBackground
|
||||||
|
{
|
||||||
|
get => _netphoneCLMGRStatusBackground;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_netphoneCLMGRStatusBackground = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TeamsMeetingStatusText
|
||||||
|
{
|
||||||
|
get => _teamsMeetingStatusText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_teamsMeetingStatusText = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush TeamsMeetingStatusBackground
|
||||||
|
{
|
||||||
|
get => _teamsMeetingStatusBackground;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_teamsMeetingStatusBackground = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NetphoneCallStatusText
|
||||||
|
{
|
||||||
|
get => _netphoneCallStatusText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_netphoneCallStatusText = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush NetphoneCallStatusBackground
|
||||||
|
{
|
||||||
|
get => _netphoneCallStatusBackground;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_netphoneCallStatusBackground = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string AppTitle2
|
||||||
|
{
|
||||||
|
get => _appTitle;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
_appTitle = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string VersionText
|
||||||
|
{
|
||||||
|
get => _versionText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
_versionText = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Visibility VersionVisibility
|
||||||
|
{
|
||||||
|
get => _versionVisibility;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
_versionVisibility = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush VersionBackground
|
||||||
|
{
|
||||||
|
get => _versionBackground;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
_versionBackground = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to update status panel
|
||||||
|
public void UpdateStatusPanel(string statusPanelName, Color color, string text)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
// Get the type of the ViewModel
|
||||||
|
var type = GetType();
|
||||||
|
|
||||||
|
// Construct property names for Text and Background
|
||||||
|
string textPropertyName = $"{statusPanelName}Text";
|
||||||
|
string backgroundPropertyName = $"{statusPanelName}Background";
|
||||||
|
|
||||||
|
// Get the properties using reflection
|
||||||
|
PropertyInfo textProperty = type.GetProperty(textPropertyName); //CS8600
|
||||||
|
PropertyInfo backgroundProperty = type.GetProperty(backgroundPropertyName); //CS8600
|
||||||
|
|
||||||
|
if (textProperty != null && backgroundProperty != null)
|
||||||
|
{
|
||||||
|
// Update the Text property
|
||||||
|
textProperty.SetValue(this, text);
|
||||||
|
|
||||||
|
// Update the Background property
|
||||||
|
backgroundProperty.SetValue(this, new SolidColorBrush(color));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Invalid status panel name: {statusPanelName}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
TeamsNetphoneLinkWPF/WPF/MVVM/FinishPanelViewModel.cs
Normal file
94
TeamsNetphoneLinkWPF/WPF/MVVM/FinishPanelViewModel.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
using Emoji.Wpf;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Effects;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.WPF.MVVM
|
||||||
|
{
|
||||||
|
public class FinishPanelViewModel : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
// Parameterloser Konstruktor
|
||||||
|
public FinishPanelViewModel()
|
||||||
|
{
|
||||||
|
// Initialisieren Sie hier Standardwerte, falls erforderlich
|
||||||
|
FinishPanelEnabled = false;
|
||||||
|
FinishPanelFinishTextColor = new SolidColorBrush(Colors.DarkOrange);
|
||||||
|
FinishPanelFinishTextText = "Authentifizierung ausstehend...";
|
||||||
|
FinishPanelEffect = new TintEffect { Tint = Colors.DarkGray };
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _finishPanelEnabled;
|
||||||
|
private Brush _finishPanelFinishTextColor;
|
||||||
|
private string _finishPanelFinishTextText;
|
||||||
|
private Effect _finishPanelEffect;
|
||||||
|
|
||||||
|
public bool FinishPanelEnabled
|
||||||
|
{
|
||||||
|
get => _finishPanelEnabled;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
System.Windows.Application.Current.Dispatcher.Invoke(delegate
|
||||||
|
{
|
||||||
|
_finishPanelEnabled = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush FinishPanelFinishTextColor
|
||||||
|
{
|
||||||
|
get => _finishPanelFinishTextColor;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
System.Windows.Application.Current.Dispatcher.Invoke(delegate
|
||||||
|
{
|
||||||
|
_finishPanelFinishTextColor = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FinishPanelFinishTextText
|
||||||
|
{
|
||||||
|
get => _finishPanelFinishTextText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
System.Windows.Application.Current.Dispatcher.Invoke(delegate
|
||||||
|
{
|
||||||
|
_finishPanelFinishTextText = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Effect FinishPanelEffect
|
||||||
|
{
|
||||||
|
get => _finishPanelEffect;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
System.Windows.Application.Current.Dispatcher.Invoke(delegate
|
||||||
|
{
|
||||||
|
_finishPanelEffect = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFinishPanelFinishTextColor(Color color)
|
||||||
|
{
|
||||||
|
System.Windows.Application.Current.Dispatcher.Invoke(delegate
|
||||||
|
{
|
||||||
|
FinishPanelFinishTextColor = new SolidColorBrush((Color)color);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
TeamsNetphoneLinkWPF/WPF/MVVM/LogViewModel.cs
Normal file
75
TeamsNetphoneLinkWPF/WPF/MVVM/LogViewModel.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.WPF.MVVM
|
||||||
|
{
|
||||||
|
public class LogEntry
|
||||||
|
{
|
||||||
|
public DateTime Time { get; set; }
|
||||||
|
public string Subsystem { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LogViewModel : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
// Collection to hold log entries
|
||||||
|
public ObservableCollection<LogEntry> LogEntries { get; } = new ObservableCollection<LogEntry>();
|
||||||
|
|
||||||
|
// Add a log entry
|
||||||
|
public void AddLogEntry(string entry)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
LogEntries.Add(new LogEntry { Time = DateTime.Now, Type = "Info", Subsystem = "NONE", Message = entry });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a log entry
|
||||||
|
public void AddLogEntry(string subsystem, string entry)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
LogEntries.Add(new LogEntry { Time = DateTime.Now, Type = "Info", Subsystem = subsystem, Message = entry });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a log entry
|
||||||
|
public void AddLogEntry(string type, string subsystem, string entry)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
LogEntries.Add(new LogEntry { Time = DateTime.Now, Type = type, Subsystem = subsystem, Message = entry });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddLogEntry(DateTime time, string subsystem, string message)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
LogEntries.Add(new LogEntry { Time = time, Type = "Info", Subsystem = subsystem, Message = message });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Clear all log entries
|
||||||
|
public void ClearLogEntries()
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
LogEntries.Clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement INotifyPropertyChanged
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
143
TeamsNetphoneLinkWPF/WPF/SettingsWindow.xaml
Normal file
143
TeamsNetphoneLinkWPF/WPF/SettingsWindow.xaml
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
<Window x:Class="TeamsNetphoneLink.WPF.SettingsWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="Einstellungen" Height="373" Width="500"
|
||||||
|
WindowStyle="ToolWindow" ResizeMode="NoResize"
|
||||||
|
Background="#FF1E1E1E" FontFamily="Segoe UI"
|
||||||
|
WindowStartupLocation="CenterScreen">
|
||||||
|
|
||||||
|
<!-- Dark Mode Resources -->
|
||||||
|
<Window.Resources>
|
||||||
|
<!-- Button Style -->
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="#FF0078D4"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="Padding" Value="10,5"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border x:Name="border"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
CornerRadius="4"
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
Opacity="1">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<!-- Hover Effect -->
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="border" Property="Background" Value="#0066B3"/>
|
||||||
|
</Trigger>
|
||||||
|
|
||||||
|
<!-- Pressed Effect -->
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter TargetName="border" Property="Background" Value="#004D86"/>
|
||||||
|
<Setter TargetName="border" Property="RenderTransform">
|
||||||
|
<Setter.Value>
|
||||||
|
<ScaleTransform ScaleX="0.98" ScaleY="0.98"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Trigger>
|
||||||
|
|
||||||
|
<!-- Disabled State -->
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter TargetName="border" Property="Background" Value="#3A3A3A"/>
|
||||||
|
<Setter TargetName="border" Property="Opacity" Value="0.7"/>
|
||||||
|
<Setter Property="Foreground" Value="#A0A0A0"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- TextBox Style -->
|
||||||
|
<Style TargetType="TextBox">
|
||||||
|
<Setter Property="Background" Value="#FF252526"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="Padding" Value="3,0,0,0"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Validation.ErrorTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate>
|
||||||
|
<Border BorderBrush="Red" BorderThickness="1">
|
||||||
|
<AdornedElementPlaceholder/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="TextBox">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="4">
|
||||||
|
<ScrollViewer x:Name="PART_ContentHost" Margin="0,0,0,0"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- CheckBox Style -->
|
||||||
|
<Style TargetType="CheckBox">
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- TextBlock Style -->
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid Margin="20">
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<!-- Title -->
|
||||||
|
<TextBlock Text="Einstellungen" FontSize="24" FontWeight="SemiBold" Margin="0,0,0,20"/>
|
||||||
|
|
||||||
|
<!-- Microsoft 365 Tenant ID -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,10">
|
||||||
|
<TextBlock Text="Tenant ID" VerticalAlignment="Center" Width="70" FontSize="14"/>
|
||||||
|
<TextBox x:Name="TenantIDTextBox" Width="372" Height="30" FontSize="14" TextChanged="TextBox_TextChanged" Text="dddddd"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Microsoft 365 App ID -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,10">
|
||||||
|
<TextBlock Text="App ID" VerticalAlignment="Center" Width="70" FontSize="14"/>
|
||||||
|
<TextBox x:Name="AppIDTextBox" Width="372" Height="30" TextChanged="TextBox_TextChanged"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Toggle SaveEntraCredentials -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,10">
|
||||||
|
<CheckBox x:Name="SaveEntraCredentialsCheck" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="Entra Login speichern" Width="137" FontSize="14" Margin="10,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Toggle UseGraphForMeetingState -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,10" ToolTip="Nutzt statt der lokalen Teams API die Graph API um festzustellen das ein Meeting läuft">
|
||||||
|
<CheckBox x:Name="UseGraphForMeetingStateCheck"/>
|
||||||
|
<TextBlock Text="Graph für den Meeting Status verwenden" VerticalAlignment="Center" Width="259" FontSize="14" Margin="10,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Save and Reset Buttons -->
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,30,0,0">
|
||||||
|
<Button x:Name="ResetButton" Background="DarkRed" Content="Alles zurücksetzen" Width="140" Height="35" Margin="0,0,10,0" Click="ResetButton_Click"/>
|
||||||
|
<Button x:Name="SaveButton" Content="Speichern" Width="100" Height="35" Click="SaveButton_Click" IsEnabled="False"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
132
TeamsNetphoneLinkWPF/WPF/SettingsWindow.xaml.cs
Normal file
132
TeamsNetphoneLinkWPF/WPF/SettingsWindow.xaml.cs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
using Microsoft.Identity.Client.NativeInterop;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using TeamsNetphoneLink;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.WPF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaktionslogik für SettingsWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class SettingsWindow : Window
|
||||||
|
{
|
||||||
|
Syncronisation _sync;
|
||||||
|
|
||||||
|
public SettingsWindow(Syncronisation sync)
|
||||||
|
{
|
||||||
|
_sync = sync;
|
||||||
|
InitializeComponent();
|
||||||
|
TenantIDTextBox.Text = Settings.Default.TenantID;
|
||||||
|
AppIDTextBox.Text = Settings.Default.AppID;
|
||||||
|
SaveEntraCredentialsCheck.IsChecked = Settings.Default.SaveEntraCredentials;
|
||||||
|
UseGraphForMeetingStateCheck.IsChecked = Settings.Default.UseGraphForMeetingState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestartApplication()
|
||||||
|
{
|
||||||
|
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||||
|
var startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = exePath,
|
||||||
|
UseShellExecute = true
|
||||||
|
};
|
||||||
|
Process.Start(startInfo);
|
||||||
|
Application.Current.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speichern der Einstellungen
|
||||||
|
private void SaveButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
string tenantId = TenantIDTextBox.Text.Replace(" ", "");
|
||||||
|
string appId = AppIDTextBox.Text.Replace(" ", "");
|
||||||
|
bool SaveEntraCredentials = SaveEntraCredentialsCheck.IsChecked.HasValue ? SaveEntraCredentialsCheck.IsChecked.Value : false;
|
||||||
|
bool UseGraphForMeetingState = UseGraphForMeetingStateCheck.IsChecked.HasValue ? UseGraphForMeetingStateCheck.IsChecked.Value : false;
|
||||||
|
|
||||||
|
if (SaveEntraCredentials && !ShowSecurityWarningMessage())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Settings.Default.TenantID = tenantId;
|
||||||
|
Settings.Default.AppID = appId;
|
||||||
|
Settings.Default.SaveEntraCredentials = SaveEntraCredentials;
|
||||||
|
Settings.Default.UseGraphForMeetingState = UseGraphForMeetingState;
|
||||||
|
Settings.Default.Save();
|
||||||
|
|
||||||
|
RestartApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset der Einstellungen
|
||||||
|
private void ResetButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Settings.Default.Reset();
|
||||||
|
RestartApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveEntraCredentialsCheck_Checked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
//ShowSecurityWarningMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowSecurityWarningMessage()
|
||||||
|
{
|
||||||
|
string message =
|
||||||
|
"Die Anmeldedaten werden mit dem MSAL Cache unter Verwendung von DPAPI (Data Protection API) gespeichert. " +
|
||||||
|
"DPAPI bietet eine sichere Verschlüsselung der Daten auf Benutzerebene, sodass die Anmeldedaten nur für das Benutzerkonto zugänglich sind, " +
|
||||||
|
"unter dem sie gespeichert wurden.\n\n" +
|
||||||
|
"Allerdings ist zu beachten, dass theoretisch jede Anwendung mit derselben App-ID auf den MSAL Cache zugreifen könnte. " +
|
||||||
|
"Das liegt daran, dass der Cache anhand der App-ID identifiziert wird. Wenn eine andere Anwendung dieselbe App-ID verwendet, " +
|
||||||
|
"könnte sie versuchen, auf die gespeicherten Anmeldedaten zuzugreifen.\n\n" +
|
||||||
|
"Bewertung des Sicherheitsrisikos:\n" +
|
||||||
|
"- In einer vertrauenswürdigen Umgebung ist das Risiko gering.\n" +
|
||||||
|
"- Die App-ID sollte geheim gehalten werden, um unbefugten Zugriff zu verhindern.\n" +
|
||||||
|
"- DPAPI bietet eine starke Verschlüsselung, solange das Benutzerkonto nicht kompromittiert ist.\n\n" +
|
||||||
|
"Unter normalen Umständen stellt die Verwendung des MSAL Caches kein signifikantes Sicherheitsrisiko dar. " +
|
||||||
|
"Es ist jedoch wichtig, die App-ID geheim zu halten und sicherzustellen, dass nur vertrauenswürdige Anwendungen dieselbe App-ID verwenden.\n\n" +
|
||||||
|
"Sollen die Einstellungen gespeichert werden?.\n\n";
|
||||||
|
|
||||||
|
var result = MessageBox.Show(
|
||||||
|
message,
|
||||||
|
"Sicherheitshinweis zur Speicherung von Anmeldedaten",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Warning
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == MessageBoxResult.Yes)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
ValidateInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateInput()
|
||||||
|
{
|
||||||
|
if (this.IsInitialized)
|
||||||
|
{
|
||||||
|
bool isAppIDValid = Teams.TeamsGraph.IsGuid(AppIDTextBox.Text);
|
||||||
|
bool isTenantIDValid = Teams.TeamsGraph.IsGuid(TenantIDTextBox.Text);
|
||||||
|
|
||||||
|
// Set border color based on validation
|
||||||
|
AppIDTextBox.BorderBrush = isAppIDValid ? System.Windows.Media.Brushes.Green : System.Windows.Media.Brushes.Red;
|
||||||
|
TenantIDTextBox.BorderBrush = isTenantIDValid ? System.Windows.Media.Brushes.Green : System.Windows.Media.Brushes.Red;
|
||||||
|
|
||||||
|
// Enable Save button only if both fields are valid
|
||||||
|
SaveButton.IsEnabled = isAppIDValid && isTenantIDValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
TeamsNetphoneLinkWPF/WPF/SetupLocalTeamsAPI.xaml
Normal file
83
TeamsNetphoneLinkWPF/WPF/SetupLocalTeamsAPI.xaml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<Window
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:Wpf="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf" xmlns:local="clr-namespace:TeamsNetphoneLink.WPF.MVVM" x:Class="TeamsNetphoneLink.WPF.SetupLocalTeamsAPI"
|
||||||
|
WindowStyle="ToolWindow" ResizeMode="NoResize"
|
||||||
|
Background="#FF1E1E1E" FontFamily="Segoe UI"
|
||||||
|
Title="Teams API Integration" Height="497" Width="525">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="#FF0078D4"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="Padding" Value="10,5"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
CornerRadius="4"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- TextBlock Style -->
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="Bitte folgende Schritte durchführen, um die Teams API zu aktivieren und damit die Anwendung zu genehmigen." Margin="10" Height="45" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Bold" FontSize="16"/>
|
||||||
|
|
||||||
|
<StackPanel x:Name="Step1Panel" Margin="10">
|
||||||
|
<TextBlock Text="1. Aktivieren der Teams Drittanbieter API:"/>
|
||||||
|
<!-- Hyperlink in einem TextBlock unterbringen -->
|
||||||
|
<TextBlock>
|
||||||
|
<Hyperlink x:Name="Step1Link" NavigateUri="https://support.microsoft.com/de-de/office/herstellen-einer-verbindung-mit-drittanbieterger%C3%A4ten-in-microsoft-teams-aabca9f2-47bb-407f-9f9b-81a104a883d6" RequestNavigate="Step1Link_RequestNavigate">
|
||||||
|
<Run Text="Anleitung für die Aktivierung der Drittanbieter API"/>
|
||||||
|
</Hyperlink>
|
||||||
|
</TextBlock>
|
||||||
|
<Button x:Name="Step1Button" Content="Drittanbieter-API aktiviert" Width="200" Margin="10" Click="Step1Button_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel x:Name="Step2Panel" Margin="10" Height="61" IsEnabled="False" >
|
||||||
|
<StackPanel.Effect >
|
||||||
|
<Wpf:TintEffect Tint="#FFA4A0A0"/>
|
||||||
|
</StackPanel.Effect>
|
||||||
|
<TextBlock Text="2. Starten Sie ein Teams Meeting mit sich selbst:"/>
|
||||||
|
<Button x:Name="Step2Button" Content="Teams Meeting ist gestartet" Width="200" Margin="10" Click="Step2Button_Click"/>
|
||||||
|
<TextBlock x:Name="Step2Status" Margin="10"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Schritt 3: Authentifizieren -->
|
||||||
|
<StackPanel x:Name="Step3Panel" Margin="10" IsEnabled="False" >
|
||||||
|
<StackPanel.Effect >
|
||||||
|
<Wpf:TintEffect Tint="#FFA4A0A0"/>
|
||||||
|
</StackPanel.Effect>
|
||||||
|
<TextBlock Text="3. Zulassen der Verbindungsanforderung:"/>
|
||||||
|
<TextBlock Margin="10,0,10,0" Height="34" TextWrapping="Wrap" TextAlignment="Center" FontSize="12"><Run Text="Nach dem Klick auf dem Button wird in Teams eine Anforderung sichtbar, diese bitte zulassen"/></TextBlock>
|
||||||
|
<Button x:Name="Step3Button" Content="Verbindungsanforderung senden" Width="227" Margin="10" Click="Step3Button_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Schritt 3: Authentifizieren -->
|
||||||
|
<StackPanel x:Name="FinishPanel" Margin="10" IsEnabled="{Binding FinishPanelEnabled}" Effect="{Binding FinishPanelEffect}" >
|
||||||
|
<TextBlock Foreground="{Binding FinishPanelFinishTextColor}" Text="{Binding FinishPanelFinishTextText}" FontWeight="Bold" HorizontalAlignment="Center"/>
|
||||||
|
<Button x:Name="FinishButton" Content="Abschließen" Width="200" Margin="10" Click="FinishButton_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
83
TeamsNetphoneLinkWPF/WPF/SetupLocalTeamsAPI.xaml.cs
Normal file
83
TeamsNetphoneLinkWPF/WPF/SetupLocalTeamsAPI.xaml.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
using Emoji.Wpf;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Policy;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using TeamsNetphoneLink.WPF.MVVM;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.WPF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaktionslogik für SetupLocalTeamsAPI.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class SetupLocalTeamsAPI : Window
|
||||||
|
{
|
||||||
|
private Teams.TeamsLocalAPI teamsLocalAPI;
|
||||||
|
public FinishPanelViewModel finishPanelViewModel { get; }
|
||||||
|
|
||||||
|
public SetupLocalTeamsAPI(Syncronisation sync)
|
||||||
|
{
|
||||||
|
teamsLocalAPI = sync.TeamsEvents;
|
||||||
|
finishPanelViewModel = sync.FinishPanelViewModel;
|
||||||
|
DataContext = finishPanelViewModel;
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Step1Link_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||||
|
{
|
||||||
|
// Öffnet den Link, um die Teams API zu aktivieren
|
||||||
|
Process.Start(new ProcessStartInfo(e.Uri.ToString().Replace("&", "^&")) { UseShellExecute = true });
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Step1Button_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Schritt 1 abgeschlossen - Schalte auf den nächsten Schritt
|
||||||
|
Step1Panel.IsEnabled = false;
|
||||||
|
Step2Panel.IsEnabled = true;
|
||||||
|
Step1Panel.Effect = new TintEffect { Tint = Colors.DarkGray };
|
||||||
|
Step2Panel.Effect = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schritt 2: Teams Meeting starten
|
||||||
|
private void Step2Button_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Öffne die URL, um ein Meeting zu starten (Hier muss der genaue Link oder die API-Integration erfolgen)
|
||||||
|
Step2Panel.IsEnabled = false;
|
||||||
|
Step3Panel.IsEnabled = true;
|
||||||
|
Step2Panel.Effect = new TintEffect { Tint = Colors.DarkGray };
|
||||||
|
Step3Panel.Effect = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schritt 3: Authentifizierung durchführen
|
||||||
|
private async void Step3Button_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Simuliere den Authentifizierungsprozess
|
||||||
|
Step3Button.Content = "Authentifizierung gesendet...";
|
||||||
|
Step3Button.IsEnabled = false;
|
||||||
|
|
||||||
|
this.finishPanelViewModel.FinishPanelFinishTextText = "Authentifizierung läuft...";
|
||||||
|
this.finishPanelViewModel.FinishPanelFinishTextColor = new SolidColorBrush(Colors.Orange);
|
||||||
|
|
||||||
|
teamsLocalAPI.SendDummyCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FinishButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
this.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
183
TeamsNetphoneLinkWPF/WPF/UpdateWindow.xaml
Normal file
183
TeamsNetphoneLinkWPF/WPF/UpdateWindow.xaml
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<Window x:Class="TeamsNetphoneLink.WPF.UpdateWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="Update verfügbar" Height="514" Width="833"
|
||||||
|
Background="#FF1E1E1E" FontFamily="Segoe UI"
|
||||||
|
WindowStyle="ToolWindow" ResizeMode="NoResize"
|
||||||
|
WindowStartupLocation="CenterScreen" Closing="Window_Closing">
|
||||||
|
|
||||||
|
<!-- Dark Mode Resources -->
|
||||||
|
<Window.Resources>
|
||||||
|
<!-- Button Style -->
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="#FF0078D4"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="Padding" Value="10,5"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
CornerRadius="4"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Special Style for Markdown TextBox -->
|
||||||
|
<Style x:Key="MarkdownTextBoxStyle" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
|
||||||
|
<Setter Property="FontFamily" Value="Consolas"/>
|
||||||
|
<Setter Property="Foreground" Value="#FFD4D4D4"/>
|
||||||
|
<Setter Property="Background" Value="#FF1E1E1E"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="IsReadOnly" Value="True"/>
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||||
|
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
|
||||||
|
<Setter Property="HorizontalScrollBarVisibility" Value="Disabled"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- TextBox Style -->
|
||||||
|
<Style TargetType="TextBox">
|
||||||
|
<Setter Property="Background" Value="#FF252526"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Padding" Value="5"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="TextBox">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="4">
|
||||||
|
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ProgressBar Style -->
|
||||||
|
<Style TargetType="ProgressBar">
|
||||||
|
<Setter Property="Background" Value="#FF252526"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Foreground" Value="#FF0078D4"/>
|
||||||
|
<Setter Property="Height" Value="10"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ProgressBar">
|
||||||
|
<Grid x:Name="Root">
|
||||||
|
<Border x:Name="PART_Track"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="5"/>
|
||||||
|
<Border x:Name="PART_Indicator"
|
||||||
|
CornerRadius="5"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<Border.Background>
|
||||||
|
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
|
||||||
|
<GradientStop Color="#FF0078D4" Offset="0"/>
|
||||||
|
<GradientStop Color="#FF00B4FF" Offset="1"/>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</Border.Background>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- TextBlock Style -->
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Title TextBlock Style -->
|
||||||
|
<Style x:Key="TitleTextStyle" TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Setter Property="FontSize" Value="18"/>
|
||||||
|
<Setter Property="FontWeight" Value="Bold"/>
|
||||||
|
<Setter Property="Margin" Value="10,10,10,20"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Status TextBlock Style -->
|
||||||
|
<Style x:Key="StatusTextStyle" TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Setter Property="Margin" Value="10,0,10,5"/>
|
||||||
|
<Setter Property="Foreground" Value="#FFD4D4D4"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid Margin="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<TextBlock x:Name="TitleText"
|
||||||
|
Text="Es ist ein neues Update verfügbar"
|
||||||
|
Style="{StaticResource TitleTextStyle}"
|
||||||
|
Grid.Row="0"/>
|
||||||
|
|
||||||
|
<!-- Markdown Release Notes -->
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Margin="10,0,10,10"
|
||||||
|
Background="#FF252526"
|
||||||
|
CornerRadius="4"
|
||||||
|
|
||||||
|
BorderBrush="#FF3E3E40"
|
||||||
|
BorderThickness="1">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Padding="5">
|
||||||
|
<TextBox x:Name="ReleaseNotesTextBox"
|
||||||
|
Style="{StaticResource MarkdownTextBoxStyle}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
VerticalContentAlignment="Top"
|
||||||
|
Text="Lade Release Notes..."/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Progress Status -->
|
||||||
|
<TextBlock x:Name="ProgressStatusText"
|
||||||
|
Text=""
|
||||||
|
Style="{StaticResource StatusTextStyle}"
|
||||||
|
Grid.Row="2"/>
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<ProgressBar x:Name="UpdateProgress"
|
||||||
|
Margin="10,0,10,10"
|
||||||
|
Grid.Row="3"/>
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
<StackPanel Grid.Row="4"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Button x:Name="CloseButton"
|
||||||
|
Content="Schließen"
|
||||||
|
Width="100"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
Click="CloseButton_Click"/>
|
||||||
|
<Button x:Name="UpdateButton"
|
||||||
|
Content="Update durchführen"
|
||||||
|
Width="150"
|
||||||
|
Background="Green"
|
||||||
|
Click="UpdateButton_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
229
TeamsNetphoneLinkWPF/WPF/UpdateWindow.xaml.cs
Normal file
229
TeamsNetphoneLinkWPF/WPF/UpdateWindow.xaml.cs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace TeamsNetphoneLink.WPF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaktionslogik für ExceptionWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class UpdateWindow : Window
|
||||||
|
{
|
||||||
|
private bool _updateRunning = false;
|
||||||
|
private string _currentVersion;
|
||||||
|
private string _latestVersion;
|
||||||
|
|
||||||
|
|
||||||
|
public UpdateWindow(string currentVersion, string latestVersion)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
// Step 1: Check for updates
|
||||||
|
_currentVersion = currentVersion;
|
||||||
|
_latestVersion = latestVersion;
|
||||||
|
|
||||||
|
TitleText.Text = $"Es ist ein Update von {currentVersion} auf {latestVersion} verfügbar";
|
||||||
|
|
||||||
|
LoadReleaseNotesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void LoadReleaseNotesAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var notes = await GetReleaseNotes();
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
ReleaseNotesTextBox.Text = notes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
ReleaseNotesTextBox.Text = $"Failed to load release notes: {ex.Message}";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button zum Schließen
|
||||||
|
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
this.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button zum Updaten
|
||||||
|
private void UpdateButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
this._updateRunning = true;
|
||||||
|
this.UpdateButton.IsEnabled = false;
|
||||||
|
this.CloseButton.IsEnabled = false;
|
||||||
|
_ = UpdateApplicationAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||||
|
{
|
||||||
|
if (this._updateRunning)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartUpdater(string tempExtractPath)
|
||||||
|
{
|
||||||
|
string appPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
string updaterPathEXE = Path.Combine(appPath, "TeamsNetphoneLinkUpdater.exe");
|
||||||
|
string updaterPathDLL = Path.Combine(appPath, "TeamsNetphoneLinkUpdater.dll");
|
||||||
|
string newUpdaterPathEXE = Path.Combine(tempExtractPath, "TeamsNetphoneLinkUpdater.exe");
|
||||||
|
string newUpdaterPathDLL = Path.Combine(tempExtractPath, "TeamsNetphoneLinkUpdater.dll");
|
||||||
|
|
||||||
|
//Replace the updater if it exists in the new release
|
||||||
|
if (File.Exists(newUpdaterPathEXE))
|
||||||
|
{
|
||||||
|
File.Copy(newUpdaterPathEXE, updaterPathEXE, true);
|
||||||
|
}
|
||||||
|
if (File.Exists(newUpdaterPathDLL))
|
||||||
|
{
|
||||||
|
File.Copy(newUpdaterPathDLL, updaterPathDLL, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Diagnostics.Process p = new System.Diagnostics.Process();
|
||||||
|
p.StartInfo.FileName = updaterPathEXE;
|
||||||
|
p.StartInfo.ArgumentList.Add(appPath);
|
||||||
|
p.StartInfo.ArgumentList.Add(tempExtractPath);
|
||||||
|
p.StartInfo.UseShellExecute = true; // This will start the process in a new shell
|
||||||
|
p.Start();
|
||||||
|
|
||||||
|
// Exit the application
|
||||||
|
Application.Current.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UnzipRelease(string zipPath, string extractPath)
|
||||||
|
{
|
||||||
|
ZipFile.ExtractToDirectory(zipPath, extractPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateApplicationAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
if (_latestVersion != _currentVersion)
|
||||||
|
{
|
||||||
|
UpdateProgress.Value = 0;
|
||||||
|
|
||||||
|
// Step 2: Download the new release
|
||||||
|
string downloadUrl = $"https://git.jan.sx/krjan02/TeamsNetphoneLink/releases/download/latest/NetphoneTeamsLink{_latestVersion}.zip";
|
||||||
|
var tempZipPath = Path.GetTempFileName();
|
||||||
|
tempZipPath = tempZipPath + ".TEAMSNETPHONEUPDATE";
|
||||||
|
Console.WriteLine(tempZipPath);
|
||||||
|
await DownloadReleaseAsync(downloadUrl, tempZipPath);
|
||||||
|
|
||||||
|
// Step 3: Unzip the release
|
||||||
|
var tempExtractPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + "TEAMSNETPHONEUPDATE");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(tempExtractPath);
|
||||||
|
UnzipRelease(tempZipPath, tempExtractPath);
|
||||||
|
|
||||||
|
File.Delete(tempZipPath);
|
||||||
|
|
||||||
|
// Step 4: Replace Updater.exe and run the updater
|
||||||
|
StartUpdater(tempExtractPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() => ProgressStatusText.Text = $"Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetReleaseNotes()
|
||||||
|
{
|
||||||
|
string apiUrl = $"https://git.jan.sx/api/v1/repos/krjan02/TeamsNetphoneLink/releases/tags/{_latestVersion}";
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
// Send a GET request to the Gitea API
|
||||||
|
HttpResponseMessage response = await client.GetAsync(apiUrl);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
// Parse the JSON response
|
||||||
|
string jsonResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
using (JsonDocument doc = JsonDocument.Parse(jsonResponse))
|
||||||
|
{
|
||||||
|
JsonElement root = doc.RootElement;
|
||||||
|
JsonElement body = root.GetProperty("body");
|
||||||
|
|
||||||
|
return body.GetString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task<long> GetFileSize()
|
||||||
|
{
|
||||||
|
string apiUrl = $"https://git.jan.sx/api/v1/repos/krjan02/TeamsNetphoneLink/releases/tags/{_latestVersion}";
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
// Send a GET request to the Gitea API
|
||||||
|
HttpResponseMessage response = await client.GetAsync(apiUrl);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
// Parse the JSON response
|
||||||
|
string jsonResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
using (JsonDocument doc = JsonDocument.Parse(jsonResponse))
|
||||||
|
{
|
||||||
|
JsonElement root = doc.RootElement;
|
||||||
|
JsonElement assets = root.GetProperty("assets");
|
||||||
|
|
||||||
|
// Iterate through the assets to find the file
|
||||||
|
foreach (JsonElement asset in assets.EnumerateArray())
|
||||||
|
{
|
||||||
|
string name = asset.GetProperty("name").GetString();
|
||||||
|
if (name == $"NetphoneTeamsLink{_latestVersion}.zip")
|
||||||
|
{
|
||||||
|
long size = asset.GetProperty("size").GetInt64();
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DownloadReleaseAsync(string downloadUrl, string destinationPath)
|
||||||
|
{
|
||||||
|
using (var client = new HttpClient())
|
||||||
|
{
|
||||||
|
var response = await client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var totalBytes = await GetFileSize();
|
||||||
|
var downloadedBytes = 0L;
|
||||||
|
var buffer = new byte[8192];
|
||||||
|
|
||||||
|
using (var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||||
|
using (var stream = await response.Content.ReadAsStreamAsync())
|
||||||
|
{
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||||
|
{
|
||||||
|
fileStream.Write(buffer, 0, bytesRead);
|
||||||
|
downloadedBytes += bytesRead;
|
||||||
|
// Update progress in the UI
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
ProgressStatusText.Text = $"Update wird heruntergeladen... ({downloadedBytes/1000}kB/{totalBytes/1000}kB)";
|
||||||
|
UpdateProgress.Value = (double)downloadedBytes / totalBytes * 100;
|
||||||
|
//StatusText.Text = $"Downloading... {DownloadProgress.Value:F2}%";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user