iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0
Mobile Development

從零開始學React Native系列 第 8

【從零開始學React Native】7. 創建Todo Tracker——創建其他頁面

  • 分享至 

  • xImage
  •  

今天我們來創建其他的頁面。任務新增頁、圖表頁、設定頁、任務詳情頁。這裡會使用一些套件來進行修改調整。

創建頁面

安裝'react-native-safe-area-context@wuba/react-native-echarts echarts @shopify/react-native-skia react-native-svg

修改一下我們目前的App.tsx:

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, StatusBar, useColorScheme } from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { Colors } from 'react-native/Libraries/NewAppScreen';
import HomePage from './src/pages/home.page';

function MainContent() {
  const isDarkMode = useColorScheme() === 'dark';
  const [currentPage, setCurrentPage] = useState('home');

  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
    flex: 1,
  };

  const renderPage = () => {
    switch (currentPage) {
      case 'home':
        return <HomePage isDarkMode={isDarkMode} />;
      case 'add':
        return <Text>新增任務頁面</Text>;
      case 'stats':
        return <Text>統計頁面</Text>;
      case 'settings':
        return <Text>設置頁面</Text>;
      default:
        return <HomePage isDarkMode={isDarkMode} />;
    }
  };

  return (
    <SafeAreaView style={backgroundStyle}>
      <StatusBar
        barStyle={isDarkMode ? 'light-content' : 'dark-content'}
        backgroundColor={backgroundStyle.backgroundColor}
      />
      <View style={styles.topArea}>
        <Text style={styles.topText}>我的應用</Text>
      </View>
      <View style={styles.container}>
        {renderPage()}
      </View>
      <View style={styles.bottomNav}>
        <TouchableOpacity style={styles.navItem} onPress={() => setCurrentPage('home')}>
          <Text style={styles.navText}>主頁</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem} onPress={() => setCurrentPage('add')}>
          <Text style={styles.navText}>新增</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem} onPress={() => setCurrentPage('stats')}>
          <Text style={styles.navText}>統計</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem} onPress={() => setCurrentPage('settings')}>
          <Text style={styles.navText}>設置</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
}

function App(): React.JSX.Element {
  return (
    <SafeAreaProvider>
      <MainContent />
    </SafeAreaProvider>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  topArea: {
    height: 50,
    justifyContent: 'center',
    alignItems: 'center',
    borderBottomWidth: 1,
    borderBottomColor: '#ccc',
    backgroundColor: '#f8f8f8',
  },
  topText: {
    fontSize: 18,
    fontWeight: 'bold',
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    alignItems: 'center',
    height: 50,
    borderTopWidth: 1,
    borderTopColor: '#ccc',
    backgroundColor: '#f8f8f8',
  },
  navItem: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    height: '100%',
  },
  navText: {
    fontSize: 12,
  },
});

export default App;

src\pages\home.page.tsx:

import React, { PropsWithChildren } from 'react';
import { View, Text, TextInput, FlatList, StyleSheet } from 'react-native';
import { Colors } from 'react-native/Libraries/NewAppScreen';


const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 10,
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  searchBar: {
    height: 40,
    borderColor: 'gray',
    borderWidth: 1,
    borderRadius: 5,
    paddingHorizontal: 10,
    marginBottom: 10,
  },
  taskList: {
    flex: 1,
  },
  taskItem: {
    padding: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#ccc',
  },
});

type Task = {
  id: string;
  title: string;
}

type HomePageProps = PropsWithChildren<{
  isDarkMode?: boolean;
}>;

const HomePage = ({children, isDarkMode = false}: HomePageProps) => {
  const tasks: Task[] = [
    { id: '1', title: 'Task 1' },
    { id: '2', title: 'Task 2' },
    { id: '3', title: 'Task 3' },
  ];

  const renderTask = ({ item }: { item: Task }) => (
    <View style={styles.taskItem}>
      <Text>{item.title}</Text>
    </View>
  );

  return (
    <View style={styles.container}>
      <Text style={styles.title}>主畫面 (任務列表)</Text>
      <TextInput
        style={styles.searchBar}
        placeholder="搜索欄"
        placeholderTextColor={isDarkMode ? Colors.light : Colors.dark}
      />
      <FlatList
        style={styles.taskList}
        data={tasks}
        renderItem={renderTask}
        keyExtractor={item => item.id}
      />
    </View>
  );
};

export default HomePage;

src\pages\add-task.page.tsx:

