Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions FinTrack/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using FinTrackForWindows.Services.Accounts;
using FinTrackForWindows.Services.Api;
using FinTrackForWindows.Services.Budgets;
using FinTrackForWindows.Services.Camera;
using FinTrackForWindows.Services.Currencies;
using FinTrackForWindows.Services.Debts;
using FinTrackForWindows.Services.Memberships;
Expand Down Expand Up @@ -87,6 +88,8 @@ private void ConfigureServices(IServiceCollection services)
services.AddSingleton<ICurrenciesStore, CurrenciesStore>();
services.AddSingleton<IMembershipStore, MembershipStore>();
services.AddSingleton<IDebtStore, DebtStore>();

services.AddTransient<ICameraService, CameraService>();
}

protected override async void OnStartup(StartupEventArgs e)
Expand Down
1 change: 1 addition & 0 deletions FinTrack/Dtos/DebtDtos/DebtDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class DebtDto
public DateTime DueDateUtc { get; set; }
public string Description { get; set; } = null!;
public DebtStatusType Status { get; set; }
public int? VideoMetadataId { get; set; }
public DateTime CreateAtUtc { get; set; }
public DateTime? UpdatedAtUtc { get; set; }
public DateTime? PaidAtUtc { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion FinTrack/Enums/DebtStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public enum DebtStatusType
{
PendingBorrowerAcceptance, // Borç Alan Onayı Bekliyor
AcceptedPendingVideoUpload, // Borçlu Kabul Etti, Video Yüklemesi Bekleniyor (YENİ)
AcceptedPendingVideoUpload, // Borçlu Kabul Etti, Video Yüklemesi Bekleniyor
PendingOperatorApproval, // Operatör Onayı Bekliyor
Active, // Aktif Borç
PaymentConfirmationPending, // Ödeme Onayı Bekliyor
Expand Down
4 changes: 4 additions & 0 deletions FinTrack/FinTrackForWindows.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@

<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Emgu.CV.Bitmap" Version="4.11.0.5746" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.11.0.5746" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc5.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.0-preview.6.25358.103">
Expand All @@ -56,10 +58,12 @@
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="ReactiveUI.WPF" Version="20.4.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.1-dev-02307" />
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.1-dev-02317" />
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.12.1" />
</ItemGroup>

Expand Down
44 changes: 34 additions & 10 deletions FinTrack/Models/Debt/DebtModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public partial class DebtModel : ObservableObject
public int BorrowerId { get; set; }
public int CurrentUserId { get; set; }

public int? VideoMetadataId { get; set; }

[ObservableProperty]
private string lenderName = string.Empty;

Expand All @@ -21,14 +23,20 @@ public partial class DebtModel : ObservableObject
private decimal amount;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanMarkAsDefaulted))]
private DateTime dueDate;

public string borrowerImageUrl = string.Empty;

public string lenderImageUrl = string.Empty;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusText), nameof(StatusBrush), nameof(IsActionRequiredForBorrower), nameof(IsRejected))]
[NotifyPropertyChangedFor(nameof(StatusText))]
[NotifyPropertyChangedFor(nameof(StatusBrush))]
[NotifyPropertyChangedFor(nameof(IsActionRequiredForBorrower))]
[NotifyPropertyChangedFor(nameof(IsRejected))]
[NotifyPropertyChangedFor(nameof(IsVideoViewableForLender))]
[NotifyPropertyChangedFor(nameof(CanMarkAsDefaulted))]
private DebtStatusType status;

public bool IsCurrentUserTheBorrower => BorrowerId == CurrentUserId;
Expand All @@ -44,13 +52,14 @@ public partial class DebtModel : ObservableObject

public Brush StatusBrush => Status switch
{
DebtStatusType.Active => new SolidColorBrush(Colors.Green),
DebtStatusType.Defaulted => new SolidColorBrush(Colors.DarkRed),
DebtStatusType.AcceptedPendingVideoUpload => new SolidColorBrush(Colors.CornflowerBlue),
DebtStatusType.PendingBorrowerAcceptance => new SolidColorBrush(Colors.DodgerBlue),
DebtStatusType.PendingOperatorApproval => new SolidColorBrush(Colors.Orange),
DebtStatusType.RejectedByOperator => new SolidColorBrush(Colors.Red),
DebtStatusType.RejectedByBorrower => new SolidColorBrush(Colors.Red),
DebtStatusType.Active => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50")), // Green
DebtStatusType.Defaulted => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#B71C1C")), // Dark Red
DebtStatusType.AcceptedPendingVideoUpload => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1E88E5")), // Blue
DebtStatusType.PendingBorrowerAcceptance => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#03A9F4")), // Light Blue
DebtStatusType.PendingOperatorApproval => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FB8C00")), // Orange
DebtStatusType.RejectedByOperator => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#E53935")), // Red
DebtStatusType.RejectedByBorrower => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#E53935")), // Red
DebtStatusType.Paid => new SolidColorBrush(Colors.Gray),
_ => new SolidColorBrush(Colors.Gray)
};

