android, desktop: add CF page (#7373)
* android, desktop: add CF page * android, desktop: update CF page * android, desktop: sync ios changes * android, desktop: sync ios CF page changes * remove utm_source --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
@@ -31,7 +31,8 @@ fun AppBarTitle(
|
||||
val connection = if (enableAlphaChanges) handler?.connection else null
|
||||
LaunchedEffect(title) {
|
||||
if (enableAlphaChanges) {
|
||||
handler?.title?.value = title
|
||||
// the app bar shows a single line, so the line breaks of the large title are replaced with spaces
|
||||
handler?.title?.value = title.replace("\n", " ")
|
||||
} else {
|
||||
handler?.connection?.scrollTrackingEnabled = false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package chat.simplex.common.views.onboarding
|
||||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.gestures.calculatePan
|
||||
import androidx.compose.foundation.gestures.calculateZoom
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
@@ -8,15 +12,29 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.PointerEventPass
|
||||
import androidx.compose.ui.input.pointer.PointerIcon
|
||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withLink
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.BuildConfigCommon
|
||||
@@ -35,6 +53,7 @@ import chat.simplex.common.views.usersettings.showAddShortLinkAlert
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.ImageResource
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@Composable
|
||||
fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Boolean = false, close: () -> Unit) {
|
||||
@@ -914,14 +933,15 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
)
|
||||
),
|
||||
VersionDescription(
|
||||
version = "v7.0",
|
||||
// the trailing space differs from the previously released "v7.0", so that What's new is shown again
|
||||
version = "v7.0 ",
|
||||
post = null,
|
||||
features = listOf(
|
||||
// VersionFeature.FeatureView(
|
||||
// icon = null,
|
||||
// titleId = MR.strings.v7_0_invest,
|
||||
// view = { _ -> InvestInSimpleXChatView() }
|
||||
// ),
|
||||
VersionFeature.FeatureView(
|
||||
icon = null,
|
||||
titleId = MR.strings.v7_0_invest,
|
||||
view = { modalManager -> InvestInSimpleXChatView(modalManager) }
|
||||
),
|
||||
VersionFeature.FeatureDescription(
|
||||
icon = MR.images.ic_alternate_email,
|
||||
titleId = MR.strings.v7_0_simplex_names,
|
||||
@@ -956,57 +976,253 @@ fun shouldShowWhatsNew(m: ChatModel): Boolean {
|
||||
return v != lastVersion
|
||||
}
|
||||
|
||||
// private const val WEFUNDER_URL = "https://wefunder.com/simplexchat"
|
||||
//
|
||||
// @Composable
|
||||
// private fun InvestInSimpleXChatView() {
|
||||
// if (platform.androidIsPlayStoreBuild) {
|
||||
// LaunchedEffect(Unit) { if (androidPlayStoreCountry.value == null) platform.androidLoadPlayStoreCountry() }
|
||||
// if (androidPlayStoreCountry.value != "US") return
|
||||
// }
|
||||
// val uriHandler = LocalUriHandler.current
|
||||
// Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(bottom = 12.dp)) {
|
||||
// Column(modifier = Modifier.weight(1f)) {
|
||||
// Row(
|
||||
// verticalAlignment = Alignment.CenterVertically,
|
||||
// horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
// modifier = Modifier.padding(bottom = 4.dp)
|
||||
// ) {
|
||||
// Icon(painterResource(MR.images.ic_redeem), stringResource(MR.strings.v7_0_invest), tint = MaterialTheme.colors.secondary)
|
||||
// Text(
|
||||
// generalGetString(MR.strings.v7_0_invest),
|
||||
// maxLines = 2,
|
||||
// overflow = TextOverflow.Ellipsis,
|
||||
// style = MaterialTheme.typography.h4,
|
||||
// fontWeight = FontWeight.Medium,
|
||||
// modifier = Modifier.padding(bottom = 6.dp)
|
||||
// )
|
||||
// }
|
||||
// Text(generalGetString(MR.strings.v7_0_invest_descr), fontSize = 15.sp, modifier = Modifier.padding(bottom = 4.dp))
|
||||
// Row(
|
||||
// verticalAlignment = Alignment.CenterVertically,
|
||||
// horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
// modifier = Modifier
|
||||
// .clickable(
|
||||
// interactionSource = remember { MutableInteractionSource() },
|
||||
// indication = null
|
||||
// ) {
|
||||
// uriHandler.openExternalLink(WEFUNDER_URL)
|
||||
// }
|
||||
// ) {
|
||||
// Text(stringResource(MR.strings.v7_0_invest_learn_more), color = MaterialTheme.colors.primary, fontSize = 15.sp)
|
||||
// Icon(painterResource(MR.images.ic_open_in_new), stringResource(MR.strings.v7_0_invest_learn_more), tint = MaterialTheme.colors.primary)
|
||||
// }
|
||||
// }
|
||||
// if (BuildConfigCommon.SIMPLEX_ASSETS) {
|
||||
// Image(
|
||||
// painterResource(if (isInDarkTheme()) MR.images.own_stake_light else MR.images.own_stake),
|
||||
// contentDescription = null,
|
||||
// modifier = Modifier.width(80.dp)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
private const val WEFUNDER_URL = "https://wefunder.com/simplex.chat"
|
||||
|
||||
private const val CROWDFUNDING_CONTACT_URI = "simplex:/a#JxGcOA1_QhlmVFzYYabloMbvMZk5Y9d9iS3ITDnhzYo?h=smp11.simplex.im"
|
||||
|
||||
// the center modal takes the remaining width of the window, so the image is limited to its design width
|
||||
private val MAX_CROWDFUNDING_IMAGE_WIDTH = DEFAULT_MIN_CENTER_MODAL_WIDTH
|
||||
|
||||
// Google Play policy restricts promoting investments, so Play builds only show it in the US
|
||||
@Composable
|
||||
fun crowdfundingAvailable(): Boolean {
|
||||
if (!platform.androidIsPlayStoreBuild) return true
|
||||
LaunchedEffect(Unit) {
|
||||
if (androidPlayStoreCountry.value == null) platform.androidLoadPlayStoreCountry()
|
||||
}
|
||||
return androidPlayStoreCountry.value == "US"
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InvestInSimpleXChatView(modalManager: ModalManager) {
|
||||
if (!crowdfundingAvailable()) return
|
||||
val showGetStake = { modalManager.showModalCloseable(cardScreen = true) { close -> GetStakeView(fromSettings = false, close = close) } }
|
||||
Column(modifier = Modifier.padding(bottom = 12.dp)) {
|
||||
Text(
|
||||
generalGetString(MR.strings.v7_0_invest),
|
||||
style = MaterialTheme.typography.h4,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(bottom = 6.dp)
|
||||
)
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
append(generalGetString(MR.strings.v7_0_invest_descr))
|
||||
append(" ")
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.primary)) {
|
||||
append(generalGetString(MR.strings.learn_more))
|
||||
}
|
||||
},
|
||||
fontSize = 15.sp,
|
||||
modifier = Modifier
|
||||
.pointerHoverIcon(PointerIcon.Hand)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = showGetStake
|
||||
)
|
||||
)
|
||||
if (BuildConfigCommon.SIMPLEX_ASSETS) {
|
||||
Image(
|
||||
painterResource(MR.images.crowdfunding_1),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.widthIn(max = MAX_CROWDFUNDING_IMAGE_WIDTH)
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.pointerHoverIcon(PointerIcon.Hand)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = showGetStake
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CrowdfundingSlide(
|
||||
val image: ImageResource,
|
||||
val heading: String,
|
||||
val info: String?,
|
||||
val text: String,
|
||||
)
|
||||
|
||||
// not localized: the page is only shown to US investors, and the text duplicates the images
|
||||
private val getStakeSlides: List<CrowdfundingSlide> = listOf(
|
||||
CrowdfundingSlide(
|
||||
MR.images.crowdfunding_1,
|
||||
"The first and the only messaging network without any user IDs",
|
||||
null,
|
||||
"By investing, you can benefit from the company growth, and help us build the future of private and secure communications."
|
||||
),
|
||||
CrowdfundingSlide(
|
||||
MR.images.crowdfunding_2,
|
||||
"480,000+ users joined on their own",
|
||||
null,
|
||||
"SimpleX users have been more than doubling every year without any paid marketing, and donated over \$650,000."
|
||||
),
|
||||
CrowdfundingSlide(
|
||||
MR.images.crowdfunding_3,
|
||||
"Developers already bet on SimpleX success",
|
||||
"Independent developers created moderation and AI bots, Telegram bridges, and a public server registry.",
|
||||
"Every service developers build on SimpleX Network may increase its value, and bring new users to SimpleX Chat."
|
||||
),
|
||||
CrowdfundingSlide(
|
||||
MR.images.crowdfunding_4,
|
||||
"Revenue plan: free for users, channels & businesses pay",
|
||||
"SimpleX Chat plans to earn from the infrastructure and services that creators, businesses and large communities need as they grow.",
|
||||
"Read about how we plan to make SimpleX Chat and network profitable, and about all the investment terms on Wefunder."
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun GetStakeView(fromSettings: Boolean, close: () -> Unit) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val stopped = chatModel.chatRunning.value == false
|
||||
|
||||
@Composable
|
||||
fun slideImage(slide: CrowdfundingSlide) {
|
||||
if (BuildConfigCommon.SIMPLEX_ASSETS) {
|
||||
Image(
|
||||
painterResource(slide.image),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(12.dp)).fullScreenOnClick(slide.image)
|
||||
)
|
||||
} else {
|
||||
Text(slide.heading, style = MaterialTheme.typography.h4, fontWeight = FontWeight.Medium)
|
||||
if (slide.info != null) {
|
||||
Text(slide.info, Modifier.padding(top = 4.dp), lineHeight = 24.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnWithScrollBar(Modifier.pinchZoom().padding(horizontal = DEFAULT_PADDING)) {
|
||||
AppBarTitle("Get a stake in\nSimpleX Chat", withPadding = false)
|
||||
// What's new already shows the image of the first slide, above the link that opens this page
|
||||
if (fromSettings) {
|
||||
slideImage(getStakeSlides[0])
|
||||
}
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
append(getStakeSlides[0].text)
|
||||
// only the link is clickable, the rest of the paragraph is not
|
||||
withLink(LinkAnnotation.Url(WEFUNDER_URL) { uriHandler.openUriCatching(WEFUNDER_URL) }) {
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.primary, fontWeight = FontWeight.Bold)) {
|
||||
append(" Learn more and invest on Wefunder.")
|
||||
}
|
||||
}
|
||||
},
|
||||
Modifier.padding(top = if (fromSettings) 8.dp else 0.dp),
|
||||
lineHeight = 24.sp
|
||||
)
|
||||
|
||||
getStakeSlides.drop(1).forEach { slide ->
|
||||
Column(Modifier.padding(top = DEFAULT_PADDING * 1.5f)) {
|
||||
slideImage(slide)
|
||||
Text(slide.text, Modifier.padding(top = 8.dp), lineHeight = 24.sp)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
Modifier.fillMaxWidth().padding(top = DEFAULT_PADDING * 2),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
OnboardingActionButton(
|
||||
if (appPlatform.isAndroid) Modifier.fillMaxWidth() else Modifier.widthIn(min = 300.dp),
|
||||
labelId = MR.strings.v7_0_invest_learn_more,
|
||||
onboarding = null,
|
||||
onclick = { uriHandler.openUriCatching(WEFUNDER_URL) }
|
||||
)
|
||||
if (!chatModel.desktopNoUserNoRemote) {
|
||||
TextButtonBelowOnboardingButton(
|
||||
"or ask SimpleX team",
|
||||
onClick = if (stopped) null else ({
|
||||
close()
|
||||
uriHandler.openVerifiedSimplexUri(CROWDFUNDING_CONTACT_URI)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there is no pinch gesture with a mouse, so on desktop a slide is opened full screen instead
|
||||
@Composable
|
||||
private fun Modifier.fullScreenOnClick(image: ImageResource): Modifier {
|
||||
if (!appPlatform.isDesktop) return this
|
||||
return pointerHoverIcon(PointerIcon.Hand).clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null
|
||||
) {
|
||||
ModalManager.fullscreen.showCustomModal { close ->
|
||||
BackHandler(onBack = close)
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black)
|
||||
.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = close),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(painterResource(image), contentDescription = null, contentScale = ContentScale.Fit, modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val MAX_PAGE_ZOOM = 5f
|
||||
|
||||
/**
|
||||
* The slide images contain small text that is unreadable at screen width, so the page can be pinch-zoomed.
|
||||
* Android only: pinch is unavailable with a mouse.
|
||||
*/
|
||||
@Composable
|
||||
private fun Modifier.pinchZoom(): Modifier {
|
||||
if (!appPlatform.isAndroid) return this
|
||||
var scale by remember { mutableStateOf(1f) }
|
||||
var offsetX by remember { mutableStateOf(0f) }
|
||||
var offsetY by remember { mutableStateOf(0f) }
|
||||
var size by remember { mutableStateOf(IntSize.Zero) }
|
||||
return this
|
||||
.onGloballyPositioned { size = it.size }
|
||||
.graphicsLayer {
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
translationX = offsetX
|
||||
translationY = offsetY
|
||||
}
|
||||
.pointerInput(Unit) {
|
||||
awaitEachGesture {
|
||||
// the initial pass, as the scroll of the same column is applied after this modifier and would take the gesture first
|
||||
awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
|
||||
var taken: Boolean? = null
|
||||
do {
|
||||
val event = awaitPointerEvent(PointerEventPass.Initial)
|
||||
val multiTouch = event.changes.count { it.pressed } > 1
|
||||
if (multiTouch || scale > 1f) {
|
||||
scale = (scale * event.calculateZoom()).coerceIn(1f, MAX_PAGE_ZOOM)
|
||||
val pan = event.calculatePan()
|
||||
// the page is scaled around its center, so it can be panned by half of the overflow in each direction
|
||||
val maxX = size.width * (scale - 1f) / 2
|
||||
val maxY = size.height * (scale - 1f) / 2
|
||||
val pannedY = offsetY + pan.y * scale
|
||||
// the clamp is applied even when the gesture is not taken: at scale 1 both bounds
|
||||
// are 0, which resets the offsets after zooming back out
|
||||
offsetX = (offsetX + pan.x * scale).coerceIn(-maxX, maxX)
|
||||
offsetY = pannedY.coerceIn(-maxY, maxY)
|
||||
// two fingers always mean zoom, taken without a touch slop: waiting for one would let
|
||||
// the scroll reach its own slop first and scroll the page. A one finger drag is left
|
||||
// to the scroll at the edges, decided once so it cannot alternate mid drag
|
||||
if (multiTouch) taken = true
|
||||
else if (taken == null && pan.y != 0f) taken = pannedY.absoluteValue < maxY
|
||||
if (taken == true) event.changes.forEach { if (it.pressed) it.consume() }
|
||||
}
|
||||
} while (event.changes.any { it.pressed })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CreateUpdateAddressShortLinkView(modalManager: ModalManager) {
|
||||
|
||||
@@ -29,8 +29,10 @@ import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.database.DatabaseView
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.migration.MigrateFromDeviceView
|
||||
import chat.simplex.common.views.onboarding.GetStakeView
|
||||
import chat.simplex.common.views.onboarding.SimpleXInfo
|
||||
import chat.simplex.common.views.onboarding.WhatsNewView
|
||||
import chat.simplex.common.views.onboarding.crowdfundingAvailable
|
||||
import chat.simplex.common.views.usersettings.networkAndServers.NetworkAndServersView
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@@ -110,6 +112,17 @@ fun SettingsLayout(
|
||||
AppShutdownItem()
|
||||
AppVersionItem(showVersion)
|
||||
}
|
||||
|
||||
if (crowdfundingAvailable()) {
|
||||
SectionDividerSpaced()
|
||||
SectionView(stringResource(MR.strings.v7_0_invest)) {
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_redeem),
|
||||
stringResource(MR.strings.v7_0_crowdfunding),
|
||||
{ ModalManager.start.showModalCloseable(cardScreen = true) { close -> GetStakeView(fromSettings = true, close = close) } }
|
||||
)
|
||||
}
|
||||
}
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2736,9 +2736,10 @@
|
||||
<string name="v6_5_safe_web_links_descr">- opt-in to send link previews.\n- use SOCKS proxy if enabled.\n- prevent hyperlink phishing.\n- remove link tracking.</string>
|
||||
<string name="v6_5_non_profit_governance">Non-profit governance</string>
|
||||
<string name="v6_5_non_profit_governance_descr">To make SimpleX Network last.</string>
|
||||
<!-- <string name="v7_0_invest">Invest in SimpleX Chat</string> -->
|
||||
<!-- <string name="v7_0_invest_descr">Equity crowdfunding launched!</string> -->
|
||||
<!-- <string name="v7_0_invest_learn_more">Learn more on Wefunder</string> -->
|
||||
<string name="v7_0_invest" translatable="false">You can now invest in SimpleX Chat! 🚀</string>
|
||||
<string name="v7_0_invest_descr" translatable="false">Crowdfunding on Wefunder.</string>
|
||||
<string name="v7_0_crowdfunding" translatable="false">Crowdfunding on Wefunder</string>
|
||||
<string name="v7_0_invest_learn_more" translatable="false">Learn more on Wefunder</string>
|
||||
<string name="v7_0_simplex_names">SimpleX public names (BETA)</string>
|
||||
<string name="v7_0_simplex_names_descr">Public names for your channel or business.</string>
|
||||
<string name="v7_0_channels">Better channels 📢</string>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<rect width="24" height="24" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 175 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<rect width="24" height="24" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 175 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<rect width="24" height="24" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 175 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<rect width="24" height="24" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 175 B |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 180 KiB |