From 87a51fa48902c20906ec443463a377c13df1133b Mon Sep 17 00:00:00 2001 From: iWisp360 Date: Wed, 8 Apr 2026 20:57:37 -0400 Subject: [PATCH 01/11] fix flake with wrong dependencies and format --- flake.nix | 125 ++++++++++++++++++++++++++---------------------------- 1 file changed, 61 insertions(+), 64 deletions(-) diff --git a/flake.nix b/flake.nix index 1d309e82..379db525 100644 --- a/flake.nix +++ b/flake.nix @@ -1,78 +1,75 @@ { - description = "Native Telegram client for Android based on TDLib."; + description = "Native Telegram client for Android based on TDLib."; - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - flake-parts.url = "github:hercules-ci/flake-parts"; - systems.url = "github:nix-systems/default"; - }; - - outputs = - inputs: - inputs.flake-parts.lib.mkFlake { inherit inputs; } { - systems = import inputs.systems; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + systems.url = "github:nix-systems/default"; + }; - perSystem = - { pkgs, system, ... }: - let - androidComposition = - pkgs: - (pkgs.androidenv.composeAndroidPackages { - platformVersions = [ - "35" - "36" - ]; + outputs = + inputs: + inputs.flake-parts.lib.mkFlake { inherit inputs; } { + systems = import inputs.systems; - buildToolsVersions = [ - "35.0.0" - ]; + perSystem = + { pkgs, system, ... }: + let + androidComposition = + pkgs: + (pkgs.androidenv.composeAndroidPackages { + platformVersions = [ + "35" + ]; - cmakeVersions = [ - "3.22.1" - ]; + buildToolsVersions = [ + "35.0.0" + ]; - ndkVersions = [ - "27.0.12077973" - ]; + cmakeVersions = [ + "3.22.1" + ]; - abiVersions = [ - "armeabi-v7a" - "arm64-v8a" - "x86_64" - ]; + ndkVersions = [ + "27.0.12077973" + "28.2.13676358" + ]; - systemImageTypes = [ "google_apis_playstore" ]; - includeNDK = true; - includeCmake = true; - includeExtras = [ "extras;google;auto" ]; - }).androidsdk; - in - { - _module.args.pkgs = import inputs.nixpkgs { - inherit system; - config = { - android_sdk.accept_license = true; - allowUnfree = true; - }; - }; + abiVersions = [ + "armeabi-v7a" + "arm64-v8a" + "x86_64" + ]; - devShells.default = - (pkgs.buildFHSEnv { - name = "monogram-sdk-env"; + systemImageTypes = [ "google_apis_playstore" ]; + includeNDK = true; + includeCmake = true; + includeSources = true; + includeExtras = [ "extras;google;auto" ]; + }).androidsdk; + in + { + _module.args.pkgs = import inputs.nixpkgs { + inherit system; + config = { + android_sdk.accept_license = true; + allowUnfree = true; + }; + }; - targetPkgs = pkgs: [ - (androidComposition pkgs) - pkgs.glibc - pkgs.jdk21 - ]; + devShells.default = pkgs.mkShell { + name = "monogram-sdk"; - profile = '' - export JAVA_HOME="/usr/lib/openjdk" - export ANDROID_HOME="/usr/libexec/android-sdk" - ''; + nativeBuildInputs = [ + (androidComposition pkgs) + pkgs.glibc + pkgs.jdk21 + pkgs.android-studio + ]; - runScript = "bash"; - }).env; - }; + JAVA_HOME = "${pkgs.jdk21}"; + ANDROID_HOME = "${androidComposition pkgs}/libexec/android-sdk"; + }; }; + }; } From 3db8e24900d34b4ea753fb02e80042128a8b137a Mon Sep 17 00:00:00 2001 From: iWisp360 Date: Thu, 9 Apr 2026 06:45:44 -0400 Subject: [PATCH 02/11] use 12h or 24h format depending on locale settings --- .../java/org/monogram/app/di/AppModule.kt | 2 ++ .../presentation/core/util/DateUtils.kt | 20 ++++++++++++++++++- .../presentation/core/util/StringUtil.kt | 16 ++++++++------- .../chatList/components/MessageSearchItem.kt | 7 ++++++- .../chatContent/RestrictUserSheet.kt | 7 ++++++- .../channels/ChannelAlbumMessageBubble.kt | 7 ++++++- .../channels/ChannelGifMessageBubble.kt | 9 +++++++-- .../channels/ChannelMessageUtils.kt | 4 ++-- .../channels/ChannelTextMessageBubble.kt | 8 ++++++-- .../chats/ChatAlbumMessageBubble.kt | 7 ++++++- .../components/chats/GifMessageBubble.kt | 9 +++++++-- .../components/chats/MessageUtils.kt | 10 +++++++--- .../components/chats/PollMessageBubble.kt | 7 ++++++- .../components/chats/StickerMessageBubble.kt | 7 ++++++- .../components/chats/TextMessageBubble.kt | 8 ++++++-- .../components/chats/VideoNoteBubble.kt | 7 ++++++- .../components/inputbar/MentionSuggestions.kt | 6 +++++- .../components/inputbar/SchedulePickers.kt | 5 +++-- .../inputbar/ScheduledMessagesSheet.kt | 10 ++++++++-- .../features/chats/newChat/NewChatContent.kt | 6 +++++- .../features/profile/ProfileContent.kt | 6 +++++- .../profile/components/ProfileMediaSection.kt | 8 +++++++- .../profile/components/StatisticsViewer.kt | 16 +++++++++------ .../profile/logs/components/ActionDetails.kt | 6 +++++- .../profile/logs/components/LogBubble.kt | 8 ++++++-- .../stickers/ui/menu/MessageOptionsMenu.kt | 11 ++++++++-- .../features/viewers/YouTubeViewer.kt | 6 +++++- .../components/VideoViewerComponents.kt | 6 +++++- .../viewers/components/ViewerComponents.kt | 6 ++++-- .../src/main/res/values-es/string.xml | 1 - .../src/main/res/values-hy/string.xml | 1 - .../src/main/res/values-pt-rBR/string.xml | 1 - .../src/main/res/values-ru-rRU/string.xml | 1 - .../src/main/res/values-sk/string.xml | 1 - .../src/main/res/values-uk/string.xml | 1 - .../src/main/res/values-zh-rCN/string.xml | 1 - presentation/src/main/res/values/string.xml | 1 - 37 files changed, 184 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/org/monogram/app/di/AppModule.kt b/app/src/main/java/org/monogram/app/di/AppModule.kt index e3d35c20..7c4b0846 100644 --- a/app/src/main/java/org/monogram/app/di/AppModule.kt +++ b/app/src/main/java/org/monogram/app/di/AppModule.kt @@ -41,6 +41,8 @@ val appModule = module { } single { LoggerImpl() } + single { DateFormatManagerImpl(androidContext()) } + factory { PhoneManagerImpl( androidContext().getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager, diff --git a/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt b/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt index 72056d4d..fc775a95 100644 --- a/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt +++ b/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt @@ -1,5 +1,9 @@ package org.monogram.presentation.core.util +import android.content.Context +import android.text.format.DateFormat +import androidx.compose.runtime.Composable +import org.koin.compose.koinInject import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date @@ -13,6 +17,7 @@ import kotlin.math.roundToLong * @param locale a [Locale] object which used with this date * @param now optional current date (for custom formatting or testing) **/ +@Composable fun Date.toShortRelativeDate( locale: Locale = Locale.getDefault(), now: Date = Date() @@ -20,6 +25,9 @@ fun Date.toShortRelativeDate( val currentCalendar = Calendar.getInstance(locale).apply { time = now } val targetCalendar = Calendar.getInstance(locale).apply { time = this@toShortRelativeDate } + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + val currentDayStart = currentCalendar.clone() as Calendar currentDayStart.apply { set(Calendar.HOUR_OF_DAY, 0) @@ -42,7 +50,7 @@ fun Date.toShortRelativeDate( return when (diffDays) { 0L -> { - SimpleDateFormat("HH:mm", locale).format(this) + SimpleDateFormat(timeFormat, locale).format(this) } in 1..6 -> { SimpleDateFormat("EEE", locale).format(this) @@ -59,3 +67,13 @@ fun Date.toShortRelativeDate( } } } + +interface DateFormatManager { + fun is24HourFormat(): Boolean + fun getHourMinuteFormat(): String +} + +class DateFormatManagerImpl(private val context: Context) : DateFormatManager { + override fun is24HourFormat(): Boolean = DateFormat.is24HourFormat(context) + override fun getHourMinuteFormat(): String = if (this.is24HourFormat()) "HH:mm" else "h:mm a" +} \ No newline at end of file diff --git a/presentation/src/main/java/org/monogram/presentation/core/util/StringUtil.kt b/presentation/src/main/java/org/monogram/presentation/core/util/StringUtil.kt index 8df82199..2188a5c1 100644 --- a/presentation/src/main/java/org/monogram/presentation/core/util/StringUtil.kt +++ b/presentation/src/main/java/org/monogram/presentation/core/util/StringUtil.kt @@ -14,17 +14,17 @@ import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withStyle +import org.koin.compose.koinInject import org.monogram.domain.models.* import org.monogram.presentation.R import java.text.SimpleDateFormat import java.util.* -fun formatLastSeen(lastSeen: Long?, context: Context): String { +fun formatLastSeen(lastSeen: Long?, context: Context, timeFormat: String): String { if (lastSeen == null || lastSeen <= 0L) return context.getString(R.string.last_seen_recently) val now = System.currentTimeMillis() val diff = now - lastSeen - if (diff < 0) return context.getString(R.string.last_seen_just_now) return when { @@ -35,12 +35,12 @@ fun formatLastSeen(lastSeen: Long?, context: Context): String { } DateUtils.isToday(lastSeen) -> { - val time = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date(lastSeen)) + val time = SimpleDateFormat(timeFormat, Locale.getDefault()).format(Date(lastSeen)) context.getString(R.string.last_seen_at, time) } isYesterday(lastSeen) -> { - val time = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date(lastSeen)) + val time = SimpleDateFormat(timeFormat, Locale.getDefault()).format(Date(lastSeen)) context.getString(R.string.last_seen_yesterday_at, time) } @@ -57,10 +57,12 @@ fun rememberUserStatusText(user: UserModel?): String { if (user.type == UserTypeEnum.BOT) return stringResource(R.string.status_bot) val context = LocalContext.current + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() return remember(user.userStatus, user.lastSeen) { when (user.userStatus) { UserStatusType.ONLINE -> context.getString(R.string.status_online) - UserStatusType.OFFLINE -> formatLastSeen(user.lastSeen, context) + UserStatusType.OFFLINE -> formatLastSeen(user.lastSeen, context, timeFormat) UserStatusType.RECENTLY -> context.getString(R.string.last_seen_recently) UserStatusType.LAST_WEEK -> context.getString(R.string.last_seen_within_week) UserStatusType.LAST_MONTH -> context.getString(R.string.last_seen_within_month) @@ -73,13 +75,13 @@ private fun isYesterday(timestamp: Long): Boolean { return DateUtils.isToday(timestamp + DateUtils.DAY_IN_MILLIS) } -fun getUserStatusText(user: UserModel?, context: Context): String { +fun getUserStatusText(user: UserModel?, context: Context, timeFormat: String): String { if (user == null) return context.getString(R.string.status_offline) if (user.type == UserTypeEnum.BOT) return context.getString(R.string.status_bot) return when (user.userStatus) { UserStatusType.ONLINE -> context.getString(R.string.status_online) - UserStatusType.OFFLINE -> formatLastSeen(user.lastSeen, context) + UserStatusType.OFFLINE -> formatLastSeen(user.lastSeen, context, timeFormat) UserStatusType.RECENTLY -> context.getString(R.string.last_seen_recently) UserStatusType.LAST_WEEK -> context.getString(R.string.last_seen_within_week) UserStatusType.LAST_MONTH -> context.getString(R.string.last_seen_within_month) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/chatList/components/MessageSearchItem.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/chatList/components/MessageSearchItem.kt index 8e46ef96..78a4978f 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/chatList/components/MessageSearchItem.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/chatList/components/MessageSearchItem.kt @@ -10,9 +10,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import org.koin.compose.koinInject import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel import org.monogram.presentation.core.ui.Avatar +import org.monogram.presentation.core.util.DateFormatManager import java.text.SimpleDateFormat import java.util.* @@ -27,11 +29,14 @@ fun MessageSearchItem( val currentCalendar = Calendar.getInstance() calendar.time = date + val dateFormatManager: DateFormatManager = koinInject(); + val timeFormat = dateFormatManager.getHourMinuteFormat() + val isToday = calendar.get(Calendar.YEAR) == currentCalendar.get(Calendar.YEAR) && calendar.get(Calendar.DAY_OF_YEAR) == currentCalendar.get(Calendar.DAY_OF_YEAR) val format = if (isToday) { - SimpleDateFormat("HH:mm", Locale.getDefault()) + SimpleDateFormat(timeFormat, Locale.getDefault()) } else { SimpleDateFormat("MMM d", Locale.getDefault()) } diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/chatContent/RestrictUserSheet.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/chatContent/RestrictUserSheet.kt index ec49902a..9bc9dac0 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/chatContent/RestrictUserSheet.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/chatContent/RestrictUserSheet.kt @@ -16,8 +16,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.koin.compose.koinInject import org.monogram.domain.models.ChatPermissionsModel import org.monogram.presentation.R +import org.monogram.presentation.core.util.DateFormatManager import java.text.SimpleDateFormat import java.util.* @@ -40,6 +42,9 @@ fun RestrictUserSheet( } } + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + if (showDatePicker) { val datePickerState = rememberDatePickerState( initialSelectedDateMillis = if (untilDate != 0) untilDate.toLong() * 1000 else System.currentTimeMillis() @@ -184,7 +189,7 @@ fun RestrictUserSheet( Text(stringResource(R.string.restrict_until), style = MaterialTheme.typography.bodyLarge) Text( text = if (untilDate == 0) stringResource(R.string.restrict_forever) else SimpleDateFormat( - "MMM d, yyyy, HH:mm", + "MMM d, yyyy, $timeFormat", Locale.getDefault() ).format(Date(untilDate.toLong() * 1000)), style = MaterialTheme.typography.bodySmall, diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelAlbumMessageBubble.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelAlbumMessageBubble.kt index 3a9470c9..dc241355 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelAlbumMessageBubble.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelAlbumMessageBubble.kt @@ -25,8 +25,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.koin.compose.koinInject import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.IDownloadUtils import org.monogram.presentation.features.chats.currentChat.components.CompactMediaMosaic import org.monogram.presentation.features.chats.currentChat.components.chats.* @@ -157,7 +159,10 @@ fun ChannelAlbumMessageBubble( } } - val formattedTime = remember(lastMsg.date) { formatTime(context, lastMsg.date) } + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + + val formattedTime = remember(lastMsg.date) { formatTime(lastMsg.date, timeFormat) } val revealedSpoilers = remember { mutableStateListOf() } var bubblePosition by remember { mutableStateOf(Offset.Zero) } diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelGifMessageBubble.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelGifMessageBubble.kt index de3f2ab3..99bd7254 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelGifMessageBubble.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelGifMessageBubble.kt @@ -32,9 +32,11 @@ import androidx.compose.ui.zIndex import coil3.compose.rememberAsyncImagePainter import coil3.request.ImageRequest import coil3.request.crossfade +import org.koin.compose.koinInject import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel import org.monogram.domain.models.MessageSendingState +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.IDownloadUtils import org.monogram.presentation.core.util.namespacedCacheKey import org.monogram.presentation.features.chats.currentChat.AutoDownloadSuppression @@ -84,6 +86,9 @@ fun ChannelGifMessageBubble( var gifPosition by remember { mutableStateOf(Offset.Zero) } val revealedSpoilers = remember { mutableStateListOf() } + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + var stablePath by remember(msg.id) { mutableStateOf(content.path) } val hasPath = !stablePath.isNullOrBlank() val gifCacheKey = remember(stablePath, content.fileId) { @@ -299,7 +304,7 @@ fun ChannelGifMessageBubble( } } Text( - text = formatTime(context, msg.date), + text = formatTime(msg.date, timeFormat), style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp), color = Color.White ) @@ -393,7 +398,7 @@ fun ChannelGifMessageBubble( } } Text( - text = formatTime(context, msg.date), + text = formatTime(msg.date, timeFormat), style = MaterialTheme.typography.labelSmall.copy(fontSize = 11.sp), color = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.6f) ) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelMessageUtils.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelMessageUtils.kt index 78f54012..42e9961a 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelMessageUtils.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelMessageUtils.kt @@ -5,8 +5,8 @@ import org.monogram.presentation.R import java.text.SimpleDateFormat import java.util.* -fun formatTime(context: Context, ts: Int): String = - SimpleDateFormat(context.getString(R.string.format_time), Locale.getDefault()).format(Date(ts.toLong() * 1000)) +fun formatTime(ts: Int, timeFormat: String): String = + SimpleDateFormat(timeFormat, Locale.getDefault()).format(Date(ts.toLong() * 1000)) fun formatViews(context: Context, views: Int): String { return when { diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelTextMessageBubble.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelTextMessageBubble.kt index e05cfa21..b7bc8225 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelTextMessageBubble.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/channels/ChannelTextMessageBubble.kt @@ -23,9 +23,11 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.koin.compose.koinInject import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel import org.monogram.domain.models.MessageSendingState +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.features.chats.currentChat.components.chats.* @Composable @@ -63,6 +65,9 @@ fun ChannelTextMessageBubble( bottomEnd = if (showComments && msg.canGetMessageThread) 4.dp else cornerRadius ) + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + val revealedSpoilers = remember { mutableStateListOf() } Column( @@ -158,9 +163,8 @@ fun ChannelTextMessageBubble( Spacer(modifier = Modifier.width(8.dp)) } } - Text( - text = formatTime(context, msg.date), + text = formatTime(msg.date, timeFormat), style = MaterialTheme.typography.labelSmall.copy(fontSize = 11.sp), color = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.6f) ) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/ChatAlbumMessageBubble.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/ChatAlbumMessageBubble.kt index 332d085c..98ef0f4d 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/ChatAlbumMessageBubble.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/ChatAlbumMessageBubble.kt @@ -15,9 +15,11 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.koin.compose.koinInject import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel import org.monogram.domain.models.MessageSendingState +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.IDownloadUtils import org.monogram.presentation.features.chats.currentChat.components.CompactMediaMosaic @@ -122,6 +124,9 @@ fun ChatAlbumMessageBubble( ) } + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + val captionMsg = remember(uniqueMessages) { uniqueMessages.firstOrNull { val content = it.content @@ -150,7 +155,7 @@ fun ChatAlbumMessageBubble( } val lastMsg = uniqueMessages.last() - val formattedTime = remember(lastMsg.date) { formatTime(lastMsg.date) } + val formattedTime = remember(lastMsg.date) { formatTime(lastMsg.date, timeFormat) } val revealedSpoilers = remember { mutableStateListOf() } var bubblePosition by remember { mutableStateOf(Offset.Zero) } diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/GifMessageBubble.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/GifMessageBubble.kt index 7a85ad4a..57c6aa19 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/GifMessageBubble.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/GifMessageBubble.kt @@ -33,8 +33,10 @@ import androidx.media3.common.util.UnstableApi import coil3.compose.rememberAsyncImagePainter import coil3.request.ImageRequest import coil3.request.crossfade +import org.koin.compose.koinInject import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.IDownloadUtils import org.monogram.presentation.core.util.namespacedCacheKey import org.monogram.presentation.features.chats.currentChat.AutoDownloadSuppression @@ -73,6 +75,9 @@ fun GifMessageBubble( val smallCorner = 4.dp val tailCorner = 2.dp + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + var stablePath by remember(msg.id) { mutableStateOf(content.path) } !stablePath.isNullOrBlank() val gifCacheKey = remember(stablePath, content.fileId) { @@ -318,7 +323,7 @@ fun GifMessageBubble( Spacer(modifier = Modifier.width(4.dp)) } Text( - text = formatTime(msg.date), + text = formatTime(msg.date, timeFormat), style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp), color = Color.White ) @@ -391,7 +396,7 @@ fun GifMessageBubble( Spacer(modifier = Modifier.width(4.dp)) } Text( - text = formatTime(msg.date), + text = formatTime(msg.date, timeFormat), style = MaterialTheme.typography.labelSmall.copy(fontSize = 11.sp), color = timeColor ) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/MessageUtils.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/MessageUtils.kt index b385b50a..6328a001 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/MessageUtils.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/MessageUtils.kt @@ -25,10 +25,12 @@ import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.koin.compose.koinInject import org.monogram.domain.models.MessageEntity import org.monogram.domain.models.MessageEntityType import org.monogram.domain.models.MessageModel import org.monogram.domain.models.MessageSendingState +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.EmojiStyle import org.monogram.presentation.features.chats.currentChat.components.channels.formatViews import java.io.File @@ -42,8 +44,8 @@ val LocalLinkHandler = staticCompositionLocalOf<(String) -> Unit> { { _ -> } } -fun formatTime(ts: Int): String = - SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date(ts.toLong() * 1000)) +fun formatTime(ts: Int, timeFormat: String): String = + SimpleDateFormat(timeFormat, Locale.getDefault()).format(Date(ts.toLong() * 1000)) fun formatDuration(seconds: Int): String { val m = seconds / 60 @@ -236,8 +238,10 @@ fun MessageMetadata( tint = contentColor ) } + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() Text( - text = formatTime(msg.date), + text = formatTime(msg.date, timeFormat), style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp), color = contentColor ) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/PollMessageBubble.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/PollMessageBubble.kt index 3cfd3d40..3d0cd133 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/PollMessageBubble.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/PollMessageBubble.kt @@ -28,8 +28,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.koin.compose.koinInject import org.monogram.domain.models.* import org.monogram.presentation.R +import org.monogram.presentation.core.util.DateFormatManager @Composable fun PollMessageBubble( @@ -444,6 +446,9 @@ private fun PollFooter( ) { val metaColor = contentColor.copy(alpha = 0.65f) + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + Row( modifier = Modifier .fillMaxWidth() @@ -461,7 +466,7 @@ private fun PollFooter( Row(verticalAlignment = Alignment.CenterVertically) { Text( - text = formatTime(date), + text = formatTime(date, timeFormat), style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp), color = metaColor ) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/StickerMessageBubble.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/StickerMessageBubble.kt index 0103eeda..a32897a0 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/StickerMessageBubble.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/StickerMessageBubble.kt @@ -22,9 +22,11 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.media3.common.util.UnstableApi +import org.koin.compose.koinInject import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel import org.monogram.domain.models.StickerModel +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.features.stickers.ui.view.StickerImage import org.monogram.presentation.features.stickers.ui.view.StickerSkeleton import java.io.File @@ -44,6 +46,9 @@ fun StickerMessageBubble( toProfile: (Long) -> Unit = {}, modifier: Modifier = Modifier ) { + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + Column( modifier = modifier, horizontalAlignment = if (isOutgoing) Alignment.End else Alignment.Start @@ -146,7 +151,7 @@ fun StickerMessageBubble( Spacer(modifier = Modifier.width(4.dp)) } Text( - text = formatTime(msg.date), + text = formatTime(msg.date, timeFormat), style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp), color = Color.White, ) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/TextMessageBubble.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/TextMessageBubble.kt index 9b3bee5d..db337c93 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/TextMessageBubble.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/TextMessageBubble.kt @@ -19,8 +19,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.koin.compose.koinInject import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel +import org.monogram.presentation.core.util.DateFormatManager @Composable @@ -49,6 +51,9 @@ fun TextMessageBubble( val smallCorner = (bubbleRadius / 4f).coerceAtLeast(4f).dp val tailCorner = 2.dp + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + val bubbleShape = remember(isOutgoing, isSameSenderAbove, isSameSenderBelow, cornerRadius, smallCorner) { RoundedCornerShape( topStart = if (!isOutgoing && isSameSenderAbove) smallCorner else cornerRadius, @@ -176,9 +181,8 @@ fun TextMessageBubble( ) Spacer(modifier = Modifier.width(4.dp)) } - Text( - text = formatTime(msg.date), + text = formatTime(msg.date, timeFormat), style = MaterialTheme.typography.labelSmall.copy(fontSize = 11.sp), color = timeColor ) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/VideoNoteBubble.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/VideoNoteBubble.kt index 4fdcd024..105584e9 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/VideoNoteBubble.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/chats/VideoNoteBubble.kt @@ -48,8 +48,10 @@ import androidx.media3.ui.PlayerView import coil3.compose.rememberAsyncImagePainter import kotlinx.coroutines.delay import kotlinx.coroutines.isActive +import org.koin.compose.koinInject import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.getMimeType import org.monogram.presentation.features.stickers.ui.view.shimmerEffect import java.io.File @@ -73,6 +75,9 @@ fun VideoNoteBubble( val size = 260.dp var notePosition by remember { mutableStateOf(Offset.Zero) } + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + Column( modifier = modifier, horizontalAlignment = if (isOutgoing) Alignment.End else Alignment.Start @@ -337,7 +342,7 @@ fun VideoNoteBubble( Spacer(modifier = Modifier.width(4.dp)) } Text( - text = formatTime(msg.date), + text = formatTime(msg.date, timeFormat), style = MaterialTheme.typography.labelSmall.copy(fontSize = 11.sp), color = Color.White ) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/MentionSuggestions.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/MentionSuggestions.kt index fa2eb057..f4fe0961 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/MentionSuggestions.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/MentionSuggestions.kt @@ -14,9 +14,11 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import org.koin.compose.koinInject import org.monogram.domain.models.UserModel import org.monogram.domain.models.UserStatusType import org.monogram.presentation.core.ui.Avatar +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.getUserStatusText @Composable @@ -25,6 +27,8 @@ fun MentionSuggestions( onMentionClick: (UserModel) -> Unit ) { val context = LocalContext.current + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() Column( modifier = Modifier .fillMaxWidth() @@ -58,7 +62,7 @@ fun MentionSuggestions( maxLines = 1, overflow = TextOverflow.Ellipsis ) - val status = user.username?.let { "@$it" } ?: getUserStatusText(user, context) + val status = user.username?.let { "@$it" } ?: getUserStatusText(user, context, timeFormat) Text( text = status, style = MaterialTheme.typography.labelMedium, diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/SchedulePickers.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/SchedulePickers.kt index f69c0b38..1d13fd40 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/SchedulePickers.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/SchedulePickers.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import org.monogram.presentation.R +import org.monogram.presentation.core.util.DateFormatManager import java.text.SimpleDateFormat import java.util.* @@ -104,9 +105,9 @@ fun buildScheduledDateEpochSeconds(selectedDateMillis: Long, hour: Int, minute: return (selected.timeInMillis / 1000L).toInt() } -fun formatScheduledTimestamp(epochSeconds: Int): String { +fun formatScheduledTimestamp(epochSeconds: Int, timeFormat: String): String { return try { - val formatter = SimpleDateFormat("dd MMM, HH:mm", Locale.getDefault()) + val formatter = SimpleDateFormat("dd MMM, $timeFormat", Locale.getDefault()) formatter.format(Date(epochSeconds * 1000L)) } catch (_: Exception) { "" diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/ScheduledMessagesSheet.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/ScheduledMessagesSheet.kt index eb688fd4..1bf40965 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/ScheduledMessagesSheet.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/currentChat/components/inputbar/ScheduledMessagesSheet.kt @@ -14,9 +14,11 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import org.koin.compose.koinInject import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel import org.monogram.presentation.R +import org.monogram.presentation.core.util.DateFormatManager @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -39,6 +41,9 @@ fun ScheduledMessagesSheet( scheduledMessagesSorted.count { canEditScheduledMessage(it) } } + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + ModalBottomSheet( onDismissRequest = onDismiss, dragHandle = { BottomSheetDefaults.DragHandle() }, @@ -102,7 +107,7 @@ fun ScheduledMessagesSheet( text = if (nextScheduled != null) { stringResource( R.string.scheduled_messages_summary_next, - formatScheduledTimestamp(nextScheduled.date) + formatScheduledTimestamp(nextScheduled.date, timeFormat) ) } else { stringResource(R.string.scheduled_messages_empty) @@ -245,8 +250,9 @@ private fun ScheduledMessageRow( maxLines = 1, overflow = TextOverflow.Ellipsis ) + val dateFormatManager: DateFormatManager = koinInject() Text( - text = formatScheduledTimestamp(message.date), + text = formatScheduledTimestamp(message.date, dateFormatManager.getHourMinuteFormat()), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/newChat/NewChatContent.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/newChat/NewChatContent.kt index 01adf2ab..2ab9333f 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/newChat/NewChatContent.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/newChat/NewChatContent.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.koin.compose.koinInject import org.monogram.domain.models.UserModel import org.monogram.domain.models.UserStatusType import org.monogram.presentation.R @@ -44,6 +45,7 @@ import org.monogram.presentation.core.ui.Avatar import org.monogram.presentation.core.ui.ConfirmationSheet import org.monogram.presentation.core.ui.ItemPosition import org.monogram.presentation.core.ui.shimmerBackground +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.FileUtils import org.monogram.presentation.core.util.getUserStatusText import org.monogram.presentation.features.chats.chatList.components.NewChannelContent @@ -621,6 +623,8 @@ private fun ContactItem( onRemoveContact: () -> Unit ) { val context = LocalContext.current + val timeFormatManager: DateFormatManager = koinInject() + val timeFormat = timeFormatManager.getHourMinuteFormat() val isSupport = user.isSupport var showMenu by remember { mutableStateOf(false) } val cornerRadius = 24.dp @@ -684,7 +688,7 @@ private fun ContactItem( color = MaterialTheme.colorScheme.onSurface ) } else { - val statusText = getUserStatusText(user, context) + val statusText = getUserStatusText(user, context, timeFormat) Text( text = statusText, style = MaterialTheme.typography.bodySmall, diff --git a/presentation/src/main/java/org/monogram/presentation/features/profile/ProfileContent.kt b/presentation/src/main/java/org/monogram/presentation/features/profile/ProfileContent.kt index 6f214f8d..8fbf94bd 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/profile/ProfileContent.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/profile/ProfileContent.kt @@ -36,10 +36,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.window.core.layout.WindowSizeClass import com.arkivanov.decompose.extensions.compose.subscribeAsState +import org.koin.compose.koinInject import org.monogram.domain.models.UserStatusType import org.monogram.domain.models.UserTypeEnum import org.monogram.presentation.R import org.monogram.presentation.core.ui.* +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.ScrollStrategy import org.monogram.presentation.core.util.getUserStatusText import org.monogram.presentation.features.chats.chatList.components.SettingsTextField @@ -55,6 +57,8 @@ fun ProfileContent(component: ProfileComponent) { val context = LocalContext.current val collapsingToolbarState = rememberCollapsingToolbarScaffoldState() + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() val chat = state.chat val user = state.user @@ -105,7 +109,7 @@ fun ProfileContent(component: ProfileComponent) { ?: ownProfileSubtitle } - else -> getUserStatusText(user, context) + else -> getUserStatusText(user, context, timeFormat) } } diff --git a/presentation/src/main/java/org/monogram/presentation/features/profile/components/ProfileMediaSection.kt b/presentation/src/main/java/org/monogram/presentation/features/profile/components/ProfileMediaSection.kt index 6581cb7b..6ded266c 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/profile/components/ProfileMediaSection.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/profile/components/ProfileMediaSection.kt @@ -35,6 +35,7 @@ import androidx.core.net.toUri import coil3.compose.AsyncImage import coil3.request.ImageRequest import coil3.request.crossfade +import org.koin.compose.koinInject import org.monogram.domain.models.GroupMemberModel import org.monogram.domain.models.MessageContent import org.monogram.domain.models.MessageModel @@ -42,6 +43,7 @@ import org.monogram.domain.models.UserStatusType import org.monogram.presentation.R import org.monogram.presentation.core.ui.Avatar import org.monogram.presentation.core.ui.rememberShimmerBrush +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.getUserStatusText import org.monogram.presentation.features.chats.currentChat.components.VideoStickerPlayer import org.monogram.presentation.features.chats.currentChat.components.VideoType @@ -378,7 +380,11 @@ private fun LazyGridScope.membersList( }, supportingContent = { val context = LocalContext.current - val statusText = getUserStatusText(user, context) + + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + + val statusText = getUserStatusText(user, context, timeFormat) Text( text = statusText, diff --git a/presentation/src/main/java/org/monogram/presentation/features/profile/components/StatisticsViewer.kt b/presentation/src/main/java/org/monogram/presentation/features/profile/components/StatisticsViewer.kt index 0baf0bbe..b5b498a0 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/profile/components/StatisticsViewer.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/profile/components/StatisticsViewer.kt @@ -36,8 +36,10 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.json.JSONObject +import org.koin.compose.koinInject import org.monogram.domain.models.* import org.monogram.presentation.R +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.features.chats.chatList.components.SectionHeader import java.text.SimpleDateFormat import java.util.* @@ -767,8 +769,10 @@ private fun RevenueMetricCard(modifier: Modifier = Modifier, label: String, valu @Composable fun GraphSection(title: String, graph: StatisticsGraphModel, color: Color, onLoadGraph: (String) -> Unit) { if (graph is StatisticsGraphModel.Error) return + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() val parsedGraph = remember(graph) { - if (graph is StatisticsGraphModel.Data) parseStatisticsGraph(graph.jsonData) else null + if (graph is StatisticsGraphModel.Data) parseStatisticsGraph(graph.jsonData, timeFormat) else null } Column { @@ -1394,7 +1398,7 @@ private data class ParsedStatisticsSeries( val values: List ) -private fun parseStatisticsGraph(jsonData: String): ParsedStatisticsGraph? { +private fun parseStatisticsGraph(jsonData: String, timeFormat: String): ParsedStatisticsGraph? { return coRunCatching { val root = JSONObject(jsonData) val columnsArray = root.optJSONArray("columns") ?: return null @@ -1423,7 +1427,7 @@ private fun parseStatisticsGraph(jsonData: String): ParsedStatisticsGraph? { val labels = (0 until labelCount).map { index -> val rawX = xValues.getOrNull(index)?.toLong() ?: index.toLong() - formatChartXAxisLabel(rawX, labelCount) + formatChartXAxisLabel(rawX, labelCount, timeFormat) } val series = mutableListOf() @@ -1450,14 +1454,14 @@ private fun parseStatisticsGraph(jsonData: String): ParsedStatisticsGraph? { }.getOrNull() } -private fun formatChartXAxisLabel(rawValue: Long, pointCount: Int): String { +private fun formatChartXAxisLabel(rawValue: Long, pointCount: Int, timeFormat: String): String { return if (rawValue > 10_000_000_000L) { val date = Date(rawValue) - if (pointCount <= 24) SimpleDateFormat("HH:mm", Locale.getDefault()).format(date) + if (pointCount <= 24) SimpleDateFormat(timeFormat, Locale.getDefault()).format(date) else SimpleDateFormat("dd MMM", Locale.getDefault()).format(date) } else if (rawValue > 1_000_000_000L) { val date = Date(rawValue * 1000L) - if (pointCount <= 24) SimpleDateFormat("HH:mm", Locale.getDefault()).format(date) + if (pointCount <= 24) SimpleDateFormat(timeFormat, Locale.getDefault()).format(date) else SimpleDateFormat("dd MMM", Locale.getDefault()).format(date) } else { rawValue.toString() diff --git a/presentation/src/main/java/org/monogram/presentation/features/profile/logs/components/ActionDetails.kt b/presentation/src/main/java/org/monogram/presentation/features/profile/logs/components/ActionDetails.kt index 8cef4e04..d25c43b0 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/profile/logs/components/ActionDetails.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/profile/logs/components/ActionDetails.kt @@ -47,10 +47,12 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage +import org.koin.compose.koinInject import org.monogram.domain.models.ChatEventActionModel import org.monogram.domain.models.ChatPermissionsModel import org.monogram.presentation.R import org.monogram.presentation.core.ui.Avatar +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.features.profile.logs.ProfileLogsComponent import java.io.File import java.text.SimpleDateFormat @@ -141,8 +143,10 @@ fun ActionDetails( if (action.untilDate > 0) { val date = Date(action.untilDate.toLong() * 1000) + val dateFormatManager: DateFormatManager = koinInject(); + val timeFormat = dateFormatManager.getHourMinuteFormat() val dateText = - SimpleDateFormat("MMM dd, yyyy HH:mm", Locale.getDefault()).format(date) + SimpleDateFormat("MMM dd, yyyy $timeFormat", Locale.getDefault()).format(date) Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 8.dp) diff --git a/presentation/src/main/java/org/monogram/presentation/features/profile/logs/components/LogBubble.kt b/presentation/src/main/java/org/monogram/presentation/features/profile/logs/components/LogBubble.kt index 8e62ff58..4a0af0bd 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/profile/logs/components/LogBubble.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/profile/logs/components/LogBubble.kt @@ -19,11 +19,13 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import org.koin.compose.koinInject import org.monogram.domain.models.ChatEventActionModel import org.monogram.domain.models.ChatEventModel import org.monogram.domain.models.MessageSenderModel import org.monogram.presentation.R import org.monogram.presentation.core.ui.Avatar +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.features.profile.logs.ProfileLogsComponent import java.text.SimpleDateFormat import java.util.* @@ -87,10 +89,12 @@ fun LogBubble( var showFullDate by remember { mutableStateOf(false) } val date = Date(event.date.toLong() * 1000) + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() val dateText = if (showFullDate) { - SimpleDateFormat("MMM dd, HH:mm:ss", Locale.getDefault()).format(date) + SimpleDateFormat("MMM dd, $timeFormat:ss", Locale.getDefault()).format(date) } else { - SimpleDateFormat("HH:mm", Locale.getDefault()).format(date) + SimpleDateFormat(timeFormat, Locale.getDefault()).format(date) } Row( diff --git a/presentation/src/main/java/org/monogram/presentation/features/stickers/ui/menu/MessageOptionsMenu.kt b/presentation/src/main/java/org/monogram/presentation/features/stickers/ui/menu/MessageOptionsMenu.kt index 5debd437..46d4e383 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/stickers/ui/menu/MessageOptionsMenu.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/stickers/ui/menu/MessageOptionsMenu.kt @@ -50,6 +50,7 @@ import org.monogram.domain.repository.EmojiRepository import org.monogram.presentation.R import org.monogram.presentation.core.ui.Avatar import org.monogram.presentation.core.util.AppPreferences +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.features.chats.currentChat.chatContent.DeleteMessagesSheet import org.monogram.presentation.features.chats.currentChat.components.chats.getEmojiFontFamily import org.monogram.presentation.features.stickers.ui.view.StickerImage @@ -111,6 +112,10 @@ fun MessageOptionsMenu( val windowSize = LocalWindowInfo.current.containerSize val screenHeight = windowSize.height + + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + val windowInsets = WindowInsets.systemBars.union(WindowInsets.ime) val topInset = windowInsets.getTop(density) val bottomInset = windowInsets.getBottom(density) @@ -778,7 +783,7 @@ fun MessageOptionsMenu( else -> { val viewerDateFormat = - remember { SimpleDateFormat("MMM d, HH:mm", Locale.getDefault()) } + remember { SimpleDateFormat("MMM d, $timeFormat", Locale.getDefault()) } val scrollState = rememberScrollState() Column( modifier = Modifier @@ -1116,7 +1121,9 @@ private fun InternalMenuHeaderInfo( showReadInfo: Boolean, showViewsInfo: Boolean ) { - val dateFormat = remember { SimpleDateFormat("MMM d, HH:mm", Locale.getDefault()) } + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + val dateFormat = remember { SimpleDateFormat("MMM d, $timeFormat", Locale.getDefault()) } val editDate = if (message.editDate > 0) dateFormat.format(Date(message.editDate.toLong() * 1000)) else null val readDate = if (showReadInfo) diff --git a/presentation/src/main/java/org/monogram/presentation/features/viewers/YouTubeViewer.kt b/presentation/src/main/java/org/monogram/presentation/features/viewers/YouTubeViewer.kt index 3677933d..bd2f9923 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/viewers/YouTubeViewer.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/viewers/YouTubeViewer.kt @@ -50,6 +50,8 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.koin.compose.koinInject +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.features.viewers.components.* import java.io.ByteArrayInputStream import java.text.SimpleDateFormat @@ -71,6 +73,8 @@ fun YouTubeViewer( val youtubeId = extractYouTubeId(videoUrl) ?: return val startTime = extractYouTubeTime(videoUrl) val context = LocalContext.current + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() val lifecycleOwner = LocalLifecycleOwner.current val playerState = remember { YouTubePlayerState() } var isInPipMode by remember { mutableStateOf(false) } @@ -290,7 +294,7 @@ fun YouTubeViewer( LaunchedEffect(Unit) { while (true) { - currentTimeStr = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date()) + currentTimeStr = SimpleDateFormat(timeFormat, Locale.getDefault()).format(Date()) delay(1000) } } diff --git a/presentation/src/main/java/org/monogram/presentation/features/viewers/components/VideoViewerComponents.kt b/presentation/src/main/java/org/monogram/presentation/features/viewers/components/VideoViewerComponents.kt index 3e5eeb82..17374990 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/viewers/components/VideoViewerComponents.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/viewers/components/VideoViewerComponents.kt @@ -55,6 +55,7 @@ import org.koin.compose.koinInject import org.monogram.domain.repository.PlayerDataSourceFactory import org.monogram.domain.repository.StreamingRepository import org.monogram.presentation.R +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.IDownloadUtils import org.monogram.presentation.core.util.getMimeType import org.monogram.presentation.features.stickers.ui.menu.MenuOptionRow @@ -100,6 +101,9 @@ fun VideoPage( val playerFactory = koinInject() val seekDurationMs = seekDuration * 1000L + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + val currentOnDismiss by rememberUpdatedState(onDismiss) val currentOnToggleControls by rememberUpdatedState(onToggleControls) val currentOnToggleSettings by rememberUpdatedState(onToggleSettings) @@ -401,7 +405,7 @@ fun VideoPage( isEnded = isEnded, currentPosition = currentPosition, totalDuration = totalDuration, - currentTime = currentTime(), + currentTime = currentTime(timeFormat), isSettingsOpen = showSettingsMenu, caption = caption, downloadProgress = downloadProgress, diff --git a/presentation/src/main/java/org/monogram/presentation/features/viewers/components/ViewerComponents.kt b/presentation/src/main/java/org/monogram/presentation/features/viewers/components/ViewerComponents.kt index 4e1da8cd..f7b6aa1d 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/viewers/components/ViewerComponents.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/viewers/components/ViewerComponents.kt @@ -30,6 +30,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.monogram.presentation.R +import org.monogram.presentation.core.util.DateFormatManager +import java.text.SimpleDateFormat import java.util.* @Composable @@ -231,8 +233,8 @@ fun formatDuration(durationMs: Long): String { return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds) } -fun currentTime(): String = - java.text.SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date()) +fun currentTime(timeFormat: String): String = + SimpleDateFormat(timeFormat, Locale.getDefault()).format(Date()) fun Context.findActivity(): ComponentActivity? = when (this) { is ComponentActivity -> this diff --git a/presentation/src/main/res/values-es/string.xml b/presentation/src/main/res/values-es/string.xml index 88f570c5..0756ec86 100644 --- a/presentation/src/main/res/values-es/string.xml +++ b/presentation/src/main/res/values-es/string.xml @@ -1607,7 +1607,6 @@ Mensaje no soportado - HH:mm %1$02d:%2$02d diff --git a/presentation/src/main/res/values-hy/string.xml b/presentation/src/main/res/values-hy/string.xml index 6d8d6d3a..da35a312 100644 --- a/presentation/src/main/res/values-hy/string.xml +++ b/presentation/src/main/res/values-hy/string.xml @@ -1460,7 +1460,6 @@ Վայր Չաջակցվող հաղորդագրություն - HH:mm %1$02d:%2$02d %1$.1fՀ diff --git a/presentation/src/main/res/values-pt-rBR/string.xml b/presentation/src/main/res/values-pt-rBR/string.xml index 072a4c06..40b03f4f 100644 --- a/presentation/src/main/res/values-pt-rBR/string.xml +++ b/presentation/src/main/res/values-pt-rBR/string.xml @@ -1635,7 +1635,6 @@ Mensagem não compatível - HH:mm %1$02d:%2$02d diff --git a/presentation/src/main/res/values-ru-rRU/string.xml b/presentation/src/main/res/values-ru-rRU/string.xml index 2e24ee89..bb1b7072 100644 --- a/presentation/src/main/res/values-ru-rRU/string.xml +++ b/presentation/src/main/res/values-ru-rRU/string.xml @@ -1569,7 +1569,6 @@ Неподдерживаемое сообщение - HH:mm %1$02d:%2$02d diff --git a/presentation/src/main/res/values-sk/string.xml b/presentation/src/main/res/values-sk/string.xml index 16488a7a..9b405391 100644 --- a/presentation/src/main/res/values-sk/string.xml +++ b/presentation/src/main/res/values-sk/string.xml @@ -1633,7 +1633,6 @@ Nepodporovaná správa - HH:mm %1$02d:%2$02d diff --git a/presentation/src/main/res/values-uk/string.xml b/presentation/src/main/res/values-uk/string.xml index 9567abc3..9b30972e 100644 --- a/presentation/src/main/res/values-uk/string.xml +++ b/presentation/src/main/res/values-uk/string.xml @@ -1569,7 +1569,6 @@ Непідтримуване повідомлення - HH:mm %1$02d:%2$02d diff --git a/presentation/src/main/res/values-zh-rCN/string.xml b/presentation/src/main/res/values-zh-rCN/string.xml index 62c0e7d8..5b91a23d 100644 --- a/presentation/src/main/res/values-zh-rCN/string.xml +++ b/presentation/src/main/res/values-zh-rCN/string.xml @@ -1556,7 +1556,6 @@ 不支持的消息 - HH:mm %1$02d:%2$02d diff --git a/presentation/src/main/res/values/string.xml b/presentation/src/main/res/values/string.xml index 47e880d4..75ea7fae 100644 --- a/presentation/src/main/res/values/string.xml +++ b/presentation/src/main/res/values/string.xml @@ -1648,7 +1648,6 @@ Unsupported message - HH:mm %1$02d:%2$02d From 3d514b937738fc6ddbe82f84d1e0044092aaf159 Mon Sep 17 00:00:00 2001 From: iWisp360 Date: Thu, 9 Apr 2026 13:58:00 -0400 Subject: [PATCH 03/11] add support for building with nix and release with github actions --- .github/workflows/nix-android.yml | 38 ++++++++++++++++++++++++++ flake.nix | 44 +++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 .github/workflows/nix-android.yml diff --git a/.github/workflows/nix-android.yml b/.github/workflows/nix-android.yml new file mode 100644 index 00000000..f80dc671 --- /dev/null +++ b/.github/workflows/nix-android.yml @@ -0,0 +1,38 @@ +name: Build APKs for Android using Nix + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + submodules: 'recursive' + + - uses: cachix/install-nix-action@v31 + + - name: Check Necessary environment variables presence + run: | + if [ -z "$LOCAL_PROPERTIES" ] || [ -z "$GOOGLE_SERVICES_JSON" ]; then + echo "You may want to add LOCAL_PROPERTIES and GOOGLE_SERVICES_JSON environment variables to your secrets" + exit 1 + fi + env: + LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} + + - name: Build Task + run: nix --experimental-features "nix-command flakes" run .#buildRelease + env: + LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} + + - name: Upload Release APKs + uses: actions/upload-artifact@v7 + with: + name: Release APKs + path: app/build/outputs/apk/release/*.apk + diff --git a/flake.nix b/flake.nix index 379db525..cdccda10 100644 --- a/flake.nix +++ b/flake.nix @@ -15,15 +15,18 @@ perSystem = { pkgs, system, ... }: let + actualBuildTools = "36.0.0"; androidComposition = pkgs: (pkgs.androidenv.composeAndroidPackages { platformVersions = [ "35" + "36" ]; buildToolsVersions = [ "35.0.0" + actualBuildTools ]; cmakeVersions = [ @@ -70,6 +73,47 @@ JAVA_HOME = "${pkgs.jdk21}"; ANDROID_HOME = "${androidComposition pkgs}/libexec/android-sdk"; }; + + apps = { + buildRelease = { + type = "app"; + program = "${pkgs.writeShellScriptBin "buildRelease" '' + if [ -z "$LOCAL_PROPERTIES" ] || [ -z "$GOOGLE_SERVICES_JSON" ]; then + echo "LOCAL_PROPERTIES and GOOGLE_SERVICES_JSON environment variables must be set" + exit 1 + fi + + if ! ${pkgs.git}/bin/git submodule update --init --recursive; then + echo "failed getting submodules" + fi + + echo $LOCAL_PROPERTIES >local.properties + echo $GOOGLE_SERVICES_JSON >app/google-services.json + + export ANDROID_HOME="${androidComposition pkgs}/libexec/android-sdk/" + export ANDROID_SDK_ROOT="${androidComposition pkgs}/libexec/android-sdk/" + export ANDROID_NDK_HOME="$ANDROID_HOME/ndk-bundle/" + export GRADLE_OPTS="-Dorg.gradle.project.android.aapt2FromMavenOverride=${androidComposition pkgs}/libexec/android-sdk/build-tools/${actualBuildTools}/aapt2" + export JAVA_HOME="${pkgs.jdk21}" + + cd presentation/src/main/cpp/ + if ! [ -d libvpx_build ]; then + sed -e "s|#!/bin/bash|#!${pkgs.bash}/bin/bash|g" \ + -e "s|make clean|${pkgs.gnumake}/bin/make clean|g" \ + -e "s|make -j|${pkgs.gnumake}/bin/make -j|g" build.sh >new_build.sh + chmod +x new_build.sh + if ! ${pkgs.bash}/bin/bash new_build.sh; then + echo "libvpx build failed" + exit 1 + fi + rm new_build.sh + fi + + cd ../../../.. + ./gradlew assembleRelease + ''}/bin/buildRelease"; + }; + }; }; }; } From b99080745ef856590c83cb1a2b22c293228644a2 Mon Sep 17 00:00:00 2001 From: iWisp360 Date: Thu, 9 Apr 2026 22:35:08 -0400 Subject: [PATCH 04/11] fix failing tests --- .../presentation/core/util/DateUtils.kt | 7 +------ .../chats/chatList/components/ChatListItem.kt | 6 +++++- .../settings/sessions/SessionItem.kt | 8 ++++++-- .../presentation/core/util/DateUtilsTest.kt | 18 +++++++++--------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt b/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt index fc775a95..940d5520 100644 --- a/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt +++ b/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt @@ -2,8 +2,6 @@ package org.monogram.presentation.core.util import android.content.Context import android.text.format.DateFormat -import androidx.compose.runtime.Composable -import org.koin.compose.koinInject import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date @@ -17,17 +15,14 @@ import kotlin.math.roundToLong * @param locale a [Locale] object which used with this date * @param now optional current date (for custom formatting or testing) **/ -@Composable fun Date.toShortRelativeDate( + timeFormat: String, locale: Locale = Locale.getDefault(), now: Date = Date() ): String { val currentCalendar = Calendar.getInstance(locale).apply { time = now } val targetCalendar = Calendar.getInstance(locale).apply { time = this@toShortRelativeDate } - val dateFormatManager: DateFormatManager = koinInject() - val timeFormat = dateFormatManager.getHourMinuteFormat() - val currentDayStart = currentCalendar.clone() as Calendar currentDayStart.apply { set(Calendar.HOUR_OF_DAY, 0) diff --git a/presentation/src/main/java/org/monogram/presentation/features/chats/chatList/components/ChatListItem.kt b/presentation/src/main/java/org/monogram/presentation/features/chats/chatList/components/ChatListItem.kt index 21592dfe..1a6c39f2 100644 --- a/presentation/src/main/java/org/monogram/presentation/features/chats/chatList/components/ChatListItem.kt +++ b/presentation/src/main/java/org/monogram/presentation/features/chats/chatList/components/ChatListItem.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import kotlinx.coroutines.flow.StateFlow +import org.koin.compose.koinInject import org.monogram.domain.models.ChatModel import org.monogram.domain.models.MessageEntityType import org.monogram.presentation.R @@ -41,6 +42,7 @@ import org.monogram.presentation.features.chats.currentChat.components.chats.bui import org.monogram.presentation.features.chats.currentChat.components.chats.rememberMessageInlineContent import org.monogram.presentation.features.stickers.ui.view.StickerImage import org.monogram.core.date.toDate +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.features.chats.ChatListComponent @OptIn(ExperimentalFoundationApi::class) @@ -196,7 +198,9 @@ private fun ChatListItemHeader( chat: ChatModel, isSavedMessages: Boolean ) { - val chatTime = chat.lastMessageDate.toDate().toShortRelativeDate() + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() + val chatTime = chat.lastMessageDate.toDate().toShortRelativeDate(timeFormat) val savedMessagesTitle = stringResource(R.string.menu_saved_messages) Row( diff --git a/presentation/src/main/java/org/monogram/presentation/settings/sessions/SessionItem.kt b/presentation/src/main/java/org/monogram/presentation/settings/sessions/SessionItem.kt index 8b987e22..02fe9655 100644 --- a/presentation/src/main/java/org/monogram/presentation/settings/sessions/SessionItem.kt +++ b/presentation/src/main/java/org/monogram/presentation/settings/sessions/SessionItem.kt @@ -29,10 +29,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.koin.compose.koinInject import org.monogram.presentation.R import org.monogram.domain.models.SessionModel import org.monogram.presentation.core.ui.ItemPosition import org.monogram.presentation.core.ui.spacer.WidthSpacer +import org.monogram.presentation.core.util.DateFormatManager import org.monogram.presentation.core.util.toShortRelativeDate @Composable @@ -43,6 +45,8 @@ internal fun SessionItem( position: ItemPosition = ItemPosition.STANDALONE, onTerminate: (() -> Unit)? ) { + val dateFormatManager: DateFormatManager = koinInject() + val timeFormat = dateFormatManager.getHourMinuteFormat() Surface( color = MaterialTheme.colorScheme.surfaceContainer, shape = position.toShape(), @@ -80,8 +84,8 @@ internal fun SessionItem( ) Text( - text = if (isPending) "${stringResource(R.string.sessions_unconfirmed)} • ${session.lastActiveDate.toShortRelativeDate()}" - else "${session.location} • ${session.lastActiveDate.toShortRelativeDate()}", + text = if (isPending) "${stringResource(R.string.sessions_unconfirmed)} • ${session.lastActiveDate.toShortRelativeDate(timeFormat)}" + else "${session.location} • ${session.lastActiveDate.toShortRelativeDate(timeFormat)}", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f) ) diff --git a/presentation/src/test/java/org/monogram/presentation/core/util/DateUtilsTest.kt b/presentation/src/test/java/org/monogram/presentation/core/util/DateUtilsTest.kt index 732c6a76..1c75e3d5 100644 --- a/presentation/src/test/java/org/monogram/presentation/core/util/DateUtilsTest.kt +++ b/presentation/src/test/java/org/monogram/presentation/core/util/DateUtilsTest.kt @@ -17,7 +17,7 @@ class DateUtilsTest { @Test fun `When date are today, then returns only hours and minutes`() { val targetDate = dateFormatter.parse("20.03.2024 15:20")!! - val result = targetDate.toShortRelativeDate(locale = ruLocale, now = mockToday) + val result = targetDate.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday) assertEquals("15:20", result) } @@ -26,38 +26,38 @@ class DateUtilsTest { fun `When date are yesterday and within 1 week, then returns day of week`() { // Yesterday val yesterday = dateFormatter.parse("19.03.2024 10:15")!! - assertEquals("вт", yesterday.toShortRelativeDate(locale = ruLocale, now = mockToday)) + assertEquals("вт", yesterday.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) // Six days ago val sixDaysAgo = dateFormatter.parse("14.03.2024 09:00")!! - assertEquals("чт", sixDaysAgo.toShortRelativeDate(locale = ruLocale, now = mockToday)) + assertEquals("чт", sixDaysAgo.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) } @Test fun `When date are older than 1 week but within 1 year, then return day and month`() { // 13.03.2024 - ровно 7 дней назад val sevenDaysAgo = dateFormatter.parse("13.03.2024 14:00")!! - assertEquals("13 мар", sevenDaysAgo.toShortRelativeDate(locale = ruLocale, now = mockToday)) + assertEquals("13 мар", sevenDaysAgo.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) // Прошлый месяц (меньше 365 дней назад) val monthsAgo = dateFormatter.parse("25.10.2023 18:30")!! - assertEquals("25 окт", monthsAgo.toShortRelativeDate(locale = ruLocale, now = mockToday)) + assertEquals("25 окт", monthsAgo.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) } @Test fun `When date are older than 1 year, then return full date`() { // Past year val olderThanYear = dateFormatter.parse("10.03.2023 11:11")!! - assertEquals("10.03.2023", olderThanYear.toShortRelativeDate(locale = ruLocale, now = mockToday)) + assertEquals("10.03.2023", olderThanYear.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) // A long time ago... val veryOldDate = dateFormatter.parse("01.01.2020 00:00")!! - assertEquals("01.01.2020", veryOldDate.toShortRelativeDate(locale = ruLocale, now = mockToday)) + assertEquals("01.01.2020", veryOldDate.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) } @Test fun `Then future date is older than 1 year, then return full date`() { val futureDate = dateFormatter.parse("16.03.2029 10:00")!! - assertEquals("16.03.2029", futureDate.toShortRelativeDate(locale = ruLocale, now = mockToday)) + assertEquals("16.03.2029", futureDate.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) } -} \ No newline at end of file +} From e601cc0076a7a42ebdb7d10ece388017bbfcbab5 Mon Sep 17 00:00:00 2001 From: iWisp360 Date: Thu, 9 Apr 2026 23:24:55 -0400 Subject: [PATCH 05/11] fix Release CI secrets handling --- .github/workflows/nix-android.yml | 14 +++----------- flake.nix | 11 ++++------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/.github/workflows/nix-android.yml b/.github/workflows/nix-android.yml index f80dc671..05c6c537 100644 --- a/.github/workflows/nix-android.yml +++ b/.github/workflows/nix-android.yml @@ -14,21 +14,13 @@ jobs: - uses: cachix/install-nix-action@v31 - - name: Check Necessary environment variables presence + - name: Add required files run: | - if [ -z "$LOCAL_PROPERTIES" ] || [ -z "$GOOGLE_SERVICES_JSON" ]; then - echo "You may want to add LOCAL_PROPERTIES and GOOGLE_SERVICES_JSON environment variables to your secrets" - exit 1 - fi - env: - LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} - GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} + echo "${{ secrets.LOCAL_PROPERTIES }}" >local.properties + echo "${{ secrets.GOOGLE_SERVICES_JSON }}" >app/google-services.json - name: Build Task run: nix --experimental-features "nix-command flakes" run .#buildRelease - env: - LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} - GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - name: Upload Release APKs uses: actions/upload-artifact@v7 diff --git a/flake.nix b/flake.nix index cdccda10..7e4f390f 100644 --- a/flake.nix +++ b/flake.nix @@ -78,17 +78,14 @@ buildRelease = { type = "app"; program = "${pkgs.writeShellScriptBin "buildRelease" '' - if [ -z "$LOCAL_PROPERTIES" ] || [ -z "$GOOGLE_SERVICES_JSON" ]; then - echo "LOCAL_PROPERTIES and GOOGLE_SERVICES_JSON environment variables must be set" - exit 1 - fi - if ! ${pkgs.git}/bin/git submodule update --init --recursive; then echo "failed getting submodules" fi - echo $LOCAL_PROPERTIES >local.properties - echo $GOOGLE_SERVICES_JSON >app/google-services.json + if ! [ -f local.properties ] || ! [ -f app/google-services.json ]; then + echo "Warning: local.properties and/or app/google-services.json aren't found," + echo "build will most likely fail" + fi export ANDROID_HOME="${androidComposition pkgs}/libexec/android-sdk/" export ANDROID_SDK_ROOT="${androidComposition pkgs}/libexec/android-sdk/" From 24e754b859baea5118c3e6d2f2ebc87f3d3dacd6 Mon Sep 17 00:00:00 2001 From: iWisp360 Date: Fri, 10 Apr 2026 01:03:50 -0400 Subject: [PATCH 06/11] add unit tests for 12h format time --- .../presentation/core/util/DateUtils.kt | 10 +++ .../presentation/core/util/DateUtilsTest.kt | 73 ++++++++++++++++--- 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt b/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt index 940d5520..5b776395 100644 --- a/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt +++ b/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt @@ -68,6 +68,16 @@ interface DateFormatManager { fun getHourMinuteFormat(): String } +class Fake12HourDateFormatManagerImpl : DateFormatManager { + override fun is24HourFormat(): Boolean = false + override fun getHourMinuteFormat(): String = "h:mm a" +} + +class Fake24HourDateFormatManagerImpl : DateFormatManager { + override fun is24HourFormat(): Boolean = true + override fun getHourMinuteFormat(): String = "HH:mm" +} + class DateFormatManagerImpl(private val context: Context) : DateFormatManager { override fun is24HourFormat(): Boolean = DateFormat.is24HourFormat(context) override fun getHourMinuteFormat(): String = if (this.is24HourFormat()) "HH:mm" else "h:mm a" diff --git a/presentation/src/test/java/org/monogram/presentation/core/util/DateUtilsTest.kt b/presentation/src/test/java/org/monogram/presentation/core/util/DateUtilsTest.kt index 1c75e3d5..eb55cafd 100644 --- a/presentation/src/test/java/org/monogram/presentation/core/util/DateUtilsTest.kt +++ b/presentation/src/test/java/org/monogram/presentation/core/util/DateUtilsTest.kt @@ -9,15 +9,17 @@ import java.util.Locale /** * Test cases for date utils **/ -class DateUtilsTest { +class DateUtils24HourTest { private val ruLocale = Locale.forLanguageTag("ru") private val dateFormatter = SimpleDateFormat("dd.MM.yyyy HH:mm", ruLocale) private val mockToday: Date = dateFormatter.parse("20.03.2024 12:00")!! + private val time24HourFormat = Fake24HourDateFormatManagerImpl().getHourMinuteFormat() @Test fun `When date are today, then returns only hours and minutes`() { + val targetDate = dateFormatter.parse("20.03.2024 15:20")!! - val result = targetDate.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday) + val result = targetDate.toShortRelativeDate(timeFormat = time24HourFormat, locale = ruLocale, now = mockToday) assertEquals("15:20", result) } @@ -26,38 +28,91 @@ class DateUtilsTest { fun `When date are yesterday and within 1 week, then returns day of week`() { // Yesterday val yesterday = dateFormatter.parse("19.03.2024 10:15")!! - assertEquals("вт", yesterday.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) + assertEquals("вт", yesterday.toShortRelativeDate(timeFormat = time24HourFormat, locale = ruLocale, now = mockToday)) // Six days ago val sixDaysAgo = dateFormatter.parse("14.03.2024 09:00")!! - assertEquals("чт", sixDaysAgo.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) + assertEquals("чт", sixDaysAgo.toShortRelativeDate(timeFormat = time24HourFormat, locale = ruLocale, now = mockToday)) } @Test fun `When date are older than 1 week but within 1 year, then return day and month`() { // 13.03.2024 - ровно 7 дней назад val sevenDaysAgo = dateFormatter.parse("13.03.2024 14:00")!! - assertEquals("13 мар", sevenDaysAgo.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) + assertEquals("13 мар", sevenDaysAgo.toShortRelativeDate(timeFormat = time24HourFormat, locale = ruLocale, now = mockToday)) // Прошлый месяц (меньше 365 дней назад) val monthsAgo = dateFormatter.parse("25.10.2023 18:30")!! - assertEquals("25 окт", monthsAgo.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) + assertEquals("25 окт", monthsAgo.toShortRelativeDate(timeFormat = time24HourFormat, locale = ruLocale, now = mockToday)) } @Test fun `When date are older than 1 year, then return full date`() { // Past year val olderThanYear = dateFormatter.parse("10.03.2023 11:11")!! - assertEquals("10.03.2023", olderThanYear.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) + assertEquals("10.03.2023", olderThanYear.toShortRelativeDate(timeFormat = time24HourFormat, locale = ruLocale, now = mockToday)) // A long time ago... val veryOldDate = dateFormatter.parse("01.01.2020 00:00")!! - assertEquals("01.01.2020", veryOldDate.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) + assertEquals("01.01.2020", veryOldDate.toShortRelativeDate(timeFormat = time24HourFormat, locale = ruLocale, now = mockToday)) } @Test fun `Then future date is older than 1 year, then return full date`() { val futureDate = dateFormatter.parse("16.03.2029 10:00")!! - assertEquals("16.03.2029", futureDate.toShortRelativeDate(timeFormat = "HH:mm", locale = ruLocale, now = mockToday)) + assertEquals("16.03.2029", futureDate.toShortRelativeDate(timeFormat = time24HourFormat, locale = ruLocale, now = mockToday)) + } +} + +class DateUtils12HourTest { + private val enLocale = Locale.forLanguageTag("en") + private val dateFormatter = SimpleDateFormat("dd.MM.yyyy h:mm a", enLocale) + private val mockToday: Date = dateFormatter.parse("20.03.2024 12:00 PM")!! + private val time12HourFormat = Fake12HourDateFormatManagerImpl().getHourMinuteFormat() + + @Test + fun `When date are today, then returns only hours and minutes`() { + + val targetDate = dateFormatter.parse("20.03.2024 3:20 PM")!! + val result = targetDate.toShortRelativeDate(timeFormat = time12HourFormat, locale = enLocale, now = mockToday) + + assertEquals("3:20 PM", result) + } + + @Test + fun `When date are yesterday and within 1 week, then returns day of week`() { + // Yesterday + val yesterday = dateFormatter.parse("19.03.2024 10:15 AM")!! + assertEquals("tue", yesterday.toShortRelativeDate(timeFormat = time12HourFormat, locale = enLocale, now = mockToday)) + + // Six days ago + val sixDaysAgo = dateFormatter.parse("14.03.2024 9:00 AM")!! + assertEquals("thu", sixDaysAgo.toShortRelativeDate(timeFormat = time12HourFormat, locale = enLocale, now = mockToday)) + } + + @Test + fun `When date are older than 1 week but within 1 year, then return day and month`() { + val sevenDaysAgo = dateFormatter.parse("13.03.2024 2:00 PM")!! + assertEquals("13 mar", sevenDaysAgo.toShortRelativeDate(timeFormat = time12HourFormat, locale = enLocale, now = mockToday)) + + val monthsAgo = dateFormatter.parse("25.10.2023 6:30 PM")!! + assertEquals("25 oct", monthsAgo.toShortRelativeDate(timeFormat = time12HourFormat, locale = enLocale, now = mockToday)) + } + + @Test + fun `When date are older than 1 year, then return full date`() { + // Past year + val olderThanYear = dateFormatter.parse("10.03.2023 11:11 AM")!! + assertEquals("10.03.2023", olderThanYear.toShortRelativeDate(timeFormat = time12HourFormat, locale = enLocale, now = mockToday)) + + // A long time ago... + val veryOldDate = dateFormatter.parse("01.01.2020 12:00 AM")!! + assertEquals("01.01.2020", veryOldDate.toShortRelativeDate(timeFormat = time12HourFormat, locale = enLocale, now = mockToday)) + } + + @Test + fun `Then future date is older than 1 year, then return full date`() { + val futureDate = dateFormatter.parse("16.03.2029 10:00 AM")!! + assertEquals("16.03.2029", futureDate.toShortRelativeDate(timeFormat = time12HourFormat, locale = enLocale, now = mockToday)) } } From 8f0f26f651d3c22c4ccad0aa07c1ec307ec198bf Mon Sep 17 00:00:00 2001 From: iWisp360 Date: Fri, 10 Apr 2026 01:26:59 -0400 Subject: [PATCH 07/11] post nix drv build logs and use better approach for writing secrets to files --- .github/workflows/nix-android.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nix-android.yml b/.github/workflows/nix-android.yml index 05c6c537..64b70af0 100644 --- a/.github/workflows/nix-android.yml +++ b/.github/workflows/nix-android.yml @@ -16,11 +16,16 @@ jobs: - name: Add required files run: | - echo "${{ secrets.LOCAL_PROPERTIES }}" >local.properties - echo "${{ secrets.GOOGLE_SERVICES_JSON }}" >app/google-services.json + cat <<"EOF" >local.properties + ${{ secrets.LOCAL_PROPERTIES }} + EOF + + cat <<"EOF" >app/google-services.json + "${{ secrets.GOOGLE_SERVICES_JSON }}" + EOF - name: Build Task - run: nix --experimental-features "nix-command flakes" run .#buildRelease + run: nix --experimental-features "nix-command flakes" run .#buildRelease -L - name: Upload Release APKs uses: actions/upload-artifact@v7 From 007c82f6f24cf9cf64eecfe11c88ac8d677bb4a8 Mon Sep 17 00:00:00 2001 From: iWisp360 Date: Fri, 10 Apr 2026 01:34:32 -0400 Subject: [PATCH 08/11] remove unnecessary stuff in flake --- flake.nix | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/flake.nix b/flake.nix index 7e4f390f..763a000a 100644 --- a/flake.nix +++ b/flake.nix @@ -16,28 +16,15 @@ { pkgs, system, ... }: let actualBuildTools = "36.0.0"; + actualPlatform = "36"; + actualNdk = "28.2.13676358"; androidComposition = pkgs: (pkgs.androidenv.composeAndroidPackages { - platformVersions = [ - "35" - "36" - ]; - - buildToolsVersions = [ - "35.0.0" - actualBuildTools - ]; - - cmakeVersions = [ - "3.22.1" - ]; - - ndkVersions = [ - "27.0.12077973" - "28.2.13676358" - ]; - + platformVersions = [ actualPlatform ]; + buildToolsVersions = [ actualBuildTools ]; + ndkVersions = [ actualNdk ]; + cmakeVersions = [ "3.22.1" ]; abiVersions = [ "armeabi-v7a" "arm64-v8a" @@ -89,7 +76,7 @@ export ANDROID_HOME="${androidComposition pkgs}/libexec/android-sdk/" export ANDROID_SDK_ROOT="${androidComposition pkgs}/libexec/android-sdk/" - export ANDROID_NDK_HOME="$ANDROID_HOME/ndk-bundle/" + export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/${actualNdk}/" export GRADLE_OPTS="-Dorg.gradle.project.android.aapt2FromMavenOverride=${androidComposition pkgs}/libexec/android-sdk/build-tools/${actualBuildTools}/aapt2" export JAVA_HOME="${pkgs.jdk21}" From 189379144e926eb6e52d9144f917f0c7d6eccf64 Mon Sep 17 00:00:00 2001 From: iWisp360 Date: Fri, 10 Apr 2026 05:32:26 -0400 Subject: [PATCH 09/11] don't quote when adding secret and avoid collision --- .github/workflows/nix-android.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/nix-android.yml b/.github/workflows/nix-android.yml index 64b70af0..fc650d4a 100644 --- a/.github/workflows/nix-android.yml +++ b/.github/workflows/nix-android.yml @@ -16,13 +16,13 @@ jobs: - name: Add required files run: | - cat <<"EOF" >local.properties + cat <<"EOF-FILE" >local.properties ${{ secrets.LOCAL_PROPERTIES }} - EOF + EOF-FILE - cat <<"EOF" >app/google-services.json - "${{ secrets.GOOGLE_SERVICES_JSON }}" - EOF + cat <<"EOF-FILE" >app/google-services.json + ${{ secrets.GOOGLE_SERVICES_JSON }} + EOF-FILE - name: Build Task run: nix --experimental-features "nix-command flakes" run .#buildRelease -L From f804b5a9953a420b1e0b3b01b57ff1b41274c9fa Mon Sep 17 00:00:00 2001 From: iWisp360 Date: Fri, 10 Apr 2026 10:20:46 -0400 Subject: [PATCH 10/11] exclude apk release workflow from this branch --- .github/workflows/nix-android.yml | 35 ------- flake.nix | 151 +++++++++++++----------------- 2 files changed, 63 insertions(+), 123 deletions(-) delete mode 100644 .github/workflows/nix-android.yml diff --git a/.github/workflows/nix-android.yml b/.github/workflows/nix-android.yml deleted file mode 100644 index fc650d4a..00000000 --- a/.github/workflows/nix-android.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Build APKs for Android using Nix - -on: - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - submodules: 'recursive' - - - uses: cachix/install-nix-action@v31 - - - name: Add required files - run: | - cat <<"EOF-FILE" >local.properties - ${{ secrets.LOCAL_PROPERTIES }} - EOF-FILE - - cat <<"EOF-FILE" >app/google-services.json - ${{ secrets.GOOGLE_SERVICES_JSON }} - EOF-FILE - - - name: Build Task - run: nix --experimental-features "nix-command flakes" run .#buildRelease -L - - - name: Upload Release APKs - uses: actions/upload-artifact@v7 - with: - name: Release APKs - path: app/build/outputs/apk/release/*.apk - diff --git a/flake.nix b/flake.nix index 763a000a..1d309e82 100644 --- a/flake.nix +++ b/flake.nix @@ -1,103 +1,78 @@ { - description = "Native Telegram client for Android based on TDLib."; + description = "Native Telegram client for Android based on TDLib."; - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - flake-parts.url = "github:hercules-ci/flake-parts"; - systems.url = "github:nix-systems/default"; - }; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + systems.url = "github:nix-systems/default"; + }; - outputs = - inputs: - inputs.flake-parts.lib.mkFlake { inherit inputs; } { - systems = import inputs.systems; + outputs = + inputs: + inputs.flake-parts.lib.mkFlake { inherit inputs; } { + systems = import inputs.systems; - perSystem = - { pkgs, system, ... }: - let - actualBuildTools = "36.0.0"; - actualPlatform = "36"; - actualNdk = "28.2.13676358"; - androidComposition = - pkgs: - (pkgs.androidenv.composeAndroidPackages { - platformVersions = [ actualPlatform ]; - buildToolsVersions = [ actualBuildTools ]; - ndkVersions = [ actualNdk ]; - cmakeVersions = [ "3.22.1" ]; - abiVersions = [ - "armeabi-v7a" - "arm64-v8a" - "x86_64" - ]; + perSystem = + { pkgs, system, ... }: + let + androidComposition = + pkgs: + (pkgs.androidenv.composeAndroidPackages { + platformVersions = [ + "35" + "36" + ]; - systemImageTypes = [ "google_apis_playstore" ]; - includeNDK = true; - includeCmake = true; - includeSources = true; - includeExtras = [ "extras;google;auto" ]; - }).androidsdk; - in - { - _module.args.pkgs = import inputs.nixpkgs { - inherit system; - config = { - android_sdk.accept_license = true; - allowUnfree = true; - }; - }; + buildToolsVersions = [ + "35.0.0" + ]; - devShells.default = pkgs.mkShell { - name = "monogram-sdk"; + cmakeVersions = [ + "3.22.1" + ]; - nativeBuildInputs = [ - (androidComposition pkgs) - pkgs.glibc - pkgs.jdk21 - pkgs.android-studio - ]; + ndkVersions = [ + "27.0.12077973" + ]; - JAVA_HOME = "${pkgs.jdk21}"; - ANDROID_HOME = "${androidComposition pkgs}/libexec/android-sdk"; - }; + abiVersions = [ + "armeabi-v7a" + "arm64-v8a" + "x86_64" + ]; - apps = { - buildRelease = { - type = "app"; - program = "${pkgs.writeShellScriptBin "buildRelease" '' - if ! ${pkgs.git}/bin/git submodule update --init --recursive; then - echo "failed getting submodules" - fi + systemImageTypes = [ "google_apis_playstore" ]; + includeNDK = true; + includeCmake = true; + includeExtras = [ "extras;google;auto" ]; + }).androidsdk; + in + { + _module.args.pkgs = import inputs.nixpkgs { + inherit system; + config = { + android_sdk.accept_license = true; + allowUnfree = true; + }; + }; - if ! [ -f local.properties ] || ! [ -f app/google-services.json ]; then - echo "Warning: local.properties and/or app/google-services.json aren't found," - echo "build will most likely fail" - fi + devShells.default = + (pkgs.buildFHSEnv { + name = "monogram-sdk-env"; - export ANDROID_HOME="${androidComposition pkgs}/libexec/android-sdk/" - export ANDROID_SDK_ROOT="${androidComposition pkgs}/libexec/android-sdk/" - export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/${actualNdk}/" - export GRADLE_OPTS="-Dorg.gradle.project.android.aapt2FromMavenOverride=${androidComposition pkgs}/libexec/android-sdk/build-tools/${actualBuildTools}/aapt2" - export JAVA_HOME="${pkgs.jdk21}" + targetPkgs = pkgs: [ + (androidComposition pkgs) + pkgs.glibc + pkgs.jdk21 + ]; - cd presentation/src/main/cpp/ - if ! [ -d libvpx_build ]; then - sed -e "s|#!/bin/bash|#!${pkgs.bash}/bin/bash|g" \ - -e "s|make clean|${pkgs.gnumake}/bin/make clean|g" \ - -e "s|make -j|${pkgs.gnumake}/bin/make -j|g" build.sh >new_build.sh - chmod +x new_build.sh - if ! ${pkgs.bash}/bin/bash new_build.sh; then - echo "libvpx build failed" - exit 1 - fi - rm new_build.sh - fi + profile = '' + export JAVA_HOME="/usr/lib/openjdk" + export ANDROID_HOME="/usr/libexec/android-sdk" + ''; - cd ../../../.. - ./gradlew assembleRelease - ''}/bin/buildRelease"; - }; - }; + runScript = "bash"; + }).env; + }; }; - }; } From b76aa127d6fa86130f53178984c21e9154932a24 Mon Sep 17 00:00:00 2001 From: Artur Skubei <41114720+gdlbo@users.noreply.github.com> Date: Fri, 10 Apr 2026 17:48:26 +0300 Subject: [PATCH 11/11] support configurable 12/24-hour time formats in chat and user status --- .../java/org/monogram/app/di/AppModule.kt | 27 ++++++-- .../monogram/core/date/DateFormatManager.kt | 23 +++++++ .../java/org/monogram/data/di/dataModule.kt | 2 +- .../org/monogram/data/mapper/ChatMapper.kt | 19 ++++-- .../data/mapper/UserStatusFormatter.kt | 15 ++++- .../monogram/data/mapper/ChatMapperTest.kt | 61 +++++++++++++++++++ .../presentation/core/util/DateUtils.kt | 29 +++------ 7 files changed, 143 insertions(+), 33 deletions(-) create mode 100644 core/src/main/kotlin/org/monogram/core/date/DateFormatManager.kt create mode 100644 data/src/test/java/org/monogram/data/mapper/ChatMapperTest.kt diff --git a/app/src/main/java/org/monogram/app/di/AppModule.kt b/app/src/main/java/org/monogram/app/di/AppModule.kt index 7c4b0846..cf75fc9b 100644 --- a/app/src/main/java/org/monogram/app/di/AppModule.kt +++ b/app/src/main/java/org/monogram/app/di/AppModule.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.ClipboardManager import android.content.Context import android.telephony.TelephonyManager +import android.text.format.DateFormat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -11,9 +12,27 @@ import org.koin.android.ext.koin.androidContext import org.koin.dsl.module import org.monogram.core.Logger import org.monogram.data.di.dataModule -import org.monogram.domain.managers.* -import org.monogram.domain.repository.* -import org.monogram.presentation.core.util.* +import org.monogram.domain.managers.AssetsManager +import org.monogram.domain.managers.ClipManager +import org.monogram.domain.managers.DistrManager +import org.monogram.domain.managers.DomainManager +import org.monogram.domain.managers.PhoneManager +import org.monogram.domain.repository.AppPreferencesProvider +import org.monogram.domain.repository.BotPreferencesProvider +import org.monogram.domain.repository.CacheProvider +import org.monogram.domain.repository.EditorSnippetProvider +import org.monogram.domain.repository.ExternalNavigator +import org.monogram.domain.repository.MessageDisplayer +import org.monogram.presentation.core.util.AppPreferences +import org.monogram.presentation.core.util.BotPreferences +import org.monogram.presentation.core.util.CachePreferences +import org.monogram.presentation.core.util.DateFormatManager +import org.monogram.presentation.core.util.DateFormatManagerImpl +import org.monogram.presentation.core.util.DownloadUtils +import org.monogram.presentation.core.util.EditorSnippetPreferences +import org.monogram.presentation.core.util.ExternalNavigatorImpl +import org.monogram.presentation.core.util.IDownloadUtils +import org.monogram.presentation.core.util.ToastMessageDisplayer import org.monogram.presentation.di.uiModule import org.monogram.presentation.features.chats.currentChat.components.ExoPlayerCache import org.monogram.presentation.features.chats.currentChat.components.VideoPlayerPool @@ -41,7 +60,7 @@ val appModule = module { } single { LoggerImpl() } - single { DateFormatManagerImpl(androidContext()) } + single { DateFormatManagerImpl(DateFormat.is24HourFormat(androidContext())) } factory { PhoneManagerImpl( diff --git a/core/src/main/kotlin/org/monogram/core/date/DateFormatManager.kt b/core/src/main/kotlin/org/monogram/core/date/DateFormatManager.kt new file mode 100644 index 00000000..4e2aeadd --- /dev/null +++ b/core/src/main/kotlin/org/monogram/core/date/DateFormatManager.kt @@ -0,0 +1,23 @@ +package org.monogram.core.date + +interface DateFormatManager { + fun is24HourFormat(): Boolean + fun getHourMinuteFormat(): String +} + +class DateFormatManagerImpl( + private val use24HourFormat: Boolean +) : DateFormatManager { + override fun is24HourFormat(): Boolean = use24HourFormat + override fun getHourMinuteFormat(): String = if (use24HourFormat) "HH:mm" else "h:mm a" +} + +class Fake12HourDateFormatManagerImpl : DateFormatManager { + override fun is24HourFormat(): Boolean = false + override fun getHourMinuteFormat(): String = "h:mm a" +} + +class Fake24HourDateFormatManagerImpl : DateFormatManager { + override fun is24HourFormat(): Boolean = true + override fun getHourMinuteFormat(): String = "HH:mm" +} diff --git a/data/src/main/java/org/monogram/data/di/dataModule.kt b/data/src/main/java/org/monogram/data/di/dataModule.kt index 7ff39758..7ec83b2f 100644 --- a/data/src/main/java/org/monogram/data/di/dataModule.kt +++ b/data/src/main/java/org/monogram/data/di/dataModule.kt @@ -389,7 +389,7 @@ val dataModule = module { } single { - ChatMapper(get()) + ChatMapper(get(), get()) } single { diff --git a/data/src/main/java/org/monogram/data/mapper/ChatMapper.kt b/data/src/main/java/org/monogram/data/mapper/ChatMapper.kt index acea7b6e..035d5afc 100644 --- a/data/src/main/java/org/monogram/data/mapper/ChatMapper.kt +++ b/data/src/main/java/org/monogram/data/mapper/ChatMapper.kt @@ -1,13 +1,22 @@ package org.monogram.data.mapper import org.drinkless.tdlib.TdApi +import org.monogram.core.date.DateFormatManager import org.monogram.data.db.model.ChatEntity -import org.monogram.domain.models.* +import org.monogram.domain.models.ChatModel +import org.monogram.domain.models.ChatType +import org.monogram.domain.models.MessageEntity +import org.monogram.domain.models.MessageEntityType +import org.monogram.domain.models.UsernamesModel import org.monogram.domain.repository.StringProvider import java.text.SimpleDateFormat -import java.util.* +import java.util.Date +import java.util.Locale -class ChatMapper(private val stringProvider: StringProvider) { +class ChatMapper( + private val stringProvider: StringProvider, + private val dateFormatManager: DateFormatManager +) { fun mapChatToModel( chat: TdApi.Chat, order: Long, @@ -431,7 +440,8 @@ class ChatMapper(private val stringProvider: StringProvider) { } val date = lastMsg.date - val timeFormat = SimpleDateFormat("HH:mm", Locale.getDefault()) + val timeFormat = + SimpleDateFormat(dateFormatManager.getHourMinuteFormat(), Locale.getDefault()) val time = if (date > 0) timeFormat.format(Date(date.toLong() * 1000)) else "" return Triple(txt, entities, time) } @@ -455,6 +465,7 @@ class ChatMapper(private val stringProvider: StringProvider) { return formatChatUserStatus( status = status, stringProvider = stringProvider, + dateFormatManager = dateFormatManager, isBot = isBot ) } diff --git a/data/src/main/java/org/monogram/data/mapper/UserStatusFormatter.kt b/data/src/main/java/org/monogram/data/mapper/UserStatusFormatter.kt index 5fd50ab0..fd101f1d 100644 --- a/data/src/main/java/org/monogram/data/mapper/UserStatusFormatter.kt +++ b/data/src/main/java/org/monogram/data/mapper/UserStatusFormatter.kt @@ -2,13 +2,16 @@ package org.monogram.data.mapper import android.text.format.DateUtils import org.drinkless.tdlib.TdApi +import org.monogram.core.date.DateFormatManager import org.monogram.domain.repository.StringProvider import java.text.SimpleDateFormat -import java.util.* +import java.util.Date +import java.util.Locale internal fun formatChatUserStatus( status: TdApi.UserStatus, stringProvider: StringProvider, + dateFormatManager: DateFormatManager, isBot: Boolean = false ): String { if (isBot) return stringProvider.getString("chat_mapper_bot") @@ -29,13 +32,19 @@ internal fun formatChatUserStatus( DateUtils.isToday(wasOnline) -> { val date = Date(wasOnline) - val format = SimpleDateFormat("HH:mm", Locale.getDefault()) + val format = SimpleDateFormat( + dateFormatManager.getHourMinuteFormat(), + Locale.getDefault() + ) stringProvider.getString("chat_mapper_seen_at", format.format(date)) } isYesterday(wasOnline) -> { val date = Date(wasOnline) - val format = SimpleDateFormat("HH:mm", Locale.getDefault()) + val format = SimpleDateFormat( + dateFormatManager.getHourMinuteFormat(), + Locale.getDefault() + ) stringProvider.getString("chat_mapper_seen_yesterday", format.format(date)) } diff --git a/data/src/test/java/org/monogram/data/mapper/ChatMapperTest.kt b/data/src/test/java/org/monogram/data/mapper/ChatMapperTest.kt new file mode 100644 index 00000000..fd3fcec8 --- /dev/null +++ b/data/src/test/java/org/monogram/data/mapper/ChatMapperTest.kt @@ -0,0 +1,61 @@ +package org.monogram.data.mapper + +import org.drinkless.tdlib.TdApi +import org.junit.Assert.assertEquals +import org.junit.Test +import org.monogram.core.date.Fake12HourDateFormatManagerImpl +import org.monogram.core.date.Fake24HourDateFormatManagerImpl +import org.monogram.domain.repository.StringProvider +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class ChatMapperTest { + + @Test + fun `formatMessageInfo uses 12 hour time format`() { + val mapper = ChatMapper(FakeStringProvider(), Fake12HourDateFormatManagerImpl()) + val timestampSeconds = 1710948000 + val message = createTextMessage(timestampSeconds) + + val (_, _, time) = mapper.formatMessageInfo(message, null) { null } + + val expected = + SimpleDateFormat("h:mm a", Locale.getDefault()).format(Date(timestampSeconds * 1000L)) + assertEquals(expected, time) + } + + @Test + fun `formatMessageInfo uses 24 hour time format`() { + val mapper = ChatMapper(FakeStringProvider(), Fake24HourDateFormatManagerImpl()) + val timestampSeconds = 1710948000 + val message = createTextMessage(timestampSeconds) + + val (_, _, time) = mapper.formatMessageInfo(message, null) { null } + + val expected = + SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date(timestampSeconds * 1000L)) + assertEquals(expected, time) + } + + private fun createTextMessage(timestampSeconds: Int): TdApi.Message { + return TdApi.Message().apply { + date = timestampSeconds + content = TdApi.MessageText().apply { + text = TdApi.FormattedText("test", emptyArray()) + } + } + } + + private class FakeStringProvider : StringProvider { + override fun getString(resName: String): String = resName + + override fun getString(resName: String, vararg formatArgs: Any): String = resName + + override fun getQuantityString( + resName: String, + quantity: Int, + vararg formatArgs: Any + ): String = resName + } +} diff --git a/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt b/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt index 5b776395..38f215f6 100644 --- a/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt +++ b/presentation/src/main/java/org/monogram/presentation/core/util/DateUtils.kt @@ -1,13 +1,15 @@ package org.monogram.presentation.core.util -import android.content.Context -import android.text.format.DateFormat import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date import java.util.Locale import kotlin.math.abs import kotlin.math.roundToLong +import org.monogram.core.date.DateFormatManager as CoreDateFormatManager +import org.monogram.core.date.DateFormatManagerImpl as CoreDateFormatManagerImpl +import org.monogram.core.date.Fake12HourDateFormatManagerImpl as CoreFake12HourDateFormatManagerImpl +import org.monogram.core.date.Fake24HourDateFormatManagerImpl as CoreFake24HourDateFormatManagerImpl /** * Formats date as relative string as for day, week, year, periods and so-on @@ -63,22 +65,7 @@ fun Date.toShortRelativeDate( } } -interface DateFormatManager { - fun is24HourFormat(): Boolean - fun getHourMinuteFormat(): String -} - -class Fake12HourDateFormatManagerImpl : DateFormatManager { - override fun is24HourFormat(): Boolean = false - override fun getHourMinuteFormat(): String = "h:mm a" -} - -class Fake24HourDateFormatManagerImpl : DateFormatManager { - override fun is24HourFormat(): Boolean = true - override fun getHourMinuteFormat(): String = "HH:mm" -} - -class DateFormatManagerImpl(private val context: Context) : DateFormatManager { - override fun is24HourFormat(): Boolean = DateFormat.is24HourFormat(context) - override fun getHourMinuteFormat(): String = if (this.is24HourFormat()) "HH:mm" else "h:mm a" -} \ No newline at end of file +typealias DateFormatManager = CoreDateFormatManager +typealias DateFormatManagerImpl = CoreDateFormatManagerImpl +typealias Fake12HourDateFormatManagerImpl = CoreFake12HourDateFormatManagerImpl +typealias Fake24HourDateFormatManagerImpl = CoreFake24HourDateFormatManagerImpl