Expand All @@ -61,17 +70,32 @@ public partial class DebtModel : ObservableObject
DebtStatusType.PendingOperatorApproval => "Operatör Onayı Bekleniyor",
DebtStatusType.Active => "Aktif",
DebtStatusType.Paid => "Ödendi",
DebtStatusType.Defaulted => "Vadesi Geçmiş",
DebtStatusType.Defaulted => "Vadesi Geçmiş - Temerrüt",
DebtStatusType.RejectedByBorrower => "Tarafınızdan Reddedildi",
DebtStatusType.RejectedByOperator => "Operatör Tarafından Reddedildi",
_ => "Bilinmeyen Durum"
};

// Borçlunun video yüklemesi gerekip gerekmediğini kontrol eder.
public bool IsActionRequiredForBorrower =>
Status == DebtStatusType.AcceptedPendingVideoUpload && IsCurrentUserTheBorrower;

public bool IsRejected =>
Status == DebtStatusType.RejectedByBorrower || Status == DebtStatusType.RejectedByOperator;

/// <summary>
/// "Teminat Videosunu İzle" düğmesinin görünür olup olmayacağını belirler.
/// </summary>
public bool IsVideoViewableForLender =>
Status == DebtStatusType.Defaulted && // Durum 'Defaulted' olmalı
IsCurrentUserTheLender && // Kullanıcı borç veren olmalı
VideoMetadataId.HasValue; // İlişkili bir video olmalı

/// <summary>
/// "Temerrüde Düştü Olarak İşaretle" düğmesinin görünür olup olmayacağını belirleyecek.
/// </summary>
public bool CanMarkAsDefaulted =>
Status == DebtStatusType.Active && // Durum 'Active' olmalı
IsCurrentUserTheLender && // Kullanıcı borç veren olmalı
DueDate < DateTime.Now; // Vadesi geçmiş olmalı
}
}
33 changes: 32 additions & 1 deletion FinTrack/Services/Api/ApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
_logger = logger;
_configuration = configuration;

_baseUrl = "http://localhost:5000/";
_baseUrl = "http://localhost:8090/";
//_baseUrl = _configuration["BaseServerUrl"];
_httpClient = new HttpClient
{
Expand All @@ -50,7 +50,7 @@
}
}

public async Task<T?> DeleteAsync<T>(string endpoint)

Check warning on line 53 in FinTrack/Services/Api/ApiService.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Nullability of reference types in return type of 'Task<T?> ApiService.DeleteAsync<T>(string endpoint)' doesn't match implicitly implemented member 'Task<T> IApiService.DeleteAsync<T>(string endpoint)'.
{
_logger.LogInformation("DELETE isteği başlatılıyor: {Endpoint}", endpoint);
if (string.IsNullOrWhiteSpace(endpoint))
Expand Down Expand Up @@ -84,7 +84,7 @@
}
}

public async Task<T?> GetAsync<T>(string endpoint)

Check warning on line 87 in FinTrack/Services/Api/ApiService.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Nullability of reference types in return type of 'Task<T?> ApiService.GetAsync<T>(string endpoint)' doesn't match implicitly implemented member 'Task<T> IApiService.GetAsync<T>(string endpoint)'.
{
_logger.LogInformation("GET isteği başlatılıyor: {Endpoint}", endpoint);
try
Expand Down Expand Up @@ -149,7 +149,7 @@
}
}

public async Task<T?> PostAsync<T>(string endpoint, object data)

Check warning on line 152 in FinTrack/Services/Api/ApiService.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Nullability of reference types in return type of 'Task<T?> ApiService.PostAsync<T>(string endpoint, object data)' doesn't match implicitly implemented member 'Task<T> IApiService.PostAsync<T>(string endpoint, object data)'.
{
_logger.LogInformation("POST isteği başlatılıyor: {Endpoint}", endpoint);
if (string.IsNullOrWhiteSpace(endpoint))
Expand Down Expand Up @@ -191,7 +191,7 @@
}
}

public async Task<T?> PutAsync<T>(string endpoint, object data)

Check warning on line 194 in FinTrack/Services/Api/ApiService.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Nullability of reference types in return type of 'Task<T?> ApiService.PutAsync<T>(string endpoint, object data)' doesn't match implicitly implemented member 'Task<T> IApiService.PutAsync<T>(string endpoint, object data)'.
{
_logger.LogInformation("PUT isteği başlatılıyor: {Endpoint}", endpoint);
if (string.IsNullOrWhiteSpace(endpoint))
Expand Down Expand Up @@ -381,5 +381,36 @@
throw;
}
}

public async Task<(Stream? Stream, string? ContentType, string? FileName)> StreamFileAsync(string endpoint)
{
_logger.LogInformation("Streaming file request started: {Endpoint}", endpoint);
try
{
AddAuthorizationHeader();

var response = await _httpClient.GetAsync(endpoint, HttpCompletionOption.ResponseHeadersRead);

response.EnsureSuccessStatusCode();

var stream = await response.Content.ReadAsStreamAsync();

var contentType = response.Content.Headers.ContentType?.ToString();
var fileName = response.Content.Headers.ContentDisposition?.FileName?.Trim('"');

_logger.LogInformation("File stream successfully retrieved from {Endpoint}", endpoint);
return (stream, contentType, fileName);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "HTTP error during file stream: {Endpoint}. Status: {StatusCode}", endpoint, ex.StatusCode);
return (null, null, null);
}
catch (Exception ex)
{
_logger.LogError(ex, "Generic error during file stream: {Endpoint}", endpoint);
return (null, null, null);
}
}
}
}
5 changes: 4 additions & 1 deletion FinTrack/Services/Api/IApiService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace FinTrackForWindows.Services.Api
using System.IO;

