Compare commits

3 Commits

Author SHA1 Message Date
Van Leemput Dayron e665dea82a feat(activities): add autocomplete & what's new popup
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 2m6s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Successful in 4m3s
Features:
- Add autocomplete support for Activity search with Google Places API.
- Add "What's New" popup system to showcase new features on app update.
- Implement logic to detect fresh installs vs updates.

Fixes:
- Switch API key handling to use Firebase config for Release mode support.
- Refactor map pins to be consistent (red pins).
- UI fixes on Create Trip page (overflow issues).

Refactor:
- Make WhatsNewDialog reusable by accepting features list as parameter.
2026-01-13 17:36:51 +01:00
Van Leemput Dayron b511ec5df0 Update map autocompletion
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 2m5s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Successful in 3m53s
2026-01-13 17:15:22 +01:00
Van Leemput Dayron c0e53cd3f6 feat: enhance global search and map experience
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 2m10s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Successful in 4m19s
- Global Activity Search:
  - Allow searching activities globally (not just in destination).
  - Add distance warning for activities > 50km away.
- Create Trip UI:
  - Fix destination suggestion list overflow.
  - Prevent suggestion list from reappearing after selection.
- Map:
  - Add generic text search support (e.g., "Restaurants") on 'Enter'.
  - Display multiple results for generic searches.
  - Resize markers (User 60.0, Places 50.0).
  - Standardize place markers to red pin.
