diff --git a/__e2e__/flows/feed-reorder.yml b/__e2e__/flows/feed-reorder.yml
index 4b96a201ce..34df679ce7 100644
--- a/__e2e__/flows/feed-reorder.yml
+++ b/__e2e__/flows/feed-reorder.yml
@@ -36,12 +36,15 @@ appId: xyz.blueskyweb.app
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
-- tapOn: "Edit Saved Feeds"
+- tapOn:
+ id: "editFeedsBtn"
- tapOn:
label: "Tap on down arrow"
point: "79%,23%"
- tapOn:
- id: "bottomBarHomeBtn"
+ id: "viewHeaderDrawerBtn"
+- tapOn:
+ id: "viewHeaderDrawerBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "alice-favs"
@@ -54,11 +57,15 @@ appId: xyz.blueskyweb.app
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
+- tapOn:
+ id: "editFeedsBtn"
- tapOn:
label: "Tap on down arrow"
point: "79%,23%"
- tapOn:
- id: "bottomBarHomeBtn"
+ id: "viewHeaderDrawerBtn"
+- tapOn:
+ id: "viewHeaderDrawerBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "Following"
@@ -71,11 +78,15 @@ appId: xyz.blueskyweb.app
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
+- tapOn:
+ id: "editFeedsBtn"
- tapOn:
label: "Tap on unpin"
point: "91%,23%"
- tapOn:
- id: "bottomBarHomeBtn"
+ id: "viewHeaderDrawerBtn"
+- tapOn:
+ id: "viewHeaderDrawerBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "alice-favs"
diff --git a/__e2e__/flows/home-screen.yml b/__e2e__/flows/home-screen.yml
index f39aceebc5..9c2d540ebb 100644
--- a/__e2e__/flows/home-screen.yml
+++ b/__e2e__/flows/home-screen.yml
@@ -28,6 +28,8 @@ appId: xyz.blueskyweb.app
- tapOn: "Pin to Home"
- tapOn:
id: "bottomBarHomeBtn"
+- tapOn:
+ id: "viewHeaderDrawerBtn"
- assertNotVisible: "Feeds ✨"
- tapOn:
diff --git a/__e2e__/flows/thread-screen.yml b/__e2e__/flows/thread-screen.yml
index 22f71345db..fdc732596b 100644
--- a/__e2e__/flows/thread-screen.yml
+++ b/__e2e__/flows/thread-screen.yml
@@ -58,7 +58,7 @@ appId: xyz.blueskyweb.app
id: "repostBtn"
childOf:
id: "postThreadItem-by-bob.test"
-- tapOn: "Undo repost"
+- tapOn: "Remove repost"
- assertNotVisible:
id: "repostCount-expanded"
@@ -77,7 +77,7 @@ appId: xyz.blueskyweb.app
id: "repostBtn"
childOf:
id: "postThreadItem-by-carla.test"
-- tapOn: "Undo repost"
+- tapOn: "Remove repost"
- assertNotVisible:
id: "repostCount"
childOf:
diff --git a/assets/icons/listPlus_stroke2_corner0_rounded.svg b/assets/icons/listPlus_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..c1f38e43b9
--- /dev/null
+++ b/assets/icons/listPlus_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/menu_stroke2_corner0_rounded.svg b/assets/icons/menu_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..4a5ad3bdf8
--- /dev/null
+++ b/assets/icons/menu_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/pin_stroke2_corner0_rounded.svg b/assets/icons/pin_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..8942f9dec9
--- /dev/null
+++ b/assets/icons/pin_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/shapes_stroke2_corner0_rounded.svg b/assets/icons/shapes_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..6d6baee6d4
--- /dev/null
+++ b/assets/icons/shapes_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/star_filled_corner0_rounded.svg b/assets/icons/star_filled_corner0_rounded.svg
new file mode 100644
index 0000000000..d7d99cafc0
--- /dev/null
+++ b/assets/icons/star_filled_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/star_stroke2_corner0_rounded.svg b/assets/icons/star_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..694b73487c
--- /dev/null
+++ b/assets/icons/star_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go
index e1b009646a..bb81e780f5 100644
--- a/bskyweb/cmd/bskyweb/server.go
+++ b/bskyweb/cmd/bskyweb/server.go
@@ -207,6 +207,7 @@ func serve(cctx *cli.Context) error {
e.GET("/profile/:handleOrDID", server.WebProfile)
e.GET("/profile/:handleOrDID/follows", server.WebGeneric)
e.GET("/profile/:handleOrDID/followers", server.WebGeneric)
+ e.GET("/profile/:handleOrDID/known-followers", server.WebGeneric)
e.GET("/profile/:handleOrDID/lists/:rkey", server.WebGeneric)
e.GET("/profile/:handleOrDID/feed/:rkey", server.WebGeneric)
e.GET("/profile/:handleOrDID/feed/:rkey/liked-by", server.WebGeneric)
diff --git a/modules/expo-bluesky-translate/expo-module.config.json b/modules/expo-bluesky-translate/expo-module.config.json
deleted file mode 100644
index 28c5dd878d..0000000000
--- a/modules/expo-bluesky-translate/expo-module.config.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "platforms": ["ios"],
- "ios": {
- "modules": ["ExpoBlueskyTranslateModule"]
- }
-}
diff --git a/modules/expo-bluesky-translate/index.ts b/modules/expo-bluesky-translate/index.ts
deleted file mode 100644
index f3e1d3b113..0000000000
--- a/modules/expo-bluesky-translate/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export {
- isAvailable,
- isLanguageSupported,
- NativeTranslationModule,
- NativeTranslationView,
-} from './src/ExpoBlueskyTranslateView'
diff --git a/modules/expo-bluesky-translate/ios/Common/UIHostingControllerCompat.swift b/modules/expo-bluesky-translate/ios/Common/UIHostingControllerCompat.swift
deleted file mode 100644
index c8ca3e0273..0000000000
--- a/modules/expo-bluesky-translate/ios/Common/UIHostingControllerCompat.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-import ExpoModulesCore
-import SwiftUI
-
-// Thanks to Andrew Levy for this code snippet
-// https://github.com/andrew-levy/swiftui-react-native/blob/d3fbb2abf07601ff0d4b83055e7717bb980910d6/ios/Common/ExpoView%2BUIHostingController.swift
-
-extension ExpoView {
- func setupHostingController(_ hostingController: UIHostingController) {
- hostingController.view.translatesAutoresizingMaskIntoConstraints = false
- hostingController.view.backgroundColor = .clear
-
- addSubview(hostingController.view)
- NSLayoutConstraint.activate([
- hostingController.view.topAnchor.constraint(equalTo: self.topAnchor),
- hostingController.view.bottomAnchor.constraint(equalTo: self.bottomAnchor),
- hostingController.view.leftAnchor.constraint(equalTo: self.leftAnchor),
- hostingController.view.rightAnchor.constraint(equalTo: self.rightAnchor),
- ])
- }
-}
diff --git a/modules/expo-bluesky-translate/ios/ExpoBlueskyTranslate.podspec b/modules/expo-bluesky-translate/ios/ExpoBlueskyTranslate.podspec
deleted file mode 100644
index 45f86a6056..0000000000
--- a/modules/expo-bluesky-translate/ios/ExpoBlueskyTranslate.podspec
+++ /dev/null
@@ -1,21 +0,0 @@
-Pod::Spec.new do |s|
- s.name = 'ExpoBlueskyTranslate'
- s.version = '1.0.0'
- s.summary = 'Uses SwiftUI translation to translate text.'
- s.description = 'Uses SwiftUI translation to translate text.'
- s.author = ''
- s.homepage = 'https://docs.expo.dev/modules/'
- s.platforms = { :ios => '13.4' }
- s.source = { git: '' }
- s.static_framework = true
-
- s.dependency 'ExpoModulesCore'
-
- # Swift/Objective-C compatibility
- s.pod_target_xcconfig = {
- 'DEFINES_MODULE' => 'YES',
- 'SWIFT_COMPILATION_MODE' => 'wholemodule'
- }
-
- s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
-end
diff --git a/modules/expo-bluesky-translate/ios/ExpoBlueskyTranslateModule.swift b/modules/expo-bluesky-translate/ios/ExpoBlueskyTranslateModule.swift
deleted file mode 100644
index afa8137229..0000000000
--- a/modules/expo-bluesky-translate/ios/ExpoBlueskyTranslateModule.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-import ExpoModulesCore
-import Foundation
-import SwiftUI
-
-public class ExpoBlueskyTranslateModule: Module {
- public func definition() -> ModuleDefinition {
- Name("ExpoBlueskyTranslate")
-
- AsyncFunction("presentAsync") { (text: String) in
- DispatchQueue.main.async { [weak state = TranslateViewState.shared] in
- state?.isPresented = true
- state?.text = text
- }
- }
-
- View(ExpoBlueskyTranslateView.self) {}
- }
-}
diff --git a/modules/expo-bluesky-translate/ios/ExpoBlueskyTranslateView.swift b/modules/expo-bluesky-translate/ios/ExpoBlueskyTranslateView.swift
deleted file mode 100644
index ca6e3be69c..0000000000
--- a/modules/expo-bluesky-translate/ios/ExpoBlueskyTranslateView.swift
+++ /dev/null
@@ -1,22 +0,0 @@
-import ExpoModulesCore
-import Foundation
-import SwiftUI
-
-class TranslateViewState: ObservableObject {
- static var shared = TranslateViewState()
-
- @Published var isPresented = false
- @Published var text = ""
-}
-
-class ExpoBlueskyTranslateView: ExpoView {
- required init(appContext: AppContext? = nil) {
- if #available(iOS 14.0, *) {
- let hostingController = UIHostingController(rootView: TranslateView())
- super.init(appContext: appContext)
- setupHostingController(hostingController)
- } else {
- super.init(appContext: appContext)
- }
- }
-}
diff --git a/modules/expo-bluesky-translate/ios/TranslateView.swift b/modules/expo-bluesky-translate/ios/TranslateView.swift
deleted file mode 100644
index e2886dc844..0000000000
--- a/modules/expo-bluesky-translate/ios/TranslateView.swift
+++ /dev/null
@@ -1,31 +0,0 @@
-import SwiftUI
-// conditionally import the Translation module
-#if canImport(Translation)
-import Translation
-#endif
-
-struct TranslateView: View {
- @ObservedObject var state = TranslateViewState.shared
-
- var body: some View {
- if #available(iOS 17.4, *) {
- VStack {
- UIViewRepresentableWrapper(view: UIView(frame: .zero))
- }
- .translationPresentation(
- isPresented: $state.isPresented,
- text: state.text
- )
- }
- }
-}
-
-struct UIViewRepresentableWrapper: UIViewRepresentable {
- let view: UIView
-
- func makeUIView(context: Context) -> UIView {
- return view
- }
-
- func updateUIView(_ uiView: UIView, context: Context) {}
-}
diff --git a/modules/expo-bluesky-translate/src/ExpoBlueskyTranslate.types.ts b/modules/expo-bluesky-translate/src/ExpoBlueskyTranslate.types.ts
deleted file mode 100644
index a01d4d479d..0000000000
--- a/modules/expo-bluesky-translate/src/ExpoBlueskyTranslate.types.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export type ExpoBlueskyTranslateModule = {
- presentAsync: (text: string) => Promise
-}
diff --git a/modules/expo-bluesky-translate/src/ExpoBlueskyTranslateView.ios.tsx b/modules/expo-bluesky-translate/src/ExpoBlueskyTranslateView.ios.tsx
deleted file mode 100644
index 290fabd30d..0000000000
--- a/modules/expo-bluesky-translate/src/ExpoBlueskyTranslateView.ios.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import React from 'react'
-import {Platform} from 'react-native'
-import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'
-
-import {ExpoBlueskyTranslateModule} from './ExpoBlueskyTranslate.types'
-
-export const NativeTranslationModule =
- requireNativeModule('ExpoBlueskyTranslate')
-
-const NativeView: React.ComponentType = requireNativeViewManager(
- 'ExpoBlueskyTranslate',
-)
-
-export function NativeTranslationView() {
- return
-}
-
-// can be something like "17.5.1", so just take the first two parts
-const version = String(Platform.Version).split('.').slice(0, 2).join('.')
-
-export const isAvailable = Number(version) >= 17.4
-
-// https://en.wikipedia.org/wiki/Translate_(Apple)#Languages
-const SUPPORTED_LANGUAGES = [
- 'ar',
- 'zh',
- 'zh',
- 'nl',
- 'en',
- 'en',
- 'fr',
- 'de',
- 'id',
- 'it',
- 'ja',
- 'ko',
- 'pl',
- 'pt',
- 'ru',
- 'es',
- 'th',
- 'tr',
- 'uk',
- 'vi',
-]
-
-export function isLanguageSupported(lang?: string) {
- // If the language is not provided, we assume it is supported
- if (!lang) return true
- return SUPPORTED_LANGUAGES.includes(lang)
-}
diff --git a/modules/expo-bluesky-translate/src/ExpoBlueskyTranslateView.tsx b/modules/expo-bluesky-translate/src/ExpoBlueskyTranslateView.tsx
deleted file mode 100644
index 16ff9d6004..0000000000
--- a/modules/expo-bluesky-translate/src/ExpoBlueskyTranslateView.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-export const NativeTranslationModule = {
- presentAsync: async (_: string) => {},
-}
-
-export function NativeTranslationView() {
- return null
-}
-
-export const isAvailable = false
-
-export function isLanguageSupported(_lang?: string) {
- return false
-}
diff --git a/patches/expo-notifications+0.28.7.patch b/patches/expo-notifications+0.28.7.patch
index 3a9985c7b0..b71da2ebb8 100644
--- a/patches/expo-notifications+0.28.7.patch
+++ b/patches/expo-notifications+0.28.7.patch
@@ -1,29 +1,54 @@
diff --git a/node_modules/expo-notifications/android/build.gradle b/node_modules/expo-notifications/android/build.gradle
-index d233e1f..cc2f856 100644
+index b863077..8b5209e 100644
--- a/node_modules/expo-notifications/android/build.gradle
+++ b/node_modules/expo-notifications/android/build.gradle
@@ -32,6 +32,7 @@ dependencies {
api 'com.google.firebase:firebase-messaging:22.0.0'
-
+
api 'me.leolin:ShortcutBadger:1.1.22@aar'
+ implementation project(':expo-background-notification-handler')
-
+
if (project.findProject(':expo-modules-test-core')) {
testImplementation project(':expo-modules-test-core')
+diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt
+index 63a46c5..f911834 100644
+--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt
++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt
+@@ -1,5 +1,6 @@
+ package expo.modules.notifications.badge
+
++import android.app.NotificationManager
+ import android.content.Context
+ import android.util.Log
+ import me.leolin.shortcutbadger.ShortcutBadgeException
+@@ -12,7 +13,12 @@ object BadgeHelper {
+
+ fun setBadgeCount(context: Context, badgeCount: Int): Boolean {
+ return try {
+- ShortcutBadger.applyCountOrThrow(context.applicationContext, badgeCount)
++ if (badgeCount == 0) {
++ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
++ notificationManager.cancelAll()
++ } else {
++ ShortcutBadger.applyCountOrThrow(context.applicationContext, badgeCount)
++ }
+ BadgeHelper.badgeCount = badgeCount
+ true
+ } catch (e: ShortcutBadgeException) {
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java
index 0af7fe0..8f2c8d8 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java
@@ -14,6 +14,7 @@ import expo.modules.notifications.notifications.enums.NotificationPriority;
import expo.modules.notifications.notifications.model.NotificationContent;
-
+
public class JSONNotificationContentBuilder extends NotificationContent.Builder {
+ private static final String CHANNEL_ID_KEY = "channelId";
private static final String TITLE_KEY = "title";
private static final String TEXT_KEY = "message";
private static final String SUBTITLE_KEY = "subtitle";
@@ -36,6 +37,7 @@ public class JSONNotificationContentBuilder extends NotificationContent.Builder
-
+
public NotificationContent.Builder setPayload(JSONObject payload) {
this.setTitle(getTitle(payload))
+ .setChannelId(getChannelId(payload))
@@ -33,7 +58,7 @@ index 0af7fe0..8f2c8d8 100644
@@ -60,6 +62,14 @@ public class JSONNotificationContentBuilder extends NotificationContent.Builder
return this;
}
-
+
+ protected String getChannelId(JSONObject payload) {
+ try {
+ return payload.getString(CHANNEL_ID_KEY);
@@ -46,7 +71,7 @@ index 0af7fe0..8f2c8d8 100644
try {
return payload.getString(TITLE_KEY);
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
-index f1fed19..80afe9e 100644
+index f1fed19..012757b 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
@@ -20,6 +20,7 @@ import expo.modules.notifications.notifications.enums.NotificationPriority;
@@ -60,10 +85,10 @@ index f1fed19..80afe9e 100644
@@ -50,6 +51,11 @@ public class NotificationContent implements Parcelable, Serializable {
}
};
-
+
+ @Nullable
+ public String getChannelId() {
-+ return mTitle;
++ return mChannelId;
+ }
+
@Nullable
@@ -71,14 +96,14 @@ index f1fed19..80afe9e 100644
return mTitle;
@@ -121,6 +127,7 @@ public class NotificationContent implements Parcelable, Serializable {
}
-
+
protected NotificationContent(Parcel in) {
+ mChannelId = in.readString();
mTitle = in.readString();
mText = in.readString();
mSubtitle = in.readString();
@@ -146,6 +153,7 @@ public class NotificationContent implements Parcelable, Serializable {
-
+
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mChannelId);
@@ -87,7 +112,7 @@ index f1fed19..80afe9e 100644
dest.writeString(mSubtitle);
@@ -166,6 +174,7 @@ public class NotificationContent implements Parcelable, Serializable {
private static final long serialVersionUID = 397666843266836802L;
-
+
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+ out.writeObject(mChannelId);
out.writeObject(mTitle);
@@ -95,7 +120,7 @@ index f1fed19..80afe9e 100644
out.writeObject(mSubtitle);
@@ -190,6 +199,7 @@ public class NotificationContent implements Parcelable, Serializable {
}
-
+
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
+ mChannelId = (String) in.readObject();
mTitle = (String) in.readObject();
@@ -103,7 +128,7 @@ index f1fed19..80afe9e 100644
mSubtitle = (String) in.readObject();
@@ -240,6 +250,7 @@ public class NotificationContent implements Parcelable, Serializable {
}
-
+
public static class Builder {
+ private String mChannelId;
private String mTitle;
@@ -112,7 +137,7 @@ index f1fed19..80afe9e 100644
@@ -260,6 +271,11 @@ public class NotificationContent implements Parcelable, Serializable {
useDefaultVibrationPattern();
}
-
+
+ public Builder setChannelId(String channelId) {
+ mChannelId = channelId;
+ return this;
@@ -122,7 +147,7 @@ index f1fed19..80afe9e 100644
mTitle = title;
return this;
@@ -336,6 +352,7 @@ public class NotificationContent implements Parcelable, Serializable {
-
+
public NotificationContent build() {
NotificationContent content = new NotificationContent();
+ content.mChannelId = mChannelId;
@@ -134,16 +159,16 @@ index 6bd9928..ee93d70 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java
@@ -48,6 +48,10 @@ public class ExpoNotificationBuilder extends ChannelAwareNotificationBuilder {
-
+
NotificationContent content = getNotificationContent();
-
+
+ if (content.getChannelId() != null) {
+ builder.setChannelId(content.getChannelId());
+ }
+
builder.setAutoCancel(content.isAutoDismiss());
builder.setOngoing(content.isSticky());
-
+
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
index 55b3a8d..1b99d5b 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
@@ -158,7 +183,7 @@ index 55b3a8d..1b99d5b 100644
import org.json.JSONObject
import java.lang.ref.WeakReference
import java.util.*
-
+
-open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate {
+open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate, BackgroundNotificationHandlerInterface {
companion object {
@@ -166,7 +191,7 @@ index 55b3a8d..1b99d5b 100644
// than by static properties. Fortunately, using weak references we can
@@ -89,12 +92,21 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM
fun getBackgroundTasks() = sBackgroundTaskConsumerReferences.values.mapNotNull { it.get() }
-
+
override fun onMessageReceived(remoteMessage: RemoteMessage) {
- NotificationsService.receive(context, createNotification(remoteMessage))
- getBackgroundTasks().forEach {
@@ -181,7 +206,7 @@ index 55b3a8d..1b99d5b 100644
+ }
}
}
-
+
+ override fun showMessage(remoteMessage: RemoteMessage) {
+ NotificationsService.receive(context, createNotification(remoteMessage))
+ }
diff --git a/patches/expo-notifications-0.28.7.patch.md b/patches/expo-notifications-0.28.7.patch.md
index 59b2598f3b..05f8417252 100644
--- a/patches/expo-notifications-0.28.7.patch.md
+++ b/patches/expo-notifications-0.28.7.patch.md
@@ -7,3 +7,8 @@ in `onMessageReceived` are sent to the module for handling.
It also allows us to set the Android notification channel ID from the notification `data`, rather
than the `notification` object in the payload.
+
+### `setBadgeCountAsync` fix on Android
+
+`ShortcutBadger`'s `setCount` doesn't work for clearing the badge on Android for some reason. Instead, let's use the
+Android API for clearing the badge.
diff --git a/src/App.native.tsx b/src/App.native.tsx
index b359ad911d..5b2071e102 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -143,7 +143,7 @@ function App() {
* that is set up in the InnerApp component above.
*/
return (
-
+
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index e00cab1daa..54b7cadb3e 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -22,7 +22,6 @@ import {buildStateObject} from 'lib/routes/helpers'
import {
AllNavigatorParams,
BottomTabNavigatorParams,
- FeedsTabNavigatorParams,
FlatNavigatorParams,
HomeTabNavigatorParams,
MessagesTabNavigatorParams,
@@ -42,6 +41,7 @@ import {PreferencesThreads} from 'view/screens/PreferencesThreads'
import {SavedFeeds} from 'view/screens/SavedFeeds'
import HashtagScreen from '#/screens/Hashtag'
import {ModerationScreen} from '#/screens/Moderation'
+import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers'
import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'
import {LandingScreen} from '#/screens/StarterPack/Landing'
import {StarterPackScreen} from '#/screens/StarterPack/Main'
@@ -94,7 +94,6 @@ const navigationRef = createNavigationContainerRef()
const HomeTab = createNativeStackNavigatorWithAuth()
const SearchTab = createNativeStackNavigatorWithAuth()
-const FeedsTab = createNativeStackNavigatorWithAuth()
const NotificationsTab =
createNativeStackNavigatorWithAuth()
const MyProfileTab =
@@ -174,6 +173,13 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
title: title(msg`People followed by @${route.params.name}`),
})}
/>
+ ProfileKnownFollowersScreen}
+ options={({route}) => ({
+ title: title(msg`Followers of @${route.params.name} that you know`),
+ })}
+ />
ProfileListScreen}
@@ -309,6 +315,7 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => MessagesSettingsScreen}
options={{title: title(msg`Chat settings`), requireAuth: true}}
/>
+ FeedsScreen} />
HomeTabNavigator} />
SearchTabNavigator} />
- FeedsTabNavigator} />
NotificationsTabNavigator}
@@ -403,24 +409,6 @@ function SearchTabNavigator() {
)
}
-function FeedsTabNavigator() {
- const pal = usePalette('default')
- return (
-
- FeedsScreen} />
- {commonScreens(FeedsTab as typeof HomeTab)}
-
- )
-}
-
function NotificationsTabNavigator() {
const pal = usePalette('default')
return (
@@ -524,11 +512,6 @@ const FlatNavigator = () => {
getComponent={() => SearchScreen}
options={{title: title(msg`Search`)}}
/>
- FeedsScreen}
- options={{title: title(msg`Feeds`)}}
- />
NotificationsScreen}
diff --git a/src/components/KeyboardPadding.android.tsx b/src/components/KeyboardControllerPadding.android.tsx
similarity index 88%
rename from src/components/KeyboardPadding.android.tsx
rename to src/components/KeyboardControllerPadding.android.tsx
index 40dec30017..92ef1b0b05 100644
--- a/src/components/KeyboardPadding.android.tsx
+++ b/src/components/KeyboardControllerPadding.android.tsx
@@ -5,7 +5,7 @@ import Animated, {
useSharedValue,
} from 'react-native-reanimated'
-export function KeyboardPadding({maxHeight}: {maxHeight?: number}) {
+export function KeyboardControllerPadding({maxHeight}: {maxHeight?: number}) {
const keyboardHeight = useSharedValue(0)
useKeyboardHandler(
diff --git a/src/components/KeyboardControllerPadding.tsx b/src/components/KeyboardControllerPadding.tsx
new file mode 100644
index 0000000000..f3163d87cf
--- /dev/null
+++ b/src/components/KeyboardControllerPadding.tsx
@@ -0,0 +1,7 @@
+export function KeyboardControllerPadding({
+ maxHeight: _,
+}: {
+ maxHeight?: number
+}) {
+ return null
+}
diff --git a/src/components/KeyboardPadding.tsx b/src/components/KeyboardPadding.tsx
deleted file mode 100644
index 797d42ba0a..0000000000
--- a/src/components/KeyboardPadding.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export function KeyboardPadding({maxHeight: _}: {maxHeight?: number}) {
- return null
-}
diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx
new file mode 100644
index 0000000000..b99fe3398e
--- /dev/null
+++ b/src/components/KnownFollowers.tsx
@@ -0,0 +1,200 @@
+import React from 'react'
+import {View} from 'react-native'
+import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
+import {msg, plural, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {makeProfileLink} from '#/lib/routes/links'
+import {sanitizeDisplayName} from 'lib/strings/display-names'
+import {UserAvatar} from '#/view/com/util/UserAvatar'
+import {atoms as a, useTheme} from '#/alf'
+import {Link} from '#/components/Link'
+import {Text} from '#/components/Typography'
+
+const AVI_SIZE = 30
+const AVI_BORDER = 1
+
+/**
+ * Shared logic to determine if `KnownFollowers` should be shown.
+ *
+ * Checks the # of actual returned users instead of the `count` value, because
+ * `count` includes blocked users and `followers` does not.
+ */
+export function shouldShowKnownFollowers(
+ knownFollowers?: AppBskyActorDefs.KnownFollowers,
+) {
+ return knownFollowers && knownFollowers.followers.length > 0
+}
+
+export function KnownFollowers({
+ profile,
+ moderationOpts,
+}: {
+ profile: AppBskyActorDefs.ProfileViewDetailed
+ moderationOpts: ModerationOpts
+}) {
+ const cache = React.useRef