mirror of https://github.com/tooot-app/app
Merge branch 'main' into release
This commit is contained in:
commit
c46f03ed37
|
@ -1,363 +0,0 @@
|
|||
diff --git a/RNFastImage.podspec b/RNFastImage.podspec
|
||||
index db0fada63fc06191f8620d336d244edde6c3dba3..9c22c36f6978530da21afe143324ff79b4e96454 100644
|
||||
--- a/RNFastImage.podspec
|
||||
+++ b/RNFastImage.podspec
|
||||
@@ -16,6 +16,6 @@ Pod::Spec.new do |s|
|
||||
s.source_files = "ios/**/*.{h,m}"
|
||||
|
||||
s.dependency 'React-Core'
|
||||
- s.dependency 'SDWebImage', '~> 5.11.1'
|
||||
- s.dependency 'SDWebImageWebPCoder', '~> 0.8.4'
|
||||
+ s.dependency 'SDWebImage', '~> 5.15.0'
|
||||
+ s.dependency 'SDWebImageWebPCoder', '~> 0.9.1'
|
||||
end
|
||||
diff --git a/android/build.gradle b/android/build.gradle
|
||||
index 5b21cd59c40a5754f5d19c77e2a0eb0229925911..19d82f826e88125c5e6d87ee7c348fac621f548c 100644
|
||||
--- a/android/build.gradle
|
||||
+++ b/android/build.gradle
|
||||
@@ -65,4 +65,5 @@ dependencies {
|
||||
implementation "com.github.bumptech.glide:glide:${glideVersion}"
|
||||
implementation "com.github.bumptech.glide:okhttp3-integration:${glideVersion}"
|
||||
annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
|
||||
+ implementation 'com.github.penfeizhou.android.animation:glide-plugin:2.12.0'
|
||||
}
|
||||
diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageEnterTransition.java b/android/src/main/java/com/dylanvann/fastimage/FastImageEnterTransition.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..55e3b4e0d463654f62d942ba05c2a5e51ae9d6d7
|
||||
--- /dev/null
|
||||
+++ b/android/src/main/java/com/dylanvann/fastimage/FastImageEnterTransition.java
|
||||
@@ -0,0 +1,6 @@
|
||||
+package com.dylanvann.fastimage;
|
||||
+
|
||||
+public enum FastImageEnterTransition {
|
||||
+ TRANSITION_NONE,
|
||||
+ FADE_IN
|
||||
+}
|
||||
diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageTransitions.java b/android/src/main/java/com/dylanvann/fastimage/FastImageTransitions.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..d764cc4b8d110f087120a4f0dc5d986754806dec
|
||||
--- /dev/null
|
||||
+++ b/android/src/main/java/com/dylanvann/fastimage/FastImageTransitions.java
|
||||
@@ -0,0 +1,20 @@
|
||||
+package com.dylanvann.fastimage;
|
||||
+
|
||||
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
||||
+import com.bumptech.glide.TransitionOptions;
|
||||
+import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
+import android.view.animation.DecelerateInterpolator;
|
||||
+
|
||||
+public class FastImageTransitions {
|
||||
+ static final DecelerateInterpolator mInterpolator = new DecelerateInterpolator();
|
||||
+
|
||||
+ public static TransitionOptions getEnterTransition(FastImageEnterTransition transition, int duration) {
|
||||
+ switch (transition) {
|
||||
+ case FADE_IN:
|
||||
+ return DrawableTransitionOptions.withCrossFade(duration);
|
||||
+
|
||||
+ default:
|
||||
+ throw new JSApplicationIllegalArgumentException("FastImage, invalid enterTransition argument");
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
\ No newline at end of file
|
||||
diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java b/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java
|
||||
index 86ca00d018d7ded0edff733373d80976c8dbb961..e6220f57b38a3fe3ae9d5a75228f791e0ec978bb 100644
|
||||
--- a/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java
|
||||
+++ b/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java
|
||||
@@ -50,6 +50,12 @@ class FastImageViewConverter {
|
||||
put("center", ScaleType.CENTER_INSIDE);
|
||||
}};
|
||||
|
||||
+ private static final Map<String, FastImageEnterTransition> FAST_IMAGE_ENTER_TRANSITION_MAP =
|
||||
+ new HashMap<String, FastImageEnterTransition>() {{
|
||||
+ put("none", FastImageEnterTransition.TRANSITION_NONE);
|
||||
+ put("fadeIn", FastImageEnterTransition.FADE_IN);
|
||||
+ }};
|
||||
+
|
||||
// Resolve the source uri to a file path that android understands.
|
||||
static @Nullable
|
||||
FastImageSource getImageSource(Context context, @Nullable ReadableMap source) {
|
||||
@@ -125,6 +131,10 @@ class FastImageViewConverter {
|
||||
return getValueFromSource("cache", "immutable", FAST_IMAGE_CACHE_CONTROL_MAP, source);
|
||||
}
|
||||
|
||||
+ static FastImageEnterTransition getEnterTransition(String propValue) {
|
||||
+ return getValue("enterTransition", "none", FAST_IMAGE_ENTER_TRANSITION_MAP, propValue);
|
||||
+ }
|
||||
+
|
||||
private static Priority getPriority(ReadableMap source) {
|
||||
return getValueFromSource("priority", "normal", FAST_IMAGE_PRIORITY_MAP, source);
|
||||
}
|
||||
diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java b/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java
|
||||
index c7a795471c8f8b48163c778836406bc5ead75dab..53b481547b44224e7791a8d3f39815c9c9a4be59 100644
|
||||
--- a/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java
|
||||
+++ b/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java
|
||||
@@ -83,6 +83,17 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> imple
|
||||
view.setScaleType(scaleType);
|
||||
}
|
||||
|
||||
+ @ReactProp(name = "enterTransition")
|
||||
+ public void setEnterTransition(FastImageViewWithUrl view, String enterTransition) {
|
||||
+ final FastImageEnterTransition transition = FastImageViewConverter.getEnterTransition(enterTransition);
|
||||
+ view.setEnterTransition(transition);
|
||||
+ }
|
||||
+
|
||||
+ @ReactProp(name = "transitionDuration")
|
||||
+ public void setTransitionDuration(FastImageViewWithUrl view, int transitionDuration) {
|
||||
+ view.setTransitionDuration(transitionDuration);
|
||||
+ }
|
||||
+
|
||||
@Override
|
||||
public void onDropViewInstance(@NonNull FastImageViewWithUrl view) {
|
||||
// This will cancel existing requests.
|
||||
diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java b/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java
|
||||
index 34fcf898d17d82fd52375e9028b71ad815b9b15b..fd57ac68de093d2a8ee53aeede45328c8d52aa39 100644
|
||||
--- a/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java
|
||||
+++ b/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java
|
||||
@@ -30,6 +30,8 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
||||
private boolean mNeedsReload = false;
|
||||
private ReadableMap mSource = null;
|
||||
private Drawable mDefaultSource = null;
|
||||
+ private FastImageEnterTransition mEnterTransition = FastImageEnterTransition.TRANSITION_NONE;
|
||||
+ private int mTransitionDuration = 350;
|
||||
|
||||
public GlideUrl glideUrl;
|
||||
|
||||
@@ -47,6 +49,14 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
||||
mDefaultSource = source;
|
||||
}
|
||||
|
||||
+ public void setEnterTransition(@Nullable FastImageEnterTransition transition) {
|
||||
+ mEnterTransition = transition;
|
||||
+ }
|
||||
+
|
||||
+ public void setTransitionDuration(int duration) {
|
||||
+ mTransitionDuration = duration == 0 ? 350 : duration;
|
||||
+ }
|
||||
+
|
||||
private boolean isNullOrEmpty(final String url) {
|
||||
return url == null || url.trim().isEmpty();
|
||||
}
|
||||
@@ -147,6 +157,10 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
||||
if (key != null)
|
||||
builder.listener(new FastImageRequestListener(key));
|
||||
|
||||
+ if (mEnterTransition != FastImageEnterTransition.TRANSITION_NONE) {
|
||||
+ builder.transition(FastImageTransitions.getEnterTransition(mEnterTransition, mTransitionDuration));
|
||||
+ }
|
||||
+
|
||||
builder.into(this);
|
||||
}
|
||||
}
|
||||
diff --git a/dist/index.cjs.js b/dist/index.cjs.js
|
||||
index 2df6a29769978d8d947dfb50b422e1f56bd97fb6..f3904e20edac5f19cc26f41a4ff02eecd73ac627 100644
|
||||
--- a/dist/index.cjs.js
|
||||
+++ b/dist/index.cjs.js
|
||||
@@ -9,6 +9,10 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
|
||||
var _extends__default = /*#__PURE__*/_interopDefaultLegacy(_extends);
|
||||
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
||||
|
||||
+const enterTransition = {
|
||||
+ none: 'none',
|
||||
+ fadeIn: 'fadeIn'
|
||||
+}
|
||||
const resizeMode = {
|
||||
contain: 'contain',
|
||||
cover: 'cover',
|
||||
@@ -115,6 +119,7 @@ const FastImageComponent = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PU
|
||||
}, props)));
|
||||
FastImageComponent.displayName = 'FastImage';
|
||||
const FastImage = FastImageComponent;
|
||||
+FastImage.enterTransition = enterTransition
|
||||
FastImage.resizeMode = resizeMode;
|
||||
FastImage.cacheControl = cacheControl;
|
||||
FastImage.priority = priority;
|
||||
diff --git a/dist/index.d.ts b/dist/index.d.ts
|
||||
index 5abb7c98b767cd0709b53f5ab2dd50c752a9377b..2da22817e3136673d40a177ae8c9fc2209f143d8 100644
|
||||
--- a/dist/index.d.ts
|
||||
+++ b/dist/index.d.ts
|
||||
@@ -1,5 +1,10 @@
|
||||
import React from 'react';
|
||||
import { FlexStyle, LayoutChangeEvent, ShadowStyleIOS, StyleProp, TransformsStyle, ImageRequireSource, AccessibilityProps, ViewProps, ColorValue } from 'react-native';
|
||||
+export declare type EnterTransition = 'none' | 'fadeIn';
|
||||
+declare const enterTransition: {
|
||||
+ readonly none: "none";
|
||||
+ readonly fadeIn: "fadeIn";
|
||||
+};
|
||||
export declare type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center';
|
||||
declare const resizeMode: {
|
||||
readonly contain: "contain";
|
||||
@@ -57,6 +62,16 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
||||
defaultSource?: ImageRequireSource;
|
||||
resizeMode?: ResizeMode;
|
||||
fallback?: boolean;
|
||||
+ /**
|
||||
+ * Transition durations.
|
||||
+ * @default none
|
||||
+ */
|
||||
+ enterTransition?: EnterTransition
|
||||
+ /**
|
||||
+ * Enter transition duration in ms.
|
||||
+ * @default 500ms
|
||||
+ */
|
||||
+ transitionDuration?: number
|
||||
onLoadStart?(): void;
|
||||
onProgress?(event: OnProgressEvent): void;
|
||||
onLoad?(event: OnLoadEvent): void;
|
||||
@@ -91,6 +106,7 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface FastImageStaticProperties {
|
||||
+ enterTransition: typeof enterTransition;
|
||||
resizeMode: typeof resizeMode;
|
||||
priority: typeof priority;
|
||||
cacheControl: typeof cacheControl;
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 58e0308bd44836aad3e4979b5c1151083956c295..5853b3b2fd05c91be8c70819fe6fc45606f26f8d 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -2,6 +2,10 @@ import _extends from '@babel/runtime/helpers/extends';
|
||||
import React, { forwardRef, memo } from 'react';
|
||||
import { NativeModules, StyleSheet, requireNativeComponent, Image, View, Platform } from 'react-native';
|
||||
|
||||
+const enterTransition = {
|
||||
+ none: 'none',
|
||||
+ fadeIn: 'fadeIn'
|
||||
+}
|
||||
const resizeMode = {
|
||||
contain: 'contain',
|
||||
cover: 'cover',
|
||||
@@ -57,6 +61,8 @@ function FastImageBase({
|
||||
children,
|
||||
// eslint-disable-next-line no-shadow
|
||||
resizeMode = 'cover',
|
||||
+ enterTransition = 'none',
|
||||
+ transitionDuration = 350,
|
||||
forwardedRef,
|
||||
...props
|
||||
}) {
|
||||
@@ -79,7 +85,9 @@ function FastImageBase({
|
||||
onLoad: onLoad,
|
||||
onError: onError,
|
||||
onLoadEnd: onLoadEnd,
|
||||
- resizeMode: resizeMode
|
||||
+ resizeMode: resizeMode,
|
||||
+ enterTransition: enterTransition,
|
||||
+ transitionDuration: transitionDuration
|
||||
})), children);
|
||||
}
|
||||
|
||||
@@ -98,7 +106,9 @@ function FastImageBase({
|
||||
onFastImageLoad: onLoad,
|
||||
onFastImageError: onError,
|
||||
onFastImageLoadEnd: onLoadEnd,
|
||||
- resizeMode: resizeMode
|
||||
+ resizeMode: resizeMode,
|
||||
+ enterTransition: enterTransition,
|
||||
+ transitionDuration: transitionDuration
|
||||
})), children);
|
||||
}
|
||||
|
||||
@@ -108,6 +118,7 @@ const FastImageComponent = /*#__PURE__*/forwardRef((props, ref) => /*#__PURE__*/
|
||||
}, props)));
|
||||
FastImageComponent.displayName = 'FastImage';
|
||||
const FastImage = FastImageComponent;
|
||||
+FastImage.enterTransition = enterTransition
|
||||
FastImage.resizeMode = resizeMode;
|
||||
FastImage.cacheControl = cacheControl;
|
||||
FastImage.priority = priority;
|
||||
diff --git a/ios/FastImage/FFFastImageView.h b/ios/FastImage/FFFastImageView.h
|
||||
index e52fca79882ad2a678487a46b2fe158427e06f3a..6c9c41b0b1a3c967a3715a24bb692447b76ef365 100644
|
||||
--- a/ios/FastImage/FFFastImageView.h
|
||||
+++ b/ios/FastImage/FFFastImageView.h
|
||||
@@ -7,6 +7,7 @@
|
||||
#import <React/RCTResizeMode.h>
|
||||
|
||||
#import "FFFastImageSource.h"
|
||||
+#import "FFFastImageViewManager.h"
|
||||
|
||||
@interface FFFastImageView : SDAnimatedImageView
|
||||
|
||||
@@ -16,6 +17,8 @@
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onFastImageLoad;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onFastImageLoadEnd;
|
||||
@property (nonatomic, assign) RCTResizeMode resizeMode;
|
||||
+@property (nonatomic, assign) FFFEnterTransition enterTransition;
|
||||
+@property (nonatomic, assign) NSTimeInterval transitionDuration;
|
||||
@property (nonatomic, strong) FFFastImageSource *source;
|
||||
@property (nonatomic, strong) UIImage *defaultSource;
|
||||
@property (nonatomic, strong) UIColor *imageColor;
|
||||
diff --git a/ios/FastImage/FFFastImageView.m b/ios/FastImage/FFFastImageView.m
|
||||
index f7100815e652539b29b1fa70ff1477c5f5db08dc..ecb79eafe566fe52090adada3cdf16eb10a67513 100644
|
||||
--- a/ios/FastImage/FFFastImageView.m
|
||||
+++ b/ios/FastImage/FFFastImageView.m
|
||||
@@ -71,6 +71,18 @@ - (void) setImageColor: (UIColor*)imageColor {
|
||||
}
|
||||
}
|
||||
|
||||
+- (void) setTransitionDuration: (NSTimeInterval)transitionDuration {
|
||||
+ self.sd_imageTransition.duration = transitionDuration;
|
||||
+}
|
||||
+
|
||||
+- (void) setEnterTransition: (FFFEnterTransition)enterTransition {
|
||||
+ switch (enterTransition) {
|
||||
+ case FFFFadeIn:
|
||||
+ self.sd_imageTransition = SDWebImageTransition.fadeTransition;
|
||||
+ break;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
- (UIImage*) makeImage: (UIImage*)image withTint: (UIColor*)color {
|
||||
UIImage* newImage = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate];
|
||||
UIGraphicsBeginImageContextWithOptions(image.size, NO, newImage.scale);
|
||||
diff --git a/ios/FastImage/FFFastImageViewManager.h b/ios/FastImage/FFFastImageViewManager.h
|
||||
index 8ba6020e2c6e5757ed778d00e3f43a6ff4c1d50a..a269669301ea00ef3c2714123d17e822094635d6 100644
|
||||
--- a/ios/FastImage/FFFastImageViewManager.h
|
||||
+++ b/ios/FastImage/FFFastImageViewManager.h
|
||||
@@ -1,5 +1,10 @@
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
+typedef NS_ENUM(NSInteger, FFFEnterTransition) {
|
||||
+ FFFTransitionNone,
|
||||
+ FFFFadeIn,
|
||||
+};
|
||||
+
|
||||
@interface FFFastImageViewManager : RCTViewManager
|
||||
|
||||
@end
|
||||
diff --git a/ios/FastImage/FFFastImageViewManager.m b/ios/FastImage/FFFastImageViewManager.m
|
||||
index 84ca94e26e546d4d139dabca6c3efd0a890eda63..2184bac31f0d547e6119356bb4fc7931be87446d 100644
|
||||
--- a/ios/FastImage/FFFastImageViewManager.m
|
||||
+++ b/ios/FastImage/FFFastImageViewManager.m
|
||||
@@ -13,6 +13,8 @@ - (FFFastImageView*)view {
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(source, FFFastImageSource)
|
||||
+RCT_EXPORT_VIEW_PROPERTY(enterTransition, FFFEnterTransition)
|
||||
+RCT_EXPORT_VIEW_PROPERTY(transitionDuration, NSTimeInterval)
|
||||
RCT_EXPORT_VIEW_PROPERTY(defaultSource, UIImage)
|
||||
RCT_EXPORT_VIEW_PROPERTY(resizeMode, RCTResizeMode)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onFastImageLoadStart, RCTDirectEventBlock)
|
||||
diff --git a/ios/FastImage/RCTConvert+FFFastImage.m b/ios/FastImage/RCTConvert+FFFastImage.m
|
||||
index 43f8922157655a7497f56a3909ef6b2a886f07d8..0705f8e05f44f3053e7239fcc9a30d986e7aaab7 100644
|
||||
--- a/ios/FastImage/RCTConvert+FFFastImage.m
|
||||
+++ b/ios/FastImage/RCTConvert+FFFastImage.m
|
||||
@@ -1,5 +1,6 @@
|
||||
#import "RCTConvert+FFFastImage.h"
|
||||
#import "FFFastImageSource.h"
|
||||
+#import "FFFastImageViewManager.h"
|
||||
|
||||
@implementation RCTConvert (FFFastImage)
|
||||
|
||||
@@ -15,6 +16,11 @@ @implementation RCTConvert (FFFastImage)
|
||||
@"cacheOnly": @(FFFCacheControlCacheOnly),
|
||||
}), FFFCacheControlImmutable, integerValue);
|
||||
|
||||
+RCT_ENUM_CONVERTER(FFFEnterTransition, (@{
|
||||
+ @"none": @(FFFTransitionNone),
|
||||
+ @"fadeIn": @(FFFFadeIn),
|
||||
+ }), FFFTransitionNone, integerValue);
|
||||
+
|
||||
+ (FFFastImageSource *)FFFastImageSource:(id)json {
|
||||
if (!json) {
|
||||
return nil;
|
|
@ -3,14 +3,14 @@ PODS:
|
|||
- DoubleConversion (1.1.6)
|
||||
- EXApplication (5.0.1):
|
||||
- ExpoModulesCore
|
||||
- EXAV (13.1.0):
|
||||
- EXAV (13.2.0):
|
||||
- ExpoModulesCore
|
||||
- ReactCommon/turbomodule/core
|
||||
- EXConstants (14.1.0):
|
||||
- EXConstants (14.2.0):
|
||||
- ExpoModulesCore
|
||||
- EXErrorRecovery (4.0.1):
|
||||
- ExpoModulesCore
|
||||
- EXFileSystem (15.1.1):
|
||||
- EXFileSystem (15.2.0):
|
||||
- ExpoModulesCore
|
||||
- EXFont (11.0.1):
|
||||
- ExpoModulesCore
|
||||
|
@ -18,31 +18,37 @@ PODS:
|
|||
- ExpoModulesCore
|
||||
- Expo (47.0.13):
|
||||
- ExpoModulesCore
|
||||
- ExpoCrypto (12.1.0):
|
||||
- ExpoCrypto (12.2.0):
|
||||
- ExpoModulesCore
|
||||
- ExpoHaptics (12.1.0):
|
||||
- ExpoHaptics (12.2.0):
|
||||
- ExpoModulesCore
|
||||
- ExpoImage (1.0.0-beta.6):
|
||||
- ExpoModulesCore
|
||||
- SDWebImage (~> 5.15.0)
|
||||
- SDWebImageAVIFCoder (~> 0.9.4)
|
||||
- SDWebImageSVGCoder (~> 1.6.1)
|
||||
- SDWebImageWebPCoder (~> 0.9.1)
|
||||
- ExpoKeepAwake (11.0.1):
|
||||
- ExpoModulesCore
|
||||
- ExpoLocalization (14.0.0):
|
||||
- ExpoLocalization (14.1.0):
|
||||
- ExpoModulesCore
|
||||
- ExpoModulesCore (1.1.1):
|
||||
- React-Core
|
||||
- ReactCommon/turbomodule/core
|
||||
- ExpoRandom (13.0.0):
|
||||
- ExpoRandom (13.1.0):
|
||||
- ExpoModulesCore
|
||||
- ExpoStoreReview (6.1.0):
|
||||
- ExpoStoreReview (6.2.0):
|
||||
- ExpoModulesCore
|
||||
- ExpoVideoThumbnails (7.1.0):
|
||||
- ExpoVideoThumbnails (7.2.0):
|
||||
- ExpoModulesCore
|
||||
- ExpoWebBrowser (12.0.0):
|
||||
- ExpoModulesCore
|
||||
- EXScreenCapture (5.0.0):
|
||||
- EXScreenCapture (5.1.0):
|
||||
- ExpoModulesCore
|
||||
- EXScreenOrientation (5.0.1):
|
||||
- EXScreenOrientation (5.1.0):
|
||||
- ExpoModulesCore
|
||||
- React-Core
|
||||
- EXSecureStore (12.0.0):
|
||||
- EXSecureStore (12.1.0):
|
||||
- ExpoModulesCore
|
||||
- EXSplashScreen (0.17.5):
|
||||
- ExpoModulesCore
|
||||
|
@ -58,7 +64,16 @@ PODS:
|
|||
- fmt (6.2.1)
|
||||
- glog (0.3.5)
|
||||
- hermes-engine (0.70.7)
|
||||
- libaom (2.0.2):
|
||||
- libvmaf
|
||||
- libavif (0.10.1):
|
||||
- libavif/libaom (= 0.10.1)
|
||||
- libavif/core (0.10.1)
|
||||
- libavif/libaom (0.10.1):
|
||||
- libaom (>= 2.0.0)
|
||||
- libavif/core
|
||||
- libevent (2.1.12)
|
||||
- libvmaf (2.2.0)
|
||||
- libwebp (1.2.4):
|
||||
- libwebp/demux (= 1.2.4)
|
||||
- libwebp/mux (= 1.2.4)
|
||||
|
@ -299,8 +314,6 @@ PODS:
|
|||
- glog
|
||||
- react-native-blur (4.3.0):
|
||||
- React-Core
|
||||
- react-native-blurhash (1.1.10):
|
||||
- React-Core
|
||||
- react-native-cameraroll (5.2.3):
|
||||
- React-Core
|
||||
- react-native-image-picker (5.0.1):
|
||||
|
@ -401,10 +414,6 @@ PODS:
|
|||
- React-Core
|
||||
- RNCClipboard (1.11.1):
|
||||
- React-Core
|
||||
- RNFastImage (8.6.3):
|
||||
- React-Core
|
||||
- SDWebImage (~> 5.15.0)
|
||||
- SDWebImageWebPCoder (~> 0.9.1)
|
||||
- RNGestureHandler (2.9.0):
|
||||
- React-Core
|
||||
- RNReanimated (2.14.4):
|
||||
|
@ -447,6 +456,11 @@ PODS:
|
|||
- SDWebImage (5.15.0):
|
||||
- SDWebImage/Core (= 5.15.0)
|
||||
- SDWebImage/Core (5.15.0)
|
||||
- SDWebImageAVIFCoder (0.9.5):
|
||||
- libavif (>= 0.9.1)
|
||||
- SDWebImage (~> 5.10)
|
||||
- SDWebImageSVGCoder (1.6.1):
|
||||
- SDWebImage/Core (~> 5.6)
|
||||
- SDWebImageWebPCoder (0.9.1):
|
||||
- libwebp (~> 1.0)
|
||||
- SDWebImage/Core (~> 5.13)
|
||||
|
@ -467,6 +481,7 @@ DEPENDENCIES:
|
|||
- Expo (from `../node_modules/expo`)
|
||||
- ExpoCrypto (from `../node_modules/expo-crypto/ios`)
|
||||
- ExpoHaptics (from `../node_modules/expo-haptics/ios`)
|
||||
- ExpoImage (from `../node_modules/expo-image/ios`)
|
||||
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
||||
- ExpoLocalization (from `../node_modules/expo-localization/ios`)
|
||||
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
|
||||
|
@ -500,7 +515,6 @@ DEPENDENCIES:
|
|||
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
||||
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
|
||||
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
|
||||
- react-native-blurhash (from `../node_modules/react-native-blurhash`)
|
||||
- "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)"
|
||||
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
|
||||
- react-native-ios-context-menu (from `../node_modules/react-native-ios-context-menu`)
|
||||
|
@ -527,7 +541,6 @@ DEPENDENCIES:
|
|||
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
||||
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
|
||||
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
|
||||
- RNFastImage (from `../node_modules/react-native-fast-image`)
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||
- RNScreens (from `../node_modules/react-native-screens`)
|
||||
|
@ -539,11 +552,16 @@ DEPENDENCIES:
|
|||
SPEC REPOS:
|
||||
trunk:
|
||||
- fmt
|
||||
- libaom
|
||||
- libavif
|
||||
- libevent
|
||||
- libvmaf
|
||||
- libwebp
|
||||
- MMKV
|
||||
- MMKVCore
|
||||
- SDWebImage
|
||||
- SDWebImageAVIFCoder
|
||||
- SDWebImageSVGCoder
|
||||
- SDWebImageWebPCoder
|
||||
- Sentry
|
||||
- Swime
|
||||
|
@ -573,6 +591,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/expo-crypto/ios"
|
||||
ExpoHaptics:
|
||||
:path: "../node_modules/expo-haptics/ios"
|
||||
ExpoImage:
|
||||
:path: "../node_modules/expo-image/ios"
|
||||
ExpoKeepAwake:
|
||||
:path: "../node_modules/expo-keep-awake/ios"
|
||||
ExpoLocalization:
|
||||
|
@ -635,8 +655,6 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native/ReactCommon/logger"
|
||||
react-native-blur:
|
||||
:path: "../node_modules/@react-native-community/blur"
|
||||
react-native-blurhash:
|
||||
:path: "../node_modules/react-native-blurhash"
|
||||
react-native-cameraroll:
|
||||
:path: "../node_modules/@react-native-camera-roll/camera-roll"
|
||||
react-native-image-picker:
|
||||
|
@ -689,8 +707,6 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/@react-native-async-storage/async-storage"
|
||||
RNCClipboard:
|
||||
:path: "../node_modules/@react-native-clipboard/clipboard"
|
||||
RNFastImage:
|
||||
:path: "../node_modules/react-native-fast-image"
|
||||
RNGestureHandler:
|
||||
:path: "../node_modules/react-native-gesture-handler"
|
||||
RNReanimated:
|
||||
|
@ -710,32 +726,36 @@ SPEC CHECKSUMS:
|
|||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
EXApplication: 034b1c40a8e9fe1bff76a1e511ee90dff64ad834
|
||||
EXAV: 4b92292fb107520a25956bea940a94a3bb4911ca
|
||||
EXConstants: 44f7d347d0432a66f469d0ce1dc4e3a0ca1b8b2d
|
||||
EXAV: 1242c4c206fc522058a2749019064e979a4c0b76
|
||||
EXConstants: 397186c7e312c33eb1ab85fa1f434dc123778136
|
||||
EXErrorRecovery: ae43433feb0608a64dc5b1c8363b3e7769a9ea24
|
||||
EXFileSystem: 60602b6eefa6873f97172c684b7537c9760b50d6
|
||||
EXFileSystem: d9fea7fe7a4390a0ef226cac33958de9178388b9
|
||||
EXFont: 319606bfe48c33b5b5063fb0994afdc496befe80
|
||||
EXNotifications: babce2a87b7922051354fcfe7a74dd279b7e272a
|
||||
Expo: b9fa98bf260992312ee3c424400819fb9beadafe
|
||||
ExpoCrypto: 6eb2a5ede7d95b7359a5f0391ee0c5d2ecd144b3
|
||||
ExpoHaptics: 129d3f8d44c2205adcdf8db760602818463d5437
|
||||
ExpoCrypto: 98c71864077c4d0fe798a6a5aee1a8c1294cef85
|
||||
ExpoHaptics: 97c532f311c3e638c14a6134f23564d007b76de4
|
||||
ExpoImage: 748f2b8d3974f1d51c7706fd61057b93241738aa
|
||||
ExpoKeepAwake: 69b59d0a8d2b24de9f82759c39b3821fec030318
|
||||
ExpoLocalization: e202d1e2a4950df17ac8d0889d65a1ffd7532d7e
|
||||
ExpoLocalization: 28ce7cfa174a752f7ace84189710f1385373655b
|
||||
ExpoModulesCore: 485dff3a59b036a33b6050c0a5aea3cf1037fdd1
|
||||
ExpoRandom: 58b7e0a5fe1adf1cb6dc1cbe503a6fe9524f36ce
|
||||
ExpoStoreReview: 713336ff504db3a6983475bf7c67519cc5efc86f
|
||||
ExpoVideoThumbnails: 424db02cedfbbe2d498bcb2712ea4ba8a9dcb453
|
||||
ExpoRandom: d8fc05d0d071485b06a97ab2a78cb7f8082052cd
|
||||
ExpoStoreReview: e96ba0690ea21dc5d341cfafd0b26bac7bc974f5
|
||||
ExpoVideoThumbnails: 865fa65f2b4f006ff02ef9e3e9c10370d9442d0a
|
||||
ExpoWebBrowser: 073e50f16669d498fb49063b9b7fe780b24f7fda
|
||||
EXScreenCapture: d9f1ec31042dfef109290d06c2b4789b7444d16d
|
||||
EXScreenOrientation: 07e5aeff07bce09a2b214981e612d87fd7719997
|
||||
EXSecureStore: daec0117c922a67c658cb229152a9e252e5c1750
|
||||
EXScreenCapture: bcf94c8199cd1876166e384b2398ff519a8ef7ee
|
||||
EXScreenOrientation: d43067a93e75234a7ce5154e2759fff2238dbfd5
|
||||
EXSecureStore: ec150f49b22269022c6184f1711abb05fe98d72d
|
||||
EXSplashScreen: 3e989924f61a8dd07ee4ea584c6ba14be9b51949
|
||||
FBLazyVector: a6454570f573a0f6f1d397e5a95c13e8e45d1700
|
||||
FBReactNativeSpec: 09e8dfba44487e5dc4882a9f5318cde67549549c
|
||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
|
||||
hermes-engine: 566e656aa95456a3f3f739fd76ea9a9656f2633f
|
||||
libaom: 9bb51e0f8f9192245e3ca2a1c9e4375d9cbccc52
|
||||
libavif: e242998ccec1c83bcba0bbdc256f460ad5077348
|
||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||
libvmaf: 8d61aabc2f4ed3e6591cf7406fa00a223ec11289
|
||||
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
|
||||
MMKV: 7f34558bbb5a33b0eaefae2de4b6a20a2ffdad6f
|
||||
MMKVCore: ddf41b9d9262f058419f9ba7598719af56c02cd3
|
||||
|
@ -755,7 +775,6 @@ SPEC CHECKSUMS:
|
|||
React-jsinspector: 1c34fea1868136ecde647bc11fae9266d4143693
|
||||
React-logger: e9f407f9fdf3f3ce7749ae6f88affe63e8446019
|
||||
react-native-blur: 50c9feabacbc5f49b61337ebc32192c6be7ec3c3
|
||||
react-native-blurhash: add4df9a937b4e021a24bc67a0714f13e0bd40b7
|
||||
react-native-cameraroll: 5b25d0be40185d02e522bf2abf8a1ba4e8faa107
|
||||
react-native-image-picker: 8cb4280e2c1efc3daeb2d9d597f9429a60472e40
|
||||
react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5
|
||||
|
@ -782,7 +801,6 @@ SPEC CHECKSUMS:
|
|||
ReactCommon: 0253d197eaa7f6689dcd3e7d5360449ab93e10df
|
||||
RNCAsyncStorage: 8616bd5a58af409453ea4e1b246521bb76578d60
|
||||
RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
|
||||
RNFastImage: bd611b5635f1e0f43c8ccf597b1ef6ee0d0f966d
|
||||
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
|
||||
RNReanimated: 6668b0587bebd4b15dd849b99e5a9c70fc12ed95
|
||||
RNScreens: ea4cd3a853063cda19a4e3c28d2e52180c80f4eb
|
||||
|
@ -790,6 +808,8 @@ SPEC CHECKSUMS:
|
|||
RNShareMenu: cb9dac548c8bf147d06f0bf07296ad51ea9f5fc3
|
||||
RNSVG: c1e76b81c76cdcd34b4e1188852892dc280eb902
|
||||
SDWebImage: 9bec4c5cdd9579e1f57104735ee0c37df274d593
|
||||
SDWebImageAVIFCoder: d759e21cf4efb640cc97250566aa556ad8bb877c
|
||||
SDWebImageSVGCoder: 6fc109f9c2a82ab44510fff410b88b1a6c271ee8
|
||||
SDWebImageWebPCoder: 18503de6621dd2c420d680e33d46bf8e1d5169b0
|
||||
Sentry: 4c9babff9034785067c896fd580b1f7de44da020
|
||||
Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "tooot",
|
||||
"version": "4.8.7",
|
||||
"version": "4.8.8",
|
||||
"description": "tooot for Mastodon",
|
||||
"author": "xmflsct <me@xmflsct.com>",
|
||||
"license": "GPL-3.0-or-later",
|
||||
|
@ -50,6 +50,7 @@
|
|||
"expo-crypto": "^12.1.0",
|
||||
"expo-file-system": "^15.1.1",
|
||||
"expo-haptics": "^12.1.0",
|
||||
"expo-image": "^1.0.0-beta.6",
|
||||
"expo-linking": "^3.3.0",
|
||||
"expo-localization": "^14.0.0",
|
||||
"expo-notifications": "^0.17.0",
|
||||
|
@ -70,14 +71,12 @@
|
|||
"react-i18next": "^12.1.4",
|
||||
"react-intl": "^6.2.7",
|
||||
"react-native": "^0.70.7",
|
||||
"react-native-blurhash": "^1.1.10",
|
||||
"react-native-fast-image": "^8.6.3",
|
||||
"react-native-flash-message": "^0.4.0",
|
||||
"react-native-gesture-handler": "~2.9.0",
|
||||
"react-native-image-picker": "^5.0.1",
|
||||
"react-native-ios-context-menu": "^1.15.3",
|
||||
"react-native-language-detection": "^0.2.2",
|
||||
"react-native-mmkv": "^2.5.1",
|
||||
"react-native-mmkv": "~2.5.1",
|
||||
"react-native-pager-view": "^6.1.2",
|
||||
"react-native-quick-base64": "^2.0.5",
|
||||
"react-native-reanimated": "^2.14.4",
|
||||
|
@ -116,7 +115,6 @@
|
|||
},
|
||||
"packageManager": "yarn@3.3.1",
|
||||
"resolutions": {
|
||||
"react-native-fast-image@^8.6.3": "patch:react-native-fast-image@npm%3A8.6.3#./.yarn/patches/react-native-fast-image-npm-8.6.3-03ee2d23c0.patch",
|
||||
"expo-av@^13.0.2": "patch:expo-av@npm%3A13.0.2#./.yarn/patches/expo-av-npm-13.0.2-7a651776f1.patch",
|
||||
"react-native-share-menu@^6.0.0": "patch:react-native-share-menu@npm%3A6.0.0#./.yarn/patches/react-native-share-menu-npm-6.0.0-f1094c3204.patch",
|
||||
"@types/react-native-share-menu@^5.0.2": "patch:@types/react-native-share-menu@npm%3A5.0.2#./.yarn/patches/@types-react-native-share-menu-npm-5.0.2-373df17ecc.patch",
|
||||
|
|
|
@ -11,6 +11,7 @@ import log from '@utils/startup/log'
|
|||
import netInfo from '@utils/startup/netInfo'
|
||||
import push from '@utils/startup/push'
|
||||
import sentry from '@utils/startup/sentry'
|
||||
import { GLOBAL } from '@utils/storage'
|
||||
import { getGlobalStorage, setAccount, setGlobalStorage } from '@utils/storage/actions'
|
||||
import { migrateFromAsyncStorage, versionStorageGlobal } from '@utils/storage/migrations/toMMKV'
|
||||
import ThemeManager from '@utils/styles/ThemeManager'
|
||||
|
@ -24,10 +25,6 @@ import { enableFreeze } from 'react-native-screens'
|
|||
import i18n from './i18n'
|
||||
import Screens from './screens'
|
||||
|
||||
export const GLOBAL: { connect?: boolean } = {
|
||||
connect: undefined
|
||||
}
|
||||
|
||||
Platform.select({
|
||||
android: LogBox.ignoreLogs(['Setting a timer for a long period of time'])
|
||||
})
|
||||
|
|
|
@ -38,7 +38,7 @@ const ComponentAccount: React.FC<PropsWithChildren & Props> = ({ account, props,
|
|||
<>
|
||||
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
||||
<GracefullyImage
|
||||
uri={{ original: account.avatar, static: account.avatar_static }}
|
||||
sources={{ default: { uri: account.avatar }, static: { uri: account.avatar_static } }}
|
||||
style={{
|
||||
width: StyleConstants.Avatar.S,
|
||||
height: StyleConstants.Avatar.S,
|
||||
|
|
|
@ -40,7 +40,7 @@ const AccountButton: React.FC<Props> = ({ account, additionalActions }) => {
|
|||
}}
|
||||
>
|
||||
<GracefullyImage
|
||||
uri={{ original: account.avatar_static }}
|
||||
sources={{ default: { uri: account.avatar_static } }}
|
||||
dimension={{
|
||||
width: StyleConstants.Font.Size.L,
|
||||
height: StyleConstants.Font.Size.L
|
||||
|
|
|
@ -8,6 +8,7 @@ import { getAccountStorage, setAccountStorage } from '@utils/storage/actions'
|
|||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { Image } from 'expo-image'
|
||||
import { chunk } from 'lodash'
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -19,7 +20,6 @@ import {
|
|||
TextInput,
|
||||
View
|
||||
} from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import EmojisContext from './Context'
|
||||
|
||||
const EmojisList = () => {
|
||||
|
@ -129,9 +129,7 @@ const EmojisList = () => {
|
|||
}}
|
||||
style={{ padding: StyleConstants.Spacing.S }}
|
||||
>
|
||||
<FastImage
|
||||
enterTransition='fadeIn'
|
||||
transitionDuration={60}
|
||||
<Image
|
||||
accessibilityLabel={t('common:customEmoji.accessibilityLabel', {
|
||||
emoji: emoji.shortcode
|
||||
})}
|
||||
|
|
|
@ -1,36 +1,26 @@
|
|||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { connectMedia } from '@utils/api/helpers/connect'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import {
|
||||
AccessibilityProps,
|
||||
Image,
|
||||
Pressable,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
View,
|
||||
ViewStyle
|
||||
} from 'react-native'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image'
|
||||
|
||||
// blurhas -> if blurhash, show before any loading succeed
|
||||
// original -> load original
|
||||
// original, remote -> if original failed, then remote
|
||||
// preview, original -> first show preview, then original
|
||||
// preview, original, remote -> first show preview, then original, if original failed, then remote
|
||||
import { Image, ImageSource, ImageStyle } from 'expo-image'
|
||||
import React, { useState } from 'react'
|
||||
import { AccessibilityProps, Pressable, StyleProp, View, ViewStyle } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
accessibilityLabel?: AccessibilityProps['accessibilityLabel']
|
||||
accessibilityHint?: AccessibilityProps['accessibilityHint']
|
||||
|
||||
hidden?: boolean
|
||||
uri: { preview?: string; original?: string; remote?: string; static?: string }
|
||||
sources: {
|
||||
preview?: ImageSource
|
||||
default?: ImageSource
|
||||
remote?: ImageSource
|
||||
static?: ImageSource
|
||||
blurhash?: string
|
||||
}
|
||||
dimension?: { width: number; height: number }
|
||||
onPress?: () => void
|
||||
style?: StyleProp<ViewStyle>
|
||||
imageStyle?: StyleProp<ImageStyle>
|
||||
imageStyle?: ImageStyle
|
||||
// For image viewer when there is no image size available
|
||||
setImageDimensions?: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
|
@ -39,49 +29,30 @@ export interface Props {
|
|||
}>
|
||||
>
|
||||
dim?: boolean
|
||||
enableLiveTextInteraction?: boolean
|
||||
}
|
||||
|
||||
const GracefullyImage = ({
|
||||
accessibilityLabel,
|
||||
accessibilityHint,
|
||||
hidden = false,
|
||||
uri,
|
||||
blurhash,
|
||||
sources,
|
||||
dimension,
|
||||
onPress,
|
||||
style,
|
||||
imageStyle,
|
||||
setImageDimensions,
|
||||
dim
|
||||
dim,
|
||||
enableLiveTextInteraction = false
|
||||
}: Props) => {
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
const { colors, theme } = useTheme()
|
||||
const [imageLoaded, setImageLoaded] = useState(false)
|
||||
const { theme } = useTheme()
|
||||
|
||||
const [currentUri, setCurrentUri] = useState<string | undefined>(uri.original || uri.remote)
|
||||
const source: { uri?: string } = {
|
||||
uri: reduceMotionEnabled && uri.static ? uri.static : currentUri
|
||||
}
|
||||
useEffect(() => {
|
||||
if (
|
||||
(uri.original ? currentUri !== uri.original : true) &&
|
||||
(uri.remote ? currentUri !== uri.remote : true)
|
||||
) {
|
||||
setCurrentUri(uri.original || uri.remote)
|
||||
}
|
||||
}, [currentUri, uri.original, uri.remote])
|
||||
|
||||
const blurhashView = () => {
|
||||
if (hidden || !imageLoaded) {
|
||||
if (blurhash) {
|
||||
return <Blurhash decodeAsync blurhash={blurhash} style={styles.placeholder} />
|
||||
} else {
|
||||
return <View style={[styles.placeholder, { backgroundColor: colors.shimmerDefault }]} />
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
const [currentSource, setCurrentSource] = useState<ImageSource | undefined>(
|
||||
sources.default || sources.remote
|
||||
)
|
||||
const source: ImageSource | undefined =
|
||||
reduceMotionEnabled && sources.static ? sources.static : currentSource
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
|
@ -91,50 +62,41 @@ const GracefullyImage = ({
|
|||
style={[style, dimension]}
|
||||
{...(onPress ? (hidden ? { disabled: true } : { onPress }) : { disabled: true })}
|
||||
>
|
||||
{uri.preview && !imageLoaded ? (
|
||||
<FastImage
|
||||
source={connectMedia({ uri: uri.preview })}
|
||||
enterTransition='fadeIn'
|
||||
transitionDuration={60}
|
||||
style={[styles.placeholder]}
|
||||
/>
|
||||
) : null}
|
||||
<FastImage
|
||||
source={connectMedia(source)}
|
||||
enterTransition={!blurhash && !uri.preview ? 'fadeIn' : 'none'}
|
||||
transitionDuration={60}
|
||||
style={[{ flex: 1 }, imageStyle]}
|
||||
onLoad={() => {
|
||||
setImageLoaded(true)
|
||||
if (setImageDimensions && source.uri) {
|
||||
Image.getSize(source.uri, (width, height) => setImageDimensions({ width, height }))
|
||||
<Image
|
||||
placeholderContentFit='cover'
|
||||
placeholder={sources.blurhash || connectMedia(sources.preview)}
|
||||
source={hidden ? undefined : connectMedia(source)}
|
||||
transition={{ duration: 80 }}
|
||||
style={{ flex: 1, ...imageStyle }}
|
||||
onLoad={event => {
|
||||
if (setImageDimensions && event.source) {
|
||||
setImageDimensions(event.source)
|
||||
}
|
||||
}}
|
||||
onError={() => {
|
||||
if (uri.original && uri.original === currentUri && uri.remote) {
|
||||
setCurrentUri(uri.remote)
|
||||
if (
|
||||
sources.default?.uri &&
|
||||
sources.default?.uri === currentSource?.uri &&
|
||||
sources.remote
|
||||
) {
|
||||
setCurrentSource(sources.remote)
|
||||
}
|
||||
}}
|
||||
enableLiveTextInteraction={enableLiveTextInteraction}
|
||||
/>
|
||||
{blurhashView()}
|
||||
{dim && theme !== 'light' ? (
|
||||
<View
|
||||
style={[
|
||||
styles.placeholder,
|
||||
{ backgroundColor: 'black', opacity: theme === 'dark_lighter' ? 0.18 : 0.36 }
|
||||
]}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
backgroundColor: 'black',
|
||||
opacity: theme === 'dark_lighter' ? 0.18 : 0.36
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
placeholder: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute'
|
||||
}
|
||||
})
|
||||
|
||||
export default GracefullyImage
|
||||
|
|
|
@ -19,12 +19,13 @@ import {
|
|||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import * as Random from 'expo-random'
|
||||
import * as Crypto from 'expo-crypto'
|
||||
import { Image } from 'expo-image'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import { debounce } from 'lodash'
|
||||
import React, { RefObject, useCallback, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'react-native'
|
||||
import { Alert, KeyboardAvoidingView, Platform, TextInput, View } from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { fromByteArray } from 'react-native-quick-base64'
|
||||
import parse from 'url-parse'
|
||||
|
@ -78,7 +79,8 @@ const ComponentInstance: React.FC<Props> = ({
|
|||
clientId,
|
||||
clientSecret,
|
||||
scopes: variables.scopes,
|
||||
redirectUri
|
||||
redirectUri,
|
||||
usePKCE: !['pawoo.net'].includes(domain)
|
||||
})
|
||||
await request.makeAuthUrlAsync(discovery)
|
||||
|
||||
|
@ -160,7 +162,7 @@ const ComponentInstance: React.FC<Props> = ({
|
|||
'admin.sign_up': false,
|
||||
'admin.report': false
|
||||
},
|
||||
key: fromByteArray(Random.getRandomBytes(16))
|
||||
key: fromByteArray(Crypto.getRandomBytes(16))
|
||||
},
|
||||
page_local: {
|
||||
showBoosts: true,
|
||||
|
@ -231,7 +233,7 @@ const ComponentInstance: React.FC<Props> = ({
|
|||
<View style={{ flexDirection: 'row' }}>
|
||||
<Image
|
||||
source={require('assets/images/welcome.png')}
|
||||
style={{ resizeMode: 'contain', flex: 1, aspectRatio: 16 / 9 }}
|
||||
style={{ flex: 1, aspectRatio: 16 / 9 }}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
|
|
|
@ -5,9 +5,9 @@ import { useGlobalStorage } from '@utils/storage/actions'
|
|||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { Image } from 'expo-image'
|
||||
import React from 'react'
|
||||
import { ColorValue, Platform, TextStyle } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
|
||||
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
||||
|
||||
|
@ -77,8 +77,8 @@ const ParseEmojis: React.FC<Props> = ({
|
|||
return (
|
||||
<CustomText key={emojiShortcode + i}>
|
||||
{i === 0 ? ' ' : undefined}
|
||||
<FastImage
|
||||
source={connectMedia({ uri: uri.trim() })}
|
||||
<Image
|
||||
source={connectMedia({ uri })}
|
||||
style={{
|
||||
width: adaptedFontsize,
|
||||
height: adaptedFontsize,
|
||||
|
|
|
@ -79,7 +79,10 @@ const TimelineConversation: React.FC<Props> = ({ conversation, queryKey, highlig
|
|||
{conversation.accounts.slice(0, 4).map(account => (
|
||||
<GracefullyImage
|
||||
key={account.id}
|
||||
uri={{ original: account.avatar, static: account.avatar_static }}
|
||||
sources={{
|
||||
default: { uri: account.avatar },
|
||||
static: { uri: account.avatar_static }
|
||||
}}
|
||||
dimension={{
|
||||
width: StyleConstants.Avatar.M,
|
||||
height:
|
||||
|
|
|
@ -12,11 +12,11 @@ import TimelinePoll from '@components/Timeline/Shared/Poll'
|
|||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { checkIsMyAccount } from '@utils/helpers/isMyAccount'
|
||||
import removeHTML from '@utils/helpers/removeHTML'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { Fragment, useRef, useState } from 'react'
|
||||
|
@ -63,10 +63,9 @@ const TimelineDefault: React.FC<Props> = ({
|
|||
const { colors } = useTheme()
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
const ownAccount = status.account?.id === accountId
|
||||
const isMyAccount = checkIsMyAccount(status.account.id)
|
||||
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
||||
preferences?.['reading:expand:spoilers'] || false
|
||||
)
|
||||
|
@ -136,7 +135,7 @@ const TimelineDefault: React.FC<Props> = ({
|
|||
const mStatus = menuStatus({ status, queryKey })
|
||||
const mInstance = menuInstance({ status, queryKey })
|
||||
|
||||
if (!ownAccount) {
|
||||
if (!isMyAccount) {
|
||||
let filterResults: FilteredProps['filterResults'] = []
|
||||
const [filterRevealed, setFilterRevealed] = useState(false)
|
||||
const hasFilterServerSide = featureCheck('filter_server_side')
|
||||
|
@ -166,7 +165,7 @@ const TimelineDefault: React.FC<Props> = ({
|
|||
value={{
|
||||
queryKey,
|
||||
status,
|
||||
ownAccount,
|
||||
isMyAccount,
|
||||
spoilerHidden,
|
||||
rawContent,
|
||||
detectedLanguage,
|
||||
|
|
|
@ -12,10 +12,10 @@ import TimelinePoll from '@components/Timeline/Shared/Poll'
|
|||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { checkIsMyAccount } from '@utils/helpers/isMyAccount'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { Fragment, useState } from 'react'
|
||||
|
@ -32,7 +32,6 @@ export interface Props {
|
|||
}
|
||||
|
||||
const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
const status = notification.status?.reblog ? notification.status.reblog : notification.status
|
||||
|
@ -42,7 +41,7 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
|||
: notification.status
|
||||
? notification.status.account
|
||||
: notification.account
|
||||
const ownAccount = notification.account?.id === accountId
|
||||
const isMyAccount = checkIsMyAccount(notification.account?.id)
|
||||
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
||||
preferences?.['reading:expand:spoilers'] || false
|
||||
)
|
||||
|
@ -109,7 +108,7 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
|||
const mStatus = menuStatus({ status: notification.status, queryKey })
|
||||
const mInstance = menuInstance({ status: notification.status, queryKey })
|
||||
|
||||
if (!ownAccount) {
|
||||
if (!isMyAccount) {
|
||||
let filterResults: FilteredProps['filterResults'] = []
|
||||
const [filterRevealed, setFilterRevealed] = useState(false)
|
||||
const hasFilterServerSide = featureCheck('filter_server_side')
|
||||
|
@ -140,7 +139,7 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
|||
value={{
|
||||
queryKey,
|
||||
status,
|
||||
ownAccount,
|
||||
isMyAccount,
|
||||
spoilerHidden
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -22,7 +22,7 @@ import { Pressable, StyleSheet, View } from 'react-native'
|
|||
import StatusContext from './Context'
|
||||
|
||||
const TimelineActions: React.FC = () => {
|
||||
const { queryKey, status, ownAccount, highlighted, disableDetails } = useContext(StatusContext)
|
||||
const { queryKey, status, isMyAccount, highlighted, disableDetails } = useContext(StatusContext)
|
||||
if (!queryKey || !status || disableDetails) return null
|
||||
|
||||
const navigationState = useNavState()
|
||||
|
@ -182,7 +182,7 @@ const TimelineActions: React.FC = () => {
|
|||
const childrenReblog = () => {
|
||||
const color = (state: boolean) => (state ? colors.green : colors.secondary)
|
||||
const disabled =
|
||||
status.visibility === 'direct' || (status.visibility === 'private' && !ownAccount)
|
||||
status.visibility === 'direct' || (status.visibility === 'private' && !isMyAccount)
|
||||
return (
|
||||
<>
|
||||
<Icon
|
||||
|
@ -196,7 +196,7 @@ const TimelineActions: React.FC = () => {
|
|||
fontStyle='S'
|
||||
style={{
|
||||
color:
|
||||
status.visibility === 'private' && !ownAccount
|
||||
status.visibility === 'private' && !isMyAccount
|
||||
? colors.disabled
|
||||
: color(status.reblogged),
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
|
@ -258,7 +258,7 @@ const TimelineActions: React.FC = () => {
|
|||
onPress={onPressReblog}
|
||||
children={childrenReblog()}
|
||||
disabled={
|
||||
status.visibility === 'direct' || (status.visibility === 'private' && !ownAccount)
|
||||
status.visibility === 'direct' || (status.visibility === 'private' && !isMyAccount)
|
||||
}
|
||||
/>
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import { StackNavigationProp } from '@react-navigation/stack'
|
|||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, View } from 'react-native'
|
||||
|
@ -207,7 +206,6 @@ const TimelineAttachment = () => {
|
|||
content={t('shared.attachment.sensitive.button')}
|
||||
overlay
|
||||
onPress={() => {
|
||||
layoutAnimation()
|
||||
setSensitiveShown(false)
|
||||
haptics('Light')
|
||||
}}
|
||||
|
|
|
@ -7,7 +7,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||
import { Audio } from 'expo-av'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { AppState, AppStateStatus, StyleSheet, View } from 'react-native'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import AttachmentAltText from './AltText'
|
||||
import { aspectRatio } from './dimensions'
|
||||
|
||||
|
@ -72,19 +71,23 @@ const AttachmentAudio: React.FC<Props> = ({ total, index, sensitiveShown, audio
|
|||
<View style={styles.overlay}>
|
||||
{sensitiveShown ? (
|
||||
audio.blurhash ? (
|
||||
<Blurhash
|
||||
blurhash={audio.blurhash}
|
||||
<GracefullyImage
|
||||
sources={{ blurhash: audio.blurhash }}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
dim
|
||||
/>
|
||||
) : null
|
||||
) : (
|
||||
<>
|
||||
{audio.preview_url ? (
|
||||
<GracefullyImage
|
||||
uri={{ original: audio.preview_url, remote: audio.preview_remote_url }}
|
||||
sources={{
|
||||
default: { uri: audio.preview_url },
|
||||
remote: { uri: audio.preview_remote_url }
|
||||
}}
|
||||
style={styles.background}
|
||||
dim
|
||||
/>
|
||||
|
|
|
@ -35,8 +35,11 @@ const AttachmentImage = ({
|
|||
<GracefullyImage
|
||||
accessibilityLabel={image.description}
|
||||
hidden={sensitiveShown}
|
||||
uri={{ original: image.preview_url, remote: image.remote_url }}
|
||||
blurhash={image.blurhash}
|
||||
sources={{
|
||||
default: { uri: image.preview_url },
|
||||
remote: { uri: image.remote_url },
|
||||
blurhash: image.blurhash
|
||||
}}
|
||||
onPress={() => navigateToImagesViewer(image.id)}
|
||||
style={{ aspectRatio: aspectRatio({ total, index, ...image.meta?.original }) }}
|
||||
dim
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Button from '@components/Button'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import openLink from '@components/openLink'
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
@ -6,7 +7,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { View } from 'react-native'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import AttachmentAltText from './AltText'
|
||||
import { aspectRatio } from './dimensions'
|
||||
|
||||
|
@ -33,8 +33,8 @@ const AttachmentUnsupported: React.FC<Props> = ({ total, index, sensitiveShown,
|
|||
}}
|
||||
>
|
||||
{attachment.blurhash ? (
|
||||
<Blurhash
|
||||
blurhash={attachment.blurhash}
|
||||
<GracefullyImage
|
||||
sources={{ blurhash: attachment.blurhash }}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Button from '@components/Button'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { connectMedia } from '@utils/api/helpers/connect'
|
||||
import { useAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
|
@ -8,7 +9,6 @@ import { Platform } from 'expo-modules-core'
|
|||
import * as ScreenOrientation from 'expo-screen-orientation'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import AttachmentAltText from './AltText'
|
||||
import { aspectRatio } from './dimensions'
|
||||
|
||||
|
@ -120,7 +120,10 @@ const AttachmentVideo: React.FC<Props> = ({
|
|||
>
|
||||
{sensitiveShown ? (
|
||||
video.blurhash ? (
|
||||
<Blurhash blurhash={video.blurhash} style={{ width: '100%', height: '100%' }} />
|
||||
<GracefullyImage
|
||||
sources={{ blurhash: video.blurhash }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
/>
|
||||
) : null
|
||||
) : !gifv || (gifv && (reduceMotionEnabled || !shouldAutoplayGifv)) ? (
|
||||
<Button
|
||||
|
|
|
@ -33,7 +33,10 @@ const TimelineAvatar: React.FC<Props> = ({ account }) => {
|
|||
onPress={() =>
|
||||
!disableOnPress && navigation.push('Tab-Shared-Account', { account: actualAccount })
|
||||
}
|
||||
uri={{ original: actualAccount.avatar, static: actualAccount.avatar_static }}
|
||||
sources={{
|
||||
default: { uri: actualAccount.avatar },
|
||||
static: { uri: actualAccount.avatar_static }
|
||||
}}
|
||||
dimension={
|
||||
disableDetails || isConversation
|
||||
? {
|
||||
|
|
|
@ -82,8 +82,7 @@ const TimelineCard: React.FC = () => {
|
|||
<>
|
||||
{status.card?.image ? (
|
||||
<GracefullyImage
|
||||
uri={{ original: status.card.image }}
|
||||
blurhash={status.card.blurhash}
|
||||
sources={{ default: { uri: status.card.image }, blurhash: status.card.blurhash }}
|
||||
style={{ flexBasis: StyleConstants.Font.LineHeight.M * 5 }}
|
||||
imageStyle={{ borderTopLeftRadius: 6, borderBottomLeftRadius: 6 }}
|
||||
dim
|
||||
|
|
|
@ -8,7 +8,7 @@ type StatusContextType = {
|
|||
|
||||
status?: Mastodon.Status
|
||||
|
||||
ownAccount?: boolean
|
||||
isMyAccount?: boolean
|
||||
spoilerHidden?: boolean
|
||||
rawContent?: React.MutableRefObject<string[]> // When highlighted, for translate, edit history
|
||||
detectedLanguage?: React.MutableRefObject<string>
|
||||
|
|
|
@ -21,7 +21,7 @@ import { Pressable, View } from 'react-native'
|
|||
import StatusContext from './Context'
|
||||
|
||||
const TimelinePoll: React.FC = () => {
|
||||
const { queryKey, status, ownAccount, spoilerHidden, disableDetails, highlighted } =
|
||||
const { queryKey, status, isMyAccount, spoilerHidden, disableDetails, highlighted } =
|
||||
useContext(StatusContext)
|
||||
if (!queryKey || !status || !status.poll) return null
|
||||
const poll = status.poll
|
||||
|
@ -72,7 +72,7 @@ const TimelinePoll: React.FC = () => {
|
|||
|
||||
const pollButton = () => {
|
||||
if (!poll.expired) {
|
||||
if (!ownAccount && !poll.voted) {
|
||||
if (!isMyAccount && !poll.voted) {
|
||||
return (
|
||||
<View style={{ marginRight: StyleConstants.Spacing.S }}>
|
||||
<Button
|
||||
|
|
|
@ -60,6 +60,9 @@ const Timeline: React.FC<Props> = ({
|
|||
const { colors, theme } = useTheme()
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
|
||||
const firstLoad = useSharedValue<boolean>(!readMarker || disableRefresh)
|
||||
const shouldAutoFetch = useSharedValue<boolean>(!!readMarker && !disableRefresh)
|
||||
|
||||
const { data, refetch, isFetching, isLoading, isRefetching, fetchNextPage, isFetchingNextPage } =
|
||||
useTimelineQuery({
|
||||
...queryKey[1],
|
||||
|
@ -68,7 +71,13 @@ const Timeline: React.FC<Props> = ({
|
|||
notifyOnChangeProps: Platform.select({
|
||||
ios: ['dataUpdatedAt', 'isFetching'],
|
||||
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
|
||||
})
|
||||
}),
|
||||
onSuccess: () => {
|
||||
if (!firstLoad.value) {
|
||||
firstLoad.value = true
|
||||
fetchingType.value = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -90,6 +99,9 @@ const Timeline: React.FC<Props> = ({
|
|||
(curr, prev) => {
|
||||
if (curr !== null && prev === null) {
|
||||
notifiedFetchedNotice.value = false
|
||||
if (curr === 0) {
|
||||
shouldAutoFetch.value = false
|
||||
}
|
||||
}
|
||||
},
|
||||
[fetchedCount]
|
||||
|
@ -129,19 +141,41 @@ const Timeline: React.FC<Props> = ({
|
|||
fetchingType.value = 2
|
||||
} else if (y <= SEPARATION_Y_1) {
|
||||
fetchingType.value = 1
|
||||
shouldAutoFetch.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[isFetching]
|
||||
)
|
||||
useAnimatedReaction(
|
||||
() => scrollY.value < 600,
|
||||
(curr, prev) => {
|
||||
if (
|
||||
curr === true &&
|
||||
prev === false &&
|
||||
!isFetchingPrev.value &&
|
||||
fetchingType.value === 0 &&
|
||||
shouldAutoFetch.value &&
|
||||
Platform.OS === 'ios'
|
||||
) {
|
||||
fetchingType.value = 1
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const latestMarker = useRef<string>()
|
||||
const updateMarkers = useCallback(
|
||||
throttle(
|
||||
() => readMarker && setAccountStorage([{ key: readMarker, value: latestMarker.current }]),
|
||||
1000 * 15
|
||||
),
|
||||
throttle(() => {
|
||||
if (readMarker) {
|
||||
const currentMarker = getAccountStorage.string(readMarker) || '0'
|
||||
if ((latestMarker.current || '0') > currentMarker) {
|
||||
setAccountStorage([{ key: readMarker, value: latestMarker.current }])
|
||||
} else {
|
||||
// setAccountStorage([{ key: readMarker, value: '105250709762254246' }])
|
||||
}
|
||||
}
|
||||
}, 1000 * 15),
|
||||
[]
|
||||
)
|
||||
readMarker &&
|
||||
|
@ -159,24 +193,14 @@ const Timeline: React.FC<Props> = ({
|
|||
{
|
||||
viewabilityConfig: {
|
||||
minimumViewTime: 300,
|
||||
itemVisiblePercentThreshold: 80,
|
||||
waitForInteraction: true
|
||||
itemVisiblePercentThreshold: 10,
|
||||
waitForInteraction: false
|
||||
},
|
||||
onViewableItemsChanged: ({ viewableItems }) => {
|
||||
const marker = readMarker ? getAccountStorage.string(readMarker) : undefined
|
||||
|
||||
const firstItemId = viewableItems.filter(item => item.isViewable)[0]?.item.id
|
||||
if (
|
||||
!isFetchingPrev.value &&
|
||||
!isRefetching &&
|
||||
firstItemId &&
|
||||
firstItemId > (marker || '0')
|
||||
) {
|
||||
if (!isFetchingPrev.value && !isRefetching && firstItemId) {
|
||||
latestMarker.current = firstItemId
|
||||
updateMarkers()
|
||||
} else {
|
||||
// latestMarker.current = '105250709762254246'
|
||||
// updateMarkers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useNavigation } from '@react-navigation/native'
|
|||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { checkIsMyAccount } from '@utils/helpers/isMyAccount'
|
||||
import { TabSharedStackParamList, useNavState } from '@utils/navigation/navigators'
|
||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||
import {
|
||||
|
@ -15,7 +16,7 @@ import {
|
|||
MutationVarsTimelineUpdateAccountProperty,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { getAccountStorage, getReadableAccounts, useAccountStorage } from '@utils/storage/actions'
|
||||
import { getAccountStorage, getReadableAccounts } from '@utils/storage/actions'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Platform } from 'react-native'
|
||||
|
@ -40,19 +41,18 @@ const menuAccount = ({
|
|||
|
||||
const [enabled, setEnabled] = useState(openChange)
|
||||
useEffect(() => {
|
||||
if (!ownAccount && enabled === false && openChange === true) {
|
||||
if (!isMyAccount && enabled === false && openChange === true) {
|
||||
setEnabled(true)
|
||||
}
|
||||
}, [openChange, enabled])
|
||||
const { data: fetchedAccount } = useAccountQuery({ account, _local: true, options: { enabled } })
|
||||
const actualAccount = status?._remote ? fetchedAccount : account
|
||||
const isMyAccount = checkIsMyAccount(actualAccount?.id)
|
||||
const { data, isFetched } = useRelationshipQuery({
|
||||
id: actualAccount?.id,
|
||||
options: { enabled: !!actualAccount?.id && enabled }
|
||||
})
|
||||
|
||||
const ownAccount = useAccountStorage.string('auth.account.id')['0'] === actualAccount?.id
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const timelineMutation = useTimelineMutation({
|
||||
onSuccess: (_, params) => {
|
||||
|
@ -134,7 +134,7 @@ const menuAccount = ({
|
|||
|
||||
if (!account) return []
|
||||
|
||||
if (!ownAccount && Platform.OS !== 'android' && type !== 'account') {
|
||||
if (!isMyAccount && Platform.OS !== 'android' && type !== 'account') {
|
||||
menus[0].push({
|
||||
type: 'item',
|
||||
key: 'account-following',
|
||||
|
@ -165,7 +165,7 @@ const menuAccount = ({
|
|||
})
|
||||
}
|
||||
|
||||
if (!ownAccount) {
|
||||
if (!isMyAccount) {
|
||||
menus[0].push({
|
||||
type: 'item',
|
||||
key: 'account-list',
|
||||
|
|
|
@ -4,6 +4,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
|||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { checkIsMyAccount } from '@utils/helpers/isMyAccount'
|
||||
import { RootStackParamList, useNavState } from '@utils/navigation/navigators'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
|
@ -57,9 +58,8 @@ const menuStatus = ({
|
|||
|
||||
const menus: ContextMenu = []
|
||||
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const [accountAcct] = useAccountStorage.string('auth.account.acct')
|
||||
const ownAccount = accountId === status.account?.id
|
||||
const isMyAccount = checkIsMyAccount(status.account.id)
|
||||
|
||||
const canEditPost = featureCheck('edit_post')
|
||||
|
||||
|
@ -98,7 +98,7 @@ const menuStatus = ({
|
|||
},
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
hidden: !ownAccount || !canEditPost
|
||||
hidden: !isMyAccount || !canEditPost
|
||||
},
|
||||
title: t('componentContextMenu:status.edit.action'),
|
||||
icon: 'square.and.pencil'
|
||||
|
@ -142,7 +142,7 @@ const menuStatus = ({
|
|||
),
|
||||
disabled: false,
|
||||
destructive: true,
|
||||
hidden: !ownAccount
|
||||
hidden: !isMyAccount
|
||||
},
|
||||
title: t('componentContextMenu:status.deleteEdit.action'),
|
||||
icon: 'pencil.and.outline'
|
||||
|
@ -171,7 +171,7 @@ const menuStatus = ({
|
|||
),
|
||||
disabled: false,
|
||||
destructive: true,
|
||||
hidden: !ownAccount
|
||||
hidden: !isMyAccount
|
||||
},
|
||||
title: t('componentContextMenu:status.delete.action'),
|
||||
icon: 'trash'
|
||||
|
@ -195,7 +195,7 @@ const menuStatus = ({
|
|||
disabled: false,
|
||||
destructive: false,
|
||||
hidden:
|
||||
!ownAccount &&
|
||||
!isMyAccount &&
|
||||
queryKey[1].page !== 'Notifications' &&
|
||||
!status.mentions?.find(
|
||||
mention => mention.acct === accountAcct && mention.username === accountAcct
|
||||
|
@ -224,7 +224,7 @@ const menuStatus = ({
|
|||
}),
|
||||
disabled: status.visibility !== 'public' && status.visibility !== 'unlisted',
|
||||
destructive: false,
|
||||
hidden: !ownAccount
|
||||
hidden: !isMyAccount
|
||||
},
|
||||
title: t('componentContextMenu:status.pin.action', {
|
||||
defaultValue: 'false',
|
||||
|
@ -236,8 +236,9 @@ const menuStatus = ({
|
|||
type: 'item',
|
||||
key: 'status-filter',
|
||||
props: {
|
||||
onSelect: () =>
|
||||
// @ts-ignore
|
||||
onSelect: () => navigation.navigate('Tab-Shared-Filter', { source: 'status', status, queryKey }),
|
||||
navigation.navigate('Tab-Shared-Filter', { source: 'status', status, queryKey }),
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
hidden: !('filtered' in status)
|
||||
|
|
|
@ -46,17 +46,9 @@ const openLink = async (url: string, navigation?: any) => {
|
|||
|
||||
// If an account can be found
|
||||
if (match?.account) {
|
||||
if (!match.account._remote && match.account.id) {
|
||||
handleNavigation('Tab-Shared-Account', { account: match.account.id })
|
||||
return
|
||||
}
|
||||
|
||||
let response: Mastodon.Account | undefined = undefined
|
||||
|
||||
const queryKey: QueryKeyAccount = [
|
||||
'Account',
|
||||
{ id: match.account.id, url: url, _remote: match.account._remote }
|
||||
]
|
||||
const queryKey: QueryKeyAccount = ['Account', { url: url, _remote: match.account._remote }]
|
||||
const cache = queryClient.getQueryData<Mastodon.Status>(queryKey)
|
||||
|
||||
if (cache) {
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"poll": "S'ha acabat una enquesta en què havies participat",
|
||||
"reblog": {
|
||||
"default": "{{name}} ha impulsat",
|
||||
"myself": "",
|
||||
"myself": "He impulsat",
|
||||
"notification": "{{name}} ha impulsat la teva publicació"
|
||||
},
|
||||
"update": "L'impuls ha sigut editat",
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"poll": "Eine Umfrage, an der du teilgenommen hast, ist beendet",
|
||||
"reblog": {
|
||||
"default": "{{name}} hat geboostet",
|
||||
"myself": "",
|
||||
"myself": "Von mir geboosted",
|
||||
"notification": "{{name}} hat deinen Tröt geboostet"
|
||||
},
|
||||
"update": "Boost wurde bearbeitet",
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"poll": "Una encuesta en la que has votado ha terminado",
|
||||
"reblog": {
|
||||
"default": "{{name}} ha impulsado",
|
||||
"myself": "",
|
||||
"myself": "He impulsado",
|
||||
"notification": "{{name}} ha impulsado tu toot"
|
||||
},
|
||||
"update": "El impulso ha sido editado",
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"poll": "Erantzun zenuen inkesta bat amaitu da",
|
||||
"reblog": {
|
||||
"default": "{{name}}-(e)k bultzatu du",
|
||||
"myself": "",
|
||||
"myself": "Bultzatu dut",
|
||||
"notification": "{{name}}-(e)k zure tuta bultzatu du"
|
||||
},
|
||||
"update": "Bultzada editatua izan da",
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
"refetch": "更新",
|
||||
"fetching": "新しいトゥートを取得しています...",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
"none": "新しいトゥートはありません",
|
||||
"found": "{{count}}トゥートを取得"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
|
@ -33,7 +33,7 @@
|
|||
"poll": "アンケートが終了しました",
|
||||
"reblog": {
|
||||
"default": "{{name}}さんがブースト",
|
||||
"myself": "",
|
||||
"myself": "ブーストしました",
|
||||
"notification": "{{name}}さんがあなたのトゥートをブーストしました"
|
||||
},
|
||||
"update": "ブーストしたトゥートが編集されました",
|
||||
|
|
|
@ -73,13 +73,13 @@
|
|||
"name": "設定"
|
||||
},
|
||||
"preferencesFilters": {
|
||||
"name": ""
|
||||
"name": "すべてのコンテンツフィルター"
|
||||
},
|
||||
"preferencesFilterAdd": {
|
||||
"name": "フィルターを作成"
|
||||
},
|
||||
"preferencesFilterEdit": {
|
||||
"name": ""
|
||||
"name": "フィルターを編集"
|
||||
},
|
||||
"profile": {
|
||||
"name": "プロフィールを編集"
|
||||
|
@ -136,7 +136,7 @@
|
|||
},
|
||||
"preferences": {
|
||||
"visibility": {
|
||||
"title": "",
|
||||
"title": "デフォルトの投稿の表示",
|
||||
"options": {
|
||||
"public": "公開",
|
||||
"unlisted": "未収載",
|
||||
|
@ -144,40 +144,40 @@
|
|||
}
|
||||
},
|
||||
"sensitive": {
|
||||
"title": ""
|
||||
"title": "メディアを常に閲覧注意としてマークする"
|
||||
},
|
||||
"media": {
|
||||
"title": "",
|
||||
"title": "メディアの表示",
|
||||
"options": {
|
||||
"default": "",
|
||||
"show_all": "",
|
||||
"hide_all": ""
|
||||
"default": "閲覧注意としてマークされたメディアを隠す",
|
||||
"show_all": "メディアを常に表示する",
|
||||
"hide_all": "メディアを常に隠す"
|
||||
}
|
||||
},
|
||||
"spoilers": {
|
||||
"title": ""
|
||||
"title": "閲覧注意としてマークされたトゥートを常に展開する"
|
||||
},
|
||||
"autoplay_gifs": {
|
||||
"title": ""
|
||||
"title": "トゥートでGIFを自動再生"
|
||||
},
|
||||
"filters": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "コンテンツフィルター",
|
||||
"content": "{{count}} がアクティブです"
|
||||
},
|
||||
"web_only": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "アップデート設定",
|
||||
"description": "以下の設定は、web UI を使用してのみ更新できます"
|
||||
}
|
||||
},
|
||||
"preferencesFilters": {
|
||||
"expired": "期限切れ",
|
||||
"keywords_one": "",
|
||||
"keywords_other": "",
|
||||
"statuses_one": "",
|
||||
"statuses_other": "",
|
||||
"context": "",
|
||||
"keywords_one": "{{count}}件のキーワード",
|
||||
"keywords_other": "{{count}}件のキーワード",
|
||||
"statuses_one": "{{count}}トゥート",
|
||||
"statuses_other": "{{count}}トゥート",
|
||||
"context": "<0 />で適用",
|
||||
"contexts": {
|
||||
"home": "",
|
||||
"home": "フォロー中とリスト",
|
||||
"notifications": "通知",
|
||||
"public": "連合",
|
||||
"thread": "会話",
|
||||
|
@ -196,22 +196,22 @@
|
|||
"604800": "1週間後",
|
||||
"18144000": "1ヶ月後"
|
||||
},
|
||||
"context": "",
|
||||
"context": "適用:",
|
||||
"contexts": {
|
||||
"home": "",
|
||||
"home": "フォロー中とリスト",
|
||||
"notifications": "通知",
|
||||
"public": "連合タイムライン",
|
||||
"thread": "",
|
||||
"account": ""
|
||||
"thread": "会話表示",
|
||||
"account": "プロフィール表示"
|
||||
},
|
||||
"action": "",
|
||||
"action": "一致した時",
|
||||
"actions": {
|
||||
"warn": "",
|
||||
"hide": ""
|
||||
"hide": "完全に非表示にする"
|
||||
},
|
||||
"keywords": "",
|
||||
"keywords": "これらのキーワードと一致",
|
||||
"keyword": "キーワード",
|
||||
"statuses": ""
|
||||
"statuses": "これらのトゥートと一致"
|
||||
},
|
||||
"profile": {
|
||||
"feedback": {
|
||||
|
@ -401,7 +401,7 @@
|
|||
},
|
||||
"filter": {
|
||||
"name": "フィルターに追加",
|
||||
"existed": ""
|
||||
"existed": "フィルター内に存在"
|
||||
},
|
||||
"history": {
|
||||
"name": "編集履歴"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"action_false": "사용자 팔로우",
|
||||
"action_true": "사용자 팔로우 해제"
|
||||
},
|
||||
"inLists": "",
|
||||
"inLists": "사용자를 포함한 리스트 ...",
|
||||
"showBoosts": {
|
||||
"action_false": "사용자의 부스트 보이기",
|
||||
"action_true": "사용자의 부스트 숨기기"
|
||||
|
@ -16,12 +16,12 @@
|
|||
"action_true": "사용자 뮤트 해제"
|
||||
},
|
||||
"followAs": {
|
||||
"trigger": "",
|
||||
"trigger": "특정 계정에서 팔로우 ...",
|
||||
"succeed_default": "@{{source}} 계정에서 @{{target}} 계정을 팔로우 했어요.",
|
||||
"succeed_locked": "@{{source}} 계정에서 @{{target}} 계정의 팔로우 승인을 요청했어요.",
|
||||
"failed": "특정 계정에서 팔로우"
|
||||
},
|
||||
"blockReport": "",
|
||||
"blockReport": "차단 및 신고",
|
||||
"block": {
|
||||
"action_false": "사용자 차단",
|
||||
"action_true": "사용자 차단 해제",
|
||||
|
@ -56,11 +56,11 @@
|
|||
},
|
||||
"hashtag": {
|
||||
"follow": {
|
||||
"action_false": "",
|
||||
"action_true": ""
|
||||
"action_false": "팔로우",
|
||||
"action_true": "팔로우 해제"
|
||||
},
|
||||
"filter": {
|
||||
"action": ""
|
||||
"action": "해시태그 필터 ..."
|
||||
}
|
||||
},
|
||||
"share": {
|
||||
|
@ -99,8 +99,8 @@
|
|||
"action_true": "툿 고정 해제"
|
||||
},
|
||||
"filter": {
|
||||
"action_false": "",
|
||||
"action_true": ""
|
||||
"action_false": "툿 필터 ...",
|
||||
"action_true": "필터 관리 ..."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,10 +17,10 @@
|
|||
"refresh": {
|
||||
"fetchPreviousPage": "이 시점에 이어서 불러오기",
|
||||
"refetch": "최신 내용 불러오기",
|
||||
"fetching": "",
|
||||
"fetching": "새로운 툿 불러오는 중 ...",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
"none": "새로운 툿이 없어요",
|
||||
"found": "새로운 툿 {{count}}개를 불러왔어요"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
|
@ -33,7 +33,7 @@
|
|||
"poll": "내가 참여한 투표가 끝났어요",
|
||||
"reblog": {
|
||||
"default": "{{name}} 님이 부스트했어요",
|
||||
"myself": "",
|
||||
"myself": "내가 부스트함",
|
||||
"notification": "{{name}} 님이 내 툿을 부스트했어요"
|
||||
},
|
||||
"update": "부스트한 툿이 수정됨",
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"title": "작성을 취소할까요?",
|
||||
"buttons": {
|
||||
"save": "임시 저장",
|
||||
"delete": "임시 저장한 내용 삭제"
|
||||
"delete": "작성중인 내용 삭제"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -70,16 +70,16 @@
|
|||
"name": "푸시 알림"
|
||||
},
|
||||
"preferences": {
|
||||
"name": ""
|
||||
"name": "설정"
|
||||
},
|
||||
"preferencesFilters": {
|
||||
"name": ""
|
||||
"name": "콘텐츠 필터"
|
||||
},
|
||||
"preferencesFilterAdd": {
|
||||
"name": ""
|
||||
"name": "필터 만들기"
|
||||
},
|
||||
"preferencesFilterEdit": {
|
||||
"name": ""
|
||||
"name": "필터 편집"
|
||||
},
|
||||
"profile": {
|
||||
"name": "프로필 편집"
|
||||
|
@ -136,81 +136,81 @@
|
|||
},
|
||||
"preferences": {
|
||||
"visibility": {
|
||||
"title": "",
|
||||
"title": "공개 범위 기본값",
|
||||
"options": {
|
||||
"public": "",
|
||||
"unlisted": "",
|
||||
"private": ""
|
||||
"public": "공개",
|
||||
"unlisted": "공개 타임라인에 비표시",
|
||||
"private": "팔로워만"
|
||||
}
|
||||
},
|
||||
"sensitive": {
|
||||
"title": ""
|
||||
"title": "민감한 미디어 표시를 기본값으로 사용"
|
||||
},
|
||||
"media": {
|
||||
"title": "",
|
||||
"title": "미디어 표시",
|
||||
"options": {
|
||||
"default": "",
|
||||
"show_all": "",
|
||||
"hide_all": ""
|
||||
"default": "민감한 미디어 숨기기",
|
||||
"show_all": "모든 미디어 표시",
|
||||
"hide_all": "모든 미디어 숨기기"
|
||||
}
|
||||
},
|
||||
"spoilers": {
|
||||
"title": ""
|
||||
"title": "스포일러 경고가 있는 툿 자동으로 펼치기"
|
||||
},
|
||||
"autoplay_gifs": {
|
||||
"title": ""
|
||||
"title": "툿에 포함된 GIF 자동 재생"
|
||||
},
|
||||
"filters": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "콘텐츠 필터",
|
||||
"content": "{{count}}개 활성화"
|
||||
},
|
||||
"web_only": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "추가 설정",
|
||||
"description": "일부 설정은 웹 UI에서만 변경할 수 있어요"
|
||||
}
|
||||
},
|
||||
"preferencesFilters": {
|
||||
"expired": "",
|
||||
"keywords_one": "",
|
||||
"keywords_other": "",
|
||||
"statuses_one": "",
|
||||
"statuses_other": "",
|
||||
"context": "",
|
||||
"expired": "만료됨",
|
||||
"keywords_one": "{{count}}개의 단어",
|
||||
"keywords_other": "{{count}}개의 단어",
|
||||
"statuses_one": "{{count}}개의 툿",
|
||||
"statuses_other": "{{count}}개의 툿",
|
||||
"context": "<0 />에 적용됨",
|
||||
"contexts": {
|
||||
"home": "",
|
||||
"notifications": "",
|
||||
"public": "",
|
||||
"thread": "",
|
||||
"account": ""
|
||||
"home": "팔로우와 리스트",
|
||||
"notifications": "알림",
|
||||
"public": "연합",
|
||||
"thread": "대화",
|
||||
"account": "프로필"
|
||||
}
|
||||
},
|
||||
"preferencesFilter": {
|
||||
"name": "",
|
||||
"expiration": "",
|
||||
"name": "이름",
|
||||
"expiration": "만료일",
|
||||
"expirationOptions": {
|
||||
"0": "",
|
||||
"1800": "",
|
||||
"3600": "",
|
||||
"43200": "",
|
||||
"86400": "",
|
||||
"604800": "",
|
||||
"18144000": ""
|
||||
"0": "만료하지 않음",
|
||||
"1800": "30분 후",
|
||||
"3600": "1시간 후",
|
||||
"43200": "12시간 후",
|
||||
"86400": "1일 후",
|
||||
"604800": "1주일 후",
|
||||
"18144000": "1개월 후"
|
||||
},
|
||||
"context": "",
|
||||
"context": "적용할 대상",
|
||||
"contexts": {
|
||||
"home": "",
|
||||
"notifications": "",
|
||||
"public": "",
|
||||
"thread": "",
|
||||
"account": ""
|
||||
"home": "팔로우와 리스트",
|
||||
"notifications": "알림",
|
||||
"public": "연합 타임라인",
|
||||
"thread": "대화 내용",
|
||||
"account": "프로필 내용"
|
||||
},
|
||||
"action": "",
|
||||
"action": "필터 동작",
|
||||
"actions": {
|
||||
"warn": "",
|
||||
"hide": ""
|
||||
"warn": "내용만 가리고 펼쳐볼 수 있게 하기",
|
||||
"hide": "완전히 숨기기"
|
||||
},
|
||||
"keywords": "",
|
||||
"keyword": "",
|
||||
"keywords": "필터할 단어",
|
||||
"keyword": "단어",
|
||||
"statuses": ""
|
||||
},
|
||||
"profile": {
|
||||
|
@ -400,7 +400,7 @@
|
|||
"name": "<0 /><1>의 미디어</1>"
|
||||
},
|
||||
"filter": {
|
||||
"name": "",
|
||||
"name": "필터에 추가",
|
||||
"existed": ""
|
||||
},
|
||||
"history": {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"buttons": {
|
||||
"OK": "OK",
|
||||
"apply": "Bruk",
|
||||
"cancel": "Avbryt",
|
||||
"discard": "Forkast",
|
||||
"continue": "Fortsett",
|
||||
"create": "Opprett",
|
||||
"delete": "Slett",
|
||||
"done": "Fullført",
|
||||
"confirm": "Bekreft"
|
||||
},
|
||||
"customEmoji": {
|
||||
"accessibilityLabel": "Tilpasset emoji {{emoji}}"
|
||||
},
|
||||
"message": {
|
||||
"success": {
|
||||
"message": "{{function}} var vellykket"
|
||||
},
|
||||
"warning": {
|
||||
"message": ""
|
||||
},
|
||||
"error": {
|
||||
"message": "{{function}} feilet, vennligst prøv igjen"
|
||||
}
|
||||
},
|
||||
"separator": ", ",
|
||||
"discard": {
|
||||
"title": "Endringene er ikke lagret",
|
||||
"message": "Dine endringer er ikke lagret. Angrer du på endringene?"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"accessibilityHint": "Handlinger for tut, for eksempel dens bruker, tut selv",
|
||||
"account": {
|
||||
"title": "Brukerhandlinger",
|
||||
"following": {
|
||||
"action_false": "Følg bruker",
|
||||
"action_true": "Slutt å følge"
|
||||
},
|
||||
"inLists": "Lister som inneholder bruker ...",
|
||||
"showBoosts": {
|
||||
"action_false": "Vis brukerens booster",
|
||||
"action_true": "Skjul brukernes booster"
|
||||
},
|
||||
"mute": {
|
||||
"action_false": "Demp bruker",
|
||||
"action_true": "Opphev demping av bruker"
|
||||
},
|
||||
"followAs": {
|
||||
"trigger": "Følg som ...",
|
||||
"succeed_default": "Følger nå @{{target}} med @{{source}}",
|
||||
"succeed_locked": "Sendt følge forespørsel til @{{target}} med {{source}}, venter på godkjenning",
|
||||
"failed": "Følg som"
|
||||
},
|
||||
"blockReport": "Blokker og rapporter",
|
||||
"block": {
|
||||
"action_false": "Blokker bruker",
|
||||
"action_true": "Fjern blokkering av brukker",
|
||||
"alert": {
|
||||
"title": "Bekreft blokkering av @{{username}}?"
|
||||
}
|
||||
},
|
||||
"reports": {
|
||||
"action": "Rapporter og blokker bruker",
|
||||
"alert": {
|
||||
"title": "Bekreft blokkering av @{{username}}?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"at": {
|
||||
"direct": "Direktemelding",
|
||||
"public": "Offentlig melding"
|
||||
},
|
||||
"copy": {
|
||||
"action": "Kopier tut",
|
||||
"succeed": "Kopiert"
|
||||
},
|
||||
"instance": {
|
||||
"title": "Instansaktivitet",
|
||||
"block": {
|
||||
"action": "Blokker instans {{instance}}",
|
||||
"alert": {
|
||||
"title": "Bekreft blokkering av @{{instance}}?",
|
||||
"message": "For det meste kan du dempe eller blokkere visse brukere.\n\nEtter at du har blokkert, vil alt innholdet fra denne forekomsten bli fjernet!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hashtag": {
|
||||
"follow": {
|
||||
"action_false": "Følg",
|
||||
"action_true": "Slutt å følge"
|
||||
},
|
||||
"filter": {
|
||||
"action": "Filtrer emnet ..."
|
||||
}
|
||||
},
|
||||
"share": {
|
||||
"status": {
|
||||
"action": "Del tut"
|
||||
},
|
||||
"account": {
|
||||
"action": "Del bruker"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"title": "Tut handlinger",
|
||||
"edit": {
|
||||
"action": "Rediger tut"
|
||||
},
|
||||
"delete": {
|
||||
"action": "Slett tut",
|
||||
"alert": {
|
||||
"title": "Bekreft sletting?",
|
||||
"message": "Alle booster og favoritter blir slettet, inkludert alle svar."
|
||||
}
|
||||
},
|
||||
"deleteEdit": {
|
||||
"action": "Slett tut og repost",
|
||||
"alert": {
|
||||
"title": "Bekreft sletting og repost?",
|
||||
"message": "Alle booster og favoritter blir slettet, inkludert alle svar."
|
||||
}
|
||||
},
|
||||
"mute": {
|
||||
"action_false": "Demp tut og svar",
|
||||
"action_true": "Avdemp tut og svar"
|
||||
},
|
||||
"pin": {
|
||||
"action_false": "Fest tut",
|
||||
"action_true": "Avfest tut"
|
||||
},
|
||||
"filter": {
|
||||
"action_false": "Filtrer tut...",
|
||||
"action_true": "Behandle filtere..."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"frequentUsed": "Ofte brukt"
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"server": {
|
||||
"textInput": {
|
||||
"placeholder": "Instansens domene"
|
||||
},
|
||||
"whitelisted": "Dette kan være en hvitelistet forekomst som tooot ikke kan hente data fra før du logger inn.",
|
||||
"button": "Logg inn",
|
||||
"information": {
|
||||
"name": "Navn",
|
||||
"accounts": "Brukere",
|
||||
"statuses": "Tuter",
|
||||
"domains": "Universum"
|
||||
},
|
||||
"disclaimer": {
|
||||
"base": "Innlogging bruker systemnettleser mens du logger inn på, din kontoinformasjon vil ikke være synlig for toot-appen."
|
||||
},
|
||||
"terms": {
|
||||
"base": "Ved å logge inn godtar du <0>retningslinjer for personvern</0> og <1>vilkårene for bruk</1>."
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"alert": {
|
||||
"title": "Logget på denne instansen",
|
||||
"message": "Du kan logge inn på en annen konto, og beholde eksisterende pålogging"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"title": "Velg mediekilde",
|
||||
"message": "Media EXIF-data er ikke lastet opp",
|
||||
"options": {
|
||||
"image": "Last opp bilder",
|
||||
"image_max": "Last opp bilder (maks {{max}})",
|
||||
"video": "Last opp video",
|
||||
"video_max": "Last opp video (maks {{max}})"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"HTML": {
|
||||
"accessibilityHint": "Trykk for å utvide eller skjule innhold",
|
||||
"expanded": "{{hint}}{{moreLines}}",
|
||||
"moreLines": " ({{count}} flere linjer)",
|
||||
"defaultHint": "Lang tut"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"follow": {
|
||||
"function": "Følg bruker"
|
||||
},
|
||||
"block": {
|
||||
"function": "Blokker bruker"
|
||||
},
|
||||
"button": {
|
||||
"error": "Feil med lasting",
|
||||
"blocked_by": "Blokkert av bruker",
|
||||
"blocking": "Fjern blokkering",
|
||||
"following": "Slutt å følge",
|
||||
"requested": "Avbryt forespørsel",
|
||||
"default": "Følg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
{
|
||||
"empty": {
|
||||
"error": {
|
||||
"message": "Lasting mislyktes",
|
||||
"button": "Prøv igjen"
|
||||
},
|
||||
"success": {
|
||||
"message": "Tidslinjen er tom"
|
||||
}
|
||||
},
|
||||
"end": {
|
||||
"message": "Slutt, hva med en kopp med <0 />"
|
||||
},
|
||||
"lookback": {
|
||||
"message": "Sist lest"
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Nyere herfra",
|
||||
"refetch": "Til nyeste",
|
||||
"fetching": "Henter nyere tuter ...",
|
||||
"fetched": {
|
||||
"none": "Ingen nyere tut",
|
||||
"found": "Hentet {{count}} tuter"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
"pinned": "Festet",
|
||||
"favourite": "{{name}} favoriserte din tut",
|
||||
"status": "{name} la nettopp ut",
|
||||
"follow": "{{name}} følger deg",
|
||||
"follow_request": "{{name}} ba om å følge deg",
|
||||
"poll": "En avstemming du har stemt på er avsluttet",
|
||||
"reblog": {
|
||||
"default": "{{name}} boostet",
|
||||
"myself": "Jeg boostet",
|
||||
"notification": "{{name}} boostet ditt tut"
|
||||
},
|
||||
"update": "Reblogg har blitt redigert",
|
||||
"admin.sign_up": "{{name}} ble med i instansen",
|
||||
"admin.report": "{{name}} rapportert:"
|
||||
},
|
||||
"actions": {
|
||||
"reply": {
|
||||
"accessibilityLabel": "Svar på tut"
|
||||
},
|
||||
"reblogged": {
|
||||
"accessibilityLabel": "Boost denne tut",
|
||||
"function": "Boost tut",
|
||||
"options": {
|
||||
"title": "Velg synlighet for boost",
|
||||
"public": "Offentlig boost",
|
||||
"unlisted": "Fjern boost"
|
||||
}
|
||||
},
|
||||
"favourited": {
|
||||
"accessibilityLabel": "Legge tut til dine favoritter",
|
||||
"function": "Favoritt tut"
|
||||
},
|
||||
"bookmarked": {
|
||||
"accessibilityLabel": "Legg tut til bokmerker",
|
||||
"function": "Bokmerk tut"
|
||||
},
|
||||
"openReport": "Åpne rapport"
|
||||
},
|
||||
"actionsUsers": {
|
||||
"reblogged_by": {
|
||||
"accessibilityLabel": "{{count}} brukere har boostet tuten",
|
||||
"accessibilityHint": "Trykk for å bli kjent med brukerne",
|
||||
"text": "$t(screenTabs:shared.users.statuses.reblogged_by)"
|
||||
},
|
||||
"favourited_by": {
|
||||
"accessibilityLabel": "{{count}} brukere har boostet tuten",
|
||||
"accessibilityHint": "Trykk for å bli kjent med brukerne",
|
||||
"text": "$t(screenTabs:shared.users.statuses.favourited_by)"
|
||||
},
|
||||
"history": {
|
||||
"accessibilityLabel": "Tut har blitt redigert {{count}} ganger",
|
||||
"accessibilityHint": "Trykk for å vise hele redigeringsloggen",
|
||||
"text_one": "{{count}} rediger",
|
||||
"text_other": "{{count}} redigeringer"
|
||||
}
|
||||
},
|
||||
"attachment": {
|
||||
"sensitive": {
|
||||
"button": "Vis sensitivt media"
|
||||
},
|
||||
"unsupported": {
|
||||
"text": "Feil med lasting",
|
||||
"button": "Prøv ekstern lenke"
|
||||
},
|
||||
"altText": "Alternativ tekst"
|
||||
},
|
||||
"avatar": {
|
||||
"accessibilityLabel": "Profilbilde av {{name}}",
|
||||
"accessibilityHint": "Trykk for å gå til {{name}} sin side"
|
||||
},
|
||||
"content": {
|
||||
"expandHint": "Skjult innhold"
|
||||
},
|
||||
"filtered": {
|
||||
"reveal": "Vis likevel",
|
||||
"match_v1": "Filtrert: {{phrase}}.",
|
||||
"match_v2_one": "Filtrert av {{filters}}.",
|
||||
"match_v2_other": "Filtrert med {{count}} filtre, {{filters}}."
|
||||
},
|
||||
"fullConversation": "Vis samtaler",
|
||||
"translate": {
|
||||
"default": "Oversett",
|
||||
"succeed": "Oversatt med {{provider}} fra {{source}}",
|
||||
"failed": "Oversettelse feilet",
|
||||
"source_not_supported": "dette språket er ikke støttet",
|
||||
"target_not_supported": "Dette språket er ikke støttet"
|
||||
},
|
||||
"header": {
|
||||
"shared": {
|
||||
"account": {
|
||||
"name": {
|
||||
"accessibilityHint": "Brukerens visningsnavn"
|
||||
},
|
||||
"account": {
|
||||
"accessibilityHint": "Brukerkonto"
|
||||
}
|
||||
},
|
||||
"application": "med {{application}}",
|
||||
"edited": {
|
||||
"accessibilityLabel": "Tut redigert"
|
||||
},
|
||||
"muted": {
|
||||
"accessibilityLabel": "Tut er dempet"
|
||||
},
|
||||
"replies": "Svar <0 />",
|
||||
"visibility": {
|
||||
"direct": {
|
||||
"accessibilityLabel": "Tut er en direkte melding"
|
||||
},
|
||||
"private": {
|
||||
"accessibilityLabel": "Tut er kun synlig for følgere"
|
||||
}
|
||||
}
|
||||
},
|
||||
"conversation": {
|
||||
"withAccounts": "Med",
|
||||
"delete": {
|
||||
"function": "Slett direkte melding"
|
||||
}
|
||||
}
|
||||
},
|
||||
"poll": {
|
||||
"meta": {
|
||||
"button": {
|
||||
"vote": "Stem",
|
||||
"refresh": "Oppdater"
|
||||
},
|
||||
"count": {
|
||||
"voters_one": "{{count}} bruker har stemt",
|
||||
"voters_other": "{{count}} bruker har stemt",
|
||||
"votes_one": "%{count} stemme",
|
||||
"votes_other": "%{count} stemmer"
|
||||
},
|
||||
"expiration": {
|
||||
"expired": "Avstemning utløpt",
|
||||
"until": "Utløper <0 />"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"screenshot": {
|
||||
"title": "Personvernbeskyttelse",
|
||||
"message": "Vennligst ikke avslør brukerens identitet, som f. eks. brukernavn, profilbilde, osv. Tusen takk!"
|
||||
},
|
||||
"localCorrupt": {
|
||||
"message": "Din økt er utløpt, vennligst logg inn igjen"
|
||||
},
|
||||
"pushError": {
|
||||
"message": "Feil ved push-tjeneste",
|
||||
"description": "Vennligst aktiver push-varsling i innstillingene"
|
||||
},
|
||||
"shareError": {
|
||||
"imageNotSupported": "Bildetype {{type}} støttes ikke",
|
||||
"videoNotSupported": "Videotype {{type}} støttes ikke"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"heading": "Del tut...",
|
||||
"content": {
|
||||
"select_account": "Velg konto"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"heading": "Kunngjøringer",
|
||||
"content": {
|
||||
"published": "Publisert <0 />",
|
||||
"button": {
|
||||
"read": "Les",
|
||||
"unread": "Merk som lest"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
{
|
||||
"heading": {
|
||||
"left": {
|
||||
"alert": {
|
||||
"title": "Avbryte redigering?",
|
||||
"buttons": {
|
||||
"save": "Lagre utkast",
|
||||
"delete": "Slett utkast"
|
||||
}
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
"button": {
|
||||
"default": "Tut",
|
||||
"conversation": "Tut DM",
|
||||
"reply": "Tut svar",
|
||||
"deleteEdit": "Tut",
|
||||
"edit": "Tut",
|
||||
"share": "Tut"
|
||||
},
|
||||
"alert": {
|
||||
"default": {
|
||||
"title": "Tut mislyktes",
|
||||
"button": "Prøv igjen"
|
||||
},
|
||||
"removeReply": {
|
||||
"title": "Svar på tut ble ikke funnet",
|
||||
"description": "Svarte tut kan ha blitt slettet. Vil du fjerne det fra din referanse?",
|
||||
"confirm": "Fjern referanse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"root": {
|
||||
"header": {
|
||||
"postingAs": "Tuter som @{{acct}}@{{domain}}",
|
||||
"spoilerInput": {
|
||||
"placeholder": "Spoiler advarsel"
|
||||
},
|
||||
"textInput": {
|
||||
"placeholder": "Hva tenker du på",
|
||||
"keyboardImage": {
|
||||
"exceedMaximum": {
|
||||
"title": "Maksimalt antall vedlegg nådd"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"attachments": {
|
||||
"sensitive": "Merk vedlegg som følsomt",
|
||||
"remove": {
|
||||
"accessibilityLabel": "Fjern opplastet vedlegg, nummer {{attachment}}"
|
||||
},
|
||||
"edit": {
|
||||
"accessibilityLabel": "Rediger opplastet vedlegg, nummer {{attachment}}"
|
||||
},
|
||||
"upload": {
|
||||
"accessibilityLabel": "Last opp flere vedlegg"
|
||||
}
|
||||
},
|
||||
"emojis": {
|
||||
"accessibilityHint": "Trykk for å legge til emoji"
|
||||
},
|
||||
"poll": {
|
||||
"option": {
|
||||
"placeholder": {
|
||||
"accessibilityLabel": "Avstemningsvalg {{index}}",
|
||||
"single": "Ett valg",
|
||||
"multiple": "Flere valg"
|
||||
}
|
||||
},
|
||||
"quantity": {
|
||||
"reduce": {
|
||||
"accessibilityLabel": "Reduser valg til {{amount}}",
|
||||
"accessibilityHint": "Minimum antall valg nådd, har {{amount}}"
|
||||
},
|
||||
"increase": {
|
||||
"accessibilityLabel": "Reduser valg til {{amount}}",
|
||||
"accessibilityHint": "Maksimum antall valg nådd, har {{amount}}"
|
||||
}
|
||||
},
|
||||
"multiple": {
|
||||
"heading": "Valg type",
|
||||
"options": {
|
||||
"single": "Ett valg",
|
||||
"multiple": "Flere valg"
|
||||
}
|
||||
},
|
||||
"expiration": {
|
||||
"heading": "Gyldighet",
|
||||
"options": {
|
||||
"300": "5 minutter",
|
||||
"1800": "30 minutter",
|
||||
"3600": "1 time",
|
||||
"21600": "6 timer",
|
||||
"86400": "1 dag",
|
||||
"259200": "3 dager",
|
||||
"604800": "7 dager"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"attachment": {
|
||||
"accessibilityLabel": "Last opp vedlegg",
|
||||
"accessibilityHint": "Avstemmingsfunksjonen blir deaktivert når der er noen vedlegg",
|
||||
"failed": {
|
||||
"alert": {
|
||||
"title": "Opplasting feilet",
|
||||
"button": "Prøv igjen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"poll": {
|
||||
"accessibilityLabel": "Legg til avstemning",
|
||||
"accessibilityHint": "Vedlegg vil bli deaktivert når avstemningen er aktiv"
|
||||
},
|
||||
"visibility": {
|
||||
"accessibilityLabel": "Tut-synlighet er {{visibility}}",
|
||||
"title": "Tut-synlighet",
|
||||
"options": {
|
||||
"public": "Offentlig",
|
||||
"unlisted": "Uoppført",
|
||||
"private": "Kun følgere",
|
||||
"direct": "Direktemelding"
|
||||
}
|
||||
},
|
||||
"spoiler": {
|
||||
"accessibilityLabel": "Spoiler"
|
||||
},
|
||||
"emoji": {
|
||||
"accessibilityLabel": "Legg til emoji",
|
||||
"accessibilityHint": "Åpne emoji-valgpanel, sveip horisontalt for å endre side"
|
||||
}
|
||||
},
|
||||
"drafts_one": "Utkast ({{count}})",
|
||||
"drafts_other": "Utkast ({{count}})"
|
||||
},
|
||||
"editAttachment": {
|
||||
"header": {
|
||||
"title": "Rediger vedlegg",
|
||||
"right": {
|
||||
"accessibilityLabel": "Lagre redigering av vedlegg",
|
||||
"failed": {
|
||||
"title": "Redigering mislyktes",
|
||||
"button": "Prøv igjen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"altText": {
|
||||
"heading": "Beskriv media for synshemmede",
|
||||
"placeholder": "Du kan legge til en beskrivelse, noen ganger kalt alt-tekst, på dine medier slik at de er tilgjengelige for enda flere mennesker, også for dem som er blinde eller svaksynte.\n\nGode beskrivelser er konkret, men presenterer hva som er i dine medier nøyaktig nok til å forstå konteksten."
|
||||
}
|
||||
}
|
||||
},
|
||||
"draftsList": {
|
||||
"header": {
|
||||
"title": "Utkast"
|
||||
},
|
||||
"warning": "Utkast lagres bare lokalt, og kan ved uhell gå tapt. Bruk ikke utkast til langtidslagring.",
|
||||
"content": {
|
||||
"accessibilityHint": "Lagret utkast, trykk for å redigere dette utkastet",
|
||||
"textEmpty": "Innhold er tomt"
|
||||
},
|
||||
"checkAttachment": "Sjekker vedlegg på serveren..."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"content": {
|
||||
"actions": {
|
||||
"accessibilityLabel": "Flere handlinger med dette bildet",
|
||||
"accessibilityHint": "Du kan lagre eller dele dette bildet"
|
||||
},
|
||||
"options": {
|
||||
"save": "Lagre bilde",
|
||||
"share": "Del bilde"
|
||||
},
|
||||
"save": {
|
||||
"succeed": "Bilde lagret",
|
||||
"failed": "Lagring av bilde feilet"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,475 @@
|
|||
{
|
||||
"tabs": {
|
||||
"local": {
|
||||
"name": "Følger",
|
||||
"options": {
|
||||
"showBoosts": "Vis booster",
|
||||
"showReplies": "Vis svar"
|
||||
}
|
||||
},
|
||||
"public": {
|
||||
"segments": {
|
||||
"federated": "Føderert",
|
||||
"local": "Lokal",
|
||||
"trending": "Populært"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"name": "Varsler"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"search": {
|
||||
"accessibilityLabel": "Søk",
|
||||
"accessibilityHint": "Søk etter emneknagger, brukere eller tuter"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"filters": {
|
||||
"accessibilityLabel": "Filter",
|
||||
"accessibilityHint": "Filtrer viste varslingstyper",
|
||||
"title": "Vis varsler"
|
||||
}
|
||||
},
|
||||
"me": {
|
||||
"stacks": {
|
||||
"bookmarks": {
|
||||
"name": "Bokmerker"
|
||||
},
|
||||
"conversations": {
|
||||
"name": "Direktemeldinger"
|
||||
},
|
||||
"favourites": {
|
||||
"name": "Favoritter"
|
||||
},
|
||||
"followedTags": {
|
||||
"name": "Fulgte emneknagger"
|
||||
},
|
||||
"fontSize": {
|
||||
"name": "Skriftstørrelse for tut"
|
||||
},
|
||||
"language": {
|
||||
"name": "Språk"
|
||||
},
|
||||
"list": {
|
||||
"name": "Liste: {{list}}"
|
||||
},
|
||||
"listAccounts": {
|
||||
"name": "Brukere i listen: {{list}}"
|
||||
},
|
||||
"listAdd": {
|
||||
"name": "Opprett liste"
|
||||
},
|
||||
"listEdit": {
|
||||
"name": "Rediger detaljer"
|
||||
},
|
||||
"lists": {
|
||||
"name": "Lister"
|
||||
},
|
||||
"push": {
|
||||
"name": "Pushvarsling"
|
||||
},
|
||||
"preferences": {
|
||||
"name": "Brukervalg"
|
||||
},
|
||||
"preferencesFilters": {
|
||||
"name": "Alle innholdsfiltre"
|
||||
},
|
||||
"preferencesFilterAdd": {
|
||||
"name": "Opprett Filter"
|
||||
},
|
||||
"preferencesFilterEdit": {
|
||||
"name": "Rediger Filter"
|
||||
},
|
||||
"profile": {
|
||||
"name": "Rediger Profil"
|
||||
},
|
||||
"profileName": {
|
||||
"name": "Rediger visningsnavn"
|
||||
},
|
||||
"profileNote": {
|
||||
"name": "Rediger beskrivelse"
|
||||
},
|
||||
"profileFields": {
|
||||
"name": "Rediger metadata"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Appinnstillinger"
|
||||
},
|
||||
"switch": {
|
||||
"name": "Bytt konto"
|
||||
}
|
||||
},
|
||||
"fontSize": {
|
||||
"demo": "<p>Dette er en demotut😊. Du kan velge flere alternativer nedenfor.<br /><br />Denne innstillingen påvirker bare hovedinnholdet i tuter, men ikke andre skriftstørrelser.</p>",
|
||||
"sizes": {
|
||||
"S": "S",
|
||||
"M": "M - Standard",
|
||||
"L": "L",
|
||||
"XL": "XL",
|
||||
"XXL": "XXL"
|
||||
}
|
||||
},
|
||||
"listAccounts": {
|
||||
"heading": "Administrer brukere",
|
||||
"error": "Slett bruker fra listen",
|
||||
"empty": "Ingen bruker lagt til i denne listen"
|
||||
},
|
||||
"listEdit": {
|
||||
"heading": "Rediger detaljer",
|
||||
"title": "Emne",
|
||||
"repliesPolicy": {
|
||||
"heading": "Vis svar:",
|
||||
"options": {
|
||||
"none": "Ingen",
|
||||
"list": "Medlemmer fra listen",
|
||||
"followed": "Enhver fulgt bruker"
|
||||
}
|
||||
}
|
||||
},
|
||||
"listDelete": {
|
||||
"heading": "Slett liste",
|
||||
"confirm": {
|
||||
"title": "Slett listen \"{{list}}\"?",
|
||||
"message": "Denne handlingen kan ikke gjenopprettes."
|
||||
}
|
||||
},
|
||||
"preferences": {
|
||||
"visibility": {
|
||||
"title": "Standard synlighet for post",
|
||||
"options": {
|
||||
"public": "Offentlig",
|
||||
"unlisted": "Uoppført",
|
||||
"private": "Kun følgere"
|
||||
}
|
||||
},
|
||||
"sensitive": {
|
||||
"title": "Marker alltid media som sensitivt"
|
||||
},
|
||||
"media": {
|
||||
"title": "Visning av media",
|
||||
"options": {
|
||||
"default": "Skjul media som er merket som sensitivt",
|
||||
"show_all": "Vis alltid media",
|
||||
"hide_all": "Skjul alltid media"
|
||||
}
|
||||
},
|
||||
"spoilers": {
|
||||
"title": "Utvid alltid tuter som er merket med innholdsadvarsler"
|
||||
},
|
||||
"autoplay_gifs": {
|
||||
"title": "Autostart GIF i tuter"
|
||||
},
|
||||
"filters": {
|
||||
"title": "Innholdsfiltre",
|
||||
"content": "{{count}} aktive"
|
||||
},
|
||||
"web_only": {
|
||||
"title": "Oppdater innstillinger",
|
||||
"description": "Innstillingene nedenfor kan kun oppdateres ved hjelp av web-grensesnittet"
|
||||
}
|
||||
},
|
||||
"preferencesFilters": {
|
||||
"expired": "Utløpt",
|
||||
"keywords_one": "{{count}} nøkkelord",
|
||||
"keywords_other": "{{count}} nøkkelord",
|
||||
"statuses_one": "{{count}} tut",
|
||||
"statuses_other": "{{count}} tut",
|
||||
"context": "Gjelder i <0 />",
|
||||
"contexts": {
|
||||
"home": "følgende og lister",
|
||||
"notifications": "varsler",
|
||||
"public": "føderert",
|
||||
"thread": "samtale",
|
||||
"account": "profil"
|
||||
}
|
||||
},
|
||||
"preferencesFilter": {
|
||||
"name": "Navn",
|
||||
"expiration": "Utløper",
|
||||
"expirationOptions": {
|
||||
"0": "Aldri",
|
||||
"1800": "Etter 30 minutter",
|
||||
"3600": "Etter 1 time",
|
||||
"43200": "Etter 12 timer",
|
||||
"86400": "Etter 1 dag",
|
||||
"604800": "Etter 1 uke",
|
||||
"18144000": "Etter 1 måned"
|
||||
},
|
||||
"context": "Gjelder i",
|
||||
"contexts": {
|
||||
"home": "Følgende og lister",
|
||||
"notifications": "Varsel",
|
||||
"public": "Føderert tidlinje",
|
||||
"thread": "Samtalevisning",
|
||||
"account": "Profil visning"
|
||||
},
|
||||
"action": "Når samsvarende",
|
||||
"actions": {
|
||||
"warn": "Kollapset, men kan vises",
|
||||
"hide": "Skjult fullstendig"
|
||||
},
|
||||
"keywords": "Treff for disse nøkkelordene",
|
||||
"keyword": "Nøkkelord",
|
||||
"statuses": "Treff disse tutene"
|
||||
},
|
||||
"profile": {
|
||||
"feedback": {
|
||||
"succeed": "{{type}} oppdatert",
|
||||
"failed": "{{type}} oppdatering feilet, prøv igjen"
|
||||
},
|
||||
"root": {
|
||||
"name": {
|
||||
"title": "Visningsnavn"
|
||||
},
|
||||
"avatar": {
|
||||
"title": "Profilbilde",
|
||||
"description": "Vil bli nedskalert til 400 x 400 px"
|
||||
},
|
||||
"header": {
|
||||
"title": "Fane",
|
||||
"description": "Vil bli nedskalert til 1500x500px"
|
||||
},
|
||||
"note": {
|
||||
"title": "Beskrivelse"
|
||||
},
|
||||
"fields": {
|
||||
"title": "Metadata",
|
||||
"total_one": "{{count}} felt",
|
||||
"total_other": "{{count}} felt"
|
||||
},
|
||||
"lock": {
|
||||
"title": "Lås konto",
|
||||
"description": "Krever at du godkjenner følgere manuelt"
|
||||
},
|
||||
"bot": {
|
||||
"title": "Bot konto",
|
||||
"description": "Denne kontoen utfører i hovedsak automatiserte handlinger og blir kanskje ikke holdt øye med"
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"group": "Gruppe {{index}}",
|
||||
"label": "Etikett",
|
||||
"content": "Innhold"
|
||||
},
|
||||
"mediaSelectionFailed": "Bildebehandling mislyktes. Vennligst prøv igjen."
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "Din telefon støtter ikke tooot's push-varsling",
|
||||
"enable": {
|
||||
"direct": "Aktiver push-varsler",
|
||||
"settings": "Aktiver i innstillinger"
|
||||
},
|
||||
"missingServerKey": {
|
||||
"message": "Feil på tjener for push-tjeneste",
|
||||
"description": "Vennligst kontakt din serveradministrator for å konfigurere push-støtte"
|
||||
},
|
||||
"global": {
|
||||
"heading": "Aktiver for {{acct}}",
|
||||
"description": "Meldinger blir sendt gjennom toot's server"
|
||||
},
|
||||
"decode": {
|
||||
"heading": "Meldingsdetaljer",
|
||||
"description": "Meldinger sendt gjennom toots server er kryptert, men du kan velge å dekode meldingen på serveren. Vår server er åpen kildekode og har ingen logging."
|
||||
},
|
||||
"default": {
|
||||
"heading": "Standard"
|
||||
},
|
||||
"follow": {
|
||||
"heading": "Ny følger"
|
||||
},
|
||||
"follow_request": {
|
||||
"heading": "Følgerforespørsel"
|
||||
},
|
||||
"favourite": {
|
||||
"heading": "Favorisert"
|
||||
},
|
||||
"reblog": {
|
||||
"heading": "Boostet"
|
||||
},
|
||||
"mention": {
|
||||
"heading": "Nevnte deg"
|
||||
},
|
||||
"poll": {
|
||||
"heading": "Oppdateringer om avstemning"
|
||||
},
|
||||
"status": {
|
||||
"heading": "Tut fra påmeldte brukere"
|
||||
},
|
||||
"update": {
|
||||
"heading": "%s er redigert"
|
||||
},
|
||||
"admin.sign_up": {
|
||||
"heading": "Administrer: registrer deg"
|
||||
},
|
||||
"admin.report": {
|
||||
"heading": "Administrer: rapporter"
|
||||
},
|
||||
"howitworks": "Lær hvordan ruting virker"
|
||||
},
|
||||
"root": {
|
||||
"announcements": {
|
||||
"content": {
|
||||
"unread": "{{amount}} ulest",
|
||||
"read": "Alt lest",
|
||||
"empty": "Ingen"
|
||||
}
|
||||
},
|
||||
"push": {
|
||||
"content_true": "Aktivert",
|
||||
"content_false": "Deaktivert"
|
||||
},
|
||||
"logout": {
|
||||
"button": "Logg ut",
|
||||
"alert": {
|
||||
"title": "Logge ut?",
|
||||
"message": "Etter å ha logget ut, må du logge på igjen",
|
||||
"buttons": {
|
||||
"logout": "Logg ut"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"theme": {
|
||||
"heading": "Utseende",
|
||||
"options": {
|
||||
"auto": "Som system",
|
||||
"light": "Lyst tema",
|
||||
"dark": "Mørkt tema"
|
||||
}
|
||||
},
|
||||
"darkTheme": {
|
||||
"heading": "Mørkt tema",
|
||||
"options": {
|
||||
"lighter": "Standard",
|
||||
"darker": "Helt svart"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
"heading": "Åpning av lenke",
|
||||
"options": {
|
||||
"internal": "I appen",
|
||||
"external": "Bruk nettleser"
|
||||
}
|
||||
},
|
||||
"autoplayGifv": {
|
||||
"heading": "Autostart GIF i tuter"
|
||||
},
|
||||
"feedback": {
|
||||
"heading": "Funksjonsforespørsler"
|
||||
},
|
||||
"support": {
|
||||
"heading": "Støtt tooot"
|
||||
},
|
||||
"contact": {
|
||||
"heading": "Kontakt tooot"
|
||||
},
|
||||
"version": "Versjon v{{version}}",
|
||||
"instanceVersion": "Mastodon versjon v{{version}}"
|
||||
},
|
||||
"switch": {
|
||||
"existing": "Velg fra innlogget",
|
||||
"new": "Logg inn på instans"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"account": {
|
||||
"actions": {
|
||||
"accessibilityLabel": "Handlinger for bruker {{user}}",
|
||||
"accessibilityHint": "Du kan dempe, blokkere, rapportere eller dele denne brukeren"
|
||||
},
|
||||
"followed_by": " følger deg",
|
||||
"moved": "Bruker flyttet",
|
||||
"created_at": "Ble med: {{date}}",
|
||||
"summary": {
|
||||
"statuses_count": "{{count}} tut"
|
||||
},
|
||||
"toots": {
|
||||
"default": "Tuter",
|
||||
"all": "Tuter og svar"
|
||||
},
|
||||
"suspended": "Konto suspendert av moderatorene på serveren din"
|
||||
},
|
||||
"accountInLists": {
|
||||
"name": "Lister av @{{username}}",
|
||||
"inLists": "I liste",
|
||||
"notInLists": "Andre lister"
|
||||
},
|
||||
"attachments": {
|
||||
"name": "<0 /><1>'s media</1>"
|
||||
},
|
||||
"filter": {
|
||||
"name": "Legg til filter",
|
||||
"existed": "Finnes i disse filtrene"
|
||||
},
|
||||
"history": {
|
||||
"name": "Rediger historikk"
|
||||
},
|
||||
"report": {
|
||||
"name": "Rapporter {{acct}}",
|
||||
"report": "Rapporter",
|
||||
"forward": {
|
||||
"heading": "Anonymt videresendt til ekstern server {{instance}}"
|
||||
},
|
||||
"reasons": {
|
||||
"heading": "Hva skjer med denne kontoen?",
|
||||
"spam": "Det er søppelpost",
|
||||
"other": "Det er noe annet",
|
||||
"violation": "Det bryter serverregler"
|
||||
},
|
||||
"comment": {
|
||||
"heading": "Noe annet du vil legge til?"
|
||||
},
|
||||
"violatedRules": {
|
||||
"heading": "Det bryter serverregler"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"header": {
|
||||
"prefix": "Søker",
|
||||
"placeholder": "etter..."
|
||||
},
|
||||
"empty": {
|
||||
"general": "Angi nøkkelord for å søke etter <bold>$t(screenTabs:shared.search.sections.accounts)</bold>、<bold>$t(screenTabs:shared.search.sections.hashtags)</bold> eller <bold>$t(screenTabs:shared.search.sections.statuses)</bold>",
|
||||
"advanced": {
|
||||
"header": "Avansert søk",
|
||||
"example": {
|
||||
"account": "$t(shared.search.header.prefix) $t(shared.search.sections.accounts)",
|
||||
"hashtag": "$t(shared.search.header.prefix) $t(shared.search.sections.hashtags)",
|
||||
"statusLink": "$t(shared.search.header.prefix) $t(shared.search.sections.statuses)",
|
||||
"accountLink": "$t(shared.search.header.prefix) $t(shared.search.sections.accounts)"
|
||||
}
|
||||
},
|
||||
"trending": {
|
||||
"tags": "Populære emner"
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"accounts": "Bruker",
|
||||
"hashtags": "Emneknagg",
|
||||
"statuses": "Tut"
|
||||
},
|
||||
"notFound": "Finner ikke <bold>{{searchTerm}}</bold> relatert til {{type}}",
|
||||
"noResult": "Kan ikke finne noe, vennligst prøv et annet begrep"
|
||||
},
|
||||
"toot": {
|
||||
"name": "Diskusjoner",
|
||||
"remoteFetch": {
|
||||
"title": "Inneholder eksternt innhold",
|
||||
"message": "Føderert innhold er ikke alltid tilgjengelig på lokal forekomst. Dette innholdet hentes fra ekstern forekomst og merkes. Du kan samhandle med disse innholdet som vanlig."
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"accounts": {
|
||||
"following": "Følger {{count}}",
|
||||
"followers": "{{count}} følgere"
|
||||
},
|
||||
"statuses": {
|
||||
"reblogged_by": "{{count}} boostet",
|
||||
"favourited_by": "{{count}} som favoritt"
|
||||
},
|
||||
"resultIncomplete": "Resultater fra en ekstern instans er ufullstendig"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@
|
|||
"poll": "Опитування, у якому ви голосували, закінчилося",
|
||||
"reblog": {
|
||||
"default": "{{name}} передмухує",
|
||||
"myself": "",
|
||||
"myself": "Передмухнуто мною",
|
||||
"notification": "{{name}} передмухує ваш дмух"
|
||||
},
|
||||
"update": "Передмух був відредагований",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Button from '@components/Button'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import haptics from '@components/haptics'
|
||||
import { Loading } from '@components/Loading'
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
|
@ -6,7 +7,6 @@ import RelativeTime from '@components/RelativeTime'
|
|||
import CustomText from '@components/Text'
|
||||
import { BlurView } from '@react-native-community/blur'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { connectMedia } from '@utils/api/helpers/connect'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useAnnouncementMutation, useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
@ -22,7 +22,6 @@ import {
|
|||
StyleSheet,
|
||||
View
|
||||
} from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { FlatList, ScrollView } from 'react-native-gesture-handler'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
|
||||
|
@ -139,12 +138,13 @@ const ScreenAnnouncements: React.FC<RootStackScreenProps<'Screen-Announcements'>
|
|||
}
|
||||
>
|
||||
{reaction.url ? (
|
||||
<FastImage
|
||||
source={connectMedia({
|
||||
uri: reduceMotionEnabled ? reaction.static_url : reaction.url
|
||||
})}
|
||||
style={{
|
||||
width: StyleConstants.Font.LineHeight.M + 3,
|
||||
<GracefullyImage
|
||||
sources={{
|
||||
default: { uri: reaction.url },
|
||||
static: { uri: reaction.static_url }
|
||||
}}
|
||||
dimension={{
|
||||
width: StyleConstants.Font.LineHeight.M,
|
||||
height: StyleConstants.Font.LineHeight.M
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -9,10 +9,10 @@ import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
|||
import { getAccountStorage, setAccountStorage, useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { Image } from 'expo-image'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dimensions, Modal, Pressable, View } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import ComposeContext from './utils/createContext'
|
||||
import { formatText } from './utils/processText'
|
||||
import { ComposeStateDraft, ExtendedAttachment } from './utils/types'
|
||||
|
@ -140,7 +140,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
|||
}}
|
||||
>
|
||||
{item.attachments.uploads.map((attachment, index) => (
|
||||
<FastImage
|
||||
<Image
|
||||
key={index}
|
||||
style={{
|
||||
width:
|
||||
|
|
|
@ -11,10 +11,10 @@ import { featureCheck } from '@utils/helpers/featureCheck'
|
|||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { Image } from 'expo-image'
|
||||
import React, { RefObject, useContext, useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FlatList, Pressable, StyleSheet, View } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import { ExtendedAttachment } from '../../utils/types'
|
||||
import chooseAndUploadAttachment from './addAttachment'
|
||||
|
@ -104,9 +104,7 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
|||
width: calculateWidth(item)
|
||||
}}
|
||||
>
|
||||
<FastImage
|
||||
enterTransition='fadeIn'
|
||||
transitionDuration={60}
|
||||
<Image
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
source={
|
||||
item.local?.thumbnail
|
||||
|
|
|
@ -193,10 +193,10 @@ const ScreenImagesViewer = ({
|
|||
}}
|
||||
>
|
||||
<GracefullyImage
|
||||
uri={{
|
||||
preview: item.preview_url,
|
||||
remote: item.remote_url,
|
||||
original: item.url
|
||||
sources={{
|
||||
preview: { uri: item.preview_url },
|
||||
default: { uri: item.url },
|
||||
remote: { uri: item.remote_url }
|
||||
}}
|
||||
dimension={{
|
||||
width:
|
||||
|
@ -208,6 +208,7 @@ const ScreenImagesViewer = ({
|
|||
? WINDOW_HEIGHT
|
||||
: (WINDOW_WIDTH / imageWidth) * imageHeight
|
||||
}}
|
||||
enableLiveTextInteraction
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ import { setAccountStorage, useAccountStorage, useGlobalStorage } from '@utils/s
|
|||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Crypto from 'expo-crypto'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import * as Random from 'expo-random'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -164,16 +164,15 @@ const TabMePush: React.FC = () => {
|
|||
url: `push/unsubscribe/${pushPath}`
|
||||
})
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, global: false } }])
|
||||
if (Platform.OS === 'android') {
|
||||
Notifications.deleteNotificationChannelGroupAsync(accountFull)
|
||||
}
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, global: false } }])
|
||||
} else {
|
||||
// Fix a bug for some users of v4.8.0
|
||||
let authKey = push.key
|
||||
if (push.key?.length <= 10) {
|
||||
authKey = fromByteArray(Random.getRandomBytes(16))
|
||||
authKey = fromByteArray(Crypto.getRandomBytes(16))
|
||||
}
|
||||
// Turning on
|
||||
const randomPath = (Math.random() + 1).toString(36).substring(2)
|
||||
|
@ -182,7 +181,7 @@ const TabMePush: React.FC = () => {
|
|||
|
||||
const body: {
|
||||
subscription: any
|
||||
alerts: Mastodon.PushSubscription['alerts']
|
||||
data: { alerts: Mastodon.PushSubscription['alerts'] }
|
||||
} = {
|
||||
subscription: {
|
||||
endpoint,
|
||||
|
@ -192,7 +191,7 @@ const TabMePush: React.FC = () => {
|
|||
auth: authKey
|
||||
}
|
||||
},
|
||||
alerts: push.alerts
|
||||
data: { alerts: push.alerts }
|
||||
}
|
||||
|
||||
const res = await apiInstance<Mastodon.PushSubscription>({
|
||||
|
@ -239,7 +238,6 @@ const TabMePush: React.FC = () => {
|
|||
setAccountStorage([
|
||||
{ key: 'push', value: { ...push, global: true, key: authKey } }
|
||||
])
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
setChannels(true)
|
||||
}
|
||||
|
|
|
@ -5,13 +5,13 @@ import { LOCALES } from '@i18n/locales'
|
|||
import { useNavigation } from '@react-navigation/native'
|
||||
import { connectVerify } from '@utils/api/helpers/connect'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { GLOBAL } from '@utils/storage'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Localization from 'expo-localization'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Linking, Platform } from 'react-native'
|
||||
import { GLOBAL } from '../../../../App'
|
||||
import { mapFontsizeToName } from '../SettingsFontsize'
|
||||
|
||||
const SettingsApp: React.FC = () => {
|
||||
|
|
|
@ -82,15 +82,25 @@ const AccountAttachments: React.FC = () => {
|
|||
} else {
|
||||
return (
|
||||
<GracefullyImage
|
||||
uri={{
|
||||
original:
|
||||
item.media_attachments[0]?.preview_url || item.media_attachments[0]?.url,
|
||||
remote: item.media_attachments[0]?.remote_url
|
||||
sources={{
|
||||
preview: {
|
||||
uri: item.media_attachments[0]?.preview_url,
|
||||
width: item.media_attachments[0]?.meta?.small?.width,
|
||||
height: item.media_attachments[0]?.meta?.small?.height
|
||||
},
|
||||
default: {
|
||||
uri: item.media_attachments[0]?.url,
|
||||
width: item.media_attachments[0]?.meta?.original?.width,
|
||||
height: item.media_attachments[0]?.meta?.original?.height
|
||||
},
|
||||
remote: {
|
||||
uri: item.media_attachments[0]?.remote_url,
|
||||
width: item.media_attachments[0]?.meta?.original?.width,
|
||||
height: item.media_attachments[0]?.meta?.original?.height
|
||||
},
|
||||
blurhash: item.media_attachments[0]?.blurhash
|
||||
}}
|
||||
blurhash={
|
||||
item.media_attachments[0] && (item.media_attachments[0].blurhash || undefined)
|
||||
}
|
||||
dimension={{ width: width, height: width }}
|
||||
dimension={{ width, height: width }}
|
||||
style={{ marginLeft: StyleConstants.Spacing.Global.PagePadding }}
|
||||
onPress={() => navigation.push('Tab-Shared-Toot', { toot: item })}
|
||||
dim
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import React, { useContext } from 'react'
|
||||
import { Dimensions, Image } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
|
@ -11,11 +10,9 @@ const AccountHeader: React.FC = () => {
|
|||
|
||||
const topInset = useSafeAreaInsets().top
|
||||
|
||||
useGlobalStorage.string('account.active')
|
||||
|
||||
return (
|
||||
<GracefullyImage
|
||||
uri={{ original: account?.header, static: account?.header_static }}
|
||||
sources={{ default: { uri: account?.header }, static: { uri: account?.header_static } }}
|
||||
style={{ height: Dimensions.get('window').width / 3 + topInset }}
|
||||
onPress={() => {
|
||||
if (account) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import Button from '@components/Button'
|
|||
import menuAt from '@components/contextMenu/at'
|
||||
import { RelationshipOutgoing } from '@components/Relationship'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { checkIsMyAccount } from '@utils/helpers/isMyAccount'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -56,12 +56,11 @@ const AccountInformationActions: React.FC = () => {
|
|||
)
|
||||
}
|
||||
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const ownAccount = account?.id === accountId
|
||||
const isMyAccount = checkIsMyAccount(account?.id)
|
||||
|
||||
const mAt = menuAt({ account })
|
||||
|
||||
if (!ownAccount && account) {
|
||||
if (!isMyAccount && account) {
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
{relationship && !relationship.blocked_by ? (
|
||||
|
|
|
@ -3,7 +3,6 @@ import { useNavigation } from '@react-navigation/native'
|
|||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useContext } from 'react'
|
||||
import AccountContext from '../Context'
|
||||
|
@ -13,20 +12,13 @@ const AccountInformationAvatar: React.FC = () => {
|
|||
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
|
||||
const [accountAvatarStatic] = useAccountStorage.string('auth.account.avatar_static')
|
||||
|
||||
return (
|
||||
<GracefullyImage
|
||||
key={account?.avatar}
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
width: StyleConstants.Avatar.L,
|
||||
height: StyleConstants.Avatar.L
|
||||
}}
|
||||
uri={{
|
||||
original: account?.avatar || (pageMe ? accountAvatarStatic : undefined),
|
||||
static: account?.avatar_static || (pageMe ? accountAvatarStatic : undefined)
|
||||
style={{ borderRadius: 8, overflow: 'hidden' }}
|
||||
dimension={{ width: StyleConstants.Avatar.L, height: StyleConstants.Avatar.L }}
|
||||
sources={{
|
||||
default: { uri: account?.avatar },
|
||||
static: { uri: account?.avatar_static }
|
||||
}}
|
||||
onPress={() => {
|
||||
if (account) {
|
||||
|
|
|
@ -248,7 +248,11 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||
body: data.map(remote => {
|
||||
const localMatch = old?.pages[0].body.find(local => local.uri === remote.uri)
|
||||
if (localMatch) {
|
||||
return { ...localMatch, _level: remote._level }
|
||||
return {
|
||||
...localMatch,
|
||||
_level: remote._level,
|
||||
key: `${localMatch.id}_remote`
|
||||
}
|
||||
} else {
|
||||
return appendRemote.status(remote)
|
||||
}
|
||||
|
@ -275,6 +279,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||
ref={flRef}
|
||||
windowSize={5}
|
||||
data={query.data?.pages?.[0].body}
|
||||
extraData={query.dataUpdatedAt}
|
||||
renderItem={({ item, index }) => {
|
||||
const prev = query.data?.pages[0].body[index - 1]?._level || 0
|
||||
const curr = item._level || 0
|
||||
|
|
|
@ -50,9 +50,9 @@ const ScreenTabs = () => {
|
|||
return <Icon name='bell' size={size} color={color} />
|
||||
case 'Tab-Me':
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<View key={avatarStatic} style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<GracefullyImage
|
||||
uri={{ original: avatarStatic }}
|
||||
sources={{ default: { uri: avatarStatic } }}
|
||||
dimension={{ width: size, height: size }}
|
||||
style={{
|
||||
borderRadius: size,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { GLOBAL } from '@utils/storage'
|
||||
import axios from 'axios'
|
||||
import { GLOBAL } from '../../App'
|
||||
import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers'
|
||||
import { CONNECT_DOMAIN } from './helpers/connect'
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { mapEnvironment } from '@utils/helpers/checkEnvironment'
|
||||
import { GLOBAL } from '@utils/storage'
|
||||
import { setGlobalStorage } from '@utils/storage/actions'
|
||||
import axios from 'axios'
|
||||
import parse from 'url-parse'
|
||||
import { userAgent } from '.'
|
||||
import { GLOBAL } from '../../../App'
|
||||
|
||||
const list = [
|
||||
'n61owz4leck',
|
||||
|
@ -81,19 +81,18 @@ export const CONNECT_DOMAIN = (index?: number) =>
|
|||
development: 'connect-development.tooot.app'
|
||||
})
|
||||
|
||||
export const connectMedia = ({
|
||||
uri
|
||||
}: {
|
||||
export const connectMedia = (args?: {
|
||||
uri?: string
|
||||
}): { uri?: string; headers?: { 'x-tooot-domain': string } } => {
|
||||
if (GLOBAL.connect) {
|
||||
if (uri) {
|
||||
const host = parse(uri).host
|
||||
if (args?.uri) {
|
||||
const host = parse(args.uri).host
|
||||
return {
|
||||
uri: uri.replace(
|
||||
...args,
|
||||
uri: args.uri.replace(
|
||||
host,
|
||||
CONNECT_DOMAIN(
|
||||
uri
|
||||
args.uri
|
||||
.split('')
|
||||
.map(i => i.charCodeAt(0))
|
||||
.reduce((a, b) => a + b, 0) %
|
||||
|
@ -103,10 +102,10 @@ export const connectMedia = ({
|
|||
headers: { 'x-tooot-domain': host }
|
||||
}
|
||||
} else {
|
||||
return { uri }
|
||||
return { ...args }
|
||||
}
|
||||
} else {
|
||||
return { uri }
|
||||
return { ...args }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as Sentry from '@sentry/react-native'
|
||||
import { GLOBAL } from '@utils/storage'
|
||||
import { setGlobalStorage } from '@utils/storage/actions'
|
||||
import chalk from 'chalk'
|
||||
import Constants from 'expo-constants'
|
||||
import { Platform } from 'react-native'
|
||||
import parse from 'url-parse'
|
||||
import { GLOBAL } from '../../../App'
|
||||
|
||||
const userAgent = {
|
||||
'User-Agent': `tooot/${Constants.expoConfig?.version} ${Platform.OS}/${Platform.Version}`
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { GLOBAL } from '@utils/storage'
|
||||
import { getAccountDetails } from '@utils/storage/actions'
|
||||
import { StorageGlobal } from '@utils/storage/global'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import { GLOBAL } from '../../App'
|
||||
import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers'
|
||||
import { CONNECT_DOMAIN } from './helpers/connect'
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
|
||||
export const checkIsMyAccount = (id?: Mastodon.Account['id']): boolean => {
|
||||
if (!id) return false
|
||||
|
||||
const accountId = getAccountStorage.string('auth.account.id')
|
||||
const accountDomain = getAccountStorage.string('auth.account.domain')
|
||||
return accountId === id || `${accountId}@${accountDomain}` === id
|
||||
}
|
|
@ -9,7 +9,7 @@ export const urlMatcher = (
|
|||
):
|
||||
| {
|
||||
domain: string
|
||||
account?: Partial<Pick<Mastodon.Account, 'id' | 'acct' | '_remote'>>
|
||||
account?: Partial<Pick<Mastodon.Account, 'acct' | '_remote'>>
|
||||
status?: Partial<Pick<Mastodon.Status, 'id' | '_remote'>>
|
||||
}
|
||||
| undefined => {
|
||||
|
@ -24,13 +24,18 @@ export const urlMatcher = (
|
|||
const _remote = parsed.hostname !== getAccountStorage.string('auth.domain')
|
||||
|
||||
let statusId: string | undefined
|
||||
let accountId: string | undefined
|
||||
let accountAcct: string | undefined
|
||||
|
||||
const segments = parsed.pathname.split('/')
|
||||
const last = segments[segments.length - 1]
|
||||
const length = segments.length // there is a starting slash
|
||||
|
||||
const testAndAssignStatusId = (id: string) => {
|
||||
if (!!parseInt(id)) {
|
||||
statusId = id
|
||||
}
|
||||
}
|
||||
|
||||
switch (last?.startsWith('@')) {
|
||||
case true:
|
||||
if (length === 2 || (length === 3 && segments[length - 2] === 'web')) {
|
||||
|
@ -45,14 +50,14 @@ export const urlMatcher = (
|
|||
if (nextToLast === 'statuses') {
|
||||
if (length === 4 && segments[length - 3] === 'web') {
|
||||
// https://social.xmflsct.com/web/statuses/105590085754428765 <- old
|
||||
statusId = last
|
||||
testAndAssignStatusId(last)
|
||||
} else if (
|
||||
length === 5 &&
|
||||
segments[length - 2] === 'statuses' &&
|
||||
segments[length - 4] === 'users'
|
||||
) {
|
||||
// https://social.xmflsct.com/users/tooot/statuses/105590085754428765 <- default Mastodon
|
||||
statusId = last
|
||||
testAndAssignStatusId(last)
|
||||
// accountAcct = `@${segments[length - 3]}@${domain}`
|
||||
}
|
||||
} else if (
|
||||
|
@ -61,7 +66,7 @@ export const urlMatcher = (
|
|||
) {
|
||||
// https://social.xmflsct.com/web/@tooot/105590085754428765 <- pretty Mastodon v3.5 and below
|
||||
// https://social.xmflsct.com/@tooot/105590085754428765 <- pretty Mastodon v4.0 and above
|
||||
statusId = last
|
||||
testAndAssignStatusId(last)
|
||||
// accountAcct = `${nextToLast}@${domain}`
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +75,7 @@ export const urlMatcher = (
|
|||
|
||||
return {
|
||||
domain,
|
||||
...((accountId || accountAcct) && { account: { id: accountId, acct: accountAcct, _remote } }),
|
||||
...(accountAcct && { account: { acct: accountAcct, _remote } }),
|
||||
...(statusId && { status: { id: statusId, _remote } })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ const accountQueryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyA
|
|||
const match = urlMatcher(key.url)
|
||||
|
||||
const domain = match?.domain
|
||||
const id = key.id || match?.account?.id
|
||||
const id = key.id
|
||||
const acct = key.acct || key.username || match?.account?.acct
|
||||
|
||||
if (!key._local && domain) {
|
||||
|
|
|
@ -244,6 +244,12 @@ export const setAccount = async (account: string) => {
|
|||
return
|
||||
}
|
||||
|
||||
log('log', 'setAccount', `binding storage of ${account}`)
|
||||
storage.account = temp
|
||||
setGlobalStorage('account.active', account)
|
||||
await queryClient.resetQueries()
|
||||
queryClient.clear()
|
||||
|
||||
await apiGeneral<Mastodon.Account>({
|
||||
method: 'get',
|
||||
domain,
|
||||
|
@ -254,15 +260,10 @@ export const setAccount = async (account: string) => {
|
|||
})
|
||||
.then(res => res.body)
|
||||
.then(async a => {
|
||||
temp.set('auth.account.acct', a.acct)
|
||||
temp.set('auth.account.avatar_static', a.avatar_static)
|
||||
storage.account?.set('auth.account.acct', a.acct)
|
||||
storage.account?.set('auth.account.avatar_static', a.avatar_static)
|
||||
|
||||
log('log', 'setAccount', `binding storage of ${account}`)
|
||||
await queryClient.resetQueries()
|
||||
queryClient.clear()
|
||||
|
||||
storage.account = temp
|
||||
setGlobalStorage('account.active', account)
|
||||
log('log', 'setAccount', 'update details')
|
||||
})
|
||||
.catch(async error => {
|
||||
if (error?.status && error.status == 401) {
|
||||
|
@ -302,7 +303,7 @@ export const removeAccount = async (account: string, warning: boolean = true) =>
|
|||
apiGeneral({
|
||||
method: 'post',
|
||||
domain: revokeDetails.domain,
|
||||
url: '/oauth/revoke',
|
||||
url: 'oauth/revoke',
|
||||
body: revokeDetails
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,3 +4,7 @@ import { MMKV } from 'react-native-mmkv'
|
|||
export const storage: { global: MMKV; account?: MMKV } = { global: new MMKV(), account: undefined }
|
||||
|
||||
export const secureStorage = createSecureStore()
|
||||
|
||||
export const GLOBAL: { connect?: boolean } = {
|
||||
connect: undefined
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue