iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0
Mobile Development

從零開始學React Native系列 第 12

【從零開始學React Native】11. 創建Todo Tracker——調整新建頁面

  • 分享至 

  • xImage
  •  

今天來調整設定頁面,並且創建使用者頁面。然後修改bottom下方的按鈕。

創建使用者頁面

首先我們先創建使用者的類型和狀態。

// src/types/index.ts
export type Task = {
    id: string;
    title: string;
    startDate: string | null;
    endDate: string | null;
    description: string;
    isDone: boolean;
    tags: Tags[];
    subTasks: Task[];
}

export type Tags = {
    title: string;
}

export type User = {
    id: string;
    username: string;
    email: string;
    avatar?: string;
    createdAt: string;
};


// src/stores/atoms.ts
import { atom } from 'jotai';
import { Task, User } from '../types';

export const userAtom = atom<User | null>(null);
export const tasksAtom = atom<Task[]>([]);

接著調整App下方頁面修改,修改我們的bottom和初始一個假的user

import React, { useEffect, 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';
import AddTaskPage from './src/pages/add-task.page';
import StatisticsPage from './src/pages/statistics-page';
import SettingsPage from './src/pages/settings.page';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import TaskDetailsPage from './src/pages/task-detail.page';
import { Tags, Task } from './src/types';
import { useAtom } from 'jotai';
import { userAtom } from './src/stores/atoms';
import UserPage from './src/pages/user.page';

function MainContent() {
  const [isDarkMode, setIsDarkMode] = useState(useColorScheme() === 'dark');
  const [currentPage, setCurrentPage] = useState('home');
  const [tasks, setTasks] = useState<Task[]>([]);
  const [selectedTask, setSelectedTask] = useState<Task | null>(null);
  const [user, setUser] = useAtom(userAtom);

  useEffect(() => {
    // 生成隨機任務當應用啟動時
    setTasks(generateRandomTasks(10));
    // 模擬用戶登入
    setUser({
      id: '1',
      username: 'JohnDoe',
      email: 'johndoe@example.com',
      createdAt: new Date().toISOString(),
    });
  }, []);

  const toggleDarkMode = () => {
    setIsDarkMode(!isDarkMode);
  };

  const generateRandomTasks = (count: number): Task[] => {
    const generateRandomDate = (start: Date, end: Date) => {
      return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).toISOString().split('T')[0];
    };

    const possibleTags: Tags[] = [
      { title: '工作' },
      { title: '個人' },
      { title: '學習' },
      { title: '娛樂' },
    ];

    for (let i = 0; i < count; i++) {
      const startDate = generateRandomDate(new Date(), new Date(Date.now() + 30 * 24 * 60 * 60 * 1000));
      const endDate = generateRandomDate(new Date(startDate), new Date(Date.now() + 60 * 24 * 60 * 60 * 1000));
      tasks.push({
        id: `task-${i + 1}`,
        title: `任務 ${i + 1}`,
        startDate,
        endDate,
        description: `隨機生成的第 ${i + 1} 筆內容。`,
        isDone: Math.random() < 0.3,
        tags: possibleTags.filter(() => Math.random() < 0.3),
        subTasks: [],
      });
    }
    return tasks;
  };

  const renderPage = () => {
    switch (currentPage) {
      case 'home':
        return (
          <HomePage
            isDarkMode={isDarkMode}
            tasks={tasks}
            setTasks={setTasks}
            onTaskSelect={(task) => {
              setSelectedTask(task);
              setCurrentPage('taskDetails');
            }}
          />
        );
      case 'add':
        return <AddTaskPage isDarkMode={isDarkMode} />;
      case 'stats':
        return <StatisticsPage isDarkMode={isDarkMode} tasks={tasks} />;
      case 'user':
          return <UserPage navigation={{ navigate: setCurrentPage }} />;
      case 'settings':
        return <SettingsPage isDarkMode={isDarkMode} toggleDarkMode={toggleDarkMode} navigation={undefined} />;
      case 'taskDetails':
        return selectedTask ? (
          <TaskDetailsPage
            isDarkMode={isDarkMode}
            task={selectedTask}
            onBack={() => setCurrentPage('home')}
            onTaskUpdate={(updatedTask) => {
              setTasks(prevTasks =>
                prevTasks.map(task =>
                  task.id === updatedTask.id ? updatedTask : task
                )
              );
              setCurrentPage('home');
            }}
            onTaskDelete={(taskId) => {
              setTasks(prevTasks => prevTasks.filter(task => task.id !== taskId));
              setCurrentPage('home');
            }}
          />
        ) : null;
      default:
        return <HomePage isDarkMode={isDarkMode} tasks={tasks} setTasks={setTasks} />;
    }
  };

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

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <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('user')}>
            <Text style={styles.navText}>用戶</Text>
          </TouchableOpacity>
        </View>
      </SafeAreaView>
    </GestureHandlerRootView>
  );
}

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;