2026-01-13 16:59:04 +01:00
67 changed files with 798 additions and 6836 deletions
+6 -1
View File
@@ -50,4 +50,9 @@ app.*.map.json
.env.*.local
firestore.rules
storage.rules
/functions/node_modules
/functions/node_modules
.idea
.vscode
.VSCodeCounter
+1 -1
View File
@@ -59,4 +59,4 @@ Travel Mate est une application mobile conçue pour simplifier l'organisation de
## 🚀 CI/CD & Déploiement
Les versions de test interne Android sont automatiquement distribuées via **Firebase App Distribution**.
Les versions de test interne Android et IOS sont automatiquement distribuées via **Firebase App Distribution**.
-17
View File
@@ -1,17 +0,0 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["TravelMate.Backend.csproj", "./"]
RUN dotnet restore "TravelMate.Backend.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "TravelMate.Backend.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "TravelMate.Backend.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=publish /app/publish .
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "TravelMate.Backend.dll"]
-356
View File
@@ -1,356 +0,0 @@
using DotNetEnv;
using Google.Apis.AnalyticsData.v1beta;
using Google.Apis.AnalyticsData.v1beta.Data;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Microsoft.AspNetCore.Mvc;
Env.Load();
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Register Google Analytics Data Client
builder.Services.AddSingleton<AnalyticsDataService>(sp =>
{
GoogleCredential credential;
try
{
// Tries to find credentials from GOOGLE_APPLICATION_CREDENTIALS environment variable
// or default cloud environment credentials.
credential = GoogleCredential.GetApplicationDefault();
}
catch (Exception)
{
// Fallback for local development if env var not set (User might need to run 'gcloud auth application-default login')
// Or we warn them. For now, we assume it's set up or will fail at runtime.
throw new InvalidOperationException("Could not load Google Credentials. Set GOOGLE_APPLICATION_CREDENTIALS or run 'gcloud auth application-default login'.");
}
if (credential.IsCreateScopedRequired)
{
credential = credential.CreateScoped(AnalyticsDataService.Scope.AnalyticsReadonly);
}
var service = new AnalyticsDataService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "TravelMate Backend",
});
return service;
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
string? ga4PropertyId = Environment.GetEnvironmentVariable("GA4_PROPERTY_ID");
if (string.IsNullOrEmpty(ga4PropertyId))
{
Console.WriteLine("Warning: GA4_PROPERTY_ID not set in .env");
// Default or error? Let's just hold it and fail in the endpoint if needed, or fail startup.
// For now, let's allow it to start but endpoint will likely fail.
}
app.MapGet("/api/metrics/daily", async (
[FromServices] AnalyticsDataService service,
[FromQuery] string from,
[FromQuery] string to) =>
{
// Basic validation
// Basic validation: Check if it's a valid date OR a valid GA4 keyword
bool IsValidDate(string date)
{
return DateTime.TryParse(date, out _) ||
date == "today" ||
date == "yesterday" ||
date.EndsWith("daysAgo");
}
if (!IsValidDate(from) || !IsValidDate(to))
{
return Results.BadRequest(new { error = "Invalid date format. Use YYYY-MM-DD, 'today', 'yesterday', or 'NdaysAgo'." });
}
var request = new RunReportRequest
{
Property = $"properties/{ga4PropertyId}",
DateRanges = new List<DateRange>
{
new DateRange { StartDate = from, EndDate = to }
},
Dimensions = new List<Dimension>
{
new Dimension { Name = "date" }
},
Metrics = new List<Metric>
{
new Metric { Name = "activeUsers" } // equivalent to Daily Active Users in this context often
// Or strictly "activeUsers"
}
};
try
{
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
var result = response.Rows?.Select(row => new
{
date = FormatDate(row.DimensionValues[0].Value), // GA4 returns date as YYYYMMDD string usually
dailyActiveUsers = int.Parse(row.MetricValues[0].Value)
}) ?? Enumerable.Empty<object>();
return Results.Ok(result);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex}");
return Results.Problem($"Analytics API Error: {ex.Message}");
}
})
.WithName("GetDailyMetrics")
.WithOpenApi();
app.MapGet("/api/metrics/general", async (
[FromServices] AnalyticsDataService service) =>
{
// "Global" means all time ideally, or a very long range.
// GA4 retention might limit this for user-level data, but aggregated metrics usually go back further.
var startDate = "2023-01-01"; // Arbitrary start date for the app
var endDate = "today";
var request = new RunReportRequest
{
Property = $"properties/{ga4PropertyId}",
DateRanges = new List<DateRange>
{
new DateRange { StartDate = startDate, EndDate = endDate }
},
Metrics = new List<Metric>
{
new Metric { Name = "totalUsers" }, // Proxy for "nb de comptes"
new Metric { Name = "eventCount" } // Proxy for "nb de requètes"
}
};
try
{
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
var totalUsers = 0;
var eventCount = 0;
if (response.Rows != null && response.Rows.Count > 0)
{
var row = response.Rows[0];
totalUsers = int.Parse(row.MetricValues[0].Value);
eventCount = int.Parse(row.MetricValues[1].Value);
}
return Results.Ok(new
{
totalAccounts = totalUsers,
totalRequests = eventCount
});
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex}");
return Results.Problem($"Analytics API Error: {ex.Message}");
}
})
.WithName("GetGeneralMetrics")
.WithOpenApi();
// 3. Tech Overview (OS & App Version)
// GET /api/metrics/tech?from=x&to=y
app.MapGet("/api/metrics/tech", async (
[FromServices] AnalyticsDataService service,
[FromQuery] string? from,
[FromQuery] string? to) =>
{
var startDate = from ?? "30daysAgo";
var endDate = to ?? "today";
// Request for OS
var requestOS = new RunReportRequest
{
Property = $"properties/{ga4PropertyId}",
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
Dimensions = new List<Dimension> { new Dimension { Name = "operatingSystem" } },
Metrics = new List<Metric> { new Metric { Name = "activeUsers" } }
};
// Request for App Version
var requestVersion = new RunReportRequest
{
Property = $"properties/{ga4PropertyId}",
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
Dimensions = new List<Dimension> { new Dimension { Name = "appVersion" } },
Metrics = new List<Metric> { new Metric { Name = "activeUsers" } } // use active users to see version adoption
};
try
{
var responseOS = await service.Properties.RunReport(requestOS, $"properties/{ga4PropertyId}").ExecuteAsync();
var responseVersion = await service.Properties.RunReport(requestVersion, $"properties/{ga4PropertyId}").ExecuteAsync();
var osData = responseOS.Rows?.Select(row => new {
os = row.DimensionValues[0].Value,
users = int.Parse(row.MetricValues[0].Value)
}) ?? Enumerable.Empty<object>();
var versionData = responseVersion.Rows?.Select(row => new {
version = row.DimensionValues[0].Value,
users = int.Parse(row.MetricValues[0].Value)
}) ?? Enumerable.Empty<object>();
return Results.Ok(new { operatingSystems = osData, appVersions = versionData });
}
catch (Exception ex)
{
return Results.Problem($"Analytics API Error: {ex.Message}");
}
})
.WithName("GetTechOverview")
.WithOpenApi();
// 4. Top Events
// GET /api/metrics/events?from=x&to=y
app.MapGet("/api/metrics/events", async (
[FromServices] AnalyticsDataService service,
[FromQuery] string? from,
[FromQuery] string? to) =>
{
var startDate = from ?? "30daysAgo";
var endDate = to ?? "today";
var request = new RunReportRequest
{
Property = $"properties/{ga4PropertyId}",
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
Dimensions = new List<Dimension> { new Dimension { Name = "eventName" } },
Metrics = new List<Metric> { new Metric { Name = "eventCount" }, new Metric { Name = "activeUsers" } },
OrderBys = new List<OrderBy> { new OrderBy { Desc = true, Metric = new MetricOrderBy { MetricName = "eventCount" } } },
Limit = 20
};
try
{
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
var result = response.Rows?.Select(row => new {
eventName = row.DimensionValues[0].Value,
count = int.Parse(row.MetricValues[0].Value),
users = int.Parse(row.MetricValues[1].Value)
}) ?? Enumerable.Empty<object>();
return Results.Ok(result);
}
catch (Exception ex)
{
return Results.Problem($"Analytics API Error: {ex.Message}");
}
})
.WithName("GetTopEvents")
.WithOpenApi();
// 5. Top Screens
// GET /api/metrics/screens?from=x&to=y
app.MapGet("/api/metrics/screens", async (
[FromServices] AnalyticsDataService service,
[FromQuery] string? from,
[FromQuery] string? to) =>
{
var startDate = from ?? "30daysAgo";
var endDate = to ?? "today";
var request = new RunReportRequest
{
Property = $"properties/{ga4PropertyId}",
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
// GA4 uses 'unifiedScreenName' or 'pagePath' depending on setup, usually 'unifiedScreenName' for apps
Dimensions = new List<Dimension> { new Dimension { Name = "unifiedScreenName" } },
Metrics = new List<Metric> { new Metric { Name = "screenPageViews" }, new Metric { Name = "activeUsers" } },
OrderBys = new List<OrderBy> { new OrderBy { Desc = true, Metric = new MetricOrderBy { MetricName = "screenPageViews" } } },
Limit = 20
};
try
{
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
var result = response.Rows?.Select(row => new {
screenName = row.DimensionValues[0].Value,
views = int.Parse(row.MetricValues[0].Value),
users = int.Parse(row.MetricValues[1].Value)
}) ?? Enumerable.Empty<object>();
return Results.Ok(result);
}
catch (Exception ex)
{
return Results.Problem($"Analytics API Error: {ex.Message}");
}
})
.WithName("GetTopScreens")
.WithOpenApi();
// 6. User Retention (New vs Returning)
// GET /api/metrics/retention?from=x&to=y
app.MapGet("/api/metrics/retention", async (
[FromServices] AnalyticsDataService service,
[FromQuery] string? from,
[FromQuery] string? to) =>
{
var startDate = from ?? "30daysAgo";
var endDate = to ?? "today";
var request = new RunReportRequest
{
Property = $"properties/{ga4PropertyId}",
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
Dimensions = new List<Dimension> { new Dimension { Name = "newVsReturning" } },
Metrics = new List<Metric> { new Metric { Name = "activeUsers" }, new Metric { Name = "sessions" } }
};
try
{
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
var result = response.Rows?.Select(row => new {
type = row.DimensionValues[0].Value,
users = int.Parse(row.MetricValues[0].Value),
sessions = int.Parse(row.MetricValues[1].Value)
}) ?? Enumerable.Empty<object>();
return Results.Ok(result);
}
catch (Exception ex)
{
return Results.Problem($"Analytics API Error: {ex.Message}");
}
})
.WithName("GetUserRetention")
.WithOpenApi();
app.Run();
// Helper to format GA4 YYYYMMDD to YYYY-MM-DD
static string FormatDate(string gaDate)
{
if (gaDate.Length == 8)
{
return $"{gaDate.Substring(0, 4)}-{gaDate.Substring(4, 2)}-{gaDate.Substring(6, 2)}";
}
return gaDate;
}
-17
View File
@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DotNetEnv" Version="3.1.1" />
<PackageReference Include="Google.Apis.AnalyticsData.v1beta" Version="1.68.0.3608" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
</Project>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,291 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"TravelMate.Backend/1.0.0": {
"dependencies": {
"DotNetEnv": "3.1.1",
"Google.Apis.AnalyticsData.v1beta": "1.68.0.3608",
"Microsoft.AspNetCore.OpenApi": "8.0.1",
"Swashbuckle.AspNetCore": "6.5.0"
},
"runtime": {
"TravelMate.Backend.dll": {}
}
},
"DotNetEnv/3.1.1": {
"dependencies": {
"Sprache": "2.3.1"
},
"runtime": {
"lib/netstandard1.3/DotNetEnv.dll": {
"assemblyVersion": "3.1.1.0",
"fileVersion": "3.1.1.0"
}
}
},
"Google.Apis/1.68.0": {
"dependencies": {
"Google.Apis.Core": "1.68.0"
},
"runtime": {
"lib/net6.0/Google.Apis.dll": {
"assemblyVersion": "1.68.0.0",
"fileVersion": "1.68.0.0"
}
}
},
"Google.Apis.AnalyticsData.v1beta/1.68.0.3608": {
"dependencies": {
"Google.Apis": "1.68.0",
"Google.Apis.Auth": "1.68.0"
},
"runtime": {
"lib/net6.0/Google.Apis.AnalyticsData.v1beta.dll": {
"assemblyVersion": "1.68.0.3608",
"fileVersion": "1.68.0.3608"
}
}
},
"Google.Apis.Auth/1.68.0": {
"dependencies": {
"Google.Apis": "1.68.0",
"Google.Apis.Core": "1.68.0",
"System.Management": "7.0.2"
},
"runtime": {
"lib/net6.0/Google.Apis.Auth.dll": {
"assemblyVersion": "1.68.0.0",
"fileVersion": "1.68.0.0"
}
}
},
"Google.Apis.Core/1.68.0": {
"dependencies": {
"Newtonsoft.Json": "13.0.3"
},
"runtime": {
"lib/net6.0/Google.Apis.Core.dll": {
"assemblyVersion": "1.68.0.0",
"fileVersion": "1.68.0.0"
}
}
},
"Microsoft.AspNetCore.OpenApi/8.0.1": {
"dependencies": {
"Microsoft.OpenApi": "1.4.3"
},
"runtime": {
"lib/net8.0/Microsoft.AspNetCore.OpenApi.dll": {
"assemblyVersion": "8.0.1.0",
"fileVersion": "8.0.123.58008"
}
}
},
"Microsoft.OpenApi/1.4.3": {
"runtime": {
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
"assemblyVersion": "1.4.3.0",
"fileVersion": "1.4.3.0"
}
}
},
"Newtonsoft.Json/13.0.3": {
"runtime": {
"lib/net6.0/Newtonsoft.Json.dll": {
"assemblyVersion": "13.0.0.0",
"fileVersion": "13.0.3.27908"
}
}
},
"Sprache/2.3.1": {
"runtime": {
"lib/netstandard2.1/Sprache.dll": {
"assemblyVersion": "2.3.1.0",
"fileVersion": "2.3.1.0"
}
}
},
"Swashbuckle.AspNetCore/6.5.0": {
"dependencies": {
"Swashbuckle.AspNetCore.Swagger": "6.5.0",
"Swashbuckle.AspNetCore.SwaggerGen": "6.5.0",
"Swashbuckle.AspNetCore.SwaggerUI": "6.5.0"
}
},
"Swashbuckle.AspNetCore.Swagger/6.5.0": {
"dependencies": {
"Microsoft.OpenApi": "1.4.3"
},
"runtime": {
"lib/net7.0/Swashbuckle.AspNetCore.Swagger.dll": {
"assemblyVersion": "6.5.0.0",
"fileVersion": "6.5.0.0"
}
}
},
"Swashbuckle.AspNetCore.SwaggerGen/6.5.0": {
"dependencies": {
"Swashbuckle.AspNetCore.Swagger": "6.5.0"
},
"runtime": {
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
"assemblyVersion": "6.5.0.0",
"fileVersion": "6.5.0.0"
}
}
},
"Swashbuckle.AspNetCore.SwaggerUI/6.5.0": {
"runtime": {
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
"assemblyVersion": "6.5.0.0",
"fileVersion": "6.5.0.0"
}
}
},
"System.CodeDom/7.0.0": {
"runtime": {
"lib/net7.0/System.CodeDom.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.0.22.51805"
}
}
},
"System.Management/7.0.2": {
"dependencies": {
"System.CodeDom": "7.0.0"
},
"runtime": {
"lib/net7.0/System.Management.dll": {
"assemblyVersion": "7.0.0.2",
"fileVersion": "7.0.723.27404"
}
},
"runtimeTargets": {
"runtimes/win/lib/net7.0/System.Management.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "7.0.0.2",
"fileVersion": "7.0.723.27404"
}
}
}
}
},
"libraries": {
"TravelMate.Backend/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"DotNetEnv/3.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-o4SqUVCq0pqHF/HYsZk6k22XGIVmvsDVo+Dy7l0ubq9uQ45JkXswrMRJmYvhGLXWFYF0M5OupMonytB+0zvpGQ==",
"path": "dotnetenv/3.1.1",
"hashPath": "dotnetenv.3.1.1.nupkg.sha512"
},
"Google.Apis/1.68.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-s2MymhdpH+ybZNBeZ2J5uFgFHApBp+QXf9FjZSdM1lk/vx5VqIknJwnaWiuAzXxPrLEkesX0Q+UsiWn39yZ9zw==",
"path": "google.apis/1.68.0",
"hashPath": "google.apis.1.68.0.nupkg.sha512"
},
"Google.Apis.AnalyticsData.v1beta/1.68.0.3608": {
"type": "package",
"serviceable": true,
"sha512": "sha512-y9BdpAyCIKmRSFPUZEwyD2CtqXgzC8L0gqCqfoeDIn2bBu3RBp+uiLfCe/ns20AuArfnyIJgR4unmvap+9CpHQ==",
"path": "google.apis.analyticsdata.v1beta/1.68.0.3608",
"hashPath": "google.apis.analyticsdata.v1beta.1.68.0.3608.nupkg.sha512"
},
"Google.Apis.Auth/1.68.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-hFx8Qz5bZ4w0hpnn4tSmZaaFpjAMsgVElZ+ZgVLUZ2r9i+AKcoVgwiNfv1pruNS5cCvpXqhKECbruBCfRezPHA==",
"path": "google.apis.auth/1.68.0",
"hashPath": "google.apis.auth.1.68.0.nupkg.sha512"
},
"Google.Apis.Core/1.68.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pAqwa6pfu53UXCR2b7A/PAPXeuVg6L1OFw38WckN27NU2+mf+KTjoEg2YGv/f0UyKxzz7DxF1urOTKg/6dTP9g==",
"path": "google.apis.core/1.68.0",
"hashPath": "google.apis.core.1.68.0.nupkg.sha512"
},
"Microsoft.AspNetCore.OpenApi/8.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JF0U31SByntKEhZNC8ch3CD87BLZb5AzSujOBppLdvnjYu3W2XZvlsol5hUJfxiaHOp720cUiL6wzG8Bv0fI2Q==",
"path": "microsoft.aspnetcore.openapi/8.0.1",
"hashPath": "microsoft.aspnetcore.openapi.8.0.1.nupkg.sha512"
},
"Microsoft.OpenApi/1.4.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-rURwggB+QZYcSVbDr7HSdhw/FELvMlriW10OeOzjPT7pstefMo7IThhtNtDudxbXhW+lj0NfX72Ka5EDsG8x6w==",
"path": "microsoft.openapi/1.4.3",
"hashPath": "microsoft.openapi.1.4.3.nupkg.sha512"
},
"Newtonsoft.Json/13.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
"path": "newtonsoft.json/13.0.3",
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
},
"Sprache/2.3.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Q+mXeiTxiUYG3lKYF6TS82/SyB4F2613Q1yXTMwg4jWGHEEVC3yrzHtNcI4B3qnDI0+eJsezGJ0V+cToUytHWw==",
"path": "sprache/2.3.1",
"hashPath": "sprache.2.3.1.nupkg.sha512"
},
"Swashbuckle.AspNetCore/6.5.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FK05XokgjgwlCI6wCT+D4/abtQkL1X1/B9Oas6uIwHFmYrIO9WUD5aLC9IzMs9GnHfUXOtXZ2S43gN1mhs5+aA==",
"path": "swashbuckle.aspnetcore/6.5.0",
"hashPath": "swashbuckle.aspnetcore.6.5.0.nupkg.sha512"
},
"Swashbuckle.AspNetCore.Swagger/6.5.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-XWmCmqyFmoItXKFsQSwQbEAsjDKcxlNf1l+/Ki42hcb6LjKL8m5Db69OTvz5vLonMSRntYO1XLqz0OP+n3vKnA==",
"path": "swashbuckle.aspnetcore.swagger/6.5.0",
"hashPath": "swashbuckle.aspnetcore.swagger.6.5.0.nupkg.sha512"
},
"Swashbuckle.AspNetCore.SwaggerGen/6.5.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Y/qW8Qdg9OEs7V013tt+94OdPxbRdbhcEbw4NiwGvf4YBcfhL/y7qp/Mjv/cENsQ2L3NqJ2AOu94weBy/h4KvA==",
"path": "swashbuckle.aspnetcore.swaggergen/6.5.0",
"hashPath": "swashbuckle.aspnetcore.swaggergen.6.5.0.nupkg.sha512"
},
"Swashbuckle.AspNetCore.SwaggerUI/6.5.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-OvbvxX+wL8skxTBttcBsVxdh73Fag4xwqEU2edh4JMn7Ws/xJHnY/JB1e9RoCb6XpDxUF3hD9A0Z1lEUx40Pfw==",
"path": "swashbuckle.aspnetcore.swaggerui/6.5.0",
"hashPath": "swashbuckle.aspnetcore.swaggerui.6.5.0.nupkg.sha512"
},
"System.CodeDom/7.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-GLltyqEsE5/3IE+zYRP5sNa1l44qKl9v+bfdMcwg+M9qnQf47wK3H0SUR/T+3N4JEQXF3vV4CSuuo0rsg+nq2A==",
"path": "system.codedom/7.0.0",
"hashPath": "system.codedom.7.0.0.nupkg.sha512"
},
"System.Management/7.0.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/qEUN91mP/MUQmJnM5y5BdT7ZoPuVrtxnFlbJ8a3kBJGhe2wCzBfnPFtK2wTtEEcf3DMGR9J00GZZfg6HRI6yA==",
"path": "system.management/7.0.2",
"hashPath": "system.management.7.0.2.nupkg.sha512"
}
}
}
Binary file not shown.
Binary file not shown.
@@ -1,21 +0,0 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "8.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Globalization.Invariant": true,
"System.Globalization.PredefinedCulturesOnly": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}
@@ -1 +0,0 @@
{"Version":1,"ManifestType":"Build","Endpoints":[]}
-43
View File
@@ -1,43 +0,0 @@
#!/bin/bash
# Configuration
PROJECT_ID="travelmate-a47f5"
SERVICE_NAME="travelmate-backend"
REGION="europe-west1" # Vous pourrez changer ceci si besoin
echo "🚀 Déploiement de $SERVICE_NAME sur $PROJECT_ID..."
# Vérification de gcloud
if ! command -v gcloud &> /dev/null
then
echo "❌ gcloud n'est pas installé."
echo "👉 Installez le Google Cloud SDK : https://cloud.google.com/sdk/docs/install"
echo "Puis lancez 'gcloud auth login' et 'gcloud auth application-default login'"
exit 1
fi
# Build sur Cloud Build (plus simple que Docker local et push)
echo "📦 Construction de l'image sur Google Cloud Build..."
gcloud builds submit --tag gcr.io/$PROJECT_ID/$SERVICE_NAME --project $PROJECT_ID .
if [ $? -ne 0 ]; then
echo "❌ Échec du build."
exit 1
fi
# Déploiement sur Cloud Run
echo "🚀 Déploiement sur Cloud Run..."
gcloud run deploy $SERVICE_NAME \
--image gcr.io/$PROJECT_ID/$SERVICE_NAME \
--platform managed \
--region $REGION \
--project $PROJECT_ID \
--allow-unauthenticated \
--set-env-vars GA4_PROPERTY_ID=507003174
if [ $? -eq 0 ]; then
echo "✅ Déploiement terminé avec succès !"
echo "️ Récupérez l'URL ci-dessus pour votre MauiProgram.cs"
else
echo "❌ Échec du déploiement."
fi
@@ -1,4 +0,0 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
@@ -1,22 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("TravelMate.Backend")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+63fc18ea740090c0a2233c46af179b2dbe32a9b8")]
[assembly: System.Reflection.AssemblyProductAttribute("TravelMate.Backend")]
[assembly: System.Reflection.AssemblyTitleAttribute("TravelMate.Backend")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Généré par la classe MSBuild WriteCodeFragment.
@@ -1 +0,0 @@
0aab77bc3ae598fb4e009b4ecda3aeffcfb640a524b7ecc7591b10a25aa5c92a
@@ -1,23 +0,0 @@
is_global = true
build_property.TargetFramework = net8.0
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v8.0
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb = true
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization = true
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = TravelMate.Backend
build_property.RootNamespace = TravelMate.Backend
build_property.ProjectDir = /Users/dayronvanleemput/Documents/Coding/travel_mate/backend/
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.RazorLangVersion = 8.0
build_property.SupportLocalizedComponentNames =
build_property.GenerateRazorMetadataSourceChecksumAttributes =
build_property.MSBuildProjectDirectory = /Users/dayronvanleemput/Documents/Coding/travel_mate/backend
build_property._RazorSourceGeneratorDebug =
build_property.EffectiveAnalysisLevelStyle = 8.0
build_property.EnableCodeStyleSeverity =
@@ -1,17 +0,0 @@
// <auto-generated/>
global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Routing;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Net.Http;
global using System.Net.Http.Json;
global using System.Threading;
global using System.Threading.Tasks;
@@ -1,17 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute("Microsoft.AspNetCore.OpenApi")]
[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute("Swashbuckle.AspNetCore.SwaggerGen")]
// Généré par la classe MSBuild WriteCodeFragment.
@@ -1 +0,0 @@
564fc6ab625b9b4d1fd8dff0a4b24b86b698bd2655c9670266b6506278a91b74
@@ -1,44 +0,0 @@
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.csproj.AssemblyReference.cache
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/rpswa.dswa.cache.json
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.GeneratedMSBuildEditorConfig.editorconfig
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.AssemblyInfoInputs.cache
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.AssemblyInfo.cs
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.csproj.CoreCompileInputs.cache
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.MvcApplicationPartsAssemblyInfo.cs
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.MvcApplicationPartsAssemblyInfo.cache
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend.staticwebassets.endpoints.json
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend.deps.json
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend.runtimeconfig.json
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend.pdb
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Google.Apis.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Google.Apis.AnalyticsData.v1beta.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Google.Apis.Auth.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Google.Apis.Core.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Microsoft.AspNetCore.OpenApi.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Microsoft.OpenApi.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Newtonsoft.Json.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Swashbuckle.AspNetCore.Swagger.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/System.CodeDom.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/System.Management.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/runtimes/win/lib/net7.0/System.Management.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/rjimswa.dswa.cache.json
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/rjsmrazor.dswa.cache.json
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/rjsmcshtml.dswa.cache.json
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/scopedcss/bundle/TravelMate.Backend.styles.css
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/staticwebassets.build.json
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/staticwebassets.build.json.cache
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/staticwebassets.development.json
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/staticwebassets.build.endpoints.json
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/swae.build.ex.cache
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMa.030FF4D7.Up2Date
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/refint/TravelMate.Backend.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.pdb
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.genruntimeconfig.cache
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/ref/TravelMate.Backend.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/DotNetEnv.dll
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Sprache.dll
Binary file not shown.
@@ -1 +0,0 @@
f79f58c3a8e99961a1a88dcd08a90777e8913bfe44cb3f95b85212ac836b242e
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
{"GlobalPropertiesHash":"mgx0JdLooAvG36KewBSjBhnWYhsOqFG08xB+ZULwM34=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["eZ2qkZPO/MYAX3sbEVnqz208bqpLunsDjAZG7XY4wtU=","KNz8obfBglIfZZ8oXDHzHOqsrgOcLsm\u002BNZPmnNIlemg="],"CachedAssets":{},"CachedCopyCandidates":{}}
@@ -1 +0,0 @@
{"GlobalPropertiesHash":"q+hAwBrcPnVtSgGz/Kp6kP1qVOxpDSxk/3MpYyK9dDk=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["eZ2qkZPO/MYAX3sbEVnqz208bqpLunsDjAZG7XY4wtU=","KNz8obfBglIfZZ8oXDHzHOqsrgOcLsm\u002BNZPmnNIlemg="],"CachedAssets":{},"CachedCopyCandidates":{}}
@@ -1 +0,0 @@
{"Version":1,"ManifestType":"Build","Endpoints":[]}
@@ -1 +0,0 @@
{"Version":1,"Hash":"Zp2AayENaHwwegD1S3er2B42GEf0rjybz+5iZ/TMps0=","Source":"TravelMate.Backend","BasePath":"/","Mode":"Root","ManifestType":"Build","ReferencedProjectsConfiguration":[],"DiscoveryPatterns":[],"Assets":[],"Endpoints":[]}
@@ -1 +0,0 @@
Zp2AayENaHwwegD1S3er2B42GEf0rjybz+5iZ/TMps0=
@@ -1,103 +0,0 @@
{
"format": 1,
"restore": {
"/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/TravelMate.Backend.csproj": {}
},
"projects": {
"/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/TravelMate.Backend.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/TravelMate.Backend.csproj",
"projectName": "TravelMate.Backend",
"projectPath": "/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/TravelMate.Backend.csproj",
"packagesPath": "/Users/dayronvanleemput/.nuget/packages/",
"outputPath": "/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/Users/dayronvanleemput/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"net8.0"
],
"sources": {
"/usr/local/share/dotnet/library-packs": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net8.0": {
"targetAlias": "net8.0",
"dependencies": {
"DotNetEnv": {
"target": "Package",
"version": "[3.1.1, )"
},
"Google.Apis.AnalyticsData.v1beta": {
"target": "Package",
"version": "[1.68.0.3608, )"
},
"Microsoft.AspNetCore.OpenApi": {
"target": "Package",
"version": "[8.0.1, )"
},
"Swashbuckle.AspNetCore": {
"target": "Package",
"version": "[6.5.0, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"downloadDependencies": [
{
"name": "Microsoft.AspNetCore.App.Ref",
"version": "[8.0.22, 8.0.22]"
},
{
"name": "Microsoft.NETCore.App.Host.osx-arm64",
"version": "[8.0.22, 8.0.22]"
},
{
"name": "Microsoft.NETCore.App.Ref",
"version": "[8.0.22, 8.0.22]"
}
],
"frameworkReferences": {
"Microsoft.AspNetCore.App": {
"privateAssets": "none"
},
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "/usr/local/share/dotnet/sdk/10.0.100/PortableRuntimeIdentifierGraph.json"
}
}
}
}
}
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/Users/dayronvanleemput/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/Users/dayronvanleemput/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/Users/dayronvanleemput/.nuget/packages/" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.props')" />
<Import Project="$(NuGetPackageRoot)swashbuckle.aspnetcore/6.5.0/build/Swashbuckle.AspNetCore.props" Condition="Exists('$(NuGetPackageRoot)swashbuckle.aspnetcore/6.5.0/build/Swashbuckle.AspNetCore.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">/Users/dayronvanleemput/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server>
</PropertyGroup>
</Project>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.targets')" />
</ImportGroup>
</Project>
File diff suppressed because it is too large Load Diff
-106
View File
@@ -1,106 +0,0 @@
{
"version": 2,
"dgSpecHash": "6JHw9LAEeo0=",
"success": true,
"projectFilePath": "/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/TravelMate.Backend.csproj",
"expectedPackageFiles": [
"/Users/dayronvanleemput/.nuget/packages/dotnetenv/3.1.1/dotnetenv.3.1.1.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/google.apis/1.68.0/google.apis.1.68.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/google.apis.analyticsdata.v1beta/1.68.0.3608/google.apis.analyticsdata.v1beta.1.68.0.3608.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/google.apis.auth/1.68.0/google.apis.auth.1.68.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/google.apis.core/1.68.0/google.apis.core.1.68.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.aspnetcore.openapi/8.0.1/microsoft.aspnetcore.openapi.8.0.1.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5/microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.extensions.configuration/1.1.2/microsoft.extensions.configuration.1.1.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.extensions.configuration.abstractions/1.1.2/microsoft.extensions.configuration.abstractions.1.1.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.extensions.primitives/1.1.1/microsoft.extensions.primitives.1.1.1.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.netcore.platforms/1.1.1/microsoft.netcore.platforms.1.1.1.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.netcore.targets/1.1.3/microsoft.netcore.targets.1.1.3.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.openapi/1.4.3/microsoft.openapi.1.4.3.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.win32.primitives/4.3.0/microsoft.win32.primitives.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/netstandard.library/1.6.1/netstandard.library.1.6.1.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.native.system/4.3.0/runtime.native.system.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.native.system.io.compression/4.3.0/runtime.native.system.io.compression.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.native.system.net.http/4.3.0/runtime.native.system.net.http.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.native.system.security.cryptography.apple/4.3.0/runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.native.system.security.cryptography.openssl/4.3.2/runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.0/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/sprache/2.3.1/sprache.2.3.1.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/swashbuckle.aspnetcore/6.5.0/swashbuckle.aspnetcore.6.5.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/swashbuckle.aspnetcore.swagger/6.5.0/swashbuckle.aspnetcore.swagger.6.5.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/swashbuckle.aspnetcore.swaggergen/6.5.0/swashbuckle.aspnetcore.swaggergen.6.5.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/swashbuckle.aspnetcore.swaggerui/6.5.0/swashbuckle.aspnetcore.swaggerui.6.5.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.appcontext/4.3.0/system.appcontext.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.buffers/4.3.0/system.buffers.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.codedom/7.0.0/system.codedom.7.0.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.collections/4.3.0/system.collections.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.collections.concurrent/4.3.0/system.collections.concurrent.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.console/4.3.0/system.console.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.diagnostics.debug/4.3.0/system.diagnostics.debug.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.diagnostics.diagnosticsource/4.3.0/system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.diagnostics.tools/4.3.0/system.diagnostics.tools.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.diagnostics.tracing/4.3.0/system.diagnostics.tracing.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.globalization/4.3.0/system.globalization.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.globalization.calendars/4.3.0/system.globalization.calendars.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.globalization.extensions/4.3.0/system.globalization.extensions.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.io/4.3.0/system.io.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.io.compression/4.3.0/system.io.compression.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.io.compression.zipfile/4.3.0/system.io.compression.zipfile.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.io.filesystem/4.3.0/system.io.filesystem.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.io.filesystem.primitives/4.3.0/system.io.filesystem.primitives.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.linq/4.3.0/system.linq.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.linq.expressions/4.3.0/system.linq.expressions.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.management/7.0.2/system.management.7.0.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.net.http/4.3.4/system.net.http.4.3.4.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.net.primitives/4.3.0/system.net.primitives.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.net.sockets/4.3.0/system.net.sockets.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.objectmodel/4.3.0/system.objectmodel.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.private.uri/4.3.2/system.private.uri.4.3.2.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.reflection/4.3.0/system.reflection.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.reflection.emit/4.3.0/system.reflection.emit.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.reflection.emit.ilgeneration/4.3.0/system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.reflection.emit.lightweight/4.3.0/system.reflection.emit.lightweight.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.reflection.extensions/4.3.0/system.reflection.extensions.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.reflection.primitives/4.3.0/system.reflection.primitives.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.reflection.typeextensions/4.3.0/system.reflection.typeextensions.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.resources.resourcemanager/4.3.0/system.resources.resourcemanager.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.runtime/4.3.1/system.runtime.4.3.1.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.runtime.compilerservices.unsafe/4.3.0/system.runtime.compilerservices.unsafe.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.runtime.extensions/4.3.0/system.runtime.extensions.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.runtime.handles/4.3.0/system.runtime.handles.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.runtime.interopservices/4.3.0/system.runtime.interopservices.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.runtime.interopservices.runtimeinformation/4.3.0/system.runtime.interopservices.runtimeinformation.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.runtime.numerics/4.3.0/system.runtime.numerics.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.algorithms/4.3.0/system.security.cryptography.algorithms.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.cng/4.3.0/system.security.cryptography.cng.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.csp/4.3.0/system.security.cryptography.csp.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.encoding/4.3.0/system.security.cryptography.encoding.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.openssl/4.3.0/system.security.cryptography.openssl.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.primitives/4.3.0/system.security.cryptography.primitives.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.x509certificates/4.3.0/system.security.cryptography.x509certificates.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.text.encoding/4.3.0/system.text.encoding.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.text.encoding.extensions/4.3.0/system.text.encoding.extensions.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.text.regularexpressions/4.3.1/system.text.regularexpressions.4.3.1.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.threading/4.3.0/system.threading.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.threading.tasks/4.3.0/system.threading.tasks.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.threading.tasks.extensions/4.3.0/system.threading.tasks.extensions.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.threading.timer/4.3.0/system.threading.timer.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.xml.readerwriter/4.3.0/system.xml.readerwriter.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/system.xml.xdocument/4.3.0/system.xml.xdocument.4.3.0.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.aspnetcore.app.ref/8.0.22/microsoft.aspnetcore.app.ref.8.0.22.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.netcore.app.host.osx-arm64/8.0.22/microsoft.netcore.app.host.osx-arm64.8.0.22.nupkg.sha512",
"/Users/dayronvanleemput/.nuget/packages/microsoft.netcore.app.ref/8.0.22/microsoft.netcore.app.ref.8.0.22.nupkg.sha512"
],
"logs": []
}
-16
View File
@@ -1,16 +0,0 @@
{
"dateRanges": [
{
"startDate": "2023-01-01",
"endDate": "today"
}
],
"metrics": [
{
"name": "totalUsers"
},
{
"name": "eventCount"
}
]
}
-8
View File
@@ -340,14 +340,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@@ -376,14 +372,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
+256 -29
View File
@@ -7,12 +7,15 @@ import '../../blocs/activity/activity_state.dart';
import '../../models/trip.dart';
import '../../models/activity.dart';
import '../../services/activity_cache_service.dart';
import '../../services/activity_places_service.dart';
import 'dart:async';
import '../loading/laoding_content.dart';
import '../../blocs/user/user_bloc.dart';
import '../../blocs/user/user_state.dart';
import '../../services/error_service.dart';
import 'activity_detail_dialog.dart';
import 'package:geolocator/geolocator.dart';
class ActivitiesPage extends StatefulWidget {
final Trip trip;
@@ -38,6 +41,14 @@ class _ActivitiesPageState extends State<ActivitiesPage>
List<Activity> _approvedActivities = [];
bool _isLoadingTripActivities = false;
// Autocomplete variables
List<Map<String, String>> _suggestions = [];
final LayerLink _layerLink = LayerLink();
OverlayEntry? _overlayEntry;
final FocusNode _searchFocusNode = FocusNode();
Timer? _debounceTimer;
bool _isLoadingSuggestions = false;
bool _autoReloadInProgress =
false; // Protection contre les rechargements en boucle
int _lastAutoReloadTriggerCount =
@@ -104,6 +115,9 @@ class _ActivitiesPageState extends State<ActivitiesPage>
void dispose() {
_tabController.dispose();
_searchController.dispose();
_searchFocusNode.dispose();
_debounceTimer?.cancel();
_hideSuggestions();
super.dispose();
}
@@ -277,42 +291,204 @@ class _ActivitiesPageState extends State<ActivitiesPage>
}
Widget _buildSearchBar(ThemeData theme) {
return Container(
padding: const EdgeInsets.all(16),
return CompositedTransformTarget(
link: _layerLink,
child: Container(
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHighest.withValues(
alpha: 0.3,
),
borderRadius: BorderRadius.circular(12),
),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Rechercher restaurants, musées...',
hintStyle: TextStyle(
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
padding: const EdgeInsets.all(16),
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHighest.withValues(
alpha: 0.3,
),
borderRadius: BorderRadius.circular(12),
),
child: TextField(
controller: _searchController,
focusNode: _searchFocusNode,
decoration: InputDecoration(
hintText: 'Rechercher restaurants, musées...',
hintStyle: TextStyle(
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
),
prefixIcon: Icon(
Icons.search,
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
suffixIcon: _isLoadingSuggestions
? const SizedBox(
width: 20,
height: 20,
child: Padding(
padding: EdgeInsets.all(10.0),
child: CircularProgressIndicator(strokeWidth: 2),
),
)
: null,
),
onChanged: _onSearchChanged,
onSubmitted: (value) {
_hideSuggestions();
if (value.isNotEmpty) {
_performSearch(value);
}
},
),
),
prefixIcon: Icon(
Icons.search,
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
onSubmitted: (value) {
if (value.isNotEmpty) {
_performSearch(value);
}
},
],
),
),
);
}
void _onSearchChanged(String value) {
if (_debounceTimer?.isActive ?? false) _debounceTimer!.cancel();
if (value.isEmpty) {
_hideSuggestions();
return;
}
_debounceTimer = Timer(const Duration(milliseconds: 300), () async {
setState(() {
_isLoadingSuggestions = true;
});
try {
final suggestions = await ActivityPlacesService().fetchSuggestions(
query: value,
lat: widget.trip.latitude,
lng: widget.trip.longitude,
);
if (mounted) {
setState(() {
_suggestions = suggestions;
_isLoadingSuggestions = false;
});
if (_suggestions.isNotEmpty) {
_showSuggestions();
} else {
_hideSuggestions();
}
}
} catch (e) {
if (mounted) {
setState(() {
_isLoadingSuggestions = false;
});
}
}
});
}
void _showSuggestions() {
_overlayEntry?.remove();
_overlayEntry = _createOverlayEntry();
Overlay.of(context).insert(_overlayEntry!);
}
void _hideSuggestions() {
_overlayEntry?.remove();
_overlayEntry = null;
}
OverlayEntry _createOverlayEntry() {
final renderBox = context.findRenderObject() as RenderBox;
final theme = Theme.of(context);
final size = renderBox.size;
final width = size.width - 32; // padding 16 * 2
return OverlayEntry(
builder: (context) => Positioned(
width: width,
child: CompositedTransformFollower(
link: _layerLink,
showWhenUnlinked: false,
offset: const Offset(16, 80), // Adjust vertical offset
child: Material(
elevation: 4,
borderRadius: BorderRadius.circular(12),
color: theme.colorScheme.surface,
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 250),
child: ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: _suggestions.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) {
final suggestion = _suggestions[index];
return ListTile(
leading: const Icon(Icons.location_on_outlined),
title: Text(
suggestion['description'] ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
onTap: () {
_selectSuggestion(suggestion);
},
);
},
),
),
),
),
),
);
}
Future<void> _selectSuggestion(Map<String, String> suggestion) async {
_hideSuggestions();
_searchController.text = suggestion['description']!;
_searchFocusNode.unfocus();
// Passer à l'onglet "Suggestion" si ce n'est pas déjà fait
if (_tabController.index != 2) {
_tabController.animateTo(2);
}
// Charger l'activité spécifique via le Bloc ou Service
// Ici on va utiliser le Bloc pour ajouter l'activité aux résultats de recherche
// Pour ça, il faudrait idéalement un événement "LoadSingleActivity" dans le Bloc
// Mais pour faire simple et rapide, on peut faire une recherche "exacte" ou hack:
// On charge l'activité manuellement et on l'ajoute comme si c'était un résultat de recherche.
setState(() {
_isLoadingSuggestions = true;
});
try {
final activity = await ActivityPlacesService().getActivityByPlaceId(
placeId: suggestion['placeId']!,
tripId: widget.trip.id!,
);
if (mounted && activity != null) {
// Injecter ce résultat unique dans le Bloc
context.read<ActivityBloc>().add(
RestoreCachedSearchResults(searchResults: [activity]),
);
}
} catch (e) {
ErrorService().showError(message: "Impossible de charger l'activité");
} finally {
if (mounted) {
setState(() {
_isLoadingSuggestions = false;
});
}
}
}
Widget _buildCategoryTabs(ThemeData theme) {
return Container(
padding: const EdgeInsets.all(16),
@@ -637,6 +813,23 @@ class _ActivitiesPageState extends State<ActivitiesPage>
activity.name.toLowerCase().trim(),
);
// Calculer la distance si c'est une suggestion Google et que le voyage a des coordonnées
double? distanceInKm;
if (isGoogleSuggestion &&
widget.trip.hasCoordinates &&
activity.latitude != null &&
activity.longitude != null) {
final distanceInMeters = Geolocator.distanceBetween(
widget.trip.latitude!,
widget.trip.longitude!,
activity.latitude!,
activity.longitude!,
);
distanceInKm = distanceInMeters / 1000;
}
final isFar = distanceInKm != null && distanceInKm > 50;
return GestureDetector(
onTap: () {
showDialog(
@@ -709,6 +902,40 @@ class _ActivitiesPageState extends State<ActivitiesPage>
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isFar)
Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: theme.colorScheme.errorContainer,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: theme.colorScheme.error.withValues(alpha: 0.5),
),
),
child: Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: theme.colorScheme.error,
size: 20,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'Activité éloignée : ${distanceInKm!.toStringAsFixed(0)} km du lieu du voyage',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onErrorContainer,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
Row(
children: [
// Icône de catégorie
+18 -15
View File
@@ -24,6 +24,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../services/place_image_service.dart';
import '../../services/trip_geocoding_service.dart';
import '../../services/logger_service.dart';
import '../../firebase_options.dart';
/// Create trip content widget for trip creation and editing functionality.
///
@@ -86,16 +87,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
/// Google Maps API key for location services
static String get _apiKey {
if (Platform.isAndroid) {
return dotenv.env['GOOGLE_MAPS_API_KEY_ANDROID'] ??
dotenv.env['GOOGLE_MAPS_API_KEY'] ??
'';
} else if (Platform.isIOS) {
return dotenv.env['GOOGLE_MAPS_API_KEY_IOS'] ??
dotenv.env['GOOGLE_MAPS_API_KEY'] ??
'';
}
return dotenv.env['GOOGLE_MAPS_API_KEY'] ?? '';
return DefaultFirebaseOptions.currentPlatform.apiKey;
}
/// Participant management
@@ -135,7 +127,11 @@ class _CreateTripContentState extends State<CreateTripContent> {
}
}
bool _isProgrammaticUpdate = false;
void _onLocationChanged() {
if (_isProgrammaticUpdate) return;
final query = _locationController.text.trim();
if (query.length < 2) {
@@ -215,15 +211,18 @@ class _CreateTripContentState extends State<CreateTripContent> {
if (_placeSuggestions.isEmpty) return;
final overlay = Overlay.of(context);
// Calculer la largeur correcte en fonction du padding parent (16 margin + 24 padding = 40 de chaque côté)
final width = MediaQuery.of(context).size.width - 80;
_suggestionsOverlay = OverlayEntry(
builder: (context) => Positioned(
width:
MediaQuery.of(context).size.width -
32, // Largeur du champ avec padding
width: width,
child: CompositedTransformFollower(
link: _layerLink,
showWhenUnlinked: false,
offset: const Offset(0, 60), // Position sous le champ
targetAnchor: Alignment.bottomLeft,
child: Material(
elevation: 4,
borderRadius: BorderRadius.circular(8),
@@ -236,6 +235,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
),
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: _placeSuggestions.length,
itemBuilder: (context, index) {
final suggestion = _placeSuggestions[index];
@@ -256,7 +256,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
),
);
Overlay.of(context).insert(_suggestionsOverlay!);
overlay.insert(_suggestionsOverlay!);
}
void _hideSuggestions() {
@@ -265,7 +265,10 @@ class _CreateTripContentState extends State<CreateTripContent> {
}
void _selectSuggestion(PlaceSuggestion suggestion) {
_isProgrammaticUpdate = true;
_locationController.text = suggestion.description;
_isProgrammaticUpdate = false;
_hideSuggestions();
setState(() {
_placeSuggestions = [];
+198 -28
View File
@@ -6,6 +6,7 @@ import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:ui' as ui;
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../firebase_options.dart';
import '../../services/error_service.dart';
import '../../services/map_navigation_service.dart';
import '../../services/logger_service.dart';
@@ -22,6 +23,7 @@ class MapContent extends StatefulWidget {
class _MapContentState extends State<MapContent> {
GoogleMapController? _mapController;
LatLng _initialPosition = const LatLng(48.8566, 2.3522);
LatLng? _currentMapCenter;
final TextEditingController _searchController = TextEditingController();
bool _isLoadingLocation = false;
bool _isSearching = false;
@@ -33,16 +35,7 @@ class _MapContentState extends State<MapContent> {
List<PlaceSuggestion> _suggestions = [];
static String get _apiKey {
if (Platform.isAndroid) {
return dotenv.env['GOOGLE_MAPS_API_KEY_ANDROID'] ??
dotenv.env['GOOGLE_MAPS_API_KEY'] ??
'';
} else if (Platform.isIOS) {
return dotenv.env['GOOGLE_MAPS_API_KEY_IOS'] ??
dotenv.env['GOOGLE_MAPS_API_KEY'] ??
'';
}
return dotenv.env['GOOGLE_MAPS_API_KEY'] ?? '';
return DefaultFirebaseOptions.currentPlatform.apiKey;
}
@override
@@ -321,20 +314,30 @@ class _MapContentState extends State<MapContent> {
}
}
// Créer une icône personnalisée à partir de l'icône Material
Future<BitmapDescriptor> _createCustomMarkerIcon() async {
// Créer une icône personnalisée
Future<BitmapDescriptor> _createMarkerIcon(
IconData iconData,
Color color, {
double size = 60.0,
}) async {
final pictureRecorder = ui.PictureRecorder();
final canvas = Canvas(pictureRecorder);
const size = 80.0;
// Dessiner l'icône person_pin_circle en bleu
final iconPainter = TextPainter(textDirection: TextDirection.ltr);
iconPainter.text = TextSpan(
text: String.fromCharCode(Icons.person_pin_circle.codePoint),
text: String.fromCharCode(iconData.codePoint),
style: TextStyle(
fontSize: 70,
fontFamily: Icons.person_pin_circle.fontFamily,
color: Colors.blue[700],
fontSize: size,
fontFamily: iconData.fontFamily,
package: iconData.fontPackage,
color: color,
shadows: [
Shadow(
offset: const Offset(1, 1),
blurRadius: 2,
color: Colors.black.withValues(alpha: 0.3),
),
],
),
);
iconPainter.layout();
@@ -364,8 +367,12 @@ class _MapContentState extends State<MapContent> {
),
);
// Créer l'icône personnalisée
final icon = await _createCustomMarkerIcon();
// Créer l'icône personnalisée (plus petite: 60 au lieu de 80)
final icon = await _createMarkerIcon(
Icons.person_pin_circle,
Colors.blue[700]!,
size: 60.0,
);
// Ajouter le marqueur avec l'icône
setState(() {
@@ -374,10 +381,7 @@ class _MapContentState extends State<MapContent> {
markerId: const MarkerId('user_location'),
position: position,
icon: icon,
anchor: const Offset(
0.5,
0.85,
), // Ancrer au bas de l'icône (le point du pin)
anchor: const Offset(0.5, 0.85), // Ancrer au bas de l'icône
infoWindow: InfoWindow(
title: 'Ma position',
snippet:
@@ -401,10 +405,21 @@ class _MapContentState extends State<MapContent> {
});
try {
final apiKey = _apiKey;
LoggerService.info('MapContent: Searching places with query "$query"');
if (apiKey.isEmpty) {
LoggerService.error('MapContent: API Key is empty!');
} else {
// Log first few chars to verify correct key is loaded without leaking full key
LoggerService.info(
'MapContent: Using API Key starting with ${apiKey.substring(0, 5)}...',
);
}
final url = Uri.parse(
'https://maps.googleapis.com/maps/api/place/autocomplete/json'
'?input=${Uri.encodeComponent(query)}'
'&key=$_apiKey'
'&key=$apiKey'
'&language=fr',
);
@@ -412,11 +427,21 @@ class _MapContentState extends State<MapContent> {
if (!mounted) return;
LoggerService.info(
'MapContent: Response status code: ${response.statusCode}',
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
final status = data['status'];
if (data['status'] == 'OK') {
LoggerService.info('MapContent: API Status: $status');
if (status == 'OK') {
final predictions = data['predictions'] as List;
LoggerService.info(
'MapContent: Found ${predictions.length} predictions',
);
setState(() {
_suggestions = predictions
@@ -430,13 +455,19 @@ class _MapContentState extends State<MapContent> {
_isSearching = false;
});
} else {
LoggerService.error(
'MapContent: API Error: $status - ${data['error_message'] ?? "No error message"}',
);
setState(() {
_suggestions = [];
_isSearching = false;
});
}
} else {
LoggerService.error('MapContent: HTTP Error ${response.statusCode}');
}
} catch (e) {
LoggerService.error('MapContent: Exception during search: $e');
_showError('Erreur lors de la recherche de lieux: $e');
setState(() {
_isSearching = false;
@@ -444,6 +475,128 @@ class _MapContentState extends State<MapContent> {
}
}
Future<void> _performTextSearch(String query) async {
if (query.isEmpty) return;
setState(() {
_isSearching = true;
_suggestions = []; // Hide suggestions
});
try {
final apiKey = _apiKey;
// Utiliser le centre actuel de la carte ou la position initiale
final center = _currentMapCenter ?? _initialPosition;
final url = Uri.parse(
'https://maps.googleapis.com/maps/api/place/textsearch/json'
'?query=${Uri.encodeComponent(query)}'
'&location=${center.latitude},${center.longitude}'
'&radius=5000' // Rechercher dans un rayon de 5km autour du centre
'&key=$apiKey'
'&language=fr',
);
final response = await http.get(url);
if (!mounted) return;
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['status'] == 'OK') {
final results = data['results'] as List;
final List<Marker> newMarkers = [];
// Garder le marqueur de position utilisateur
final userMarker = _markers
.where((m) => m.markerId.value == 'user_location')
.toList();
double minLat = center.latitude;
double maxLat = center.latitude;
double minLng = center.longitude;
double maxLng = center.longitude;
// Créer le marqueur rouge standard personnalisé
final markerIcon = await _createMarkerIcon(
Icons.location_on,
Colors.red,
size: 50.0,
);
for (final place in results) {
final geometry = place['geometry']['location'];
final lat = geometry['lat'];
final lng = geometry['lng'];
final name = place['name'];
final placeId = place['place_id'];
final position = LatLng(lat, lng);
// Mettre à jour les bornes
if (lat < minLat) minLat = lat;
if (lat > maxLat) maxLat = lat;
if (lng < minLng) minLng = lng;
if (lng > maxLng) maxLng = lng;
newMarkers.add(
Marker(
markerId: MarkerId(placeId),
position: position,
icon: markerIcon,
anchor: const Offset(0.5, 0.85), // Standard anchor for pins
infoWindow: InfoWindow(
title: name,
snippet: place['formatted_address'] ?? 'Lieu trouvé',
),
),
);
}
setState(() {
_markers.clear();
if (userMarker.isNotEmpty) {
_markers.add(userMarker.first);
}
_markers.addAll(newMarkers);
_isSearching = false;
});
// Ajuster la caméra pour montrer tous les résultats
if (newMarkers.isNotEmpty) {
_mapController?.animateCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
),
50.0, // padding
),
);
}
FocusScope.of(context).unfocus();
} else {
_showError('Aucun résultat trouvé pour "$query"');
setState(() {
_isSearching = false;
});
}
} else {
_showError('Erreur lors de la recherche: ${response.statusCode}');
setState(() {
_isSearching = false;
});
}
} catch (e) {
_showError('Erreur lors de la recherche: $e');
setState(() {
_isSearching = false;
});
}
}
Future<void> _selectPlace(PlaceSuggestion suggestion) async {
setState(() {
_isSearching = true;
@@ -467,13 +620,21 @@ class _MapContentState extends State<MapContent> {
final data = json.decode(response.body);
if (data['status'] == 'OK') {
final location = data['result']['geometry']['location'];
final result = data['result'];
final location = result['geometry']['location'];
final lat = location['lat'];
final lng = location['lng'];
final name = data['result']['name'];
final name = result['name'];
final newPosition = LatLng(lat, lng);
// Utiliser le marqueur rouge standard personnalisé
final markerIcon = await _createMarkerIcon(
Icons.location_on,
Colors.red,
size: 50.0,
);
// Ajouter un marqueur pour le lieu recherché (ne pas supprimer le marqueur de position)
setState(() {
// Garder le marqueur de position utilisateur
@@ -484,6 +645,8 @@ class _MapContentState extends State<MapContent> {
Marker(
markerId: MarkerId(suggestion.placeId),
position: newPosition,
icon: markerIcon,
anchor: const Offset(0.5, 0.85),
infoWindow: InfoWindow(title: name),
),
);
@@ -533,6 +696,9 @@ class _MapContentState extends State<MapContent> {
onMapCreated: (GoogleMapController controller) {
_mapController = controller;
},
onCameraMove: (CameraPosition position) {
_currentMapCenter = position.target;
},
markers: _markers,
circles: _circles,
myLocationEnabled: false,
@@ -664,6 +830,10 @@ class _MapContentState extends State<MapContent> {
vertical: 14,
),
),
textInputAction: TextInputAction.search,
onSubmitted: (value) {
_performTextSearch(value);
},
onChanged: (value) {
// Ne pas rechercher si c'est juste le remplissage initial
if (widget.initialSearchQuery != null &&
+140
View File
@@ -0,0 +1,140 @@
import 'package:flutter/material.dart';
import '../../services/whats_new_service.dart';
class WhatsNewDialog extends StatelessWidget {
final VoidCallback onDismiss;
final List<WhatsNewItem> features;
const WhatsNewDialog({
super.key,
required this.onDismiss,
required this.features,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: theme.colorScheme.surface,
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer,
shape: BoxShape.circle,
),
child: Icon(
Icons.auto_awesome,
color: theme.colorScheme.primary,
size: 24,
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
'Quoi de neuf ?',
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 24),
// Features List
Flexible(
child: ListView.separated(
shrinkWrap: true,
itemCount: features.length,
separatorBuilder: (_, __) => const SizedBox(height: 16),
itemBuilder: (context, index) {
final feature = features[index];
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
feature.icon,
color: theme.colorScheme.primary,
size: 20,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
feature.title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
feature.description,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
],
);
},
),
),
const SizedBox(height: 32),
// Button
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () {
// Marquer comme vu via le service
WhatsNewService().markCurrentVersionAsSeen();
onDismiss();
},
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
foregroundColor: theme.colorScheme.onPrimary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text(
'C\'est parti !',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
),
],
),
),
);
}
}
class WhatsNewItem {
final IconData icon;
final String title;
final String description;
const WhatsNewItem({
required this.icon,
required this.title,
required this.description,
});
}
-23
View File
@@ -27,18 +27,6 @@ class DefaultFirebaseOptions {
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for macos - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.windows:
return windows;
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
@@ -63,15 +51,4 @@ class DefaultFirebaseOptions {
iosClientId: '521527250907-3i1qe2656eojs8k9hjdi573j09i9p41m.apps.googleusercontent.com',
iosBundleId: 'com.example.travelMate',
);
static const FirebaseOptions windows = FirebaseOptions(
apiKey: 'AIzaSyC4t-WOvp22zns9b9t58urznsNAhSHRAag',
appId: '1:521527250907:web:53ff98bcdb8c218f7da1fe',
messagingSenderId: '521527250907',
projectId: 'travelmate-a47f5',
authDomain: 'travelmate-a47f5.firebaseapp.com',
storageBucket: 'travelmate-a47f5.firebasestorage.app',
measurementId: 'G-J246Y7J61M',
);
}
+41
View File
@@ -14,6 +14,8 @@ import '../blocs/auth/auth_event.dart';
import '../services/error_service.dart';
import '../services/notification_service.dart';
import '../services/map_navigation_service.dart';
import '../services/whats_new_service.dart';
import '../components/whats_new_dialog.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@@ -57,6 +59,45 @@ class _HomePageState extends State<HomePage> {
});
}
});
// Vérifier les nouveautés
WidgetsBinding.instance.addPostFrameCallback((_) async {
await _checkAndShowWhatsNew();
});
}
Future<void> _checkAndShowWhatsNew() async {
final service = WhatsNewService();
if (await service.shouldShowWhatsNew()) {
if (!mounted) return;
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => WhatsNewDialog(
onDismiss: () => Navigator.pop(context),
features: const [
WhatsNewItem(
icon: Icons.map_outlined,
title: 'Recherche globale',
description:
'Recherchez des restaurants, musées et plus encore directement depuis la carte.',
),
WhatsNewItem(
icon: Icons.search,
title: 'Autocomplétion améliorée',
description:
'Découvrez des suggestions intelligentes lors de la recherche de lieux et d\'activités.',
),
WhatsNewItem(
icon: Icons.warning_amber_rounded,
title: 'Alertes de distance',
description:
'Soyez averti si une activité est trop éloignée de votre lieu de séjour.',
),
],
),
);
}
}
Widget _buildPage(int index) {
+69 -1
View File
@@ -365,7 +365,7 @@ class ActivityPlacesService {
final encodedQuery = Uri.encodeComponent(query);
final url =
'https://maps.googleapis.com/maps/api/place/textsearch/json'
'?query=$encodedQuery in $destination'
'?query=$encodedQuery'
'&location=${coordinates['lat']},${coordinates['lng']}'
'&radius=$radius'
'&key=$_apiKey'
@@ -654,4 +654,72 @@ class ActivityPlacesService {
throw Exception('Erreur HTTP ${response.statusCode}');
}
}
/// Récupère des suggestions d'autocomplétion
Future<List<Map<String, String>>> fetchSuggestions({
required String query,
double? lat,
double? lng,
}) async {
if (query.isEmpty) return [];
try {
String url =
'https://maps.googleapis.com/maps/api/place/autocomplete/json'
'?input=${Uri.encodeComponent(query)}'
'&key=$_apiKey'
'&language=fr';
if (lat != null && lng != null) {
url += '&location=$lat,$lng&radius=50000'; // 50km bias
}
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['status'] == 'OK') {
return (data['predictions'] as List).map<Map<String, String>>((p) {
return {
'description': p['description'] as String,
'placeId': p['place_id'] as String,
};
}).toList();
}
}
return [];
} catch (e) {
LoggerService.error('ActivityPlacesService: Erreur autocomplete: $e');
return [];
}
}
/// Récupère une activité via son Place ID
Future<Activity?> getActivityByPlaceId({
required String placeId,
required String tripId,
}) async {
try {
final details = await _getPlaceDetails(placeId);
if (details == null) return null;
// Créer une map simulant la structure "place" attendue par _convertPlaceToActivity
// Note: _getPlaceDetails retourne "result", qui est déjà ce qu'on veut,
// mais _convertPlaceToActivity attend le format "search result" qui a geometry au premier niveau.
// Heureusement _getPlaceDetails retourne une structure compatible pour geometry/photos etc.
// On doit s'assurer d'avoir les types pour déterminer la catégorie
final types = List<String>.from(details['types'] ?? []);
final category = _determineCategoryFromTypes(types);
return await _convertPlaceToActivity(
details, // details a la structure nécessaire (geometry, name, etc)
tripId,
category,
);
} catch (e) {
LoggerService.error('ActivityPlacesService: Erreur get details: $e');
return null;
}
}
}
+68
View File
@@ -0,0 +1,68 @@
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../services/logger_service.dart';
class WhatsNewService {
static const String _lastVersionKey = 'last_known_version';
/// Vérifie si le popup "Nouveautés" doit être affiché.
///
/// Retourne true si:
/// - Ce n'est PAS une nouvelle installation
/// - ET la version actuelle est plus récente que la version stockée
Future<bool> shouldShowWhatsNew() async {
try {
final prefs = await SharedPreferences.getInstance();
final packageInfo = await PackageInfo.fromPlatform();
final currentVersion = packageInfo.version;
final lastVersion = prefs.getString(_lastVersionKey);
LoggerService.info(
'WhatsNewService: Current=$currentVersion, Last=$lastVersion',
);
// Cas 1: Première installation (lastVersion est null)
if (lastVersion == null) {
// On sauvegarde la version actuelle pour ne pas afficher le popup
// la prochaine fois, et on retourne false maintenant.
await prefs.setString(_lastVersionKey, currentVersion);
LoggerService.info(
'WhatsNewService: Fresh install detected. Marking version $currentVersion as read.',
);
return false;
}
// Cas 2: Mise à jour (lastVersion != currentVersion)
if (lastVersion != currentVersion) {
// C'est une mise à jour, on doit afficher le popup.
// On NE met PAS à jour la version ici, on attend que l'utilisateur ait vu le popup.
LoggerService.info(
'WhatsNewService: Update detected ($lastVersion -> $currentVersion). Showing popup.',
);
return true;
}
// Cas 3: Même version
return false;
} catch (e) {
LoggerService.error('WhatsNewService: Error checking version: $e');
return false;
}
}
/// Marque la version actuelle comme "Vue".
/// À appeler quand l'utilisateur ferme le popup.
Future<void> markCurrentVersionAsSeen() async {
try {
final prefs = await SharedPreferences.getInstance();
final packageInfo = await PackageInfo.fromPlatform();
await prefs.setString(_lastVersionKey, packageInfo.version);
LoggerService.info(
'WhatsNewService: Version ${packageInfo.version} marked as seen.',
);
} catch (e) {
LoggerService.error('WhatsNewService: Error marking seen: $e');
}
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2026.1.1+1
version: 2026.1.3+1
environment:
sdk: ^3.9.2