diff --git a/android/app/src/main/res/values-ar/strings.xml b/android/app/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..d266a56
--- /dev/null
+++ b/android/app/src/main/res/values-ar/strings.xml
@@ -0,0 +1,26 @@
+
+ Conduit
+ contain
+ false
+ automatic
+
+ قف
+
+ %1$d / %2$d \u00B7 %3$s
+
+ المستخدمين: %1$dمتصل / %2$dمتصلين\nالبيانات: %3$s تم نقلها
+
+ في انتظار اتصال الشبكة
+
+ إشعار بشأن خدمة Conduit
+
+ التحديث مطلوب: يرجى تنزيل أحدث إصدار من التطبيق لمواصلة استخدامه.
+
+ فشلت خدمة Conduit في البدء التشغيل. انقر فوق الإشعار للحصول على مزيد من المعلومات
+
+ فشلت خدمة Conduit في إعادة التشغيل. انقر فوق الإشعار للحصول على مزيد من المعلومات
+
+ بدأت خدمة Conduit…
+
+ true
+
diff --git a/android/app/src/main/res/values-fa/strings.xml b/android/app/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000..fab8920
--- /dev/null
+++ b/android/app/src/main/res/values-fa/strings.xml
@@ -0,0 +1,26 @@
+
+ Conduit
+ contain
+ false
+ automatic
+
+ توقف
+
+ ۱\n۲\n۳
+
+ اتصال فعال \nدر حال اتصال \nانتقال داده شد
+
+ منتظر اتصال به شبکه
+
+ اعلانهای Conduit
+
+ آپدیت کنید: برای استفاده از این برنامه، آخرین نسخه را دانلود کنید.
+
+ Conduit راه نیفتاد. برای اطلاعات بیشتر اینجا کلیک کنید.
+
+ Conduit راه نیفتاد. برای اطلاعات بیشتر اینجا کلیک کنید.
+
+ Conduit در حال شروع است...
+
+ true
+
diff --git a/android/app/src/main/res/values-tr/strings.xml b/android/app/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..54d327d
--- /dev/null
+++ b/android/app/src/main/res/values-tr/strings.xml
@@ -0,0 +1,26 @@
+
+ Conduit
+ contain
+ false
+ automatic
+
+ Durdur
+
+ %1$d / %2$d \u00B7 %3$s
+
+ İstemciler: %1$d bağlı / %2$d bağlanıyor\nVeri: %3$s aktarıldı
+
+ Ağ bağlantısı bekleniyor
+
+ Conduit hizmeti bildirimi
+
+ Güncelleme gerekiyor: kullanmaya devam etmek için lütfen uygulamanın son sürümünü indirin.
+
+ Conduit hizmeti başlatılamadı. Daha fazla bilgi için bildirime tıklayın.
+
+ Conduit hizmeti yeniden başlatılamadı. Daha fazla bilgi için bildirime tıklayın.
+
+ Conduit hizmeti başlatılıyor…
+
+ true
+
diff --git a/src/app/(app)/onboarding.tsx b/src/app/(app)/onboarding.tsx
index bab7923..404fda9 100644
--- a/src/app/(app)/onboarding.tsx
+++ b/src/app/(app)/onboarding.tsx
@@ -32,6 +32,7 @@ import {
SkTextStyle,
Skia,
TextAlign,
+ TextDirection,
useFonts,
vec,
} from "@shopify/react-native-skia";
@@ -72,7 +73,8 @@ import { fonts, palette, sharedStyles as ss } from "@/src/styles";
export default function OnboardingScreen() {
const win = useWindowDimensions();
const insets = useSafeAreaInsets();
- const { t } = useTranslation();
+ const { t, i18n } = useTranslation();
+ const isRTL = i18n.dir() === "rtl" ? true : false;
const notificationPermissions = useNotificationsPermissions();
const router = useRouter();
@@ -228,9 +230,12 @@ export default function OnboardingScreen() {
if (!fontMgr) {
return null;
}
- const paragraphStyle = {
+ const paragraphStyle: SkParagraphStyle = {
textAlign: TextAlign.Center,
};
+ if (isRTL) {
+ paragraphStyle.textDirection = TextDirection.RTL;
+ }
const textStyle: SkTextStyle = {
color: Skia.Color(palette.white),
fontFamilies: ["Jura"],
@@ -252,8 +257,11 @@ export default function OnboardingScreen() {
return null;
}
const paragraphStyle: SkParagraphStyle = {
- textAlign: TextAlign.Left,
+ textAlign: isRTL ? TextAlign.Right : TextAlign.Left,
};
+ if (isRTL) {
+ paragraphStyle.textDirection = TextDirection.RTL;
+ }
const textStyle: SkTextStyle = {
color: Skia.Color(palette.white),
fontFamilies: ["Rajdhani"],
@@ -275,9 +283,12 @@ export default function OnboardingScreen() {
if (!fontMgr) {
return null;
}
- const paragraphStyle = {
+ const paragraphStyle: SkParagraphStyle = {
textAlign: TextAlign.Center,
};
+ if (isRTL) {
+ paragraphStyle.textDirection = TextDirection.RTL;
+ }
const textStyle: SkTextStyle = {
color: Skia.Color(palette.blueTint2),
diff --git a/src/components/ConduitName.tsx b/src/components/ConduitName.tsx
index 72f8988..787e3a1 100644
--- a/src/components/ConduitName.tsx
+++ b/src/components/ConduitName.tsx
@@ -47,7 +47,8 @@ export function ConduitName() {
// The Conduit Name is purely cosmetic, it is optional and only stored locally
export function EditableConduitName({ initialName }: { initialName: string }) {
- const { t } = useTranslation();
+ const { t, i18n } = useTranslation();
+ const isRTL = i18n.dir() === "rtl" ? true : false;
const queryClient = useQueryClient();
@@ -126,6 +127,7 @@ export function EditableConduitName({ initialName }: { initialName: string }) {
maxLength={maxLength}
autoCorrect={false}
autoComplete={"off"}
+ textAlign={isRTL ? "right" : "left"}
/>
{showCharsUsed && (
diff --git a/src/components/ConduitOrbToggle.tsx b/src/components/ConduitOrbToggle.tsx
index 543a748..a7b0735 100644
--- a/src/components/ConduitOrbToggle.tsx
+++ b/src/components/ConduitOrbToggle.tsx
@@ -22,22 +22,26 @@ import {
Canvas,
Circle,
ColorMatrix,
- ColorShader,
Group,
Image,
Paint,
+ Paragraph,
RadialGradient,
Shadow,
- Text,
+ SkParagraphStyle,
+ SkTextStyle,
+ Skia,
+ TextAlign,
+ TextDirection,
interpolateColors,
- useFont,
+ useFonts,
useImage,
vec,
} from "@shopify/react-native-skia";
import * as Haptics from "expo-haptics";
import React from "react";
import { useTranslation } from "react-i18next";
-import { View } from "react-native";
+import { View, useWindowDimensions } from "react-native";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, {
Easing,
@@ -54,7 +58,7 @@ import Animated, {
import { z } from "zod";
import { useAnimatedImageValue } from "@/src/animationHooks";
-import { timedLog } from "@/src/common/utils";
+import { drawBigFont, timedLog } from "@/src/common/utils";
import { ConduitConnectionLight } from "@/src/components/canvas/ConduitConnectionLight";
import {
INPROXY_MAX_CLIENTS_MAX,
@@ -67,6 +71,7 @@ import {
useInproxyStatus,
} from "@/src/inproxy/hooks";
import { fonts, palette, sharedStyles as ss } from "@/src/styles";
+import { FaderGroup } from "./canvas/FaderGroup";
export function ConduitOrbToggle({
width,
@@ -75,7 +80,10 @@ export function ConduitOrbToggle({
width: number;
height: number;
}) {
- const { t } = useTranslation();
+ const { t, i18n } = useTranslation();
+ const isRTL = i18n.dir() === "rtl" ? true : false;
+ const win = useWindowDimensions();
+
const { toggleInproxy } = useInproxyContext();
const { data: inproxyStatus } = useInproxyStatus();
const { data: inproxyCurrentConnectedClients } =
@@ -116,15 +124,43 @@ export function ConduitOrbToggle({
// The "Turn On" text also uses interpolation to appear to fade in by going
// from transparent to it's final color.
const orbText = t("TAP_TO_TURN_ON_I18N.string");
- const orbTextColors = [palette.transparent, palette.midGrey];
- const orbTextColorIndex = useSharedValue(0);
- const orbTextColor = useDerivedValue(() => {
- return interpolateColors(
- orbTextColorIndex.value,
- [0, 1],
- orbTextColors,
- );
- });
+ const orbTextOpacity = useSharedValue(0);
+
+ const fontMgr = useFonts({ Jura: [fonts.JuraRegular] });
+ const fontSize = drawBigFont(win) ? 20 : 16;
+ const orbTextParagraph = React.useMemo(() => {
+ if (!fontMgr) {
+ return null;
+ }
+ let paragraphStyle: SkParagraphStyle = {
+ textAlign: TextAlign.Center,
+ };
+ if (isRTL) {
+ paragraphStyle.textDirection = TextDirection.RTL;
+ }
+
+ const mainTextStyle: SkTextStyle = {
+ fontFamilies: ["Jura"],
+ fontSize: fontSize,
+ fontStyle: {
+ weight: 300,
+ },
+ letterSpacing: 1, // 5% of 20
+ };
+
+ return Skia.ParagraphBuilder.Make(paragraphStyle, fontMgr)
+ .pushStyle(mainTextStyle)
+ .addText(orbText)
+ .build();
+ }, [fontMgr]);
+
+ const orbTextXOffset = orbTextParagraph
+ ? -orbTextParagraph.getMaxWidth() / 2
+ : 0;
+ const orbTextYOffset = orbTextParagraph
+ ? -orbTextParagraph.getHeight() / 2
+ : 0;
+
// The orb will pop into existence at the start, animating from radius 0 up
const orbRadius = useSharedValue(0);
const orbDiameter = useDerivedValue(() => orbRadius.value * 2);
@@ -141,6 +177,17 @@ export function ConduitOrbToggle({
},
];
+ // The overlay is rendered in a regular view, while the orb itself is within
+ // Skia, so we need to accomodate RTL in the regular view.
+ const orbOverlayTransform = [
+ {
+ translateY: orbCenterY,
+ },
+ {
+ translateX: isRTL ? (-1 * width) / 2 : width / 2,
+ },
+ ];
+
function animateProxyAnnouncing() {
timedLog("animateProxyAnnouncing()");
orbColorsIndex.value = withRepeat(
@@ -151,7 +198,7 @@ export function ConduitOrbToggle({
-1,
true,
);
- orbTextColorIndex.value = withTiming(0, { duration: 500 });
+ orbTextOpacity.value = withTiming(0, { duration: 500 });
dotsOpacity.value = withTiming(1, { duration: 1000 });
}
@@ -166,7 +213,7 @@ export function ConduitOrbToggle({
timedLog("animateTurnOffProxy()");
cancelAnimation(orbColorsIndex);
orbColorsIndex.value = withTiming(0, { duration: 500 });
- orbTextColorIndex.value = withTiming(1, { duration: 500 });
+ orbTextOpacity.value = withTiming(1, { duration: 500 });
dotsOpacity.value = withTiming(0.2, { duration: 1000 });
}
@@ -186,14 +233,10 @@ export function ConduitOrbToggle({
delay,
withTiming(0.2, { duration: 1000 }),
);
- if (delay > 0) {
- // if we're introing with a delay, it means the Inproxy is stopped,
- // so we will fade in our button text.
- orbTextColorIndex.value = withDelay(
- delay,
- withTiming(1, { duration: 1000 }),
- );
- }
+ orbTextOpacity.value = withDelay(
+ delay,
+ withTiming(1, { duration: 1000 }),
+ );
}
// We have 4 animation states that depend on the state of the Inproxy:
@@ -301,11 +344,6 @@ export function ConduitOrbToggle({
);
}, []);
- // TODO: switch to the newer Paragraph model
- const font = useFont(fonts.JuraRegular, 20);
- const orbTextXOffset = font ? -font.measureText(orbText).width / 2 : 0;
- const orbTextYOffset = font ? font.measureText(orbText).height / 2 : 0;
-
// Orb Gesture
// Since turning off the proxy will disconnect any connected users, require
// a long press to turn off. When the user clicks the orb and a toggle would
@@ -393,10 +431,10 @@ export function ConduitOrbToggle({
{/* Intro particle swirl animation */}
@@ -480,14 +518,14 @@ export function ConduitOrbToggle({
{/* Turn ON text displayed when Conduit is off */}
-
-
-
+
+
+
@@ -519,7 +557,7 @@ export function ConduitOrbToggle({
width: orbDiameter,
height: orbDiameter,
borderRadius: orbRadius,
- transform: orbCenteringTransform,
+ transform: orbOverlayTransform,
},
]}
/>
diff --git a/src/components/ConduitSettings.tsx b/src/components/ConduitSettings.tsx
index c9c83cd..3637277 100644
--- a/src/components/ConduitSettings.tsx
+++ b/src/components/ConduitSettings.tsx
@@ -72,16 +72,10 @@ import {
InproxyParametersSchema,
} from "@/src/inproxy/types";
import { getProxyId } from "@/src/inproxy/utils";
-import {
- lineItemRTLStyle,
- lineItemStyle,
- palette,
- sharedStyles as ss,
-} from "@/src/styles";
+import { lineItemStyle, palette, sharedStyles as ss } from "@/src/styles";
export function ConduitSettings() {
- const { t, i18n } = useTranslation();
- const isRTL = i18n.dir() === "rtl" ? true : false;
+ const { t } = useTranslation();
const win = useWindowDimensions();
const router = useRouter();
@@ -182,18 +176,12 @@ export function ConduitSettings() {
const scrollRef = React.useRef(null);
function Settings() {
- let lineStyle;
- if (isRTL) {
- lineStyle = lineItemRTLStyle;
- } else {
- lineStyle = lineItemStyle;
- }
return (
@@ -268,13 +256,13 @@ export function ConduitSettings() {
)}
min={8}
max={INPROXY_MAX_MBPS_PER_PEER_MAX}
- style={[...lineStyle, ss.alignCenter]}
+ style={[...lineItemStyle, ss.alignCenter]}
onChange={updateInproxyLimitBytesPerSecond}
scrollRef={scrollRef}
/>
)}
-
+
{t("ALIAS_I18N.string")}:
@@ -337,7 +319,7 @@ export function ConduitSettings() {
{
const changed = value.value === originalValue ? " " : "*";
@@ -122,23 +114,11 @@ export function EditableNumberSlider({
});
// Overlay for GestureDetector
- const overlaySize = useDerivedValue(() => circleR.value * 5);
- const overlayTransform = useDerivedValue(() => {
- return [
- {
- translateX: circleCx.value - circleR.value * 2.5,
- },
- {
- translateY: circleCy.value - circleR.value * 2,
- },
- ];
- });
const overlayStyle = useAnimatedStyle(() => ({
- position: "absolute",
flex: 1,
- height: overlaySize.value,
- width: overlaySize.value,
- transform: overlayTransform.value,
+ position: "absolute",
+ width: "100%",
+ height: "100%",
}));
const sliderGesture = Gesture.Pan()
@@ -165,15 +145,10 @@ export function EditableNumberSlider({
return (
{label}
-
-
+
+