我們來創建User頁面

// src\pages\user.page.tsx

import React from 'react';
import { View, Text, StyleSheet, Image, TouchableOpacity, ScrollView } from 'react-native';
import { useAtom } from 'jotai';
import { userAtom } from '../stores/atoms';

type UserPageProps = {
  navigation: any; 
};

const UserPage: React.FC<UserPageProps> = ({ navigation }) => {
  const [user] = useAtom(userAtom);

  if (!user) {
    return (
      <View style={styles.container}>
        <Text>請先登入</Text>
      </View>
    );
  }

  return (
    <ScrollView style={styles.container}>
      <View style={styles.profileHeader}>
        <Image
          source={{ uri: user.avatar || 'https://via.placeholder.com/150' }}
          style={styles.avatar}
        />
        <Text style={styles.username}>{user.username}</Text>
        <Text style={styles.email}>{user.email}</Text>
      </View>

      <View style={styles.section}>
        <Text style={styles.sectionTitle}>用戶信息</Text>
        <View style={styles.infoItem}>
          <Text style={styles.infoLabel}>ID:</Text>
          <Text style={styles.infoValue}>{user.id}</Text>
        </View>
        <View style={styles.infoItem}>
          <Text style={styles.infoLabel}>創建日期:</Text>
          <Text style={styles.infoValue}>{new Date(user.createdAt).toLocaleDateString()}</Text>
        </View>
      </View>

      <TouchableOpacity
        style={styles.settingsButton}
        onPress={() => navigation.navigate('Settings')}
      >
        <Text style={styles.settingsButtonText}>設置</Text>
      </TouchableOpacity>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  profileHeader: {
    alignItems: 'center',
    marginBottom: 20,
  },
  avatar: {
    width: 100,
    height: 100,
    borderRadius: 50,
    marginBottom: 10,
  },
  username: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 5,
  },
  email: {
    fontSize: 16,
    color: '#666',
  },
  section: {
    marginBottom: 20,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  infoItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 5,
  },
  infoLabel: {
    fontWeight: 'bold',
  },
  infoValue: {
    color: '#666',
  },
  settingsButton: {
    backgroundColor: '#007AFF',
    padding: 10,
    borderRadius: 5,
    alignItems: 'center',
  },
  settingsButtonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
});

export default UserPage;

調整設置頁面

接著,調整我們的設置頁面。

// src\pages\settings.page.tsx

import React from 'react';
import { View, Text, StyleSheet, Switch, TouchableOpacity, ScrollView } from 'react-native';
import { useAtom } from 'jotai';
import { userAtom } from '../stores/atoms';

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

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

  const handleLogout = () => {
    console.log('User logged out');
    // navigation.navigate('Login');
  };

  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>
          <Switch value={isDarkMode} onValueChange={toggleDarkMode} />
        </View>
        <View style={styles.settingItem}>
          <Text style={styles.settingLabel}>通知</Text>
          <Switch value={notifications} onValueChange={setNotifications} />
        </View>
        <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({
  // ...
});

export default SettingsPage;

心得

今天我們調整了bottom的頁面部分,並新增了user和調整setting頁面。明天接著繼續完成。


上一篇
【從零開始學React Native】10. 創建Todo Tracker——調整任務細節和統計頁面
下一篇
【從零開始學React Native】12. 關於React Native的本地存儲
系列文
從零開始學React Native20
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言