import React, { useState } from 'react';
import { Text, TextInput, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';

type AddTaskPageProps = {
  isDarkMode: boolean;
};

const AddTaskPage: React.FC<AddTaskPageProps> = ({ isDarkMode }) => {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [dueDate, setDueDate] = useState(new Date());
  const [priority, setPriority] = useState('');
  const [reminder, setReminder] = useState('');
  const [tags, setTags] = useState('');

  const handleSubmit = () => {
    // Handle task submission logic here
    console.log({ title, description, dueDate, priority, reminder, tags });
  };

  return (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>新增任務畫面</Text>

      <TextInput
        style={styles.input}
        placeholder="任務標題"
        value={title}
        onChangeText={setTitle}
      />

      <TextInput
        style={[styles.input, styles.multilineInput]}
        placeholder="任務描述"
        multiline
        numberOfLines={3}
        value={description}
        onChangeText={setDescription}
      />
      <Text style={styles.label}>到期日期</Text>

      <TextInput
        style={styles.input}
        placeholder="優先級"
        value={priority}
        onChangeText={setPriority}
      />

      <TextInput
        style={styles.input}
        placeholder="提醒設置"
        value={reminder}
        onChangeText={setReminder}
      />

      <TextInput
        style={styles.input}
        placeholder="標籤"
        value={tags}
        onChangeText={setTags}
      />

      <TouchableOpacity style={styles.submitButton} onPress={handleSubmit}>
        <Text style={styles.submitButtonText}>新增任務</Text>
      </TouchableOpacity>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 10,
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  input: {
    height: 40,
    borderColor: 'gray',
    borderWidth: 1,
    borderRadius: 5,
    paddingHorizontal: 10,
    marginBottom: 10,
  },
  multilineInput: {
    height: 80,
  },
  label: {
    fontSize: 16,
    marginBottom: 5,
  },
  submitButton: {
    backgroundColor: '#007AFF',
    padding: 10,
    borderRadius: 5,
    alignItems: 'center',
    marginTop: 10,
  },
  submitButtonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
});

export default AddTaskPage;

src\pages\task-detail.page.tsx:

import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Switch } from 'react-native';

type TaskDetailsPageProps = {
  isDarkMode: boolean;
  task: {
    id: string;
    title: string;
    description: string;
    dueDate: Date;
    priority: string;
    isCompleted: boolean;
  };
};

const TaskDetailsPage: React.FC<TaskDetailsPageProps> = ({ isDarkMode, task }) => {
  const [isCompleted, setIsCompleted] = useState(task.isCompleted);

  const handleEdit = () => {
    // Handle edit logic
    console.log('Edit task:', task.id);
  };

  const handleDelete = () => {
    // Handle delete logic
    console.log('Delete task:', task.id);
  };

  const toggleCompletion = () => {
    setIsCompleted(!isCompleted);
    // Update task completion status in the backend
    console.log('Task completion toggled:', task.id, !isCompleted);
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{task.title}</Text>
      <Text style={styles.description}>{task.description}</Text>
      <Text style={styles.info}>到期日期: {task.dueDate.toLocaleDateString()}</Text>
      <Text style={styles.info}>優先級: {task.priority}</Text>

      <View style={styles.actionContainer}>
        <TouchableOpacity style={styles.actionButton} onPress={handleEdit}>
          <Text style={styles.actionButtonText}>編輯</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.actionButton, styles.deleteButton]} onPress={handleDelete}>
          <Text style={styles.actionButtonText}>刪除</Text>
        </TouchableOpacity>
      </View>
      
      <View style={styles.completionContainer}>
        <Text style={styles.completionText}>任務完成</Text>
        <Switch
          value={isCompleted}
          onValueChange={toggleCompletion}
        />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 10,
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  description: {
    fontSize: 16,
    marginBottom: 10,
  },
  info: {
    fontSize: 14,
    marginBottom: 5,
  },
  actionContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginTop: 20,
  },
  actionButton: {
    backgroundColor: '#007AFF',
    padding: 10,
    borderRadius: 5,
    flex: 1,
    marginRight: 10,
  },
  deleteButton: {
    backgroundColor: '#FF3B30',
    marginRight: 0,
  },
  actionButtonText: {
    color: 'white',
    textAlign: 'center',
    fontWeight: 'bold',
  },
  completionContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginTop: 20,
  },
  completionText: {
    fontSize: 16,
  },
});

export default TaskDetailsPage;

src\pages\statistics-page.tsx:

