Commit 797979f5 authored by Thor-Herman's avatar Thor-Herman
Browse files

Merge with port-redux

Squashed commit of the following:

commit faf163c5
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 22:34:45 2020 +0100

    Remove unnecessary interface #2

commit dda20fef
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 22:24:21 2020 +0100

    Add axiosREST #2

commit aa3062cd
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 22:24:13 2020 +0100

    Minor Native refactor

commit 00186911
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 22:23:30 2020 +0100

    Update dependencies #1

commit 64fd9ac8
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 22:23:20 2020 +0100

    Change history to use MemoryHistory #3

    Native cannot use a DOM-router. Must use a memory-router

commit e641673f
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 22:22:39 2020 +0100

    Update expo settings #1

commit d2d751e5
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 15:06:48 2020 +0100

    Add reducers from project 3 #2

commit e8edb3b7
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 15:06:40 2020 +0100

    Fix redux type issues #2

commit 81f58ea2
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 14:52:04 2020 +0100

    Add actions from project 3 #2

commit e2331f47
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 14:51:54 2020 +0100

    And types from project 3 #2

commit eb53019d
Author: Thor-Herman <44784559+Thor-Herman@users.noreply.github.com>
Date:   Tue Nov 10 14:51:01 2020 +0100

    Add lodash #1
