mirror of
https://github.com/timepill/timepill-app.git
synced 2025-05-22 11:09:13 +08:00
1. 日记本全部日记列表
2. android更新react-native-vector-icons的font文件
This commit is contained in:
parent
0b682dd777
commit
762c46eead
20 changed files with 344 additions and 49 deletions
android/app/src/main/assets/fonts
src
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -19,15 +19,21 @@ export default class DiaryBrief extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={localStyle.box}>
|
<View style={localStyle.box}>
|
||||||
<UserIcon iconUrl={user.iconUrl}></UserIcon>
|
{(user && user.iconUrl)
|
||||||
|
? <UserIcon iconUrl={user.iconUrl}></UserIcon> : null}
|
||||||
|
|
||||||
<View style={localStyle.body}>
|
<View style={localStyle.body}>
|
||||||
<View style={localStyle.title}>
|
<View style={localStyle.title}>
|
||||||
<Text style={localStyle.titleName} numberOfLines={1}>
|
{(user && user.name)
|
||||||
|
? ( <Text style={localStyle.titleName} numberOfLines={1}>
|
||||||
{user.name}
|
{user.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[localStyle.titleText, {flex: 1}]} numberOfLines={1}>
|
) : null}
|
||||||
|
{(user && user.name)
|
||||||
|
? ( <Text style={[localStyle.titleText, {flex: 1}]} numberOfLines={1}>
|
||||||
《{diary.notebook_subject}》
|
《{diary.notebook_subject}》
|
||||||
</Text>
|
</Text>
|
||||||
|
) : null}
|
||||||
<Text style={localStyle.titleText}>
|
<Text style={localStyle.titleText}>
|
||||||
{moment(diary.created).format('H:mm')}
|
{moment(diary.created).format('H:mm')}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -99,7 +105,7 @@ const localStyle = StyleSheet.create({
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: 30,
|
height: 30,
|
||||||
marginTop: 5,
|
marginTop: 10,
|
||||||
justifyContent: 'space-between'
|
justifyContent: 'space-between'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,10 +17,6 @@ export default class Notebook extends Component {
|
||||||
let notebook = this.props.notebook;
|
let notebook = this.props.notebook;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity key={notebook.id} style={localStyle.container}
|
|
||||||
activeOpacity={0.7}
|
|
||||||
onPress={() => {}}>
|
|
||||||
|
|
||||||
<View style={Api.IS_ANDROID ? localStyle.androidBox : localStyle.iosBox}>
|
<View style={Api.IS_ANDROID ? localStyle.androidBox : localStyle.iosBox}>
|
||||||
<ImageBackground key={notebook.id}
|
<ImageBackground key={notebook.id}
|
||||||
style={localStyle.cover} imageStyle={{resizeMode: 'cover'}}
|
style={localStyle.cover} imageStyle={{resizeMode: 'cover'}}
|
||||||
|
@ -40,23 +36,19 @@ export default class Notebook extends Component {
|
||||||
{notebook.created}至{notebook.expired}
|
{notebook.created}至{notebook.expired}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const localStyle = StyleSheet.create({
|
const localStyle = StyleSheet.create({
|
||||||
container: {
|
|
||||||
marginBottom: 15
|
|
||||||
},
|
|
||||||
androidBox: {
|
androidBox: {
|
||||||
width: 140,
|
width: 140,
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
margin: 3
|
margin: 3,
|
||||||
|
marginBottom: 15
|
||||||
},
|
},
|
||||||
iosBox: {
|
iosBox: {
|
||||||
width: 140,
|
width: 140,
|
||||||
|
@ -65,7 +57,8 @@ const localStyle = StyleSheet.create({
|
||||||
shadowOffset: {width: 0, height: 0},
|
shadowOffset: {width: 0, height: 0},
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
margin: 3
|
margin: 3,
|
||||||
|
marginBottom: 15
|
||||||
},
|
},
|
||||||
privateLabel: {
|
privateLabel: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|
216
src/component/notebook/notebookDiaryList.js
Normal file
216
src/component/notebook/notebookDiaryList.js
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
SectionList,
|
||||||
|
InteractionManager,
|
||||||
|
TouchableOpacity,
|
||||||
|
ActivityIndicator
|
||||||
|
} from 'react-native';
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
import Api from '../../util/api';
|
||||||
|
import Color from '../../style/color';
|
||||||
|
import Touchable from '../touchable';
|
||||||
|
|
||||||
|
import DiaryBrief from '../diary/diaryBrief';
|
||||||
|
import NotebookDiaryData from '../../dataLoader/notebookDiaryData';
|
||||||
|
|
||||||
|
|
||||||
|
export default class NotebookDiaryList extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.dataSource = new NotebookDiaryData();
|
||||||
|
this.notebook = props.notebook;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
diaries: [],
|
||||||
|
hasMore: false,
|
||||||
|
|
||||||
|
refreshing: false,
|
||||||
|
refreshFailed: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
this.refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDiaries(diaries) {
|
||||||
|
let today = moment().format('YYYY年M月D日');
|
||||||
|
let reducedDiaries = diaries.reduce((maplist, item) => {
|
||||||
|
let [year, month, day] =
|
||||||
|
item.created.substr(0, 10).split('-')
|
||||||
|
.map(it => Number(it));
|
||||||
|
|
||||||
|
let date = `${year}年${month}月${day}日`;
|
||||||
|
if(date === today) {
|
||||||
|
date = '今天';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!maplist[date]) {
|
||||||
|
maplist[date] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
maplist[date].push(item);
|
||||||
|
|
||||||
|
return maplist;
|
||||||
|
|
||||||
|
}, {});
|
||||||
|
console.log('reduce result:', reducedDiaries);
|
||||||
|
|
||||||
|
let result = [];
|
||||||
|
for(let key in reducedDiaries) {
|
||||||
|
result.push({
|
||||||
|
title: key,
|
||||||
|
data: reducedDiaries[key]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('format result:', result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(loadMore = false) {
|
||||||
|
this.setState({refreshing: true});
|
||||||
|
this.dataSource.refresh(this.notebook.id, loadMore)
|
||||||
|
.then(result => {
|
||||||
|
console.log('get notebook diaries:', result);
|
||||||
|
|
||||||
|
if(!result) {
|
||||||
|
throw {
|
||||||
|
message: 'refresh no result'
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let diaries = this.formatDiaries(result.list);
|
||||||
|
this.setState({
|
||||||
|
diaries,
|
||||||
|
hasMore: result.more,
|
||||||
|
refreshFailed: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(e => {
|
||||||
|
this.setState({
|
||||||
|
diaries: [],
|
||||||
|
hasMore: false,
|
||||||
|
refreshFailed: true
|
||||||
|
});
|
||||||
|
|
||||||
|
}).done(() => {
|
||||||
|
this.setState({
|
||||||
|
refreshing: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMore() {
|
||||||
|
if (this.state.refreshing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.notebook ? (
|
||||||
|
<View style={localStyle.container}>
|
||||||
|
<SectionList
|
||||||
|
|
||||||
|
keyExtractor={(item, index) => item.id}
|
||||||
|
|
||||||
|
sections={this.state.diaries}
|
||||||
|
|
||||||
|
renderItem={(rowData) => {
|
||||||
|
return (<Touchable onPress={() => {}}>
|
||||||
|
<DiaryBrief diary={rowData.item}></DiaryBrief>
|
||||||
|
</Touchable>);
|
||||||
|
}}
|
||||||
|
|
||||||
|
renderSectionHeader={(info) => {
|
||||||
|
return (<View style={localStyle.sectionHeader}>
|
||||||
|
<Text>{info.section.title}</Text>
|
||||||
|
</View>);
|
||||||
|
}}
|
||||||
|
|
||||||
|
ListFooterComponent={this.renderFooter.bind(this)}
|
||||||
|
|
||||||
|
automaticallyAdjustContentInsets={true}
|
||||||
|
|
||||||
|
ItemSeparatorComponent={(sectionID, rowID, adjacentRowHighlighted) =>
|
||||||
|
<View key={`${sectionID}-${rowID}`} style={localStyle.itemSeparator} />}
|
||||||
|
|
||||||
|
SectionSeparatorComponent={() => <View style={localStyle.sectionSeparator} />}
|
||||||
|
|
||||||
|
onEndReached={this.state.hasMore ? this.loadMore.bind(this) : null}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFooter() {
|
||||||
|
if (this.state.refreshing || this.state.diaries.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.refreshFailed) {
|
||||||
|
return (
|
||||||
|
<View style={localStyle.footer}>
|
||||||
|
<TouchableOpacity style={{marginTop: 15}}
|
||||||
|
onPress={() => {this.loadMore();}}>
|
||||||
|
<Text style={{color: Color.primary}}>加载失败,请点击重试</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.hasMore) {
|
||||||
|
return (
|
||||||
|
<View style={localStyle.footer}>
|
||||||
|
<Text style={{color: Color.inactiveText, fontSize: 12}}>—— THE END ——</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={localStyle.footer}>
|
||||||
|
<ActivityIndicator animating={true} color={Color.primary}
|
||||||
|
size={Api.IS_ANDROID ? 'large' : 'small'}/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const localStyle = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
backgroundColor: '#F9F9F9',
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
paddingVertical: 8,
|
||||||
|
color: Color.text
|
||||||
|
},
|
||||||
|
itemSeparator: {
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderColor: Color.line
|
||||||
|
},
|
||||||
|
sectionSeparator: {
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderColor: Color.line
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
height: 60,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingBottom: 15
|
||||||
|
}
|
||||||
|
});
|
|
@ -5,7 +5,8 @@ import {
|
||||||
Text,
|
Text,
|
||||||
FlatList,
|
FlatList,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
RefreshControl
|
RefreshControl,
|
||||||
|
TouchableOpacity
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
|
||||||
import Api from '../../util/api';
|
import Api from '../../util/api';
|
||||||
|
@ -72,11 +73,14 @@ export default class NotebookList extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderItem(notebook) {
|
_renderItem(notebook) {
|
||||||
|
|
||||||
return notebook ? (
|
return notebook ? (
|
||||||
<Notebook key={notebook.id} notebook={notebook} onPress={() => {}} />
|
<TouchableOpacity key={notebook.id} activeOpacity={0.7}
|
||||||
) : null
|
onPress={() => this.props.onNotebookPress(notebook)}>
|
||||||
|
|
||||||
|
<Notebook key={notebook.id} notebook={notebook} />
|
||||||
|
|
||||||
|
</TouchableOpacity>
|
||||||
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Api from '../util/api'
|
||||||
|
|
||||||
const PAGE_SIZE = 21;
|
const PAGE_SIZE = 21;
|
||||||
|
|
||||||
export default class FollowListData {
|
export default class FollowDiaryData {
|
||||||
|
|
||||||
list: [];
|
list: [];
|
||||||
last_id: 0;
|
last_id: 0;
|
|
@ -1,7 +1,7 @@
|
||||||
import Api from '../util/api'
|
import Api from '../util/api'
|
||||||
|
|
||||||
|
|
||||||
const PAGE_SIZE = 20;
|
const PAGE_SIZE = 21;
|
||||||
|
|
||||||
export default class FollowedByUserData {
|
export default class FollowedByUserData {
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Api from '../util/api'
|
import Api from '../util/api'
|
||||||
|
|
||||||
|
|
||||||
const PAGE_SIZE = 20;
|
const PAGE_SIZE = 21;
|
||||||
|
|
||||||
export default class FollowingUserData {
|
export default class FollowingUserData {
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Api from '../util/api'
|
||||||
|
|
||||||
const PAGE_SIZE = 21;
|
const PAGE_SIZE = 21;
|
||||||
|
|
||||||
export default class HomeListData {
|
export default class HomeDiaryData {
|
||||||
|
|
||||||
list: [];
|
list: [];
|
||||||
last_id: 0;
|
last_id: 0;
|
30
src/dataLoader/notebookDiaryData.js
Normal file
30
src/dataLoader/notebookDiaryData.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import Api from '../util/api'
|
||||||
|
|
||||||
|
|
||||||
|
const PAGE_SIZE = 21;
|
||||||
|
|
||||||
|
export default class NotebookDiaryData {
|
||||||
|
|
||||||
|
page: 1;
|
||||||
|
list: [];
|
||||||
|
|
||||||
|
async refresh(notebookId, loadMore = false) {
|
||||||
|
let page = !loadMore ? 1 : this.page + 1;
|
||||||
|
let data = await Api.getNotebookDiaries(notebookId, page, PAGE_SIZE);
|
||||||
|
let more = data.items.length === PAGE_SIZE;
|
||||||
|
|
||||||
|
if(!loadMore) {
|
||||||
|
this.page = page;
|
||||||
|
this.list = data.items.slice(0, PAGE_SIZE - 1);
|
||||||
|
|
||||||
|
} else if(data.items.length > 0) {
|
||||||
|
this.page = page;
|
||||||
|
this.list = this.list.concat(data.items.slice(0, PAGE_SIZE - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: this.list,
|
||||||
|
more
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import Api from '../util/api'
|
||||||
import {Icon} from '../style/icon'
|
import {Icon} from '../style/icon'
|
||||||
|
|
||||||
import DiaryList from '../component/diary/diaryList'
|
import DiaryList from '../component/diary/diaryList'
|
||||||
import FollowListData from '../dataLoader/followListData';
|
import FollowListData from '../dataLoader/followDiaryData';
|
||||||
|
|
||||||
|
|
||||||
export default class FollowPage extends Component {
|
export default class FollowPage extends Component {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {Navigation} from 'react-native-navigation';
|
||||||
import Api from '../util/api';
|
import Api from '../util/api';
|
||||||
|
|
||||||
import DiaryList from '../component/diary/diaryList'
|
import DiaryList from '../component/diary/diaryList'
|
||||||
import HomeListData from '../dataLoader/homeListData';
|
import HomeListData from '../dataLoader/homeDiaryData';
|
||||||
|
|
||||||
|
|
||||||
export default class HomePage extends Component {
|
export default class HomePage extends Component {
|
||||||
|
|
21
src/page/NotebookDetailPage.js
Normal file
21
src/page/NotebookDetailPage.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import {StyleSheet, Text, View, ScrollView} from 'react-native';
|
||||||
|
|
||||||
|
import Color from '../style/color';
|
||||||
|
import NotebookDiaryList from '../component/notebook/notebookDiaryList';
|
||||||
|
|
||||||
|
|
||||||
|
export default class NotebookDetailPage extends Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ScrollView style={{flex: 1}}>
|
||||||
|
<NotebookDiaryList notebook={this.props.notebook}></NotebookDiaryList>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const localStyle = StyleSheet.create({
|
||||||
|
|
||||||
|
});
|
|
@ -6,6 +6,7 @@ import {
|
||||||
TabBar,
|
TabBar,
|
||||||
SceneMap
|
SceneMap
|
||||||
} from 'react-native-tab-view';
|
} from 'react-native-tab-view';
|
||||||
|
import {Navigation} from 'react-native-navigation';
|
||||||
|
|
||||||
import Api from '../util/api';
|
import Api from '../util/api';
|
||||||
import Color from '../style/color';
|
import Color from '../style/color';
|
||||||
|
@ -27,6 +28,18 @@ export default class UserPage extends Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onNotebookPress(notebook) {
|
||||||
|
Navigation.push(this.props.componentId, {
|
||||||
|
component: {
|
||||||
|
name: 'NotebookDetail',
|
||||||
|
passProps: {
|
||||||
|
notebook: notebook
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
_renderLabel = props => ({route}) => {
|
_renderLabel = props => ({route}) => {
|
||||||
let routes = props.navigationState.routes;
|
let routes = props.navigationState.routes;
|
||||||
let index = props.navigationState.index;
|
let index = props.navigationState.index;
|
||||||
|
@ -58,7 +71,7 @@ export default class UserPage extends Component {
|
||||||
userInfo: () => <View style={localStyle.container}><Text style={localStyle.welcome}>user info !</Text></View>,
|
userInfo: () => <View style={localStyle.container}><Text style={localStyle.welcome}>user info !</Text></View>,
|
||||||
diary: () => <View style={localStyle.container}><Text style={localStyle.welcome}>diary !</Text></View>,
|
diary: () => <View style={localStyle.container}><Text style={localStyle.welcome}>diary !</Text></View>,
|
||||||
notebook: () => <NotebookList
|
notebook: () => <NotebookList
|
||||||
|
onNotebookPress={this._onNotebookPress.bind(this)}
|
||||||
navigator={this.props.navigator}
|
navigator={this.props.navigator}
|
||||||
/>
|
/>
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@ DiaryDetail: require("./DiaryDetailPage.js").default,
|
||||||
Follow: require("./FollowPage.js").default,
|
Follow: require("./FollowPage.js").default,
|
||||||
FollowUser: require("./FollowUserPage.js").default,
|
FollowUser: require("./FollowUserPage.js").default,
|
||||||
Home: require("./HomePage.js").default,
|
Home: require("./HomePage.js").default,
|
||||||
|
NotebookDetail: require("./NotebookDetailPage.js").default,
|
||||||
Notification: require("./NotificationPage.js").default,
|
Notification: require("./NotificationPage.js").default,
|
||||||
User: require("./UserPage.js").default,
|
User: require("./UserPage.js").default,
|
||||||
Write: require("./WritePage.js").default
|
Write: require("./WritePage.js").default
|
||||||
|
|
|
@ -80,6 +80,16 @@ async function getRelationReverseUsers(page, page_size) {
|
||||||
return call('GET', `/relation/reverse?page=${page}&page_size=${page_size}`);
|
return call('GET', `/relation/reverse?page=${page}&page_size=${page_size}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getNotebookDiaries(id, page, page_size) {
|
||||||
|
return call('GET', '/notebooks/' + id + '/diaries?page=' + page + '&page_size=' + page_size, null, 30000)
|
||||||
|
.then((json) => {
|
||||||
|
json.page = Number(json.page);
|
||||||
|
json.page_size = Number(json.page_size);
|
||||||
|
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async function call(method, api, body, _timeout = 10000) {
|
async function call(method, api, body, _timeout = 10000) {
|
||||||
let token = await TokenManager.getUserToken();
|
let token = await TokenManager.getUserToken();
|
||||||
|
@ -170,6 +180,7 @@ export default {
|
||||||
|
|
||||||
getTodayDiaries,
|
getTodayDiaries,
|
||||||
getFollowDiaries,
|
getFollowDiaries,
|
||||||
|
getNotebookDiaries,
|
||||||
|
|
||||||
getDiaryComments,
|
getDiaryComments,
|
||||||
getSelfNotebooks,
|
getSelfNotebooks,
|
||||||
|
|
Loading…
Reference in a new issue