Skip to content

Commit

Permalink
Merge pull request #363 from TheStanfordDaily/dev
Browse files Browse the repository at this point in the history
Analytics, loading and navigation
  • Loading branch information
MatthewTurk247 authored Oct 4, 2022
2 parents 03c54b3 + 0a7f5fa commit c885e30
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 117 deletions.
97 changes: 74 additions & 23 deletions App.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import * as Notifications from "expo-notifications"
import { initializeApp } from "firebase/app"
import { getDatabase, ref, push, set } from "firebase/database"
import { getAuth, signInWithEmailAndPassword } from "firebase/auth"
import { APIKEY, MESSAGING_SENDER_ID, APP_ID, MEASUREMENT_ID, FIREBASE_PASSWORD, SERVICE_ACCOUNT_ID } from "@env"
import { Strings } from "./constants"
import * as eva from "@eva-design/eva"
import { ApplicationProvider, Icon, IconRegistry, Text } from "@ui-kitten/components"
Expand Down Expand Up @@ -36,15 +35,15 @@ Notifications.setNotificationHandler({
})

const firebaseConfig = {
apiKey: APIKEY,
apiKey: process.env.APIKEY,
authDomain: "daily-mobile-app-notifications.firebaseapp.com",
databaseURL: "https://daily-mobile-app-notifications-default-rtdb.firebaseio.com",
projectId: "daily-mobile-app-notifications",
storageBucket: "daily-mobile-app-notifications.appspot.com",
messagingSenderId: MESSAGING_SENDER_ID,
appId: APP_ID,
measurementId: MEASUREMENT_ID,
serviceAccountId: SERVICE_ACCOUNT_ID
messagingSenderId: process.env.MESSAGING_SENDER_ID,
appId: process.env.APP_ID,
measurementId: process.env.MEASUREMENT_ID,
serviceAccountId: process.env.SERVICE_ACCOUNT_ID
}

const Stack = createStackNavigator()
Expand All @@ -58,6 +57,7 @@ export default function App() {
const colorScheme = Appearance.getColorScheme()
const [theme, setTheme] = useState(colorScheme)
const [deviceType, setDeviceType] = useState(Device.DeviceType.PHONE)
const [seen, setSeen] = useState(new Set())

const toggleTheme = () => {
const next = theme === "light" ? "dark" : "light"
Expand Down Expand Up @@ -129,31 +129,50 @@ export default function App() {
}
}

const sectionOptions = ({ route }) => ({
headerTitle: () => <Text category="h4">{decode(route.params.category.name).replace('\'', '\u{2019}')}</Text>,
headerTitleStyle: { fontFamily: "MinionProBold" },
headerTintColor: bread[theme]["color-primary-500"]
})

const authorOptions = ({ route }) => ({
headerTitle: () => <Text category="h4">{route.params.name}</Text>,
headerTitleStyle: { fontFamily: "MinionProBold" },
headerTintColor: bread[theme]["color-primary-500"]
})

const searchHeaderOptions = {
headerTintColor: bread[theme]["color-primary-500"]
}

var events = []
let app
let auth
let db
if (Object.keys(firebaseConfig).length > 0) {
app = initializeApp(firebaseConfig)
auth = getAuth(app)
db = getDatabase(app)
}

useEffect(() => {
// Loads fonts from static resource.
Font.loadAsync(minion).then(() => setFontsLoaded(true))
registerForPushNotificationsAsync().then(token => {
setExpoPushToken(token)
if (Object.keys(firebaseConfig).length > 0) {
const app = initializeApp(firebaseConfig)
const db = getDatabase(app)
var matches = expoPushToken.match(/\[(.*?)\]/)
if (matches) {

if (Object.keys(firebaseConfig).length > 0) {
registerForPushNotificationsAsync().then(token => {
setExpoPushToken(token)
var matches = token?.match(/\[(.*?)\]/)

if (matches) {
var submatch = matches[1]
const auth = getAuth(app)
signInWithEmailAndPassword(auth, "[email protected]", FIREBASE_PASSWORD).then((userCredential) => {
signInWithEmailAndPassword(auth, "[email protected]", process.env.FIREBASE_PASSWORD).then((userCredential) => {
const tokenRef = ref(db, "ExpoPushTokens/" + submatch, userCredential)
set(tokenRef, Date())
}).catch((error) => {
console.log("Could not sign in: ", error)
})
}
}
})
})
}

Device.getDeviceTypeAsync().then(setDeviceType)

Expand All @@ -169,8 +188,9 @@ export default function App() {
setNotification(notification)
})

// This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded or killed).
// This listener is fired whenever a user taps on or interacts with a notification.
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
// Works when app is foregrounded, backgrounded or killed.
Model.posts().id(response.notification.request.trigger.payload.body.postID).embed().then((result) => {
navigate(Strings.post, { item: result })
})
Expand All @@ -184,7 +204,16 @@ export default function App() {


return fontsLoaded && (
<NavigationContainer theme={navigatorTheme[theme]}>
<NavigationContainer onStateChange={e => {
const name = getActiveRouteName(e)
if (!(name in seen)) {
signInWithEmailAndPassword(auth, "[email protected]", process.env.FIREBASE_PASSWORD).then((userCredential) => {
const analyticsRef = ref(db, "Analytics/", userCredential)
push(analyticsRef, JSON.stringify(e)) // Still figuring out how to format as an object in Firebase.
})
events.push(e)
}
}} theme={navigatorTheme[theme]}>
<IconRegistry icons={EvaIconsPack} />
<ThemeContext.Provider value={{ theme, toggleTheme, deviceType }}>
<ApplicationProvider {...eva} theme={{...eva[theme], ...bread[theme]}} customMapping={mapping}>
Expand All @@ -204,12 +233,12 @@ export default function App() {
<Stack.Screen
name="Section"
component={Section}
options={({ route }) => ({ headerTitle: () => <Text category="h4">{decode(route.params.category.name).replace('\'', '\u{2019}')}</Text>, headerTitleStyle: { fontFamily: "MinionProBold" }, headerTintColor: bread[theme]["color-primary-500"] })}
options={sectionOptions}
/>
<Stack.Screen
name="Author"
component={Author}
options={({ route }) => ({ headerTitle: () => <Text category="h4">{route.params.name}</Text>, headerTitleStyle: { fontFamily: "MinionProBold" }, headerTintColor: bread[theme]["color-primary-500"] })}
options={authorOptions}
/>
<Stack.Screen
name="Search"
Expand Down Expand Up @@ -253,3 +282,25 @@ async function registerForPushNotificationsAsync() {

return token
}

function getActiveRouteName(navigationState) {
if (!navigationState) return null;
const route = navigationState.routes[navigationState.index];
// Traverse the nested navigators.
if (route.routes) return getActiveRouteName(route)

var out = route.key + "-"
if (route.params?.id) {
out += route.params?.id
} else if (route.params?.article?.id) {
out += route.params?.article?.id
} else if (route.params?.category?.id) {
out += route.params?.category?.id
} else if (route.params?.name) {
out += route.params?.name
} else {
out = route.key
}

return out
}
5 changes: 4 additions & 1 deletion app.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
},
"notification": {
"icon": "./assets/images/icon.png"
}
},
"plugins": [
"expo-build-properties"
]
}
}
2 changes: 1 addition & 1 deletion components/Shelf.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function Shelf(props) {
return (
<PagerView style={[styles.container, { backgroundColor: inactiveColor }]} initialPage={0} scrollEnabled={shelfArticles?.length > 3*groupSize} overdrag>
{_.chunk(shelfArticles, 3*groupSize).map((triplet, index) => (
<View style={{ flex: 1, flexDirection: "row" }}>
<View key={index} style={{ flex: 1, flexDirection: "row" }}>
{_.chunk(triplet, 3).map((group, outerIndex) => (<View collapsable={false} style={{ flex: 1, flexDirection: "column" }} key={outerIndex}>
{group.map((item) => (
<React.Fragment>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"expo": "^45.0.0",
"expo-av": "~11.2.3",
"expo-blur": "~11.1.0",
"expo-build-properties": "~0.2.0",
"expo-constants": "~13.1.1",
"expo-crypto": "~10.2.0",
"expo-dev-client": "~1.0.1",
Expand Down Expand Up @@ -51,7 +52,6 @@
"react-native-render-html": "^6.3.3",
"react-native-safe-area-context": "4.2.4",
"react-native-screens": "~3.11.1",
"react-native-skeleton-content": "^1.0.24",
"react-native-snap-carousel": "^3.9.1",
"react-native-svg": "12.3.0",
"react-native-track-player": "^3.1.0",
Expand Down
4 changes: 2 additions & 2 deletions screens/Author.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function Author({ route, navigation }) {
const [authorDetail, setAuthorDetail] = useState(null)
const [possiblyReachedEnd, setPossiblyReachedEnd] = useState(false)
const { deviceType } = useContext(ThemeContext)
const groupSize = deviceType === DeviceType.PHONE ? 2 : 3
const groupSize = deviceType === DeviceType.PHONE ? 2 : 3

useEffect(() => {
setArticlesLoading(true)
Expand All @@ -46,7 +46,7 @@ export default function Author({ route, navigation }) {
setArticlesLoading(false)

// FIXME: Add clean-up function.
// Not all of the asynchronous tasks are being canceled, leadinfg to memory leaks.
// Not all of the asynchronous tasks are being canceled, leading to memory leaks.
}, [pageNumber])


Expand Down
16 changes: 13 additions & 3 deletions screens/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function Home({ navigation }) {
setSeeds(articles => ({
...articles,
[value.slug]: (value.slug in articles && appendItems) ? [...articles[value.slug], ...homeSeeds] : homeSeeds
}))
}))
}

const cultureMembers = _.shuffle(posts.filter(items => items.categories.includes(Sections.THE_GRIND.id) || items.categories.includes(Sections.ARTS_LIFE.id))).slice(0, 4)
Expand All @@ -88,6 +88,7 @@ export default function Home({ navigation }) {

// Retrieve second batch.
// If an infrequent section is empty after initial call, retrieve more for those categories.
// TODO: Add humor and logical conditions.

RSVP.hash({
posts: Model.posts().perPage(batchSize).page(2),
Expand All @@ -111,7 +112,7 @@ export default function Home({ navigation }) {
}, [pageNumber]) // Runs once at the beginning, and anytime pageNumber changes thereafter.


return layoutLoaded && (
return layoutLoaded ? (
<Layout style={styles.container}>
<ScrollView onScroll={checkBottom} scrollEventThrottle={0}>
<Carousel articles={articles[Sections.FEATURED.slug]} navigation={navigation} />
Expand Down Expand Up @@ -139,7 +140,7 @@ export default function Home({ navigation }) {
{/* <Canvas articles={articles[Sections.CARTOONS.slug]} /> */}
<Divider />
{_.chunk(articles.wildcard, groupSize)?.map((group, outerIndex) => (
<View>
<View key={outerIndex}>
<View style={{ flex: 1/groupSize, flexDirection: "row" }}>
{group.map((item, index) => <Wildcard item={item} index={outerIndex*index + index} key={item.id.toString()} navigation={navigation} verbose />)}
</View>
Expand All @@ -148,11 +149,20 @@ export default function Home({ navigation }) {
))}
</ScrollView>
</Layout>
) : (
<Layout style={styles.loading}>
<ActivityIndicator />
</Layout>
)
}

const styles = StyleSheet.create({
container: {
flex: 1
},
loading: {
flex: 1,
justifyContent: "center",
alignItems: "center",
}
})
25 changes: 22 additions & 3 deletions screens/Post.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useContext, useEffect, useState } from "react"
import { ActivityIndicator, Appearance, Dimensions, LayoutAnimation, PixelRatio, Platform, StatusBar, StyleSheet, useColorScheme, View, UIManager } from "react-native"
import { ActivityIndicator, Appearance, Dimensions, LayoutAnimation, PixelRatio, Platform, StatusBar, StyleSheet, useColorScheme, View, UIManager, Linking } from "react-native"
import { Icon, Text, useTheme } from "@ui-kitten/components"
import { ImageHeaderScrollView, TriggeringView } from "react-native-image-header-scroll-view"
import { Spacing } from "../constants"
Expand Down Expand Up @@ -40,6 +40,24 @@ export default function Post({ route, navigation }) {
const headerHeight = useHeaderHeight()
const contentEdgeInset = deviceType === Device.DeviceType.PHONE ? 14 : 56

const openArticleIfPresent = (url) => {
const pruned = url.slice(-1) === "/" ? url.slice(0, -1) : url
const preSlug = pruned.split("/")
const slug = (preSlug[preSlug.length - 1])

// Hopefully this doesn't take too long to load. Might have to preload.

if (url.match(/stanforddaily.com\/\d{4}\/\d{2}\/\d{2}\/(.*)/)) {
Model.posts().slug(slug).embed().then(result => {
if (result.length > 0) {
navigation.push("Post", { article: result[0], sourceName: "Stanford Daily" })
}
}).catch(error => console.trace(error))
} else {
Linking.openURL(url)
}
}

const renderers = {
// Note: Chrome URL protocol causes a crash with the renderer below.
// iframe: IframeRenderer,
Expand Down Expand Up @@ -74,8 +92,8 @@ export default function Post({ route, navigation }) {
setCaption(decode(media.caption?.rendered).slice(3, -5))
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
})


return () => {
if (colorScheme === "light") {
StatusBar.setBarStyle("dark-content", true)
Expand Down Expand Up @@ -115,6 +133,7 @@ export default function Post({ route, navigation }) {
baseStyle={{ fontFamily: "MinionProRegular", fontSize: 20*fontScale, color: theme["text-basic-color"], backgroundColor: theme["background-basic-color-1"] }}
tagsStyles={{ a: { color: theme["color-primary-500"], textDecorationLine: "none" } }} // The font color is slightly off in Dark Mode.
renderers={renderers}
renderersProps={{ a: { onPress: (e, href) => openArticleIfPresent(href) } }}
WebView={WebView}
backgroundColor={theme["background-color-basic-2"]}
enableExperimentalMarginCollapsing
Expand Down
Loading

0 comments on commit c885e30

Please sign in to comment.