parent 82a67afa
import {CHANGE_ORDERING, OrderingAction, OrderingActionPayload} from "../types/ordering";
import AppThunk from "../types";
import {searchMovies, searchMovieTitles} from "./movieActions";
import {CHANGE_FILTER, FilterAction, FilterFormData} from "../types/filter";
import _ from 'lodash';
const applyFilter = (action: OrderingAction | FilterAction): AppThunk =>
(dispatch, getState) => {
dispatch(action);
return new Promise((resolve) => {
dispatch(searchMovies(true));
resolve();
})
}
export const changeOrdering = (newOrder: OrderingActionPayload) =>
applyFilter({type: CHANGE_ORDERING, payload: newOrder});
export const changeFilters = (data: FilterFormData) => {
return applyFilter({type: CHANGE_FILTER, payload: {
genres: {
..._.omit(data, ["to", "from"]) // Will return every genre and not to and from properties
},
year: {to: data.to, from: data.from}
}});
}
\ No newline at end of file
export * from './movieActions';
export * from './filterActions';
export * from './pageActions';
\ No newline at end of file
import AppThunk from '../types';
import {ADD_MOVIE, SEARCH_TITLES, Movie, MovieActionTypes, UPDATE_MOVIES, MoviePage} from "../types/movies";
import axiosREST from "../api/axiosREST";
import {updatePages} from "./pageActions";
import {decideFilters} from "./utility/decideFilters";
export const addMovie = (movie: Movie): MovieActionTypes => {
return {
type: ADD_MOVIE,
payload: movie
}
};
export const updateMovies = (movies: Array<Movie>): MovieActionTypes => {
return {
type: UPDATE_MOVIES,
payload: movies,
}
}
export const searchMovieTitles = (searchTerm: string): MovieActionTypes => {
return {
type: SEARCH_TITLES,
payload: searchTerm
}
};
export const fetchMovie = (id: number): AppThunk => {
return async (dispatch) => {
const response = await axiosREST.get<Movie>(`/movies/${id}`);
dispatch(addMovie(response.data));
};
};
export const searchMovies = (resetPages = false): AppThunk => {
return async (dispatch, getState) => {
const filtering = getState().filtering;
const searchTerm = getState().movies.searchTerm;
const currentPage = resetPages ? 1 : getState().page.current;
const filters = decideFilters(filtering);
const query = `/movies?search=${searchTerm}${filters}&page=${currentPage}`
const response = await axiosREST.get<MoviePage>(query);
dispatch(updateMovies(response.data.results));
dispatch(updatePages(response.data.count, currentPage));
}
};
\ No newline at end of file
import {CHANGE_PAGE, PAGE_SIZE, PageActions, UPDATE_TOTAL_PAGES} from "../types/page";
import AppThunk from "../types";
import {searchMovies} from "./movieActions";
export const changePage = (newCurrent: number, next: number | null, prev: number | null): PageActions => {
return {
type: CHANGE_PAGE,
payload: {current: newCurrent, next, prev}
}
};
export const updatePages = (count: number, current: number): PageActions => {
const total = Math.ceil(count / PAGE_SIZE);
return {
type: UPDATE_TOTAL_PAGES,
payload: {total, current},
}
};
export const newPage = (newCurrent: number, total: number): AppThunk => (dispatch, getState) => {
const next = newCurrent < total ? newCurrent + 1 : null;
const prev = newCurrent > 1 ? newCurrent - 1 : null;
dispatch(changePage(newCurrent, next, prev));
return new Promise((resolve) => {
dispatch(searchMovies());
resolve();
})
};
\ No newline at end of file
import { FilteringState, Genres } from '../../types/filter';
import _ from 'lodash';
export const decideFilters = ({ filter, ordering }: FilteringState) => {
let returnString = '';
if (filter.year.to && filter.year.from) {
returnString += `&year__gte=${filter.year.from}&year__lte=${filter.year.to}`;
}
const regex = decideRegex(filter.genres);
returnString += `&genre__regex=${regex}`;
if (ordering) {
const orderingPrefix = ordering.order === 'asc' ? '' : '-'; // Django needs - to sort desc
returnString += `&ordering=${orderingPrefix}${ordering.key}`;
}
return returnString;
};
const decideRegex = (filterGenres: { [key in Genres]: boolean }) => {
let regex = '(';
const checkedGenres = _.values(_.pickBy(filterGenres, (genre: boolean) => genre))
for (let i = 0; i < checkedGenres.length; i++) {
regex += checkedGenres[i].toString();
regex += i === checkedGenres.length - 1 ? '' : '|'; // Last element, don't add final |
}
regex += ')';
return regex;
};
......@@ -29,10 +29,12 @@
android:allowBackup="false"
android:theme="@style/AppTheme"
>
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="YOUR-APP-URL-HERE"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="YOUR-APP-SDK-VERSION-HERE"/>
<activity
android:name=".MainActivity"
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@thorherman/prosjekt-4" />
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="39.0.0" />
<meta-data android:name="expo.modules.updates.EXPO_RELEASE_CHANNEL" android:value="default" />
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
......
import axios from 'axios';
const prod = {
url: 'http://it2810-75.idi.ntnu.no:8000',
};
const dev = {
url: 'http://127.0.0.1:8000',
// url: 'http://localhost:8000',
};
export const config = process.env.NODE_ENV === 'development' ? dev : prod;
export default axios.create({baseURL: config.url});
......@@ -8,5 +8,12 @@
"assetBundlePatterns": [
"**/*"
]
},
"web": {
"build": {
"babel": {
"include": ["react-router-native"]
}
}
}
}
import { Route, Switch } from 'react-router';
import { Route, Switch } from 'react-router-native';
import BrowsePage from '../pages/BrowsePage';
import LandingPage from '../pages/LandingPage';
import MoviePage from '../pages/MoviePage';
import React from 'react';
import { View } from 'react-native';
const App = () => {
return (
<Switch>
<Route exact path='/' component={LandingPage} />
<Route path='/browse' component={BrowsePage} />
<Route path='/movie/:movieId' component={MoviePage} />
</Switch>
<View>
<Switch>
<Route exact path='/' component={LandingPage} />
<Route path='/browse' component={BrowsePage} />
<Route path='/movie/:movieId' component={MoviePage} />
</Switch>
</View>
);
};
......
import { createBrowserHistory } from 'history';
import { createMemoryHistory } from 'history';
export default createBrowserHistory();
\ No newline at end of file
export default createMemoryHistory();
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EXUpdatesSDKVersion</key>
<string>YOUR-APP-SDK-VERSION-HERE</string>
<key>EXUpdatesURL</key>
<string>YOUR-APP-URL-HERE</string>
</dict>
</plist>
<dict>
<key>EXUpdatesSDKVersion</key>
<string>39.0.0</string>
<key>EXUpdatesURL</key>
<string>https://exp.host/@thorherman/prosjekt-4</string>
<key>EXUpdatesReleaseChannel</key>
<string>default</string>
</dict>
</plist>
\ No newline at end of file
......@@ -12,18 +12,19 @@
"@types/history": "^4.7.8",
"@types/react-redux": "^7.1.11",
"@types/react-router": "^5.1.8",
"@types/redux": "^3.6.0",
"@types/react-router-native": "^5.1.0",
"axios": "^0.21.0",
"expo": "~39.0.2",
"expo-font": "~8.3.0",
"expo-splash-screen": "~0.6.2",
"expo-updates": "~0.3.3",
"history": "^5.0.0",
"lodash": "^4.17.20",
"native-base": "^2.13.14",
"react": "16.13.1",
"react": "^17.0.1",
"react-dom": "16.13.1",
"react-hook-form": "^6.11.0",
"react-native": "~0.63.3",
"react-native": "^0.63.3",
"react-native-gesture-handler": "~1.7.0",
"react-native-reanimated": "~1.13.0",
"react-native-screens": "~2.10.1",
......@@ -31,14 +32,17 @@
"react-native-web": "~0.13.12",
"react-redux": "^7.2.2",
"react-router": "^5.2.0",
"react-router-native": "^5.2.0",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-thunk": "^2.3.0"
},
"devDependencies": {
"@babel/core": "~7.9.0",
"@types/react": "~16.9.35",
"@types/lodash": "^4.14.165",
"@types/react": "^16.9.56",
"@types/react-dom": "~16.9.8",
"@types/react-native": "~0.63.2",
"@types/react-native": "^0.63.33",
"babel-preset-expo": "~8.3.0",
"jest-expo": "~39.0.0",
"typescript": "~3.9.5"
......
import {combineReducers} from "redux";
import {CHANGE_FILTER, CLEAR_FILTER, FilterAction, FilterState} from "../types/filter";
import {CHANGE_ORDERING, CLEAR_ORDERING, OrderingAction, OrderingState} from "../types/ordering";
const initialFilterState = {
genres: {
Action: false,
Romance: false,
Horror: false,
Comedy: false,
Musical: false,
Drama: false,
},
year: {to: "", from: ""}
}
const filter = (state: FilterState = initialFilterState, action: FilterAction): FilterState => {
switch (action.type) {
case CHANGE_FILTER:
return {...action.payload}
case CLEAR_FILTER:
return {...initialFilterState};
default:
return state;
}
};
const ordering = (state: OrderingState = {key: "title", order: "asc"},
action: OrderingAction): OrderingState => {
switch (action.type) {
case CHANGE_ORDERING:
return {...state, ...action.payload};
case CLEAR_ORDERING:
return {key: "", order: "asc"};
default:
return state;
}
};
export default combineReducers({
filter,
ordering,
});
\ No newline at end of file
import { combineReducers } from "redux";
import {combineReducers} from "redux";
import movies from './movieReducer';
import filtering from './filterReducer';
import page from './pageReducer';
const dummyReducer = (type: string, action: any) => {
return "State";
}
const rootReducer = combineReducers(
{movies, filtering, page}
);
export default combineReducers({
state: dummyReducer,
});
\ No newline at end of file
export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;
\ No newline at end of file
import {combineReducers} from "redux";
import {ADD_MOVIE, MovieActionTypes, SEARCH_TITLES, UPDATE_MOVIES} from '../types/movies';
import _ from 'lodash';
import { Movie } from "../types/movies";
type byIdState = {
[key: number]: Movie
}
type allIdsState = Array<Movie>;
const byId = (state: byIdState = {}, action: MovieActionTypes) => {
switch (action.type) {
case ADD_MOVIE:
return {...state, [action.payload.id]: action.payload};
case UPDATE_MOVIES:
return action.payload;
default:
return state;
}
};
const allIds = (state: allIdsState = [], action: MovieActionTypes) => {
switch (action.type) {
case ADD_MOVIE:
return [...state, action.payload.id];
case UPDATE_MOVIES:
return action.payload.map(movie => movie.id);
default:
return state;
}
};
const searchTerm = (state = "", action: MovieActionTypes) => {
switch (action.type){
case SEARCH_TITLES:
return action.payload
default:
return state;
}
}
export default combineReducers({
byId,
allIds,
searchTerm,
})
import {CHANGE_PAGE, PAGE_SIZE, ChangePageAction, PageState, UPDATE_TOTAL_PAGES, PageActions} from "../types/page";
const initialPageState = {
total: 1,
current: 1,
next: null,
prev: null,
}
const page = (state: PageState = initialPageState, action: PageActions) => {
switch(action.type) {
case CHANGE_PAGE:
case UPDATE_TOTAL_PAGES:
return {...state, ...action.payload}
default:
return state;
}
};
export default page;
\ No newline at end of file
import { compose, createStore, applyMiddleware } from 'redux';
import reducers from '../reducers';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
}
}
import {composeWithDevTools} from 'redux-devtools-extension';
const configureStore = () => {
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
return createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
return createStore(reducers, composeWithDevTools(applyMiddleware(thunk)));
};
export default configureStore;
\ No newline at end of file
import {OrderingState} from "./ordering";
export const CHANGE_FILTER = "CHANGE_FILTER";
export const CLEAR_FILTER = "CLEAR_FILTER";
export type FilterKeys = "" | "length" | "genre" | "year";
export type FilterState = {
genres: {[key in Genres]: boolean}
year: { to: string, from: string, },
}
export type FilteringState = {
filter: FilterState,
ordering: OrderingState,
}
export type FilterAction = { type: string, payload: FilterState };
export type FilterFormData = {
[key in Genres]: boolean;
} & {
from: string;
to: string;
};
export type Genres = "Action" | "Comedy" | "Musical" | "Romance" | "Drama" | "Horror";
import {Action} from "redux";
import {RootState} from "../reducers";
import {ThunkAction} from "redux-thunk";
type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>
export default AppThunk;
\ No newline at end of file
export const LOG_IN = "LOG_IN";
export const LOG_OUT = "LOG_OUT";
export const ADD_MOVIE = 'ADD_MOVIE';
export const UPDATE_MOVIES = 'UPDATE_MOVIES';
export const SEARCH_TITLES = 'SEARCH_TITLES';
export type Movie = {
id: number,
title: string,
genre: string,
year: number,
length: number,
description: string,
image: string,
}
export type MoviePage = {
count: number,
next: string | null,
previous: string | null,
results: Movie[],
}
type FetchMovieAction = {
type: typeof ADD_MOVIE,
payload: Movie
}
type UpdateMoviesAction = {
type: typeof UPDATE_MOVIES,
payload: Array<Movie>
}
type SearchTitlesAction = {
type: typeof SEARCH_TITLES;
payload: Record<Movie['id'], Movie['title']>
}
export type MovieActionTypes = FetchMovieAction | SearchTitlesAction | UpdateMoviesAction;
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment