Compare commits

..

4 Commits

Author SHA1 Message Date
Jonas Bark
6ae2297246 version++ 2025-12-21 16:00:10 +01:00
Jonas Bark
c6fb2e68b5 win fix 2025-12-21 15:43:44 +01:00
jonas.bark@gmx.de
c84c685a8f attempt to fix remaining trial days calculation 2025-12-21 15:42:17 +01:00
Jonas Bark
102f4a8818 add info for Android users regarding existing purchase 2025-12-21 13:54:08 +01:00
11 changed files with 65 additions and 9 deletions

View File

@@ -20,6 +20,7 @@
"allowLocationForBluetooth": "Standortzugriff erlauben, damit Bluetooth-Scan funktioniert",
"allowPersistentNotification": "Benachrichtigungen zulassen",
"allowsRunningInBackground": "Ermöglicht es BikeControl, im Hintergrund weiterzulaufen.",
"alreadyBoughtTheApp": "App bereits gekauft? Tut mir leid, Du solltest eigentlich schon die Vollversion haben. \nSchreibe mir bitte eine E-Mail mit Deinem Android-Kaufbeleg, dann kümmere ich mich darum!",
"appIdActions": "{appId} Aktionen",
"@appIdActions": {
"placeholders": {

View File

@@ -20,6 +20,7 @@
"allowLocationForBluetooth": "Allow Location so Bluetooth scan works",
"allowPersistentNotification": "Allow Notifications",
"allowsRunningInBackground": "Allows BikeControl to keep running in background",
"alreadyBoughtTheApp": "Already bought the app? Sorry about that, you should have the full version already.\nPlease write a mail and attach your Android purchase receipt and I'll get it resolved!",
"appIdActions": "{appId} actions",
"@appIdActions": {
"placeholders": {

View File

@@ -20,6 +20,7 @@
"allowLocationForBluetooth": "Autoriser la localisation pour que la recherche Bluetooth fonctionne",
"allowPersistentNotification": "Autoriser les notifications",
"allowsRunningInBackground": "Permet à BikeControl de continuer à fonctionner en arrière-plan",
"alreadyBoughtTheApp": "Already bought the app? Sorry about that, you should have the full version already.\nPlease write a mail and attach your Android purchase receipt and I'll get it resolved!",
"appIdActions": "{appId} actions",
"@appIdActions": {
"placeholders": {

View File

@@ -20,6 +20,7 @@
"allowLocationForBluetooth": "Zezwól na dostęp do lokalizacji, aby umożliwić skanowanie Bluetooth",
"allowPersistentNotification": "Zezwól na powiadomienia",
"allowsRunningInBackground": "Umożliwia działanie BikeControl w tle",
"alreadyBoughtTheApp": "Już kupiłeś aplikację? Przepraszamy, powinieneś już mieć pełną wersję.\nNapisz maila i dołącz dowód zakupu Androida, a ja to rozwiążę!",
"appIdActions": "{appId} działania",
"@appIdActions": {
"placeholders": {

View File

@@ -215,6 +215,7 @@ class IAPService {
// while the app is still paid. Only users who downloaded the paid version will have
// a last_seen_version. After changing the app to free, new users won't have this set.
final lastSeenVersion = core.settings.getLastSeenVersion();
core.connection.signalNotification(LogNotification('Android last seen version: $lastSeenVersion'));
if (lastSeenVersion != null && lastSeenVersion.isNotEmpty) {
Version lastVersion = Version.parse(lastSeenVersion);
// If they had a previous version, they're an existing paid user

View File

@@ -60,8 +60,18 @@ class WindowsIAPService {
}
final trial = await _windowsIapPlugin.getTrialStatusAndRemainingDays();
core.connection.signalNotification(LogNotification('Trial status: $trial'));
trialDaysRemaining = trial.remainingDays;
if (trial.isActive && !trial.isTrial && trial.remainingDays <= 0) {
final trialEndDate = trial.remainingDays;
if (trial.isTrial && trialEndDate.isNotEmpty && !trialEndDate.contains("?")) {
try {
trialDaysRemaining = DateTime.parse(trialEndDate).difference(DateTime.now()).inDays;
} catch (e) {
core.connection.signalNotification(LogNotification('Error parsing trial end date: $e'));
trialDaysRemaining = 0;
}
} else {
trialDaysRemaining = 0;
}
if (trial.isActive && !trial.isTrial && trialDaysRemaining <= 0) {
IAPManager.instance.isPurchased.value = true;
await _prefs.write(key: _purchaseStatusKey, value: "true");
} else {

View File

@@ -6,6 +6,7 @@ import 'package:bike_control/utils/iap/iap_manager.dart';
import 'package:bike_control/widgets/ui/small_progress_indicator.dart';
import 'package:bike_control/widgets/ui/toast.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
/// Widget to display IAP status and allow purchases
class IAPStatusWidget extends StatefulWidget {
@@ -185,6 +186,29 @@ class _IAPStatusWidgetState extends State<IAPStatusWidget> {
padding: const EdgeInsets.only(left: 42.0, top: 8.0),
child: Text(AppLocalizations.of(context).fullVersionDescription).xSmall,
),
if (Platform.isAndroid)
Padding(
padding: const EdgeInsets.only(left: 42.0, top: 8.0),
child: Column(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(),
Text(
AppLocalizations.of(context).alreadyBoughtTheApp,
).small,
OutlineButton(
child: Text(context.i18n.getSupport),
onPressed: () {
String email = Uri.encodeComponent('jonas@bikecontrol.app');
Uri mail = Uri.parse("mailto:$email?subject=Unlock full version");
launchUrl(mail);
},
),
],
),
),
],
],
);

View File

@@ -1,7 +1,7 @@
name: bike_control
description: "BikeControl - Control your virtual riding"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 4.2.2+62
version: 4.2.2+63
environment:
sdk: ^3.9.0

View File

@@ -1,6 +1,6 @@
class Trial {
final bool isTrial;
final int remainingDays;
final String remainingDays;
final bool isActive;
final bool isTrialOwnedByThisUser;

View File

@@ -112,7 +112,8 @@ class MethodChannelWindowsIap extends WindowsIapPlatform {
isActive: result?['isActive'] as bool? ?? false,
isTrialOwnedByThisUser:
result?['isTrialOwnedByThisUser'] as bool? ?? false,
remainingDays: result?['remainingDays'] as int? ?? 0,
remainingDays: result?['remainingDays'] as String? ??
DateTime.now().add(Duration(days: 7)).toString(),
);
}

View File

@@ -15,6 +15,8 @@
#include <winrt/Windows.Foundation.Collections.h>
#include <shobjidl.h>
#include <chrono>
#include <iomanip>
#include <flutter/event_sink.h>
#include <flutter/event_channel.h>
#include <flutter/event_stream_handler.h>
@@ -267,7 +269,7 @@ namespace windows_iap
flutter::EncodableMap result;
result[flutter::EncodableValue("isTrial")] = flutter::EncodableValue(true);
result[flutter::EncodableValue("remainingDays")] = flutter::EncodableValue(0);
result[flutter::EncodableValue("remainingDays")] = flutter::EncodableValue("");
result[flutter::EncodableValue("isActive")] = flutter::EncodableValue(license.IsActive());
result[flutter::EncodableValue("isTrialOwnedByThisUser")] = flutter::EncodableValue(license.IsTrialOwnedByThisUser());
@@ -282,10 +284,24 @@ namespace windows_iap
{
result[flutter::EncodableValue("isTrial")] = flutter::EncodableValue(true);
winrt::Windows::Foundation::TimeSpan expiration = license.TrialTimeRemaining();
const auto inDays = std::chrono::duration_cast<std::chrono::hours>(expiration).count() / 24.0;
auto expirationDate = license.ExpirationDate();
// dt is your winrt::Windows::Foundation::DateTime
std::time_t t = winrt::clock::to_time_t(expirationDate); // Convert to time_t (UTC seconds since 1970)
std::tm tm_buf;
localtime_s(&tm_buf, &t); // Safe version
std::wstringstream wss;
wss << std::put_time(&tm_buf, L"%Y-%m-%d %H:%M:%S"); // Custom format
winrt::hstring readable = winrt::hstring{ wss.str() };
std::string utf8 = winrt::to_string(readable); // Converts hstring to UTF-8 std::string
result[flutter::EncodableValue("remainingDays")] = flutter::EncodableValue(utf8);
}
else {
result[flutter::EncodableValue("isTrial")] = flutter::EncodableValue(false);
result[flutter::EncodableValue("remainingDays")] = flutter::EncodableValue(inDays);
}
resultCallback->Success(flutter::EncodableValue(result));