namespace FinTrackForWindows.Services.Api
{
public interface IApiService
{
Expand All @@ -9,5 +11,6 @@ public interface IApiService
Task<bool> CreateCategoryAsync(string endpoint, object payload);
Task<bool> UploadFileAsync(string endpoint, string filePath);
Task<(byte[] FileBytes, string FileName)?> PostAndDownloadReportAsync<T>(string endpoint, T payload);
Task<(Stream? Stream, string? ContentType, string? FileName)> StreamFileAsync(string endpoint);
}
}
2 changes: 1 addition & 1 deletion FinTrack/Services/AuthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public AuthService(IConfiguration configuration)
_configuration = configuration;
_httpClient = new HttpClient
{
BaseAddress = new Uri("http://localhost:5000/")
BaseAddress = new Uri("http://localhost:8090/")
//BaseAddress = new Uri(_configuration["BaseServerUrl"])
};

Expand Down
117 changes: 117 additions & 0 deletions FinTrack/Services/Camera/CameraService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using Emgu.CV;
using Emgu.CV.CvEnum;
using Microsoft.Extensions.Logging;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace FinTrackForWindows.Services.Camera
{
public class CameraService : ICameraService
{
private VideoCapture? _capture;
private VideoWriter? _writer;
private DispatcherTimer? _timer;

private readonly ILogger<CameraService> _logger;

public Action<BitmapSource>? OnFrameReady { get; set; }

Check warning on line 19 in FinTrack/Services/Camera/CameraService.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Nullability of reference types in return type of 'Action<BitmapSource>? CameraService.OnFrameReady.get' doesn't match implicitly implemented member 'Action<BitmapSource> ICameraService.OnFrameReady.get' (possibly because of nullability attributes).
Copy link

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider using an event instead of an Action property for better encapsulation and thread safety. This would prevent external code from setting the callback to null accidentally.

Suggested change
public Action<BitmapSource>? OnFrameReady { get; set; }
public event Action<BitmapSource>? OnFrameReady;

Copilot uses AI. Check for mistakes.

public CameraService(ILogger<CameraService> logger)
{
_logger = logger;
}

public bool InitializeCamera(int cameraIndex = 0)
{
try
{
_capture = new VideoCapture(cameraIndex);
if (!_capture.IsOpened) return false;

double fps = _capture.Get(CapProp.Fps);
_timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(1000 / (fps > 0 ? fps : 30))
};
_timer.Tick += Timer_Tick;
_timer.Start();
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize camera.");
return false;
}
}

private void Timer_Tick(object? sender, EventArgs e)
{
if (_capture == null || !_capture.IsOpened) return;

using (Mat frame = _capture.QueryFrame())
{
if (frame != null)
{
OnFrameReady?.Invoke(ConvertMatToBitmapSource(frame));

if (_writer != null && _writer.IsOpened)
{
_writer.Write(frame);
}
}
}
}

public string StartRecording()
{
if (_capture == null || !_capture.IsOpened)
throw new InvalidOperationException("Camera is not initialized.");

string tempPath = Path.GetTempPath();
string outputFilePath = Path.Combine(tempPath, $"debt_video_{Guid.NewGuid()}.mp4");

int frameWidth = (int)_capture.Get(CapProp.FrameWidth);
int frameHeight = (int)_capture.Get(CapProp.FrameHeight);
double fps = _capture.Get(CapProp.Fps);

_writer = new VideoWriter(outputFilePath, VideoWriter.Fourcc('X', '2', '6', '4'), fps > 0 ? fps : 30, new System.Drawing.Size(frameWidth, frameHeight), true);
return outputFilePath;
}

public void StopRecording()
{
_writer?.Dispose();
_writer = null;
}

public void Release()
{
_timer?.Stop();
_capture?.Dispose();
_writer?.Dispose();
}

public void Dispose()
{
Release();
GC.SuppressFinalize(this);
}

private static BitmapSource ConvertMatToBitmapSource(Mat image)
{
using var bitmap = image.ToBitmap();
using var stream = new MemoryStream();
bitmap.Save(stream, ImageFormat.Bmp);
stream.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = stream;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
return bitmapImage;
}
}
}
13 changes: 13 additions & 0 deletions FinTrack/Services/Camera/ICameraService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Windows.Media.Imaging;

namespace FinTrackForWindows.Services.Camera
{
public interface ICameraService : IDisposable
{
Action<BitmapSource> OnFrameReady { get; set; }
bool InitializeCamera(int cameraIndex = 0);
string StartRecording();
void StopRecording();
void Release();
}
}
Loading
Loading