import React, { useRef, useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import * as echarts from 'echarts/core';
import { SkiaChart, SVGRenderer } from '@wuba/react-native-echarts';
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';

echarts.use([SVGRenderer, LineChart, GridComponent]);

type StatisticsPageProps = {
  isDarkMode: boolean;
};

const StatisticsPage: React.FC<StatisticsPageProps> = ({ isDarkMode }) => {
  const skiaRef = useRef<any>(null);

  useEffect(() => {
    const option = {
      xAxis: {
        type: 'category',
        data: ['一月', '二月', '三月', '四月', '五月', '六月'],
      },
      yAxis: {
        type: 'value',
        min: 0,
        max: 100,
      },
      series: [
        {
          name: '完成率',
          data: [60, 70, 65, 80, 75, 90],
          type: 'line',
          smooth: true,
        },
      ],
      tooltip: {
        trigger: 'axis',
      },
      legend: {
        data: ['完成率'],
      },
    };

    let chart: any;
    if (skiaRef.current) {
      chart = echarts.init(skiaRef.current, isDarkMode ? 'dark' : 'light', {
        renderer: 'svg',
        width: 350,
        height: 300,
      });
      chart.setOption(option);
    }

    return () => chart?.dispose();
  }, [isDarkMode]);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>統計畫面</Text>

      <View style={styles.chartContainer}>
        <Text style={styles.chartTitle}>任務完成率趨勢</Text>
        <SkiaChart ref={skiaRef} />
      </View>

    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 10,
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  chartContainer: {
    marginBottom: 20,
  },
  chartTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 5,
  },
});

export default StatisticsPage;

src\pages\settings.page.tsx:

import React, { useState } from 'react';
import { View, Text, StyleSheet, Switch, TouchableOpacity, ScrollView } from 'react-native';

type SettingsPageProps = {
  isDarkMode: boolean;
  toggleDarkMode: () => void;
};

const SettingsPage: React.FC<SettingsPageProps> = ({ isDarkMode, toggleDarkMode }) => {
  const [notifications, setNotifications] = useState(true);
  const [autoSync, setAutoSync] = useState(true);

  const handleLogout = () => {
    // Handle logout logic
    console.log('User logged out');
  };

  return (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>用戶設置畫面</Text>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>用戶信息</Text>
        <View style={styles.settingItem}>
          <Text style={styles.settingLabel}>用戶名稱</Text>
          <Text style={styles.settingValue}>John Doe</Text>
        </View>
        <View style={styles.settingItem}>
          <Text style={styles.settingLabel}>電子郵件</Text>
          <Text style={styles.settingValue}>johndoe@example.com</Text>
        </View>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>應用設置</Text>
        <View style={styles.settingItem}>
          <Text style={styles.settingLabel}>深色模式</Text>
          <Switch value={isDarkMode} onValueChange={toggleDarkMode} />
        </View>
        <View style={styles.settingItem}>
          <Text style={styles.settingLabel}>通知</Text>
          <Switch value={notifications} onValueChange={setNotifications} />
        </View>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>同步設置</Text>
        <View style={styles.settingItem}>
          <Text style={styles.settingLabel}>自動同步</Text>
          <Switch value={autoSync} onValueChange={setAutoSync} />
        </View>
      </View>
      
      <TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
        <Text style={styles.logoutButtonText}>登出</Text>
      </TouchableOpacity>
      
      <View style={styles.aboutSection}>
        <Text style={styles.sectionTitle}>關於應用</Text>
        <Text style={styles.aboutText}>版本: 1.0.0</Text>
        <Text style={styles.aboutText}>開發者: Your Company Name</Text>
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 10,
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  section: {
    marginBottom: 20,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  settingItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingVertical: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#ccc',
  },
  settingLabel: {
    fontSize: 14,
  },
  settingValue: {
    fontSize: 14,
    color: '#666',
  },
  logoutButton: {
    backgroundColor: '#FF3B30',
    padding: 10,
    borderRadius: 5,
    alignItems: 'center',
    marginTop: 20,
  },
  logoutButtonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
  aboutSection: {
    marginTop: 20,
  },
  aboutText: {
    fontSize: 14,
    color: '#666',
    marginBottom: 5,
  },
});

export default SettingsPage;

這是目前暫時設計的pages,之後會一個一個修改並調整。

https://ithelp.ithome.com.tw/upload/images/20240922/20108931Y4f9VZrZdu.png

https://ithelp.ithome.com.tw/upload/images/20240922/2010893111mD0txY5j.png

https://ithelp.ithome.com.tw/upload/images/20240922/20108931HH7YadXa6t.png

https://ithelp.ithome.com.tw/upload/images/20240922/20108931mE8YougqNL.png

心得

發現使用一些套件安裝後,總會出現一些問題。目前的作法是將模擬器的app關掉,然後整個重啟重新跑指令這樣,忽然就好了。


上一篇
【從零開始學React Native】6. 創建Todo Tracker——創建頁面
下一篇
【從零開始學React Native】8. 創建Todo Tracker——改進首頁頁面
系列文
從零開始學React Native14
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言